gitnexus 1.5.3 → 1.6.0
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/README.md +10 -0
- package/dist/_shared/graph/types.d.ts +1 -1
- package/dist/_shared/graph/types.d.ts.map +1 -1
- package/dist/_shared/index.d.ts +1 -0
- package/dist/_shared/index.d.ts.map +1 -1
- package/dist/_shared/language-detection.d.ts.map +1 -1
- package/dist/_shared/language-detection.js +2 -0
- package/dist/_shared/language-detection.js.map +1 -1
- package/dist/_shared/languages.d.ts +1 -0
- package/dist/_shared/languages.d.ts.map +1 -1
- package/dist/_shared/languages.js +1 -0
- package/dist/_shared/languages.js.map +1 -1
- package/dist/_shared/lbug/schema-constants.d.ts +1 -1
- package/dist/_shared/lbug/schema-constants.d.ts.map +1 -1
- package/dist/_shared/lbug/schema-constants.js +3 -1
- package/dist/_shared/lbug/schema-constants.js.map +1 -1
- package/dist/_shared/mro-strategy.d.ts +19 -0
- package/dist/_shared/mro-strategy.d.ts.map +1 -0
- package/dist/_shared/mro-strategy.js +2 -0
- package/dist/_shared/mro-strategy.js.map +1 -0
- package/dist/cli/ai-context.d.ts +1 -0
- package/dist/cli/ai-context.js +28 -4
- package/dist/cli/analyze.d.ts +2 -0
- package/dist/cli/analyze.js +2 -1
- package/dist/cli/group.d.ts +2 -0
- package/dist/cli/group.js +233 -0
- package/dist/cli/index.js +3 -0
- package/dist/cli/serve.js +4 -1
- package/dist/cli/setup.js +34 -3
- package/dist/config/ignore-service.js +8 -3
- package/dist/core/augmentation/engine.js +1 -1
- package/dist/core/git-staleness.d.ts +13 -0
- package/dist/core/git-staleness.js +29 -0
- package/dist/core/group/bridge-db.d.ts +82 -0
- package/dist/core/group/bridge-db.js +460 -0
- package/dist/core/group/bridge-schema.d.ts +27 -0
- package/dist/core/group/bridge-schema.js +55 -0
- package/dist/core/group/config-parser.d.ts +3 -0
- package/dist/core/group/config-parser.js +83 -0
- package/dist/core/group/contract-extractor.d.ts +7 -0
- package/dist/core/group/contract-extractor.js +1 -0
- package/dist/core/group/extractors/grpc-extractor.d.ts +16 -0
- package/dist/core/group/extractors/grpc-extractor.js +264 -0
- package/dist/core/group/extractors/http-route-extractor.d.ts +24 -0
- package/dist/core/group/extractors/http-route-extractor.js +428 -0
- package/dist/core/group/extractors/topic-extractor.d.ts +9 -0
- package/dist/core/group/extractors/topic-extractor.js +234 -0
- package/dist/core/group/matching.d.ts +13 -0
- package/dist/core/group/matching.js +198 -0
- package/dist/core/group/normalization.d.ts +3 -0
- package/dist/core/group/normalization.js +115 -0
- package/dist/core/group/service-boundary-detector.d.ts +8 -0
- package/dist/core/group/service-boundary-detector.js +155 -0
- package/dist/core/group/service.d.ts +46 -0
- package/dist/core/group/service.js +160 -0
- package/dist/core/group/storage.d.ts +9 -0
- package/dist/core/group/storage.js +91 -0
- package/dist/core/group/sync.d.ts +21 -0
- package/dist/core/group/sync.js +148 -0
- package/dist/core/group/types.d.ts +130 -0
- package/dist/core/group/types.js +1 -0
- package/dist/core/ingestion/binding-accumulator.d.ts +207 -0
- package/dist/core/ingestion/binding-accumulator.js +332 -0
- package/dist/core/ingestion/call-processor.d.ts +155 -24
- package/dist/core/ingestion/call-processor.js +1129 -247
- package/dist/core/ingestion/class-extractors/generic.d.ts +2 -0
- package/dist/core/ingestion/class-extractors/generic.js +135 -0
- package/dist/core/ingestion/class-types.d.ts +34 -0
- package/dist/core/ingestion/class-types.js +1 -0
- package/dist/core/ingestion/entry-point-scoring.d.ts +1 -0
- package/dist/core/ingestion/entry-point-scoring.js +1 -0
- package/dist/core/ingestion/field-types.d.ts +2 -2
- package/dist/core/ingestion/filesystem-walker.js +8 -0
- package/dist/core/ingestion/framework-detection.d.ts +1 -0
- package/dist/core/ingestion/framework-detection.js +1 -0
- package/dist/core/ingestion/heritage-processor.d.ts +8 -15
- package/dist/core/ingestion/heritage-processor.js +15 -28
- package/dist/core/ingestion/import-processor.d.ts +1 -11
- package/dist/core/ingestion/import-processor.js +0 -12
- package/dist/core/ingestion/import-resolvers/utils.js +1 -0
- package/dist/core/ingestion/import-resolvers/vue.d.ts +8 -0
- package/dist/core/ingestion/import-resolvers/vue.js +9 -0
- package/dist/core/ingestion/language-provider.d.ts +6 -3
- package/dist/core/ingestion/languages/c-cpp.js +168 -1
- package/dist/core/ingestion/languages/csharp.js +20 -0
- package/dist/core/ingestion/languages/dart.js +26 -4
- package/dist/core/ingestion/languages/go.js +22 -0
- package/dist/core/ingestion/languages/index.d.ts +1 -0
- package/dist/core/ingestion/languages/index.js +2 -0
- package/dist/core/ingestion/languages/java.js +17 -0
- package/dist/core/ingestion/languages/kotlin.js +24 -1
- package/dist/core/ingestion/languages/php.js +23 -11
- package/dist/core/ingestion/languages/python.js +9 -0
- package/dist/core/ingestion/languages/ruby.js +28 -0
- package/dist/core/ingestion/languages/rust.js +38 -0
- package/dist/core/ingestion/languages/swift.js +31 -0
- package/dist/core/ingestion/languages/typescript.d.ts +1 -0
- package/dist/core/ingestion/languages/typescript.js +52 -3
- package/dist/core/ingestion/languages/vue.d.ts +13 -0
- package/dist/core/ingestion/languages/vue.js +81 -0
- package/dist/core/ingestion/method-extractors/configs/c-cpp.d.ts +3 -0
- package/dist/core/ingestion/method-extractors/configs/c-cpp.js +387 -0
- package/dist/core/ingestion/method-extractors/configs/csharp.js +5 -1
- package/dist/core/ingestion/method-extractors/configs/dart.d.ts +2 -0
- package/dist/core/ingestion/method-extractors/configs/dart.js +376 -0
- package/dist/core/ingestion/method-extractors/configs/go.d.ts +2 -0
- package/dist/core/ingestion/method-extractors/configs/go.js +176 -0
- package/dist/core/ingestion/method-extractors/configs/jvm.js +13 -4
- package/dist/core/ingestion/method-extractors/configs/php.d.ts +2 -0
- package/dist/core/ingestion/method-extractors/configs/php.js +304 -0
- package/dist/core/ingestion/method-extractors/configs/python.d.ts +2 -0
- package/dist/core/ingestion/method-extractors/configs/python.js +309 -0
- package/dist/core/ingestion/method-extractors/configs/ruby.d.ts +2 -0
- package/dist/core/ingestion/method-extractors/configs/ruby.js +285 -0
- package/dist/core/ingestion/method-extractors/configs/rust.d.ts +2 -0
- package/dist/core/ingestion/method-extractors/configs/rust.js +195 -0
- package/dist/core/ingestion/method-extractors/configs/swift.d.ts +2 -0
- package/dist/core/ingestion/method-extractors/configs/swift.js +277 -0
- package/dist/core/ingestion/method-extractors/configs/typescript-javascript.js +85 -8
- package/dist/core/ingestion/method-extractors/generic.js +38 -15
- package/dist/core/ingestion/method-types.d.ts +25 -0
- package/dist/core/ingestion/model/field-registry.d.ts +18 -0
- package/dist/core/ingestion/model/field-registry.js +22 -0
- package/dist/core/ingestion/model/heritage-map.d.ts +70 -0
- package/dist/core/ingestion/model/heritage-map.js +159 -0
- package/dist/core/ingestion/model/index.d.ts +20 -0
- package/dist/core/ingestion/model/index.js +41 -0
- package/dist/core/ingestion/model/method-registry.d.ts +62 -0
- package/dist/core/ingestion/model/method-registry.js +130 -0
- package/dist/core/ingestion/model/registration-table.d.ts +139 -0
- package/dist/core/ingestion/model/registration-table.js +224 -0
- package/dist/core/ingestion/model/resolution-context.d.ts +93 -0
- package/dist/core/ingestion/model/resolution-context.js +337 -0
- package/dist/core/ingestion/model/resolve.d.ts +56 -0
- package/dist/core/ingestion/model/resolve.js +242 -0
- package/dist/core/ingestion/model/semantic-model.d.ts +86 -0
- package/dist/core/ingestion/model/semantic-model.js +120 -0
- package/dist/core/ingestion/model/symbol-table.d.ts +222 -0
- package/dist/core/ingestion/model/symbol-table.js +206 -0
- package/dist/core/ingestion/model/type-registry.d.ts +39 -0
- package/dist/core/ingestion/model/type-registry.js +62 -0
- package/dist/core/ingestion/mro-processor.d.ts +4 -3
- package/dist/core/ingestion/mro-processor.js +310 -106
- package/dist/core/ingestion/parsing-processor.d.ts +5 -4
- package/dist/core/ingestion/parsing-processor.js +210 -85
- package/dist/core/ingestion/pipeline.d.ts +2 -0
- package/dist/core/ingestion/pipeline.js +192 -68
- package/dist/core/ingestion/tree-sitter-queries.d.ts +5 -5
- package/dist/core/ingestion/tree-sitter-queries.js +21 -0
- package/dist/core/ingestion/type-env.d.ts +15 -2
- package/dist/core/ingestion/type-env.js +163 -102
- package/dist/core/ingestion/type-extractors/csharp.js +17 -0
- package/dist/core/ingestion/type-extractors/jvm.js +11 -0
- package/dist/core/ingestion/type-extractors/php.js +0 -55
- package/dist/core/ingestion/type-extractors/ruby.js +0 -32
- package/dist/core/ingestion/type-extractors/swift.js +13 -0
- package/dist/core/ingestion/type-extractors/types.d.ts +8 -8
- package/dist/core/ingestion/type-extractors/typescript.js +66 -69
- package/dist/core/ingestion/utils/ast-helpers.d.ts +33 -43
- package/dist/core/ingestion/utils/ast-helpers.js +129 -572
- package/dist/core/ingestion/utils/method-props.d.ts +32 -0
- package/dist/core/ingestion/utils/method-props.js +147 -0
- package/dist/core/ingestion/vue-sfc-extractor.d.ts +44 -0
- package/dist/core/ingestion/vue-sfc-extractor.js +94 -0
- package/dist/core/ingestion/workers/parse-worker.d.ts +31 -19
- package/dist/core/ingestion/workers/parse-worker.js +463 -198
- package/dist/core/lbug/lbug-adapter.d.ts +6 -0
- package/dist/core/lbug/lbug-adapter.js +68 -3
- package/dist/core/lbug/pool-adapter.d.ts +76 -0
- package/dist/core/lbug/pool-adapter.js +522 -0
- package/dist/core/run-analyze.d.ts +2 -0
- package/dist/core/run-analyze.js +1 -1
- package/dist/core/search/bm25-index.js +1 -1
- package/dist/core/tree-sitter/parser-loader.js +1 -0
- package/dist/core/wiki/graph-queries.js +1 -1
- package/dist/mcp/core/embedder.js +6 -5
- package/dist/mcp/core/lbug-adapter.d.ts +3 -63
- package/dist/mcp/core/lbug-adapter.js +3 -484
- package/dist/mcp/local/local-backend.d.ts +31 -2
- package/dist/mcp/local/local-backend.js +255 -46
- package/dist/mcp/resources.js +5 -4
- package/dist/mcp/staleness.d.ts +3 -13
- package/dist/mcp/staleness.js +2 -31
- package/dist/mcp/tools.js +80 -4
- package/dist/server/analyze-job.d.ts +2 -0
- package/dist/server/analyze-job.js +4 -0
- package/dist/server/api.d.ts +20 -1
- package/dist/server/api.js +306 -71
- package/dist/server/git-clone.d.ts +2 -1
- package/dist/server/git-clone.js +98 -5
- package/dist/storage/git.d.ts +13 -0
- package/dist/storage/git.js +25 -0
- package/dist/storage/repo-manager.js +1 -1
- package/package.json +8 -2
- package/scripts/patch-tree-sitter-swift.cjs +78 -0
- package/dist/core/ingestion/named-binding-processor.d.ts +0 -18
- package/dist/core/ingestion/named-binding-processor.js +0 -42
- package/dist/core/ingestion/resolution-context.d.ts +0 -58
- package/dist/core/ingestion/resolution-context.js +0 -135
- package/dist/core/ingestion/symbol-table.d.ts +0 -79
- package/dist/core/ingestion/symbol-table.js +0 -115
|
@@ -35,14 +35,15 @@ try {
|
|
|
35
35
|
}
|
|
36
36
|
catch { }
|
|
37
37
|
import { getLanguageFromFilename } from '../../../_shared/index.js';
|
|
38
|
-
import { FUNCTION_NODE_TYPES,
|
|
38
|
+
import { FUNCTION_NODE_TYPES, getDefinitionNodeFromCaptures, findEnclosingClassInfo, getLabelFromCaptures, findDescendant, extractStringContent, genericFuncName, inferFunctionLabel, CLASS_CONTAINER_TYPES, } from '../utils/ast-helpers.js';
|
|
39
39
|
import { countCallArguments, inferCallForm, extractReceiverName, extractReceiverNode, extractMixedChain, extractCallArgTypes, } from '../utils/call-analysis.js';
|
|
40
40
|
import { extractParsedCallSite } from '../call-sites/extract-language-call-site.js';
|
|
41
41
|
import { buildTypeEnv } from '../type-env.js';
|
|
42
42
|
import { detectFrameworkFromAST } from '../framework-detection.js';
|
|
43
43
|
import { generateId } from '../../../lib/utils.js';
|
|
44
44
|
import { preprocessImportPath } from '../import-processor.js';
|
|
45
|
-
import {
|
|
45
|
+
import { extractVueScript, extractTemplateComponents, isVueSetupTopLevel, } from '../vue-sfc-extractor.js';
|
|
46
|
+
import { buildMethodProps, arityForIdFromInfo, typeTagForId, constTagForId, buildCollisionGroups, } from '../utils/method-props.js';
|
|
46
47
|
// ============================================================================
|
|
47
48
|
// Worker-local parser + language map
|
|
48
49
|
// ============================================================================
|
|
@@ -61,6 +62,7 @@ const languageMap = {
|
|
|
61
62
|
...(Kotlin ? { [SupportedLanguages.Kotlin]: Kotlin } : {}),
|
|
62
63
|
[SupportedLanguages.PHP]: PHP.php_only,
|
|
63
64
|
[SupportedLanguages.Ruby]: Ruby,
|
|
65
|
+
[SupportedLanguages.Vue]: TypeScript.typescript,
|
|
64
66
|
...(Dart ? { [SupportedLanguages.Dart]: Dart } : {}),
|
|
65
67
|
...(Swift ? { [SupportedLanguages.Swift]: Swift } : {}),
|
|
66
68
|
};
|
|
@@ -114,21 +116,100 @@ function findEnclosingClassNode(node) {
|
|
|
114
116
|
let current = node.parent;
|
|
115
117
|
while (current) {
|
|
116
118
|
if (CLASS_CONTAINER_TYPES.has(current.type)) {
|
|
119
|
+
// Return singleton_class directly so the method extractor sees it as
|
|
120
|
+
// the owner node and correctly marks methods as static. Name resolution
|
|
121
|
+
// for qualified names is handled separately by findEnclosingClassInfo.
|
|
117
122
|
return current;
|
|
118
123
|
}
|
|
119
124
|
current = current.parent;
|
|
120
125
|
}
|
|
121
126
|
return null;
|
|
122
127
|
}
|
|
128
|
+
/**
|
|
129
|
+
* For C++ out-of-class method definitions (e.g. `void Foo::bar() {}`), extract the
|
|
130
|
+
* class name from the qualified_identifier scope and find the class declaration in the
|
|
131
|
+
* file's AST. Returns the class SyntaxNode or null if not found.
|
|
132
|
+
*
|
|
133
|
+
* Handles pointer/reference return types where function_declarator is nested inside
|
|
134
|
+
* pointer_declarator or reference_declarator.
|
|
135
|
+
*/
|
|
136
|
+
function findClassNodeByQualifiedName(node) {
|
|
137
|
+
const declarator = node.childForFieldName('declarator');
|
|
138
|
+
if (!declarator)
|
|
139
|
+
return null;
|
|
140
|
+
// Find the function_declarator, recursively unwrapping pointer_declarator /
|
|
141
|
+
// reference_declarator chains (e.g. int** Foo::bar() has
|
|
142
|
+
// pointer_declarator → pointer_declarator → function_declarator).
|
|
143
|
+
let funcDecl = null;
|
|
144
|
+
if (declarator.type === 'function_declarator') {
|
|
145
|
+
funcDecl = declarator;
|
|
146
|
+
}
|
|
147
|
+
else {
|
|
148
|
+
let current = declarator;
|
|
149
|
+
while (current && !funcDecl) {
|
|
150
|
+
for (let i = 0; i < current.namedChildCount; i++) {
|
|
151
|
+
const child = current.namedChild(i);
|
|
152
|
+
if (child?.type === 'function_declarator') {
|
|
153
|
+
funcDecl = child;
|
|
154
|
+
break;
|
|
155
|
+
}
|
|
156
|
+
}
|
|
157
|
+
if (!funcDecl) {
|
|
158
|
+
const next = current.namedChildren.find((c) => c.type === 'pointer_declarator' || c.type === 'reference_declarator');
|
|
159
|
+
current = next ?? null;
|
|
160
|
+
}
|
|
161
|
+
}
|
|
162
|
+
}
|
|
163
|
+
if (!funcDecl)
|
|
164
|
+
return null;
|
|
165
|
+
// Check if the inner declarator is a qualified_identifier (Foo::bar)
|
|
166
|
+
const innerDecl = funcDecl.childForFieldName('declarator');
|
|
167
|
+
if (!innerDecl || innerDecl.type !== 'qualified_identifier')
|
|
168
|
+
return null;
|
|
169
|
+
const scope = innerDecl.childForFieldName('scope');
|
|
170
|
+
if (!scope)
|
|
171
|
+
return null;
|
|
172
|
+
const className = scope.text;
|
|
173
|
+
// Search the file for a matching class/struct specifier, including inside
|
|
174
|
+
// namespace_definition blocks (the majority of production C++ uses namespaces).
|
|
175
|
+
const root = node.tree.rootNode;
|
|
176
|
+
const classTypes = new Set(['class_specifier', 'struct_specifier']);
|
|
177
|
+
const searchIn = (parent) => {
|
|
178
|
+
for (let i = 0; i < parent.namedChildCount; i++) {
|
|
179
|
+
const child = parent.namedChild(i);
|
|
180
|
+
if (!child)
|
|
181
|
+
continue;
|
|
182
|
+
if (classTypes.has(child.type)) {
|
|
183
|
+
const nameNode = child.childForFieldName('name');
|
|
184
|
+
if (nameNode?.text === className)
|
|
185
|
+
return child;
|
|
186
|
+
}
|
|
187
|
+
// Recurse into namespace blocks
|
|
188
|
+
if (child.type === 'namespace_definition') {
|
|
189
|
+
const found = searchIn(child);
|
|
190
|
+
if (found)
|
|
191
|
+
return found;
|
|
192
|
+
}
|
|
193
|
+
}
|
|
194
|
+
return null;
|
|
195
|
+
};
|
|
196
|
+
return searchIn(root);
|
|
197
|
+
}
|
|
123
198
|
/**
|
|
124
199
|
* Minimal no-op SymbolTable stub for FieldExtractorContext in the worker.
|
|
125
|
-
* Field extraction only uses symbolTable.lookupExactAll for optional type
|
|
126
|
-
* returning [] causes the extractor to use the raw type
|
|
200
|
+
* Field extraction only uses symbolTable.lookupExactAll for optional type
|
|
201
|
+
* resolution — returning [] causes the extractor to use the raw type
|
|
202
|
+
* string, which is fine for us. Every other method is a no-op so the
|
|
203
|
+
* stub remains safe if a future FieldExtractor consults it through the
|
|
204
|
+
* full {@link SymbolTableReader} surface.
|
|
127
205
|
*/
|
|
128
206
|
const NOOP_SYMBOL_TABLE = {
|
|
129
|
-
lookupExactAll: () => [],
|
|
130
207
|
lookupExact: () => undefined,
|
|
131
208
|
lookupExactFull: () => undefined,
|
|
209
|
+
lookupExactAll: () => [],
|
|
210
|
+
lookupCallableByName: () => [],
|
|
211
|
+
getFiles: () => [][Symbol.iterator](),
|
|
212
|
+
getStats: () => ({ fileCount: 0 }),
|
|
132
213
|
};
|
|
133
214
|
/**
|
|
134
215
|
* Get (or extract and cache) field info for a class node.
|
|
@@ -180,6 +261,9 @@ function getMethodInfo(classNode, provider, context) {
|
|
|
180
261
|
methodInfoCache.set(cacheKey, cached);
|
|
181
262
|
return cached;
|
|
182
263
|
}
|
|
264
|
+
// ============================================================================
|
|
265
|
+
// Enclosing function detection (for call extraction) — cached
|
|
266
|
+
// ============================================================================
|
|
183
267
|
/** Walk up AST to find enclosing function, return its generateId or null for top-level.
|
|
184
268
|
* Applies provider.labelOverride so the label matches the definition phase (single source of truth). */
|
|
185
269
|
const findEnclosingFunctionId = (node, filePath, provider) => {
|
|
@@ -189,7 +273,9 @@ const findEnclosingFunctionId = (node, filePath, provider) => {
|
|
|
189
273
|
let current = node.parent;
|
|
190
274
|
while (current) {
|
|
191
275
|
if (FUNCTION_NODE_TYPES.has(current.type)) {
|
|
192
|
-
const
|
|
276
|
+
const efnResult = provider.methodExtractor?.extractFunctionName?.(current);
|
|
277
|
+
const funcName = efnResult?.funcName ?? genericFuncName(current);
|
|
278
|
+
const label = efnResult?.label ?? inferFunctionLabel(current.type);
|
|
193
279
|
if (funcName) {
|
|
194
280
|
// Apply labelOverride so label matches definition phase (e.g., Kotlin Function→Method).
|
|
195
281
|
// null means "skip as definition" — keep original label for scope identification.
|
|
@@ -199,7 +285,39 @@ const findEnclosingFunctionId = (node, filePath, provider) => {
|
|
|
199
285
|
if (override !== null)
|
|
200
286
|
finalLabel = override;
|
|
201
287
|
}
|
|
202
|
-
|
|
288
|
+
// Qualify with enclosing class to match definition-phase node IDs
|
|
289
|
+
const classInfo = cachedFindEnclosingClassInfo(current, filePath);
|
|
290
|
+
const qualifiedName = classInfo ? `${classInfo.className}.${funcName}` : funcName;
|
|
291
|
+
// Include #<arity> suffix to match definition-phase Method/Constructor IDs.
|
|
292
|
+
// Use the same MethodExtractor (getMethodInfo) as the definition phase.
|
|
293
|
+
// When same-arity collisions exist, also append ~type1,type2.
|
|
294
|
+
let arity;
|
|
295
|
+
let encTypeTag = '';
|
|
296
|
+
if (finalLabel === 'Method' || finalLabel === 'Constructor') {
|
|
297
|
+
const encLang = getLanguageFromFilename(filePath);
|
|
298
|
+
const classNode = findEnclosingClassNode(current) ?? findClassNodeByQualifiedName(current);
|
|
299
|
+
if (classNode && encLang) {
|
|
300
|
+
const methodMap = getMethodInfo(classNode, provider, {
|
|
301
|
+
filePath,
|
|
302
|
+
language: encLang,
|
|
303
|
+
});
|
|
304
|
+
const defLine = current.startPosition.row + 1;
|
|
305
|
+
const info = methodMap?.get(`${funcName}:${defLine}`);
|
|
306
|
+
if (info) {
|
|
307
|
+
arity = info.parameters.some((p) => p.isVariadic)
|
|
308
|
+
? undefined
|
|
309
|
+
: info.parameters.length;
|
|
310
|
+
if (methodMap && arity !== undefined) {
|
|
311
|
+
const g = buildCollisionGroups(methodMap);
|
|
312
|
+
encTypeTag =
|
|
313
|
+
typeTagForId(methodMap, funcName, arity, info, encLang, g) +
|
|
314
|
+
constTagForId(methodMap, funcName, arity, info, g);
|
|
315
|
+
}
|
|
316
|
+
}
|
|
317
|
+
}
|
|
318
|
+
}
|
|
319
|
+
const arityTag = arity !== undefined ? `#${arity}${encTypeTag}` : '';
|
|
320
|
+
const result = generateId(finalLabel, `${filePath}:${qualifiedName}${arityTag}`);
|
|
203
321
|
functionIdCache.set(node, result);
|
|
204
322
|
return result;
|
|
205
323
|
}
|
|
@@ -215,7 +333,41 @@ const findEnclosingFunctionId = (node, filePath, provider) => {
|
|
|
215
333
|
if (override !== null)
|
|
216
334
|
finalLabel = override;
|
|
217
335
|
}
|
|
218
|
-
|
|
336
|
+
// Qualify custom result with enclosing class
|
|
337
|
+
const classInfo = cachedFindEnclosingClassInfo(current.previousSibling ?? current, filePath);
|
|
338
|
+
const qualifiedName = classInfo
|
|
339
|
+
? `${classInfo.className}.${customResult.funcName}`
|
|
340
|
+
: customResult.funcName;
|
|
341
|
+
// Include #<arity> suffix to match definition-phase Method/Constructor IDs.
|
|
342
|
+
// When same-arity collisions exist, also append ~type1,type2.
|
|
343
|
+
const sigNode = current.previousSibling ?? current;
|
|
344
|
+
let arity2;
|
|
345
|
+
let encTypeTag2 = '';
|
|
346
|
+
if (finalLabel === 'Method' || finalLabel === 'Constructor') {
|
|
347
|
+
const encLang2 = getLanguageFromFilename(filePath);
|
|
348
|
+
const classNode2 = findEnclosingClassNode(sigNode) ?? findClassNodeByQualifiedName(sigNode);
|
|
349
|
+
if (classNode2 && encLang2) {
|
|
350
|
+
const methodMap2 = getMethodInfo(classNode2, provider, {
|
|
351
|
+
filePath,
|
|
352
|
+
language: encLang2,
|
|
353
|
+
});
|
|
354
|
+
const defLine2 = sigNode.startPosition.row + 1;
|
|
355
|
+
const info2 = methodMap2?.get(`${customResult.funcName}:${defLine2}`);
|
|
356
|
+
if (info2) {
|
|
357
|
+
arity2 = info2.parameters.some((p) => p.isVariadic)
|
|
358
|
+
? undefined
|
|
359
|
+
: info2.parameters.length;
|
|
360
|
+
if (methodMap2 && arity2 !== undefined) {
|
|
361
|
+
const g2 = buildCollisionGroups(methodMap2);
|
|
362
|
+
encTypeTag2 =
|
|
363
|
+
typeTagForId(methodMap2, customResult.funcName, arity2, info2, encLang2, g2) +
|
|
364
|
+
constTagForId(methodMap2, customResult.funcName, arity2, info2, g2);
|
|
365
|
+
}
|
|
366
|
+
}
|
|
367
|
+
}
|
|
368
|
+
}
|
|
369
|
+
const arityTag2 = arity2 !== undefined ? `#${arity2}${encTypeTag2}` : '';
|
|
370
|
+
const result = generateId(finalLabel, `${filePath}:${qualifiedName}${arityTag2}`);
|
|
219
371
|
functionIdCache.set(node, result);
|
|
220
372
|
return result;
|
|
221
373
|
}
|
|
@@ -225,12 +377,12 @@ const findEnclosingFunctionId = (node, filePath, provider) => {
|
|
|
225
377
|
functionIdCache.set(node, null);
|
|
226
378
|
return null;
|
|
227
379
|
};
|
|
228
|
-
/** Cached wrapper for
|
|
229
|
-
const
|
|
380
|
+
/** Cached wrapper for findEnclosingClassInfo — avoids repeated parent walks. */
|
|
381
|
+
const cachedFindEnclosingClassInfo = (node, filePath) => {
|
|
230
382
|
const cached = classIdCache.get(node);
|
|
231
383
|
if (cached !== undefined)
|
|
232
384
|
return cached;
|
|
233
|
-
const result =
|
|
385
|
+
const result = findEnclosingClassInfo(node, filePath);
|
|
234
386
|
classIdCache.set(node, result);
|
|
235
387
|
return result;
|
|
236
388
|
};
|
|
@@ -263,7 +415,7 @@ const processBatch = (files, onProgress) => {
|
|
|
263
415
|
toolDefs: [],
|
|
264
416
|
ormQueries: [],
|
|
265
417
|
constructorBindings: [],
|
|
266
|
-
|
|
418
|
+
fileScopeBindings: [],
|
|
267
419
|
skippedLanguages: {},
|
|
268
420
|
fileCount: 0,
|
|
269
421
|
};
|
|
@@ -375,6 +527,27 @@ const EXPRESS_ROUTE_METHODS = new Set([
|
|
|
375
527
|
// the express_route handler as route definitions, not consumers. The fetch() global
|
|
376
528
|
// function is captured separately by the route.fetch query.
|
|
377
529
|
const HTTP_CLIENT_ONLY_METHODS = new Set(['head', 'options', 'request', 'ajax']);
|
|
530
|
+
// Known HTTP client receivers u2014 skip these, they're API consumers not routes
|
|
531
|
+
const HTTP_CLIENT_RECEIVERS = new Set([
|
|
532
|
+
'axios',
|
|
533
|
+
'request',
|
|
534
|
+
'fetch',
|
|
535
|
+
'http',
|
|
536
|
+
'https',
|
|
537
|
+
'got',
|
|
538
|
+
'ky',
|
|
539
|
+
'superagent',
|
|
540
|
+
'needle',
|
|
541
|
+
'undici',
|
|
542
|
+
'apiclient',
|
|
543
|
+
'client',
|
|
544
|
+
'httpclient',
|
|
545
|
+
'api',
|
|
546
|
+
'$http',
|
|
547
|
+
'session',
|
|
548
|
+
'httpservice',
|
|
549
|
+
'conn',
|
|
550
|
+
]);
|
|
378
551
|
// Decorator names that indicate HTTP route handlers (NestJS, Flask, FastAPI, Spring)
|
|
379
552
|
const ROUTE_DECORATOR_NAMES = new Set([
|
|
380
553
|
'Get',
|
|
@@ -684,24 +857,28 @@ function extractLaravelRoutes(tree, filePath) {
|
|
|
684
857
|
});
|
|
685
858
|
}
|
|
686
859
|
}
|
|
687
|
-
|
|
860
|
+
const walkStack = [{ node: tree.rootNode, groupSnapshot: [] }];
|
|
861
|
+
while (walkStack.length > 0) {
|
|
862
|
+
const { node, groupSnapshot } = walkStack.pop();
|
|
688
863
|
// Case 1: Simple Route::get(...), Route::post(...), etc.
|
|
689
864
|
if (isRouteStaticCall(node)) {
|
|
690
865
|
const method = getCallMethodName(node);
|
|
691
866
|
if (method && (ROUTE_HTTP_METHODS.has(method) || ROUTE_RESOURCE_METHODS.has(method))) {
|
|
692
|
-
emitRoute(method, getArguments(node), node.startPosition.row,
|
|
693
|
-
|
|
867
|
+
emitRoute(method, getArguments(node), node.startPosition.row, groupSnapshot, []);
|
|
868
|
+
continue;
|
|
694
869
|
}
|
|
695
870
|
if (method === 'group') {
|
|
696
871
|
const argsNode = getArguments(node);
|
|
697
872
|
const groupCtx = parseArrayGroupArgs(argsNode);
|
|
698
873
|
const body = findClosureBody(argsNode);
|
|
699
874
|
if (body) {
|
|
700
|
-
|
|
701
|
-
|
|
702
|
-
|
|
875
|
+
const childSnapshot = [...groupSnapshot, groupCtx];
|
|
876
|
+
const children = body.children ?? [];
|
|
877
|
+
for (let i = children.length - 1; i >= 0; i--) {
|
|
878
|
+
walkStack.push({ node: children[i], groupSnapshot: childSnapshot });
|
|
879
|
+
}
|
|
703
880
|
}
|
|
704
|
-
|
|
881
|
+
continue;
|
|
705
882
|
}
|
|
706
883
|
}
|
|
707
884
|
// Case 2: Fluent chain — Route::middleware(...)->group(...) or Route::middleware(...)->get(...)
|
|
@@ -719,27 +896,26 @@ function extractLaravelRoutes(tree, filePath) {
|
|
|
719
896
|
}
|
|
720
897
|
const body = findClosureBody(chain.terminalArgs);
|
|
721
898
|
if (body) {
|
|
722
|
-
|
|
723
|
-
|
|
724
|
-
|
|
899
|
+
const childSnapshot = [...groupSnapshot, groupCtx];
|
|
900
|
+
const children = body.children ?? [];
|
|
901
|
+
for (let i = children.length - 1; i >= 0; i--) {
|
|
902
|
+
walkStack.push({ node: children[i], groupSnapshot: childSnapshot });
|
|
903
|
+
}
|
|
725
904
|
}
|
|
726
|
-
|
|
905
|
+
continue;
|
|
727
906
|
}
|
|
728
907
|
if (ROUTE_HTTP_METHODS.has(chain.terminalMethod) ||
|
|
729
908
|
ROUTE_RESOURCE_METHODS.has(chain.terminalMethod)) {
|
|
730
|
-
emitRoute(chain.terminalMethod, chain.terminalArgs, node.startPosition.row,
|
|
731
|
-
|
|
909
|
+
emitRoute(chain.terminalMethod, chain.terminalArgs, node.startPosition.row, groupSnapshot, chain.attributes);
|
|
910
|
+
continue;
|
|
732
911
|
}
|
|
733
912
|
}
|
|
734
|
-
// Default:
|
|
735
|
-
|
|
736
|
-
|
|
737
|
-
|
|
738
|
-
for (const child of node.children ?? []) {
|
|
739
|
-
walk(child, groupStack);
|
|
913
|
+
// Default: push children in reverse so leftmost is processed first
|
|
914
|
+
const children = node.children ?? [];
|
|
915
|
+
for (let i = children.length - 1; i >= 0; i--) {
|
|
916
|
+
walkStack.push({ node: children[i], groupSnapshot });
|
|
740
917
|
}
|
|
741
918
|
}
|
|
742
|
-
walk(tree.rootNode, []);
|
|
743
919
|
return routes;
|
|
744
920
|
}
|
|
745
921
|
// ============================================================================
|
|
@@ -806,11 +982,23 @@ const processFileGroup = (files, language, queryString, result, onFileProcessed)
|
|
|
806
982
|
// Skip files larger than the max tree-sitter buffer (32 MB)
|
|
807
983
|
if (file.content.length > TREE_SITTER_MAX_BUFFER)
|
|
808
984
|
continue;
|
|
985
|
+
// Vue SFC preprocessing: extract <script> block content
|
|
986
|
+
let parseContent = file.content;
|
|
987
|
+
let lineOffset = 0;
|
|
988
|
+
let isVueSetup = false;
|
|
989
|
+
if (language === SupportedLanguages.Vue) {
|
|
990
|
+
const extracted = extractVueScript(file.content);
|
|
991
|
+
if (!extracted)
|
|
992
|
+
continue; // skip .vue files with no script block
|
|
993
|
+
parseContent = extracted.scriptContent;
|
|
994
|
+
lineOffset = extracted.lineOffset;
|
|
995
|
+
isVueSetup = extracted.isSetup;
|
|
996
|
+
}
|
|
809
997
|
clearCaches(); // Reset memoization before each new file
|
|
810
998
|
let tree;
|
|
811
999
|
try {
|
|
812
|
-
tree = parser.parse(
|
|
813
|
-
bufferSize: getTreeSitterBufferSize(
|
|
1000
|
+
tree = parser.parse(parseContent, undefined, {
|
|
1001
|
+
bufferSize: getTreeSitterBufferSize(parseContent.length),
|
|
814
1002
|
});
|
|
815
1003
|
}
|
|
816
1004
|
catch (err) {
|
|
@@ -861,6 +1049,7 @@ const processFileGroup = (files, language, queryString, result, onFileProcessed)
|
|
|
861
1049
|
const typeEnv = buildTypeEnv(tree, language, {
|
|
862
1050
|
parentMap,
|
|
863
1051
|
enclosingFunctionFinder: provider?.enclosingFunctionFinder,
|
|
1052
|
+
extractFunctionName: provider?.methodExtractor?.extractFunctionName,
|
|
864
1053
|
});
|
|
865
1054
|
const callRouter = provider.callRouter;
|
|
866
1055
|
if (typeEnv.constructorBindings.length > 0) {
|
|
@@ -869,15 +1058,23 @@ const processFileGroup = (files, language, queryString, result, onFileProcessed)
|
|
|
869
1058
|
bindings: [...typeEnv.constructorBindings],
|
|
870
1059
|
});
|
|
871
1060
|
}
|
|
872
|
-
//
|
|
873
|
-
//
|
|
874
|
-
//
|
|
1061
|
+
// Serialize file-scope bindings for BindingAccumulator. These feed the
|
|
1062
|
+
// ExportedTypeMap enrichment loop in pipeline.ts — the only current
|
|
1063
|
+
// consumer of worker-path binding data.
|
|
1064
|
+
//
|
|
1065
|
+
// Historical note: we previously serialized all scopes
|
|
1066
|
+
// (`typeEnv.allScopes()`), which pushed ~4.9 MB of function-scope
|
|
1067
|
+
// bindings across the IPC boundary on every worker batch with zero
|
|
1068
|
+
// downstream readers. Narrowing to `fileScope()` recovers that cost.
|
|
1069
|
+
// See the `FileScopeBindings` JSDoc above for the Phase 9 reversion
|
|
1070
|
+
// path when a function-scope consumer lands.
|
|
875
1071
|
const fileScope = typeEnv.fileScope();
|
|
876
1072
|
if (fileScope.size > 0) {
|
|
877
|
-
const
|
|
878
|
-
for (const [
|
|
879
|
-
|
|
880
|
-
|
|
1073
|
+
const scopeBindings = [];
|
|
1074
|
+
for (const [varName, typeName] of fileScope) {
|
|
1075
|
+
scopeBindings.push([varName, typeName]);
|
|
1076
|
+
}
|
|
1077
|
+
result.fileScopeBindings.push({ filePath: file.path, bindings: scopeBindings });
|
|
881
1078
|
}
|
|
882
1079
|
// Per-file map: decorator end-line → decorator info, for associating with definitions
|
|
883
1080
|
const fileDecorators = new Map();
|
|
@@ -946,7 +1143,7 @@ const processFileGroup = (files, language, queryString, result, onFileProcessed)
|
|
|
946
1143
|
routePath,
|
|
947
1144
|
httpMethod,
|
|
948
1145
|
decoratorName,
|
|
949
|
-
lineNumber: decoratorNode.startPosition.row,
|
|
1146
|
+
lineNumber: decoratorNode.startPosition.row + lineOffset,
|
|
950
1147
|
});
|
|
951
1148
|
}
|
|
952
1149
|
// MCP/RPC tool detection: @mcp.tool(), @app.tool(), @server.tool()
|
|
@@ -967,7 +1164,7 @@ const processFileGroup = (files, language, queryString, result, onFileProcessed)
|
|
|
967
1164
|
result.fetchCalls.push({
|
|
968
1165
|
filePath: file.path,
|
|
969
1166
|
fetchURL: urlNode.text,
|
|
970
|
-
lineNumber: captureMap['route.fetch'].startPosition.row,
|
|
1167
|
+
lineNumber: captureMap['route.fetch'].startPosition.row + lineOffset,
|
|
971
1168
|
});
|
|
972
1169
|
}
|
|
973
1170
|
continue;
|
|
@@ -982,7 +1179,7 @@ const processFileGroup = (files, language, queryString, result, onFileProcessed)
|
|
|
982
1179
|
result.fetchCalls.push({
|
|
983
1180
|
filePath: file.path,
|
|
984
1181
|
fetchURL: url,
|
|
985
|
-
lineNumber: captureMap['http_client'].startPosition.row,
|
|
1182
|
+
lineNumber: captureMap['http_client'].startPosition.row + lineOffset,
|
|
986
1183
|
});
|
|
987
1184
|
}
|
|
988
1185
|
continue;
|
|
@@ -994,6 +1191,45 @@ const processFileGroup = (files, language, queryString, result, onFileProcessed)
|
|
|
994
1191
|
const method = captureMap['express_route.method'].text;
|
|
995
1192
|
const routePath = captureMap['express_route.path'].text;
|
|
996
1193
|
if (EXPRESS_ROUTE_METHODS.has(method) && routePath.startsWith('/')) {
|
|
1194
|
+
// Extract the receiver (the object the method is called on) to filter out
|
|
1195
|
+
// HTTP client calls like axios.get('/api/users') that match the same pattern
|
|
1196
|
+
// as Express route registrations.
|
|
1197
|
+
const callNode = captureMap['express_route'];
|
|
1198
|
+
const funcNode = callNode.childForFieldName?.('function') ?? callNode.children?.[0];
|
|
1199
|
+
// Walk through nested member_expressions and call_expressions to
|
|
1200
|
+
// reach the innermost receiver identifier. Handles chains like:
|
|
1201
|
+
// this.httpService.get('/path') -> member chain -> 'httpservice'
|
|
1202
|
+
// getClient().get('/path') -> call_expression -> 'getclient'
|
|
1203
|
+
// axios.get('/path') -> bare identifier -> 'axios'
|
|
1204
|
+
let receiverNode = funcNode?.childForFieldName?.('object') ?? funcNode?.children?.[0];
|
|
1205
|
+
while (receiverNode?.type === 'member_expression' ||
|
|
1206
|
+
receiverNode?.type === 'call_expression') {
|
|
1207
|
+
if (receiverNode.type === 'member_expression') {
|
|
1208
|
+
// Drill into the property (rightmost part) of the member expression
|
|
1209
|
+
const propNode = receiverNode.childForFieldName?.('property');
|
|
1210
|
+
if (propNode) {
|
|
1211
|
+
receiverNode = propNode;
|
|
1212
|
+
}
|
|
1213
|
+
else {
|
|
1214
|
+
break;
|
|
1215
|
+
}
|
|
1216
|
+
}
|
|
1217
|
+
else {
|
|
1218
|
+
// call_expression: unwrap to the function being called
|
|
1219
|
+
const innerFunc = receiverNode.childForFieldName?.('function') ?? receiverNode.children?.[0];
|
|
1220
|
+
if (innerFunc && innerFunc !== receiverNode) {
|
|
1221
|
+
receiverNode = innerFunc;
|
|
1222
|
+
}
|
|
1223
|
+
else {
|
|
1224
|
+
break;
|
|
1225
|
+
}
|
|
1226
|
+
}
|
|
1227
|
+
}
|
|
1228
|
+
const receiverText = receiverNode?.text?.toLowerCase() ?? '';
|
|
1229
|
+
if (HTTP_CLIENT_RECEIVERS.has(receiverText)) {
|
|
1230
|
+
// This is an HTTP client call, not a route definition u2014 skip it
|
|
1231
|
+
continue;
|
|
1232
|
+
}
|
|
997
1233
|
const httpMethod = method === 'all' || method === 'use' || method === 'route'
|
|
998
1234
|
? 'GET'
|
|
999
1235
|
: method.toUpperCase();
|
|
@@ -1002,7 +1238,7 @@ const processFileGroup = (files, language, queryString, result, onFileProcessed)
|
|
|
1002
1238
|
routePath,
|
|
1003
1239
|
httpMethod,
|
|
1004
1240
|
decoratorName: `express.${method}`,
|
|
1005
|
-
lineNumber: captureMap['express_route'].startPosition.row,
|
|
1241
|
+
lineNumber: captureMap['express_route'].startPosition.row + lineOffset,
|
|
1006
1242
|
});
|
|
1007
1243
|
}
|
|
1008
1244
|
continue;
|
|
@@ -1069,7 +1305,8 @@ const processFileGroup = (files, language, queryString, result, onFileProcessed)
|
|
|
1069
1305
|
continue;
|
|
1070
1306
|
}
|
|
1071
1307
|
if (routed.kind === 'properties') {
|
|
1072
|
-
const
|
|
1308
|
+
const propEnclosingInfo = cachedFindEnclosingClassInfo(captureMap['call'], file.path);
|
|
1309
|
+
const propEnclosingClassId = propEnclosingInfo?.classId ?? null;
|
|
1073
1310
|
// Enrich routed properties with FieldExtractor metadata
|
|
1074
1311
|
let routedFieldMap;
|
|
1075
1312
|
if (provider.fieldExtractor && typeEnv) {
|
|
@@ -1085,7 +1322,10 @@ const processFileGroup = (files, language, queryString, result, onFileProcessed)
|
|
|
1085
1322
|
}
|
|
1086
1323
|
for (const item of routed.items) {
|
|
1087
1324
|
const routedFieldInfo = routedFieldMap?.get(item.propName);
|
|
1088
|
-
const
|
|
1325
|
+
const propQualifiedName = propEnclosingInfo
|
|
1326
|
+
? `${propEnclosingInfo.className}.${item.propName}`
|
|
1327
|
+
: item.propName;
|
|
1328
|
+
const nodeId = generateId('Property', `${file.path}:${propQualifiedName}`);
|
|
1089
1329
|
result.nodes.push({
|
|
1090
1330
|
id: nodeId,
|
|
1091
1331
|
label: 'Property',
|
|
@@ -1250,25 +1490,119 @@ const processFileGroup = (files, language, queryString, result, onFileProcessed)
|
|
|
1250
1490
|
continue;
|
|
1251
1491
|
}
|
|
1252
1492
|
}
|
|
1253
|
-
const
|
|
1254
|
-
|
|
1493
|
+
const definitionNode = getDefinitionNodeFromCaptures(captureMap);
|
|
1494
|
+
const defaultNodeLabel = getLabelFromCaptures(captureMap, provider);
|
|
1495
|
+
if (!defaultNodeLabel)
|
|
1255
1496
|
continue;
|
|
1256
1497
|
const nameNode = captureMap['name'];
|
|
1498
|
+
const extractedClassSymbol = definitionNode && provider.classExtractor?.isTypeDeclaration(definitionNode)
|
|
1499
|
+
? provider.classExtractor.extract(definitionNode, {
|
|
1500
|
+
name: nameNode?.text,
|
|
1501
|
+
type: defaultNodeLabel,
|
|
1502
|
+
})
|
|
1503
|
+
: null;
|
|
1504
|
+
const nodeLabel = extractedClassSymbol?.type ?? defaultNodeLabel;
|
|
1257
1505
|
// Synthesize name for constructors without explicit @name capture (e.g. Swift init)
|
|
1258
|
-
if (!nameNode && nodeLabel !== 'Constructor')
|
|
1506
|
+
if (!nameNode && nodeLabel !== 'Constructor' && !extractedClassSymbol)
|
|
1259
1507
|
continue;
|
|
1260
|
-
const nodeName = nameNode ? nameNode.text : 'init';
|
|
1261
|
-
const definitionNode = getDefinitionNodeFromCaptures(captureMap);
|
|
1508
|
+
const nodeName = extractedClassSymbol?.name ?? (nameNode ? nameNode.text : 'init');
|
|
1262
1509
|
const startLine = definitionNode
|
|
1263
|
-
? definitionNode.startPosition.row
|
|
1510
|
+
? definitionNode.startPosition.row + lineOffset
|
|
1264
1511
|
: nameNode
|
|
1265
|
-
? nameNode.startPosition.row
|
|
1266
|
-
:
|
|
1267
|
-
|
|
1512
|
+
? nameNode.startPosition.row + lineOffset
|
|
1513
|
+
: lineOffset;
|
|
1514
|
+
// Compute enclosing class BEFORE node ID — needed to qualify method IDs
|
|
1515
|
+
const needsOwner = nodeLabel === 'Method' ||
|
|
1516
|
+
nodeLabel === 'Constructor' ||
|
|
1517
|
+
nodeLabel === 'Property' ||
|
|
1518
|
+
nodeLabel === 'Function';
|
|
1519
|
+
const enclosingClassInfo = needsOwner
|
|
1520
|
+
? cachedFindEnclosingClassInfo(nameNode || definitionNode, file.path)
|
|
1521
|
+
: null;
|
|
1522
|
+
const enclosingClassId = enclosingClassInfo?.classId ?? null;
|
|
1523
|
+
// Qualify method/property IDs with enclosing class name to avoid collisions
|
|
1524
|
+
const qualifiedName = enclosingClassInfo
|
|
1525
|
+
? `${enclosingClassInfo.className}.${nodeName}`
|
|
1526
|
+
: nodeName;
|
|
1527
|
+
// Extract method metadata BEFORE generating node ID — parameterCount is needed
|
|
1528
|
+
// to disambiguate overloaded methods via #<arity> suffix in the ID.
|
|
1529
|
+
let declaredType;
|
|
1530
|
+
let methodProps = {};
|
|
1531
|
+
let arityForId; // raw param count for ID, even for variadic
|
|
1532
|
+
let defMethodMap;
|
|
1533
|
+
let defMethodInfo;
|
|
1534
|
+
if (nodeLabel === 'Function' || nodeLabel === 'Method' || nodeLabel === 'Constructor') {
|
|
1535
|
+
// Use MethodExtractor for method metadata — provides parameterCount, parameterTypes,
|
|
1536
|
+
// returnType, isAbstract/isFinal/annotations, visibility, and more.
|
|
1537
|
+
let enrichedByMethodExtractor = false;
|
|
1538
|
+
if (provider.methodExtractor && definitionNode) {
|
|
1539
|
+
const classNode = findEnclosingClassNode(definitionNode) ?? findClassNodeByQualifiedName(definitionNode);
|
|
1540
|
+
if (classNode) {
|
|
1541
|
+
const methodMap = getMethodInfo(classNode, provider, {
|
|
1542
|
+
filePath: file.path,
|
|
1543
|
+
language,
|
|
1544
|
+
});
|
|
1545
|
+
const defLine = definitionNode.startPosition.row + 1;
|
|
1546
|
+
const info = methodMap?.get(`${nodeName}:${defLine}`);
|
|
1547
|
+
if (info) {
|
|
1548
|
+
enrichedByMethodExtractor = true;
|
|
1549
|
+
arityForId = arityForIdFromInfo(info);
|
|
1550
|
+
methodProps = buildMethodProps(info);
|
|
1551
|
+
defMethodMap = methodMap;
|
|
1552
|
+
defMethodInfo = info;
|
|
1553
|
+
}
|
|
1554
|
+
}
|
|
1555
|
+
}
|
|
1556
|
+
// For top-level methods (e.g. Go method_declaration), try extractFromNode
|
|
1557
|
+
if (!enrichedByMethodExtractor &&
|
|
1558
|
+
provider.methodExtractor?.extractFromNode &&
|
|
1559
|
+
definitionNode) {
|
|
1560
|
+
const info = provider.methodExtractor.extractFromNode(definitionNode, {
|
|
1561
|
+
filePath: file.path,
|
|
1562
|
+
language,
|
|
1563
|
+
});
|
|
1564
|
+
if (info) {
|
|
1565
|
+
enrichedByMethodExtractor = true;
|
|
1566
|
+
arityForId = arityForIdFromInfo(info);
|
|
1567
|
+
methodProps = buildMethodProps(info);
|
|
1568
|
+
}
|
|
1569
|
+
}
|
|
1570
|
+
}
|
|
1571
|
+
// Append #<paramCount> to Method/Constructor IDs to disambiguate overloads.
|
|
1572
|
+
// Functions are not suffixed — they don't overload by name in the same scope.
|
|
1573
|
+
// When same-arity collisions exist, append ~type1,type2 for further disambiguation.
|
|
1574
|
+
const needsAritySuffix = nodeLabel === 'Method' || nodeLabel === 'Constructor';
|
|
1575
|
+
let arityTag = needsAritySuffix && arityForId !== undefined ? `#${arityForId}` : '';
|
|
1576
|
+
if (arityTag && defMethodMap && defMethodInfo) {
|
|
1577
|
+
const groups = buildCollisionGroups(defMethodMap);
|
|
1578
|
+
arityTag += typeTagForId(defMethodMap, nodeName, arityForId, defMethodInfo, language, groups);
|
|
1579
|
+
arityTag += constTagForId(defMethodMap, nodeName, arityForId, defMethodInfo, groups);
|
|
1580
|
+
}
|
|
1581
|
+
const nodeId = generateId(nodeLabel, `${file.path}:${qualifiedName}${arityTag}`);
|
|
1582
|
+
const classNodeForSymbol = definitionNode || nameNode;
|
|
1583
|
+
const qualifiedTypeName = extractedClassSymbol?.qualifiedName ??
|
|
1584
|
+
(classNodeForSymbol && provider.classExtractor?.isTypeDeclaration(classNodeForSymbol)
|
|
1585
|
+
? (provider.classExtractor.extractQualifiedName(classNodeForSymbol, nodeName) ?? nodeName)
|
|
1586
|
+
: undefined);
|
|
1268
1587
|
const description = provider.descriptionExtractor?.(nodeLabel, nodeName, captureMap);
|
|
1269
1588
|
let frameworkHint = definitionNode
|
|
1270
1589
|
? detectFrameworkFromAST(language, (definitionNode.text || '').slice(0, 300))
|
|
1271
1590
|
: null;
|
|
1591
|
+
// Suppress Spring framework hint for methods inside interfaces
|
|
1592
|
+
// (Feign clients, JAX-RS proxies are consumers, not providers)
|
|
1593
|
+
if (frameworkHint && definitionNode) {
|
|
1594
|
+
let classCheck = definitionNode.parent;
|
|
1595
|
+
while (classCheck) {
|
|
1596
|
+
if (classCheck.type === 'interface_declaration') {
|
|
1597
|
+
frameworkHint = null;
|
|
1598
|
+
break;
|
|
1599
|
+
}
|
|
1600
|
+
if (classCheck.type === 'class_declaration' || classCheck.type === 'program') {
|
|
1601
|
+
break;
|
|
1602
|
+
}
|
|
1603
|
+
classCheck = classCheck.parent;
|
|
1604
|
+
}
|
|
1605
|
+
}
|
|
1272
1606
|
// Decorators appear on lines immediately before their definition; allow up to
|
|
1273
1607
|
// MAX_DECORATOR_SCAN_LINES gap for blank lines / multi-line decorator stacks.
|
|
1274
1608
|
const MAX_DECORATOR_SCAN_LINES = 5;
|
|
@@ -1291,94 +1625,15 @@ const processFileGroup = (files, language, queryString, result, onFileProcessed)
|
|
|
1291
1625
|
filePath: file.path,
|
|
1292
1626
|
toolName: nodeName,
|
|
1293
1627
|
description: dec.arg || '',
|
|
1294
|
-
lineNumber: definitionNode.startPosition.row,
|
|
1628
|
+
lineNumber: definitionNode.startPosition.row + lineOffset,
|
|
1295
1629
|
});
|
|
1296
1630
|
}
|
|
1297
1631
|
fileDecorators.delete(checkLine);
|
|
1298
1632
|
}
|
|
1299
1633
|
}
|
|
1300
1634
|
}
|
|
1301
|
-
|
|
1302
|
-
|
|
1303
|
-
let parameterTypes;
|
|
1304
|
-
let returnType;
|
|
1305
|
-
let declaredType;
|
|
1306
|
-
let visibility;
|
|
1307
|
-
let isStatic;
|
|
1308
|
-
let isReadonly;
|
|
1309
|
-
let isAbstract;
|
|
1310
|
-
let isFinal;
|
|
1311
|
-
let isVirtual;
|
|
1312
|
-
let isOverride;
|
|
1313
|
-
let isAsync;
|
|
1314
|
-
let isPartial;
|
|
1315
|
-
let annotations;
|
|
1316
|
-
if (nodeLabel === 'Function' || nodeLabel === 'Method' || nodeLabel === 'Constructor') {
|
|
1317
|
-
// Try MethodExtractor first — it provides everything extractMethodSignature does, plus
|
|
1318
|
-
// isAbstract/isFinal/annotations. Only fall back to extractMethodSignature when no
|
|
1319
|
-
// MethodExtractor is available or the method isn't inside a class body.
|
|
1320
|
-
let enrichedByMethodExtractor = false;
|
|
1321
|
-
if (provider.methodExtractor && definitionNode) {
|
|
1322
|
-
const classNode = findEnclosingClassNode(definitionNode);
|
|
1323
|
-
if (classNode) {
|
|
1324
|
-
const methodMap = getMethodInfo(classNode, provider, {
|
|
1325
|
-
filePath: file.path,
|
|
1326
|
-
language,
|
|
1327
|
-
});
|
|
1328
|
-
const defLine = definitionNode.startPosition.row + 1;
|
|
1329
|
-
const info = methodMap?.get(`${nodeName}:${defLine}`);
|
|
1330
|
-
if (info) {
|
|
1331
|
-
enrichedByMethodExtractor = true;
|
|
1332
|
-
parameterCount = info.parameters.length;
|
|
1333
|
-
const types = [];
|
|
1334
|
-
let optionalCount = 0;
|
|
1335
|
-
for (const p of info.parameters) {
|
|
1336
|
-
if (p.type !== null)
|
|
1337
|
-
types.push(p.type);
|
|
1338
|
-
if (p.isOptional)
|
|
1339
|
-
optionalCount++;
|
|
1340
|
-
}
|
|
1341
|
-
parameterTypes = types.length > 0 ? types : undefined;
|
|
1342
|
-
requiredParameterCount =
|
|
1343
|
-
optionalCount > 0 ? parameterCount - optionalCount : undefined;
|
|
1344
|
-
returnType = info.returnType ?? undefined;
|
|
1345
|
-
visibility = info.visibility;
|
|
1346
|
-
isStatic = info.isStatic;
|
|
1347
|
-
isAbstract = info.isAbstract;
|
|
1348
|
-
isFinal = info.isFinal;
|
|
1349
|
-
if (info.isVirtual)
|
|
1350
|
-
isVirtual = info.isVirtual;
|
|
1351
|
-
if (info.isOverride)
|
|
1352
|
-
isOverride = info.isOverride;
|
|
1353
|
-
if (info.isAsync)
|
|
1354
|
-
isAsync = info.isAsync;
|
|
1355
|
-
if (info.isPartial)
|
|
1356
|
-
isPartial = info.isPartial;
|
|
1357
|
-
if (info.annotations.length > 0)
|
|
1358
|
-
annotations = info.annotations;
|
|
1359
|
-
}
|
|
1360
|
-
}
|
|
1361
|
-
}
|
|
1362
|
-
if (!enrichedByMethodExtractor) {
|
|
1363
|
-
const sig = extractMethodSignature(definitionNode);
|
|
1364
|
-
parameterCount = sig.parameterCount;
|
|
1365
|
-
requiredParameterCount = sig.requiredParameterCount;
|
|
1366
|
-
parameterTypes = sig.parameterTypes;
|
|
1367
|
-
returnType = sig.returnType;
|
|
1368
|
-
}
|
|
1369
|
-
// Language-specific return type fallback (e.g. Ruby YARD @return [Type])
|
|
1370
|
-
// Also upgrades uninformative AST types like PHP `array` with PHPDoc `@return User[]`
|
|
1371
|
-
if ((!returnType || returnType === 'array' || returnType === 'iterable') &&
|
|
1372
|
-
definitionNode) {
|
|
1373
|
-
const tc = provider.typeConfig;
|
|
1374
|
-
if (tc?.extractReturnType) {
|
|
1375
|
-
const docReturn = tc.extractReturnType(definitionNode);
|
|
1376
|
-
if (docReturn)
|
|
1377
|
-
returnType = docReturn;
|
|
1378
|
-
}
|
|
1379
|
-
}
|
|
1380
|
-
}
|
|
1381
|
-
else if (nodeLabel === 'Property' && definitionNode) {
|
|
1635
|
+
// Property metadata extraction (not needed before nodeId — Properties don't overload)
|
|
1636
|
+
if (nodeLabel === 'Property' && definitionNode) {
|
|
1382
1637
|
// FieldExtractor is the single source of truth when available
|
|
1383
1638
|
if (provider.fieldExtractor && typeEnv) {
|
|
1384
1639
|
const classNode = findEnclosingClassNode(definitionNode);
|
|
@@ -1392,9 +1647,9 @@ const processFileGroup = (files, language, queryString, result, onFileProcessed)
|
|
|
1392
1647
|
const info = fieldMap?.get(nodeName);
|
|
1393
1648
|
if (info) {
|
|
1394
1649
|
declaredType = info.type ?? undefined;
|
|
1395
|
-
visibility = info.visibility;
|
|
1396
|
-
isStatic = info.isStatic;
|
|
1397
|
-
isReadonly = info.isReadonly;
|
|
1650
|
+
methodProps.visibility = info.visibility;
|
|
1651
|
+
methodProps.isStatic = info.isStatic;
|
|
1652
|
+
methodProps.isReadonly = info.isReadonly;
|
|
1398
1653
|
}
|
|
1399
1654
|
}
|
|
1400
1655
|
}
|
|
@@ -1405,10 +1660,13 @@ const processFileGroup = (files, language, queryString, result, onFileProcessed)
|
|
|
1405
1660
|
properties: {
|
|
1406
1661
|
name: nodeName,
|
|
1407
1662
|
filePath: file.path,
|
|
1408
|
-
startLine: definitionNode ? definitionNode.startPosition.row : startLine,
|
|
1409
|
-
endLine: definitionNode ? definitionNode.endPosition.row : startLine,
|
|
1663
|
+
startLine: definitionNode ? definitionNode.startPosition.row + lineOffset : startLine,
|
|
1664
|
+
endLine: definitionNode ? definitionNode.endPosition.row + lineOffset : startLine,
|
|
1410
1665
|
language: language,
|
|
1411
|
-
isExported:
|
|
1666
|
+
isExported: language === SupportedLanguages.Vue && isVueSetup
|
|
1667
|
+
? isVueSetupTopLevel(nameNode || definitionNode)
|
|
1668
|
+
: cachedExportCheck(provider.exportChecker, nameNode || definitionNode, nodeName),
|
|
1669
|
+
...(qualifiedTypeName !== undefined ? { qualifiedName: qualifiedTypeName } : {}),
|
|
1412
1670
|
...(frameworkHint
|
|
1413
1671
|
? {
|
|
1414
1672
|
astFrameworkMultiplier: frameworkHint.entryPointMultiplier,
|
|
@@ -1416,53 +1674,41 @@ const processFileGroup = (files, language, queryString, result, onFileProcessed)
|
|
|
1416
1674
|
}
|
|
1417
1675
|
: {}),
|
|
1418
1676
|
...(description !== undefined ? { description } : {}),
|
|
1419
|
-
...
|
|
1420
|
-
...(requiredParameterCount !== undefined ? { requiredParameterCount } : {}),
|
|
1421
|
-
...(parameterTypes !== undefined ? { parameterTypes } : {}),
|
|
1422
|
-
...(returnType !== undefined ? { returnType } : {}),
|
|
1677
|
+
...methodProps,
|
|
1423
1678
|
...(declaredType !== undefined ? { declaredType } : {}),
|
|
1424
|
-
...(visibility !== undefined ? { visibility } : {}),
|
|
1425
|
-
...(isStatic !== undefined ? { isStatic } : {}),
|
|
1426
|
-
...(isReadonly !== undefined ? { isReadonly } : {}),
|
|
1427
|
-
...(isAbstract !== undefined ? { isAbstract } : {}),
|
|
1428
|
-
...(isFinal !== undefined ? { isFinal } : {}),
|
|
1429
|
-
...(isVirtual !== undefined ? { isVirtual } : {}),
|
|
1430
|
-
...(isOverride !== undefined ? { isOverride } : {}),
|
|
1431
|
-
...(isAsync !== undefined ? { isAsync } : {}),
|
|
1432
|
-
...(isPartial !== undefined ? { isPartial } : {}),
|
|
1433
|
-
...(annotations !== undefined ? { annotations } : {}),
|
|
1434
1679
|
},
|
|
1435
1680
|
});
|
|
1436
|
-
//
|
|
1437
|
-
// Function is included because Kotlin/Rust/Python capture class methods as Function nodes
|
|
1438
|
-
const needsOwner = nodeLabel === 'Method' ||
|
|
1439
|
-
nodeLabel === 'Constructor' ||
|
|
1440
|
-
nodeLabel === 'Property' ||
|
|
1441
|
-
nodeLabel === 'Function';
|
|
1442
|
-
const enclosingClassId = needsOwner
|
|
1443
|
-
? cachedFindEnclosingClassId(nameNode || definitionNode, file.path)
|
|
1444
|
-
: null;
|
|
1681
|
+
// enclosingClassId already computed above (before nodeId generation)
|
|
1445
1682
|
result.symbols.push({
|
|
1446
1683
|
filePath: file.path,
|
|
1447
1684
|
name: nodeName,
|
|
1448
1685
|
nodeId,
|
|
1449
1686
|
type: nodeLabel,
|
|
1450
|
-
...(
|
|
1451
|
-
|
|
1452
|
-
|
|
1453
|
-
|
|
1687
|
+
...(qualifiedTypeName !== undefined ? { qualifiedName: qualifiedTypeName } : {}),
|
|
1688
|
+
parameterCount: methodProps.parameterCount,
|
|
1689
|
+
requiredParameterCount: methodProps.requiredParameterCount,
|
|
1690
|
+
parameterTypes: methodProps.parameterTypes,
|
|
1691
|
+
returnType: methodProps.returnType,
|
|
1454
1692
|
...(declaredType !== undefined ? { declaredType } : {}),
|
|
1455
1693
|
...(enclosingClassId ? { ownerId: enclosingClassId } : {}),
|
|
1456
|
-
|
|
1457
|
-
|
|
1458
|
-
|
|
1459
|
-
|
|
1460
|
-
|
|
1461
|
-
...(isVirtual !== undefined
|
|
1462
|
-
|
|
1463
|
-
|
|
1464
|
-
...(
|
|
1465
|
-
|
|
1694
|
+
visibility: methodProps.visibility,
|
|
1695
|
+
isStatic: methodProps.isStatic,
|
|
1696
|
+
isReadonly: methodProps.isReadonly,
|
|
1697
|
+
isAbstract: methodProps.isAbstract,
|
|
1698
|
+
isFinal: methodProps.isFinal,
|
|
1699
|
+
...(methodProps.isVirtual !== undefined
|
|
1700
|
+
? { isVirtual: methodProps.isVirtual }
|
|
1701
|
+
: {}),
|
|
1702
|
+
...(methodProps.isOverride !== undefined
|
|
1703
|
+
? { isOverride: methodProps.isOverride }
|
|
1704
|
+
: {}),
|
|
1705
|
+
...(methodProps.isAsync !== undefined ? { isAsync: methodProps.isAsync } : {}),
|
|
1706
|
+
...(methodProps.isPartial !== undefined
|
|
1707
|
+
? { isPartial: methodProps.isPartial }
|
|
1708
|
+
: {}),
|
|
1709
|
+
...(methodProps.annotations !== undefined
|
|
1710
|
+
? { annotations: methodProps.annotations }
|
|
1711
|
+
: {}),
|
|
1466
1712
|
});
|
|
1467
1713
|
const fileId = generateId('File', file.path);
|
|
1468
1714
|
const relId = generateId('DEFINES', `${fileId}->${nodeId}`);
|
|
@@ -1493,7 +1739,19 @@ const processFileGroup = (files, language, queryString, result, onFileProcessed)
|
|
|
1493
1739
|
result.routes.push(...extractedRoutes);
|
|
1494
1740
|
}
|
|
1495
1741
|
// Extract ORM queries (Prisma, Supabase)
|
|
1496
|
-
extractORMQueries(file.path,
|
|
1742
|
+
extractORMQueries(file.path, parseContent, result.ormQueries);
|
|
1743
|
+
// Vue: emit CALLS edges for components used in <template>
|
|
1744
|
+
if (language === SupportedLanguages.Vue) {
|
|
1745
|
+
const templateComponents = extractTemplateComponents(file.content);
|
|
1746
|
+
for (const componentName of templateComponents) {
|
|
1747
|
+
result.calls.push({
|
|
1748
|
+
filePath: file.path,
|
|
1749
|
+
calledName: componentName,
|
|
1750
|
+
sourceId: generateId('File', file.path),
|
|
1751
|
+
callForm: 'free',
|
|
1752
|
+
});
|
|
1753
|
+
}
|
|
1754
|
+
}
|
|
1497
1755
|
}
|
|
1498
1756
|
};
|
|
1499
1757
|
// ============================================================================
|
|
@@ -1514,26 +1772,33 @@ let accumulated = {
|
|
|
1514
1772
|
toolDefs: [],
|
|
1515
1773
|
ormQueries: [],
|
|
1516
1774
|
constructorBindings: [],
|
|
1517
|
-
|
|
1775
|
+
fileScopeBindings: [],
|
|
1518
1776
|
skippedLanguages: {},
|
|
1519
1777
|
fileCount: 0,
|
|
1520
1778
|
};
|
|
1521
1779
|
let cumulativeProcessed = 0;
|
|
1780
|
+
// Use a loop instead of push(...spread) to avoid hitting V8's argument limit
|
|
1781
|
+
// when merging large result sets (push(...arr) calls apply() under the hood
|
|
1782
|
+
// and blows the stack when arr has >~65k elements).
|
|
1783
|
+
const appendAll = (target, src) => {
|
|
1784
|
+
for (let i = 0; i < src.length; i++)
|
|
1785
|
+
target.push(src[i]);
|
|
1786
|
+
};
|
|
1522
1787
|
const mergeResult = (target, src) => {
|
|
1523
|
-
target.nodes
|
|
1524
|
-
target.relationships
|
|
1525
|
-
target.symbols
|
|
1526
|
-
target.imports
|
|
1527
|
-
target.calls
|
|
1528
|
-
target.assignments
|
|
1529
|
-
target.heritage
|
|
1530
|
-
target.routes
|
|
1531
|
-
target.fetchCalls
|
|
1532
|
-
target.decoratorRoutes
|
|
1533
|
-
target.toolDefs
|
|
1534
|
-
target.ormQueries
|
|
1535
|
-
target.constructorBindings
|
|
1536
|
-
target.
|
|
1788
|
+
appendAll(target.nodes, src.nodes);
|
|
1789
|
+
appendAll(target.relationships, src.relationships);
|
|
1790
|
+
appendAll(target.symbols, src.symbols);
|
|
1791
|
+
appendAll(target.imports, src.imports);
|
|
1792
|
+
appendAll(target.calls, src.calls);
|
|
1793
|
+
appendAll(target.assignments, src.assignments);
|
|
1794
|
+
appendAll(target.heritage, src.heritage);
|
|
1795
|
+
appendAll(target.routes, src.routes);
|
|
1796
|
+
appendAll(target.fetchCalls, src.fetchCalls);
|
|
1797
|
+
appendAll(target.decoratorRoutes, src.decoratorRoutes);
|
|
1798
|
+
appendAll(target.toolDefs, src.toolDefs);
|
|
1799
|
+
appendAll(target.ormQueries, src.ormQueries);
|
|
1800
|
+
appendAll(target.constructorBindings, src.constructorBindings);
|
|
1801
|
+
appendAll(target.fileScopeBindings, src.fileScopeBindings);
|
|
1537
1802
|
for (const [lang, count] of Object.entries(src.skippedLanguages)) {
|
|
1538
1803
|
target.skippedLanguages[lang] = (target.skippedLanguages[lang] || 0) + count;
|
|
1539
1804
|
}
|
|
@@ -1581,7 +1846,7 @@ parentPort.on('message', (msg) => {
|
|
|
1581
1846
|
toolDefs: [],
|
|
1582
1847
|
ormQueries: [],
|
|
1583
1848
|
constructorBindings: [],
|
|
1584
|
-
|
|
1849
|
+
fileScopeBindings: [],
|
|
1585
1850
|
skippedLanguages: {},
|
|
1586
1851
|
fileCount: 0,
|
|
1587
1852
|
};
|