gitnexus 1.4.8 → 1.4.9
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 +7 -0
- package/dist/cli/index-repo.d.ts +15 -0
- package/dist/cli/index-repo.js +115 -0
- package/dist/cli/index.js +11 -2
- package/dist/cli/setup.js +12 -9
- package/dist/cli/wiki.d.ts +4 -0
- package/dist/cli/wiki.js +174 -53
- package/dist/config/supported-languages.d.ts +7 -5
- package/dist/config/supported-languages.js +6 -4
- package/dist/core/graph/graph.js +9 -1
- package/dist/core/graph/types.d.ts +10 -2
- package/dist/core/ingestion/call-processor.d.ts +18 -1
- package/dist/core/ingestion/call-processor.js +297 -38
- package/dist/core/ingestion/call-routing.d.ts +3 -18
- package/dist/core/ingestion/call-routing.js +0 -19
- package/dist/core/ingestion/cobol/cobol-copy-expander.d.ts +57 -0
- package/dist/core/ingestion/cobol/cobol-copy-expander.js +385 -0
- package/dist/core/ingestion/cobol/cobol-preprocessor.d.ts +210 -0
- package/dist/core/ingestion/cobol/cobol-preprocessor.js +1509 -0
- package/dist/core/ingestion/cobol/jcl-parser.d.ts +68 -0
- package/dist/core/ingestion/cobol/jcl-parser.js +217 -0
- package/dist/core/ingestion/cobol/jcl-processor.d.ts +33 -0
- package/dist/core/ingestion/cobol/jcl-processor.js +229 -0
- package/dist/core/ingestion/cobol-processor.d.ts +54 -0
- package/dist/core/ingestion/cobol-processor.js +1186 -0
- package/dist/core/ingestion/entry-point-scoring.d.ts +17 -0
- package/dist/core/ingestion/entry-point-scoring.js +18 -4
- package/dist/core/ingestion/export-detection.d.ts +47 -8
- package/dist/core/ingestion/export-detection.js +29 -50
- package/dist/core/ingestion/field-extractor.d.ts +29 -0
- package/dist/core/ingestion/field-extractor.js +25 -0
- package/dist/core/ingestion/field-extractors/configs/c-cpp.d.ts +3 -0
- package/dist/core/ingestion/field-extractors/configs/c-cpp.js +108 -0
- package/dist/core/ingestion/field-extractors/configs/csharp.d.ts +8 -0
- package/dist/core/ingestion/field-extractors/configs/csharp.js +73 -0
- package/dist/core/ingestion/field-extractors/configs/dart.d.ts +8 -0
- package/dist/core/ingestion/field-extractors/configs/dart.js +76 -0
- package/dist/core/ingestion/field-extractors/configs/go.d.ts +11 -0
- package/dist/core/ingestion/field-extractors/configs/go.js +64 -0
- package/dist/core/ingestion/field-extractors/configs/helpers.d.ts +44 -0
- package/dist/core/ingestion/field-extractors/configs/helpers.js +134 -0
- package/dist/core/ingestion/field-extractors/configs/jvm.d.ts +3 -0
- package/dist/core/ingestion/field-extractors/configs/jvm.js +118 -0
- package/dist/core/ingestion/field-extractors/configs/php.d.ts +8 -0
- package/dist/core/ingestion/field-extractors/configs/php.js +67 -0
- package/dist/core/ingestion/field-extractors/configs/python.d.ts +12 -0
- package/dist/core/ingestion/field-extractors/configs/python.js +91 -0
- package/dist/core/ingestion/field-extractors/configs/ruby.d.ts +16 -0
- package/dist/core/ingestion/field-extractors/configs/ruby.js +75 -0
- package/dist/core/ingestion/field-extractors/configs/rust.d.ts +9 -0
- package/dist/core/ingestion/field-extractors/configs/rust.js +55 -0
- package/dist/core/ingestion/field-extractors/configs/swift.d.ts +8 -0
- package/dist/core/ingestion/field-extractors/configs/swift.js +63 -0
- package/dist/core/ingestion/field-extractors/configs/typescript-javascript.d.ts +3 -0
- package/dist/core/ingestion/field-extractors/configs/typescript-javascript.js +60 -0
- package/dist/core/ingestion/field-extractors/generic.d.ts +46 -0
- package/dist/core/ingestion/field-extractors/generic.js +111 -0
- package/dist/core/ingestion/field-extractors/typescript.d.ts +77 -0
- package/dist/core/ingestion/field-extractors/typescript.js +291 -0
- package/dist/core/ingestion/field-types.d.ts +59 -0
- package/dist/core/ingestion/field-types.js +2 -0
- package/dist/core/ingestion/framework-detection.d.ts +87 -0
- package/dist/core/ingestion/framework-detection.js +65 -2
- package/dist/core/ingestion/heritage-processor.js +15 -17
- package/dist/core/ingestion/import-processor.d.ts +9 -10
- package/dist/core/ingestion/import-processor.js +59 -14
- package/dist/core/ingestion/{resolvers → import-resolvers}/csharp.d.ts +6 -9
- package/dist/core/ingestion/{resolvers → import-resolvers}/csharp.js +20 -2
- package/dist/core/ingestion/import-resolvers/dart.d.ts +7 -0
- package/dist/core/ingestion/import-resolvers/dart.js +44 -0
- package/dist/core/ingestion/{resolvers → import-resolvers}/go.d.ts +4 -5
- package/dist/core/ingestion/{resolvers → import-resolvers}/go.js +17 -0
- package/dist/core/ingestion/{resolvers → import-resolvers}/jvm.d.ts +9 -1
- package/dist/core/ingestion/{resolvers → import-resolvers}/jvm.js +56 -0
- package/dist/core/ingestion/{resolvers → import-resolvers}/php.d.ts +6 -10
- package/dist/core/ingestion/{resolvers → import-resolvers}/php.js +7 -2
- package/dist/core/ingestion/{resolvers → import-resolvers}/python.d.ts +9 -3
- package/dist/core/ingestion/{resolvers → import-resolvers}/python.js +35 -3
- package/dist/core/ingestion/{resolvers → import-resolvers}/ruby.d.ts +5 -2
- package/dist/core/ingestion/{resolvers → import-resolvers}/ruby.js +7 -2
- package/dist/core/ingestion/{resolvers → import-resolvers}/rust.d.ts +5 -2
- package/dist/core/ingestion/{resolvers → import-resolvers}/rust.js +41 -2
- package/dist/core/ingestion/{resolvers → import-resolvers}/standard.d.ts +15 -7
- package/dist/core/ingestion/{resolvers → import-resolvers}/standard.js +22 -3
- package/dist/core/ingestion/import-resolvers/swift.d.ts +7 -0
- package/dist/core/ingestion/import-resolvers/swift.js +23 -0
- package/dist/core/ingestion/import-resolvers/types.d.ts +44 -0
- package/dist/core/ingestion/import-resolvers/types.js +6 -0
- package/dist/core/ingestion/{resolvers → import-resolvers}/utils.d.ts +0 -3
- package/dist/core/ingestion/{resolvers → import-resolvers}/utils.js +0 -9
- package/dist/core/ingestion/language-config.d.ts +4 -1
- package/dist/core/ingestion/language-provider.d.ts +121 -0
- package/dist/core/ingestion/language-provider.js +24 -0
- package/dist/core/ingestion/languages/c-cpp.d.ts +12 -0
- package/dist/core/ingestion/languages/c-cpp.js +71 -0
- package/dist/core/ingestion/languages/cobol.d.ts +1 -0
- package/dist/core/ingestion/languages/cobol.js +26 -0
- package/dist/core/ingestion/languages/csharp.d.ts +8 -0
- package/dist/core/ingestion/languages/csharp.js +49 -0
- package/dist/core/ingestion/languages/dart.d.ts +12 -0
- package/dist/core/ingestion/languages/dart.js +58 -0
- package/dist/core/ingestion/languages/go.d.ts +11 -0
- package/dist/core/ingestion/languages/go.js +28 -0
- package/dist/core/ingestion/languages/index.d.ts +38 -0
- package/dist/core/ingestion/languages/index.js +63 -0
- package/dist/core/ingestion/languages/java.d.ts +9 -0
- package/dist/core/ingestion/languages/java.js +29 -0
- package/dist/core/ingestion/languages/kotlin.d.ts +9 -0
- package/dist/core/ingestion/languages/kotlin.js +53 -0
- package/dist/core/ingestion/languages/php.d.ts +8 -0
- package/dist/core/ingestion/languages/php.js +145 -0
- package/dist/core/ingestion/languages/python.d.ts +12 -0
- package/dist/core/ingestion/languages/python.js +39 -0
- package/dist/core/ingestion/languages/ruby.d.ts +9 -0
- package/dist/core/ingestion/languages/ruby.js +44 -0
- package/dist/core/ingestion/languages/rust.d.ts +12 -0
- package/dist/core/ingestion/languages/rust.js +44 -0
- package/dist/core/ingestion/languages/swift.d.ts +12 -0
- package/dist/core/ingestion/languages/swift.js +133 -0
- package/dist/core/ingestion/languages/typescript.d.ts +10 -0
- package/dist/core/ingestion/languages/typescript.js +60 -0
- package/dist/core/ingestion/mro-processor.js +14 -15
- package/dist/core/ingestion/{named-binding-extraction.d.ts → named-binding-processor.d.ts} +0 -9
- package/dist/core/ingestion/named-binding-processor.js +42 -0
- package/dist/core/ingestion/named-bindings/csharp.d.ts +3 -0
- package/dist/core/ingestion/named-bindings/csharp.js +37 -0
- package/dist/core/ingestion/named-bindings/java.d.ts +3 -0
- package/dist/core/ingestion/named-bindings/java.js +29 -0
- package/dist/core/ingestion/named-bindings/kotlin.d.ts +3 -0
- package/dist/core/ingestion/named-bindings/kotlin.js +36 -0
- package/dist/core/ingestion/named-bindings/php.d.ts +3 -0
- package/dist/core/ingestion/named-bindings/php.js +61 -0
- package/dist/core/ingestion/named-bindings/python.d.ts +3 -0
- package/dist/core/ingestion/named-bindings/python.js +49 -0
- package/dist/core/ingestion/named-bindings/rust.d.ts +3 -0
- package/dist/core/ingestion/named-bindings/rust.js +64 -0
- package/dist/core/ingestion/named-bindings/types.d.ts +16 -0
- package/dist/core/ingestion/named-bindings/types.js +6 -0
- package/dist/core/ingestion/named-bindings/typescript.d.ts +3 -0
- package/dist/core/ingestion/named-bindings/typescript.js +58 -0
- package/dist/core/ingestion/parsing-processor.d.ts +5 -1
- package/dist/core/ingestion/parsing-processor.js +115 -16
- package/dist/core/ingestion/pipeline.js +925 -424
- package/dist/core/ingestion/resolution-context.js +1 -1
- package/dist/core/ingestion/route-extractors/expo.d.ts +1 -0
- package/dist/core/ingestion/route-extractors/expo.js +36 -0
- package/dist/core/ingestion/route-extractors/middleware.d.ts +47 -0
- package/dist/core/ingestion/route-extractors/middleware.js +143 -0
- package/dist/core/ingestion/route-extractors/nextjs.d.ts +3 -0
- package/dist/core/ingestion/route-extractors/nextjs.js +76 -0
- package/dist/core/ingestion/route-extractors/php.d.ts +7 -0
- package/dist/core/ingestion/route-extractors/php.js +21 -0
- package/dist/core/ingestion/route-extractors/response-shapes.d.ts +20 -0
- package/dist/core/ingestion/route-extractors/response-shapes.js +290 -0
- package/dist/core/ingestion/tree-sitter-queries.d.ts +8 -7
- package/dist/core/ingestion/tree-sitter-queries.js +231 -9
- package/dist/core/ingestion/type-env.d.ts +14 -17
- package/dist/core/ingestion/type-env.js +66 -14
- package/dist/core/ingestion/type-extractors/c-cpp.d.ts +1 -1
- package/dist/core/ingestion/type-extractors/csharp.js +1 -1
- package/dist/core/ingestion/type-extractors/dart.d.ts +15 -0
- package/dist/core/ingestion/type-extractors/dart.js +371 -0
- package/dist/core/ingestion/type-extractors/jvm.js +1 -1
- package/dist/core/ingestion/type-extractors/shared.d.ts +1 -13
- package/dist/core/ingestion/type-extractors/shared.js +9 -102
- package/dist/core/ingestion/type-extractors/swift.js +334 -4
- package/dist/core/ingestion/type-extractors/types.d.ts +3 -1
- package/dist/core/ingestion/{ast-helpers.d.ts → utils/ast-helpers.d.ts} +16 -13
- package/dist/core/ingestion/{ast-helpers.js → utils/ast-helpers.js} +111 -32
- package/dist/core/ingestion/{call-analysis.js → utils/call-analysis.js} +37 -0
- package/dist/core/ingestion/utils/event-loop.d.ts +5 -0
- package/dist/core/ingestion/utils/event-loop.js +5 -0
- package/dist/core/ingestion/utils/language-detection.d.ts +9 -0
- package/dist/core/ingestion/utils/language-detection.js +70 -0
- package/dist/core/ingestion/utils/verbose.d.ts +1 -0
- package/dist/core/ingestion/utils/verbose.js +7 -0
- package/dist/core/ingestion/workers/parse-worker.d.ts +43 -2
- package/dist/core/ingestion/workers/parse-worker.js +361 -150
- package/dist/core/lbug/csv-generator.js +34 -1
- package/dist/core/lbug/lbug-adapter.js +6 -0
- package/dist/core/lbug/schema.d.ts +5 -3
- package/dist/core/lbug/schema.js +39 -2
- package/dist/core/tree-sitter/parser-loader.js +7 -1
- package/dist/core/wiki/cursor-client.d.ts +31 -0
- package/dist/core/wiki/cursor-client.js +127 -0
- package/dist/core/wiki/generator.d.ts +28 -9
- package/dist/core/wiki/generator.js +115 -18
- package/dist/core/wiki/graph-queries.d.ts +4 -0
- package/dist/core/wiki/graph-queries.js +7 -1
- package/dist/core/wiki/llm-client.d.ts +2 -0
- package/dist/core/wiki/llm-client.js +8 -4
- package/dist/core/wiki/prompts.d.ts +3 -3
- package/dist/core/wiki/prompts.js +6 -0
- package/dist/mcp/core/lbug-adapter.d.ts +5 -0
- package/dist/mcp/core/lbug-adapter.js +11 -1
- package/dist/mcp/local/local-backend.d.ts +16 -5
- package/dist/mcp/local/local-backend.js +711 -74
- package/dist/mcp/tools.js +71 -2
- package/dist/storage/repo-manager.d.ts +3 -0
- package/package.json +17 -16
- package/dist/core/ingestion/import-resolution.d.ts +0 -101
- package/dist/core/ingestion/import-resolution.js +0 -251
- package/dist/core/ingestion/named-binding-extraction.js +0 -373
- package/dist/core/ingestion/resolvers/index.d.ts +0 -18
- package/dist/core/ingestion/resolvers/index.js +0 -13
- package/dist/core/ingestion/type-extractors/index.d.ts +0 -22
- package/dist/core/ingestion/type-extractors/index.js +0 -31
- package/dist/core/ingestion/utils.d.ts +0 -20
- package/dist/core/ingestion/utils.js +0 -242
- package/scripts/patch-tree-sitter-swift.cjs +0 -74
- /package/dist/core/ingestion/{call-analysis.d.ts → utils/call-analysis.d.ts} +0 -0
|
@@ -13,7 +13,7 @@ import PHP from 'tree-sitter-php';
|
|
|
13
13
|
import Ruby from 'tree-sitter-ruby';
|
|
14
14
|
import { createRequire } from 'node:module';
|
|
15
15
|
import { SupportedLanguages } from '../../../config/supported-languages.js';
|
|
16
|
-
import {
|
|
16
|
+
import { getProvider } from '../languages/index.js';
|
|
17
17
|
import { getTreeSitterBufferSize, TREE_SITTER_MAX_BUFFER } from '../constants.js';
|
|
18
18
|
// tree-sitter-swift is an optionalDependency — may not be installed
|
|
19
19
|
const _require = createRequire(import.meta.url);
|
|
@@ -22,21 +22,26 @@ try {
|
|
|
22
22
|
Swift = _require('tree-sitter-swift');
|
|
23
23
|
}
|
|
24
24
|
catch { }
|
|
25
|
+
// tree-sitter-dart is an optionalDependency — may not be installed
|
|
26
|
+
let Dart = null;
|
|
27
|
+
try {
|
|
28
|
+
Dart = _require('tree-sitter-dart');
|
|
29
|
+
}
|
|
30
|
+
catch { }
|
|
25
31
|
// tree-sitter-kotlin is an optionalDependency — may not be installed
|
|
26
32
|
let Kotlin = null;
|
|
27
33
|
try {
|
|
28
34
|
Kotlin = _require('tree-sitter-kotlin');
|
|
29
35
|
}
|
|
30
36
|
catch { }
|
|
31
|
-
import { getLanguageFromFilename
|
|
37
|
+
import { getLanguageFromFilename } from '../utils/language-detection.js';
|
|
38
|
+
import { FUNCTION_NODE_TYPES, extractFunctionName, getDefinitionNodeFromCaptures, findEnclosingClassId, getLabelFromCaptures, extractMethodSignature, findDescendant, extractStringContent, } from '../utils/ast-helpers.js';
|
|
39
|
+
import { countCallArguments, inferCallForm, extractReceiverName, extractReceiverNode, extractMixedChain, } from '../utils/call-analysis.js';
|
|
32
40
|
import { buildTypeEnv } from '../type-env.js';
|
|
33
|
-
import { isNodeExported } from '../export-detection.js';
|
|
34
41
|
import { detectFrameworkFromAST } from '../framework-detection.js';
|
|
35
|
-
import { typeConfigs } from '../type-extractors/index.js';
|
|
36
42
|
import { generateId } from '../../../lib/utils.js';
|
|
37
|
-
import {
|
|
38
|
-
import {
|
|
39
|
-
import { extractPropertyDeclaredType } from '../type-extractors/shared.js';
|
|
43
|
+
import { preprocessImportPath } from '../import-processor.js';
|
|
44
|
+
import { CLASS_CONTAINER_TYPES } from '../utils/ast-helpers.js';
|
|
40
45
|
// ============================================================================
|
|
41
46
|
// Worker-local parser + language map
|
|
42
47
|
// ============================================================================
|
|
@@ -55,6 +60,7 @@ const languageMap = {
|
|
|
55
60
|
...(Kotlin ? { [SupportedLanguages.Kotlin]: Kotlin } : {}),
|
|
56
61
|
[SupportedLanguages.PHP]: PHP.php_only,
|
|
57
62
|
[SupportedLanguages.Ruby]: Ruby,
|
|
63
|
+
...(Dart ? { [SupportedLanguages.Dart]: Dart } : {}),
|
|
58
64
|
...(Swift ? { [SupportedLanguages.Swift]: Swift } : {}),
|
|
59
65
|
};
|
|
60
66
|
/**
|
|
@@ -78,24 +84,130 @@ const setLanguage = (language, filePath) => {
|
|
|
78
84
|
throw new Error(`Unsupported language: ${language}`);
|
|
79
85
|
parser.setLanguage(lang);
|
|
80
86
|
};
|
|
81
|
-
// isNodeExported imported from ../export-detection.js (shared module)
|
|
82
87
|
// ============================================================================
|
|
83
|
-
//
|
|
88
|
+
// Per-file O(1) memoization — avoids repeated parent-chain walks per symbol.
|
|
89
|
+
// Three bare Maps cleared at file boundaries. Map.get() returns undefined for
|
|
90
|
+
// missing keys, so `cached !== undefined` distinguishes "not computed" from
|
|
91
|
+
// a stored null (enclosing class/function not found = top-level).
|
|
92
|
+
// ============================================================================
|
|
93
|
+
const classIdCache = new Map();
|
|
94
|
+
const functionIdCache = new Map();
|
|
95
|
+
const exportCache = new Map();
|
|
96
|
+
const clearCaches = () => { classIdCache.clear(); functionIdCache.clear(); exportCache.clear(); fieldInfoCache.clear(); };
|
|
84
97
|
// ============================================================================
|
|
85
|
-
|
|
86
|
-
|
|
98
|
+
// FieldExtractor cache — extract field metadata once per class, reuse for each property.
|
|
99
|
+
// Keyed by class node startIndex (unique per AST node within a file).
|
|
100
|
+
// ============================================================================
|
|
101
|
+
const fieldInfoCache = new Map();
|
|
102
|
+
/**
|
|
103
|
+
* Walk up from a definition node to find the nearest enclosing class/struct/interface
|
|
104
|
+
* AST node. Returns the SyntaxNode itself (not an ID) for passing to FieldExtractor.
|
|
105
|
+
*/
|
|
106
|
+
function findEnclosingClassNode(node) {
|
|
107
|
+
let current = node.parent;
|
|
108
|
+
while (current) {
|
|
109
|
+
if (CLASS_CONTAINER_TYPES.has(current.type)) {
|
|
110
|
+
return current;
|
|
111
|
+
}
|
|
112
|
+
current = current.parent;
|
|
113
|
+
}
|
|
114
|
+
return null;
|
|
115
|
+
}
|
|
116
|
+
/**
|
|
117
|
+
* Minimal no-op SymbolTable stub for FieldExtractorContext in the worker.
|
|
118
|
+
* Field extraction only uses symbolTable.lookupExactAll for optional type resolution —
|
|
119
|
+
* returning [] causes the extractor to use the raw type string, which is fine for us.
|
|
120
|
+
*/
|
|
121
|
+
const NOOP_SYMBOL_TABLE = {
|
|
122
|
+
lookupExactAll: () => [],
|
|
123
|
+
lookupExact: () => undefined,
|
|
124
|
+
lookupExactFull: () => undefined,
|
|
125
|
+
};
|
|
126
|
+
/**
|
|
127
|
+
* Get (or extract and cache) field info for a class node.
|
|
128
|
+
* Returns a name→FieldInfo map, or undefined if the provider has no field extractor
|
|
129
|
+
* or the class yielded no fields.
|
|
130
|
+
*/
|
|
131
|
+
function getFieldInfo(classNode, provider, context) {
|
|
132
|
+
if (!provider.fieldExtractor)
|
|
133
|
+
return undefined;
|
|
134
|
+
const cacheKey = classNode.startIndex;
|
|
135
|
+
let cached = fieldInfoCache.get(cacheKey);
|
|
136
|
+
if (cached)
|
|
137
|
+
return cached;
|
|
138
|
+
const result = provider.fieldExtractor.extract(classNode, context);
|
|
139
|
+
if (!result?.fields?.length)
|
|
140
|
+
return undefined;
|
|
141
|
+
cached = new Map();
|
|
142
|
+
for (const field of result.fields) {
|
|
143
|
+
cached.set(field.name, field);
|
|
144
|
+
}
|
|
145
|
+
fieldInfoCache.set(cacheKey, cached);
|
|
146
|
+
return cached;
|
|
147
|
+
}
|
|
148
|
+
/** Walk up AST to find enclosing function, return its generateId or null for top-level.
|
|
149
|
+
* Applies provider.labelOverride so the label matches the definition phase (single source of truth). */
|
|
150
|
+
const findEnclosingFunctionId = (node, filePath, provider) => {
|
|
151
|
+
const cached = functionIdCache.get(node);
|
|
152
|
+
if (cached !== undefined)
|
|
153
|
+
return cached;
|
|
87
154
|
let current = node.parent;
|
|
88
155
|
while (current) {
|
|
89
156
|
if (FUNCTION_NODE_TYPES.has(current.type)) {
|
|
90
157
|
const { funcName, label } = extractFunctionName(current);
|
|
91
158
|
if (funcName) {
|
|
92
|
-
|
|
159
|
+
// Apply labelOverride so label matches definition phase (e.g., Kotlin Function→Method).
|
|
160
|
+
// null means "skip as definition" — keep original label for scope identification.
|
|
161
|
+
let finalLabel = label;
|
|
162
|
+
if (provider.labelOverride) {
|
|
163
|
+
const override = provider.labelOverride(current, label);
|
|
164
|
+
if (override !== null)
|
|
165
|
+
finalLabel = override;
|
|
166
|
+
}
|
|
167
|
+
const result = generateId(finalLabel, `${filePath}:${funcName}`);
|
|
168
|
+
functionIdCache.set(node, result);
|
|
169
|
+
return result;
|
|
170
|
+
}
|
|
171
|
+
}
|
|
172
|
+
// Language-specific enclosing function resolution (e.g., Dart where
|
|
173
|
+
// function_body is a sibling of function_signature, not a child).
|
|
174
|
+
if (provider.enclosingFunctionFinder) {
|
|
175
|
+
const customResult = provider.enclosingFunctionFinder(current);
|
|
176
|
+
if (customResult) {
|
|
177
|
+
let finalLabel = customResult.label;
|
|
178
|
+
if (provider.labelOverride) {
|
|
179
|
+
const override = provider.labelOverride(current.previousSibling, finalLabel);
|
|
180
|
+
if (override !== null)
|
|
181
|
+
finalLabel = override;
|
|
182
|
+
}
|
|
183
|
+
const result = generateId(finalLabel, `${filePath}:${customResult.funcName}`);
|
|
184
|
+
functionIdCache.set(node, result);
|
|
185
|
+
return result;
|
|
93
186
|
}
|
|
94
187
|
}
|
|
95
188
|
current = current.parent;
|
|
96
189
|
}
|
|
190
|
+
functionIdCache.set(node, null);
|
|
97
191
|
return null;
|
|
98
192
|
};
|
|
193
|
+
/** Cached wrapper for findEnclosingClassId — avoids repeated parent walks. */
|
|
194
|
+
const cachedFindEnclosingClassId = (node, filePath) => {
|
|
195
|
+
const cached = classIdCache.get(node);
|
|
196
|
+
if (cached !== undefined)
|
|
197
|
+
return cached;
|
|
198
|
+
const result = findEnclosingClassId(node, filePath);
|
|
199
|
+
classIdCache.set(node, result);
|
|
200
|
+
return result;
|
|
201
|
+
};
|
|
202
|
+
/** Cached wrapper for export checking — avoids repeated parent walks per symbol. */
|
|
203
|
+
const cachedExportCheck = (checker, node, name) => {
|
|
204
|
+
const cached = exportCache.get(node);
|
|
205
|
+
if (cached !== undefined)
|
|
206
|
+
return cached;
|
|
207
|
+
const result = checker(node, name);
|
|
208
|
+
exportCache.set(node, result);
|
|
209
|
+
return result;
|
|
210
|
+
};
|
|
99
211
|
// Label detection moved to shared getLabelFromCaptures in utils.ts
|
|
100
212
|
// DEFINITION_CAPTURE_KEYS and getDefinitionNodeFromCaptures imported from ../utils.js
|
|
101
213
|
// ============================================================================
|
|
@@ -111,6 +223,10 @@ const processBatch = (files, onProgress) => {
|
|
|
111
223
|
assignments: [],
|
|
112
224
|
heritage: [],
|
|
113
225
|
routes: [],
|
|
226
|
+
fetchCalls: [],
|
|
227
|
+
decoratorRoutes: [],
|
|
228
|
+
toolDefs: [],
|
|
229
|
+
ormQueries: [],
|
|
114
230
|
constructorBindings: [],
|
|
115
231
|
typeEnvBindings: [],
|
|
116
232
|
skippedLanguages: {},
|
|
@@ -140,7 +256,8 @@ const processBatch = (files, onProgress) => {
|
|
|
140
256
|
}
|
|
141
257
|
} : undefined;
|
|
142
258
|
for (const [language, langFiles] of byLanguage) {
|
|
143
|
-
const
|
|
259
|
+
const provider = getProvider(language);
|
|
260
|
+
const queryString = provider.treeSitterQueries;
|
|
144
261
|
if (!queryString)
|
|
145
262
|
continue;
|
|
146
263
|
// Track if we need to handle tsx separately
|
|
@@ -192,114 +309,23 @@ const processBatch = (files, onProgress) => {
|
|
|
192
309
|
}
|
|
193
310
|
return result;
|
|
194
311
|
};
|
|
195
|
-
// ============================================================================
|
|
196
|
-
// PHP Eloquent metadata extraction
|
|
197
|
-
// ============================================================================
|
|
198
|
-
/** Eloquent model properties whose array values are worth indexing */
|
|
199
|
-
const ELOQUENT_ARRAY_PROPS = new Set(['fillable', 'casts', 'hidden', 'guarded', 'with', 'appends']);
|
|
200
|
-
/** Eloquent relationship method names */
|
|
201
|
-
const ELOQUENT_RELATIONS = new Set([
|
|
202
|
-
'hasMany', 'hasOne', 'belongsTo', 'belongsToMany',
|
|
203
|
-
'morphTo', 'morphMany', 'morphOne', 'morphToMany', 'morphedByMany',
|
|
204
|
-
'hasManyThrough', 'hasOneThrough',
|
|
205
|
-
]);
|
|
206
|
-
function findDescendant(node, type) {
|
|
207
|
-
if (node.type === type)
|
|
208
|
-
return node;
|
|
209
|
-
for (const child of (node.children ?? [])) {
|
|
210
|
-
const found = findDescendant(child, type);
|
|
211
|
-
if (found)
|
|
212
|
-
return found;
|
|
213
|
-
}
|
|
214
|
-
return null;
|
|
215
|
-
}
|
|
216
|
-
function extractStringContent(node) {
|
|
217
|
-
if (!node)
|
|
218
|
-
return null;
|
|
219
|
-
const content = node.children?.find((c) => c.type === 'string_content');
|
|
220
|
-
if (content)
|
|
221
|
-
return content.text;
|
|
222
|
-
if (node.type === 'string_content')
|
|
223
|
-
return node.text;
|
|
224
|
-
return null;
|
|
225
|
-
}
|
|
226
|
-
/**
|
|
227
|
-
* For a PHP property_declaration node, extract array values as a description string.
|
|
228
|
-
* Returns null if not an Eloquent model property or no array values found.
|
|
229
|
-
*/
|
|
230
|
-
function extractPhpPropertyDescription(propName, propDeclNode) {
|
|
231
|
-
if (!ELOQUENT_ARRAY_PROPS.has(propName))
|
|
232
|
-
return null;
|
|
233
|
-
const arrayNode = findDescendant(propDeclNode, 'array_creation_expression');
|
|
234
|
-
if (!arrayNode)
|
|
235
|
-
return null;
|
|
236
|
-
const items = [];
|
|
237
|
-
for (const child of (arrayNode.children ?? [])) {
|
|
238
|
-
if (child.type !== 'array_element_initializer')
|
|
239
|
-
continue;
|
|
240
|
-
const children = child.children ?? [];
|
|
241
|
-
const arrowIdx = children.findIndex((c) => c.type === '=>');
|
|
242
|
-
if (arrowIdx !== -1) {
|
|
243
|
-
// key => value pair (used in $casts)
|
|
244
|
-
const key = extractStringContent(children[arrowIdx - 1]);
|
|
245
|
-
const val = extractStringContent(children[arrowIdx + 1]);
|
|
246
|
-
if (key && val)
|
|
247
|
-
items.push(`${key}:${val}`);
|
|
248
|
-
}
|
|
249
|
-
else {
|
|
250
|
-
// Simple value (used in $fillable, $hidden, etc.)
|
|
251
|
-
const val = extractStringContent(children[0]);
|
|
252
|
-
if (val)
|
|
253
|
-
items.push(val);
|
|
254
|
-
}
|
|
255
|
-
}
|
|
256
|
-
return items.length > 0 ? items.join(', ') : null;
|
|
257
|
-
}
|
|
258
|
-
/**
|
|
259
|
-
* For a PHP method_declaration node, detect if it defines an Eloquent relationship.
|
|
260
|
-
* Returns description like "hasMany(Post)" or null.
|
|
261
|
-
*/
|
|
262
|
-
function extractEloquentRelationDescription(methodNode) {
|
|
263
|
-
function findRelationCall(node) {
|
|
264
|
-
if (node.type === 'member_call_expression') {
|
|
265
|
-
const children = node.children ?? [];
|
|
266
|
-
const objectNode = children.find((c) => c.type === 'variable_name' && c.text === '$this');
|
|
267
|
-
const nameNode = children.find((c) => c.type === 'name');
|
|
268
|
-
if (objectNode && nameNode && ELOQUENT_RELATIONS.has(nameNode.text))
|
|
269
|
-
return node;
|
|
270
|
-
}
|
|
271
|
-
for (const child of (node.children ?? [])) {
|
|
272
|
-
const found = findRelationCall(child);
|
|
273
|
-
if (found)
|
|
274
|
-
return found;
|
|
275
|
-
}
|
|
276
|
-
return null;
|
|
277
|
-
}
|
|
278
|
-
const callNode = findRelationCall(methodNode);
|
|
279
|
-
if (!callNode)
|
|
280
|
-
return null;
|
|
281
|
-
const relType = callNode.children?.find((c) => c.type === 'name')?.text;
|
|
282
|
-
const argsNode = callNode.children?.find((c) => c.type === 'arguments');
|
|
283
|
-
let targetModel = null;
|
|
284
|
-
if (argsNode) {
|
|
285
|
-
const firstArg = argsNode.children?.find((c) => c.type === 'argument');
|
|
286
|
-
if (firstArg) {
|
|
287
|
-
const classConstant = firstArg.children?.find((c) => c.type === 'class_constant_access_expression');
|
|
288
|
-
if (classConstant) {
|
|
289
|
-
targetModel = classConstant.children?.find((c) => c.type === 'name')?.text ?? null;
|
|
290
|
-
}
|
|
291
|
-
}
|
|
292
|
-
}
|
|
293
|
-
if (relType && targetModel)
|
|
294
|
-
return `${relType}(${targetModel})`;
|
|
295
|
-
if (relType)
|
|
296
|
-
return relType;
|
|
297
|
-
return null;
|
|
298
|
-
}
|
|
299
312
|
const ROUTE_HTTP_METHODS = new Set([
|
|
300
313
|
'get', 'post', 'put', 'patch', 'delete', 'options', 'any', 'match',
|
|
301
314
|
]);
|
|
302
315
|
const ROUTE_RESOURCE_METHODS = new Set(['resource', 'apiResource']);
|
|
316
|
+
// Express/Hono method names that register routes
|
|
317
|
+
const EXPRESS_ROUTE_METHODS = new Set(['get', 'post', 'put', 'delete', 'patch', 'all', 'use', 'route']);
|
|
318
|
+
// HTTP client methods that are ONLY used by clients, not Express route registration.
|
|
319
|
+
// Methods like get/post/put/delete/patch overlap with Express — those are captured by
|
|
320
|
+
// the express_route handler as route definitions, not consumers. The fetch() global
|
|
321
|
+
// function is captured separately by the route.fetch query.
|
|
322
|
+
const HTTP_CLIENT_ONLY_METHODS = new Set(['head', 'options', 'request', 'ajax']);
|
|
323
|
+
// Decorator names that indicate HTTP route handlers (NestJS, Flask, FastAPI, Spring)
|
|
324
|
+
const ROUTE_DECORATOR_NAMES = new Set([
|
|
325
|
+
'Get', 'Post', 'Put', 'Delete', 'Patch', 'Route',
|
|
326
|
+
'get', 'post', 'put', 'delete', 'patch', 'route',
|
|
327
|
+
'RequestMapping', 'GetMapping', 'PostMapping', 'PutMapping', 'DeleteMapping',
|
|
328
|
+
]);
|
|
303
329
|
const RESOURCE_ACTIONS = ['index', 'create', 'store', 'show', 'edit', 'update', 'destroy'];
|
|
304
330
|
const API_RESOURCE_ACTIONS = ['index', 'store', 'show', 'update', 'destroy'];
|
|
305
331
|
/** Check if node is a scoped_call_expression with object 'Route' */
|
|
@@ -643,6 +669,50 @@ function extractLaravelRoutes(tree, filePath) {
|
|
|
643
669
|
walk(tree.rootNode, []);
|
|
644
670
|
return routes;
|
|
645
671
|
}
|
|
672
|
+
// ============================================================================
|
|
673
|
+
// ORM Query Detection (Prisma + Supabase)
|
|
674
|
+
// ============================================================================
|
|
675
|
+
const PRISMA_QUERY_RE = /\bprisma\.(\w+)\.(findMany|findFirst|findUnique|findUniqueOrThrow|findFirstOrThrow|create|createMany|update|updateMany|delete|deleteMany|upsert|count|aggregate|groupBy)\s*\(/g;
|
|
676
|
+
const SUPABASE_QUERY_RE = /\bsupabase\.from\s*\(\s*['"](\w+)['"]\s*\)\s*\.(select|insert|update|delete|upsert)\s*\(/g;
|
|
677
|
+
/**
|
|
678
|
+
* Extract ORM query calls from file content via regex.
|
|
679
|
+
* Appends results to the provided array (avoids allocation when no matches).
|
|
680
|
+
*/
|
|
681
|
+
export function extractORMQueries(filePath, content, out) {
|
|
682
|
+
const hasPrisma = content.includes('prisma.');
|
|
683
|
+
const hasSupabase = content.includes('supabase.from');
|
|
684
|
+
if (!hasPrisma && !hasSupabase)
|
|
685
|
+
return;
|
|
686
|
+
if (hasPrisma) {
|
|
687
|
+
PRISMA_QUERY_RE.lastIndex = 0;
|
|
688
|
+
let m;
|
|
689
|
+
while ((m = PRISMA_QUERY_RE.exec(content)) !== null) {
|
|
690
|
+
const model = m[1];
|
|
691
|
+
if (model.startsWith('$'))
|
|
692
|
+
continue;
|
|
693
|
+
out.push({
|
|
694
|
+
filePath,
|
|
695
|
+
orm: 'prisma',
|
|
696
|
+
model,
|
|
697
|
+
method: m[2],
|
|
698
|
+
lineNumber: content.substring(0, m.index).split('\n').length - 1,
|
|
699
|
+
});
|
|
700
|
+
}
|
|
701
|
+
}
|
|
702
|
+
if (hasSupabase) {
|
|
703
|
+
SUPABASE_QUERY_RE.lastIndex = 0;
|
|
704
|
+
let m;
|
|
705
|
+
while ((m = SUPABASE_QUERY_RE.exec(content)) !== null) {
|
|
706
|
+
out.push({
|
|
707
|
+
filePath,
|
|
708
|
+
orm: 'supabase',
|
|
709
|
+
model: m[1],
|
|
710
|
+
method: m[2],
|
|
711
|
+
lineNumber: content.substring(0, m.index).split('\n').length - 1,
|
|
712
|
+
});
|
|
713
|
+
}
|
|
714
|
+
}
|
|
715
|
+
}
|
|
646
716
|
const processFileGroup = (files, language, queryString, result, onFileProcessed) => {
|
|
647
717
|
let query;
|
|
648
718
|
try {
|
|
@@ -663,6 +733,7 @@ const processFileGroup = (files, language, queryString, result, onFileProcessed)
|
|
|
663
733
|
// Skip files larger than the max tree-sitter buffer (32 MB)
|
|
664
734
|
if (file.content.length > TREE_SITTER_MAX_BUFFER)
|
|
665
735
|
continue;
|
|
736
|
+
clearCaches(); // Reset memoization before each new file
|
|
666
737
|
let tree;
|
|
667
738
|
try {
|
|
668
739
|
tree = parser.parse(file.content, undefined, { bufferSize: getTreeSitterBufferSize(file.content.length) });
|
|
@@ -711,21 +782,24 @@ const processFileGroup = (files, language, queryString, result, onFileProcessed)
|
|
|
711
782
|
// Build per-file type environment + constructor bindings in a single AST walk.
|
|
712
783
|
// Constructor bindings are verified against the SymbolTable in processCallsFromExtracted.
|
|
713
784
|
const parentMap = fileParentMap;
|
|
714
|
-
const
|
|
715
|
-
const
|
|
785
|
+
const provider = getProvider(language);
|
|
786
|
+
const typeEnv = buildTypeEnv(tree, language, { parentMap, enclosingFunctionFinder: provider?.enclosingFunctionFinder });
|
|
787
|
+
const callRouter = provider.callRouter;
|
|
716
788
|
if (typeEnv.constructorBindings.length > 0) {
|
|
717
789
|
result.constructorBindings.push({ filePath: file.path, bindings: [...typeEnv.constructorBindings] });
|
|
718
790
|
}
|
|
719
791
|
// Extract file-scope bindings for ExportedTypeMap (closes worker/sequential quality gap).
|
|
720
792
|
// Sequential path uses collectExportedBindings(typeEnv) directly; worker path serializes
|
|
721
793
|
// these bindings so the main thread can merge them into ExportedTypeMap.
|
|
722
|
-
const fileScope = typeEnv.
|
|
723
|
-
if (fileScope
|
|
794
|
+
const fileScope = typeEnv.fileScope();
|
|
795
|
+
if (fileScope.size > 0) {
|
|
724
796
|
const bindings = [];
|
|
725
797
|
for (const [name, type] of fileScope)
|
|
726
798
|
bindings.push([name, type]);
|
|
727
799
|
result.typeEnvBindings.push({ filePath: file.path, bindings });
|
|
728
800
|
}
|
|
801
|
+
// Per-file map: decorator end-line → decorator info, for associating with definitions
|
|
802
|
+
const fileDecorators = new Map();
|
|
729
803
|
for (const match of matches) {
|
|
730
804
|
const captureMap = {};
|
|
731
805
|
for (const c of match.captures) {
|
|
@@ -733,10 +807,10 @@ const processFileGroup = (files, language, queryString, result, onFileProcessed)
|
|
|
733
807
|
}
|
|
734
808
|
// Extract import paths before skipping
|
|
735
809
|
if (captureMap['import'] && captureMap['import.source']) {
|
|
736
|
-
const rawImportPath = preprocessImportPath(captureMap['import.source'].text, captureMap['import'],
|
|
810
|
+
const rawImportPath = preprocessImportPath(captureMap['import.source'].text, captureMap['import'], provider);
|
|
737
811
|
if (!rawImportPath)
|
|
738
812
|
continue;
|
|
739
|
-
const extractor =
|
|
813
|
+
const extractor = provider.namedBindingExtractor;
|
|
740
814
|
const namedBindings = extractor ? extractor(captureMap['import']) : undefined;
|
|
741
815
|
result.imports.push({
|
|
742
816
|
filePath: file.path,
|
|
@@ -751,7 +825,7 @@ const processFileGroup = (files, language, queryString, result, onFileProcessed)
|
|
|
751
825
|
const receiverText = captureMap['assignment.receiver'].text;
|
|
752
826
|
const propertyName = captureMap['assignment.property'].text;
|
|
753
827
|
if (receiverText && propertyName) {
|
|
754
|
-
const srcId = findEnclosingFunctionId(captureMap['assignment'], file.path)
|
|
828
|
+
const srcId = findEnclosingFunctionId(captureMap['assignment'], file.path, provider)
|
|
755
829
|
|| generateId('File', file.path);
|
|
756
830
|
let receiverTypeName;
|
|
757
831
|
if (typeEnv) {
|
|
@@ -768,13 +842,82 @@ const processFileGroup = (files, language, queryString, result, onFileProcessed)
|
|
|
768
842
|
if (!captureMap['call'])
|
|
769
843
|
continue;
|
|
770
844
|
}
|
|
845
|
+
// Store decorator metadata for later association with definitions
|
|
846
|
+
if (captureMap['decorator'] && captureMap['decorator.name']) {
|
|
847
|
+
const decoratorName = captureMap['decorator.name'].text;
|
|
848
|
+
const decoratorArg = captureMap['decorator.arg']?.text;
|
|
849
|
+
const decoratorNode = captureMap['decorator'];
|
|
850
|
+
// Store by the decorator's end line — the definition follows immediately after
|
|
851
|
+
fileDecorators.set(decoratorNode.endPosition.row, { name: decoratorName, arg: decoratorArg });
|
|
852
|
+
if (ROUTE_DECORATOR_NAMES.has(decoratorName)) {
|
|
853
|
+
const routePath = decoratorArg || '';
|
|
854
|
+
const method = decoratorName.replace('Mapping', '').toUpperCase();
|
|
855
|
+
const httpMethod = ['GET', 'POST', 'PUT', 'DELETE', 'PATCH'].includes(method) ? method : 'GET';
|
|
856
|
+
result.decoratorRoutes.push({
|
|
857
|
+
filePath: file.path,
|
|
858
|
+
routePath,
|
|
859
|
+
httpMethod,
|
|
860
|
+
decoratorName,
|
|
861
|
+
lineNumber: decoratorNode.startPosition.row,
|
|
862
|
+
});
|
|
863
|
+
}
|
|
864
|
+
// MCP/RPC tool detection: @mcp.tool(), @app.tool(), @server.tool()
|
|
865
|
+
if (decoratorName === 'tool') {
|
|
866
|
+
// Re-store with isTool flag for the definition handler
|
|
867
|
+
fileDecorators.set(decoratorNode.endPosition.row, { name: decoratorName, arg: decoratorArg, isTool: true });
|
|
868
|
+
}
|
|
869
|
+
continue;
|
|
870
|
+
}
|
|
871
|
+
// Extract HTTP consumer URLs: fetch(), axios.get(), $.get(), requests.get(), etc.
|
|
872
|
+
if (captureMap['route.fetch']) {
|
|
873
|
+
const urlNode = captureMap['route.url'] ?? captureMap['route.template_url'];
|
|
874
|
+
if (urlNode) {
|
|
875
|
+
result.fetchCalls.push({
|
|
876
|
+
filePath: file.path,
|
|
877
|
+
fetchURL: urlNode.text,
|
|
878
|
+
lineNumber: captureMap['route.fetch'].startPosition.row,
|
|
879
|
+
});
|
|
880
|
+
}
|
|
881
|
+
continue;
|
|
882
|
+
}
|
|
883
|
+
// HTTP client calls: axios.get('/path'), $.post('/path'), requests.get('/path')
|
|
884
|
+
// Skip methods also in EXPRESS_ROUTE_METHODS to avoid double-registering Express
|
|
885
|
+
// routes as both route definitions AND consumers (both queries match same AST node)
|
|
886
|
+
if (captureMap['http_client'] && captureMap['http_client.url']) {
|
|
887
|
+
const method = captureMap['http_client.method']?.text;
|
|
888
|
+
const url = captureMap['http_client.url'].text;
|
|
889
|
+
if (method && HTTP_CLIENT_ONLY_METHODS.has(method) && url.startsWith('/')) {
|
|
890
|
+
result.fetchCalls.push({
|
|
891
|
+
filePath: file.path,
|
|
892
|
+
fetchURL: url,
|
|
893
|
+
lineNumber: captureMap['http_client'].startPosition.row,
|
|
894
|
+
});
|
|
895
|
+
}
|
|
896
|
+
continue;
|
|
897
|
+
}
|
|
898
|
+
// Express/Hono route registration: app.get('/path', handler)
|
|
899
|
+
if (captureMap['express_route'] && captureMap['express_route.method'] && captureMap['express_route.path']) {
|
|
900
|
+
const method = captureMap['express_route.method'].text;
|
|
901
|
+
const routePath = captureMap['express_route.path'].text;
|
|
902
|
+
if (EXPRESS_ROUTE_METHODS.has(method) && routePath.startsWith('/')) {
|
|
903
|
+
const httpMethod = method === 'all' || method === 'use' || method === 'route' ? 'GET' : method.toUpperCase();
|
|
904
|
+
result.decoratorRoutes.push({
|
|
905
|
+
filePath: file.path,
|
|
906
|
+
routePath,
|
|
907
|
+
httpMethod,
|
|
908
|
+
decoratorName: `express.${method}`,
|
|
909
|
+
lineNumber: captureMap['express_route'].startPosition.row,
|
|
910
|
+
});
|
|
911
|
+
}
|
|
912
|
+
continue;
|
|
913
|
+
}
|
|
771
914
|
// Extract call sites
|
|
772
915
|
if (captureMap['call']) {
|
|
773
916
|
const callNameNode = captureMap['call.name'];
|
|
774
917
|
if (callNameNode) {
|
|
775
918
|
const calledName = callNameNode.text;
|
|
776
919
|
// Dispatch: route language-specific calls (heritage, properties, imports)
|
|
777
|
-
const routed = callRouter(calledName, captureMap['call']);
|
|
920
|
+
const routed = callRouter?.(calledName, captureMap['call']);
|
|
778
921
|
if (routed) {
|
|
779
922
|
if (routed.kind === 'skip')
|
|
780
923
|
continue;
|
|
@@ -798,8 +941,19 @@ const processFileGroup = (files, language, queryString, result, onFileProcessed)
|
|
|
798
941
|
continue;
|
|
799
942
|
}
|
|
800
943
|
if (routed.kind === 'properties') {
|
|
801
|
-
const propEnclosingClassId =
|
|
944
|
+
const propEnclosingClassId = cachedFindEnclosingClassId(captureMap['call'], file.path);
|
|
945
|
+
// Enrich routed properties with FieldExtractor metadata
|
|
946
|
+
let routedFieldMap;
|
|
947
|
+
if (provider.fieldExtractor && typeEnv) {
|
|
948
|
+
const classNode = findEnclosingClassNode(captureMap['call']);
|
|
949
|
+
if (classNode) {
|
|
950
|
+
routedFieldMap = getFieldInfo(classNode, provider, {
|
|
951
|
+
typeEnv, symbolTable: NOOP_SYMBOL_TABLE, filePath: file.path, language,
|
|
952
|
+
});
|
|
953
|
+
}
|
|
954
|
+
}
|
|
802
955
|
for (const item of routed.items) {
|
|
956
|
+
const routedFieldInfo = routedFieldMap?.get(item.propName);
|
|
803
957
|
const nodeId = generateId('Property', `${file.path}:${item.propName}`);
|
|
804
958
|
result.nodes.push({
|
|
805
959
|
id: nodeId,
|
|
@@ -812,6 +966,10 @@ const processFileGroup = (files, language, queryString, result, onFileProcessed)
|
|
|
812
966
|
language,
|
|
813
967
|
isExported: true,
|
|
814
968
|
description: item.accessorType,
|
|
969
|
+
...(item.declaredType ? { declaredType: item.declaredType } : routedFieldInfo?.type ? { declaredType: routedFieldInfo.type } : {}),
|
|
970
|
+
...(routedFieldInfo?.visibility !== undefined ? { visibility: routedFieldInfo.visibility } : {}),
|
|
971
|
+
...(routedFieldInfo?.isStatic !== undefined ? { isStatic: routedFieldInfo.isStatic } : {}),
|
|
972
|
+
...(routedFieldInfo?.isReadonly !== undefined ? { isReadonly: routedFieldInfo.isReadonly } : {}),
|
|
815
973
|
},
|
|
816
974
|
});
|
|
817
975
|
result.symbols.push({
|
|
@@ -820,7 +978,10 @@ const processFileGroup = (files, language, queryString, result, onFileProcessed)
|
|
|
820
978
|
nodeId,
|
|
821
979
|
type: 'Property',
|
|
822
980
|
...(propEnclosingClassId ? { ownerId: propEnclosingClassId } : {}),
|
|
823
|
-
...(item.declaredType ? { declaredType: item.declaredType } : {}),
|
|
981
|
+
...(item.declaredType ? { declaredType: item.declaredType } : routedFieldInfo?.type ? { declaredType: routedFieldInfo.type } : {}),
|
|
982
|
+
...(routedFieldInfo?.visibility !== undefined ? { visibility: routedFieldInfo.visibility } : {}),
|
|
983
|
+
...(routedFieldInfo?.isStatic !== undefined ? { isStatic: routedFieldInfo.isStatic } : {}),
|
|
984
|
+
...(routedFieldInfo?.isReadonly !== undefined ? { isReadonly: routedFieldInfo.isReadonly } : {}),
|
|
824
985
|
});
|
|
825
986
|
const fileId = generateId('File', file.path);
|
|
826
987
|
const relId = generateId('DEFINES', `${fileId}->${nodeId}`);
|
|
@@ -847,9 +1008,9 @@ const processFileGroup = (files, language, queryString, result, onFileProcessed)
|
|
|
847
1008
|
}
|
|
848
1009
|
// kind === 'call' — fall through to normal call processing below
|
|
849
1010
|
}
|
|
850
|
-
if (!
|
|
1011
|
+
if (!provider.isBuiltInName(calledName)) {
|
|
851
1012
|
const callNode = captureMap['call'];
|
|
852
|
-
const sourceId = findEnclosingFunctionId(callNode, file.path)
|
|
1013
|
+
const sourceId = findEnclosingFunctionId(callNode, file.path, provider)
|
|
853
1014
|
|| generateId('File', file.path);
|
|
854
1015
|
const callForm = inferCallForm(callNode, callNameNode);
|
|
855
1016
|
let receiverName = callForm === 'member' ? extractReceiverName(callNameNode) : undefined;
|
|
@@ -926,7 +1087,7 @@ const processFileGroup = (files, language, queryString, result, onFileProcessed)
|
|
|
926
1087
|
continue;
|
|
927
1088
|
}
|
|
928
1089
|
}
|
|
929
|
-
const nodeLabel = getLabelFromCaptures(captureMap,
|
|
1090
|
+
const nodeLabel = getLabelFromCaptures(captureMap, provider);
|
|
930
1091
|
if (!nodeLabel)
|
|
931
1092
|
continue;
|
|
932
1093
|
const nameNode = captureMap['name'];
|
|
@@ -937,23 +1098,47 @@ const processFileGroup = (files, language, queryString, result, onFileProcessed)
|
|
|
937
1098
|
const definitionNode = getDefinitionNodeFromCaptures(captureMap);
|
|
938
1099
|
const startLine = definitionNode ? definitionNode.startPosition.row : (nameNode ? nameNode.startPosition.row : 0);
|
|
939
1100
|
const nodeId = generateId(nodeLabel, `${file.path}:${nodeName}`);
|
|
940
|
-
|
|
941
|
-
|
|
942
|
-
if (nodeLabel === 'Property' && captureMap['definition.property']) {
|
|
943
|
-
description = extractPhpPropertyDescription(nodeName, captureMap['definition.property']) ?? undefined;
|
|
944
|
-
}
|
|
945
|
-
else if (nodeLabel === 'Method' && captureMap['definition.method']) {
|
|
946
|
-
description = extractEloquentRelationDescription(captureMap['definition.method']) ?? undefined;
|
|
947
|
-
}
|
|
948
|
-
}
|
|
949
|
-
const frameworkHint = definitionNode
|
|
1101
|
+
const description = provider.descriptionExtractor?.(nodeLabel, nodeName, captureMap);
|
|
1102
|
+
let frameworkHint = definitionNode
|
|
950
1103
|
? detectFrameworkFromAST(language, (definitionNode.text || '').slice(0, 300))
|
|
951
1104
|
: null;
|
|
1105
|
+
// Decorators appear on lines immediately before their definition; allow up to
|
|
1106
|
+
// MAX_DECORATOR_SCAN_LINES gap for blank lines / multi-line decorator stacks.
|
|
1107
|
+
const MAX_DECORATOR_SCAN_LINES = 5;
|
|
1108
|
+
if (definitionNode) {
|
|
1109
|
+
const defStartLine = definitionNode.startPosition.row;
|
|
1110
|
+
for (let checkLine = defStartLine - 1; checkLine >= Math.max(0, defStartLine - MAX_DECORATOR_SCAN_LINES); checkLine--) {
|
|
1111
|
+
const dec = fileDecorators.get(checkLine);
|
|
1112
|
+
if (dec) {
|
|
1113
|
+
// Use first (closest) decorator found for framework hint
|
|
1114
|
+
if (!frameworkHint) {
|
|
1115
|
+
frameworkHint = {
|
|
1116
|
+
framework: 'decorator',
|
|
1117
|
+
entryPointMultiplier: 1.2,
|
|
1118
|
+
reason: `@${dec.name}${dec.arg ? `("${dec.arg}")` : ''}`,
|
|
1119
|
+
};
|
|
1120
|
+
}
|
|
1121
|
+
// Emit tool definition if this is a @tool decorator
|
|
1122
|
+
if (dec.isTool) {
|
|
1123
|
+
result.toolDefs.push({
|
|
1124
|
+
filePath: file.path,
|
|
1125
|
+
toolName: nodeName,
|
|
1126
|
+
description: dec.arg || '',
|
|
1127
|
+
lineNumber: definitionNode.startPosition.row,
|
|
1128
|
+
});
|
|
1129
|
+
}
|
|
1130
|
+
fileDecorators.delete(checkLine);
|
|
1131
|
+
}
|
|
1132
|
+
}
|
|
1133
|
+
}
|
|
952
1134
|
let parameterCount;
|
|
953
1135
|
let requiredParameterCount;
|
|
954
1136
|
let parameterTypes;
|
|
955
1137
|
let returnType;
|
|
956
1138
|
let declaredType;
|
|
1139
|
+
let visibility;
|
|
1140
|
+
let isStatic;
|
|
1141
|
+
let isReadonly;
|
|
957
1142
|
if (nodeLabel === 'Function' || nodeLabel === 'Method' || nodeLabel === 'Constructor') {
|
|
958
1143
|
const sig = extractMethodSignature(definitionNode);
|
|
959
1144
|
parameterCount = sig.parameterCount;
|
|
@@ -963,7 +1148,7 @@ const processFileGroup = (files, language, queryString, result, onFileProcessed)
|
|
|
963
1148
|
// Language-specific return type fallback (e.g. Ruby YARD @return [Type])
|
|
964
1149
|
// Also upgrades uninformative AST types like PHP `array` with PHPDoc `@return User[]`
|
|
965
1150
|
if ((!returnType || returnType === 'array' || returnType === 'iterable') && definitionNode) {
|
|
966
|
-
const tc =
|
|
1151
|
+
const tc = provider.typeConfig;
|
|
967
1152
|
if (tc?.extractReturnType) {
|
|
968
1153
|
const docReturn = tc.extractReturnType(definitionNode);
|
|
969
1154
|
if (docReturn)
|
|
@@ -972,9 +1157,22 @@ const processFileGroup = (files, language, queryString, result, onFileProcessed)
|
|
|
972
1157
|
}
|
|
973
1158
|
}
|
|
974
1159
|
else if (nodeLabel === 'Property' && definitionNode) {
|
|
975
|
-
//
|
|
976
|
-
|
|
977
|
-
|
|
1160
|
+
// FieldExtractor is the single source of truth when available
|
|
1161
|
+
if (provider.fieldExtractor && typeEnv) {
|
|
1162
|
+
const classNode = findEnclosingClassNode(definitionNode);
|
|
1163
|
+
if (classNode) {
|
|
1164
|
+
const fieldMap = getFieldInfo(classNode, provider, {
|
|
1165
|
+
typeEnv, symbolTable: NOOP_SYMBOL_TABLE, filePath: file.path, language,
|
|
1166
|
+
});
|
|
1167
|
+
const info = fieldMap?.get(nodeName);
|
|
1168
|
+
if (info) {
|
|
1169
|
+
declaredType = info.type ?? undefined;
|
|
1170
|
+
visibility = info.visibility;
|
|
1171
|
+
isStatic = info.isStatic;
|
|
1172
|
+
isReadonly = info.isReadonly;
|
|
1173
|
+
}
|
|
1174
|
+
}
|
|
1175
|
+
}
|
|
978
1176
|
}
|
|
979
1177
|
result.nodes.push({
|
|
980
1178
|
id: nodeId,
|
|
@@ -985,7 +1183,7 @@ const processFileGroup = (files, language, queryString, result, onFileProcessed)
|
|
|
985
1183
|
startLine: definitionNode ? definitionNode.startPosition.row : startLine,
|
|
986
1184
|
endLine: definitionNode ? definitionNode.endPosition.row : startLine,
|
|
987
1185
|
language: language,
|
|
988
|
-
isExported:
|
|
1186
|
+
isExported: cachedExportCheck(provider.exportChecker, nameNode || definitionNode, nodeName),
|
|
989
1187
|
...(frameworkHint ? {
|
|
990
1188
|
astFrameworkMultiplier: frameworkHint.entryPointMultiplier,
|
|
991
1189
|
astFrameworkReason: frameworkHint.reason,
|
|
@@ -995,12 +1193,16 @@ const processFileGroup = (files, language, queryString, result, onFileProcessed)
|
|
|
995
1193
|
...(requiredParameterCount !== undefined ? { requiredParameterCount } : {}),
|
|
996
1194
|
...(parameterTypes !== undefined ? { parameterTypes } : {}),
|
|
997
1195
|
...(returnType !== undefined ? { returnType } : {}),
|
|
1196
|
+
...(declaredType !== undefined ? { declaredType } : {}),
|
|
1197
|
+
...(visibility !== undefined ? { visibility } : {}),
|
|
1198
|
+
...(isStatic !== undefined ? { isStatic } : {}),
|
|
1199
|
+
...(isReadonly !== undefined ? { isReadonly } : {}),
|
|
998
1200
|
},
|
|
999
1201
|
});
|
|
1000
1202
|
// Compute enclosing class for Method/Constructor/Property/Function — used for both ownerId and HAS_METHOD
|
|
1001
1203
|
// Function is included because Kotlin/Rust/Python capture class methods as Function nodes
|
|
1002
1204
|
const needsOwner = nodeLabel === 'Method' || nodeLabel === 'Constructor' || nodeLabel === 'Property' || nodeLabel === 'Function';
|
|
1003
|
-
const enclosingClassId = needsOwner ?
|
|
1205
|
+
const enclosingClassId = needsOwner ? cachedFindEnclosingClassId(nameNode || definitionNode, file.path) : null;
|
|
1004
1206
|
result.symbols.push({
|
|
1005
1207
|
filePath: file.path,
|
|
1006
1208
|
name: nodeName,
|
|
@@ -1012,6 +1214,9 @@ const processFileGroup = (files, language, queryString, result, onFileProcessed)
|
|
|
1012
1214
|
...(returnType !== undefined ? { returnType } : {}),
|
|
1013
1215
|
...(declaredType !== undefined ? { declaredType } : {}),
|
|
1014
1216
|
...(enclosingClassId ? { ownerId: enclosingClassId } : {}),
|
|
1217
|
+
...(visibility !== undefined ? { visibility } : {}),
|
|
1218
|
+
...(isStatic !== undefined ? { isStatic } : {}),
|
|
1219
|
+
...(isReadonly !== undefined ? { isReadonly } : {}),
|
|
1015
1220
|
});
|
|
1016
1221
|
const fileId = generateId('File', file.path);
|
|
1017
1222
|
const relId = generateId('DEFINES', `${fileId}->${nodeId}`);
|
|
@@ -1036,11 +1241,13 @@ const processFileGroup = (files, language, queryString, result, onFileProcessed)
|
|
|
1036
1241
|
});
|
|
1037
1242
|
}
|
|
1038
1243
|
}
|
|
1039
|
-
// Extract
|
|
1040
|
-
if (
|
|
1244
|
+
// Extract framework routes via provider detection (e.g., Laravel routes.php)
|
|
1245
|
+
if (provider.isRouteFile?.(file.path)) {
|
|
1041
1246
|
const extractedRoutes = extractLaravelRoutes(tree, file.path);
|
|
1042
1247
|
result.routes.push(...extractedRoutes);
|
|
1043
1248
|
}
|
|
1249
|
+
// Extract ORM queries (Prisma, Supabase)
|
|
1250
|
+
extractORMQueries(file.path, file.content, result.ormQueries);
|
|
1044
1251
|
}
|
|
1045
1252
|
};
|
|
1046
1253
|
// ============================================================================
|
|
@@ -1049,7 +1256,7 @@ const processFileGroup = (files, language, queryString, result, onFileProcessed)
|
|
|
1049
1256
|
/** Accumulated result across sub-batches */
|
|
1050
1257
|
let accumulated = {
|
|
1051
1258
|
nodes: [], relationships: [], symbols: [],
|
|
1052
|
-
imports: [], calls: [], assignments: [], heritage: [], routes: [], constructorBindings: [], typeEnvBindings: [], skippedLanguages: {}, fileCount: 0,
|
|
1259
|
+
imports: [], calls: [], assignments: [], heritage: [], routes: [], fetchCalls: [], decoratorRoutes: [], toolDefs: [], ormQueries: [], constructorBindings: [], typeEnvBindings: [], skippedLanguages: {}, fileCount: 0,
|
|
1053
1260
|
};
|
|
1054
1261
|
let cumulativeProcessed = 0;
|
|
1055
1262
|
const mergeResult = (target, src) => {
|
|
@@ -1061,6 +1268,10 @@ const mergeResult = (target, src) => {
|
|
|
1061
1268
|
target.assignments.push(...src.assignments);
|
|
1062
1269
|
target.heritage.push(...src.heritage);
|
|
1063
1270
|
target.routes.push(...src.routes);
|
|
1271
|
+
target.fetchCalls.push(...src.fetchCalls);
|
|
1272
|
+
target.decoratorRoutes.push(...src.decoratorRoutes);
|
|
1273
|
+
target.toolDefs.push(...src.toolDefs);
|
|
1274
|
+
target.ormQueries.push(...src.ormQueries);
|
|
1064
1275
|
target.constructorBindings.push(...src.constructorBindings);
|
|
1065
1276
|
target.typeEnvBindings.push(...src.typeEnvBindings);
|
|
1066
1277
|
for (const [lang, count] of Object.entries(src.skippedLanguages)) {
|
|
@@ -1085,7 +1296,7 @@ parentPort.on('message', (msg) => {
|
|
|
1085
1296
|
if (msg && msg.type === 'flush') {
|
|
1086
1297
|
parentPort.postMessage({ type: 'result', data: accumulated });
|
|
1087
1298
|
// Reset for potential reuse
|
|
1088
|
-
accumulated = { nodes: [], relationships: [], symbols: [], imports: [], calls: [], assignments: [], heritage: [], routes: [], constructorBindings: [], typeEnvBindings: [], skippedLanguages: {}, fileCount: 0 };
|
|
1299
|
+
accumulated = { nodes: [], relationships: [], symbols: [], imports: [], calls: [], assignments: [], heritage: [], routes: [], fetchCalls: [], decoratorRoutes: [], toolDefs: [], ormQueries: [], constructorBindings: [], typeEnvBindings: [], skippedLanguages: {}, fileCount: 0 };
|
|
1089
1300
|
cumulativeProcessed = 0;
|
|
1090
1301
|
return;
|
|
1091
1302
|
}
|