gitnexus 1.6.10-rc.45 → 1.6.10-rc.47
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/dist/_shared/scope-resolution/types.d.ts +14 -2
- package/dist/_shared/scope-resolution/types.d.ts.map +1 -1
- package/dist/core/ingestion/class-extractors/configs/jvm.js +24 -0
- package/dist/core/ingestion/languages/java/captures.js +78 -1
- package/dist/core/ingestion/languages/java/query.js +8 -0
- package/dist/core/ingestion/languages/java/scope-resolver.js +6 -0
- package/dist/core/ingestion/languages/javascript/query.js +7 -0
- package/dist/core/ingestion/languages/kotlin/query.js +6 -0
- package/dist/core/ingestion/languages/typescript/query.js +17 -0
- package/dist/core/ingestion/scope-extractor.js +2 -0
- package/dist/core/ingestion/scope-resolution/contract/scope-resolver.d.ts +17 -0
- package/dist/core/ingestion/scope-resolution/passes/free-call-fallback.d.ts +9 -0
- package/dist/core/ingestion/scope-resolution/passes/free-call-fallback.js +95 -1
- package/dist/core/ingestion/scope-resolution/pipeline/run.js +2 -0
- package/dist/core/ingestion/scope-resolution/resolution-outcome.d.ts +1 -1
- package/dist/core/ingestion/scope-resolution/scope/walkers.js +63 -44
- package/dist/core/ingestion/tree-sitter-queries.d.ts +1 -1
- package/dist/core/ingestion/tree-sitter-queries.js +4 -0
- package/dist/core/ingestion/type-extractors/jvm.js +17 -2
- package/dist/core/ingestion/utils/ast-helpers.d.ts +1 -0
- package/dist/core/ingestion/utils/ast-helpers.js +99 -1
- package/dist/core/run-analyze.js +21 -9
- package/dist/core/search/fts-indexes.d.ts +15 -0
- package/dist/core/search/fts-indexes.js +23 -0
- package/dist/storage/parse-cache.js +1 -1
- package/dist/storage/repo-manager.d.ts +8 -2
- package/dist/storage/repo-manager.js +7 -1
- package/package.json +1 -1
|
@@ -27,8 +27,20 @@ import type { SymbolDefinition } from './symbol-definition.js';
|
|
|
27
27
|
export type ScopeId = string;
|
|
28
28
|
/** Stable symbol-definition identifier (graph nodeId). */
|
|
29
29
|
export type DefId = string;
|
|
30
|
-
/**
|
|
31
|
-
|
|
30
|
+
/**
|
|
31
|
+
* Kinds of lexical scope a `Scope` node can represent.
|
|
32
|
+
*
|
|
33
|
+
* `Object` is a hoist boundary ONLY: an object/record literal body
|
|
34
|
+
* (TS/JS `{...}`, Kotlin anonymous `object {...}`). Members are
|
|
35
|
+
* reachable via property access, never as bare identifiers, so
|
|
36
|
+
* scope-chain walkers (`scope/walkers.ts`) must skip an `Object`
|
|
37
|
+
* scope's own bindings while still traversing past it to the parent
|
|
38
|
+
* (#2545/#2551) -- unlike `Block`, where a nested closure legitimately
|
|
39
|
+
* DOES see a sibling `let`/`const` from an enclosing `if`/`for`/`while`,
|
|
40
|
+
* a nested closure inside an object literal must NOT see a sibling
|
|
41
|
+
* property's name as a free identifier.
|
|
42
|
+
*/
|
|
43
|
+
export type ScopeKind = 'Module' | 'Namespace' | 'Class' | 'Function' | 'Block' | 'Expression' | 'Object';
|
|
32
44
|
/** Source-text range. 1-based `startLine`/`endLine`; 0-based `startCol`/`endCol`. */
|
|
33
45
|
export interface Range {
|
|
34
46
|
readonly startLine: number;
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"types.d.ts","sourceRoot":"","sources":["../../src/scope-resolution/types.ts"],"names":[],"mappings":"AAAA;;;;;;;;;;;;;;;;;;;;;;GAsBG;AAEH,OAAO,KAAK,EAAE,SAAS,EAAE,MAAM,mBAAmB,CAAC;AACnD,OAAO,KAAK,EAAE,gBAAgB,EAAE,MAAM,wBAAwB,CAAC;AAI/D,4FAA4F;AAC5F,MAAM,MAAM,OAAO,GAAG,MAAM,CAAC;AAE7B,0DAA0D;AAC1D,MAAM,MAAM,KAAK,GAAG,MAAM,CAAC;AAE3B
|
|
1
|
+
{"version":3,"file":"types.d.ts","sourceRoot":"","sources":["../../src/scope-resolution/types.ts"],"names":[],"mappings":"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"}
|
|
@@ -1,5 +1,6 @@
|
|
|
1
1
|
// gitnexus/src/core/ingestion/class-extractors/configs/jvm.ts
|
|
2
2
|
import { SupportedLanguages } from '../../../../_shared/index.js';
|
|
3
|
+
import { synthesizeJavaAnonymousClassName } from '../../utils/ast-helpers.js';
|
|
3
4
|
// ---------------------------------------------------------------------------
|
|
4
5
|
// Java
|
|
5
6
|
// ---------------------------------------------------------------------------
|
|
@@ -10,6 +11,12 @@ export const javaClassConfig = {
|
|
|
10
11
|
'interface_declaration',
|
|
11
12
|
'enum_declaration',
|
|
12
13
|
'record_declaration',
|
|
14
|
+
// Anonymous class bodies (`new Runnable() { ... }`) — the matching
|
|
15
|
+
// JAVA_QUERIES pattern only captures `object_creation_expression`
|
|
16
|
+
// WITH a `class_body`, and `extractName` below returns undefined for
|
|
17
|
+
// any other shape, so plain `new Foo()` constructor calls never
|
|
18
|
+
// produce a Class node (#2550).
|
|
19
|
+
'object_creation_expression',
|
|
13
20
|
],
|
|
14
21
|
fileScopeNodeTypes: ['package_declaration'],
|
|
15
22
|
ancestorScopeNodeTypes: [
|
|
@@ -18,6 +25,23 @@ export const javaClassConfig = {
|
|
|
18
25
|
'enum_declaration',
|
|
19
26
|
'record_declaration',
|
|
20
27
|
],
|
|
28
|
+
extractName(node) {
|
|
29
|
+
if (node.type === 'object_creation_expression') {
|
|
30
|
+
return synthesizeJavaAnonymousClassName(node);
|
|
31
|
+
}
|
|
32
|
+
return undefined;
|
|
33
|
+
},
|
|
34
|
+
// An anonymous body whose name CANNOT be synthesized (no supported host
|
|
35
|
+
// type declaration) must not become a Class node at all. Without this
|
|
36
|
+
// skip, `extract()`'s `extractTypeNameFromNode` fallback names the node
|
|
37
|
+
// after the CONSTRUCTED type — emitting a phantom `Class:...:Runnable`
|
|
38
|
+
// for `new Runnable() { ... }` (empirically caught in review).
|
|
39
|
+
shouldSkipClassCapture({ definitionNode }) {
|
|
40
|
+
return (definitionNode !== null &&
|
|
41
|
+
definitionNode !== undefined &&
|
|
42
|
+
definitionNode.type === 'object_creation_expression' &&
|
|
43
|
+
synthesizeJavaAnonymousClassName(definitionNode) === undefined);
|
|
44
|
+
},
|
|
21
45
|
};
|
|
22
46
|
// ---------------------------------------------------------------------------
|
|
23
47
|
// Kotlin
|
|
@@ -13,7 +13,7 @@
|
|
|
13
13
|
*
|
|
14
14
|
* Pure given the input source text. No I/O, no globals consulted.
|
|
15
15
|
*/
|
|
16
|
-
import { nodeIfType, nodeToCapture, syntheticCapture } from '../../utils/ast-helpers.js';
|
|
16
|
+
import { nodeIfType, nodeToCapture, synthesizeJavaAnonymousClassName, syntheticCapture, } from '../../utils/ast-helpers.js';
|
|
17
17
|
import { splitImportDeclaration } from './import-decomposer.js';
|
|
18
18
|
import { computeJavaArityMetadata } from './arity-metadata.js';
|
|
19
19
|
import { synthesizeJavaReceiverBinding } from './receiver-binding.js';
|
|
@@ -184,9 +184,79 @@ export function emitJavaScopeCaptures(sourceText, _filePath, cachedTree) {
|
|
|
184
184
|
...resolveVarTypeBindings(out),
|
|
185
185
|
...synthesizeJavaInheritanceReferences(tree.rootNode),
|
|
186
186
|
...synthesizeJavaExplicitConstructorReferences(tree.rootNode),
|
|
187
|
+
...synthesizeJavaAnonymousClassDeclarations(tree.rootNode),
|
|
187
188
|
...synthesizeCallableFlowCaptures(tree.rootNode, JAVA_CALLABLE_CAPTURE_OPTIONS),
|
|
188
189
|
];
|
|
189
190
|
}
|
|
191
|
+
/**
|
|
192
|
+
* Synthesize `@declaration.class` matches for anonymous class bodies
|
|
193
|
+
* (`new Runnable() { ... }`), named by the same javac-style authority
|
|
194
|
+
* (`synthesizeJavaAnonymousClassName` → `Worker$N`) the structure phase
|
|
195
|
+
* uses — the two layers agree by construction (#2550).
|
|
196
|
+
*
|
|
197
|
+
* The anchor is the `class_body` node: it shares its range with the
|
|
198
|
+
* `(object_creation_expression (class_body) @scope.class)` scope rule in
|
|
199
|
+
* query.ts, so the def is owned by that Class scope's `ownedDefs`
|
|
200
|
+
* (making `populateClassOwnedMembers` stamp `ownerId` on the anonymous
|
|
201
|
+
* class's methods) and the name auto-hoists to the enclosing scope —
|
|
202
|
+
* exactly the binding shape a named class declaration produces.
|
|
203
|
+
*/
|
|
204
|
+
function synthesizeJavaAnonymousClassDeclarations(rootNode) {
|
|
205
|
+
const out = [];
|
|
206
|
+
for (const oce of rootNode.descendantsOfType('object_creation_expression')) {
|
|
207
|
+
const name = synthesizeJavaAnonymousClassName(oce);
|
|
208
|
+
if (name === undefined)
|
|
209
|
+
continue;
|
|
210
|
+
const body = oce.namedChildren.find((c) => c.type === 'class_body');
|
|
211
|
+
if (body === undefined)
|
|
212
|
+
continue;
|
|
213
|
+
out.push({
|
|
214
|
+
'@declaration.class': nodeToCapture('@declaration.class', body),
|
|
215
|
+
'@declaration.name': syntheticCapture('@declaration.name', body, name),
|
|
216
|
+
});
|
|
217
|
+
// Inheritance: the anonymous class extends/implements its constructed
|
|
218
|
+
// type. Anchor the `@reference.inherits` on the `class_body` — its
|
|
219
|
+
// range equals the anonymous Class scope, so the reference site's
|
|
220
|
+
// enclosing class resolves to the SYNTHESIZED `Worker$N` def (anchoring
|
|
221
|
+
// on the constructed-type node would sit OUTSIDE the anonymous scope
|
|
222
|
+
// and mis-attribute the edge to the lexically enclosing class). The
|
|
223
|
+
// synthetic `@reference.name` carries the base's simple name; a JDK
|
|
224
|
+
// type with no repo def simply resolves to nothing (no edge). Without
|
|
225
|
+
// this edge `mroFor(Worker$N)` is empty and the #2550 instance-
|
|
226
|
+
// ownership gate suppressed TRUE bare calls to inherited methods
|
|
227
|
+
// inside the anonymous body (empirically caught in review).
|
|
228
|
+
const constructedType = oce.childForFieldName?.('type');
|
|
229
|
+
const baseSimpleName = constructedType !== null && constructedType !== undefined
|
|
230
|
+
? javaBaseSimpleNameOf(constructedType)
|
|
231
|
+
: undefined;
|
|
232
|
+
if (baseSimpleName !== undefined) {
|
|
233
|
+
out.push({
|
|
234
|
+
'@reference.inherits': nodeToCapture('@reference.inherits', body),
|
|
235
|
+
'@reference.name': syntheticCapture('@reference.name', body, baseSimpleName),
|
|
236
|
+
});
|
|
237
|
+
}
|
|
238
|
+
// Receiver typeBinding: `Runnable handler = new Runnable() { ... }`
|
|
239
|
+
// binds `handler` to the ANONYMOUS class (`Worker$1`), not the declared
|
|
240
|
+
// interface — the instance is what `handler.run()` dispatches into, and
|
|
241
|
+
// the declared type is frequently a JDK interface with no repo def.
|
|
242
|
+
// Appended after the raw matches, so it overwrites the declared-type
|
|
243
|
+
// binding the `@type-binding.annotation` query rule produced for the
|
|
244
|
+
// same variable (pass-4 applies bindings in match order; last wins).
|
|
245
|
+
const declarator = oce.parent;
|
|
246
|
+
if (declarator !== null && declarator.type === 'variable_declarator') {
|
|
247
|
+
const varName = declarator.childForFieldName?.('name');
|
|
248
|
+
const declNode = declarator.parent ?? declarator;
|
|
249
|
+
if (varName !== null && varName !== undefined) {
|
|
250
|
+
out.push({
|
|
251
|
+
'@type-binding.annotation': nodeToCapture('@type-binding.annotation', declNode),
|
|
252
|
+
'@type-binding.name': nodeToCapture('@type-binding.name', varName),
|
|
253
|
+
'@type-binding.type': syntheticCapture('@type-binding.type', oce, name),
|
|
254
|
+
});
|
|
255
|
+
}
|
|
256
|
+
}
|
|
257
|
+
}
|
|
258
|
+
return out;
|
|
259
|
+
}
|
|
190
260
|
/**
|
|
191
261
|
* Synthesize `@reference.call.constructor` captures for explicit constructor
|
|
192
262
|
* invocations — `super(...)` and `this(...)` (F38 #1928). tree-sitter-java
|
|
@@ -377,6 +447,13 @@ function emitJavaInheritanceBase(out, base) {
|
|
|
377
447
|
});
|
|
378
448
|
}
|
|
379
449
|
/** Resolve a Java base-type node to its bare simple-name identifier node. */
|
|
450
|
+
/** Simple name of a constructed/base type node, reusing the same node
|
|
451
|
+
* shapes `javaBaseLookupNameNode` handles (`Runnable`, `a.b.Base`,
|
|
452
|
+
* `Box<T>`). Returns undefined when the node is none of those. */
|
|
453
|
+
function javaBaseSimpleNameOf(typeNode) {
|
|
454
|
+
const nameNode = javaBaseLookupNameNode(typeNode);
|
|
455
|
+
return nameNode === null ? undefined : nameNode.text;
|
|
456
|
+
}
|
|
380
457
|
function javaBaseLookupNameNode(node) {
|
|
381
458
|
switch (node.type) {
|
|
382
459
|
case 'type_identifier':
|
|
@@ -37,6 +37,14 @@ const JAVA_SCOPE_QUERY = `
|
|
|
37
37
|
(record_declaration) @scope.class
|
|
38
38
|
(annotation_type_declaration) @scope.class
|
|
39
39
|
|
|
40
|
+
;; Anonymous class body: \`new Runnable() { public void run() {} }\`.
|
|
41
|
+
;; Without its own scope, a method's auto-hoist (scope-extractor.ts) has
|
|
42
|
+
;; nowhere to stop and leaks the name past the anonymous class into the
|
|
43
|
+
;; enclosing scope -- the same failure mode fixed for TS/JS object
|
|
44
|
+
;; literals (#2545).
|
|
45
|
+
(object_creation_expression
|
|
46
|
+
(class_body) @scope.class)
|
|
47
|
+
|
|
40
48
|
(method_declaration) @scope.function
|
|
41
49
|
(constructor_declaration) @scope.function
|
|
42
50
|
|
|
@@ -31,6 +31,12 @@ const javaScopeResolver = {
|
|
|
31
31
|
collapseMemberCallsByCallerTarget: true,
|
|
32
32
|
hoistTypeBindingsToModule: true,
|
|
33
33
|
stripReceiverCastExpressions: true,
|
|
34
|
+
// #2550: every Java method belongs to a class instance — a free call may
|
|
35
|
+
// resolve to a Method only when the caller's enclosing class chain
|
|
36
|
+
// (self + MRO) contains the method's owner. Closes the finalize-bucket
|
|
37
|
+
// leak (unqualified `run()` matching an unrelated same-file anonymous
|
|
38
|
+
// class's method). C# is the intended next adopter.
|
|
39
|
+
freeCallsRequireInstanceOwnership: true,
|
|
34
40
|
populateNamespaceSiblings: populateJavaPackageSiblings,
|
|
35
41
|
populateRangeBindings: populateJavaCrossFileReturnTypes,
|
|
36
42
|
};
|
|
@@ -66,6 +66,13 @@ const JAVASCRIPT_SCOPE_QUERY = `
|
|
|
66
66
|
(arrow_function) @scope.function
|
|
67
67
|
(method_definition) @scope.function
|
|
68
68
|
|
|
69
|
+
;; Object literals get their own scope boundary -- see the matching
|
|
70
|
+
;; comment in typescript/query.ts (#2545/#2551). Prevents a
|
|
71
|
+
;; method_definition/property-arrow's auto-hoist from leaking its name
|
|
72
|
+
;; past the literal into the enclosing scope, and (unlike Block) keeps
|
|
73
|
+
;; sibling properties from seeing each other as bare identifiers.
|
|
74
|
+
(object) @scope.object
|
|
75
|
+
|
|
69
76
|
;; Declarations — classes
|
|
70
77
|
(class_declaration
|
|
71
78
|
name: (identifier) @declaration.name) @declaration.class
|
|
@@ -15,6 +15,12 @@ const KOTLIN_SCOPE_QUERY = `
|
|
|
15
15
|
(class_declaration) @scope.class
|
|
16
16
|
(object_declaration) @scope.class
|
|
17
17
|
(companion_object) @scope.class
|
|
18
|
+
;; Anonymous object expression: \`val h = object { fun fetch() {} }\`
|
|
19
|
+
;; (distinct from the named \`object_declaration\`/\`companion_object\` above).
|
|
20
|
+
;; Without its own scope, a method's auto-hoist (scope-extractor.ts) has
|
|
21
|
+
;; nowhere to stop and leaks the name past the literal into the enclosing
|
|
22
|
+
;; scope -- the same failure mode fixed for TS/JS object literals (#2545).
|
|
23
|
+
(object_literal) @scope.class
|
|
18
24
|
(function_declaration) @scope.function
|
|
19
25
|
|
|
20
26
|
;; Secondary-constructor body scope (issue #1919 review CF1). A
|
|
@@ -96,6 +96,23 @@ const TYPESCRIPT_SCOPE_QUERY = `
|
|
|
96
96
|
(arrow_function) @scope.function
|
|
97
97
|
(function_expression) @scope.function
|
|
98
98
|
|
|
99
|
+
;; Object literals (the { ... } value expression, NOT object_type or
|
|
100
|
+
;; object_pattern) get their own scope boundary. Without it, a
|
|
101
|
+
;; method_definition/property-arrow's auto-hoist (scope-extractor.ts)
|
|
102
|
+
;; has nowhere to stop and leaks the name past the literal into whatever
|
|
103
|
+
;; lexically encloses it -- e.g. 'export default { async fetch(req) {} }'
|
|
104
|
+
;; would bind fetch at Module scope, letting an unrelated same-file
|
|
105
|
+
;; fetch(...) call (the platform global) incorrectly resolve to it
|
|
106
|
+
;; (#2545). Object (not Block or Class): object-literal members are
|
|
107
|
+
;; reachable only via property access, never as bare identifiers -- not
|
|
108
|
+
;; even by a SIBLING property's function body, unlike a real Block
|
|
109
|
+
;; (if/for/while, where a nested closure legitimately sees a sibling
|
|
110
|
+
;; let/const) or a Class (implicit-this sibling dispatch). Scope-chain
|
|
111
|
+
;; walkers (scope-resolution/scope/walkers.ts) skip an Object scope's
|
|
112
|
+
;; own bindings entirely while still treating it as a hoist boundary
|
|
113
|
+
;; (#2551).
|
|
114
|
+
(object) @scope.object
|
|
115
|
+
|
|
99
116
|
;; Type aliases that contain an object_type are structurally class-like —
|
|
100
117
|
;; they define a shape with named members. Emit @scope.class so the
|
|
101
118
|
;; field-extractor's type-alias-with-object-type handling (in
|
|
@@ -626,6 +626,23 @@ export interface ScopeResolver {
|
|
|
626
626
|
* but is too loose as a default for strict module systems.
|
|
627
627
|
*/
|
|
628
628
|
readonly allowGlobalFreeCallFallback?: boolean;
|
|
629
|
+
/**
|
|
630
|
+
* In this language every `Method` belongs to a class instance, so a
|
|
631
|
+
* FREE (receiver-less) call may resolve to a `Method` only when the
|
|
632
|
+
* caller's enclosing class chain — the class itself plus its MRO —
|
|
633
|
+
* contains the method's owner (#2550). Suppresses the finalize-bucket
|
|
634
|
+
* leak where an unqualified call matched any same-file method by bare
|
|
635
|
+
* name (`materializeBindings` flattens every declaration onto module
|
|
636
|
+
* scope). Java opts in; C# is the intended next adopter.
|
|
637
|
+
*
|
|
638
|
+
* NOT implemented via `LanguageProvider.builtInNames`: that mechanism
|
|
639
|
+
* has unrelated consumers (`parse-worker`'s call-site extraction gate
|
|
640
|
+
* suppresses member calls too; `type-env`'s return-type lookup) which
|
|
641
|
+
* assume a flagged name is never a real repository declaration —
|
|
642
|
+
* false for common method names like `run`/`get`/`compare` (verified
|
|
643
|
+
* regression).
|
|
644
|
+
*/
|
|
645
|
+
readonly freeCallsRequireInstanceOwnership?: boolean;
|
|
629
646
|
/**
|
|
630
647
|
* When true, a constructor-form call `Type(...)` links to the Class def
|
|
631
648
|
* itself rather than its explicit Constructor def. Default
|
|
@@ -57,6 +57,15 @@ export declare function emitFreeCallFallback(graph: KnowledgeGraph, scopes: Scop
|
|
|
57
57
|
* fail at the call site. Three-valued; `'unknown'` keeps the
|
|
58
58
|
* candidate (monotonicity). */
|
|
59
59
|
readonly constraintCompatibility?: ScopeResolver['constraintCompatibility'];
|
|
60
|
+
/** Platform/language built-in names (e.g. `fetch`, `setTimeout`) that
|
|
61
|
+
* are never real repository declarations. Gates the finalize-bucket
|
|
62
|
+
* guard below (#2545) -- see `hasGenuineLexicalBinding`. */
|
|
63
|
+
readonly isBuiltInName?: (name: string) => boolean;
|
|
64
|
+
/** Instance-ownership gate (#2550): a free call may resolve to a
|
|
65
|
+
* `Method` only when the caller's enclosing class chain (self + MRO)
|
|
66
|
+
* contains the method's owner. See
|
|
67
|
+
* `ScopeResolver.freeCallsRequireInstanceOwnership`. */
|
|
68
|
+
readonly freeCallsRequireInstanceOwnership?: boolean;
|
|
60
69
|
readonly recordResolutionOutcome?: ResolutionOutcomeRecorder;
|
|
61
70
|
/** Call sites owned by a later precise pass (for example callable-value-flow). */
|
|
62
71
|
readonly skipSites?: ReadonlySet<string>;
|
|
@@ -17,7 +17,7 @@
|
|
|
17
17
|
* generalization plan.
|
|
18
18
|
*/
|
|
19
19
|
import { resolveCallerGraphId, resolveDefGraphId } from '../graph-bridge/ids.js';
|
|
20
|
-
import { findAllCallableBindingsInScope, findCallableBindingInScope, findCallableBindingsAndAdlBlocker, resolveInheritanceBaseInScope, } from '../scope/walkers.js';
|
|
20
|
+
import { findAllCallableBindingsInScope, findCallableBindingInScope, findCallableBindingsAndAdlBlocker, findEnclosingClassDef, resolveInheritanceBaseInScope, } from '../scope/walkers.js';
|
|
21
21
|
import { isOverloadAmbiguousAfterNormalization, narrowOverloadCandidates, } from './overload-narrowing.js';
|
|
22
22
|
export function emitFreeCallFallback(graph, scopes, parsedFiles, nodeLookup, _referenceIndex, handledSites, model, workspaceIndex, options = {}) {
|
|
23
23
|
let emitted = 0;
|
|
@@ -109,6 +109,72 @@ export function emitFreeCallFallback(graph, scopes, parsedFiles, nodeLookup, _re
|
|
|
109
109
|
// available AND the binding scope contains multiple overloads,
|
|
110
110
|
// refine with narrowOverloadCandidates (#1578).
|
|
111
111
|
fnDef = findCallableBindingInScope(site.inScope, site.name, scopes);
|
|
112
|
+
if (fnDef !== undefined &&
|
|
113
|
+
options.isBuiltInName?.(site.name) === true &&
|
|
114
|
+
fnDef.filePath === parsed.filePath &&
|
|
115
|
+
!hasGenuineLexicalBinding(site.inScope, site.name, scopes)) {
|
|
116
|
+
// A platform/language built-in (e.g. `fetch`, `setTimeout`)
|
|
117
|
+
// with no binding reachable via the TRUE lexical scope chain
|
|
118
|
+
// (Scope.bindings only) -- the match came solely from
|
|
119
|
+
// finalize's per-file "local + imports + wildcards" bucket,
|
|
120
|
+
// which flattens every declaration in the file onto the
|
|
121
|
+
// module scope regardless of true nesting depth
|
|
122
|
+
// (gitnexus-shared's `materializeBindings`). That flattening
|
|
123
|
+
// is correct for its purpose (cross-file import targets) but
|
|
124
|
+
// over-matches same-file built-in-shadowing declarations that
|
|
125
|
+
// were never really module-scope-visible -- e.g. a Cloudflare
|
|
126
|
+
// Worker's `export default { fetch(req) {} }` handler (#2545).
|
|
127
|
+
// Leave the call unresolved rather than emit a false edge.
|
|
128
|
+
//
|
|
129
|
+
// `fnDef.filePath === parsed.filePath` is the load-bearing
|
|
130
|
+
// guard against a real regression: `materializeBindings`'s
|
|
131
|
+
// flat bucket is per-file, so the leak this guard targets is
|
|
132
|
+
// ALWAYS same-file. A cross-file match at this point can only
|
|
133
|
+
// come from a genuine import/namespace/workspace-FQN channel
|
|
134
|
+
// (the separate, gated `pickUniqueGlobalCallable` global
|
|
135
|
+
// fallback runs later and isn't what populated `fnDef` here)
|
|
136
|
+
// -- e.g. `import { fetch } from './fetch-polyfill'` must
|
|
137
|
+
// keep resolving. Without this check, that import silently
|
|
138
|
+
// stopped resolving (verified via a scratch probe fixture).
|
|
139
|
+
fnDef = undefined;
|
|
140
|
+
}
|
|
141
|
+
// Instance-ownership gate (#2550). Placement matters: after the
|
|
142
|
+
// scope-chain lookup, BEFORE overload narrowing -- a suppressed
|
|
143
|
+
// candidate must not participate in overload selection. The
|
|
144
|
+
// legitimate same-class bare call already resolved earlier via
|
|
145
|
+
// `pickImplicitThisOverload`; an inherited bare call passes the
|
|
146
|
+
// MRO arm here; what remains is the finalize-bucket leak (an
|
|
147
|
+
// unrelated same-file method matched by bare name).
|
|
148
|
+
//
|
|
149
|
+
// Same-file only (mirrors the #2545 guard's load-bearing
|
|
150
|
+
// condition): the `materializeBindings` bucket is per-file, so
|
|
151
|
+
// the leak is ALWAYS same-file. A cross-file Method match here
|
|
152
|
+
// came through a genuine import channel -- e.g. the arity-
|
|
153
|
+
// narrowing parity fixtures resolve a bare `writeAudit(u)` to
|
|
154
|
+
// an imported class's method, which must keep working
|
|
155
|
+
// (suppressing it broke `java.test.ts`'s arity-filtering suite,
|
|
156
|
+
// verified empirically).
|
|
157
|
+
if (fnDef !== undefined &&
|
|
158
|
+
options.freeCallsRequireInstanceOwnership === true &&
|
|
159
|
+
fnDef.type === 'Method' &&
|
|
160
|
+
fnDef.ownerId !== undefined &&
|
|
161
|
+
fnDef.filePath === parsed.filePath) {
|
|
162
|
+
const enclosing = findEnclosingClassDef(site.inScope, scopes);
|
|
163
|
+
const ownerReachable = enclosing !== undefined &&
|
|
164
|
+
(enclosing.nodeId === fnDef.ownerId ||
|
|
165
|
+
scopes.methodDispatch.mroFor(enclosing.nodeId).includes(fnDef.ownerId));
|
|
166
|
+
if (!ownerReachable) {
|
|
167
|
+
recordSuppressedOutcome(options.recordResolutionOutcome, {
|
|
168
|
+
phase: 'free-call-fallback',
|
|
169
|
+
filePath: parsed.filePath,
|
|
170
|
+
name: site.name,
|
|
171
|
+
range: site.atRange,
|
|
172
|
+
reason: 'free-call-instance-ownership',
|
|
173
|
+
candidates: [fnDef],
|
|
174
|
+
});
|
|
175
|
+
fnDef = undefined;
|
|
176
|
+
}
|
|
177
|
+
}
|
|
112
178
|
if (fnDef !== undefined && options.conversionRankFn !== undefined) {
|
|
113
179
|
const allCallables = findAllCallableBindingsInScope(site.inScope, site.name, scopes);
|
|
114
180
|
if (allCallables.length > 1) {
|
|
@@ -709,3 +775,31 @@ export function pickImplicitThisOverload(site, scopes, workspaceIndex, model, ho
|
|
|
709
775
|
return undefined;
|
|
710
776
|
return candidates[0];
|
|
711
777
|
}
|
|
778
|
+
/**
|
|
779
|
+
* True when `name` is bound somewhere along the TRUE lexical scope
|
|
780
|
+
* chain from `startScope` -- i.e. via `Scope.bindings` (the raw,
|
|
781
|
+
* nesting-aware per-scope map built during extraction), NOT via
|
|
782
|
+
* finalize's `indexes.bindings` module-scope bucket (which flattens
|
|
783
|
+
* every declaration in the file onto the module scope regardless of
|
|
784
|
+
* true nesting -- see `hasGenuineLexicalBinding`'s caller for why that
|
|
785
|
+
* distinction matters, #2545).
|
|
786
|
+
*/
|
|
787
|
+
function hasGenuineLexicalBinding(startScope, name, scopes) {
|
|
788
|
+
let currentId = startScope;
|
|
789
|
+
const visited = new Set();
|
|
790
|
+
while (currentId !== null) {
|
|
791
|
+
if (visited.has(currentId))
|
|
792
|
+
return false;
|
|
793
|
+
visited.add(currentId);
|
|
794
|
+
const scope = scopes.scopeTree.getScope(currentId);
|
|
795
|
+
if (scope === undefined)
|
|
796
|
+
return false;
|
|
797
|
+
// `Object` scopes (object/record literal bodies) are a hoist
|
|
798
|
+
// boundary only -- never a genuine lexical binding, not even to
|
|
799
|
+
// their own nested children (#2551, mirrors scope/walkers.ts).
|
|
800
|
+
if (scope.kind !== 'Object' && scope.bindings.get(name) !== undefined)
|
|
801
|
+
return true;
|
|
802
|
+
currentId = scope.parent;
|
|
803
|
+
}
|
|
804
|
+
return false;
|
|
805
|
+
}
|
|
@@ -508,6 +508,8 @@ export function runScopeResolution(input, provider) {
|
|
|
508
508
|
allowGlobalFallback: provider.allowGlobalFreeCallFallback === true,
|
|
509
509
|
constructorCallTargetsClass: provider.constructorCallTargetsClass === true,
|
|
510
510
|
isFileLocalDef: provider.isFileLocalDef,
|
|
511
|
+
isBuiltInName: provider.languageProvider.isBuiltInName,
|
|
512
|
+
freeCallsRequireInstanceOwnership: provider.freeCallsRequireInstanceOwnership === true,
|
|
511
513
|
isCallableVisibleFromCaller: provider.isCallableVisibleFromCaller,
|
|
512
514
|
resolveAdlCandidates: provider.resolveAdlCandidates,
|
|
513
515
|
conversionRankFn: provider.conversionRankFn,
|
|
@@ -1,5 +1,5 @@
|
|
|
1
1
|
import type { Range } from '../../../_shared/index.js';
|
|
2
|
-
export type ResolutionSuppressionReason = 'adl-ordinary-lookup-blocked' | 'conversion-rank-tied' | 'inline-ns-ambiguous' | 'member-lookup-ambiguous' | 'selected-callable-deleted' | 'overload-ambiguous' | 'overload-ambiguous-normalization';
|
|
2
|
+
export type ResolutionSuppressionReason = 'adl-ordinary-lookup-blocked' | 'conversion-rank-tied' | 'inline-ns-ambiguous' | 'member-lookup-ambiguous' | 'selected-callable-deleted' | 'overload-ambiguous' | 'overload-ambiguous-normalization' | 'free-call-instance-ownership';
|
|
3
3
|
export type ResolutionOutcome = {
|
|
4
4
|
readonly kind: 'resolved';
|
|
5
5
|
readonly targetId: string;
|
|
@@ -577,20 +577,26 @@ function walkScopeChain(startScope, name, scopes, predicate) {
|
|
|
577
577
|
const scope = scopes.scopeTree.getScope(currentId);
|
|
578
578
|
if (scope === undefined)
|
|
579
579
|
return undefined;
|
|
580
|
-
//
|
|
581
|
-
|
|
582
|
-
|
|
583
|
-
|
|
580
|
+
// `Object` scopes (object/record literal bodies) are a hoist
|
|
581
|
+
// boundary only -- their members are reachable via property access,
|
|
582
|
+
// never bare identifiers, so they contribute nothing to lookup
|
|
583
|
+
// (#2545/#2551). Still traverse past to the parent.
|
|
584
|
+
if (scope.kind !== 'Object') {
|
|
585
|
+
// Local first: a `const x` in this scope shadows any imported `x`.
|
|
586
|
+
const localBindings = scope.bindings.get(name);
|
|
587
|
+
if (localBindings !== undefined) {
|
|
588
|
+
for (const b of localBindings) {
|
|
589
|
+
if (predicate(b.def))
|
|
590
|
+
return b.def;
|
|
591
|
+
}
|
|
592
|
+
}
|
|
593
|
+
// Then imported/augmented bindings — only consulted when no local match.
|
|
594
|
+
const importedBindings = lookupBindingsAt(currentId, name, scopes);
|
|
595
|
+
for (const b of importedBindings) {
|
|
584
596
|
if (predicate(b.def))
|
|
585
597
|
return b.def;
|
|
586
598
|
}
|
|
587
599
|
}
|
|
588
|
-
// Then imported/augmented bindings — only consulted when no local match.
|
|
589
|
-
const importedBindings = lookupBindingsAt(currentId, name, scopes);
|
|
590
|
-
for (const b of importedBindings) {
|
|
591
|
-
if (predicate(b.def))
|
|
592
|
-
return b.def;
|
|
593
|
-
}
|
|
594
600
|
currentId = scope.parent;
|
|
595
601
|
}
|
|
596
602
|
return undefined;
|
|
@@ -626,28 +632,32 @@ export function findAllCallableBindingsInScope(startScope, callableName, scopes)
|
|
|
626
632
|
const scope = scopes.scopeTree.getScope(currentId);
|
|
627
633
|
if (scope === undefined)
|
|
628
634
|
return [];
|
|
629
|
-
|
|
630
|
-
|
|
631
|
-
|
|
632
|
-
|
|
633
|
-
|
|
634
|
-
|
|
635
|
-
|
|
636
|
-
|
|
637
|
-
|
|
638
|
-
|
|
639
|
-
|
|
640
|
-
|
|
641
|
-
|
|
635
|
+
// `Object` scopes are a hoist boundary only -- see walkScopeChain's
|
|
636
|
+
// comment (#2545/#2551). Skip lookup here, still traverse to parent.
|
|
637
|
+
if (scope.kind !== 'Object') {
|
|
638
|
+
const out = [];
|
|
639
|
+
const seen = new Set();
|
|
640
|
+
const pushCallable = (def) => {
|
|
641
|
+
if (def.type !== 'Function' && def.type !== 'Method' && def.type !== 'Constructor')
|
|
642
|
+
return;
|
|
643
|
+
if (seen.has(def.nodeId))
|
|
644
|
+
return;
|
|
645
|
+
seen.add(def.nodeId);
|
|
646
|
+
out.push(def);
|
|
647
|
+
};
|
|
648
|
+
const localBindings = scope.bindings.get(callableName);
|
|
649
|
+
if (localBindings !== undefined) {
|
|
650
|
+
for (const b of localBindings) {
|
|
651
|
+
pushCallable(b.def);
|
|
652
|
+
}
|
|
653
|
+
}
|
|
654
|
+
const importedBindings = lookupBindingsAt(currentId, callableName, scopes);
|
|
655
|
+
for (const b of importedBindings) {
|
|
642
656
|
pushCallable(b.def);
|
|
643
657
|
}
|
|
658
|
+
if (out.length > 0)
|
|
659
|
+
return out;
|
|
644
660
|
}
|
|
645
|
-
const importedBindings = lookupBindingsAt(currentId, callableName, scopes);
|
|
646
|
-
for (const b of importedBindings) {
|
|
647
|
-
pushCallable(b.def);
|
|
648
|
-
}
|
|
649
|
-
if (out.length > 0)
|
|
650
|
-
return out;
|
|
651
661
|
currentId = scope.parent;
|
|
652
662
|
}
|
|
653
663
|
return [];
|
|
@@ -694,16 +704,22 @@ export function findCallableBindingsAndAdlBlocker(startScope, name, scopes) {
|
|
|
694
704
|
nonCallableFound = true;
|
|
695
705
|
}
|
|
696
706
|
};
|
|
697
|
-
|
|
698
|
-
|
|
699
|
-
|
|
707
|
+
// `Object` scopes are a hoist boundary only (#2545/#2551) -- never
|
|
708
|
+
// reached by C++'s ADL path in practice (no language reusing this
|
|
709
|
+
// function emits `@scope.object`), guarded for consistency with the
|
|
710
|
+
// other scope-chain walkers in this file.
|
|
711
|
+
if (scope.kind !== 'Object') {
|
|
712
|
+
const localBindings = scope.bindings.get(name);
|
|
713
|
+
if (localBindings !== undefined) {
|
|
714
|
+
for (const b of localBindings) {
|
|
715
|
+
process(b.def);
|
|
716
|
+
}
|
|
717
|
+
}
|
|
718
|
+
const importedBindings = lookupBindingsAt(currentId, name, scopes);
|
|
719
|
+
for (const b of importedBindings) {
|
|
700
720
|
process(b.def);
|
|
701
721
|
}
|
|
702
722
|
}
|
|
703
|
-
const importedBindings = lookupBindingsAt(currentId, name, scopes);
|
|
704
|
-
for (const b of importedBindings) {
|
|
705
|
-
process(b.def);
|
|
706
|
-
}
|
|
707
723
|
if (anyBinding) {
|
|
708
724
|
// ISO C++: a block-scope function declaration (Function or Block scope)
|
|
709
725
|
// that is NOT a using-declaration blocks ADL. If we found callables at
|
|
@@ -930,18 +946,21 @@ export function findExportedDefByName(name, inScope, scopes, index) {
|
|
|
930
946
|
const scope = scopes.scopeTree.getScope(currentId);
|
|
931
947
|
if (scope === undefined)
|
|
932
948
|
break;
|
|
933
|
-
|
|
934
|
-
if (
|
|
935
|
-
|
|
949
|
+
// `Object` scopes are a hoist boundary only (#2545/#2551).
|
|
950
|
+
if (scope.kind !== 'Object') {
|
|
951
|
+
const local = scope.bindings.get(name);
|
|
952
|
+
if (local !== undefined) {
|
|
953
|
+
for (const b of local) {
|
|
954
|
+
if (b.def.type === 'Function' || b.def.type === 'Method')
|
|
955
|
+
return b.def;
|
|
956
|
+
}
|
|
957
|
+
}
|
|
958
|
+
const finalized = lookupBindingsAt(currentId, name, scopes);
|
|
959
|
+
for (const b of finalized) {
|
|
936
960
|
if (b.def.type === 'Function' || b.def.type === 'Method')
|
|
937
961
|
return b.def;
|
|
938
962
|
}
|
|
939
963
|
}
|
|
940
|
-
const finalized = lookupBindingsAt(currentId, name, scopes);
|
|
941
|
-
for (const b of finalized) {
|
|
942
|
-
if (b.def.type === 'Function' || b.def.type === 'Method')
|
|
943
|
-
return b.def;
|
|
944
|
-
}
|
|
945
964
|
currentId = scope.parent;
|
|
946
965
|
}
|
|
947
966
|
// Workspace-wide fallback: the first locally-declared callable binding
|
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
export declare const TYPESCRIPT_QUERIES: string;
|
|
2
2
|
export declare const JAVASCRIPT_QUERIES: string;
|
|
3
3
|
export declare const PYTHON_QUERIES = "\n(class_definition\n name: (identifier) @name) @definition.class\n\n(function_definition\n name: (identifier) @name) @definition.function\n\n(import_statement\n name: (dotted_name) @import.source) @import\n\n; import numpy as np \u2192 aliased_import captures the module name so the\n; import path is resolved and named-binding extraction stores \"np\" \u2192 \"numpy\".\n(import_statement\n name: (aliased_import\n name: (dotted_name) @import.source)) @import\n\n(import_from_statement\n module_name: (dotted_name) @import.source) @import\n\n(import_from_statement\n module_name: (relative_import) @import.source) @import\n\n(call\n function: (identifier) @call.name) @call\n\n(call\n function: (attribute\n attribute: (identifier) @call.name)) @call\n\n; Class attribute type annotations \u2014 PEP 526: address: Address or address: Address = Address()\n; Both bare annotations (address: Address) and annotated assignments (name: str = \"test\")\n; are parsed as (assignment left: ... type: ...) in tree-sitter-python.\n(expression_statement\n (assignment\n left: (identifier) @name\n type: (type)) @definition.property)\n\n; Plain variable assignments without type annotation: x = 5, MAX_SIZE = 100\n; Overlap with @definition.property (typed) is handled by parse-worker dedup.\n(expression_statement\n (assignment\n left: (identifier) @name)) @definition.variable\n\n; Write access: obj.field = value\n(assignment\n left: (attribute\n object: (_) @assignment.receiver\n attribute: (identifier) @assignment.property)\n right: (_)) @assignment\n\n; Write access: obj.field += value (compound assignment)\n(augmented_assignment\n left: (attribute\n object: (_) @assignment.receiver\n attribute: (identifier) @assignment.property)\n right: (_)) @assignment\n\n; Python HTTP clients: requests.get('/path'), httpx.post('/path'), session.get('/path')\n(call\n function: (attribute\n attribute: (identifier) @http_client.method)\n arguments: (argument_list\n (string (string_content) @http_client.url))) @http_client\n\n; Python decorators: @app.route, @router.get, etc.\n; The first positional argument is captured three ways (#2391): a string literal\n; path via @decorator.arg (quote-free, the fast path); a bare constant name or a\n; plus-concatenation via @decorator.arg_expr (resolved cross-file by the constant\n; resolver). The anchored optional alternation pins to the FIRST arg and stays\n; optional, so no-arg decorators (@app.tool(), etc.) and non-path first args still\n; match.\n(decorator\n (call\n function: (attribute\n object: (identifier) @decorator.receiver\n attribute: (identifier) @decorator.name)\n arguments: (argument_list\n .\n [\n (string (string_content)? @decorator.arg) @decorator.arg_str\n (identifier) @decorator.arg_expr\n (binary_operator) @decorator.arg_expr\n ]?))) @decorator\n";
|
|
4
|
-
export declare const JAVA_QUERIES = "\n; Classes, Interfaces, Enums, Annotations\n(class_declaration name: (identifier) @name) @definition.class\n(interface_declaration name: (identifier) @name) @definition.interface\n(enum_declaration name: (identifier) @name) @definition.enum\n(annotation_type_declaration name: (identifier) @name) @definition.annotation\n\n; Methods & Constructors\n(method_declaration name: (identifier) @name) @definition.method\n(constructor_declaration name: (identifier) @name) @definition.constructor\n\n; Fields \u2014 typed field declarations inside class bodies\n(field_declaration\n declarator: (variable_declarator\n name: (identifier) @name)) @definition.property\n\n; Imports - capture any import declaration child as source\n(import_declaration (_) @import.source) @import\n\n; Calls\n(method_invocation name: (identifier) @call.name) @call\n(method_invocation object: (_) name: (identifier) @call.name) @call\n(method_reference) @call\n\n; Constructor calls: new Foo()\n(object_creation_expression type: (type_identifier) @call.name) @call\n\n; Local variable declarations inside method bodies\n(local_variable_declaration\n declarator: (variable_declarator\n name: (identifier) @name)) @definition.variable\n\n; Write access: obj.field = value\n(assignment_expression\n left: (field_access\n object: (_) @assignment.receiver\n field: (identifier) @assignment.property)\n right: (_)) @assignment\n";
|
|
4
|
+
export declare const JAVA_QUERIES = "\n; Classes, Interfaces, Enums, Annotations\n(class_declaration name: (identifier) @name) @definition.class\n(interface_declaration name: (identifier) @name) @definition.interface\n(enum_declaration name: (identifier) @name) @definition.enum\n(annotation_type_declaration name: (identifier) @name) @definition.annotation\n\n; Anonymous class bodies: new Runnable() { ... } \u2014 no @name capture; the\n; class extractor synthesizes the javac-style Worker$N name (#2550)\n(object_creation_expression (class_body)) @definition.class\n\n; Methods & Constructors\n(method_declaration name: (identifier) @name) @definition.method\n(constructor_declaration name: (identifier) @name) @definition.constructor\n\n; Fields \u2014 typed field declarations inside class bodies\n(field_declaration\n declarator: (variable_declarator\n name: (identifier) @name)) @definition.property\n\n; Imports - capture any import declaration child as source\n(import_declaration (_) @import.source) @import\n\n; Calls\n(method_invocation name: (identifier) @call.name) @call\n(method_invocation object: (_) name: (identifier) @call.name) @call\n(method_reference) @call\n\n; Constructor calls: new Foo()\n(object_creation_expression type: (type_identifier) @call.name) @call\n\n; Local variable declarations inside method bodies\n(local_variable_declaration\n declarator: (variable_declarator\n name: (identifier) @name)) @definition.variable\n\n; Write access: obj.field = value\n(assignment_expression\n left: (field_access\n object: (_) @assignment.receiver\n field: (identifier) @assignment.property)\n right: (_)) @assignment\n";
|
|
5
5
|
export declare const C_QUERIES = "\n; Functions (direct declarator)\n(function_definition declarator: (function_declarator declarator: (identifier) @name)) @definition.function\n(declaration declarator: (function_declarator declarator: (identifier) @name)) @definition.function\n\n; Functions returning pointers (pointer_declarator wraps function_declarator)\n(function_definition declarator: (pointer_declarator declarator: (function_declarator declarator: (identifier) @name))) @definition.function\n(declaration declarator: (pointer_declarator declarator: (function_declarator declarator: (identifier) @name))) @definition.function\n\n; Functions returning double pointers (nested pointer_declarator)\n(function_definition declarator: (pointer_declarator declarator: (pointer_declarator declarator: (function_declarator declarator: (identifier) @name)))) @definition.function\n\n; Structs, Unions, Enums, Typedefs\n(struct_specifier name: (type_identifier) @name) @definition.struct\n(type_definition\n type: (struct_specifier\n body: (field_declaration_list))\n declarator: (type_identifier) @name) @definition.struct\n(union_specifier name: (type_identifier) @name) @definition.union\n(enum_specifier name: (type_identifier) @name) @definition.enum\n(type_definition\n type: (enum_specifier\n body: (enumerator_list))\n declarator: (type_identifier) @name) @definition.enum\n(enumerator name: (identifier) @name) @definition.const\n(type_definition declarator: (type_identifier) @name) @definition.typedef\n\n; Macros\n(preproc_function_def name: (identifier) @name) @definition.macro\n(preproc_def name: (identifier) @name) @definition.macro\n\n; Includes\n(preproc_include path: (_) @import.source) @import\n\n; Calls\n(call_expression function: (identifier) @call.name) @call\n(call_expression function: (field_expression field: (field_identifier) @call.name)) @call\n\n; Variable declarations: int x = 5; or int x;\n(declaration\n declarator: (init_declarator\n declarator: (identifier) @name)) @definition.variable\n";
|
|
6
6
|
export declare const GO_QUERIES = "\n; Functions & Methods\n(function_declaration name: (identifier) @name) @definition.function\n(method_declaration name: (field_identifier) @name) @definition.method\n(method_elem name: (field_identifier) @name) @definition.method\n\n; Types\n(type_declaration (type_spec name: (type_identifier) @name type: (struct_type))) @definition.struct\n(type_declaration (type_spec name: (type_identifier) @name type: (interface_type))) @definition.interface\n\n; Imports\n(import_declaration (import_spec path: (interpreted_string_literal) @import.source)) @import\n(import_declaration (import_spec_list (import_spec path: (interpreted_string_literal) @import.source))) @import\n\n; Struct fields \u2014 named field declarations inside struct types\n(field_declaration_list\n (field_declaration\n name: (field_identifier) @name) @definition.property)\n\n; Calls\n(call_expression function: (identifier) @call.name) @call\n(call_expression function: (selector_expression field: (field_identifier) @call.name)) @call\n\n; Const/var declarations\n(const_declaration (const_spec (identifier) @name)) @definition.const\n(var_declaration (var_spec (identifier) @name)) @definition.variable\n(var_declaration (var_spec_list (var_spec (identifier) @name))) @definition.variable\n\n; Short variable declaration: x := 5\n(short_var_declaration left: (expression_list (identifier) @name)) @definition.variable\n\n; Struct literal construction: User{Name: \"Alice\"}\n(composite_literal type: (type_identifier) @call.name) @call\n\n; Write access: obj.field = value\n(assignment_statement\n left: (expression_list\n (selector_expression\n operand: (_) @assignment.receiver\n field: (field_identifier) @assignment.property))\n right: (_)) @assignment\n\n; Write access: obj.field++ / obj.field--\n(inc_statement\n (selector_expression\n operand: (_) @assignment.receiver\n field: (field_identifier) @assignment.property)) @assignment\n(dec_statement\n (selector_expression\n operand: (_) @assignment.receiver\n field: (field_identifier) @assignment.property)) @assignment\n";
|
|
7
7
|
export declare const CPP_QUERIES = "\n; Classes, Structs, Namespaces\n(class_specifier name: (type_identifier) @name) @definition.class\n(class_specifier\n name: (template_type\n (type_identifier) @name\n (template_argument_list) @template-arguments)) @definition.class\n; Out-of-line nested definition: class Outer::Inner { ... } / struct Outer::Inner { ... }.\n; Key the node by the full qualified_identifier text so the def materializes a\n; node that matches the HAS_METHOD owner id (also the full qualified text) and\n; stays distinct from a same-tail type in another scope (#1975, #1978).\n(class_specifier name: (qualified_identifier) @name) @definition.class\n(struct_specifier name: (qualified_identifier) @name) @definition.struct\n(struct_specifier name: (type_identifier) @name) @definition.struct\n(struct_specifier\n name: (template_type\n (type_identifier) @name\n (template_argument_list) @template-arguments)) @definition.struct\n(namespace_definition name: (namespace_identifier) @name) @definition.namespace\n(enum_specifier name: (type_identifier) @name) @definition.enum\n\n; Typedefs and unions (common in C-style headers and mixed C/C++ code)\n(type_definition\n type: (struct_specifier\n body: (field_declaration_list))\n declarator: (type_identifier) @name) @definition.struct\n(type_definition\n type: (enum_specifier\n body: (enumerator_list))\n declarator: (type_identifier) @name) @definition.enum\n(enumerator name: (identifier) @name) @definition.const\n(type_definition declarator: (type_identifier) @name) @definition.typedef\n(union_specifier name: (type_identifier) @name) @definition.union\n\n; Macros\n(preproc_function_def name: (identifier) @name) @definition.macro\n(preproc_def name: (identifier) @name) @definition.macro\n\n; Functions & Methods (direct declarator)\n(function_definition declarator: (function_declarator declarator: (identifier) @name)) @definition.function\n(function_definition declarator: (function_declarator declarator: (operator_name) @name)) @definition.function\n(function_definition declarator: (function_declarator declarator: (qualified_identifier name: (identifier) @name))) @definition.method\n(function_definition declarator: (function_declarator declarator: (qualified_identifier name: (operator_name) @name))) @definition.method\n\n; Functions/methods returning pointers (pointer_declarator wraps function_declarator)\n(function_definition declarator: (pointer_declarator declarator: (function_declarator declarator: (identifier) @name))) @definition.function\n(function_definition declarator: (pointer_declarator declarator: (function_declarator declarator: (qualified_identifier name: (identifier) @name)))) @definition.method\n\n; Functions/methods returning double pointers (nested pointer_declarator)\n(function_definition declarator: (pointer_declarator declarator: (pointer_declarator declarator: (function_declarator declarator: (identifier) @name)))) @definition.function\n(function_definition declarator: (pointer_declarator declarator: (pointer_declarator declarator: (function_declarator declarator: (qualified_identifier name: (identifier) @name))))) @definition.method\n\n; Functions/methods returning references (reference_declarator wraps function_declarator)\n(function_definition declarator: (reference_declarator (function_declarator declarator: (identifier) @name))) @definition.function\n(function_definition declarator: (reference_declarator (function_declarator declarator: (operator_name) @name))) @definition.function\n(function_definition declarator: (reference_declarator (function_declarator declarator: (qualified_identifier name: (identifier) @name)))) @definition.method\n(function_definition declarator: (reference_declarator (function_declarator declarator: (qualified_identifier name: (operator_name) @name)))) @definition.method\n\n; Destructors (destructor_name is distinct from identifier in tree-sitter-cpp)\n(function_definition declarator: (function_declarator declarator: (qualified_identifier name: (destructor_name) @name))) @definition.method\n\n; Function declarations / prototypes (common in headers)\n(declaration declarator: (function_declarator declarator: (identifier) @name)) @definition.function\n(declaration declarator: (function_declarator declarator: (operator_name) @name)) @definition.function\n(declaration declarator: (pointer_declarator declarator: (function_declarator declarator: (identifier) @name))) @definition.function\n(declaration declarator: (reference_declarator (function_declarator declarator: (operator_name) @name))) @definition.function\n\n; Class/struct data member fields (Address address; int count;)\n; Uses field_identifier to exclude method declarations (which use function_declarator)\n(field_declaration\n declarator: (field_identifier) @name) @definition.property\n\n; Pointer member fields (Address* address;)\n(field_declaration\n declarator: (pointer_declarator\n declarator: (field_identifier) @name)) @definition.property\n\n; Reference member fields (Address& address;)\n(field_declaration\n declarator: (reference_declarator\n (field_identifier) @name)) @definition.property\n\n; Inline class method declarations (inside class body, no body: void save();)\n; tree-sitter-cpp uses field_identifier (not identifier) for names inside class bodies\n(field_declaration declarator: (function_declarator declarator: [(field_identifier) (identifier) (operator_name)] @name)) @definition.method\n\n; Inline class method declarations returning a pointer (User* lookup();)\n(field_declaration declarator: (pointer_declarator declarator: (function_declarator declarator: [(field_identifier) (identifier)] @name))) @definition.method\n\n; Inline class method declarations returning a reference (User& lookup();)\n(field_declaration declarator: (reference_declarator (function_declarator declarator: [(field_identifier) (identifier) (operator_name)] @name))) @definition.method\n\n; Inline class method definitions (inside class body, with body: void Foo() { ... })\n(field_declaration_list\n (function_definition\n declarator: (function_declarator\n declarator: [(field_identifier) (identifier) (operator_name) (destructor_name)] @name)) @definition.method)\n\n; Inline class methods returning a pointer type (User* lookup(int id) { ... })\n(field_declaration_list\n (function_definition\n declarator: (pointer_declarator\n declarator: (function_declarator\n declarator: [(field_identifier) (identifier) (operator_name)] @name))) @definition.method)\n\n; Inline class methods returning a reference type (User& lookup(int id) { ... })\n(field_declaration_list\n (function_definition\n declarator: (reference_declarator\n (function_declarator\n declarator: [(field_identifier) (identifier) (operator_name)] @name))) @definition.method)\n\n; Templates\n(template_declaration (class_specifier name: (type_identifier) @name)) @definition.template\n(template_declaration\n (class_specifier\n name: (template_type\n (type_identifier) @name\n (template_argument_list) @template-arguments))) @definition.template\n(template_declaration (function_definition declarator: (function_declarator declarator: (identifier) @name))) @definition.template\n\n; Includes\n(preproc_include path: (_) @import.source) @import\n\n; Calls\n(call_expression function: (identifier) @call.name) @call\n(call_expression function: (field_expression field: (field_identifier) @call.name)) @call\n(call_expression function: (qualified_identifier name: (identifier) @call.name)) @call\n(call_expression function: (template_function name: (identifier) @call.name)) @call\n(binary_expression operator: \"+\" @call.name) @call\n(binary_expression operator: \"<<\" @call.name) @call\n\n; Constructor calls: new User()\n(new_expression type: (type_identifier) @call.name) @call\n\n; Variable declarations: int x = 5; or auto x = 5;\n(declaration\n declarator: (init_declarator\n declarator: (identifier) @name)) @definition.variable\n\n; Structured bindings: auto [a, b] = makePair(); (one @name per bound identifier)\n(declaration\n declarator: (init_declarator\n declarator: (structured_binding_declarator\n (identifier) @name))) @definition.variable\n\n; Structured bindings, reference form: auto& [x, y] = tup;\n(declaration\n declarator: (init_declarator\n declarator: (reference_declarator\n (structured_binding_declarator\n (identifier) @name)))) @definition.variable\n\n; Write access: obj.field = value\n(assignment_expression\n left: (field_expression\n argument: (_) @assignment.receiver\n field: (field_identifier) @assignment.property)\n right: (_)) @assignment\n\n";
|
|
@@ -744,6 +744,10 @@ export const JAVA_QUERIES = `
|
|
|
744
744
|
(enum_declaration name: (identifier) @name) @definition.enum
|
|
745
745
|
(annotation_type_declaration name: (identifier) @name) @definition.annotation
|
|
746
746
|
|
|
747
|
+
; Anonymous class bodies: new Runnable() { ... } — no @name capture; the
|
|
748
|
+
; class extractor synthesizes the javac-style Worker$N name (#2550)
|
|
749
|
+
(object_creation_expression (class_body)) @definition.class
|
|
750
|
+
|
|
747
751
|
; Methods & Constructors
|
|
748
752
|
(method_declaration name: (identifier) @name) @definition.method
|
|
749
753
|
(constructor_declaration name: (identifier) @name) @definition.constructor
|
|
@@ -1,10 +1,20 @@
|
|
|
1
|
-
import { findChild } from '../utils/ast-helpers.js';
|
|
1
|
+
import { findChild, synthesizeJavaAnonymousClassName, } from '../utils/ast-helpers.js';
|
|
2
2
|
import { extractSimpleTypeName, extractVarName, extractGenericTypeArgs, resolveIterableElementType, methodToTypeArgPosition, extractElementTypeFromString, } from './shared.js';
|
|
3
3
|
// ── Java ──────────────────────────────────────────────────────────────────
|
|
4
4
|
const JAVA_DECLARATION_NODE_TYPES = new Set([
|
|
5
5
|
'local_variable_declaration',
|
|
6
6
|
'field_declaration',
|
|
7
7
|
]);
|
|
8
|
+
/** `Runnable handler = new Runnable() { ... }` — the variable's effective
|
|
9
|
+
* type is the ANONYMOUS class (`Worker$1`), not the declared interface;
|
|
10
|
+
* that is the instance `handler.run()` dispatches into (#2550). Returns
|
|
11
|
+
* undefined for declarators without an anonymous-body initializer. */
|
|
12
|
+
const anonymousInitializerTypeName = (declarator) => {
|
|
13
|
+
const valueNode = declarator.childForFieldName('value');
|
|
14
|
+
if (!valueNode || valueNode.type !== 'object_creation_expression')
|
|
15
|
+
return undefined;
|
|
16
|
+
return synthesizeJavaAnonymousClassName(valueNode);
|
|
17
|
+
};
|
|
8
18
|
/** Java: Type x = ...; Type x; */
|
|
9
19
|
const extractJavaDeclaration = (node, env) => {
|
|
10
20
|
const typeNode = node.childForFieldName('type');
|
|
@@ -22,7 +32,7 @@ const extractJavaDeclaration = (node, env) => {
|
|
|
22
32
|
if (nameNode) {
|
|
23
33
|
const varName = extractVarName(nameNode);
|
|
24
34
|
if (varName)
|
|
25
|
-
env.set(varName, typeName);
|
|
35
|
+
env.set(varName, anonymousInitializerTypeName(child) ?? typeName);
|
|
26
36
|
}
|
|
27
37
|
}
|
|
28
38
|
};
|
|
@@ -42,6 +52,11 @@ const extractJavaInitializer = (node, env, _classNames) => {
|
|
|
42
52
|
continue;
|
|
43
53
|
if (valueNode.type !== 'object_creation_expression')
|
|
44
54
|
continue;
|
|
55
|
+
const anonName = anonymousInitializerTypeName(child);
|
|
56
|
+
if (anonName) {
|
|
57
|
+
env.set(varName, anonName);
|
|
58
|
+
continue;
|
|
59
|
+
}
|
|
45
60
|
const ctorType = valueNode.childForFieldName('type');
|
|
46
61
|
if (!ctorType)
|
|
47
62
|
continue;
|
|
@@ -102,6 +102,7 @@ export interface EnclosingClassInfo {
|
|
|
102
102
|
*/
|
|
103
103
|
qualifiedClassId?: string;
|
|
104
104
|
}
|
|
105
|
+
export declare const synthesizeJavaAnonymousClassName: (node: SyntaxNode) => string | undefined;
|
|
105
106
|
export declare const findEnclosingClassInfo: (node: SyntaxNode, filePath: string, resolveEnclosingOwner?: (node: SyntaxNode) => SyntaxNode | null,
|
|
106
107
|
/**
|
|
107
108
|
* Optional (#1978): returns the enclosing type's fully-qualified name
|
|
@@ -270,7 +270,14 @@ export function getLabelFromCaptures(captureMap, provider) {
|
|
|
270
270
|
return null;
|
|
271
271
|
const hasDefaultExportHocNameSeed = captureMap['definition.function'] !== undefined &&
|
|
272
272
|
(captureMap['hoc'] !== undefined || captureMap['callee'] !== undefined);
|
|
273
|
-
|
|
273
|
+
// Nameless `definition.class` passes through: a class extractor may
|
|
274
|
+
// synthesize the name (Java anonymous class bodies → `Worker$N`, #2550).
|
|
275
|
+
// Downstream stays safe — parse-worker skips any nameless definition the
|
|
276
|
+
// extractor could not name (its `!nameNode && !extractedClassSymbol` gate).
|
|
277
|
+
if (!captureMap['name'] &&
|
|
278
|
+
!captureMap['definition.constructor'] &&
|
|
279
|
+
!captureMap['definition.class'] &&
|
|
280
|
+
!hasDefaultExportHocNameSeed)
|
|
274
281
|
return null;
|
|
275
282
|
if (captureMap['definition.function']) {
|
|
276
283
|
if (provider.labelOverride) {
|
|
@@ -353,6 +360,82 @@ export function getLabelFromCaptures(captureMap, provider) {
|
|
|
353
360
|
* `MAX_ENCLOSING_WALK_ITERATIONS` defense-in-depth guard below prevents
|
|
354
361
|
* pathological hooks from creating an infinite loop. */
|
|
355
362
|
const MAX_ENCLOSING_WALK_ITERATIONS = 4096;
|
|
363
|
+
/**
|
|
364
|
+
* Synthesize a javac-style name for a Java anonymous class body:
|
|
365
|
+
* `new Runnable() { ... }` inside top-level class `Worker` becomes
|
|
366
|
+
* `Worker$1` (`$N` = 1-based source order of anonymous bodies within the
|
|
367
|
+
* top-level class). Returns undefined when the node is not an
|
|
368
|
+
* `object_creation_expression` carrying a `class_body` child — which also
|
|
369
|
+
* keeps this a no-op for C#, whose `object_creation_expression` uses
|
|
370
|
+
* `initializer_expression`, never `class_body` (#2550).
|
|
371
|
+
*
|
|
372
|
+
* The SAME name must be produced by every layer that keys the anonymous
|
|
373
|
+
* class (structure-phase node id, enclosing-owner walk, scope-side
|
|
374
|
+
* declaration synthesis, receiver typeBinding) — they agree by all calling
|
|
375
|
+
* this one helper.
|
|
376
|
+
*/
|
|
377
|
+
/** Type-declaration node types that can host (and name) a Java anonymous
|
|
378
|
+
* class body — javac numbers `$N` per top-level type of any of these
|
|
379
|
+
* kinds. Enum constant bodies (`A { ... }`) are a different node shape
|
|
380
|
+
* and remain unmodeled. */
|
|
381
|
+
const JAVA_ANON_HOST_TYPES = new Set([
|
|
382
|
+
'class_declaration',
|
|
383
|
+
'enum_declaration',
|
|
384
|
+
'interface_declaration',
|
|
385
|
+
'record_declaration',
|
|
386
|
+
]);
|
|
387
|
+
/** Per-parse-tree memo of anonymous-body numbering: tree → (startIndex →
|
|
388
|
+
* synthesized name). Keyed by the tree OBJECT via WeakMap so entries die
|
|
389
|
+
* with the parse; without it every call re-scans the host subtree
|
|
390
|
+
* (`descendantsOfType`), and the helper is called from four independent
|
|
391
|
+
* layers per anonymous body — quadratic on anon-heavy files (old-style
|
|
392
|
+
* listener-per-widget Java). */
|
|
393
|
+
const javaAnonNameMemo = new WeakMap();
|
|
394
|
+
export const synthesizeJavaAnonymousClassName = (node) => {
|
|
395
|
+
if (node.type !== 'object_creation_expression')
|
|
396
|
+
return undefined;
|
|
397
|
+
const hasClassBody = node.namedChildren?.some((c) => c.type === 'class_body');
|
|
398
|
+
if (hasClassBody !== true)
|
|
399
|
+
return undefined;
|
|
400
|
+
const tree = node.tree;
|
|
401
|
+
if (tree !== undefined) {
|
|
402
|
+
const cached = javaAnonNameMemo.get(tree)?.get(node.startIndex);
|
|
403
|
+
if (cached !== undefined)
|
|
404
|
+
return cached;
|
|
405
|
+
}
|
|
406
|
+
// Topmost enclosing host type declaration — javac numbers per top-level type.
|
|
407
|
+
let topHost = null;
|
|
408
|
+
let cursor = node.parent;
|
|
409
|
+
let iterations = 0;
|
|
410
|
+
while (cursor) {
|
|
411
|
+
if (++iterations > MAX_ENCLOSING_WALK_ITERATIONS)
|
|
412
|
+
return undefined;
|
|
413
|
+
if (JAVA_ANON_HOST_TYPES.has(cursor.type))
|
|
414
|
+
topHost = cursor;
|
|
415
|
+
cursor = cursor.parent;
|
|
416
|
+
}
|
|
417
|
+
if (topHost === null)
|
|
418
|
+
return undefined;
|
|
419
|
+
const topName = topHost.childForFieldName?.('name')?.text;
|
|
420
|
+
if (topName === undefined || topName.length === 0)
|
|
421
|
+
return undefined;
|
|
422
|
+
const anonBodies = (topHost.descendantsOfType?.('object_creation_expression') ?? []).filter((c) => c.namedChildren?.some((n) => n.type === 'class_body'));
|
|
423
|
+
if (tree !== undefined) {
|
|
424
|
+
let byStart = javaAnonNameMemo.get(tree);
|
|
425
|
+
if (byStart === undefined) {
|
|
426
|
+
byStart = new Map();
|
|
427
|
+
javaAnonNameMemo.set(tree, byStart);
|
|
428
|
+
}
|
|
429
|
+
for (let i = 0; i < anonBodies.length; i++) {
|
|
430
|
+
byStart.set(anonBodies[i].startIndex, `${topName}$${i + 1}`);
|
|
431
|
+
}
|
|
432
|
+
return byStart.get(node.startIndex);
|
|
433
|
+
}
|
|
434
|
+
const index = anonBodies.findIndex((c) => c.startIndex === node.startIndex);
|
|
435
|
+
if (index === -1)
|
|
436
|
+
return undefined;
|
|
437
|
+
return `${topName}$${index + 1}`;
|
|
438
|
+
};
|
|
356
439
|
export const findEnclosingClassInfo = (node, filePath, resolveEnclosingOwner,
|
|
357
440
|
/**
|
|
358
441
|
* Optional (#1978): returns the enclosing type's fully-qualified name
|
|
@@ -411,6 +494,21 @@ getQualifiedOwnerName) => {
|
|
|
411
494
|
}
|
|
412
495
|
}
|
|
413
496
|
}
|
|
497
|
+
// Java: an anonymous class body (`new Runnable() { ... }`) owns its
|
|
498
|
+
// members — attribute to the synthesized `Worker$N` class, not the
|
|
499
|
+
// lexically enclosing named class (#2550). `synthesizeJavaAnonymousClassName`
|
|
500
|
+
// returns undefined for `object_creation_expression` without a
|
|
501
|
+
// `class_body` (plain `new Foo()`, and every C# shape), so the walk
|
|
502
|
+
// continues unchanged for those.
|
|
503
|
+
if (current.type === 'object_creation_expression') {
|
|
504
|
+
const anonName = synthesizeJavaAnonymousClassName(current);
|
|
505
|
+
if (anonName !== undefined) {
|
|
506
|
+
return {
|
|
507
|
+
classId: generateId('Class', `${filePath}:${anonName}`),
|
|
508
|
+
className: anonName,
|
|
509
|
+
};
|
|
510
|
+
}
|
|
511
|
+
}
|
|
414
512
|
if (CLASS_CONTAINER_TYPES.has(current.type)) {
|
|
415
513
|
// Delegate language-specific container remapping to the provider hook.
|
|
416
514
|
if (resolveEnclosingOwner) {
|
package/dist/core/run-analyze.js
CHANGED
|
@@ -15,7 +15,7 @@ import { runPipelineFromRepo } from './ingestion/pipeline.js';
|
|
|
15
15
|
import { resetDegradedParseCounter } from './tree-sitter/safe-parse.js';
|
|
16
16
|
import { initLbug, loadGraphToLbug, getLbugStats, executeQuery, executeWithReusedStatement, closeLbug, closeLbugBeforeExit, loadCachedEmbeddings, deleteNodesForFiles, deleteAllCommunitiesAndProcesses, deleteAllInterprocTaintPaths, deleteAllCallSummaries, deleteAllInjects, queryImportersBatch, loadFTSExtension, wipeLbugDbFiles, LbugWipeError, DELETE_FILES_CHUNK_SIZE, } from './lbug/lbug-adapter.js';
|
|
17
17
|
import { escapeCypherString } from './lbug/cypher-escape.js';
|
|
18
|
-
import { createSearchFTSIndexes, initialiseSearchFTSStemmer, verifySearchFTSIndexes, } from './search/fts-indexes.js';
|
|
18
|
+
import { buildSearchIndexesOrDegrade, createSearchFTSIndexes, initialiseSearchFTSStemmer, verifySearchFTSIndexes, } from './search/fts-indexes.js';
|
|
19
19
|
import { cjkSegmentationModeMismatch, getSearchFTSCjkSegmentation, initialiseSearchFTSCjkSegmentation, } from './search/cjk-segmentation.js';
|
|
20
20
|
import { getExtensionCapabilities, resolveAnalyzeInstallPolicy } from './lbug/extension-loader.js';
|
|
21
21
|
import { diagnoseExtensionLoad } from './lbug/extension-load-error.js';
|
|
@@ -1206,8 +1206,17 @@ export async function runFullAnalysis(repoPath, options, callbacks) {
|
|
|
1206
1206
|
const ftsAvailable = await loadFTSExtension(undefined, {
|
|
1207
1207
|
policy: resolveAnalyzeInstallPolicy(),
|
|
1208
1208
|
});
|
|
1209
|
+
// Tracks whether search indexes actually ended up usable this run — starts
|
|
1210
|
+
// as ftsAvailable (extension loaded) but flips to false below when the
|
|
1211
|
+
// build/verify step itself fails, so capabilities.fts.status / ftsSkipped
|
|
1212
|
+
// stay honest even though that failure no longer aborts the whole analyze.
|
|
1213
|
+
let ftsReady = ftsAvailable;
|
|
1209
1214
|
if (ftsAvailable) {
|
|
1210
|
-
|
|
1215
|
+
// Degrade rather than throw: createSearchFTSIndexes re-tokenizes every
|
|
1216
|
+
// stored row on every run, so a native tokenizer error on a single
|
|
1217
|
+
// pre-existing row (#2544/#2546) must not discard this run's otherwise-
|
|
1218
|
+
// successful graph/embeddings work — only keyword search degrades.
|
|
1219
|
+
const ftsResult = await buildSearchIndexesOrDegrade(executeQuery, {
|
|
1211
1220
|
onIndexStart: options.verbose
|
|
1212
1221
|
? (table, indexName) => log(`FTS: creating ${table}.${indexName}`)
|
|
1213
1222
|
: undefined,
|
|
@@ -1215,12 +1224,15 @@ export async function runFullAnalysis(repoPath, options, callbacks) {
|
|
|
1215
1224
|
? (table, indexName) => log(`FTS: ready ${table}.${indexName}`)
|
|
1216
1225
|
: undefined,
|
|
1217
1226
|
});
|
|
1218
|
-
|
|
1219
|
-
|
|
1220
|
-
|
|
1221
|
-
|
|
1227
|
+
if (ftsResult.ok) {
|
|
1228
|
+
progress('fts', 90, 'Search indexes ready');
|
|
1229
|
+
}
|
|
1230
|
+
else {
|
|
1231
|
+
ftsReady = false;
|
|
1232
|
+
log(`FTS index build failed (${ftsResult.error}) — keyword search degraded this run. ` +
|
|
1233
|
+
'Graph and embeddings analysis completed successfully. Run `gitnexus analyze --repair-fts` to retry.');
|
|
1234
|
+
progress('fts', 90, 'Search indexes skipped (build failed)');
|
|
1222
1235
|
}
|
|
1223
|
-
progress('fts', 90, 'Search indexes ready');
|
|
1224
1236
|
}
|
|
1225
1237
|
else {
|
|
1226
1238
|
// For a missing runtime dependency (#2374) the file is present, so the
|
|
@@ -1559,7 +1571,7 @@ export async function runFullAnalysis(repoPath, options, callbacks) {
|
|
|
1559
1571
|
// meta.json / `gitnexus doctor` honest about degraded search.
|
|
1560
1572
|
fts: {
|
|
1561
1573
|
provider: 'ladybugdb-fts',
|
|
1562
|
-
status:
|
|
1574
|
+
status: ftsReady ? runtimeCapabilities.fts : 'unavailable',
|
|
1563
1575
|
},
|
|
1564
1576
|
vectorSearch: {
|
|
1565
1577
|
provider: effectiveSemanticMode === 'vector-index' ? 'ladybugdb-vector' : 'exact-scan',
|
|
@@ -1723,7 +1735,7 @@ export async function runFullAnalysis(repoPath, options, callbacks) {
|
|
|
1723
1735
|
repoPath,
|
|
1724
1736
|
stats: meta.stats,
|
|
1725
1737
|
pipelineResult,
|
|
1726
|
-
ftsSkipped: !
|
|
1738
|
+
ftsSkipped: !ftsReady,
|
|
1727
1739
|
isPrimaryBranch: !placement.branch,
|
|
1728
1740
|
};
|
|
1729
1741
|
}
|
|
@@ -26,3 +26,18 @@ export declare function initialiseSearchFTSStemmer(): string;
|
|
|
26
26
|
export declare function getSearchFTSStemmer(): string;
|
|
27
27
|
export declare function createSearchFTSIndexes(options?: CreateSearchFTSIndexesOptions): Promise<void>;
|
|
28
28
|
export declare function verifySearchFTSIndexes(executeQuery: (cypher: string) => Promise<unknown[]>): Promise<string[]>;
|
|
29
|
+
export interface BuildSearchIndexesResult {
|
|
30
|
+
ok: boolean;
|
|
31
|
+
error?: string;
|
|
32
|
+
}
|
|
33
|
+
/**
|
|
34
|
+
* Build + verify FTS indexes, catching any failure instead of letting it
|
|
35
|
+
* propagate. `createSearchFTSIndexes` re-tokenizes every stored row on every
|
|
36
|
+
* analyze run (see the `ponytail:` comment above) — a native LadybugDB
|
|
37
|
+
* tokenizer error on a single pre-existing row (e.g. a "Failed calling
|
|
38
|
+
* LOWER: Invalid UTF-8", #2544/#2546) must not discard an otherwise-
|
|
39
|
+
* successful analyze's graph/embeddings work. The caller degrades keyword
|
|
40
|
+
* search for this run instead, mirroring the existing FTS-extension-
|
|
41
|
+
* unavailable degrade path in `run-analyze.ts`.
|
|
42
|
+
*/
|
|
43
|
+
export declare function buildSearchIndexesOrDegrade(executeQuery: (cypher: string) => Promise<unknown[]>, options?: CreateSearchFTSIndexesOptions): Promise<BuildSearchIndexesResult>;
|
|
@@ -152,3 +152,26 @@ export async function verifySearchFTSIndexes(executeQuery) {
|
|
|
152
152
|
}
|
|
153
153
|
return missing;
|
|
154
154
|
}
|
|
155
|
+
/**
|
|
156
|
+
* Build + verify FTS indexes, catching any failure instead of letting it
|
|
157
|
+
* propagate. `createSearchFTSIndexes` re-tokenizes every stored row on every
|
|
158
|
+
* analyze run (see the `ponytail:` comment above) — a native LadybugDB
|
|
159
|
+
* tokenizer error on a single pre-existing row (e.g. a "Failed calling
|
|
160
|
+
* LOWER: Invalid UTF-8", #2544/#2546) must not discard an otherwise-
|
|
161
|
+
* successful analyze's graph/embeddings work. The caller degrades keyword
|
|
162
|
+
* search for this run instead, mirroring the existing FTS-extension-
|
|
163
|
+
* unavailable degrade path in `run-analyze.ts`.
|
|
164
|
+
*/
|
|
165
|
+
export async function buildSearchIndexesOrDegrade(executeQuery, options) {
|
|
166
|
+
try {
|
|
167
|
+
await createSearchFTSIndexes(options);
|
|
168
|
+
const missing = await verifySearchFTSIndexes(executeQuery);
|
|
169
|
+
if (missing.length > 0) {
|
|
170
|
+
return { ok: false, error: `missing indexes after build: ${missing.join(', ')}` };
|
|
171
|
+
}
|
|
172
|
+
return { ok: true };
|
|
173
|
+
}
|
|
174
|
+
catch (e) {
|
|
175
|
+
return { ok: false, error: e instanceof Error ? e.message : String(e) };
|
|
176
|
+
}
|
|
177
|
+
}
|
|
@@ -52,7 +52,7 @@ import { fileURLToPath } from 'url';
|
|
|
52
52
|
// the main thread (the #1983 OOM). Because the two stores share this version,
|
|
53
53
|
// any future change to the `ParsedFile` serialization shape MUST bump
|
|
54
54
|
// SCHEMA_BUMP so both invalidate in lockstep.
|
|
55
|
-
const SCHEMA_BUMP =
|
|
55
|
+
const SCHEMA_BUMP = 18; // Java anonymous class bodies emit synthesized Worker$N Class nodes and re-keyed methods (#2550). (17 = callable-value-flow operand identity; 16 = direct callee identity; 15 = always-on callableFlowSites.)
|
|
56
56
|
const GITNEXUS_PKG_VERSION = (() => {
|
|
57
57
|
try {
|
|
58
58
|
// package.json sits at gitnexus/package.json — two levels up from
|
|
@@ -335,8 +335,14 @@ export interface RepoMeta {
|
|
|
335
335
|
* only covers changed files (`computeEffectiveWriteSet`), so a top-up against a
|
|
336
336
|
* pre-v7 index would silently omit the new edges for every unchanged file pair;
|
|
337
337
|
* force a full re-analyze instead (same contract as v2–v6).
|
|
338
|
-
|
|
339
|
-
|
|
338
|
+
* v8: Java anonymous class bodies became first-class Class nodes (#2550):
|
|
339
|
+
* `new Runnable() { run(){} }` now emits `Class:...:Worker$1` and its methods
|
|
340
|
+
* re-keyed from `Worker.run` to `Worker$1.run`. Node identities move on
|
|
341
|
+
* unchanged files — a top-up against a pre-v8 index would strand the old
|
|
342
|
+
* `Worker.run`-keyed Method nodes alongside the new ones (the v5 Route
|
|
343
|
+
* precedent); force a full re-analyze instead.
|
|
344
|
+
*/
|
|
345
|
+
export declare const INCREMENTAL_SCHEMA_VERSION = 8;
|
|
340
346
|
export interface IndexedRepo {
|
|
341
347
|
repoPath: string;
|
|
342
348
|
storagePath: string;
|
|
@@ -112,8 +112,14 @@ export const cloneDirBelongsToEntry = (cloneDir, entryPath) => registryPathEqual
|
|
|
112
112
|
* only covers changed files (`computeEffectiveWriteSet`), so a top-up against a
|
|
113
113
|
* pre-v7 index would silently omit the new edges for every unchanged file pair;
|
|
114
114
|
* force a full re-analyze instead (same contract as v2–v6).
|
|
115
|
+
* v8: Java anonymous class bodies became first-class Class nodes (#2550):
|
|
116
|
+
* `new Runnable() { run(){} }` now emits `Class:...:Worker$1` and its methods
|
|
117
|
+
* re-keyed from `Worker.run` to `Worker$1.run`. Node identities move on
|
|
118
|
+
* unchanged files — a top-up against a pre-v8 index would strand the old
|
|
119
|
+
* `Worker.run`-keyed Method nodes alongside the new ones (the v5 Route
|
|
120
|
+
* precedent); force a full re-analyze instead.
|
|
115
121
|
*/
|
|
116
|
-
export const INCREMENTAL_SCHEMA_VERSION =
|
|
122
|
+
export const INCREMENTAL_SCHEMA_VERSION = 8;
|
|
117
123
|
const GITNEXUS_DIR = '.gitnexus';
|
|
118
124
|
const GITNEXUS_EXCLUDE_ENTRY = `${GITNEXUS_DIR}/`;
|
|
119
125
|
export const INDEX_METADATA_FILE = 'gitnexus.json';
|
package/package.json
CHANGED