@colbymchenry/codegraph 0.6.8 → 0.7.2
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 +179 -476
- package/dist/bin/codegraph.d.ts +0 -5
- package/dist/bin/codegraph.d.ts.map +1 -1
- package/dist/bin/codegraph.js +217 -237
- package/dist/bin/codegraph.js.map +1 -1
- package/dist/bin/uninstall.d.ts +0 -1
- package/dist/bin/uninstall.d.ts.map +1 -1
- package/dist/bin/uninstall.js +3 -29
- package/dist/bin/uninstall.js.map +1 -1
- package/dist/context/index.d.ts +3 -5
- package/dist/context/index.d.ts.map +1 -1
- package/dist/context/index.js +497 -46
- package/dist/context/index.js.map +1 -1
- package/dist/db/migrations.d.ts +1 -1
- package/dist/db/migrations.d.ts.map +1 -1
- package/dist/db/migrations.js +10 -1
- package/dist/db/migrations.js.map +1 -1
- package/dist/db/queries.d.ts +53 -0
- package/dist/db/queries.d.ts.map +1 -1
- package/dist/db/queries.js +244 -14
- package/dist/db/queries.js.map +1 -1
- package/dist/db/schema.sql +1 -16
- package/dist/extraction/dfm-extractor.d.ts +31 -0
- package/dist/extraction/dfm-extractor.d.ts.map +1 -0
- package/dist/extraction/dfm-extractor.js +151 -0
- package/dist/extraction/dfm-extractor.js.map +1 -0
- package/dist/extraction/grammars.d.ts +9 -1
- package/dist/extraction/grammars.d.ts.map +1 -1
- package/dist/extraction/grammars.js +34 -2
- package/dist/extraction/grammars.js.map +1 -1
- package/dist/extraction/index.d.ts +7 -1
- package/dist/extraction/index.d.ts.map +1 -1
- package/dist/extraction/index.js +373 -22
- package/dist/extraction/index.js.map +1 -1
- package/dist/extraction/languages/c-cpp.d.ts +4 -0
- package/dist/extraction/languages/c-cpp.d.ts.map +1 -0
- package/dist/extraction/languages/c-cpp.js +126 -0
- package/dist/extraction/languages/c-cpp.js.map +1 -0
- package/dist/extraction/languages/csharp.d.ts +3 -0
- package/dist/extraction/languages/csharp.d.ts.map +1 -0
- package/dist/extraction/languages/csharp.js +72 -0
- package/dist/extraction/languages/csharp.js.map +1 -0
- package/dist/extraction/languages/dart.d.ts +3 -0
- package/dist/extraction/languages/dart.d.ts.map +1 -0
- package/dist/extraction/languages/dart.js +192 -0
- package/dist/extraction/languages/dart.js.map +1 -0
- package/dist/extraction/languages/go.d.ts +3 -0
- package/dist/extraction/languages/go.d.ts.map +1 -0
- package/dist/extraction/languages/go.js +58 -0
- package/dist/extraction/languages/go.js.map +1 -0
- package/dist/extraction/languages/index.d.ts +10 -0
- package/dist/extraction/languages/index.d.ts.map +1 -0
- package/dist/extraction/languages/index.js +43 -0
- package/dist/extraction/languages/index.js.map +1 -0
- package/dist/extraction/languages/java.d.ts +3 -0
- package/dist/extraction/languages/java.d.ts.map +1 -0
- package/dist/extraction/languages/java.js +64 -0
- package/dist/extraction/languages/java.js.map +1 -0
- package/dist/extraction/languages/javascript.d.ts +3 -0
- package/dist/extraction/languages/javascript.d.ts.map +1 -0
- package/dist/extraction/languages/javascript.js +90 -0
- package/dist/extraction/languages/javascript.js.map +1 -0
- package/dist/extraction/languages/kotlin.d.ts +3 -0
- package/dist/extraction/languages/kotlin.d.ts.map +1 -0
- package/dist/extraction/languages/kotlin.js +253 -0
- package/dist/extraction/languages/kotlin.js.map +1 -0
- package/dist/extraction/languages/pascal.d.ts +3 -0
- package/dist/extraction/languages/pascal.d.ts.map +1 -0
- package/dist/extraction/languages/pascal.js +66 -0
- package/dist/extraction/languages/pascal.js.map +1 -0
- package/dist/extraction/languages/php.d.ts +3 -0
- package/dist/extraction/languages/php.d.ts.map +1 -0
- package/dist/extraction/languages/php.js +107 -0
- package/dist/extraction/languages/php.js.map +1 -0
- package/dist/extraction/languages/python.d.ts +3 -0
- package/dist/extraction/languages/python.d.ts.map +1 -0
- package/dist/extraction/languages/python.js +56 -0
- package/dist/extraction/languages/python.js.map +1 -0
- package/dist/extraction/languages/ruby.d.ts +3 -0
- package/dist/extraction/languages/ruby.d.ts.map +1 -0
- package/dist/extraction/languages/ruby.js +114 -0
- package/dist/extraction/languages/ruby.js.map +1 -0
- package/dist/extraction/languages/rust.d.ts +3 -0
- package/dist/extraction/languages/rust.d.ts.map +1 -0
- package/dist/extraction/languages/rust.js +109 -0
- package/dist/extraction/languages/rust.js.map +1 -0
- package/dist/extraction/languages/swift.d.ts +3 -0
- package/dist/extraction/languages/swift.d.ts.map +1 -0
- package/dist/extraction/languages/swift.js +91 -0
- package/dist/extraction/languages/swift.js.map +1 -0
- package/dist/extraction/languages/typescript.d.ts +3 -0
- package/dist/extraction/languages/typescript.d.ts.map +1 -0
- package/dist/extraction/languages/typescript.js +129 -0
- package/dist/extraction/languages/typescript.js.map +1 -0
- package/dist/extraction/liquid-extractor.d.ts +52 -0
- package/dist/extraction/liquid-extractor.d.ts.map +1 -0
- package/dist/extraction/liquid-extractor.js +313 -0
- package/dist/extraction/liquid-extractor.js.map +1 -0
- package/dist/extraction/parse-worker.d.ts +8 -0
- package/dist/extraction/parse-worker.d.ts.map +1 -0
- package/dist/extraction/parse-worker.js +57 -0
- package/dist/extraction/parse-worker.js.map +1 -0
- package/dist/extraction/svelte-extractor.d.ts +47 -0
- package/dist/extraction/svelte-extractor.d.ts.map +1 -0
- package/dist/extraction/svelte-extractor.js +230 -0
- package/dist/extraction/svelte-extractor.js.map +1 -0
- package/dist/extraction/tree-sitter-helpers.d.ts +28 -0
- package/dist/extraction/tree-sitter-helpers.d.ts.map +1 -0
- package/dist/extraction/tree-sitter-helpers.js +103 -0
- package/dist/extraction/tree-sitter-helpers.js.map +1 -0
- package/dist/extraction/tree-sitter-types.d.ts +179 -0
- package/dist/extraction/tree-sitter-types.d.ts.map +1 -0
- package/dist/extraction/tree-sitter-types.js +10 -0
- package/dist/extraction/tree-sitter-types.js.map +1 -0
- package/dist/extraction/tree-sitter.d.ts +67 -125
- package/dist/extraction/tree-sitter.d.ts.map +1 -1
- package/dist/extraction/tree-sitter.js +1052 -1855
- package/dist/extraction/tree-sitter.js.map +1 -1
- package/dist/graph/traversal.d.ts.map +1 -1
- package/dist/graph/traversal.js +20 -2
- package/dist/graph/traversal.js.map +1 -1
- package/dist/index.d.ts +29 -53
- package/dist/index.d.ts.map +1 -1
- package/dist/index.js +88 -114
- package/dist/index.js.map +1 -1
- package/dist/installer/claude-md-template.d.ts +1 -1
- package/dist/installer/claude-md-template.d.ts.map +1 -1
- package/dist/installer/claude-md-template.js +15 -15
- package/dist/installer/config-writer.d.ts +1 -10
- package/dist/installer/config-writer.d.ts.map +1 -1
- package/dist/installer/config-writer.js +0 -79
- package/dist/installer/config-writer.js.map +1 -1
- package/dist/installer/index.d.ts +3 -4
- package/dist/installer/index.d.ts.map +1 -1
- package/dist/installer/index.js +118 -116
- package/dist/installer/index.js.map +1 -1
- package/dist/mcp/index.d.ts +5 -0
- package/dist/mcp/index.d.ts.map +1 -1
- package/dist/mcp/index.js +25 -1
- package/dist/mcp/index.js.map +1 -1
- package/dist/mcp/tools.d.ts +33 -0
- package/dist/mcp/tools.d.ts.map +1 -1
- package/dist/mcp/tools.js +405 -21
- package/dist/mcp/tools.js.map +1 -1
- package/dist/resolution/frameworks/csharp.js +29 -84
- package/dist/resolution/frameworks/csharp.js.map +1 -1
- package/dist/resolution/frameworks/express.js +44 -48
- package/dist/resolution/frameworks/express.js.map +1 -1
- package/dist/resolution/frameworks/go.js +34 -70
- package/dist/resolution/frameworks/go.js.map +1 -1
- package/dist/resolution/frameworks/java.js +29 -87
- package/dist/resolution/frameworks/java.js.map +1 -1
- package/dist/resolution/frameworks/laravel.js +6 -6
- package/dist/resolution/frameworks/laravel.js.map +1 -1
- package/dist/resolution/frameworks/python.js +33 -98
- package/dist/resolution/frameworks/python.js.map +1 -1
- package/dist/resolution/frameworks/react.js +53 -76
- package/dist/resolution/frameworks/react.js.map +1 -1
- package/dist/resolution/frameworks/ruby.js +12 -24
- package/dist/resolution/frameworks/ruby.js.map +1 -1
- package/dist/resolution/frameworks/rust.js +26 -66
- package/dist/resolution/frameworks/rust.js.map +1 -1
- package/dist/resolution/frameworks/svelte.js +11 -31
- package/dist/resolution/frameworks/svelte.js.map +1 -1
- package/dist/resolution/frameworks/swift.js +42 -160
- package/dist/resolution/frameworks/swift.js.map +1 -1
- package/dist/resolution/index.d.ts +19 -6
- package/dist/resolution/index.d.ts.map +1 -1
- package/dist/resolution/index.js +300 -141
- package/dist/resolution/index.js.map +1 -1
- package/dist/resolution/name-matcher.d.ts +5 -0
- package/dist/resolution/name-matcher.d.ts.map +1 -1
- package/dist/resolution/name-matcher.js +148 -8
- package/dist/resolution/name-matcher.js.map +1 -1
- package/dist/resolution/types.d.ts +1 -1
- package/dist/resolution/types.d.ts.map +1 -1
- package/dist/search/query-utils.d.ts +26 -1
- package/dist/search/query-utils.d.ts.map +1 -1
- package/dist/search/query-utils.js +209 -9
- package/dist/search/query-utils.js.map +1 -1
- package/dist/sync/index.d.ts +2 -4
- package/dist/sync/index.d.ts.map +1 -1
- package/dist/sync/index.js +4 -3
- package/dist/sync/index.js.map +1 -1
- package/dist/sync/watcher.d.ts +81 -0
- package/dist/sync/watcher.d.ts.map +1 -0
- package/dist/sync/watcher.js +184 -0
- package/dist/sync/watcher.js.map +1 -0
- package/dist/types.d.ts +2 -0
- package/dist/types.d.ts.map +1 -1
- package/dist/types.js.map +1 -1
- package/dist/ui/shimmer-progress.d.ts +11 -0
- package/dist/ui/shimmer-progress.d.ts.map +1 -0
- package/dist/ui/shimmer-progress.js +90 -0
- package/dist/ui/shimmer-progress.js.map +1 -0
- package/dist/ui/shimmer-worker.d.ts +2 -0
- package/dist/ui/shimmer-worker.d.ts.map +1 -0
- package/dist/ui/shimmer-worker.js +112 -0
- package/dist/ui/shimmer-worker.js.map +1 -0
- package/dist/ui/types.d.ts +17 -0
- package/dist/ui/types.d.ts.map +1 -0
- package/dist/ui/types.js +3 -0
- package/dist/ui/types.js.map +1 -0
- package/dist/vectors/embedder.js +1 -1
- package/dist/vectors/embedder.js.map +1 -1
- package/package.json +7 -12
- package/scripts/postinstall.js +0 -68
|
@@ -38,812 +38,39 @@ var __importStar = (this && this.__importStar) || (function () {
|
|
|
38
38
|
};
|
|
39
39
|
})();
|
|
40
40
|
Object.defineProperty(exports, "__esModule", { value: true });
|
|
41
|
-
exports.
|
|
42
|
-
exports.generateNodeId = generateNodeId;
|
|
41
|
+
exports.TreeSitterExtractor = exports.generateNodeId = void 0;
|
|
43
42
|
exports.extractFromSource = extractFromSource;
|
|
44
|
-
const crypto = __importStar(require("crypto"));
|
|
45
43
|
const path = __importStar(require("path"));
|
|
46
44
|
const grammars_1 = require("./grammars");
|
|
47
|
-
|
|
48
|
-
|
|
49
|
-
|
|
50
|
-
|
|
51
|
-
|
|
52
|
-
|
|
53
|
-
|
|
54
|
-
|
|
55
|
-
.createHash('sha256')
|
|
56
|
-
.update(`${filePath}:${kind}:${name}:${line}`)
|
|
57
|
-
.digest('hex')
|
|
58
|
-
.substring(0, 32);
|
|
59
|
-
return `${kind}:${hash}`;
|
|
60
|
-
}
|
|
61
|
-
/**
|
|
62
|
-
* Extract text from a syntax node
|
|
63
|
-
*/
|
|
64
|
-
function getNodeText(node, source) {
|
|
65
|
-
return source.substring(node.startIndex, node.endIndex);
|
|
66
|
-
}
|
|
67
|
-
/**
|
|
68
|
-
* Find a child node by field name
|
|
69
|
-
*/
|
|
70
|
-
function getChildByField(node, fieldName) {
|
|
71
|
-
return node.childForFieldName(fieldName);
|
|
72
|
-
}
|
|
73
|
-
/**
|
|
74
|
-
* Get the docstring/comment preceding a node
|
|
75
|
-
*/
|
|
76
|
-
function getPrecedingDocstring(node, source) {
|
|
77
|
-
let sibling = node.previousNamedSibling;
|
|
78
|
-
const comments = [];
|
|
79
|
-
while (sibling) {
|
|
80
|
-
if (sibling.type === 'comment' ||
|
|
81
|
-
sibling.type === 'line_comment' ||
|
|
82
|
-
sibling.type === 'block_comment' ||
|
|
83
|
-
sibling.type === 'documentation_comment') {
|
|
84
|
-
comments.unshift(getNodeText(sibling, source));
|
|
85
|
-
sibling = sibling.previousNamedSibling;
|
|
86
|
-
}
|
|
87
|
-
else {
|
|
88
|
-
break;
|
|
89
|
-
}
|
|
90
|
-
}
|
|
91
|
-
if (comments.length === 0)
|
|
92
|
-
return undefined;
|
|
93
|
-
// Clean up comment markers
|
|
94
|
-
return comments
|
|
95
|
-
.map((c) => c
|
|
96
|
-
.replace(/^\/\*\*?|\*\/$/g, '')
|
|
97
|
-
.replace(/^\/\/\s?/gm, '')
|
|
98
|
-
.replace(/^\s*\*\s?/gm, '')
|
|
99
|
-
.trim())
|
|
100
|
-
.join('\n')
|
|
101
|
-
.trim();
|
|
102
|
-
}
|
|
103
|
-
/**
|
|
104
|
-
* Language-specific extractors
|
|
105
|
-
*/
|
|
106
|
-
const EXTRACTORS = {
|
|
107
|
-
typescript: {
|
|
108
|
-
functionTypes: ['function_declaration', 'arrow_function', 'function_expression'],
|
|
109
|
-
classTypes: ['class_declaration'],
|
|
110
|
-
methodTypes: ['method_definition', 'public_field_definition'],
|
|
111
|
-
interfaceTypes: ['interface_declaration'],
|
|
112
|
-
structTypes: [],
|
|
113
|
-
enumTypes: ['enum_declaration'],
|
|
114
|
-
typeAliasTypes: ['type_alias_declaration'],
|
|
115
|
-
importTypes: ['import_statement'],
|
|
116
|
-
callTypes: ['call_expression'],
|
|
117
|
-
variableTypes: ['lexical_declaration', 'variable_declaration'],
|
|
118
|
-
nameField: 'name',
|
|
119
|
-
bodyField: 'body',
|
|
120
|
-
paramsField: 'parameters',
|
|
121
|
-
returnField: 'return_type',
|
|
122
|
-
getSignature: (node, source) => {
|
|
123
|
-
const params = getChildByField(node, 'parameters');
|
|
124
|
-
const returnType = getChildByField(node, 'return_type');
|
|
125
|
-
if (!params)
|
|
126
|
-
return undefined;
|
|
127
|
-
let sig = getNodeText(params, source);
|
|
128
|
-
if (returnType) {
|
|
129
|
-
sig += ': ' + getNodeText(returnType, source).replace(/^:\s*/, '');
|
|
130
|
-
}
|
|
131
|
-
return sig;
|
|
132
|
-
},
|
|
133
|
-
getVisibility: (node) => {
|
|
134
|
-
for (let i = 0; i < node.childCount; i++) {
|
|
135
|
-
const child = node.child(i);
|
|
136
|
-
if (child?.type === 'accessibility_modifier') {
|
|
137
|
-
const text = child.text;
|
|
138
|
-
if (text === 'public')
|
|
139
|
-
return 'public';
|
|
140
|
-
if (text === 'private')
|
|
141
|
-
return 'private';
|
|
142
|
-
if (text === 'protected')
|
|
143
|
-
return 'protected';
|
|
144
|
-
}
|
|
145
|
-
}
|
|
146
|
-
return undefined;
|
|
147
|
-
},
|
|
148
|
-
isExported: (node, _source) => {
|
|
149
|
-
// Walk the parent chain to find an export_statement ancestor.
|
|
150
|
-
// This correctly handles deeply nested nodes like arrow functions
|
|
151
|
-
// inside variable declarations: `export const X = () => { ... }`
|
|
152
|
-
// where the arrow_function is 3 levels deep under export_statement.
|
|
153
|
-
let current = node.parent;
|
|
154
|
-
while (current) {
|
|
155
|
-
if (current.type === 'export_statement')
|
|
156
|
-
return true;
|
|
157
|
-
current = current.parent;
|
|
158
|
-
}
|
|
159
|
-
return false;
|
|
160
|
-
},
|
|
161
|
-
isAsync: (node) => {
|
|
162
|
-
for (let i = 0; i < node.childCount; i++) {
|
|
163
|
-
const child = node.child(i);
|
|
164
|
-
if (child?.type === 'async')
|
|
165
|
-
return true;
|
|
166
|
-
}
|
|
167
|
-
return false;
|
|
168
|
-
},
|
|
169
|
-
isStatic: (node) => {
|
|
170
|
-
for (let i = 0; i < node.childCount; i++) {
|
|
171
|
-
const child = node.child(i);
|
|
172
|
-
if (child?.type === 'static')
|
|
173
|
-
return true;
|
|
174
|
-
}
|
|
175
|
-
return false;
|
|
176
|
-
},
|
|
177
|
-
isConst: (node) => {
|
|
178
|
-
// For lexical_declaration, check if it's 'const' or 'let'
|
|
179
|
-
// For variable_declaration, it's always 'var'
|
|
180
|
-
if (node.type === 'lexical_declaration') {
|
|
181
|
-
for (let i = 0; i < node.childCount; i++) {
|
|
182
|
-
const child = node.child(i);
|
|
183
|
-
if (child?.type === 'const')
|
|
184
|
-
return true;
|
|
185
|
-
}
|
|
186
|
-
}
|
|
187
|
-
return false;
|
|
188
|
-
},
|
|
189
|
-
},
|
|
190
|
-
javascript: {
|
|
191
|
-
functionTypes: ['function_declaration', 'arrow_function', 'function_expression'],
|
|
192
|
-
classTypes: ['class_declaration'],
|
|
193
|
-
methodTypes: ['method_definition', 'field_definition'],
|
|
194
|
-
interfaceTypes: [],
|
|
195
|
-
structTypes: [],
|
|
196
|
-
enumTypes: [],
|
|
197
|
-
typeAliasTypes: [],
|
|
198
|
-
importTypes: ['import_statement'],
|
|
199
|
-
callTypes: ['call_expression'],
|
|
200
|
-
variableTypes: ['lexical_declaration', 'variable_declaration'],
|
|
201
|
-
nameField: 'name',
|
|
202
|
-
bodyField: 'body',
|
|
203
|
-
paramsField: 'parameters',
|
|
204
|
-
getSignature: (node, source) => {
|
|
205
|
-
const params = getChildByField(node, 'parameters');
|
|
206
|
-
return params ? getNodeText(params, source) : undefined;
|
|
207
|
-
},
|
|
208
|
-
isExported: (node, _source) => {
|
|
209
|
-
let current = node.parent;
|
|
210
|
-
while (current) {
|
|
211
|
-
if (current.type === 'export_statement')
|
|
212
|
-
return true;
|
|
213
|
-
current = current.parent;
|
|
214
|
-
}
|
|
215
|
-
return false;
|
|
216
|
-
},
|
|
217
|
-
isAsync: (node) => {
|
|
218
|
-
for (let i = 0; i < node.childCount; i++) {
|
|
219
|
-
const child = node.child(i);
|
|
220
|
-
if (child?.type === 'async')
|
|
221
|
-
return true;
|
|
222
|
-
}
|
|
223
|
-
return false;
|
|
224
|
-
},
|
|
225
|
-
isConst: (node) => {
|
|
226
|
-
if (node.type === 'lexical_declaration') {
|
|
227
|
-
for (let i = 0; i < node.childCount; i++) {
|
|
228
|
-
const child = node.child(i);
|
|
229
|
-
if (child?.type === 'const')
|
|
230
|
-
return true;
|
|
231
|
-
}
|
|
232
|
-
}
|
|
233
|
-
return false;
|
|
234
|
-
},
|
|
235
|
-
},
|
|
236
|
-
python: {
|
|
237
|
-
functionTypes: ['function_definition'],
|
|
238
|
-
classTypes: ['class_definition'],
|
|
239
|
-
methodTypes: ['function_definition'], // Methods are functions inside classes
|
|
240
|
-
interfaceTypes: [],
|
|
241
|
-
structTypes: [],
|
|
242
|
-
enumTypes: [],
|
|
243
|
-
typeAliasTypes: [],
|
|
244
|
-
importTypes: ['import_statement', 'import_from_statement'],
|
|
245
|
-
callTypes: ['call'],
|
|
246
|
-
variableTypes: ['assignment'], // Python uses assignment for variable declarations
|
|
247
|
-
nameField: 'name',
|
|
248
|
-
bodyField: 'body',
|
|
249
|
-
paramsField: 'parameters',
|
|
250
|
-
returnField: 'return_type',
|
|
251
|
-
getSignature: (node, source) => {
|
|
252
|
-
const params = getChildByField(node, 'parameters');
|
|
253
|
-
const returnType = getChildByField(node, 'return_type');
|
|
254
|
-
if (!params)
|
|
255
|
-
return undefined;
|
|
256
|
-
let sig = getNodeText(params, source);
|
|
257
|
-
if (returnType) {
|
|
258
|
-
sig += ' -> ' + getNodeText(returnType, source);
|
|
259
|
-
}
|
|
260
|
-
return sig;
|
|
261
|
-
},
|
|
262
|
-
isAsync: (node) => {
|
|
263
|
-
const prev = node.previousSibling;
|
|
264
|
-
return prev?.type === 'async';
|
|
265
|
-
},
|
|
266
|
-
isStatic: (node) => {
|
|
267
|
-
// Check for @staticmethod decorator
|
|
268
|
-
const prev = node.previousNamedSibling;
|
|
269
|
-
if (prev?.type === 'decorator') {
|
|
270
|
-
const text = prev.text;
|
|
271
|
-
return text.includes('staticmethod');
|
|
272
|
-
}
|
|
273
|
-
return false;
|
|
274
|
-
},
|
|
275
|
-
},
|
|
276
|
-
go: {
|
|
277
|
-
functionTypes: ['function_declaration'],
|
|
278
|
-
classTypes: [], // Go doesn't have classes
|
|
279
|
-
methodTypes: ['method_declaration'],
|
|
280
|
-
interfaceTypes: ['interface_type'],
|
|
281
|
-
structTypes: ['struct_type'],
|
|
282
|
-
enumTypes: [],
|
|
283
|
-
typeAliasTypes: ['type_spec'], // Go type declarations
|
|
284
|
-
importTypes: ['import_declaration'],
|
|
285
|
-
callTypes: ['call_expression'],
|
|
286
|
-
variableTypes: ['var_declaration', 'short_var_declaration', 'const_declaration'],
|
|
287
|
-
nameField: 'name',
|
|
288
|
-
bodyField: 'body',
|
|
289
|
-
paramsField: 'parameters',
|
|
290
|
-
returnField: 'result',
|
|
291
|
-
getSignature: (node, source) => {
|
|
292
|
-
const params = getChildByField(node, 'parameters');
|
|
293
|
-
const result = getChildByField(node, 'result');
|
|
294
|
-
if (!params)
|
|
295
|
-
return undefined;
|
|
296
|
-
let sig = getNodeText(params, source);
|
|
297
|
-
if (result) {
|
|
298
|
-
sig += ' ' + getNodeText(result, source);
|
|
299
|
-
}
|
|
300
|
-
return sig;
|
|
301
|
-
},
|
|
302
|
-
},
|
|
303
|
-
rust: {
|
|
304
|
-
functionTypes: ['function_item'],
|
|
305
|
-
classTypes: [], // Rust has impl blocks
|
|
306
|
-
methodTypes: ['function_item'], // Methods are functions in impl blocks
|
|
307
|
-
interfaceTypes: ['trait_item'],
|
|
308
|
-
structTypes: ['struct_item'],
|
|
309
|
-
enumTypes: ['enum_item'],
|
|
310
|
-
typeAliasTypes: ['type_item'], // Rust type aliases
|
|
311
|
-
importTypes: ['use_declaration'],
|
|
312
|
-
callTypes: ['call_expression'],
|
|
313
|
-
variableTypes: ['let_declaration', 'const_item', 'static_item'],
|
|
314
|
-
nameField: 'name',
|
|
315
|
-
bodyField: 'body',
|
|
316
|
-
paramsField: 'parameters',
|
|
317
|
-
returnField: 'return_type',
|
|
318
|
-
getSignature: (node, source) => {
|
|
319
|
-
const params = getChildByField(node, 'parameters');
|
|
320
|
-
const returnType = getChildByField(node, 'return_type');
|
|
321
|
-
if (!params)
|
|
322
|
-
return undefined;
|
|
323
|
-
let sig = getNodeText(params, source);
|
|
324
|
-
if (returnType) {
|
|
325
|
-
sig += ' -> ' + getNodeText(returnType, source);
|
|
326
|
-
}
|
|
327
|
-
return sig;
|
|
328
|
-
},
|
|
329
|
-
isAsync: (node) => {
|
|
330
|
-
for (let i = 0; i < node.childCount; i++) {
|
|
331
|
-
const child = node.child(i);
|
|
332
|
-
if (child?.type === 'async')
|
|
333
|
-
return true;
|
|
334
|
-
}
|
|
335
|
-
return false;
|
|
336
|
-
},
|
|
337
|
-
getVisibility: (node) => {
|
|
338
|
-
for (let i = 0; i < node.childCount; i++) {
|
|
339
|
-
const child = node.child(i);
|
|
340
|
-
if (child?.type === 'visibility_modifier') {
|
|
341
|
-
return child.text.includes('pub') ? 'public' : 'private';
|
|
342
|
-
}
|
|
343
|
-
}
|
|
344
|
-
return 'private'; // Rust defaults to private
|
|
345
|
-
},
|
|
346
|
-
},
|
|
347
|
-
java: {
|
|
348
|
-
functionTypes: [],
|
|
349
|
-
classTypes: ['class_declaration'],
|
|
350
|
-
methodTypes: ['method_declaration', 'constructor_declaration'],
|
|
351
|
-
interfaceTypes: ['interface_declaration'],
|
|
352
|
-
structTypes: [],
|
|
353
|
-
enumTypes: ['enum_declaration'],
|
|
354
|
-
typeAliasTypes: [],
|
|
355
|
-
importTypes: ['import_declaration'],
|
|
356
|
-
callTypes: ['method_invocation'],
|
|
357
|
-
variableTypes: ['local_variable_declaration', 'field_declaration'],
|
|
358
|
-
nameField: 'name',
|
|
359
|
-
bodyField: 'body',
|
|
360
|
-
paramsField: 'parameters',
|
|
361
|
-
returnField: 'type',
|
|
362
|
-
getSignature: (node, source) => {
|
|
363
|
-
const params = getChildByField(node, 'parameters');
|
|
364
|
-
const returnType = getChildByField(node, 'type');
|
|
365
|
-
if (!params)
|
|
366
|
-
return undefined;
|
|
367
|
-
const paramsText = getNodeText(params, source);
|
|
368
|
-
return returnType ? getNodeText(returnType, source) + ' ' + paramsText : paramsText;
|
|
369
|
-
},
|
|
370
|
-
getVisibility: (node) => {
|
|
371
|
-
for (let i = 0; i < node.childCount; i++) {
|
|
372
|
-
const child = node.child(i);
|
|
373
|
-
if (child?.type === 'modifiers') {
|
|
374
|
-
const text = child.text;
|
|
375
|
-
if (text.includes('public'))
|
|
376
|
-
return 'public';
|
|
377
|
-
if (text.includes('private'))
|
|
378
|
-
return 'private';
|
|
379
|
-
if (text.includes('protected'))
|
|
380
|
-
return 'protected';
|
|
381
|
-
}
|
|
382
|
-
}
|
|
383
|
-
return undefined;
|
|
384
|
-
},
|
|
385
|
-
isStatic: (node) => {
|
|
386
|
-
for (let i = 0; i < node.childCount; i++) {
|
|
387
|
-
const child = node.child(i);
|
|
388
|
-
if (child?.type === 'modifiers' && child.text.includes('static')) {
|
|
389
|
-
return true;
|
|
390
|
-
}
|
|
391
|
-
}
|
|
392
|
-
return false;
|
|
393
|
-
},
|
|
394
|
-
},
|
|
395
|
-
c: {
|
|
396
|
-
functionTypes: ['function_definition'],
|
|
397
|
-
classTypes: [],
|
|
398
|
-
methodTypes: [],
|
|
399
|
-
interfaceTypes: [],
|
|
400
|
-
structTypes: ['struct_specifier'],
|
|
401
|
-
enumTypes: ['enum_specifier'],
|
|
402
|
-
typeAliasTypes: ['type_definition'], // typedef
|
|
403
|
-
importTypes: ['preproc_include'],
|
|
404
|
-
callTypes: ['call_expression'],
|
|
405
|
-
variableTypes: ['declaration'],
|
|
406
|
-
nameField: 'declarator',
|
|
407
|
-
bodyField: 'body',
|
|
408
|
-
paramsField: 'parameters',
|
|
409
|
-
},
|
|
410
|
-
cpp: {
|
|
411
|
-
functionTypes: ['function_definition'],
|
|
412
|
-
classTypes: ['class_specifier'],
|
|
413
|
-
methodTypes: ['function_definition'],
|
|
414
|
-
interfaceTypes: [],
|
|
415
|
-
structTypes: ['struct_specifier'],
|
|
416
|
-
enumTypes: ['enum_specifier'],
|
|
417
|
-
typeAliasTypes: ['type_definition', 'alias_declaration'], // typedef and using
|
|
418
|
-
importTypes: ['preproc_include'],
|
|
419
|
-
callTypes: ['call_expression'],
|
|
420
|
-
variableTypes: ['declaration'],
|
|
421
|
-
nameField: 'declarator',
|
|
422
|
-
bodyField: 'body',
|
|
423
|
-
paramsField: 'parameters',
|
|
424
|
-
getVisibility: (node) => {
|
|
425
|
-
// Check for access specifier in parent
|
|
426
|
-
const parent = node.parent;
|
|
427
|
-
if (parent) {
|
|
428
|
-
for (let i = 0; i < parent.childCount; i++) {
|
|
429
|
-
const child = parent.child(i);
|
|
430
|
-
if (child?.type === 'access_specifier') {
|
|
431
|
-
const text = child.text;
|
|
432
|
-
if (text.includes('public'))
|
|
433
|
-
return 'public';
|
|
434
|
-
if (text.includes('private'))
|
|
435
|
-
return 'private';
|
|
436
|
-
if (text.includes('protected'))
|
|
437
|
-
return 'protected';
|
|
438
|
-
}
|
|
439
|
-
}
|
|
440
|
-
}
|
|
441
|
-
return undefined;
|
|
442
|
-
},
|
|
443
|
-
},
|
|
444
|
-
csharp: {
|
|
445
|
-
functionTypes: [],
|
|
446
|
-
classTypes: ['class_declaration'],
|
|
447
|
-
methodTypes: ['method_declaration', 'constructor_declaration'],
|
|
448
|
-
interfaceTypes: ['interface_declaration'],
|
|
449
|
-
structTypes: ['struct_declaration'],
|
|
450
|
-
enumTypes: ['enum_declaration'],
|
|
451
|
-
typeAliasTypes: [],
|
|
452
|
-
importTypes: ['using_directive'],
|
|
453
|
-
callTypes: ['invocation_expression'],
|
|
454
|
-
variableTypes: ['local_declaration_statement', 'field_declaration'],
|
|
455
|
-
nameField: 'name',
|
|
456
|
-
bodyField: 'body',
|
|
457
|
-
paramsField: 'parameter_list',
|
|
458
|
-
getVisibility: (node) => {
|
|
459
|
-
for (let i = 0; i < node.childCount; i++) {
|
|
460
|
-
const child = node.child(i);
|
|
461
|
-
if (child?.type === 'modifier') {
|
|
462
|
-
const text = child.text;
|
|
463
|
-
if (text === 'public')
|
|
464
|
-
return 'public';
|
|
465
|
-
if (text === 'private')
|
|
466
|
-
return 'private';
|
|
467
|
-
if (text === 'protected')
|
|
468
|
-
return 'protected';
|
|
469
|
-
if (text === 'internal')
|
|
470
|
-
return 'internal';
|
|
471
|
-
}
|
|
472
|
-
}
|
|
473
|
-
return 'private'; // C# defaults to private
|
|
474
|
-
},
|
|
475
|
-
isStatic: (node) => {
|
|
476
|
-
for (let i = 0; i < node.childCount; i++) {
|
|
477
|
-
const child = node.child(i);
|
|
478
|
-
if (child?.type === 'modifier' && child.text === 'static') {
|
|
479
|
-
return true;
|
|
480
|
-
}
|
|
481
|
-
}
|
|
482
|
-
return false;
|
|
483
|
-
},
|
|
484
|
-
isAsync: (node) => {
|
|
485
|
-
for (let i = 0; i < node.childCount; i++) {
|
|
486
|
-
const child = node.child(i);
|
|
487
|
-
if (child?.type === 'modifier' && child.text === 'async') {
|
|
488
|
-
return true;
|
|
489
|
-
}
|
|
490
|
-
}
|
|
491
|
-
return false;
|
|
492
|
-
},
|
|
493
|
-
},
|
|
494
|
-
php: {
|
|
495
|
-
functionTypes: ['function_definition'],
|
|
496
|
-
classTypes: ['class_declaration'],
|
|
497
|
-
methodTypes: ['method_declaration'],
|
|
498
|
-
interfaceTypes: ['interface_declaration'],
|
|
499
|
-
structTypes: [],
|
|
500
|
-
enumTypes: ['enum_declaration'],
|
|
501
|
-
typeAliasTypes: [],
|
|
502
|
-
importTypes: ['namespace_use_declaration'],
|
|
503
|
-
callTypes: ['function_call_expression', 'member_call_expression', 'scoped_call_expression'],
|
|
504
|
-
variableTypes: ['property_declaration', 'const_declaration'],
|
|
505
|
-
nameField: 'name',
|
|
506
|
-
bodyField: 'body',
|
|
507
|
-
paramsField: 'parameters',
|
|
508
|
-
returnField: 'return_type',
|
|
509
|
-
getVisibility: (node) => {
|
|
510
|
-
for (let i = 0; i < node.childCount; i++) {
|
|
511
|
-
const child = node.child(i);
|
|
512
|
-
if (child?.type === 'visibility_modifier') {
|
|
513
|
-
const text = child.text;
|
|
514
|
-
if (text === 'public')
|
|
515
|
-
return 'public';
|
|
516
|
-
if (text === 'private')
|
|
517
|
-
return 'private';
|
|
518
|
-
if (text === 'protected')
|
|
519
|
-
return 'protected';
|
|
520
|
-
}
|
|
521
|
-
}
|
|
522
|
-
return 'public'; // PHP defaults to public
|
|
523
|
-
},
|
|
524
|
-
isStatic: (node) => {
|
|
525
|
-
for (let i = 0; i < node.childCount; i++) {
|
|
526
|
-
const child = node.child(i);
|
|
527
|
-
if (child?.type === 'static_modifier')
|
|
528
|
-
return true;
|
|
529
|
-
}
|
|
530
|
-
return false;
|
|
531
|
-
},
|
|
532
|
-
},
|
|
533
|
-
ruby: {
|
|
534
|
-
functionTypes: ['method'],
|
|
535
|
-
classTypes: ['class'],
|
|
536
|
-
methodTypes: ['method', 'singleton_method'],
|
|
537
|
-
interfaceTypes: [], // Ruby uses modules
|
|
538
|
-
structTypes: [],
|
|
539
|
-
enumTypes: [],
|
|
540
|
-
typeAliasTypes: [],
|
|
541
|
-
importTypes: ['call'], // require/require_relative
|
|
542
|
-
callTypes: ['call', 'method_call'],
|
|
543
|
-
variableTypes: ['assignment'], // Ruby uses assignment like Python
|
|
544
|
-
nameField: 'name',
|
|
545
|
-
bodyField: 'body',
|
|
546
|
-
paramsField: 'parameters',
|
|
547
|
-
getVisibility: (node) => {
|
|
548
|
-
// Ruby visibility is based on preceding visibility modifiers
|
|
549
|
-
let sibling = node.previousNamedSibling;
|
|
550
|
-
while (sibling) {
|
|
551
|
-
if (sibling.type === 'call') {
|
|
552
|
-
const methodName = getChildByField(sibling, 'method');
|
|
553
|
-
if (methodName) {
|
|
554
|
-
const text = methodName.text;
|
|
555
|
-
if (text === 'private')
|
|
556
|
-
return 'private';
|
|
557
|
-
if (text === 'protected')
|
|
558
|
-
return 'protected';
|
|
559
|
-
if (text === 'public')
|
|
560
|
-
return 'public';
|
|
561
|
-
}
|
|
562
|
-
}
|
|
563
|
-
sibling = sibling.previousNamedSibling;
|
|
564
|
-
}
|
|
565
|
-
return 'public';
|
|
566
|
-
},
|
|
567
|
-
},
|
|
568
|
-
swift: {
|
|
569
|
-
functionTypes: ['function_declaration'],
|
|
570
|
-
classTypes: ['class_declaration'],
|
|
571
|
-
methodTypes: ['function_declaration'], // Methods are functions inside classes
|
|
572
|
-
interfaceTypes: ['protocol_declaration'],
|
|
573
|
-
structTypes: ['struct_declaration'],
|
|
574
|
-
enumTypes: ['enum_declaration'],
|
|
575
|
-
typeAliasTypes: ['typealias_declaration'],
|
|
576
|
-
importTypes: ['import_declaration'],
|
|
577
|
-
callTypes: ['call_expression'],
|
|
578
|
-
variableTypes: ['property_declaration', 'constant_declaration'],
|
|
579
|
-
nameField: 'name',
|
|
580
|
-
bodyField: 'body',
|
|
581
|
-
paramsField: 'parameter',
|
|
582
|
-
returnField: 'return_type',
|
|
583
|
-
getSignature: (node, source) => {
|
|
584
|
-
// Swift function signature: func name(params) -> ReturnType
|
|
585
|
-
const params = getChildByField(node, 'parameter');
|
|
586
|
-
const returnType = getChildByField(node, 'return_type');
|
|
587
|
-
if (!params)
|
|
588
|
-
return undefined;
|
|
589
|
-
let sig = getNodeText(params, source);
|
|
590
|
-
if (returnType) {
|
|
591
|
-
sig += ' -> ' + getNodeText(returnType, source);
|
|
592
|
-
}
|
|
593
|
-
return sig;
|
|
594
|
-
},
|
|
595
|
-
getVisibility: (node) => {
|
|
596
|
-
// Check for visibility modifiers in Swift
|
|
597
|
-
for (let i = 0; i < node.childCount; i++) {
|
|
598
|
-
const child = node.child(i);
|
|
599
|
-
if (child?.type === 'modifiers') {
|
|
600
|
-
const text = child.text;
|
|
601
|
-
if (text.includes('public'))
|
|
602
|
-
return 'public';
|
|
603
|
-
if (text.includes('private'))
|
|
604
|
-
return 'private';
|
|
605
|
-
if (text.includes('internal'))
|
|
606
|
-
return 'internal';
|
|
607
|
-
if (text.includes('fileprivate'))
|
|
608
|
-
return 'private';
|
|
609
|
-
}
|
|
610
|
-
}
|
|
611
|
-
return 'internal'; // Swift defaults to internal
|
|
612
|
-
},
|
|
613
|
-
isStatic: (node) => {
|
|
614
|
-
for (let i = 0; i < node.childCount; i++) {
|
|
615
|
-
const child = node.child(i);
|
|
616
|
-
if (child?.type === 'modifiers') {
|
|
617
|
-
if (child.text.includes('static') || child.text.includes('class')) {
|
|
618
|
-
return true;
|
|
619
|
-
}
|
|
620
|
-
}
|
|
621
|
-
}
|
|
622
|
-
return false;
|
|
623
|
-
},
|
|
624
|
-
isAsync: (node) => {
|
|
625
|
-
for (let i = 0; i < node.childCount; i++) {
|
|
626
|
-
const child = node.child(i);
|
|
627
|
-
if (child?.type === 'modifiers' && child.text.includes('async')) {
|
|
628
|
-
return true;
|
|
629
|
-
}
|
|
630
|
-
}
|
|
631
|
-
return false;
|
|
632
|
-
},
|
|
633
|
-
},
|
|
634
|
-
kotlin: {
|
|
635
|
-
functionTypes: ['function_declaration'],
|
|
636
|
-
classTypes: ['class_declaration'],
|
|
637
|
-
methodTypes: ['function_declaration'], // Methods are functions inside classes
|
|
638
|
-
interfaceTypes: ['class_declaration'], // Interfaces use class_declaration with 'interface' modifier
|
|
639
|
-
structTypes: [], // Kotlin uses data classes
|
|
640
|
-
enumTypes: ['class_declaration'], // Enums use class_declaration with 'enum' modifier
|
|
641
|
-
typeAliasTypes: ['type_alias'],
|
|
642
|
-
importTypes: ['import_header'],
|
|
643
|
-
callTypes: ['call_expression'],
|
|
644
|
-
variableTypes: ['property_declaration'],
|
|
645
|
-
nameField: 'simple_identifier',
|
|
646
|
-
bodyField: 'function_body',
|
|
647
|
-
paramsField: 'function_value_parameters',
|
|
648
|
-
returnField: 'type',
|
|
649
|
-
getSignature: (node, source) => {
|
|
650
|
-
// Kotlin function signature: fun name(params): ReturnType
|
|
651
|
-
const params = getChildByField(node, 'function_value_parameters');
|
|
652
|
-
const returnType = getChildByField(node, 'type');
|
|
653
|
-
if (!params)
|
|
654
|
-
return undefined;
|
|
655
|
-
let sig = getNodeText(params, source);
|
|
656
|
-
if (returnType) {
|
|
657
|
-
sig += ': ' + getNodeText(returnType, source);
|
|
658
|
-
}
|
|
659
|
-
return sig;
|
|
660
|
-
},
|
|
661
|
-
getVisibility: (node) => {
|
|
662
|
-
// Check for visibility modifiers in Kotlin
|
|
663
|
-
for (let i = 0; i < node.childCount; i++) {
|
|
664
|
-
const child = node.child(i);
|
|
665
|
-
if (child?.type === 'modifiers') {
|
|
666
|
-
const text = child.text;
|
|
667
|
-
if (text.includes('public'))
|
|
668
|
-
return 'public';
|
|
669
|
-
if (text.includes('private'))
|
|
670
|
-
return 'private';
|
|
671
|
-
if (text.includes('protected'))
|
|
672
|
-
return 'protected';
|
|
673
|
-
if (text.includes('internal'))
|
|
674
|
-
return 'internal';
|
|
675
|
-
}
|
|
676
|
-
}
|
|
677
|
-
return 'public'; // Kotlin defaults to public
|
|
678
|
-
},
|
|
679
|
-
isStatic: (_node) => {
|
|
680
|
-
// Kotlin doesn't have static, uses companion objects
|
|
681
|
-
// Check if inside companion object would require more context
|
|
682
|
-
return false;
|
|
683
|
-
},
|
|
684
|
-
isAsync: (node) => {
|
|
685
|
-
// Kotlin uses suspend keyword for coroutines
|
|
686
|
-
for (let i = 0; i < node.childCount; i++) {
|
|
687
|
-
const child = node.child(i);
|
|
688
|
-
if (child?.type === 'modifiers' && child.text.includes('suspend')) {
|
|
689
|
-
return true;
|
|
690
|
-
}
|
|
691
|
-
}
|
|
692
|
-
return false;
|
|
693
|
-
},
|
|
694
|
-
},
|
|
695
|
-
dart: {
|
|
696
|
-
functionTypes: ['function_signature'],
|
|
697
|
-
classTypes: ['class_definition'],
|
|
698
|
-
methodTypes: ['method_signature'],
|
|
699
|
-
interfaceTypes: [],
|
|
700
|
-
structTypes: [],
|
|
701
|
-
enumTypes: ['enum_declaration'],
|
|
702
|
-
typeAliasTypes: ['type_alias'],
|
|
703
|
-
importTypes: ['import_or_export'],
|
|
704
|
-
callTypes: [], // Dart calls use identifier+selector, handled via function body traversal
|
|
705
|
-
variableTypes: [],
|
|
706
|
-
nameField: 'name',
|
|
707
|
-
bodyField: 'body', // class_definition uses 'body' field
|
|
708
|
-
paramsField: 'formal_parameter_list',
|
|
709
|
-
returnField: 'type',
|
|
710
|
-
getSignature: (node, source) => {
|
|
711
|
-
// For function_signature: extract params + return type
|
|
712
|
-
// For method_signature: delegate to inner function_signature
|
|
713
|
-
let sig = node;
|
|
714
|
-
if (node.type === 'method_signature') {
|
|
715
|
-
const inner = node.namedChildren.find((c) => c.type === 'function_signature' || c.type === 'getter_signature' || c.type === 'setter_signature');
|
|
716
|
-
if (inner)
|
|
717
|
-
sig = inner;
|
|
718
|
-
}
|
|
719
|
-
const params = sig.namedChildren.find((c) => c.type === 'formal_parameter_list');
|
|
720
|
-
const retType = sig.namedChildren.find((c) => c.type === 'type_identifier' || c.type === 'void_type');
|
|
721
|
-
if (!params && !retType)
|
|
722
|
-
return undefined;
|
|
723
|
-
let result = '';
|
|
724
|
-
if (retType)
|
|
725
|
-
result += getNodeText(retType, source) + ' ';
|
|
726
|
-
if (params)
|
|
727
|
-
result += getNodeText(params, source);
|
|
728
|
-
return result.trim() || undefined;
|
|
729
|
-
},
|
|
730
|
-
getVisibility: (node) => {
|
|
731
|
-
// Dart convention: _ prefix means private, otherwise public
|
|
732
|
-
let nameNode = null;
|
|
733
|
-
if (node.type === 'method_signature') {
|
|
734
|
-
const inner = node.namedChildren.find((c) => c.type === 'function_signature' || c.type === 'getter_signature' || c.type === 'setter_signature');
|
|
735
|
-
if (inner)
|
|
736
|
-
nameNode = inner.namedChildren.find((c) => c.type === 'identifier') || null;
|
|
737
|
-
}
|
|
738
|
-
else {
|
|
739
|
-
nameNode = node.childForFieldName('name');
|
|
740
|
-
}
|
|
741
|
-
if (nameNode && nameNode.text.startsWith('_'))
|
|
742
|
-
return 'private';
|
|
743
|
-
return 'public';
|
|
744
|
-
},
|
|
745
|
-
isAsync: (node) => {
|
|
746
|
-
// In Dart, 'async' is on the function_body (next sibling), not the signature
|
|
747
|
-
const nextSibling = node.nextNamedSibling;
|
|
748
|
-
if (nextSibling?.type === 'function_body') {
|
|
749
|
-
for (let i = 0; i < nextSibling.childCount; i++) {
|
|
750
|
-
const child = nextSibling.child(i);
|
|
751
|
-
if (child?.type === 'async')
|
|
752
|
-
return true;
|
|
753
|
-
}
|
|
754
|
-
}
|
|
755
|
-
return false;
|
|
756
|
-
},
|
|
757
|
-
isStatic: (node) => {
|
|
758
|
-
// For method_signature, check for 'static' child
|
|
759
|
-
if (node.type === 'method_signature') {
|
|
760
|
-
for (let i = 0; i < node.childCount; i++) {
|
|
761
|
-
const child = node.child(i);
|
|
762
|
-
if (child?.type === 'static')
|
|
763
|
-
return true;
|
|
764
|
-
}
|
|
765
|
-
}
|
|
766
|
-
return false;
|
|
767
|
-
},
|
|
768
|
-
},
|
|
769
|
-
pascal: {
|
|
770
|
-
functionTypes: ['declProc'],
|
|
771
|
-
classTypes: ['declClass'],
|
|
772
|
-
methodTypes: ['declProc'],
|
|
773
|
-
interfaceTypes: ['declIntf'],
|
|
774
|
-
structTypes: [],
|
|
775
|
-
enumTypes: ['declEnum'],
|
|
776
|
-
typeAliasTypes: ['declType'],
|
|
777
|
-
importTypes: ['declUses'],
|
|
778
|
-
callTypes: ['exprCall'],
|
|
779
|
-
variableTypes: ['declField', 'declConst'],
|
|
780
|
-
nameField: 'name',
|
|
781
|
-
bodyField: 'body',
|
|
782
|
-
paramsField: 'args',
|
|
783
|
-
returnField: 'type',
|
|
784
|
-
getSignature: (node, source) => {
|
|
785
|
-
const args = getChildByField(node, 'args');
|
|
786
|
-
const returnType = node.namedChildren.find((c) => c.type === 'typeref');
|
|
787
|
-
if (!args && !returnType)
|
|
788
|
-
return undefined;
|
|
789
|
-
let sig = '';
|
|
790
|
-
if (args)
|
|
791
|
-
sig = getNodeText(args, source);
|
|
792
|
-
if (returnType) {
|
|
793
|
-
sig += ': ' + getNodeText(returnType, source);
|
|
794
|
-
}
|
|
795
|
-
return sig || undefined;
|
|
796
|
-
},
|
|
797
|
-
getVisibility: (node) => {
|
|
798
|
-
let current = node.parent;
|
|
799
|
-
while (current) {
|
|
800
|
-
if (current.type === 'declSection') {
|
|
801
|
-
for (let i = 0; i < current.childCount; i++) {
|
|
802
|
-
const child = current.child(i);
|
|
803
|
-
if (child?.type === 'kPublic' || child?.type === 'kPublished')
|
|
804
|
-
return 'public';
|
|
805
|
-
if (child?.type === 'kPrivate')
|
|
806
|
-
return 'private';
|
|
807
|
-
if (child?.type === 'kProtected')
|
|
808
|
-
return 'protected';
|
|
809
|
-
}
|
|
810
|
-
}
|
|
811
|
-
current = current.parent;
|
|
812
|
-
}
|
|
813
|
-
return undefined;
|
|
814
|
-
},
|
|
815
|
-
isExported: (_node, _source) => {
|
|
816
|
-
// In Pascal, symbols declared in the interface section are exported
|
|
817
|
-
return false;
|
|
818
|
-
},
|
|
819
|
-
isStatic: (node) => {
|
|
820
|
-
for (let i = 0; i < node.childCount; i++) {
|
|
821
|
-
if (node.child(i)?.type === 'kClass')
|
|
822
|
-
return true;
|
|
823
|
-
}
|
|
824
|
-
return false;
|
|
825
|
-
},
|
|
826
|
-
isConst: (node) => {
|
|
827
|
-
return node.type === 'declConst';
|
|
828
|
-
},
|
|
829
|
-
},
|
|
830
|
-
};
|
|
831
|
-
// TSX and JSX use the same extractors as their base languages
|
|
832
|
-
EXTRACTORS.tsx = EXTRACTORS.typescript;
|
|
833
|
-
EXTRACTORS.jsx = EXTRACTORS.javascript;
|
|
45
|
+
const tree_sitter_helpers_1 = require("./tree-sitter-helpers");
|
|
46
|
+
const languages_1 = require("./languages");
|
|
47
|
+
const liquid_extractor_1 = require("./liquid-extractor");
|
|
48
|
+
const svelte_extractor_1 = require("./svelte-extractor");
|
|
49
|
+
const dfm_extractor_1 = require("./dfm-extractor");
|
|
50
|
+
// Re-export for backward compatibility
|
|
51
|
+
var tree_sitter_helpers_2 = require("./tree-sitter-helpers");
|
|
52
|
+
Object.defineProperty(exports, "generateNodeId", { enumerable: true, get: function () { return tree_sitter_helpers_2.generateNodeId; } });
|
|
834
53
|
/**
|
|
835
54
|
* Extract the name from a node based on language
|
|
836
55
|
*/
|
|
837
56
|
function extractName(node, source, extractor) {
|
|
838
57
|
// Try field name first
|
|
839
|
-
const nameNode = getChildByField(node, extractor.nameField);
|
|
58
|
+
const nameNode = (0, tree_sitter_helpers_1.getChildByField)(node, extractor.nameField);
|
|
840
59
|
if (nameNode) {
|
|
60
|
+
// Unwrap pointer_declarator(s) for C/C++ pointer return types
|
|
61
|
+
let resolved = nameNode;
|
|
62
|
+
while (resolved.type === 'pointer_declarator') {
|
|
63
|
+
const inner = (0, tree_sitter_helpers_1.getChildByField)(resolved, 'declarator') || resolved.namedChild(0);
|
|
64
|
+
if (!inner)
|
|
65
|
+
break;
|
|
66
|
+
resolved = inner;
|
|
67
|
+
}
|
|
841
68
|
// Handle complex declarators (C/C++)
|
|
842
|
-
if (
|
|
843
|
-
const innerName = getChildByField(
|
|
844
|
-
return innerName ? getNodeText(innerName, source) : getNodeText(
|
|
69
|
+
if (resolved.type === 'function_declarator' || resolved.type === 'declarator') {
|
|
70
|
+
const innerName = (0, tree_sitter_helpers_1.getChildByField)(resolved, 'declarator') || resolved.namedChild(0);
|
|
71
|
+
return innerName ? (0, tree_sitter_helpers_1.getNodeText)(innerName, source) : (0, tree_sitter_helpers_1.getNodeText)(resolved, source);
|
|
845
72
|
}
|
|
846
|
-
return getNodeText(
|
|
73
|
+
return (0, tree_sitter_helpers_1.getNodeText)(resolved, source);
|
|
847
74
|
}
|
|
848
75
|
// For Dart method_signature, look inside inner signature types
|
|
849
76
|
if (node.type === 'method_signature') {
|
|
@@ -858,12 +85,19 @@ function extractName(node, source, extractor) {
|
|
|
858
85
|
for (let j = 0; j < child.namedChildCount; j++) {
|
|
859
86
|
const inner = child.namedChild(j);
|
|
860
87
|
if (inner?.type === 'identifier') {
|
|
861
|
-
return getNodeText(inner, source);
|
|
88
|
+
return (0, tree_sitter_helpers_1.getNodeText)(inner, source);
|
|
862
89
|
}
|
|
863
90
|
}
|
|
864
91
|
}
|
|
865
92
|
}
|
|
866
93
|
}
|
|
94
|
+
// Arrow/function expressions get their name from the parent variable_declarator,
|
|
95
|
+
// not from identifiers in their body. Without this, single-expression arrow
|
|
96
|
+
// functions like `const fn = () => someIdentifier` get named "someIdentifier"
|
|
97
|
+
// instead of "fn", because the fallback below finds the body identifier.
|
|
98
|
+
if (node.type === 'arrow_function' || node.type === 'function_expression') {
|
|
99
|
+
return '<anonymous>';
|
|
100
|
+
}
|
|
867
101
|
// Fall back to first identifier child
|
|
868
102
|
for (let i = 0; i < node.namedChildCount; i++) {
|
|
869
103
|
const child = node.namedChild(i);
|
|
@@ -872,7 +106,7 @@ function extractName(node, source, extractor) {
|
|
|
872
106
|
child.type === 'type_identifier' ||
|
|
873
107
|
child.type === 'simple_identifier' ||
|
|
874
108
|
child.type === 'constant')) {
|
|
875
|
-
return getNodeText(child, source);
|
|
109
|
+
return (0, tree_sitter_helpers_1.getNodeText)(child, source);
|
|
876
110
|
}
|
|
877
111
|
}
|
|
878
112
|
return '<anonymous>';
|
|
@@ -895,8 +129,8 @@ class TreeSitterExtractor {
|
|
|
895
129
|
constructor(filePath, source, language) {
|
|
896
130
|
this.filePath = filePath;
|
|
897
131
|
this.source = source;
|
|
898
|
-
this.language = language || (0, grammars_1.detectLanguage)(filePath);
|
|
899
|
-
this.extractor = EXTRACTORS[this.language] || null;
|
|
132
|
+
this.language = language || (0, grammars_1.detectLanguage)(filePath, source);
|
|
133
|
+
this.extractor = languages_1.EXTRACTORS[this.language] || null;
|
|
900
134
|
}
|
|
901
135
|
/**
|
|
902
136
|
* Parse and extract from the source code
|
|
@@ -911,7 +145,9 @@ class TreeSitterExtractor {
|
|
|
911
145
|
errors: [
|
|
912
146
|
{
|
|
913
147
|
message: `Unsupported language: ${this.language}`,
|
|
148
|
+
filePath: this.filePath,
|
|
914
149
|
severity: 'error',
|
|
150
|
+
code: 'unsupported_language',
|
|
915
151
|
},
|
|
916
152
|
],
|
|
917
153
|
durationMs: Date.now() - startTime,
|
|
@@ -926,7 +162,9 @@ class TreeSitterExtractor {
|
|
|
926
162
|
errors: [
|
|
927
163
|
{
|
|
928
164
|
message: `Failed to get parser for language: ${this.language}`,
|
|
165
|
+
filePath: this.filePath,
|
|
929
166
|
severity: 'error',
|
|
167
|
+
code: 'parser_error',
|
|
930
168
|
},
|
|
931
169
|
],
|
|
932
170
|
durationMs: Date.now() - startTime,
|
|
@@ -959,11 +197,30 @@ class TreeSitterExtractor {
|
|
|
959
197
|
this.nodeStack.pop();
|
|
960
198
|
}
|
|
961
199
|
catch (error) {
|
|
200
|
+
const msg = error instanceof Error ? error.message : String(error);
|
|
201
|
+
// WASM memory errors leave the module in a corrupted state — all subsequent
|
|
202
|
+
// parses would also fail. Re-throw so the worker can detect and crash,
|
|
203
|
+
// forcing a clean restart with a fresh heap.
|
|
204
|
+
if (msg.includes('memory access out of bounds') || msg.includes('out of memory')) {
|
|
205
|
+
throw error;
|
|
206
|
+
}
|
|
962
207
|
this.errors.push({
|
|
963
|
-
message: `Parse error: ${
|
|
208
|
+
message: `Parse error: ${msg}`,
|
|
209
|
+
filePath: this.filePath,
|
|
964
210
|
severity: 'error',
|
|
211
|
+
code: 'parse_error',
|
|
965
212
|
});
|
|
966
213
|
}
|
|
214
|
+
finally {
|
|
215
|
+
// Free tree-sitter WASM memory immediately — trees hold native heap memory
|
|
216
|
+
// invisible to V8's GC that accumulates across thousands of files.
|
|
217
|
+
if (this.tree) {
|
|
218
|
+
this.tree.delete();
|
|
219
|
+
this.tree = null;
|
|
220
|
+
}
|
|
221
|
+
// Release source string to reduce GC pressure
|
|
222
|
+
this.source = '';
|
|
223
|
+
}
|
|
967
224
|
return {
|
|
968
225
|
nodes: this.nodes,
|
|
969
226
|
edges: this.edges,
|
|
@@ -980,6 +237,13 @@ class TreeSitterExtractor {
|
|
|
980
237
|
return;
|
|
981
238
|
const nodeType = node.type;
|
|
982
239
|
let skipChildren = false;
|
|
240
|
+
// Language-specific custom visitor hook
|
|
241
|
+
if (this.extractor.visitNode) {
|
|
242
|
+
const ctx = this.makeExtractorContext();
|
|
243
|
+
const handled = this.extractor.visitNode(node, ctx);
|
|
244
|
+
if (handled)
|
|
245
|
+
return;
|
|
246
|
+
}
|
|
983
247
|
// Pascal-specific AST handling
|
|
984
248
|
if (this.language === 'pascal') {
|
|
985
249
|
skipChildren = this.visitPascalNode(node);
|
|
@@ -1001,21 +265,27 @@ class TreeSitterExtractor {
|
|
|
1001
265
|
}
|
|
1002
266
|
// Check for class declarations
|
|
1003
267
|
else if (this.extractor.classTypes.includes(nodeType)) {
|
|
1004
|
-
//
|
|
1005
|
-
|
|
1006
|
-
if (
|
|
268
|
+
// Some languages reuse class_declaration for structs/enums (e.g. Swift)
|
|
269
|
+
const classification = this.extractor.classifyClassNode?.(node) ?? 'class';
|
|
270
|
+
if (classification === 'struct') {
|
|
1007
271
|
this.extractStruct(node);
|
|
1008
272
|
}
|
|
1009
|
-
else if (
|
|
273
|
+
else if (classification === 'enum') {
|
|
1010
274
|
this.extractEnum(node);
|
|
1011
275
|
}
|
|
276
|
+
else if (classification === 'interface') {
|
|
277
|
+
this.extractInterface(node);
|
|
278
|
+
}
|
|
279
|
+
else if (classification === 'trait') {
|
|
280
|
+
this.extractClass(node, 'trait');
|
|
281
|
+
}
|
|
1012
282
|
else {
|
|
1013
283
|
this.extractClass(node);
|
|
1014
284
|
}
|
|
1015
285
|
skipChildren = true; // extractClass visits body children
|
|
1016
286
|
}
|
|
1017
|
-
//
|
|
1018
|
-
else if (this.
|
|
287
|
+
// Extra class node types (e.g. Dart mixin_declaration, extension_declaration)
|
|
288
|
+
else if (this.extractor.extraClassNodeTypes?.includes(nodeType)) {
|
|
1019
289
|
this.extractClass(node);
|
|
1020
290
|
skipChildren = true;
|
|
1021
291
|
}
|
|
@@ -1040,8 +310,20 @@ class TreeSitterExtractor {
|
|
|
1040
310
|
skipChildren = true; // extractEnum visits body children
|
|
1041
311
|
}
|
|
1042
312
|
// Check for type alias declarations (e.g. `type X = ...` in TypeScript)
|
|
313
|
+
// For Go, type_spec wraps struct/interface definitions — resolveTypeAliasKind
|
|
314
|
+
// detects these and extractTypeAlias creates the correct node kind.
|
|
1043
315
|
else if (this.extractor.typeAliasTypes.includes(nodeType)) {
|
|
1044
|
-
this.extractTypeAlias(node);
|
|
316
|
+
skipChildren = this.extractTypeAlias(node);
|
|
317
|
+
}
|
|
318
|
+
// Check for class properties (e.g. C# property_declaration)
|
|
319
|
+
else if (this.extractor.propertyTypes?.includes(nodeType) && this.isInsideClassLikeNode()) {
|
|
320
|
+
this.extractProperty(node);
|
|
321
|
+
skipChildren = true;
|
|
322
|
+
}
|
|
323
|
+
// Check for class fields (e.g. Java field_declaration, C# field_declaration)
|
|
324
|
+
else if (this.extractor.fieldTypes?.includes(nodeType) && this.isInsideClassLikeNode()) {
|
|
325
|
+
this.extractField(node);
|
|
326
|
+
skipChildren = true;
|
|
1045
327
|
}
|
|
1046
328
|
// Check for variable declarations (const, let, var, etc.)
|
|
1047
329
|
// Only extract top-level variables (not inside functions/methods)
|
|
@@ -1063,6 +345,10 @@ class TreeSitterExtractor {
|
|
|
1063
345
|
else if (this.extractor.callTypes.includes(nodeType)) {
|
|
1064
346
|
this.extractCall(node);
|
|
1065
347
|
}
|
|
348
|
+
// Rust: `impl Trait for Type { ... }` — creates implements edge from Type to Trait
|
|
349
|
+
else if (nodeType === 'impl_item') {
|
|
350
|
+
this.extractRustImplItem(node);
|
|
351
|
+
}
|
|
1066
352
|
// Visit children (unless the extract method already visited them)
|
|
1067
353
|
if (!skipChildren) {
|
|
1068
354
|
for (let i = 0; i < node.namedChildCount; i++) {
|
|
@@ -1082,7 +368,7 @@ class TreeSitterExtractor {
|
|
|
1082
368
|
if (!name) {
|
|
1083
369
|
return null;
|
|
1084
370
|
}
|
|
1085
|
-
const id = generateNodeId(this.filePath, kind, name, node.startPosition.row + 1);
|
|
371
|
+
const id = (0, tree_sitter_helpers_1.generateNodeId)(this.filePath, kind, name, node.startPosition.row + 1);
|
|
1086
372
|
const newNode = {
|
|
1087
373
|
id,
|
|
1088
374
|
kind,
|
|
@@ -1111,15 +397,28 @@ class TreeSitterExtractor {
|
|
|
1111
397
|
}
|
|
1112
398
|
return newNode;
|
|
1113
399
|
}
|
|
400
|
+
/**
|
|
401
|
+
* Find first named child whose type is in the given list.
|
|
402
|
+
* Used to locate inner type nodes (e.g. enum_specifier inside a typedef).
|
|
403
|
+
*/
|
|
404
|
+
findChildByTypes(node, types) {
|
|
405
|
+
for (let i = 0; i < node.namedChildCount; i++) {
|
|
406
|
+
const child = node.namedChild(i);
|
|
407
|
+
if (child && types.includes(child.type))
|
|
408
|
+
return child;
|
|
409
|
+
}
|
|
410
|
+
return null;
|
|
411
|
+
}
|
|
1114
412
|
/**
|
|
1115
413
|
* Build qualified name from node stack
|
|
1116
414
|
*/
|
|
1117
415
|
buildQualifiedName(name) {
|
|
1118
|
-
//
|
|
1119
|
-
|
|
416
|
+
// Build a qualified name from the semantic hierarchy only (no file path).
|
|
417
|
+
// The file path is stored separately in filePath and pollutes FTS if included here.
|
|
418
|
+
const parts = [];
|
|
1120
419
|
for (const nodeId of this.nodeStack) {
|
|
1121
420
|
const node = this.nodes.find((n) => n.id === nodeId);
|
|
1122
|
-
if (node) {
|
|
421
|
+
if (node && node.kind !== 'file') {
|
|
1123
422
|
parts.push(node.name);
|
|
1124
423
|
}
|
|
1125
424
|
}
|
|
@@ -1127,16 +426,23 @@ class TreeSitterExtractor {
|
|
|
1127
426
|
return parts.join('::');
|
|
1128
427
|
}
|
|
1129
428
|
/**
|
|
1130
|
-
*
|
|
429
|
+
* Build an ExtractorContext for passing to language-specific visitNode hooks.
|
|
1131
430
|
*/
|
|
1132
|
-
|
|
1133
|
-
|
|
1134
|
-
|
|
1135
|
-
|
|
1136
|
-
|
|
1137
|
-
|
|
1138
|
-
|
|
1139
|
-
|
|
431
|
+
makeExtractorContext() {
|
|
432
|
+
// eslint-disable-next-line @typescript-eslint/no-this-alias
|
|
433
|
+
const self = this;
|
|
434
|
+
return {
|
|
435
|
+
createNode: (kind, name, node, extra) => self.createNode(kind, name, node, extra),
|
|
436
|
+
visitNode: (node) => self.visitNode(node),
|
|
437
|
+
visitFunctionBody: (body, functionId) => self.visitFunctionBody(body, functionId),
|
|
438
|
+
addUnresolvedReference: (ref) => self.unresolvedReferences.push(ref),
|
|
439
|
+
pushScope: (nodeId) => self.nodeStack.push(nodeId),
|
|
440
|
+
popScope: () => self.nodeStack.pop(),
|
|
441
|
+
get filePath() { return self.filePath; },
|
|
442
|
+
get source() { return self.source; },
|
|
443
|
+
get nodeStack() { return self.nodeStack; },
|
|
444
|
+
get nodes() { return self.nodes; },
|
|
445
|
+
};
|
|
1140
446
|
}
|
|
1141
447
|
/**
|
|
1142
448
|
* Check if the current node stack indicates we are inside a class-like node
|
|
@@ -1155,7 +461,8 @@ class TreeSitterExtractor {
|
|
|
1155
461
|
parentNode.kind === 'struct' ||
|
|
1156
462
|
parentNode.kind === 'interface' ||
|
|
1157
463
|
parentNode.kind === 'trait' ||
|
|
1158
|
-
parentNode.kind === 'enum'
|
|
464
|
+
parentNode.kind === 'enum' ||
|
|
465
|
+
parentNode.kind === 'module');
|
|
1159
466
|
}
|
|
1160
467
|
/**
|
|
1161
468
|
* Extract a function
|
|
@@ -1163,6 +470,12 @@ class TreeSitterExtractor {
|
|
|
1163
470
|
extractFunction(node) {
|
|
1164
471
|
if (!this.extractor)
|
|
1165
472
|
return;
|
|
473
|
+
// If the language provides getReceiverType and this function has a receiver
|
|
474
|
+
// (e.g., Rust function_item inside an impl block), extract as method instead
|
|
475
|
+
if (this.extractor.getReceiverType?.(node, this.source)) {
|
|
476
|
+
this.extractMethod(node);
|
|
477
|
+
return;
|
|
478
|
+
}
|
|
1166
479
|
let name = extractName(node, this.source, this.extractor);
|
|
1167
480
|
// For arrow functions and function expressions assigned to variables,
|
|
1168
481
|
// resolve the name from the parent variable_declarator.
|
|
@@ -1172,15 +485,25 @@ class TreeSitterExtractor {
|
|
|
1172
485
|
(node.type === 'arrow_function' || node.type === 'function_expression')) {
|
|
1173
486
|
const parent = node.parent;
|
|
1174
487
|
if (parent?.type === 'variable_declarator') {
|
|
1175
|
-
const varName = getChildByField(parent, 'name');
|
|
488
|
+
const varName = (0, tree_sitter_helpers_1.getChildByField)(parent, 'name');
|
|
1176
489
|
if (varName) {
|
|
1177
|
-
name = getNodeText(varName, this.source);
|
|
490
|
+
name = (0, tree_sitter_helpers_1.getNodeText)(varName, this.source);
|
|
1178
491
|
}
|
|
1179
492
|
}
|
|
1180
493
|
}
|
|
1181
494
|
if (name === '<anonymous>')
|
|
1182
495
|
return; // Skip anonymous functions
|
|
1183
|
-
|
|
496
|
+
// Check for misparse artifacts (e.g. C++ macros causing "namespace detail" functions)
|
|
497
|
+
// Skip the node but still visit the body for calls and structural nodes
|
|
498
|
+
if (this.extractor.isMisparsedFunction?.(name, node)) {
|
|
499
|
+
const body = this.extractor.resolveBody?.(node, this.extractor.bodyField)
|
|
500
|
+
?? (0, tree_sitter_helpers_1.getChildByField)(node, this.extractor.bodyField);
|
|
501
|
+
if (body) {
|
|
502
|
+
this.visitFunctionBody(body, '');
|
|
503
|
+
}
|
|
504
|
+
return;
|
|
505
|
+
}
|
|
506
|
+
const docstring = (0, tree_sitter_helpers_1.getPrecedingDocstring)(node, this.source);
|
|
1184
507
|
const signature = this.extractor.getSignature?.(node, this.source);
|
|
1185
508
|
const visibility = this.extractor.getVisibility?.(node);
|
|
1186
509
|
const isExported = this.extractor.isExported?.(node, this.source);
|
|
@@ -1196,12 +519,12 @@ class TreeSitterExtractor {
|
|
|
1196
519
|
});
|
|
1197
520
|
if (!funcNode)
|
|
1198
521
|
return;
|
|
522
|
+
// Extract type annotations (parameter types and return type)
|
|
523
|
+
this.extractTypeAnnotations(node, funcNode.id);
|
|
1199
524
|
// Push to stack and visit body
|
|
1200
525
|
this.nodeStack.push(funcNode.id);
|
|
1201
|
-
|
|
1202
|
-
|
|
1203
|
-
? node.nextNamedSibling?.type === 'function_body' ? node.nextNamedSibling : null
|
|
1204
|
-
: getChildByField(node, this.extractor.bodyField);
|
|
526
|
+
const body = this.extractor.resolveBody?.(node, this.extractor.bodyField)
|
|
527
|
+
?? (0, tree_sitter_helpers_1.getChildByField)(node, this.extractor.bodyField);
|
|
1205
528
|
if (body) {
|
|
1206
529
|
this.visitFunctionBody(body, funcNode.id);
|
|
1207
530
|
}
|
|
@@ -1210,14 +533,14 @@ class TreeSitterExtractor {
|
|
|
1210
533
|
/**
|
|
1211
534
|
* Extract a class
|
|
1212
535
|
*/
|
|
1213
|
-
extractClass(node) {
|
|
536
|
+
extractClass(node, kind = 'class') {
|
|
1214
537
|
if (!this.extractor)
|
|
1215
538
|
return;
|
|
1216
539
|
const name = extractName(node, this.source, this.extractor);
|
|
1217
|
-
const docstring = getPrecedingDocstring(node, this.source);
|
|
540
|
+
const docstring = (0, tree_sitter_helpers_1.getPrecedingDocstring)(node, this.source);
|
|
1218
541
|
const visibility = this.extractor.getVisibility?.(node);
|
|
1219
542
|
const isExported = this.extractor.isExported?.(node, this.source);
|
|
1220
|
-
const classNode = this.createNode(
|
|
543
|
+
const classNode = this.createNode(kind, name, node, {
|
|
1221
544
|
docstring,
|
|
1222
545
|
visibility,
|
|
1223
546
|
isExported,
|
|
@@ -1228,11 +551,8 @@ class TreeSitterExtractor {
|
|
|
1228
551
|
this.extractInheritance(node, classNode.id);
|
|
1229
552
|
// Push to stack and visit body
|
|
1230
553
|
this.nodeStack.push(classNode.id);
|
|
1231
|
-
let body =
|
|
1232
|
-
|
|
1233
|
-
if (!body && this.language === 'dart') {
|
|
1234
|
-
body = node.namedChildren.find((c) => c.type === 'class_body' || c.type === 'extension_body') || null;
|
|
1235
|
-
}
|
|
554
|
+
let body = this.extractor.resolveBody?.(node, this.extractor.bodyField)
|
|
555
|
+
?? (0, tree_sitter_helpers_1.getChildByField)(node, this.extractor.bodyField);
|
|
1236
556
|
if (!body)
|
|
1237
557
|
body = node;
|
|
1238
558
|
// Visit all children for methods and properties
|
|
@@ -1250,34 +570,71 @@ class TreeSitterExtractor {
|
|
|
1250
570
|
extractMethod(node) {
|
|
1251
571
|
if (!this.extractor)
|
|
1252
572
|
return;
|
|
573
|
+
// For languages with receiver types (Go, Rust), include receiver in qualified name
|
|
574
|
+
// so FTS can match "scrapeLoop.run" → qualified_name "...::scrapeLoop::run"
|
|
575
|
+
const receiverType = this.extractor.getReceiverType?.(node, this.source);
|
|
1253
576
|
// For most languages, only extract as method if inside a class-like node
|
|
1254
|
-
//
|
|
1255
|
-
|
|
1256
|
-
|
|
577
|
+
// Languages with methodsAreTopLevel (e.g. Go) always treat them as methods
|
|
578
|
+
// Languages with getReceiverType (e.g. Rust) extract as method when receiver is found
|
|
579
|
+
if (!this.isInsideClassLikeNode() && !this.extractor.methodsAreTopLevel && !receiverType) {
|
|
580
|
+
// Skip method_definition nodes inside object literals (getters/setters/methods
|
|
581
|
+
// in inline objects). These are ephemeral and create noise (e.g., Svelte context
|
|
582
|
+
// objects: `ctx.set({ get view() { ... } })`).
|
|
583
|
+
if (node.parent?.type === 'object' || node.parent?.type === 'object_expression') {
|
|
584
|
+
return;
|
|
585
|
+
}
|
|
586
|
+
// Not inside a class-like node and no receiver type, treat as function
|
|
1257
587
|
this.extractFunction(node);
|
|
1258
588
|
return;
|
|
1259
589
|
}
|
|
1260
590
|
const name = extractName(node, this.source, this.extractor);
|
|
1261
|
-
|
|
591
|
+
// Check for misparse artifacts (e.g. C++ "switch" inside macro-confused class body)
|
|
592
|
+
if (this.extractor.isMisparsedFunction?.(name, node)) {
|
|
593
|
+
const body = this.extractor.resolveBody?.(node, this.extractor.bodyField)
|
|
594
|
+
?? (0, tree_sitter_helpers_1.getChildByField)(node, this.extractor.bodyField);
|
|
595
|
+
if (body) {
|
|
596
|
+
this.visitFunctionBody(body, '');
|
|
597
|
+
}
|
|
598
|
+
return;
|
|
599
|
+
}
|
|
600
|
+
const docstring = (0, tree_sitter_helpers_1.getPrecedingDocstring)(node, this.source);
|
|
1262
601
|
const signature = this.extractor.getSignature?.(node, this.source);
|
|
1263
602
|
const visibility = this.extractor.getVisibility?.(node);
|
|
1264
603
|
const isAsync = this.extractor.isAsync?.(node);
|
|
1265
604
|
const isStatic = this.extractor.isStatic?.(node);
|
|
1266
|
-
const
|
|
605
|
+
const extraProps = {
|
|
1267
606
|
docstring,
|
|
1268
607
|
signature,
|
|
1269
608
|
visibility,
|
|
1270
609
|
isAsync,
|
|
1271
610
|
isStatic,
|
|
1272
|
-
}
|
|
611
|
+
};
|
|
612
|
+
if (receiverType) {
|
|
613
|
+
extraProps.qualifiedName = `${receiverType}::${name}`;
|
|
614
|
+
}
|
|
615
|
+
const methodNode = this.createNode('method', name, node, extraProps);
|
|
1273
616
|
if (!methodNode)
|
|
1274
617
|
return;
|
|
618
|
+
// For methods with a receiver type but no class-like parent on the stack
|
|
619
|
+
// (e.g., Rust impl blocks), add a contains edge from the owning struct/trait
|
|
620
|
+
if (receiverType && !this.isInsideClassLikeNode()) {
|
|
621
|
+
const ownerNode = this.nodes.find((n) => n.name === receiverType &&
|
|
622
|
+
n.filePath === this.filePath &&
|
|
623
|
+
(n.kind === 'struct' || n.kind === 'class' || n.kind === 'enum' || n.kind === 'trait'));
|
|
624
|
+
if (ownerNode) {
|
|
625
|
+
this.edges.push({
|
|
626
|
+
source: ownerNode.id,
|
|
627
|
+
target: methodNode.id,
|
|
628
|
+
kind: 'contains',
|
|
629
|
+
});
|
|
630
|
+
}
|
|
631
|
+
}
|
|
632
|
+
// Extract type annotations (parameter types and return type)
|
|
633
|
+
this.extractTypeAnnotations(node, methodNode.id);
|
|
1275
634
|
// Push to stack and visit body
|
|
1276
635
|
this.nodeStack.push(methodNode.id);
|
|
1277
|
-
|
|
1278
|
-
|
|
1279
|
-
? node.nextNamedSibling?.type === 'function_body' ? node.nextNamedSibling : null
|
|
1280
|
-
: getChildByField(node, this.extractor.bodyField);
|
|
636
|
+
const body = this.extractor.resolveBody?.(node, this.extractor.bodyField)
|
|
637
|
+
?? (0, tree_sitter_helpers_1.getChildByField)(node, this.extractor.bodyField);
|
|
1281
638
|
if (body) {
|
|
1282
639
|
this.visitFunctionBody(body, methodNode.id);
|
|
1283
640
|
}
|
|
@@ -1290,16 +647,30 @@ class TreeSitterExtractor {
|
|
|
1290
647
|
if (!this.extractor)
|
|
1291
648
|
return;
|
|
1292
649
|
const name = extractName(node, this.source, this.extractor);
|
|
1293
|
-
const docstring = getPrecedingDocstring(node, this.source);
|
|
650
|
+
const docstring = (0, tree_sitter_helpers_1.getPrecedingDocstring)(node, this.source);
|
|
1294
651
|
const isExported = this.extractor.isExported?.(node, this.source);
|
|
1295
|
-
|
|
1296
|
-
|
|
1297
|
-
if (this.language === 'rust')
|
|
1298
|
-
kind = 'trait';
|
|
1299
|
-
this.createNode(kind, name, node, {
|
|
652
|
+
const kind = this.extractor.interfaceKind ?? 'interface';
|
|
653
|
+
const interfaceNode = this.createNode(kind, name, node, {
|
|
1300
654
|
docstring,
|
|
1301
655
|
isExported,
|
|
1302
656
|
});
|
|
657
|
+
if (!interfaceNode)
|
|
658
|
+
return;
|
|
659
|
+
// Extract extends (interface inheritance)
|
|
660
|
+
this.extractInheritance(node, interfaceNode.id);
|
|
661
|
+
// Visit body children for interface methods and nested types
|
|
662
|
+
this.nodeStack.push(interfaceNode.id);
|
|
663
|
+
let body = this.extractor.resolveBody?.(node, this.extractor.bodyField)
|
|
664
|
+
?? (0, tree_sitter_helpers_1.getChildByField)(node, this.extractor.bodyField);
|
|
665
|
+
if (!body)
|
|
666
|
+
body = node;
|
|
667
|
+
for (let i = 0; i < body.namedChildCount; i++) {
|
|
668
|
+
const child = body.namedChild(i);
|
|
669
|
+
if (child) {
|
|
670
|
+
this.visitNode(child);
|
|
671
|
+
}
|
|
672
|
+
}
|
|
673
|
+
this.nodeStack.pop();
|
|
1303
674
|
}
|
|
1304
675
|
/**
|
|
1305
676
|
* Extract a struct
|
|
@@ -1307,8 +678,12 @@ class TreeSitterExtractor {
|
|
|
1307
678
|
extractStruct(node) {
|
|
1308
679
|
if (!this.extractor)
|
|
1309
680
|
return;
|
|
681
|
+
// Skip forward declarations and type references (no body = not a definition)
|
|
682
|
+
const body = (0, tree_sitter_helpers_1.getChildByField)(node, this.extractor.bodyField);
|
|
683
|
+
if (!body)
|
|
684
|
+
return;
|
|
1310
685
|
const name = extractName(node, this.source, this.extractor);
|
|
1311
|
-
const docstring = getPrecedingDocstring(node, this.source);
|
|
686
|
+
const docstring = (0, tree_sitter_helpers_1.getPrecedingDocstring)(node, this.source);
|
|
1312
687
|
const visibility = this.extractor.getVisibility?.(node);
|
|
1313
688
|
const isExported = this.extractor.isExported?.(node, this.source);
|
|
1314
689
|
const structNode = this.createNode('struct', name, node, {
|
|
@@ -1318,9 +693,10 @@ class TreeSitterExtractor {
|
|
|
1318
693
|
});
|
|
1319
694
|
if (!structNode)
|
|
1320
695
|
return;
|
|
696
|
+
// Extract inheritance (e.g. Swift: struct HTTPMethod: RawRepresentable)
|
|
697
|
+
this.extractInheritance(node, structNode.id);
|
|
1321
698
|
// Push to stack for field extraction
|
|
1322
699
|
this.nodeStack.push(structNode.id);
|
|
1323
|
-
const body = getChildByField(node, this.extractor.bodyField) || node;
|
|
1324
700
|
for (let i = 0; i < body.namedChildCount; i++) {
|
|
1325
701
|
const child = body.namedChild(i);
|
|
1326
702
|
if (child) {
|
|
@@ -1335,15 +711,177 @@ class TreeSitterExtractor {
|
|
|
1335
711
|
extractEnum(node) {
|
|
1336
712
|
if (!this.extractor)
|
|
1337
713
|
return;
|
|
714
|
+
// Skip forward declarations and type references (no body = not a definition)
|
|
715
|
+
const body = this.extractor.resolveBody?.(node, this.extractor.bodyField)
|
|
716
|
+
?? (0, tree_sitter_helpers_1.getChildByField)(node, this.extractor.bodyField);
|
|
717
|
+
if (!body)
|
|
718
|
+
return;
|
|
1338
719
|
const name = extractName(node, this.source, this.extractor);
|
|
1339
|
-
const docstring = getPrecedingDocstring(node, this.source);
|
|
720
|
+
const docstring = (0, tree_sitter_helpers_1.getPrecedingDocstring)(node, this.source);
|
|
1340
721
|
const visibility = this.extractor.getVisibility?.(node);
|
|
1341
722
|
const isExported = this.extractor.isExported?.(node, this.source);
|
|
1342
|
-
this.createNode('enum', name, node, {
|
|
723
|
+
const enumNode = this.createNode('enum', name, node, {
|
|
1343
724
|
docstring,
|
|
1344
725
|
visibility,
|
|
1345
726
|
isExported,
|
|
1346
727
|
});
|
|
728
|
+
if (!enumNode)
|
|
729
|
+
return;
|
|
730
|
+
// Extract inheritance (e.g. Swift: enum AFError: Error)
|
|
731
|
+
this.extractInheritance(node, enumNode.id);
|
|
732
|
+
// Push to stack and visit body children (enum members, nested types, methods)
|
|
733
|
+
this.nodeStack.push(enumNode.id);
|
|
734
|
+
const memberTypes = this.extractor.enumMemberTypes;
|
|
735
|
+
for (let i = 0; i < body.namedChildCount; i++) {
|
|
736
|
+
const child = body.namedChild(i);
|
|
737
|
+
if (!child)
|
|
738
|
+
continue;
|
|
739
|
+
if (memberTypes?.includes(child.type)) {
|
|
740
|
+
this.extractEnumMembers(child);
|
|
741
|
+
}
|
|
742
|
+
else {
|
|
743
|
+
this.visitNode(child);
|
|
744
|
+
}
|
|
745
|
+
}
|
|
746
|
+
this.nodeStack.pop();
|
|
747
|
+
}
|
|
748
|
+
/**
|
|
749
|
+
* Extract enum member names from an enum member node.
|
|
750
|
+
* Handles multi-case declarations (Swift: `case put, delete`) and single-case patterns.
|
|
751
|
+
*/
|
|
752
|
+
extractEnumMembers(node) {
|
|
753
|
+
// Try field-based name first (e.g. Rust enum_variant has a 'name' field)
|
|
754
|
+
const nameNode = (0, tree_sitter_helpers_1.getChildByField)(node, 'name');
|
|
755
|
+
if (nameNode) {
|
|
756
|
+
this.createNode('enum_member', (0, tree_sitter_helpers_1.getNodeText)(nameNode, this.source), node);
|
|
757
|
+
return;
|
|
758
|
+
}
|
|
759
|
+
// Check for identifier-like children (Swift: simple_identifier, TS: property_identifier)
|
|
760
|
+
let found = false;
|
|
761
|
+
for (let i = 0; i < node.namedChildCount; i++) {
|
|
762
|
+
const child = node.namedChild(i);
|
|
763
|
+
if (child && (child.type === 'simple_identifier' || child.type === 'identifier' || child.type === 'property_identifier')) {
|
|
764
|
+
this.createNode('enum_member', (0, tree_sitter_helpers_1.getNodeText)(child, this.source), child);
|
|
765
|
+
found = true;
|
|
766
|
+
}
|
|
767
|
+
}
|
|
768
|
+
// If the node itself IS the identifier (e.g. TS property_identifier directly in enum body)
|
|
769
|
+
if (!found && node.namedChildCount === 0) {
|
|
770
|
+
this.createNode('enum_member', (0, tree_sitter_helpers_1.getNodeText)(node, this.source), node);
|
|
771
|
+
}
|
|
772
|
+
}
|
|
773
|
+
/**
|
|
774
|
+
* Extract a class property declaration (e.g. C# `public string Name { get; set; }`).
|
|
775
|
+
* Extracts as 'property' kind node inside the owning class.
|
|
776
|
+
*/
|
|
777
|
+
extractProperty(node) {
|
|
778
|
+
if (!this.extractor)
|
|
779
|
+
return;
|
|
780
|
+
const docstring = (0, tree_sitter_helpers_1.getPrecedingDocstring)(node, this.source);
|
|
781
|
+
const visibility = this.extractor.getVisibility?.(node);
|
|
782
|
+
const isStatic = this.extractor.isStatic?.(node) ?? false;
|
|
783
|
+
// Property name is a direct identifier child
|
|
784
|
+
const nameNode = (0, tree_sitter_helpers_1.getChildByField)(node, 'name')
|
|
785
|
+
|| node.namedChildren.find(c => c.type === 'identifier');
|
|
786
|
+
if (!nameNode)
|
|
787
|
+
return;
|
|
788
|
+
const name = (0, tree_sitter_helpers_1.getNodeText)(nameNode, this.source);
|
|
789
|
+
// Get property type from the type child (first named child that isn't modifier or identifier)
|
|
790
|
+
const typeNode = node.namedChildren.find(c => c.type !== 'modifier' && c.type !== 'modifiers'
|
|
791
|
+
&& c.type !== 'identifier' && c.type !== 'accessor_list'
|
|
792
|
+
&& c.type !== 'accessors' && c.type !== 'equals_value_clause');
|
|
793
|
+
const typeText = typeNode ? (0, tree_sitter_helpers_1.getNodeText)(typeNode, this.source) : undefined;
|
|
794
|
+
const signature = typeText ? `${typeText} ${name}` : name;
|
|
795
|
+
this.createNode('property', name, node, {
|
|
796
|
+
docstring,
|
|
797
|
+
signature,
|
|
798
|
+
visibility,
|
|
799
|
+
isStatic,
|
|
800
|
+
});
|
|
801
|
+
}
|
|
802
|
+
/**
|
|
803
|
+
* Extract a class field declaration (e.g. Java field_declaration, C# field_declaration).
|
|
804
|
+
* Extracts each declarator as a 'field' kind node inside the owning class.
|
|
805
|
+
*/
|
|
806
|
+
extractField(node) {
|
|
807
|
+
if (!this.extractor)
|
|
808
|
+
return;
|
|
809
|
+
const docstring = (0, tree_sitter_helpers_1.getPrecedingDocstring)(node, this.source);
|
|
810
|
+
const visibility = this.extractor.getVisibility?.(node);
|
|
811
|
+
const isStatic = this.extractor.isStatic?.(node) ?? false;
|
|
812
|
+
// Java field_declaration: "private final String name = value;" → variable_declarator(s) are direct children
|
|
813
|
+
// C# field_declaration: wraps in variable_declaration → variable_declarator(s)
|
|
814
|
+
let declarators = node.namedChildren.filter(c => c.type === 'variable_declarator');
|
|
815
|
+
// C#: look inside variable_declaration wrapper
|
|
816
|
+
if (declarators.length === 0) {
|
|
817
|
+
const varDecl = node.namedChildren.find(c => c.type === 'variable_declaration');
|
|
818
|
+
if (varDecl) {
|
|
819
|
+
declarators = varDecl.namedChildren.filter(c => c.type === 'variable_declarator');
|
|
820
|
+
}
|
|
821
|
+
}
|
|
822
|
+
// PHP property_declaration: property_element → variable_name → name
|
|
823
|
+
if (declarators.length === 0) {
|
|
824
|
+
const propElements = node.namedChildren.filter(c => c.type === 'property_element');
|
|
825
|
+
if (propElements.length > 0) {
|
|
826
|
+
// Get type annotation if present (e.g. "string", "int", "?Foo")
|
|
827
|
+
const typeNode = node.namedChildren.find(c => c.type !== 'visibility_modifier' && c.type !== 'static_modifier'
|
|
828
|
+
&& c.type !== 'readonly_modifier' && c.type !== 'property_element'
|
|
829
|
+
&& c.type !== 'var_modifier');
|
|
830
|
+
const typeText = typeNode ? (0, tree_sitter_helpers_1.getNodeText)(typeNode, this.source) : undefined;
|
|
831
|
+
for (const elem of propElements) {
|
|
832
|
+
const varName = elem.namedChildren.find(c => c.type === 'variable_name');
|
|
833
|
+
const nameNode = varName?.namedChildren.find(c => c.type === 'name');
|
|
834
|
+
if (!nameNode)
|
|
835
|
+
continue;
|
|
836
|
+
const name = (0, tree_sitter_helpers_1.getNodeText)(nameNode, this.source);
|
|
837
|
+
const signature = typeText ? `${typeText} $${name}` : `$${name}`;
|
|
838
|
+
this.createNode('field', name, elem, {
|
|
839
|
+
docstring,
|
|
840
|
+
signature,
|
|
841
|
+
visibility,
|
|
842
|
+
isStatic,
|
|
843
|
+
});
|
|
844
|
+
}
|
|
845
|
+
return;
|
|
846
|
+
}
|
|
847
|
+
}
|
|
848
|
+
if (declarators.length > 0) {
|
|
849
|
+
// Get field type from the type child
|
|
850
|
+
// Java: type is a direct child of field_declaration
|
|
851
|
+
// C#: type is inside variable_declaration wrapper
|
|
852
|
+
const varDecl = node.namedChildren.find(c => c.type === 'variable_declaration');
|
|
853
|
+
const typeSearchNode = varDecl ?? node;
|
|
854
|
+
const typeNode = typeSearchNode.namedChildren.find(c => c.type !== 'modifiers' && c.type !== 'modifier' && c.type !== 'variable_declarator'
|
|
855
|
+
&& c.type !== 'variable_declaration' && c.type !== 'marker_annotation' && c.type !== 'annotation');
|
|
856
|
+
const typeText = typeNode ? (0, tree_sitter_helpers_1.getNodeText)(typeNode, this.source) : undefined;
|
|
857
|
+
for (const decl of declarators) {
|
|
858
|
+
const nameNode = (0, tree_sitter_helpers_1.getChildByField)(decl, 'name')
|
|
859
|
+
|| decl.namedChildren.find(c => c.type === 'identifier');
|
|
860
|
+
if (!nameNode)
|
|
861
|
+
continue;
|
|
862
|
+
const name = (0, tree_sitter_helpers_1.getNodeText)(nameNode, this.source);
|
|
863
|
+
const signature = typeText ? `${typeText} ${name}` : name;
|
|
864
|
+
this.createNode('field', name, decl, {
|
|
865
|
+
docstring,
|
|
866
|
+
signature,
|
|
867
|
+
visibility,
|
|
868
|
+
isStatic,
|
|
869
|
+
});
|
|
870
|
+
}
|
|
871
|
+
}
|
|
872
|
+
else {
|
|
873
|
+
// Fallback: try to find an identifier child directly
|
|
874
|
+
const nameNode = (0, tree_sitter_helpers_1.getChildByField)(node, 'name')
|
|
875
|
+
|| node.namedChildren.find(c => c.type === 'identifier');
|
|
876
|
+
if (nameNode) {
|
|
877
|
+
const name = (0, tree_sitter_helpers_1.getNodeText)(nameNode, this.source);
|
|
878
|
+
this.createNode('field', name, node, {
|
|
879
|
+
docstring,
|
|
880
|
+
visibility,
|
|
881
|
+
isStatic,
|
|
882
|
+
});
|
|
883
|
+
}
|
|
884
|
+
}
|
|
1347
885
|
}
|
|
1348
886
|
/**
|
|
1349
887
|
* Extract a variable declaration (const, let, var, etc.)
|
|
@@ -1360,7 +898,7 @@ class TreeSitterExtractor {
|
|
|
1360
898
|
// Go: var_declaration, short_var_declaration, const_declaration
|
|
1361
899
|
const isConst = this.extractor.isConst?.(node) ?? false;
|
|
1362
900
|
const kind = isConst ? 'constant' : 'variable';
|
|
1363
|
-
const docstring = getPrecedingDocstring(node, this.source);
|
|
901
|
+
const docstring = (0, tree_sitter_helpers_1.getPrecedingDocstring)(node, this.source);
|
|
1364
902
|
const isExported = this.extractor.isExported?.(node, this.source) ?? false;
|
|
1365
903
|
// Extract variable declarators based on language
|
|
1366
904
|
if (this.language === 'typescript' || this.language === 'javascript' ||
|
|
@@ -1370,36 +908,45 @@ class TreeSitterExtractor {
|
|
|
1370
908
|
for (let i = 0; i < node.namedChildCount; i++) {
|
|
1371
909
|
const child = node.namedChild(i);
|
|
1372
910
|
if (child?.type === 'variable_declarator') {
|
|
1373
|
-
const nameNode = getChildByField(child, 'name');
|
|
1374
|
-
const valueNode = getChildByField(child, 'value');
|
|
911
|
+
const nameNode = (0, tree_sitter_helpers_1.getChildByField)(child, 'name');
|
|
912
|
+
const valueNode = (0, tree_sitter_helpers_1.getChildByField)(child, 'value');
|
|
1375
913
|
if (nameNode) {
|
|
1376
|
-
|
|
914
|
+
// Skip destructured patterns (e.g., `let { x, y } = $props()` in Svelte)
|
|
915
|
+
// These produce ugly multi-line names like "{ class: className }"
|
|
916
|
+
if (nameNode.type === 'object_pattern' || nameNode.type === 'array_pattern') {
|
|
917
|
+
continue;
|
|
918
|
+
}
|
|
919
|
+
const name = (0, tree_sitter_helpers_1.getNodeText)(nameNode, this.source);
|
|
1377
920
|
// Arrow functions / function expressions: extract as function instead of variable
|
|
1378
921
|
if (valueNode && (valueNode.type === 'arrow_function' || valueNode.type === 'function_expression')) {
|
|
1379
922
|
this.extractFunction(valueNode);
|
|
1380
923
|
continue;
|
|
1381
924
|
}
|
|
1382
925
|
// Capture first 100 chars of initializer for context (stored in signature for searchability)
|
|
1383
|
-
const initValue = valueNode ? getNodeText(valueNode, this.source).slice(0, 100) : undefined;
|
|
926
|
+
const initValue = valueNode ? (0, tree_sitter_helpers_1.getNodeText)(valueNode, this.source).slice(0, 100) : undefined;
|
|
1384
927
|
const initSignature = initValue ? `= ${initValue}${initValue.length >= 100 ? '...' : ''}` : undefined;
|
|
1385
|
-
this.createNode(kind, name, child, {
|
|
928
|
+
const varNode = this.createNode(kind, name, child, {
|
|
1386
929
|
docstring,
|
|
1387
930
|
signature: initSignature,
|
|
1388
931
|
isExported,
|
|
1389
932
|
});
|
|
933
|
+
// Extract type annotation references (e.g., const x: ITextModel = ...)
|
|
934
|
+
if (varNode) {
|
|
935
|
+
this.extractVariableTypeAnnotation(child, varNode.id);
|
|
936
|
+
}
|
|
1390
937
|
}
|
|
1391
938
|
}
|
|
1392
939
|
}
|
|
1393
940
|
}
|
|
1394
941
|
else if (this.language === 'python' || this.language === 'ruby') {
|
|
1395
942
|
// Python/Ruby assignment: left = right
|
|
1396
|
-
const left = getChildByField(node, 'left') || node.namedChild(0);
|
|
1397
|
-
const right = getChildByField(node, 'right') || node.namedChild(1);
|
|
943
|
+
const left = (0, tree_sitter_helpers_1.getChildByField)(node, 'left') || node.namedChild(0);
|
|
944
|
+
const right = (0, tree_sitter_helpers_1.getChildByField)(node, 'right') || node.namedChild(1);
|
|
1398
945
|
if (left && left.type === 'identifier') {
|
|
1399
|
-
const name = getNodeText(left, this.source);
|
|
946
|
+
const name = (0, tree_sitter_helpers_1.getNodeText)(left, this.source);
|
|
1400
947
|
// Skip if name starts with lowercase and looks like a function call result
|
|
1401
948
|
// Python constants are usually UPPER_CASE
|
|
1402
|
-
const initValue = right ? getNodeText(right, this.source).slice(0, 100) : undefined;
|
|
949
|
+
const initValue = right ? (0, tree_sitter_helpers_1.getNodeText)(right, this.source).slice(0, 100) : undefined;
|
|
1403
950
|
const initSignature = initValue ? `= ${initValue}${initValue.length >= 100 ? '...' : ''}` : undefined;
|
|
1404
951
|
this.createNode(kind, name, node, {
|
|
1405
952
|
docstring,
|
|
@@ -1414,9 +961,9 @@ class TreeSitterExtractor {
|
|
|
1414
961
|
for (const spec of specs) {
|
|
1415
962
|
const nameNode = spec.namedChild(0);
|
|
1416
963
|
if (nameNode && nameNode.type === 'identifier') {
|
|
1417
|
-
const name = getNodeText(nameNode, this.source);
|
|
964
|
+
const name = (0, tree_sitter_helpers_1.getNodeText)(nameNode, this.source);
|
|
1418
965
|
const valueNode = spec.namedChildCount > 1 ? spec.namedChild(spec.namedChildCount - 1) : null;
|
|
1419
|
-
const initValue = valueNode ? getNodeText(valueNode, this.source).slice(0, 100) : undefined;
|
|
966
|
+
const initValue = valueNode ? (0, tree_sitter_helpers_1.getNodeText)(valueNode, this.source).slice(0, 100) : undefined;
|
|
1420
967
|
const initSignature = initValue ? `= ${initValue}${initValue.length >= 100 ? '...' : ''}` : undefined;
|
|
1421
968
|
this.createNode(node.type === 'const_declaration' ? 'constant' : 'variable', name, spec, {
|
|
1422
969
|
docstring,
|
|
@@ -1426,16 +973,16 @@ class TreeSitterExtractor {
|
|
|
1426
973
|
}
|
|
1427
974
|
// Handle short_var_declaration (:=)
|
|
1428
975
|
if (node.type === 'short_var_declaration') {
|
|
1429
|
-
const left = getChildByField(node, 'left');
|
|
1430
|
-
const right = getChildByField(node, 'right');
|
|
976
|
+
const left = (0, tree_sitter_helpers_1.getChildByField)(node, 'left');
|
|
977
|
+
const right = (0, tree_sitter_helpers_1.getChildByField)(node, 'right');
|
|
1431
978
|
if (left) {
|
|
1432
979
|
// Can be expression_list with multiple identifiers
|
|
1433
980
|
const identifiers = left.type === 'expression_list'
|
|
1434
981
|
? left.namedChildren.filter(c => c.type === 'identifier')
|
|
1435
982
|
: [left];
|
|
1436
983
|
for (const id of identifiers) {
|
|
1437
|
-
const name = getNodeText(id, this.source);
|
|
1438
|
-
const initValue = right ? getNodeText(right, this.source).slice(0, 100) : undefined;
|
|
984
|
+
const name = (0, tree_sitter_helpers_1.getNodeText)(id, this.source);
|
|
985
|
+
const initValue = right ? (0, tree_sitter_helpers_1.getNodeText)(right, this.source).slice(0, 100) : undefined;
|
|
1439
986
|
const initSignature = initValue ? `= ${initValue}${initValue.length >= 100 ? '...' : ''}` : undefined;
|
|
1440
987
|
this.createNode('variable', name, node, {
|
|
1441
988
|
docstring,
|
|
@@ -1452,7 +999,7 @@ class TreeSitterExtractor {
|
|
|
1452
999
|
const child = node.namedChild(i);
|
|
1453
1000
|
if (child?.type === 'identifier' || child?.type === 'variable_declarator') {
|
|
1454
1001
|
const name = child.type === 'identifier'
|
|
1455
|
-
? getNodeText(child, this.source)
|
|
1002
|
+
? (0, tree_sitter_helpers_1.getNodeText)(child, this.source)
|
|
1456
1003
|
: extractName(child, this.source, this.extractor);
|
|
1457
1004
|
if (name && name !== '<anonymous>') {
|
|
1458
1005
|
this.createNode(kind, name, child, {
|
|
@@ -1465,20 +1012,98 @@ class TreeSitterExtractor {
|
|
|
1465
1012
|
}
|
|
1466
1013
|
}
|
|
1467
1014
|
/**
|
|
1468
|
-
* Extract a type alias (e.g. `export type X = ...` in TypeScript)
|
|
1015
|
+
* Extract a type alias (e.g. `export type X = ...` in TypeScript).
|
|
1016
|
+
* For languages like Go, resolveTypeAliasKind detects when the type_spec
|
|
1017
|
+
* wraps a struct or interface definition and creates the correct node kind.
|
|
1018
|
+
* Returns true if children should be skipped (struct/interface handled body visiting).
|
|
1469
1019
|
*/
|
|
1470
1020
|
extractTypeAlias(node) {
|
|
1471
1021
|
if (!this.extractor)
|
|
1472
|
-
return;
|
|
1022
|
+
return false;
|
|
1473
1023
|
const name = extractName(node, this.source, this.extractor);
|
|
1474
1024
|
if (name === '<anonymous>')
|
|
1475
|
-
return;
|
|
1476
|
-
const docstring = getPrecedingDocstring(node, this.source);
|
|
1025
|
+
return false;
|
|
1026
|
+
const docstring = (0, tree_sitter_helpers_1.getPrecedingDocstring)(node, this.source);
|
|
1477
1027
|
const isExported = this.extractor.isExported?.(node, this.source);
|
|
1478
|
-
this
|
|
1028
|
+
// Check if this type alias is actually a struct or interface definition
|
|
1029
|
+
// (e.g. Go: `type Foo struct { ... }` is a type_spec wrapping struct_type)
|
|
1030
|
+
const resolvedKind = this.extractor.resolveTypeAliasKind?.(node, this.source);
|
|
1031
|
+
if (resolvedKind === 'struct') {
|
|
1032
|
+
const structNode = this.createNode('struct', name, node, { docstring, isExported });
|
|
1033
|
+
if (!structNode)
|
|
1034
|
+
return true;
|
|
1035
|
+
// Visit body children for field extraction
|
|
1036
|
+
this.nodeStack.push(structNode.id);
|
|
1037
|
+
// Try Go-style 'type' field first, then find inner struct child (C typedef struct)
|
|
1038
|
+
const typeChild = (0, tree_sitter_helpers_1.getChildByField)(node, 'type')
|
|
1039
|
+
|| this.findChildByTypes(node, this.extractor.structTypes);
|
|
1040
|
+
if (typeChild) {
|
|
1041
|
+
// Extract struct embedding (e.g. Go: `type DB struct { *Head; Queryable }`)
|
|
1042
|
+
this.extractInheritance(typeChild, structNode.id);
|
|
1043
|
+
const body = (0, tree_sitter_helpers_1.getChildByField)(typeChild, this.extractor.bodyField) || typeChild;
|
|
1044
|
+
for (let i = 0; i < body.namedChildCount; i++) {
|
|
1045
|
+
const child = body.namedChild(i);
|
|
1046
|
+
if (child)
|
|
1047
|
+
this.visitNode(child);
|
|
1048
|
+
}
|
|
1049
|
+
}
|
|
1050
|
+
this.nodeStack.pop();
|
|
1051
|
+
return true;
|
|
1052
|
+
}
|
|
1053
|
+
if (resolvedKind === 'enum') {
|
|
1054
|
+
const enumNode = this.createNode('enum', name, node, { docstring, isExported });
|
|
1055
|
+
if (!enumNode)
|
|
1056
|
+
return true;
|
|
1057
|
+
this.nodeStack.push(enumNode.id);
|
|
1058
|
+
// Find the inner enum type child (e.g. C: typedef enum { ... } name)
|
|
1059
|
+
const innerEnum = this.findChildByTypes(node, this.extractor.enumTypes);
|
|
1060
|
+
if (innerEnum) {
|
|
1061
|
+
this.extractInheritance(innerEnum, enumNode.id);
|
|
1062
|
+
const body = this.extractor.resolveBody?.(innerEnum, this.extractor.bodyField)
|
|
1063
|
+
?? (0, tree_sitter_helpers_1.getChildByField)(innerEnum, this.extractor.bodyField);
|
|
1064
|
+
if (body) {
|
|
1065
|
+
const memberTypes = this.extractor.enumMemberTypes;
|
|
1066
|
+
for (let i = 0; i < body.namedChildCount; i++) {
|
|
1067
|
+
const child = body.namedChild(i);
|
|
1068
|
+
if (!child)
|
|
1069
|
+
continue;
|
|
1070
|
+
if (memberTypes?.includes(child.type)) {
|
|
1071
|
+
this.extractEnumMembers(child);
|
|
1072
|
+
}
|
|
1073
|
+
else {
|
|
1074
|
+
this.visitNode(child);
|
|
1075
|
+
}
|
|
1076
|
+
}
|
|
1077
|
+
}
|
|
1078
|
+
}
|
|
1079
|
+
this.nodeStack.pop();
|
|
1080
|
+
return true;
|
|
1081
|
+
}
|
|
1082
|
+
if (resolvedKind === 'interface') {
|
|
1083
|
+
const kind = this.extractor.interfaceKind ?? 'interface';
|
|
1084
|
+
const interfaceNode = this.createNode(kind, name, node, { docstring, isExported });
|
|
1085
|
+
if (!interfaceNode)
|
|
1086
|
+
return true;
|
|
1087
|
+
// Extract interface inheritance from the inner type node
|
|
1088
|
+
const typeChild = (0, tree_sitter_helpers_1.getChildByField)(node, 'type');
|
|
1089
|
+
if (typeChild)
|
|
1090
|
+
this.extractInheritance(typeChild, interfaceNode.id);
|
|
1091
|
+
return true;
|
|
1092
|
+
}
|
|
1093
|
+
const typeAliasNode = this.createNode('type_alias', name, node, {
|
|
1479
1094
|
docstring,
|
|
1480
1095
|
isExported,
|
|
1481
1096
|
});
|
|
1097
|
+
// Extract type references from the alias value (e.g., `type X = ITextModel | null`)
|
|
1098
|
+
if (typeAliasNode && this.TYPE_ANNOTATION_LANGUAGES.has(this.language)) {
|
|
1099
|
+
// The value is everything after the `=`, which is typically the last named child
|
|
1100
|
+
// In tree-sitter TS: type_alias_declaration has name + value children
|
|
1101
|
+
const value = (0, tree_sitter_helpers_1.getChildByField)(node, 'value');
|
|
1102
|
+
if (value) {
|
|
1103
|
+
this.extractTypeRefsFromSubtree(value, typeAliasNode.id);
|
|
1104
|
+
}
|
|
1105
|
+
}
|
|
1106
|
+
return false;
|
|
1482
1107
|
}
|
|
1483
1108
|
/**
|
|
1484
1109
|
* Extract an exported variable declaration that isn't a function.
|
|
@@ -1507,20 +1132,20 @@ class TreeSitterExtractor {
|
|
|
1507
1132
|
const declarator = decl.namedChild(j);
|
|
1508
1133
|
if (!declarator || declarator.type !== 'variable_declarator')
|
|
1509
1134
|
continue;
|
|
1510
|
-
const nameNode = getChildByField(declarator, 'name');
|
|
1135
|
+
const nameNode = (0, tree_sitter_helpers_1.getChildByField)(declarator, 'name');
|
|
1511
1136
|
if (!nameNode)
|
|
1512
1137
|
continue;
|
|
1513
|
-
const name = getNodeText(nameNode, this.source);
|
|
1138
|
+
const name = (0, tree_sitter_helpers_1.getNodeText)(nameNode, this.source);
|
|
1514
1139
|
// Skip if the value is a function type — those are already handled
|
|
1515
1140
|
// by extractFunction via the functionTypes dispatch
|
|
1516
|
-
const value = getChildByField(declarator, 'value');
|
|
1141
|
+
const value = (0, tree_sitter_helpers_1.getChildByField)(declarator, 'value');
|
|
1517
1142
|
if (value) {
|
|
1518
1143
|
const valueType = value.type;
|
|
1519
1144
|
if (this.extractor.functionTypes.includes(valueType)) {
|
|
1520
1145
|
continue; // Already handled by extractFunction
|
|
1521
1146
|
}
|
|
1522
1147
|
}
|
|
1523
|
-
const docstring = getPrecedingDocstring(exportNode, this.source);
|
|
1148
|
+
const docstring = (0, tree_sitter_helpers_1.getPrecedingDocstring)(exportNode, this.source);
|
|
1524
1149
|
this.createNode('variable', name, declarator, {
|
|
1525
1150
|
docstring,
|
|
1526
1151
|
isExported: true,
|
|
@@ -1535,210 +1160,108 @@ class TreeSitterExtractor {
|
|
|
1535
1160
|
* Also creates unresolved references for resolution purposes.
|
|
1536
1161
|
*/
|
|
1537
1162
|
extractImport(node) {
|
|
1538
|
-
|
|
1539
|
-
|
|
1540
|
-
|
|
1541
|
-
|
|
1542
|
-
|
|
1543
|
-
const
|
|
1544
|
-
if (
|
|
1545
|
-
|
|
1546
|
-
|
|
1547
|
-
// Create import node with full statement as signature for searchability
|
|
1548
|
-
if (moduleName) {
|
|
1549
|
-
this.createNode('import', moduleName, node, {
|
|
1550
|
-
signature: importText,
|
|
1163
|
+
if (!this.extractor)
|
|
1164
|
+
return;
|
|
1165
|
+
const importText = (0, tree_sitter_helpers_1.getNodeText)(node, this.source).trim();
|
|
1166
|
+
// Try language-specific hook first
|
|
1167
|
+
if (this.extractor.extractImport) {
|
|
1168
|
+
const info = this.extractor.extractImport(node, this.source);
|
|
1169
|
+
if (info) {
|
|
1170
|
+
this.createNode('import', info.moduleName, node, {
|
|
1171
|
+
signature: info.signature,
|
|
1551
1172
|
});
|
|
1173
|
+
// Create unresolved reference unless the hook handled it
|
|
1174
|
+
if (!info.handledRefs && info.moduleName && this.nodeStack.length > 0) {
|
|
1175
|
+
const parentId = this.nodeStack[this.nodeStack.length - 1];
|
|
1176
|
+
if (parentId) {
|
|
1177
|
+
this.unresolvedReferences.push({
|
|
1178
|
+
fromNodeId: parentId,
|
|
1179
|
+
referenceName: info.moduleName,
|
|
1180
|
+
referenceKind: 'imports',
|
|
1181
|
+
line: node.startPosition.row + 1,
|
|
1182
|
+
column: node.startPosition.column,
|
|
1183
|
+
});
|
|
1184
|
+
}
|
|
1185
|
+
}
|
|
1186
|
+
return;
|
|
1552
1187
|
}
|
|
1553
|
-
|
|
1554
|
-
|
|
1555
|
-
//
|
|
1556
|
-
|
|
1557
|
-
|
|
1558
|
-
|
|
1559
|
-
|
|
1560
|
-
|
|
1561
|
-
|
|
1562
|
-
|
|
1563
|
-
|
|
1564
|
-
|
|
1565
|
-
|
|
1566
|
-
|
|
1567
|
-
|
|
1568
|
-
const
|
|
1569
|
-
if (
|
|
1570
|
-
|
|
1571
|
-
this.createNode('import', name, node, {
|
|
1188
|
+
// Hook returned null — fall through to multi-import inline handlers only
|
|
1189
|
+
// (hook returning null means "I didn't handle this" for multi-import cases,
|
|
1190
|
+
// NOT "use generic fallback" — the hook already declined)
|
|
1191
|
+
}
|
|
1192
|
+
// Multi-import cases that create multiple nodes (can't be expressed with single-return hook)
|
|
1193
|
+
// Python import_statement: import os, sys (creates one import per module)
|
|
1194
|
+
if (this.language === 'python' && node.type === 'import_statement') {
|
|
1195
|
+
for (let i = 0; i < node.namedChildCount; i++) {
|
|
1196
|
+
const child = node.namedChild(i);
|
|
1197
|
+
if (child?.type === 'dotted_name') {
|
|
1198
|
+
this.createNode('import', (0, tree_sitter_helpers_1.getNodeText)(child, this.source), node, {
|
|
1199
|
+
signature: importText,
|
|
1200
|
+
});
|
|
1201
|
+
}
|
|
1202
|
+
else if (child?.type === 'aliased_import') {
|
|
1203
|
+
const dottedName = child.namedChildren.find(c => c.type === 'dotted_name');
|
|
1204
|
+
if (dottedName) {
|
|
1205
|
+
this.createNode('import', (0, tree_sitter_helpers_1.getNodeText)(dottedName, this.source), node, {
|
|
1572
1206
|
signature: importText,
|
|
1573
1207
|
});
|
|
1574
1208
|
}
|
|
1575
|
-
else if (child?.type === 'aliased_import') {
|
|
1576
|
-
// Extract the module name from inside aliased_import
|
|
1577
|
-
const dottedName = child.namedChildren.find(c => c.type === 'dotted_name');
|
|
1578
|
-
if (dottedName) {
|
|
1579
|
-
const name = getNodeText(dottedName, this.source);
|
|
1580
|
-
this.createNode('import', name, node, {
|
|
1581
|
-
signature: importText,
|
|
1582
|
-
});
|
|
1583
|
-
}
|
|
1584
|
-
}
|
|
1585
|
-
}
|
|
1586
|
-
// Skip creating another node below if we handled import_statement
|
|
1587
|
-
if (node.type === 'import_statement') {
|
|
1588
|
-
return;
|
|
1589
1209
|
}
|
|
1590
1210
|
}
|
|
1591
|
-
|
|
1592
|
-
this.createNode('import', moduleName, node, {
|
|
1593
|
-
signature: importText,
|
|
1594
|
-
});
|
|
1595
|
-
}
|
|
1211
|
+
return;
|
|
1596
1212
|
}
|
|
1597
|
-
|
|
1598
|
-
|
|
1599
|
-
|
|
1600
|
-
// Grouped: import ( "fmt" \n "os" ) - uses import_spec_list containing import_spec children
|
|
1601
|
-
// Helper function to extract path from import_spec
|
|
1213
|
+
// Go imports: single or grouped (creates one import per spec)
|
|
1214
|
+
if (this.language === 'go') {
|
|
1215
|
+
const parentId = this.nodeStack.length > 0 ? this.nodeStack[this.nodeStack.length - 1] : null;
|
|
1602
1216
|
const extractFromSpec = (spec) => {
|
|
1603
1217
|
const stringLiteral = spec.namedChildren.find(c => c.type === 'interpreted_string_literal');
|
|
1604
1218
|
if (stringLiteral) {
|
|
1605
|
-
const
|
|
1606
|
-
if (
|
|
1607
|
-
this.createNode('import',
|
|
1608
|
-
signature: getNodeText(spec, this.source).trim(),
|
|
1219
|
+
const importPath = (0, tree_sitter_helpers_1.getNodeText)(stringLiteral, this.source).replace(/['"]/g, '');
|
|
1220
|
+
if (importPath) {
|
|
1221
|
+
this.createNode('import', importPath, spec, {
|
|
1222
|
+
signature: (0, tree_sitter_helpers_1.getNodeText)(spec, this.source).trim(),
|
|
1609
1223
|
});
|
|
1224
|
+
// Create unresolved reference so the resolver can create imports edges
|
|
1225
|
+
if (parentId) {
|
|
1226
|
+
this.unresolvedReferences.push({
|
|
1227
|
+
fromNodeId: parentId,
|
|
1228
|
+
referenceName: importPath,
|
|
1229
|
+
referenceKind: 'imports',
|
|
1230
|
+
line: spec.startPosition.row + 1,
|
|
1231
|
+
column: spec.startPosition.column,
|
|
1232
|
+
});
|
|
1233
|
+
}
|
|
1610
1234
|
}
|
|
1611
1235
|
}
|
|
1612
1236
|
};
|
|
1613
|
-
// Find import_spec_list for grouped imports
|
|
1614
1237
|
const importSpecList = node.namedChildren.find(c => c.type === 'import_spec_list');
|
|
1615
1238
|
if (importSpecList) {
|
|
1616
|
-
|
|
1617
|
-
const importSpecs = importSpecList.namedChildren.filter(c => c.type === 'import_spec');
|
|
1618
|
-
for (const spec of importSpecs) {
|
|
1239
|
+
for (const spec of importSpecList.namedChildren.filter(c => c.type === 'import_spec')) {
|
|
1619
1240
|
extractFromSpec(spec);
|
|
1620
1241
|
}
|
|
1621
1242
|
}
|
|
1622
1243
|
else {
|
|
1623
|
-
// Single import: import "fmt" - import_spec is direct child
|
|
1624
1244
|
const importSpec = node.namedChildren.find(c => c.type === 'import_spec');
|
|
1625
1245
|
if (importSpec) {
|
|
1626
1246
|
extractFromSpec(importSpec);
|
|
1627
1247
|
}
|
|
1628
1248
|
}
|
|
1629
|
-
return;
|
|
1630
|
-
}
|
|
1631
|
-
else if (this.language === 'rust') {
|
|
1632
|
-
// Rust use declarations
|
|
1633
|
-
// use std::{ffi::OsStr, io}; -> scoped_use_list with identifier "std"
|
|
1634
|
-
// use crate::error::Error; -> scoped_identifier starting with "crate"
|
|
1635
|
-
// use super::utils; -> scoped_identifier starting with "super"
|
|
1636
|
-
// Helper to get the root crate/module from a scoped path
|
|
1637
|
-
const getRootModule = (scopedNode) => {
|
|
1638
|
-
// Recursively find the leftmost identifier/crate/super/self
|
|
1639
|
-
const firstChild = scopedNode.namedChild(0);
|
|
1640
|
-
if (!firstChild)
|
|
1641
|
-
return getNodeText(scopedNode, this.source);
|
|
1642
|
-
if (firstChild.type === 'identifier' ||
|
|
1643
|
-
firstChild.type === 'crate' ||
|
|
1644
|
-
firstChild.type === 'super' ||
|
|
1645
|
-
firstChild.type === 'self') {
|
|
1646
|
-
return getNodeText(firstChild, this.source);
|
|
1647
|
-
}
|
|
1648
|
-
else if (firstChild.type === 'scoped_identifier') {
|
|
1649
|
-
return getRootModule(firstChild);
|
|
1650
|
-
}
|
|
1651
|
-
return getNodeText(firstChild, this.source);
|
|
1652
|
-
};
|
|
1653
|
-
// Find the use argument (scoped_use_list or scoped_identifier)
|
|
1654
|
-
const useArg = node.namedChildren.find(c => c.type === 'scoped_use_list' ||
|
|
1655
|
-
c.type === 'scoped_identifier' ||
|
|
1656
|
-
c.type === 'use_list' ||
|
|
1657
|
-
c.type === 'identifier');
|
|
1658
|
-
if (useArg) {
|
|
1659
|
-
moduleName = getRootModule(useArg);
|
|
1660
|
-
this.createNode('import', moduleName, node, {
|
|
1661
|
-
signature: importText,
|
|
1662
|
-
});
|
|
1663
|
-
}
|
|
1664
|
-
return; // Rust handled completely above
|
|
1665
|
-
}
|
|
1666
|
-
else if (this.language === 'swift') {
|
|
1667
|
-
// Swift imports: import Foundation, @testable import Alamofire
|
|
1668
|
-
// AST structure: import_declaration -> identifier -> simple_identifier
|
|
1669
|
-
const identifier = node.namedChildren.find(c => c.type === 'identifier');
|
|
1670
|
-
if (identifier) {
|
|
1671
|
-
moduleName = getNodeText(identifier, this.source);
|
|
1672
|
-
this.createNode('import', moduleName, node, {
|
|
1673
|
-
signature: importText,
|
|
1674
|
-
});
|
|
1675
|
-
}
|
|
1676
|
-
return; // Swift handled completely above
|
|
1677
|
-
}
|
|
1678
|
-
else if (this.language === 'kotlin') {
|
|
1679
|
-
// Kotlin imports: import java.io.IOException, import x.y.Z as Alias, import x.y.*
|
|
1680
|
-
// AST structure: import_header -> identifier (dotted path)
|
|
1681
|
-
const identifier = node.namedChildren.find(c => c.type === 'identifier');
|
|
1682
|
-
if (identifier) {
|
|
1683
|
-
moduleName = getNodeText(identifier, this.source);
|
|
1684
|
-
this.createNode('import', moduleName, node, {
|
|
1685
|
-
signature: importText,
|
|
1686
|
-
});
|
|
1687
|
-
}
|
|
1688
|
-
return; // Kotlin handled completely above
|
|
1689
|
-
}
|
|
1690
|
-
else if (this.language === 'java') {
|
|
1691
|
-
// Java imports: import java.util.List, import static x.Y.method, import x.y.*
|
|
1692
|
-
// AST structure: import_declaration -> scoped_identifier (dotted path)
|
|
1693
|
-
const scopedId = node.namedChildren.find(c => c.type === 'scoped_identifier');
|
|
1694
|
-
if (scopedId) {
|
|
1695
|
-
moduleName = getNodeText(scopedId, this.source);
|
|
1696
|
-
this.createNode('import', moduleName, node, {
|
|
1697
|
-
signature: importText,
|
|
1698
|
-
});
|
|
1699
|
-
}
|
|
1700
|
-
return; // Java handled completely above
|
|
1701
|
-
}
|
|
1702
|
-
else if (this.language === 'csharp') {
|
|
1703
|
-
// C# using directives: using System, using System.Collections.Generic, using static X, using Alias = X
|
|
1704
|
-
// AST structure: using_directive -> qualified_name (dotted) or identifier (simple)
|
|
1705
|
-
// For alias imports: identifier = qualified_name - we want the qualified_name
|
|
1706
|
-
const qualifiedName = node.namedChildren.find(c => c.type === 'qualified_name');
|
|
1707
|
-
if (qualifiedName) {
|
|
1708
|
-
moduleName = getNodeText(qualifiedName, this.source);
|
|
1709
|
-
}
|
|
1710
|
-
else {
|
|
1711
|
-
// Simple namespace like "using System;" - get the first identifier
|
|
1712
|
-
const identifier = node.namedChildren.find(c => c.type === 'identifier');
|
|
1713
|
-
if (identifier) {
|
|
1714
|
-
moduleName = getNodeText(identifier, this.source);
|
|
1715
|
-
}
|
|
1716
|
-
}
|
|
1717
|
-
if (moduleName) {
|
|
1718
|
-
this.createNode('import', moduleName, node, {
|
|
1719
|
-
signature: importText,
|
|
1720
|
-
});
|
|
1721
|
-
}
|
|
1722
|
-
return; // C# handled completely above
|
|
1249
|
+
return;
|
|
1723
1250
|
}
|
|
1724
|
-
|
|
1725
|
-
|
|
1726
|
-
// AST structure: namespace_use_declaration -> namespace_use_clause -> qualified_name or name
|
|
1727
|
-
// Check for grouped imports first: use X\{A, B}
|
|
1251
|
+
// PHP grouped imports: use X\{A, B} (creates one import per item)
|
|
1252
|
+
if (this.language === 'php') {
|
|
1728
1253
|
const namespacePrefix = node.namedChildren.find(c => c.type === 'namespace_name');
|
|
1729
1254
|
const useGroup = node.namedChildren.find(c => c.type === 'namespace_use_group');
|
|
1730
1255
|
if (namespacePrefix && useGroup) {
|
|
1731
|
-
|
|
1732
|
-
const prefix = getNodeText(namespacePrefix, this.source);
|
|
1256
|
+
const prefix = (0, tree_sitter_helpers_1.getNodeText)(namespacePrefix, this.source);
|
|
1733
1257
|
const useClauses = useGroup.namedChildren.filter((c) => c.type === 'namespace_use_group_clause' || c.type === 'namespace_use_clause');
|
|
1734
1258
|
for (const clause of useClauses) {
|
|
1735
|
-
// WASM grammar wraps names in namespace_name; native uses name directly
|
|
1736
1259
|
const nsName = clause.namedChildren.find((c) => c.type === 'namespace_name');
|
|
1737
1260
|
const name = nsName
|
|
1738
1261
|
? nsName.namedChildren.find((c) => c.type === 'name')
|
|
1739
1262
|
: clause.namedChildren.find((c) => c.type === 'name');
|
|
1740
1263
|
if (name) {
|
|
1741
|
-
const fullPath = `${prefix}\\${getNodeText(name, this.source)}`;
|
|
1264
|
+
const fullPath = `${prefix}\\${(0, tree_sitter_helpers_1.getNodeText)(name, this.source)}`;
|
|
1742
1265
|
this.createNode('import', fullPath, node, {
|
|
1743
1266
|
signature: importText,
|
|
1744
1267
|
});
|
|
@@ -1746,147 +1269,14 @@ class TreeSitterExtractor {
|
|
|
1746
1269
|
}
|
|
1747
1270
|
return;
|
|
1748
1271
|
}
|
|
1749
|
-
// Single import - find namespace_use_clause
|
|
1750
|
-
const useClause = node.namedChildren.find(c => c.type === 'namespace_use_clause');
|
|
1751
|
-
if (useClause) {
|
|
1752
|
-
// Look for qualified_name (full path) or name (simple)
|
|
1753
|
-
const qualifiedName = useClause.namedChildren.find((c) => c.type === 'qualified_name');
|
|
1754
|
-
if (qualifiedName) {
|
|
1755
|
-
moduleName = getNodeText(qualifiedName, this.source);
|
|
1756
|
-
}
|
|
1757
|
-
else {
|
|
1758
|
-
const name = useClause.namedChildren.find((c) => c.type === 'name');
|
|
1759
|
-
if (name) {
|
|
1760
|
-
moduleName = getNodeText(name, this.source);
|
|
1761
|
-
}
|
|
1762
|
-
}
|
|
1763
|
-
}
|
|
1764
|
-
if (moduleName) {
|
|
1765
|
-
this.createNode('import', moduleName, node, {
|
|
1766
|
-
signature: importText,
|
|
1767
|
-
});
|
|
1768
|
-
}
|
|
1769
|
-
return; // PHP handled completely above
|
|
1770
|
-
}
|
|
1771
|
-
else if (this.language === 'ruby') {
|
|
1772
|
-
// Ruby imports: require 'json', require_relative '../helper'
|
|
1773
|
-
// AST structure: call -> identifier (require/require_relative) + argument_list -> string -> string_content
|
|
1774
|
-
// Check if this is a require/require_relative call
|
|
1775
|
-
const identifier = node.namedChildren.find(c => c.type === 'identifier');
|
|
1776
|
-
if (!identifier)
|
|
1777
|
-
return;
|
|
1778
|
-
const methodName = getNodeText(identifier, this.source);
|
|
1779
|
-
if (methodName !== 'require' && methodName !== 'require_relative') {
|
|
1780
|
-
return; // Not an import, skip
|
|
1781
|
-
}
|
|
1782
|
-
// Find the argument (string)
|
|
1783
|
-
const argList = node.namedChildren.find(c => c.type === 'argument_list');
|
|
1784
|
-
if (argList) {
|
|
1785
|
-
const stringNode = argList.namedChildren.find((c) => c.type === 'string');
|
|
1786
|
-
if (stringNode) {
|
|
1787
|
-
// Get string_content (without quotes)
|
|
1788
|
-
const stringContent = stringNode.namedChildren.find((c) => c.type === 'string_content');
|
|
1789
|
-
if (stringContent) {
|
|
1790
|
-
moduleName = getNodeText(stringContent, this.source);
|
|
1791
|
-
}
|
|
1792
|
-
}
|
|
1793
|
-
}
|
|
1794
|
-
if (moduleName) {
|
|
1795
|
-
this.createNode('import', moduleName, node, {
|
|
1796
|
-
signature: importText,
|
|
1797
|
-
});
|
|
1798
|
-
}
|
|
1799
|
-
return; // Ruby handled completely above
|
|
1800
|
-
}
|
|
1801
|
-
else if (this.language === 'dart') {
|
|
1802
|
-
// Dart imports: import 'dart:async'; import 'package:foo/bar.dart' as bar;
|
|
1803
|
-
// AST: import_or_export -> library_import -> import_specification -> configurable_uri -> uri -> string_literal
|
|
1804
|
-
const libraryImport = node.namedChildren.find(c => c.type === 'library_import');
|
|
1805
|
-
if (libraryImport) {
|
|
1806
|
-
const importSpec = libraryImport.namedChildren.find((c) => c.type === 'import_specification');
|
|
1807
|
-
if (importSpec) {
|
|
1808
|
-
const configurableUri = importSpec.namedChildren.find((c) => c.type === 'configurable_uri');
|
|
1809
|
-
if (configurableUri) {
|
|
1810
|
-
const uri = configurableUri.namedChildren.find((c) => c.type === 'uri');
|
|
1811
|
-
if (uri) {
|
|
1812
|
-
const stringLiteral = uri.namedChildren.find((c) => c.type === 'string_literal');
|
|
1813
|
-
if (stringLiteral) {
|
|
1814
|
-
moduleName = getNodeText(stringLiteral, this.source).replace(/['"]/g, '');
|
|
1815
|
-
}
|
|
1816
|
-
}
|
|
1817
|
-
}
|
|
1818
|
-
}
|
|
1819
|
-
}
|
|
1820
|
-
// Also handle exports: export 'src/foo.dart';
|
|
1821
|
-
const libraryExport = node.namedChildren.find(c => c.type === 'library_export');
|
|
1822
|
-
if (libraryExport) {
|
|
1823
|
-
const configurableUri = libraryExport.namedChildren.find((c) => c.type === 'configurable_uri');
|
|
1824
|
-
if (configurableUri) {
|
|
1825
|
-
const uri = configurableUri.namedChildren.find((c) => c.type === 'uri');
|
|
1826
|
-
if (uri) {
|
|
1827
|
-
const stringLiteral = uri.namedChildren.find((c) => c.type === 'string_literal');
|
|
1828
|
-
if (stringLiteral) {
|
|
1829
|
-
moduleName = getNodeText(stringLiteral, this.source).replace(/['"]/g, '');
|
|
1830
|
-
}
|
|
1831
|
-
}
|
|
1832
|
-
}
|
|
1833
|
-
}
|
|
1834
|
-
if (moduleName) {
|
|
1835
|
-
this.createNode('import', moduleName, node, {
|
|
1836
|
-
signature: importText,
|
|
1837
|
-
});
|
|
1838
|
-
}
|
|
1839
|
-
return; // Dart handled completely above
|
|
1840
|
-
}
|
|
1841
|
-
else if (this.language === 'c' || this.language === 'cpp') {
|
|
1842
|
-
// C/C++ includes: #include <iostream>, #include "myheader.h"
|
|
1843
|
-
// AST: preproc_include -> system_lib_string (<...>) or string_literal ("...")
|
|
1844
|
-
// Check for system include: <path>
|
|
1845
|
-
const systemLib = node.namedChildren.find(c => c.type === 'system_lib_string');
|
|
1846
|
-
if (systemLib) {
|
|
1847
|
-
// Remove angle brackets: <iostream> -> iostream
|
|
1848
|
-
moduleName = getNodeText(systemLib, this.source).replace(/^<|>$/g, '');
|
|
1849
|
-
}
|
|
1850
|
-
else {
|
|
1851
|
-
// Check for local include: "path"
|
|
1852
|
-
const stringLiteral = node.namedChildren.find(c => c.type === 'string_literal');
|
|
1853
|
-
if (stringLiteral) {
|
|
1854
|
-
const stringContent = stringLiteral.namedChildren.find((c) => c.type === 'string_content');
|
|
1855
|
-
if (stringContent) {
|
|
1856
|
-
moduleName = getNodeText(stringContent, this.source);
|
|
1857
|
-
}
|
|
1858
|
-
}
|
|
1859
|
-
}
|
|
1860
|
-
if (moduleName) {
|
|
1861
|
-
this.createNode('import', moduleName, node, {
|
|
1862
|
-
signature: importText,
|
|
1863
|
-
});
|
|
1864
|
-
}
|
|
1865
|
-
return; // C/C++ handled completely above
|
|
1866
|
-
}
|
|
1867
|
-
else {
|
|
1868
|
-
// Generic extraction for other languages
|
|
1869
|
-
moduleName = importText;
|
|
1870
|
-
if (moduleName) {
|
|
1871
|
-
this.createNode('import', moduleName, node, {
|
|
1872
|
-
signature: importText,
|
|
1873
|
-
});
|
|
1874
|
-
}
|
|
1875
|
-
}
|
|
1876
|
-
// Keep unresolved reference creation for resolution purposes
|
|
1877
|
-
// This is used to resolve imports to their target files/modules
|
|
1878
|
-
if (moduleName && this.nodeStack.length > 0) {
|
|
1879
|
-
const parentId = this.nodeStack[this.nodeStack.length - 1];
|
|
1880
|
-
if (parentId) {
|
|
1881
|
-
this.unresolvedReferences.push({
|
|
1882
|
-
fromNodeId: parentId,
|
|
1883
|
-
referenceName: moduleName,
|
|
1884
|
-
referenceKind: 'imports',
|
|
1885
|
-
line: node.startPosition.row + 1,
|
|
1886
|
-
column: node.startPosition.column,
|
|
1887
|
-
});
|
|
1888
|
-
}
|
|
1889
1272
|
}
|
|
1273
|
+
// If a hook exists but returned null, it intentionally declined this node — don't create fallback
|
|
1274
|
+
if (this.extractor.extractImport)
|
|
1275
|
+
return;
|
|
1276
|
+
// Generic fallback for languages without hooks
|
|
1277
|
+
this.createNode('import', importText, node, {
|
|
1278
|
+
signature: importText,
|
|
1279
|
+
});
|
|
1890
1280
|
}
|
|
1891
1281
|
/**
|
|
1892
1282
|
* Extract a function call
|
|
@@ -1899,21 +1289,74 @@ class TreeSitterExtractor {
|
|
|
1899
1289
|
return;
|
|
1900
1290
|
// Get the function/method being called
|
|
1901
1291
|
let calleeName = '';
|
|
1902
|
-
|
|
1903
|
-
|
|
1904
|
-
|
|
1905
|
-
|
|
1906
|
-
|
|
1907
|
-
|
|
1908
|
-
|
|
1292
|
+
// Java/Kotlin method_invocation has 'object' + 'name' fields instead of 'function'
|
|
1293
|
+
// PHP member_call_expression has 'object' + 'name', scoped_call_expression has 'scope' + 'name'
|
|
1294
|
+
const nameField = (0, tree_sitter_helpers_1.getChildByField)(node, 'name');
|
|
1295
|
+
const objectField = (0, tree_sitter_helpers_1.getChildByField)(node, 'object') || (0, tree_sitter_helpers_1.getChildByField)(node, 'scope');
|
|
1296
|
+
if (nameField && objectField && (node.type === 'method_invocation' || node.type === 'member_call_expression' || node.type === 'scoped_call_expression')) {
|
|
1297
|
+
// Method call with explicit receiver: receiver.method() / $receiver->method() / ClassName::method()
|
|
1298
|
+
const methodName = (0, tree_sitter_helpers_1.getNodeText)(nameField, this.source);
|
|
1299
|
+
let receiverName = (0, tree_sitter_helpers_1.getNodeText)(objectField, this.source);
|
|
1300
|
+
// Strip PHP $ prefix from variable names
|
|
1301
|
+
receiverName = receiverName.replace(/^\$/, '');
|
|
1302
|
+
if (methodName) {
|
|
1303
|
+
// Skip self/this/parent/static receivers — they don't aid resolution
|
|
1304
|
+
const SKIP_RECEIVERS = new Set(['self', 'this', 'cls', 'super', 'parent', 'static']);
|
|
1305
|
+
if (SKIP_RECEIVERS.has(receiverName)) {
|
|
1306
|
+
calleeName = methodName;
|
|
1307
|
+
}
|
|
1308
|
+
else {
|
|
1309
|
+
calleeName = `${receiverName}.${methodName}`;
|
|
1909
1310
|
}
|
|
1910
1311
|
}
|
|
1911
|
-
|
|
1912
|
-
|
|
1913
|
-
|
|
1914
|
-
|
|
1915
|
-
|
|
1916
|
-
|
|
1312
|
+
}
|
|
1313
|
+
else {
|
|
1314
|
+
const func = (0, tree_sitter_helpers_1.getChildByField)(node, 'function') || node.namedChild(0);
|
|
1315
|
+
if (func) {
|
|
1316
|
+
if (func.type === 'member_expression' || func.type === 'attribute' || func.type === 'selector_expression' || func.type === 'navigation_expression') {
|
|
1317
|
+
// Method call: obj.method() or obj.field.method()
|
|
1318
|
+
// Go uses selector_expression with 'field', JS/TS uses member_expression with 'property'
|
|
1319
|
+
// Kotlin uses navigation_expression with navigation_suffix > simple_identifier
|
|
1320
|
+
let property = (0, tree_sitter_helpers_1.getChildByField)(func, 'property') || (0, tree_sitter_helpers_1.getChildByField)(func, 'field');
|
|
1321
|
+
if (!property) {
|
|
1322
|
+
const child1 = func.namedChild(1);
|
|
1323
|
+
// Kotlin: navigation_suffix wraps the method name — extract simple_identifier from it
|
|
1324
|
+
if (child1?.type === 'navigation_suffix') {
|
|
1325
|
+
property = child1.namedChildren.find((c) => c.type === 'simple_identifier') ?? child1;
|
|
1326
|
+
}
|
|
1327
|
+
else {
|
|
1328
|
+
property = child1;
|
|
1329
|
+
}
|
|
1330
|
+
}
|
|
1331
|
+
if (property) {
|
|
1332
|
+
const methodName = (0, tree_sitter_helpers_1.getNodeText)(property, this.source);
|
|
1333
|
+
// Include receiver name for qualified resolution (e.g., console.print → "console.print")
|
|
1334
|
+
// This helps the resolver distinguish method calls from bare function calls
|
|
1335
|
+
// (e.g., Python's console.print() vs builtin print())
|
|
1336
|
+
// Skip self/this/cls as they don't aid resolution
|
|
1337
|
+
const receiver = (0, tree_sitter_helpers_1.getChildByField)(func, 'object') || (0, tree_sitter_helpers_1.getChildByField)(func, 'operand') || func.namedChild(0);
|
|
1338
|
+
const SKIP_RECEIVERS = new Set(['self', 'this', 'cls', 'super']);
|
|
1339
|
+
if (receiver && (receiver.type === 'identifier' || receiver.type === 'simple_identifier')) {
|
|
1340
|
+
const receiverName = (0, tree_sitter_helpers_1.getNodeText)(receiver, this.source);
|
|
1341
|
+
if (!SKIP_RECEIVERS.has(receiverName)) {
|
|
1342
|
+
calleeName = `${receiverName}.${methodName}`;
|
|
1343
|
+
}
|
|
1344
|
+
else {
|
|
1345
|
+
calleeName = methodName;
|
|
1346
|
+
}
|
|
1347
|
+
}
|
|
1348
|
+
else {
|
|
1349
|
+
calleeName = methodName;
|
|
1350
|
+
}
|
|
1351
|
+
}
|
|
1352
|
+
}
|
|
1353
|
+
else if (func.type === 'scoped_identifier' || func.type === 'scoped_call_expression') {
|
|
1354
|
+
// Scoped call: Module::function()
|
|
1355
|
+
calleeName = (0, tree_sitter_helpers_1.getNodeText)(func, this.source);
|
|
1356
|
+
}
|
|
1357
|
+
else {
|
|
1358
|
+
calleeName = (0, tree_sitter_helpers_1.getNodeText)(func, this.source);
|
|
1359
|
+
}
|
|
1917
1360
|
}
|
|
1918
1361
|
}
|
|
1919
1362
|
if (calleeName) {
|
|
@@ -1927,24 +1370,74 @@ class TreeSitterExtractor {
|
|
|
1927
1370
|
}
|
|
1928
1371
|
}
|
|
1929
1372
|
/**
|
|
1930
|
-
* Visit function body and extract calls
|
|
1373
|
+
* Visit function body and extract calls (and structural nodes).
|
|
1374
|
+
*
|
|
1375
|
+
* In addition to call expressions, this also detects class/struct/enum
|
|
1376
|
+
* definitions inside function bodies. This handles two cases:
|
|
1377
|
+
* 1. Local class/struct/enum definitions (valid in C++, Java, etc.)
|
|
1378
|
+
* 2. C++ macro misparsing — macros like NLOHMANN_JSON_NAMESPACE_BEGIN cause
|
|
1379
|
+
* tree-sitter to interpret the namespace block as a function_definition,
|
|
1380
|
+
* hiding real class/struct/enum nodes inside the "function body".
|
|
1931
1381
|
*/
|
|
1932
1382
|
visitFunctionBody(body, _functionId) {
|
|
1933
1383
|
if (!this.extractor)
|
|
1934
1384
|
return;
|
|
1935
|
-
|
|
1936
|
-
|
|
1937
|
-
if (this.extractor.callTypes.includes(
|
|
1385
|
+
const visitForCallsAndStructure = (node) => {
|
|
1386
|
+
const nodeType = node.type;
|
|
1387
|
+
if (this.extractor.callTypes.includes(nodeType)) {
|
|
1938
1388
|
this.extractCall(node);
|
|
1939
1389
|
}
|
|
1390
|
+
else if (this.extractor.extractBareCall) {
|
|
1391
|
+
const calleeName = this.extractor.extractBareCall(node, this.source);
|
|
1392
|
+
if (calleeName && this.nodeStack.length > 0) {
|
|
1393
|
+
const callerId = this.nodeStack[this.nodeStack.length - 1];
|
|
1394
|
+
if (callerId) {
|
|
1395
|
+
this.unresolvedReferences.push({
|
|
1396
|
+
fromNodeId: callerId,
|
|
1397
|
+
referenceName: calleeName,
|
|
1398
|
+
referenceKind: 'calls',
|
|
1399
|
+
line: node.startPosition.row + 1,
|
|
1400
|
+
column: node.startPosition.column,
|
|
1401
|
+
});
|
|
1402
|
+
}
|
|
1403
|
+
}
|
|
1404
|
+
}
|
|
1405
|
+
// Extract structural nodes found inside function bodies.
|
|
1406
|
+
// Each extract method visits its own children, so we return after extracting.
|
|
1407
|
+
if (this.extractor.classTypes.includes(nodeType)) {
|
|
1408
|
+
const classification = this.extractor.classifyClassNode?.(node) ?? 'class';
|
|
1409
|
+
if (classification === 'struct')
|
|
1410
|
+
this.extractStruct(node);
|
|
1411
|
+
else if (classification === 'enum')
|
|
1412
|
+
this.extractEnum(node);
|
|
1413
|
+
else if (classification === 'interface')
|
|
1414
|
+
this.extractInterface(node);
|
|
1415
|
+
else if (classification === 'trait')
|
|
1416
|
+
this.extractClass(node, 'trait');
|
|
1417
|
+
else
|
|
1418
|
+
this.extractClass(node);
|
|
1419
|
+
return;
|
|
1420
|
+
}
|
|
1421
|
+
if (this.extractor.structTypes.includes(nodeType)) {
|
|
1422
|
+
this.extractStruct(node);
|
|
1423
|
+
return;
|
|
1424
|
+
}
|
|
1425
|
+
if (this.extractor.enumTypes.includes(nodeType)) {
|
|
1426
|
+
this.extractEnum(node);
|
|
1427
|
+
return;
|
|
1428
|
+
}
|
|
1429
|
+
if (this.extractor.interfaceTypes.includes(nodeType)) {
|
|
1430
|
+
this.extractInterface(node);
|
|
1431
|
+
return;
|
|
1432
|
+
}
|
|
1940
1433
|
for (let i = 0; i < node.namedChildCount; i++) {
|
|
1941
1434
|
const child = node.namedChild(i);
|
|
1942
1435
|
if (child) {
|
|
1943
|
-
|
|
1436
|
+
visitForCallsAndStructure(child);
|
|
1944
1437
|
}
|
|
1945
1438
|
}
|
|
1946
1439
|
};
|
|
1947
|
-
|
|
1440
|
+
visitForCallsAndStructure(body);
|
|
1948
1441
|
}
|
|
1949
1442
|
/**
|
|
1950
1443
|
* Extract inheritance relationships
|
|
@@ -1956,30 +1449,39 @@ class TreeSitterExtractor {
|
|
|
1956
1449
|
if (!child)
|
|
1957
1450
|
continue;
|
|
1958
1451
|
if (child.type === 'extends_clause' ||
|
|
1959
|
-
child.type === '
|
|
1960
|
-
child.type === '
|
|
1961
|
-
//
|
|
1962
|
-
|
|
1963
|
-
|
|
1964
|
-
|
|
1965
|
-
|
|
1966
|
-
|
|
1967
|
-
|
|
1968
|
-
|
|
1969
|
-
|
|
1970
|
-
|
|
1971
|
-
|
|
1452
|
+
child.type === 'superclass' ||
|
|
1453
|
+
child.type === 'base_clause' || // PHP class extends
|
|
1454
|
+
child.type === 'extends_interfaces' // Java interface extends
|
|
1455
|
+
) {
|
|
1456
|
+
// Extract parent class/interface names
|
|
1457
|
+
// Java uses type_list wrapper: superclass -> type_identifier, extends_interfaces -> type_list -> type_identifier
|
|
1458
|
+
const typeList = child.namedChildren.find((c) => c.type === 'type_list');
|
|
1459
|
+
const targets = typeList ? typeList.namedChildren : [child.namedChild(0)];
|
|
1460
|
+
for (const target of targets) {
|
|
1461
|
+
if (target) {
|
|
1462
|
+
const name = (0, tree_sitter_helpers_1.getNodeText)(target, this.source);
|
|
1463
|
+
this.unresolvedReferences.push({
|
|
1464
|
+
fromNodeId: classId,
|
|
1465
|
+
referenceName: name,
|
|
1466
|
+
referenceKind: 'extends',
|
|
1467
|
+
line: target.startPosition.row + 1,
|
|
1468
|
+
column: target.startPosition.column,
|
|
1469
|
+
});
|
|
1470
|
+
}
|
|
1972
1471
|
}
|
|
1973
1472
|
}
|
|
1974
1473
|
if (child.type === 'implements_clause' ||
|
|
1975
1474
|
child.type === 'class_interface_clause' ||
|
|
1475
|
+
child.type === 'super_interfaces' || // Java class implements
|
|
1976
1476
|
child.type === 'interfaces' // Dart
|
|
1977
1477
|
) {
|
|
1978
1478
|
// Extract implemented interfaces
|
|
1979
|
-
|
|
1980
|
-
|
|
1479
|
+
// Java uses type_list wrapper: super_interfaces -> type_list -> type_identifier
|
|
1480
|
+
const typeList = child.namedChildren.find((c) => c.type === 'type_list');
|
|
1481
|
+
const targets = typeList ? typeList.namedChildren : child.namedChildren;
|
|
1482
|
+
for (const iface of targets) {
|
|
1981
1483
|
if (iface) {
|
|
1982
|
-
const name = getNodeText(iface, this.source);
|
|
1484
|
+
const name = (0, tree_sitter_helpers_1.getNodeText)(iface, this.source);
|
|
1983
1485
|
this.unresolvedReferences.push({
|
|
1984
1486
|
fromNodeId: classId,
|
|
1985
1487
|
referenceName: name,
|
|
@@ -1990,6 +1492,306 @@ class TreeSitterExtractor {
|
|
|
1990
1492
|
}
|
|
1991
1493
|
}
|
|
1992
1494
|
}
|
|
1495
|
+
// Python superclass list: `class Flask(Scaffold, Mixin):`
|
|
1496
|
+
// argument_list contains identifier children for each parent class
|
|
1497
|
+
if (child.type === 'argument_list' && node.type === 'class_definition') {
|
|
1498
|
+
for (const arg of child.namedChildren) {
|
|
1499
|
+
if (arg.type === 'identifier' || arg.type === 'attribute') {
|
|
1500
|
+
const name = (0, tree_sitter_helpers_1.getNodeText)(arg, this.source);
|
|
1501
|
+
this.unresolvedReferences.push({
|
|
1502
|
+
fromNodeId: classId,
|
|
1503
|
+
referenceName: name,
|
|
1504
|
+
referenceKind: 'extends',
|
|
1505
|
+
line: arg.startPosition.row + 1,
|
|
1506
|
+
column: arg.startPosition.column,
|
|
1507
|
+
});
|
|
1508
|
+
}
|
|
1509
|
+
}
|
|
1510
|
+
}
|
|
1511
|
+
// Go interface embedding: `type Querier interface { LabelQuerier; ... }`
|
|
1512
|
+
// constraint_elem wraps the embedded interface type identifier
|
|
1513
|
+
if (child.type === 'constraint_elem') {
|
|
1514
|
+
const typeId = child.namedChildren.find((c) => c.type === 'type_identifier');
|
|
1515
|
+
if (typeId) {
|
|
1516
|
+
const name = (0, tree_sitter_helpers_1.getNodeText)(typeId, this.source);
|
|
1517
|
+
this.unresolvedReferences.push({
|
|
1518
|
+
fromNodeId: classId,
|
|
1519
|
+
referenceName: name,
|
|
1520
|
+
referenceKind: 'extends',
|
|
1521
|
+
line: typeId.startPosition.row + 1,
|
|
1522
|
+
column: typeId.startPosition.column,
|
|
1523
|
+
});
|
|
1524
|
+
}
|
|
1525
|
+
}
|
|
1526
|
+
// Go struct embedding: field_declaration without field_identifier
|
|
1527
|
+
// e.g. `type DB struct { *Head; Queryable }` — no field name means embedded type
|
|
1528
|
+
if (child.type === 'field_declaration') {
|
|
1529
|
+
const hasFieldIdentifier = child.namedChildren.some((c) => c.type === 'field_identifier');
|
|
1530
|
+
if (!hasFieldIdentifier) {
|
|
1531
|
+
const typeId = child.namedChildren.find((c) => c.type === 'type_identifier');
|
|
1532
|
+
if (typeId) {
|
|
1533
|
+
const name = (0, tree_sitter_helpers_1.getNodeText)(typeId, this.source);
|
|
1534
|
+
this.unresolvedReferences.push({
|
|
1535
|
+
fromNodeId: classId,
|
|
1536
|
+
referenceName: name,
|
|
1537
|
+
referenceKind: 'extends',
|
|
1538
|
+
line: typeId.startPosition.row + 1,
|
|
1539
|
+
column: typeId.startPosition.column,
|
|
1540
|
+
});
|
|
1541
|
+
}
|
|
1542
|
+
}
|
|
1543
|
+
}
|
|
1544
|
+
// Rust trait supertraits: `trait SubTrait: SuperTrait + Display { ... }`
|
|
1545
|
+
// trait_bounds contains type_identifier, generic_type, or higher_ranked_trait_bound children
|
|
1546
|
+
if (child.type === 'trait_bounds') {
|
|
1547
|
+
for (const bound of child.namedChildren) {
|
|
1548
|
+
let typeName;
|
|
1549
|
+
let posNode;
|
|
1550
|
+
if (bound.type === 'type_identifier') {
|
|
1551
|
+
typeName = (0, tree_sitter_helpers_1.getNodeText)(bound, this.source);
|
|
1552
|
+
posNode = bound;
|
|
1553
|
+
}
|
|
1554
|
+
else if (bound.type === 'generic_type') {
|
|
1555
|
+
// e.g. `Deserialize<'de>`
|
|
1556
|
+
const inner = bound.namedChildren.find((c) => c.type === 'type_identifier');
|
|
1557
|
+
if (inner) {
|
|
1558
|
+
typeName = (0, tree_sitter_helpers_1.getNodeText)(inner, this.source);
|
|
1559
|
+
posNode = inner;
|
|
1560
|
+
}
|
|
1561
|
+
}
|
|
1562
|
+
else if (bound.type === 'higher_ranked_trait_bound') {
|
|
1563
|
+
// e.g. `for<'de> Deserialize<'de>`
|
|
1564
|
+
const generic = bound.namedChildren.find((c) => c.type === 'generic_type');
|
|
1565
|
+
const typeId = generic?.namedChildren.find((c) => c.type === 'type_identifier')
|
|
1566
|
+
?? bound.namedChildren.find((c) => c.type === 'type_identifier');
|
|
1567
|
+
if (typeId) {
|
|
1568
|
+
typeName = (0, tree_sitter_helpers_1.getNodeText)(typeId, this.source);
|
|
1569
|
+
posNode = typeId;
|
|
1570
|
+
}
|
|
1571
|
+
}
|
|
1572
|
+
if (typeName && posNode) {
|
|
1573
|
+
this.unresolvedReferences.push({
|
|
1574
|
+
fromNodeId: classId,
|
|
1575
|
+
referenceName: typeName,
|
|
1576
|
+
referenceKind: 'extends',
|
|
1577
|
+
line: posNode.startPosition.row + 1,
|
|
1578
|
+
column: posNode.startPosition.column,
|
|
1579
|
+
});
|
|
1580
|
+
}
|
|
1581
|
+
}
|
|
1582
|
+
}
|
|
1583
|
+
// C#: `class Movie : BaseItem, IPlugin` → base_list with identifier children
|
|
1584
|
+
// base_list combines both base class and interfaces in a single colon-separated list.
|
|
1585
|
+
// We emit all as 'extends' since the syntax doesn't distinguish them.
|
|
1586
|
+
if (child.type === 'base_list') {
|
|
1587
|
+
for (const baseType of child.namedChildren) {
|
|
1588
|
+
if (baseType) {
|
|
1589
|
+
// For generic base types like `ClientBase<T>`, extract just the type name
|
|
1590
|
+
const name = baseType.type === 'generic_name'
|
|
1591
|
+
? (0, tree_sitter_helpers_1.getNodeText)(baseType.namedChildren.find((c) => c.type === 'identifier') ?? baseType, this.source)
|
|
1592
|
+
: (0, tree_sitter_helpers_1.getNodeText)(baseType, this.source);
|
|
1593
|
+
this.unresolvedReferences.push({
|
|
1594
|
+
fromNodeId: classId,
|
|
1595
|
+
referenceName: name,
|
|
1596
|
+
referenceKind: 'extends',
|
|
1597
|
+
line: baseType.startPosition.row + 1,
|
|
1598
|
+
column: baseType.startPosition.column,
|
|
1599
|
+
});
|
|
1600
|
+
}
|
|
1601
|
+
}
|
|
1602
|
+
}
|
|
1603
|
+
// Kotlin: `class Foo : Bar, Baz` → delegation_specifier > user_type > type_identifier
|
|
1604
|
+
// Also handles `class Foo : Bar()` → delegation_specifier > constructor_invocation > user_type
|
|
1605
|
+
if (child.type === 'delegation_specifier') {
|
|
1606
|
+
const userType = child.namedChildren.find((c) => c.type === 'user_type');
|
|
1607
|
+
const constructorInvocation = child.namedChildren.find((c) => c.type === 'constructor_invocation');
|
|
1608
|
+
const target = userType ?? constructorInvocation;
|
|
1609
|
+
if (target) {
|
|
1610
|
+
const typeId = target.type === 'user_type'
|
|
1611
|
+
? target.namedChildren.find((c) => c.type === 'type_identifier') ?? target
|
|
1612
|
+
: target.namedChildren.find((c) => c.type === 'user_type')?.namedChildren.find((c) => c.type === 'type_identifier')
|
|
1613
|
+
?? target.namedChildren.find((c) => c.type === 'user_type') ?? target;
|
|
1614
|
+
const name = (0, tree_sitter_helpers_1.getNodeText)(typeId, this.source);
|
|
1615
|
+
this.unresolvedReferences.push({
|
|
1616
|
+
fromNodeId: classId,
|
|
1617
|
+
referenceName: name,
|
|
1618
|
+
referenceKind: 'extends',
|
|
1619
|
+
line: typeId.startPosition.row + 1,
|
|
1620
|
+
column: typeId.startPosition.column,
|
|
1621
|
+
});
|
|
1622
|
+
}
|
|
1623
|
+
}
|
|
1624
|
+
// Swift: inheritance_specifier > user_type > type_identifier
|
|
1625
|
+
// Used for class inheritance, protocol conformance, and protocol inheritance
|
|
1626
|
+
if (child.type === 'inheritance_specifier') {
|
|
1627
|
+
const userType = child.namedChildren.find((c) => c.type === 'user_type');
|
|
1628
|
+
const typeId = userType?.namedChildren.find((c) => c.type === 'type_identifier');
|
|
1629
|
+
if (typeId) {
|
|
1630
|
+
const name = (0, tree_sitter_helpers_1.getNodeText)(typeId, this.source);
|
|
1631
|
+
this.unresolvedReferences.push({
|
|
1632
|
+
fromNodeId: classId,
|
|
1633
|
+
referenceName: name,
|
|
1634
|
+
referenceKind: 'extends',
|
|
1635
|
+
line: typeId.startPosition.row + 1,
|
|
1636
|
+
column: typeId.startPosition.column,
|
|
1637
|
+
});
|
|
1638
|
+
}
|
|
1639
|
+
}
|
|
1640
|
+
// JavaScript class_heritage has bare identifier without extends_clause wrapper
|
|
1641
|
+
// e.g. `class Foo extends Bar {}` → class_heritage → identifier("Bar")
|
|
1642
|
+
if ((child.type === 'identifier' || child.type === 'type_identifier') &&
|
|
1643
|
+
node.type === 'class_heritage') {
|
|
1644
|
+
const name = (0, tree_sitter_helpers_1.getNodeText)(child, this.source);
|
|
1645
|
+
this.unresolvedReferences.push({
|
|
1646
|
+
fromNodeId: classId,
|
|
1647
|
+
referenceName: name,
|
|
1648
|
+
referenceKind: 'extends',
|
|
1649
|
+
line: child.startPosition.row + 1,
|
|
1650
|
+
column: child.startPosition.column,
|
|
1651
|
+
});
|
|
1652
|
+
}
|
|
1653
|
+
// Recurse into container nodes (e.g. field_declaration_list in Go structs,
|
|
1654
|
+
// class_heritage in TypeScript which wraps extends_clause/implements_clause)
|
|
1655
|
+
if (child.type === 'field_declaration_list' || child.type === 'class_heritage') {
|
|
1656
|
+
this.extractInheritance(child, classId);
|
|
1657
|
+
}
|
|
1658
|
+
}
|
|
1659
|
+
}
|
|
1660
|
+
/**
|
|
1661
|
+
* Rust `impl Trait for Type` — creates an implements edge from Type to Trait.
|
|
1662
|
+
* For plain `impl Type { ... }` (no trait), no inheritance edge is needed.
|
|
1663
|
+
*/
|
|
1664
|
+
extractRustImplItem(node) {
|
|
1665
|
+
// Check if this is `impl Trait for Type` by looking for a `for` keyword
|
|
1666
|
+
const hasFor = node.children.some((c) => c.type === 'for' && !c.isNamed);
|
|
1667
|
+
if (!hasFor)
|
|
1668
|
+
return;
|
|
1669
|
+
// In `impl Trait for Type`, the type_identifiers are:
|
|
1670
|
+
// first = Trait name, last = implementing Type name
|
|
1671
|
+
// Also handle generic types like `impl<T> Trait for MyStruct<T>`
|
|
1672
|
+
const typeIdents = node.namedChildren.filter((c) => c.type === 'type_identifier' || c.type === 'generic_type' || c.type === 'scoped_type_identifier');
|
|
1673
|
+
if (typeIdents.length < 2)
|
|
1674
|
+
return;
|
|
1675
|
+
const traitNode = typeIdents[0];
|
|
1676
|
+
const typeNode = typeIdents[typeIdents.length - 1];
|
|
1677
|
+
// Get the trait name (handle scoped paths like std::fmt::Display)
|
|
1678
|
+
const traitName = traitNode.type === 'scoped_type_identifier'
|
|
1679
|
+
? this.source.substring(traitNode.startIndex, traitNode.endIndex)
|
|
1680
|
+
: (0, tree_sitter_helpers_1.getNodeText)(traitNode, this.source);
|
|
1681
|
+
// Get the implementing type name (extract inner type_identifier for generics)
|
|
1682
|
+
let typeName;
|
|
1683
|
+
if (typeNode.type === 'generic_type') {
|
|
1684
|
+
const inner = typeNode.namedChildren.find((c) => c.type === 'type_identifier');
|
|
1685
|
+
typeName = inner ? (0, tree_sitter_helpers_1.getNodeText)(inner, this.source) : (0, tree_sitter_helpers_1.getNodeText)(typeNode, this.source);
|
|
1686
|
+
}
|
|
1687
|
+
else {
|
|
1688
|
+
typeName = (0, tree_sitter_helpers_1.getNodeText)(typeNode, this.source);
|
|
1689
|
+
}
|
|
1690
|
+
// Find the struct/type node for the implementing type
|
|
1691
|
+
const typeNodeId = this.findNodeByName(typeName);
|
|
1692
|
+
if (typeNodeId) {
|
|
1693
|
+
this.unresolvedReferences.push({
|
|
1694
|
+
fromNodeId: typeNodeId,
|
|
1695
|
+
referenceName: traitName,
|
|
1696
|
+
referenceKind: 'implements',
|
|
1697
|
+
line: traitNode.startPosition.row + 1,
|
|
1698
|
+
column: traitNode.startPosition.column,
|
|
1699
|
+
});
|
|
1700
|
+
}
|
|
1701
|
+
}
|
|
1702
|
+
/**
|
|
1703
|
+
* Find a previously-extracted node by name (used for back-references like impl blocks)
|
|
1704
|
+
*/
|
|
1705
|
+
findNodeByName(name) {
|
|
1706
|
+
for (const node of this.nodes) {
|
|
1707
|
+
if (node.name === name && (node.kind === 'struct' || node.kind === 'enum' || node.kind === 'class')) {
|
|
1708
|
+
return node.id;
|
|
1709
|
+
}
|
|
1710
|
+
}
|
|
1711
|
+
return undefined;
|
|
1712
|
+
}
|
|
1713
|
+
/**
|
|
1714
|
+
* Languages that support type annotations (TypeScript, etc.)
|
|
1715
|
+
*/
|
|
1716
|
+
TYPE_ANNOTATION_LANGUAGES = new Set([
|
|
1717
|
+
'typescript', 'tsx', 'dart', 'kotlin', 'swift', 'rust', 'go', 'java', 'csharp',
|
|
1718
|
+
]);
|
|
1719
|
+
/**
|
|
1720
|
+
* Built-in/primitive type names that shouldn't create references
|
|
1721
|
+
*/
|
|
1722
|
+
BUILTIN_TYPES = new Set([
|
|
1723
|
+
'string', 'number', 'boolean', 'void', 'null', 'undefined', 'never', 'any', 'unknown',
|
|
1724
|
+
'object', 'symbol', 'bigint', 'true', 'false',
|
|
1725
|
+
// Rust
|
|
1726
|
+
'str', 'bool', 'i8', 'i16', 'i32', 'i64', 'i128', 'isize',
|
|
1727
|
+
'u8', 'u16', 'u32', 'u64', 'u128', 'usize', 'f32', 'f64', 'char',
|
|
1728
|
+
// Java/C#
|
|
1729
|
+
'int', 'long', 'short', 'byte', 'float', 'double', 'char',
|
|
1730
|
+
// Go
|
|
1731
|
+
'int8', 'int16', 'int32', 'int64', 'uint8', 'uint16', 'uint32', 'uint64',
|
|
1732
|
+
'float32', 'float64', 'complex64', 'complex128', 'rune', 'error',
|
|
1733
|
+
]);
|
|
1734
|
+
/**
|
|
1735
|
+
* Extract type references from type annotations on a function/method/field node.
|
|
1736
|
+
* Creates 'references' edges for parameter types, return types, and field types.
|
|
1737
|
+
*/
|
|
1738
|
+
extractTypeAnnotations(node, nodeId) {
|
|
1739
|
+
if (!this.extractor)
|
|
1740
|
+
return;
|
|
1741
|
+
if (!this.TYPE_ANNOTATION_LANGUAGES.has(this.language))
|
|
1742
|
+
return;
|
|
1743
|
+
// Extract parameter type annotations
|
|
1744
|
+
const params = (0, tree_sitter_helpers_1.getChildByField)(node, this.extractor.paramsField || 'parameters');
|
|
1745
|
+
if (params) {
|
|
1746
|
+
this.extractTypeRefsFromSubtree(params, nodeId);
|
|
1747
|
+
}
|
|
1748
|
+
// Extract return type annotation
|
|
1749
|
+
const returnType = (0, tree_sitter_helpers_1.getChildByField)(node, this.extractor.returnField || 'return_type');
|
|
1750
|
+
if (returnType) {
|
|
1751
|
+
this.extractTypeRefsFromSubtree(returnType, nodeId);
|
|
1752
|
+
}
|
|
1753
|
+
// Extract direct type annotation (for class fields like `model: ITextModel`)
|
|
1754
|
+
const typeAnnotation = node.namedChildren.find((c) => c.type === 'type_annotation');
|
|
1755
|
+
if (typeAnnotation) {
|
|
1756
|
+
this.extractTypeRefsFromSubtree(typeAnnotation, nodeId);
|
|
1757
|
+
}
|
|
1758
|
+
}
|
|
1759
|
+
/**
|
|
1760
|
+
* Extract type references from a variable's type annotation.
|
|
1761
|
+
*/
|
|
1762
|
+
extractVariableTypeAnnotation(node, nodeId) {
|
|
1763
|
+
if (!this.TYPE_ANNOTATION_LANGUAGES.has(this.language))
|
|
1764
|
+
return;
|
|
1765
|
+
// Find type_annotation child (covers TS `: Type`, Rust `: Type`, etc.)
|
|
1766
|
+
const typeAnnotation = node.namedChildren.find((c) => c.type === 'type_annotation');
|
|
1767
|
+
if (typeAnnotation) {
|
|
1768
|
+
this.extractTypeRefsFromSubtree(typeAnnotation, nodeId);
|
|
1769
|
+
}
|
|
1770
|
+
}
|
|
1771
|
+
/**
|
|
1772
|
+
* Recursively walk a subtree and extract all type_identifier references.
|
|
1773
|
+
* Handles unions, intersections, generics, arrays, etc.
|
|
1774
|
+
*/
|
|
1775
|
+
extractTypeRefsFromSubtree(node, fromNodeId) {
|
|
1776
|
+
if (node.type === 'type_identifier') {
|
|
1777
|
+
const typeName = (0, tree_sitter_helpers_1.getNodeText)(node, this.source);
|
|
1778
|
+
if (typeName && !this.BUILTIN_TYPES.has(typeName)) {
|
|
1779
|
+
this.unresolvedReferences.push({
|
|
1780
|
+
fromNodeId,
|
|
1781
|
+
referenceName: typeName,
|
|
1782
|
+
referenceKind: 'references',
|
|
1783
|
+
line: node.startPosition.row + 1,
|
|
1784
|
+
column: node.startPosition.column,
|
|
1785
|
+
});
|
|
1786
|
+
}
|
|
1787
|
+
return; // type_identifier is a leaf
|
|
1788
|
+
}
|
|
1789
|
+
// Recurse into children (handles union_type, intersection_type, generic_type, etc.)
|
|
1790
|
+
for (let i = 0; i < node.namedChildCount; i++) {
|
|
1791
|
+
const child = node.namedChild(i);
|
|
1792
|
+
if (child) {
|
|
1793
|
+
this.extractTypeRefsFromSubtree(child, fromNodeId);
|
|
1794
|
+
}
|
|
1993
1795
|
}
|
|
1994
1796
|
}
|
|
1995
1797
|
/**
|
|
@@ -2001,7 +1803,7 @@ class TreeSitterExtractor {
|
|
|
2001
1803
|
// Unit/Program/Library → module node
|
|
2002
1804
|
if (nodeType === 'unit' || nodeType === 'program' || nodeType === 'library') {
|
|
2003
1805
|
const moduleNameNode = node.namedChildren.find((c) => c.type === 'moduleName');
|
|
2004
|
-
const name = moduleNameNode ? getNodeText(moduleNameNode, this.source) : '';
|
|
1806
|
+
const name = moduleNameNode ? (0, tree_sitter_helpers_1.getNodeText)(moduleNameNode, this.source) : '';
|
|
2005
1807
|
// Fallback to filename without extension if module name is empty
|
|
2006
1808
|
const moduleName = name || path.basename(this.filePath).replace(/\.[^.]+$/, '');
|
|
2007
1809
|
this.createNode('module', moduleName, node);
|
|
@@ -2053,9 +1855,9 @@ class TreeSitterExtractor {
|
|
|
2053
1855
|
for (let i = 0; i < node.namedChildCount; i++) {
|
|
2054
1856
|
const child = node.namedChild(i);
|
|
2055
1857
|
if (child?.type === 'declVar') {
|
|
2056
|
-
const nameNode = getChildByField(child, 'name');
|
|
1858
|
+
const nameNode = (0, tree_sitter_helpers_1.getChildByField)(child, 'name');
|
|
2057
1859
|
if (nameNode) {
|
|
2058
|
-
const name = getNodeText(nameNode, this.source);
|
|
1860
|
+
const name = (0, tree_sitter_helpers_1.getNodeText)(nameNode, this.source);
|
|
2059
1861
|
this.createNode('variable', name, child);
|
|
2060
1862
|
}
|
|
2061
1863
|
}
|
|
@@ -2069,9 +1871,9 @@ class TreeSitterExtractor {
|
|
|
2069
1871
|
}
|
|
2070
1872
|
// declProp → property node
|
|
2071
1873
|
if (nodeType === 'declProp') {
|
|
2072
|
-
const nameNode = getChildByField(node, 'name');
|
|
1874
|
+
const nameNode = (0, tree_sitter_helpers_1.getChildByField)(node, 'name');
|
|
2073
1875
|
if (nameNode) {
|
|
2074
|
-
const name = getNodeText(nameNode, this.source);
|
|
1876
|
+
const name = (0, tree_sitter_helpers_1.getNodeText)(nameNode, this.source);
|
|
2075
1877
|
const visibility = this.extractor.getVisibility?.(node);
|
|
2076
1878
|
this.createNode('property', name, node, { visibility });
|
|
2077
1879
|
}
|
|
@@ -2079,9 +1881,9 @@ class TreeSitterExtractor {
|
|
|
2079
1881
|
}
|
|
2080
1882
|
// declField → field node
|
|
2081
1883
|
if (nodeType === 'declField') {
|
|
2082
|
-
const nameNode = getChildByField(node, 'name');
|
|
1884
|
+
const nameNode = (0, tree_sitter_helpers_1.getChildByField)(node, 'name');
|
|
2083
1885
|
if (nameNode) {
|
|
2084
|
-
const name = getNodeText(nameNode, this.source);
|
|
1886
|
+
const name = (0, tree_sitter_helpers_1.getNodeText)(nameNode, this.source);
|
|
2085
1887
|
const visibility = this.extractor.getVisibility?.(node);
|
|
2086
1888
|
this.createNode('field', name, node, { visibility });
|
|
2087
1889
|
}
|
|
@@ -2121,10 +1923,10 @@ class TreeSitterExtractor {
|
|
|
2121
1923
|
* Extract a Pascal declType node (class, interface, enum, or type alias)
|
|
2122
1924
|
*/
|
|
2123
1925
|
extractPascalDeclType(node) {
|
|
2124
|
-
const nameNode = getChildByField(node, 'name');
|
|
1926
|
+
const nameNode = (0, tree_sitter_helpers_1.getChildByField)(node, 'name');
|
|
2125
1927
|
if (!nameNode)
|
|
2126
1928
|
return;
|
|
2127
|
-
const name = getNodeText(nameNode, this.source);
|
|
1929
|
+
const name = (0, tree_sitter_helpers_1.getNodeText)(nameNode, this.source);
|
|
2128
1930
|
// Find the inner type declaration
|
|
2129
1931
|
const declClass = node.namedChildren.find((c) => c.type === 'declClass');
|
|
2130
1932
|
const declIntf = node.namedChildren.find((c) => c.type === 'declIntf');
|
|
@@ -2168,9 +1970,9 @@ class TreeSitterExtractor {
|
|
|
2168
1970
|
for (let i = 0; i < declEnum.namedChildCount; i++) {
|
|
2169
1971
|
const child = declEnum.namedChild(i);
|
|
2170
1972
|
if (child?.type === 'declEnumValue') {
|
|
2171
|
-
const memberName = getChildByField(child, 'name');
|
|
1973
|
+
const memberName = (0, tree_sitter_helpers_1.getChildByField)(child, 'name');
|
|
2172
1974
|
if (memberName) {
|
|
2173
|
-
this.createNode('enum_member', getNodeText(memberName, this.source), child);
|
|
1975
|
+
this.createNode('enum_member', (0, tree_sitter_helpers_1.getNodeText)(memberName, this.source), child);
|
|
2174
1976
|
}
|
|
2175
1977
|
}
|
|
2176
1978
|
}
|
|
@@ -2191,11 +1993,11 @@ class TreeSitterExtractor {
|
|
|
2191
1993
|
* Extract Pascal uses clause into individual import nodes
|
|
2192
1994
|
*/
|
|
2193
1995
|
extractPascalUses(node) {
|
|
2194
|
-
const importText = getNodeText(node, this.source).trim();
|
|
1996
|
+
const importText = (0, tree_sitter_helpers_1.getNodeText)(node, this.source).trim();
|
|
2195
1997
|
for (let i = 0; i < node.namedChildCount; i++) {
|
|
2196
1998
|
const child = node.namedChild(i);
|
|
2197
1999
|
if (child?.type === 'moduleName') {
|
|
2198
|
-
const unitName = getNodeText(child, this.source);
|
|
2000
|
+
const unitName = (0, tree_sitter_helpers_1.getNodeText)(child, this.source);
|
|
2199
2001
|
this.createNode('import', unitName, child, {
|
|
2200
2002
|
signature: importText,
|
|
2201
2003
|
});
|
|
@@ -2219,12 +2021,12 @@ class TreeSitterExtractor {
|
|
|
2219
2021
|
* Extract a Pascal constant declaration
|
|
2220
2022
|
*/
|
|
2221
2023
|
extractPascalConst(node) {
|
|
2222
|
-
const nameNode = getChildByField(node, 'name');
|
|
2024
|
+
const nameNode = (0, tree_sitter_helpers_1.getChildByField)(node, 'name');
|
|
2223
2025
|
if (!nameNode)
|
|
2224
2026
|
return;
|
|
2225
|
-
const name = getNodeText(nameNode, this.source);
|
|
2027
|
+
const name = (0, tree_sitter_helpers_1.getNodeText)(nameNode, this.source);
|
|
2226
2028
|
const defaultValue = node.namedChildren.find((c) => c.type === 'defaultValue');
|
|
2227
|
-
const sig = defaultValue ? getNodeText(defaultValue, this.source) : undefined;
|
|
2029
|
+
const sig = defaultValue ? (0, tree_sitter_helpers_1.getNodeText)(defaultValue, this.source) : undefined;
|
|
2228
2030
|
this.createNode('constant', name, node, { signature: sig });
|
|
2229
2031
|
}
|
|
2230
2032
|
/**
|
|
@@ -2234,7 +2036,7 @@ class TreeSitterExtractor {
|
|
|
2234
2036
|
const typerefs = declClass.namedChildren.filter((c) => c.type === 'typeref');
|
|
2235
2037
|
for (let i = 0; i < typerefs.length; i++) {
|
|
2236
2038
|
const ref = typerefs[i];
|
|
2237
|
-
const name = getNodeText(ref, this.source);
|
|
2039
|
+
const name = (0, tree_sitter_helpers_1.getNodeText)(ref, this.source);
|
|
2238
2040
|
this.unresolvedReferences.push({
|
|
2239
2041
|
fromNodeId: classId,
|
|
2240
2042
|
referenceName: name,
|
|
@@ -2253,10 +2055,10 @@ class TreeSitterExtractor {
|
|
|
2253
2055
|
const declProc = node.namedChildren.find((c) => c.type === 'declProc');
|
|
2254
2056
|
if (!declProc)
|
|
2255
2057
|
return;
|
|
2256
|
-
const nameNode = getChildByField(declProc, 'name');
|
|
2058
|
+
const nameNode = (0, tree_sitter_helpers_1.getChildByField)(declProc, 'name');
|
|
2257
2059
|
if (!nameNode)
|
|
2258
2060
|
return;
|
|
2259
|
-
const fullName = getNodeText(nameNode, this.source).trim();
|
|
2061
|
+
const fullName = (0, tree_sitter_helpers_1.getNodeText)(nameNode, this.source).trim();
|
|
2260
2062
|
// fullName is like "TAuthService.Create"
|
|
2261
2063
|
const shortName = fullName.includes('.') ? fullName.split('.').pop() : fullName;
|
|
2262
2064
|
const fullNameKey = fullName.toLowerCase();
|
|
@@ -2273,7 +2075,7 @@ class TreeSitterExtractor {
|
|
|
2273
2075
|
}
|
|
2274
2076
|
// For Pascal methods, also index qualified forms (e.g. TAuthService.Create).
|
|
2275
2077
|
if (n.kind === 'method') {
|
|
2276
|
-
const qualifiedParts = n.qualifiedName.split('::')
|
|
2078
|
+
const qualifiedParts = n.qualifiedName.split('::');
|
|
2277
2079
|
if (qualifiedParts.length >= 2) {
|
|
2278
2080
|
// Create suffix keys so both "Module.Class.Method" and "Class.Method" can resolve.
|
|
2279
2081
|
for (let i = 0; i < qualifiedParts.length - 1; i++) {
|
|
@@ -2316,11 +2118,11 @@ class TreeSitterExtractor {
|
|
|
2316
2118
|
// Qualified call: Obj.Method(...)
|
|
2317
2119
|
const identifiers = firstChild.namedChildren.filter((c) => c.type === 'identifier');
|
|
2318
2120
|
if (identifiers.length > 0) {
|
|
2319
|
-
calleeName = identifiers.map((id) => getNodeText(id, this.source)).join('.');
|
|
2121
|
+
calleeName = identifiers.map((id) => (0, tree_sitter_helpers_1.getNodeText)(id, this.source)).join('.');
|
|
2320
2122
|
}
|
|
2321
2123
|
}
|
|
2322
2124
|
else if (firstChild.type === 'identifier') {
|
|
2323
|
-
calleeName = getNodeText(firstChild, this.source);
|
|
2125
|
+
calleeName = (0, tree_sitter_helpers_1.getNodeText)(firstChild, this.source);
|
|
2324
2126
|
}
|
|
2325
2127
|
if (calleeName) {
|
|
2326
2128
|
this.unresolvedReferences.push({
|
|
@@ -2364,631 +2166,26 @@ class TreeSitterExtractor {
|
|
|
2364
2166
|
}
|
|
2365
2167
|
}
|
|
2366
2168
|
exports.TreeSitterExtractor = TreeSitterExtractor;
|
|
2367
|
-
/**
|
|
2368
|
-
* LiquidExtractor - Extracts relationships from Liquid template files
|
|
2369
|
-
*
|
|
2370
|
-
* Liquid is a templating language (used by Shopify, Jekyll, etc.) that doesn't
|
|
2371
|
-
* have traditional functions or classes. Instead, we extract:
|
|
2372
|
-
* - Section references ({% section 'name' %})
|
|
2373
|
-
* - Snippet references ({% render 'name' %} and {% include 'name' %})
|
|
2374
|
-
* - Schema blocks ({% schema %}...{% endschema %})
|
|
2375
|
-
*/
|
|
2376
|
-
class LiquidExtractor {
|
|
2377
|
-
filePath;
|
|
2378
|
-
source;
|
|
2379
|
-
nodes = [];
|
|
2380
|
-
edges = [];
|
|
2381
|
-
unresolvedReferences = [];
|
|
2382
|
-
errors = [];
|
|
2383
|
-
constructor(filePath, source) {
|
|
2384
|
-
this.filePath = filePath;
|
|
2385
|
-
this.source = source;
|
|
2386
|
-
}
|
|
2387
|
-
/**
|
|
2388
|
-
* Extract from Liquid source
|
|
2389
|
-
*/
|
|
2390
|
-
extract() {
|
|
2391
|
-
const startTime = Date.now();
|
|
2392
|
-
try {
|
|
2393
|
-
// Create file node
|
|
2394
|
-
const fileNode = this.createFileNode();
|
|
2395
|
-
// Extract render/include statements (snippet references)
|
|
2396
|
-
this.extractSnippetReferences(fileNode.id);
|
|
2397
|
-
// Extract section references
|
|
2398
|
-
this.extractSectionReferences(fileNode.id);
|
|
2399
|
-
// Extract schema block
|
|
2400
|
-
this.extractSchema(fileNode.id);
|
|
2401
|
-
// Extract assign statements as variables
|
|
2402
|
-
this.extractAssignments(fileNode.id);
|
|
2403
|
-
}
|
|
2404
|
-
catch (error) {
|
|
2405
|
-
this.errors.push({
|
|
2406
|
-
message: `Liquid extraction error: ${error instanceof Error ? error.message : String(error)}`,
|
|
2407
|
-
severity: 'error',
|
|
2408
|
-
});
|
|
2409
|
-
}
|
|
2410
|
-
return {
|
|
2411
|
-
nodes: this.nodes,
|
|
2412
|
-
edges: this.edges,
|
|
2413
|
-
unresolvedReferences: this.unresolvedReferences,
|
|
2414
|
-
errors: this.errors,
|
|
2415
|
-
durationMs: Date.now() - startTime,
|
|
2416
|
-
};
|
|
2417
|
-
}
|
|
2418
|
-
/**
|
|
2419
|
-
* Create a file node for the Liquid template
|
|
2420
|
-
*/
|
|
2421
|
-
createFileNode() {
|
|
2422
|
-
const lines = this.source.split('\n');
|
|
2423
|
-
const id = generateNodeId(this.filePath, 'file', this.filePath, 1);
|
|
2424
|
-
const fileNode = {
|
|
2425
|
-
id,
|
|
2426
|
-
kind: 'file',
|
|
2427
|
-
name: this.filePath.split('/').pop() || this.filePath,
|
|
2428
|
-
qualifiedName: this.filePath,
|
|
2429
|
-
filePath: this.filePath,
|
|
2430
|
-
language: 'liquid',
|
|
2431
|
-
startLine: 1,
|
|
2432
|
-
endLine: lines.length,
|
|
2433
|
-
startColumn: 0,
|
|
2434
|
-
endColumn: lines[lines.length - 1]?.length || 0,
|
|
2435
|
-
updatedAt: Date.now(),
|
|
2436
|
-
};
|
|
2437
|
-
this.nodes.push(fileNode);
|
|
2438
|
-
return fileNode;
|
|
2439
|
-
}
|
|
2440
|
-
/**
|
|
2441
|
-
* Extract {% render 'snippet' %} and {% include 'snippet' %} references
|
|
2442
|
-
*/
|
|
2443
|
-
extractSnippetReferences(fileNodeId) {
|
|
2444
|
-
// Match {% render 'name' %} or {% include 'name' %} with optional parameters
|
|
2445
|
-
const renderRegex = /\{%[-]?\s*(render|include)\s+['"]([^'"]+)['"]/g;
|
|
2446
|
-
let match;
|
|
2447
|
-
while ((match = renderRegex.exec(this.source)) !== null) {
|
|
2448
|
-
const [fullMatch, tagType, snippetName] = match;
|
|
2449
|
-
const line = this.getLineNumber(match.index);
|
|
2450
|
-
// Create an import node for searchability
|
|
2451
|
-
const importNodeId = generateNodeId(this.filePath, 'import', snippetName, line);
|
|
2452
|
-
const importNode = {
|
|
2453
|
-
id: importNodeId,
|
|
2454
|
-
kind: 'import',
|
|
2455
|
-
name: snippetName,
|
|
2456
|
-
qualifiedName: `${this.filePath}::import:${snippetName}`,
|
|
2457
|
-
filePath: this.filePath,
|
|
2458
|
-
language: 'liquid',
|
|
2459
|
-
signature: fullMatch,
|
|
2460
|
-
startLine: line,
|
|
2461
|
-
endLine: line,
|
|
2462
|
-
startColumn: match.index - this.getLineStart(line),
|
|
2463
|
-
endColumn: match.index - this.getLineStart(line) + fullMatch.length,
|
|
2464
|
-
updatedAt: Date.now(),
|
|
2465
|
-
};
|
|
2466
|
-
this.nodes.push(importNode);
|
|
2467
|
-
// Add containment edge from file to import
|
|
2468
|
-
this.edges.push({
|
|
2469
|
-
source: fileNodeId,
|
|
2470
|
-
target: importNodeId,
|
|
2471
|
-
kind: 'contains',
|
|
2472
|
-
});
|
|
2473
|
-
// Create a component node for the snippet reference
|
|
2474
|
-
const nodeId = generateNodeId(this.filePath, 'component', `${tagType}:${snippetName}`, line);
|
|
2475
|
-
const node = {
|
|
2476
|
-
id: nodeId,
|
|
2477
|
-
kind: 'component',
|
|
2478
|
-
name: snippetName,
|
|
2479
|
-
qualifiedName: `${this.filePath}::${tagType}:${snippetName}`,
|
|
2480
|
-
filePath: this.filePath,
|
|
2481
|
-
language: 'liquid',
|
|
2482
|
-
startLine: line,
|
|
2483
|
-
endLine: line,
|
|
2484
|
-
startColumn: match.index - this.getLineStart(line),
|
|
2485
|
-
endColumn: match.index - this.getLineStart(line) + fullMatch.length,
|
|
2486
|
-
updatedAt: Date.now(),
|
|
2487
|
-
};
|
|
2488
|
-
this.nodes.push(node);
|
|
2489
|
-
// Add containment edge from file
|
|
2490
|
-
this.edges.push({
|
|
2491
|
-
source: fileNodeId,
|
|
2492
|
-
target: nodeId,
|
|
2493
|
-
kind: 'contains',
|
|
2494
|
-
});
|
|
2495
|
-
// Add unresolved reference to the snippet file
|
|
2496
|
-
this.unresolvedReferences.push({
|
|
2497
|
-
fromNodeId: fileNodeId,
|
|
2498
|
-
referenceName: `snippets/${snippetName}.liquid`,
|
|
2499
|
-
referenceKind: 'references',
|
|
2500
|
-
line,
|
|
2501
|
-
column: match.index - this.getLineStart(line),
|
|
2502
|
-
});
|
|
2503
|
-
}
|
|
2504
|
-
}
|
|
2505
|
-
/**
|
|
2506
|
-
* Extract {% section 'name' %} references
|
|
2507
|
-
*/
|
|
2508
|
-
extractSectionReferences(fileNodeId) {
|
|
2509
|
-
// Match {% section 'name' %}
|
|
2510
|
-
const sectionRegex = /\{%[-]?\s*section\s+['"]([^'"]+)['"]/g;
|
|
2511
|
-
let match;
|
|
2512
|
-
while ((match = sectionRegex.exec(this.source)) !== null) {
|
|
2513
|
-
const [fullMatch, sectionName] = match;
|
|
2514
|
-
const line = this.getLineNumber(match.index);
|
|
2515
|
-
// Create an import node for searchability
|
|
2516
|
-
const importNodeId = generateNodeId(this.filePath, 'import', sectionName, line);
|
|
2517
|
-
const importNode = {
|
|
2518
|
-
id: importNodeId,
|
|
2519
|
-
kind: 'import',
|
|
2520
|
-
name: sectionName,
|
|
2521
|
-
qualifiedName: `${this.filePath}::import:${sectionName}`,
|
|
2522
|
-
filePath: this.filePath,
|
|
2523
|
-
language: 'liquid',
|
|
2524
|
-
signature: fullMatch,
|
|
2525
|
-
startLine: line,
|
|
2526
|
-
endLine: line,
|
|
2527
|
-
startColumn: match.index - this.getLineStart(line),
|
|
2528
|
-
endColumn: match.index - this.getLineStart(line) + fullMatch.length,
|
|
2529
|
-
updatedAt: Date.now(),
|
|
2530
|
-
};
|
|
2531
|
-
this.nodes.push(importNode);
|
|
2532
|
-
// Add containment edge from file to import
|
|
2533
|
-
this.edges.push({
|
|
2534
|
-
source: fileNodeId,
|
|
2535
|
-
target: importNodeId,
|
|
2536
|
-
kind: 'contains',
|
|
2537
|
-
});
|
|
2538
|
-
// Create a component node for the section reference
|
|
2539
|
-
const nodeId = generateNodeId(this.filePath, 'component', `section:${sectionName}`, line);
|
|
2540
|
-
const node = {
|
|
2541
|
-
id: nodeId,
|
|
2542
|
-
kind: 'component',
|
|
2543
|
-
name: sectionName,
|
|
2544
|
-
qualifiedName: `${this.filePath}::section:${sectionName}`,
|
|
2545
|
-
filePath: this.filePath,
|
|
2546
|
-
language: 'liquid',
|
|
2547
|
-
startLine: line,
|
|
2548
|
-
endLine: line,
|
|
2549
|
-
startColumn: match.index - this.getLineStart(line),
|
|
2550
|
-
endColumn: match.index - this.getLineStart(line) + fullMatch.length,
|
|
2551
|
-
updatedAt: Date.now(),
|
|
2552
|
-
};
|
|
2553
|
-
this.nodes.push(node);
|
|
2554
|
-
// Add containment edge from file
|
|
2555
|
-
this.edges.push({
|
|
2556
|
-
source: fileNodeId,
|
|
2557
|
-
target: nodeId,
|
|
2558
|
-
kind: 'contains',
|
|
2559
|
-
});
|
|
2560
|
-
// Add unresolved reference to the section file
|
|
2561
|
-
this.unresolvedReferences.push({
|
|
2562
|
-
fromNodeId: fileNodeId,
|
|
2563
|
-
referenceName: `sections/${sectionName}.liquid`,
|
|
2564
|
-
referenceKind: 'references',
|
|
2565
|
-
line,
|
|
2566
|
-
column: match.index - this.getLineStart(line),
|
|
2567
|
-
});
|
|
2568
|
-
}
|
|
2569
|
-
}
|
|
2570
|
-
/**
|
|
2571
|
-
* Extract {% schema %}...{% endschema %} blocks
|
|
2572
|
-
*/
|
|
2573
|
-
extractSchema(fileNodeId) {
|
|
2574
|
-
// Match {% schema %}...{% endschema %}
|
|
2575
|
-
const schemaRegex = /\{%[-]?\s*schema\s*[-]?%\}([\s\S]*?)\{%[-]?\s*endschema\s*[-]?%\}/g;
|
|
2576
|
-
let match;
|
|
2577
|
-
while ((match = schemaRegex.exec(this.source)) !== null) {
|
|
2578
|
-
const [fullMatch, schemaContent] = match;
|
|
2579
|
-
const startLine = this.getLineNumber(match.index);
|
|
2580
|
-
const endLine = this.getLineNumber(match.index + fullMatch.length);
|
|
2581
|
-
// Try to parse the schema JSON to get the name
|
|
2582
|
-
let schemaName = 'schema';
|
|
2583
|
-
try {
|
|
2584
|
-
const schemaJson = JSON.parse(schemaContent);
|
|
2585
|
-
if (schemaJson.name) {
|
|
2586
|
-
schemaName = schemaJson.name;
|
|
2587
|
-
}
|
|
2588
|
-
}
|
|
2589
|
-
catch {
|
|
2590
|
-
// Schema isn't valid JSON, use default name
|
|
2591
|
-
}
|
|
2592
|
-
// Create a node for the schema
|
|
2593
|
-
const nodeId = generateNodeId(this.filePath, 'constant', `schema:${schemaName}`, startLine);
|
|
2594
|
-
const node = {
|
|
2595
|
-
id: nodeId,
|
|
2596
|
-
kind: 'constant',
|
|
2597
|
-
name: schemaName,
|
|
2598
|
-
qualifiedName: `${this.filePath}::schema:${schemaName}`,
|
|
2599
|
-
filePath: this.filePath,
|
|
2600
|
-
language: 'liquid',
|
|
2601
|
-
startLine,
|
|
2602
|
-
endLine,
|
|
2603
|
-
startColumn: match.index - this.getLineStart(startLine),
|
|
2604
|
-
endColumn: 0,
|
|
2605
|
-
docstring: schemaContent?.trim().substring(0, 200), // Store first 200 chars as docstring
|
|
2606
|
-
updatedAt: Date.now(),
|
|
2607
|
-
};
|
|
2608
|
-
this.nodes.push(node);
|
|
2609
|
-
// Add containment edge from file
|
|
2610
|
-
this.edges.push({
|
|
2611
|
-
source: fileNodeId,
|
|
2612
|
-
target: nodeId,
|
|
2613
|
-
kind: 'contains',
|
|
2614
|
-
});
|
|
2615
|
-
}
|
|
2616
|
-
}
|
|
2617
|
-
/**
|
|
2618
|
-
* Extract {% assign var = value %} statements
|
|
2619
|
-
*/
|
|
2620
|
-
extractAssignments(fileNodeId) {
|
|
2621
|
-
// Match {% assign variable_name = ... %}
|
|
2622
|
-
const assignRegex = /\{%[-]?\s*assign\s+(\w+)\s*=/g;
|
|
2623
|
-
let match;
|
|
2624
|
-
while ((match = assignRegex.exec(this.source)) !== null) {
|
|
2625
|
-
const [, variableName] = match;
|
|
2626
|
-
const line = this.getLineNumber(match.index);
|
|
2627
|
-
// Create a variable node
|
|
2628
|
-
const nodeId = generateNodeId(this.filePath, 'variable', variableName, line);
|
|
2629
|
-
const node = {
|
|
2630
|
-
id: nodeId,
|
|
2631
|
-
kind: 'variable',
|
|
2632
|
-
name: variableName,
|
|
2633
|
-
qualifiedName: `${this.filePath}::${variableName}`,
|
|
2634
|
-
filePath: this.filePath,
|
|
2635
|
-
language: 'liquid',
|
|
2636
|
-
startLine: line,
|
|
2637
|
-
endLine: line,
|
|
2638
|
-
startColumn: match.index - this.getLineStart(line),
|
|
2639
|
-
endColumn: match.index - this.getLineStart(line) + match[0].length,
|
|
2640
|
-
updatedAt: Date.now(),
|
|
2641
|
-
};
|
|
2642
|
-
this.nodes.push(node);
|
|
2643
|
-
// Add containment edge from file
|
|
2644
|
-
this.edges.push({
|
|
2645
|
-
source: fileNodeId,
|
|
2646
|
-
target: nodeId,
|
|
2647
|
-
kind: 'contains',
|
|
2648
|
-
});
|
|
2649
|
-
}
|
|
2650
|
-
}
|
|
2651
|
-
/**
|
|
2652
|
-
* Get the line number for a character index
|
|
2653
|
-
*/
|
|
2654
|
-
getLineNumber(index) {
|
|
2655
|
-
const substring = this.source.substring(0, index);
|
|
2656
|
-
return (substring.match(/\n/g) || []).length + 1;
|
|
2657
|
-
}
|
|
2658
|
-
/**
|
|
2659
|
-
* Get the character index of the start of a line
|
|
2660
|
-
*/
|
|
2661
|
-
getLineStart(lineNumber) {
|
|
2662
|
-
const lines = this.source.split('\n');
|
|
2663
|
-
let index = 0;
|
|
2664
|
-
for (let i = 0; i < lineNumber - 1 && i < lines.length; i++) {
|
|
2665
|
-
index += lines[i].length + 1; // +1 for newline
|
|
2666
|
-
}
|
|
2667
|
-
return index;
|
|
2668
|
-
}
|
|
2669
|
-
}
|
|
2670
|
-
exports.LiquidExtractor = LiquidExtractor;
|
|
2671
|
-
/**
|
|
2672
|
-
* SvelteExtractor - Extracts code relationships from Svelte component files
|
|
2673
|
-
*
|
|
2674
|
-
* Svelte files are multi-language (script + template + style). Rather than
|
|
2675
|
-
* parsing the full Svelte grammar, we extract the <script> block content
|
|
2676
|
-
* and delegate it to the TypeScript/JavaScript TreeSitterExtractor.
|
|
2677
|
-
*
|
|
2678
|
-
* Every .svelte file produces a component node (Svelte components are always importable).
|
|
2679
|
-
*/
|
|
2680
|
-
class SvelteExtractor {
|
|
2681
|
-
filePath;
|
|
2682
|
-
source;
|
|
2683
|
-
nodes = [];
|
|
2684
|
-
edges = [];
|
|
2685
|
-
unresolvedReferences = [];
|
|
2686
|
-
errors = [];
|
|
2687
|
-
constructor(filePath, source) {
|
|
2688
|
-
this.filePath = filePath;
|
|
2689
|
-
this.source = source;
|
|
2690
|
-
}
|
|
2691
|
-
/**
|
|
2692
|
-
* Extract from Svelte source
|
|
2693
|
-
*/
|
|
2694
|
-
extract() {
|
|
2695
|
-
const startTime = Date.now();
|
|
2696
|
-
try {
|
|
2697
|
-
// Create component node for the .svelte file itself
|
|
2698
|
-
const componentNode = this.createComponentNode();
|
|
2699
|
-
// Extract and process script blocks
|
|
2700
|
-
const scriptBlocks = this.extractScriptBlocks();
|
|
2701
|
-
for (const block of scriptBlocks) {
|
|
2702
|
-
this.processScriptBlock(block, componentNode.id);
|
|
2703
|
-
}
|
|
2704
|
-
}
|
|
2705
|
-
catch (error) {
|
|
2706
|
-
this.errors.push({
|
|
2707
|
-
message: `Svelte extraction error: ${error instanceof Error ? error.message : String(error)}`,
|
|
2708
|
-
severity: 'error',
|
|
2709
|
-
});
|
|
2710
|
-
}
|
|
2711
|
-
return {
|
|
2712
|
-
nodes: this.nodes,
|
|
2713
|
-
edges: this.edges,
|
|
2714
|
-
unresolvedReferences: this.unresolvedReferences,
|
|
2715
|
-
errors: this.errors,
|
|
2716
|
-
durationMs: Date.now() - startTime,
|
|
2717
|
-
};
|
|
2718
|
-
}
|
|
2719
|
-
/**
|
|
2720
|
-
* Create a component node for the .svelte file
|
|
2721
|
-
*/
|
|
2722
|
-
createComponentNode() {
|
|
2723
|
-
const lines = this.source.split('\n');
|
|
2724
|
-
const fileName = this.filePath.split(/[/\\]/).pop() || this.filePath;
|
|
2725
|
-
const componentName = fileName.replace(/\.svelte$/, '');
|
|
2726
|
-
const id = generateNodeId(this.filePath, 'component', componentName, 1);
|
|
2727
|
-
const node = {
|
|
2728
|
-
id,
|
|
2729
|
-
kind: 'component',
|
|
2730
|
-
name: componentName,
|
|
2731
|
-
qualifiedName: `${this.filePath}::${componentName}`,
|
|
2732
|
-
filePath: this.filePath,
|
|
2733
|
-
language: 'svelte',
|
|
2734
|
-
startLine: 1,
|
|
2735
|
-
endLine: lines.length,
|
|
2736
|
-
startColumn: 0,
|
|
2737
|
-
endColumn: lines[lines.length - 1]?.length || 0,
|
|
2738
|
-
isExported: true, // Svelte components are always importable
|
|
2739
|
-
updatedAt: Date.now(),
|
|
2740
|
-
};
|
|
2741
|
-
this.nodes.push(node);
|
|
2742
|
-
return node;
|
|
2743
|
-
}
|
|
2744
|
-
/**
|
|
2745
|
-
* Extract <script> blocks from the Svelte source
|
|
2746
|
-
*/
|
|
2747
|
-
extractScriptBlocks() {
|
|
2748
|
-
const blocks = [];
|
|
2749
|
-
const scriptRegex = /<script(\s[^>]*)?>(?<content>[\s\S]*?)<\/script>/g;
|
|
2750
|
-
let match;
|
|
2751
|
-
while ((match = scriptRegex.exec(this.source)) !== null) {
|
|
2752
|
-
const attrs = match[1] || '';
|
|
2753
|
-
const content = match.groups?.content || match[2] || '';
|
|
2754
|
-
// Detect TypeScript from lang attribute
|
|
2755
|
-
const isTypeScript = /lang\s*=\s*["'](ts|typescript)["']/.test(attrs);
|
|
2756
|
-
// Detect module script
|
|
2757
|
-
const isModule = /context\s*=\s*["']module["']/.test(attrs);
|
|
2758
|
-
// Calculate start line of the script content (line after <script>)
|
|
2759
|
-
const beforeScript = this.source.substring(0, match.index);
|
|
2760
|
-
const scriptTagLine = (beforeScript.match(/\n/g) || []).length;
|
|
2761
|
-
// The content starts on the line after the opening <script> tag
|
|
2762
|
-
const openingTag = match[0].substring(0, match[0].indexOf('>') + 1);
|
|
2763
|
-
const openingTagLines = (openingTag.match(/\n/g) || []).length;
|
|
2764
|
-
const contentStartLine = scriptTagLine + openingTagLines + 1; // 0-indexed line
|
|
2765
|
-
blocks.push({
|
|
2766
|
-
content,
|
|
2767
|
-
startLine: contentStartLine,
|
|
2768
|
-
isModule,
|
|
2769
|
-
isTypeScript,
|
|
2770
|
-
});
|
|
2771
|
-
}
|
|
2772
|
-
return blocks;
|
|
2773
|
-
}
|
|
2774
|
-
/**
|
|
2775
|
-
* Process a script block by delegating to TreeSitterExtractor
|
|
2776
|
-
*/
|
|
2777
|
-
processScriptBlock(block, componentNodeId) {
|
|
2778
|
-
const scriptLanguage = block.isTypeScript ? 'typescript' : 'javascript';
|
|
2779
|
-
// Check if the script language parser is available
|
|
2780
|
-
if (!(0, grammars_1.isLanguageSupported)(scriptLanguage)) {
|
|
2781
|
-
this.errors.push({
|
|
2782
|
-
message: `Parser for ${scriptLanguage} not available, cannot parse Svelte script block`,
|
|
2783
|
-
severity: 'warning',
|
|
2784
|
-
});
|
|
2785
|
-
return;
|
|
2786
|
-
}
|
|
2787
|
-
// Delegate to TreeSitterExtractor
|
|
2788
|
-
const extractor = new TreeSitterExtractor(this.filePath, block.content, scriptLanguage);
|
|
2789
|
-
const result = extractor.extract();
|
|
2790
|
-
// Offset line numbers from script block back to .svelte file positions
|
|
2791
|
-
for (const node of result.nodes) {
|
|
2792
|
-
node.startLine += block.startLine;
|
|
2793
|
-
node.endLine += block.startLine;
|
|
2794
|
-
node.language = 'svelte'; // Mark as svelte, not TS/JS
|
|
2795
|
-
this.nodes.push(node);
|
|
2796
|
-
// Add containment edge from component to this node
|
|
2797
|
-
this.edges.push({
|
|
2798
|
-
source: componentNodeId,
|
|
2799
|
-
target: node.id,
|
|
2800
|
-
kind: 'contains',
|
|
2801
|
-
});
|
|
2802
|
-
}
|
|
2803
|
-
// Offset edges (they reference line numbers)
|
|
2804
|
-
for (const edge of result.edges) {
|
|
2805
|
-
if (edge.line) {
|
|
2806
|
-
edge.line += block.startLine;
|
|
2807
|
-
}
|
|
2808
|
-
this.edges.push(edge);
|
|
2809
|
-
}
|
|
2810
|
-
// Offset unresolved references
|
|
2811
|
-
for (const ref of result.unresolvedReferences) {
|
|
2812
|
-
ref.line += block.startLine;
|
|
2813
|
-
ref.filePath = this.filePath;
|
|
2814
|
-
ref.language = 'svelte';
|
|
2815
|
-
this.unresolvedReferences.push(ref);
|
|
2816
|
-
}
|
|
2817
|
-
// Carry over errors
|
|
2818
|
-
for (const error of result.errors) {
|
|
2819
|
-
if (error.line) {
|
|
2820
|
-
error.line += block.startLine;
|
|
2821
|
-
}
|
|
2822
|
-
this.errors.push(error);
|
|
2823
|
-
}
|
|
2824
|
-
}
|
|
2825
|
-
}
|
|
2826
|
-
exports.SvelteExtractor = SvelteExtractor;
|
|
2827
|
-
/**
|
|
2828
|
-
* Custom extractor for Delphi DFM/FMX form files.
|
|
2829
|
-
*
|
|
2830
|
-
* DFM/FMX files describe the visual component hierarchy and event handler
|
|
2831
|
-
* bindings. They use a simple text format (object/end blocks) that we parse
|
|
2832
|
-
* with regex — no tree-sitter grammar exists for this format.
|
|
2833
|
-
*
|
|
2834
|
-
* Extracted information:
|
|
2835
|
-
* - Components as NodeKind `component`
|
|
2836
|
-
* - Nesting as EdgeKind `contains`
|
|
2837
|
-
* - Event handlers (OnClick = MethodName) as UnresolvedReference → EdgeKind `references`
|
|
2838
|
-
*/
|
|
2839
|
-
class DfmExtractor {
|
|
2840
|
-
filePath;
|
|
2841
|
-
source;
|
|
2842
|
-
nodes = [];
|
|
2843
|
-
edges = [];
|
|
2844
|
-
unresolvedReferences = [];
|
|
2845
|
-
errors = [];
|
|
2846
|
-
constructor(filePath, source) {
|
|
2847
|
-
this.filePath = filePath;
|
|
2848
|
-
this.source = source;
|
|
2849
|
-
}
|
|
2850
|
-
/**
|
|
2851
|
-
* Extract components and event handler references from DFM/FMX source
|
|
2852
|
-
*/
|
|
2853
|
-
extract() {
|
|
2854
|
-
const startTime = Date.now();
|
|
2855
|
-
try {
|
|
2856
|
-
const fileNode = this.createFileNode();
|
|
2857
|
-
this.parseComponents(fileNode.id);
|
|
2858
|
-
}
|
|
2859
|
-
catch (error) {
|
|
2860
|
-
this.errors.push({
|
|
2861
|
-
message: `DFM extraction error: ${error instanceof Error ? error.message : String(error)}`,
|
|
2862
|
-
severity: 'error',
|
|
2863
|
-
});
|
|
2864
|
-
}
|
|
2865
|
-
return {
|
|
2866
|
-
nodes: this.nodes,
|
|
2867
|
-
edges: this.edges,
|
|
2868
|
-
unresolvedReferences: this.unresolvedReferences,
|
|
2869
|
-
errors: this.errors,
|
|
2870
|
-
durationMs: Date.now() - startTime,
|
|
2871
|
-
};
|
|
2872
|
-
}
|
|
2873
|
-
/** Create a file node for the DFM form file */
|
|
2874
|
-
createFileNode() {
|
|
2875
|
-
const lines = this.source.split('\n');
|
|
2876
|
-
const id = generateNodeId(this.filePath, 'file', this.filePath, 1);
|
|
2877
|
-
const fileNode = {
|
|
2878
|
-
id,
|
|
2879
|
-
kind: 'file',
|
|
2880
|
-
name: this.filePath.split('/').pop() || this.filePath,
|
|
2881
|
-
qualifiedName: this.filePath,
|
|
2882
|
-
filePath: this.filePath,
|
|
2883
|
-
language: 'pascal',
|
|
2884
|
-
startLine: 1,
|
|
2885
|
-
endLine: lines.length,
|
|
2886
|
-
startColumn: 0,
|
|
2887
|
-
endColumn: lines[lines.length - 1]?.length || 0,
|
|
2888
|
-
updatedAt: Date.now(),
|
|
2889
|
-
};
|
|
2890
|
-
this.nodes.push(fileNode);
|
|
2891
|
-
return fileNode;
|
|
2892
|
-
}
|
|
2893
|
-
/** Parse object/end blocks and extract components + event handlers */
|
|
2894
|
-
parseComponents(fileNodeId) {
|
|
2895
|
-
const lines = this.source.split('\n');
|
|
2896
|
-
const stack = [fileNodeId];
|
|
2897
|
-
const objectPattern = /^\s*(object|inherited|inline)\s+(\w+)\s*:\s*(\w+)/;
|
|
2898
|
-
const eventPattern = /^\s*(On\w+)\s*=\s*(\w+)\s*$/;
|
|
2899
|
-
const endPattern = /^\s*end\s*$/;
|
|
2900
|
-
const multiLineStart = /=\s*\(\s*$/;
|
|
2901
|
-
const multiLineItemStart = /=\s*<\s*$/;
|
|
2902
|
-
let inMultiLine = false;
|
|
2903
|
-
let multiLineEndChar = ')';
|
|
2904
|
-
for (let i = 0; i < lines.length; i++) {
|
|
2905
|
-
const line = lines[i];
|
|
2906
|
-
const lineNum = i + 1;
|
|
2907
|
-
// Skip multi-line properties
|
|
2908
|
-
if (inMultiLine) {
|
|
2909
|
-
if (line.trimEnd().endsWith(multiLineEndChar))
|
|
2910
|
-
inMultiLine = false;
|
|
2911
|
-
continue;
|
|
2912
|
-
}
|
|
2913
|
-
if (multiLineStart.test(line)) {
|
|
2914
|
-
inMultiLine = true;
|
|
2915
|
-
multiLineEndChar = ')';
|
|
2916
|
-
continue;
|
|
2917
|
-
}
|
|
2918
|
-
if (multiLineItemStart.test(line)) {
|
|
2919
|
-
inMultiLine = true;
|
|
2920
|
-
multiLineEndChar = '>';
|
|
2921
|
-
continue;
|
|
2922
|
-
}
|
|
2923
|
-
// Component declaration
|
|
2924
|
-
const objMatch = line.match(objectPattern);
|
|
2925
|
-
if (objMatch) {
|
|
2926
|
-
const [, , name, typeName] = objMatch;
|
|
2927
|
-
const nodeId = generateNodeId(this.filePath, 'component', name, lineNum);
|
|
2928
|
-
this.nodes.push({
|
|
2929
|
-
id: nodeId,
|
|
2930
|
-
kind: 'component',
|
|
2931
|
-
name: name,
|
|
2932
|
-
qualifiedName: `${this.filePath}#${name}`,
|
|
2933
|
-
filePath: this.filePath,
|
|
2934
|
-
language: 'pascal',
|
|
2935
|
-
startLine: lineNum,
|
|
2936
|
-
endLine: lineNum,
|
|
2937
|
-
startColumn: 0,
|
|
2938
|
-
endColumn: line.length,
|
|
2939
|
-
signature: typeName,
|
|
2940
|
-
updatedAt: Date.now(),
|
|
2941
|
-
});
|
|
2942
|
-
this.edges.push({
|
|
2943
|
-
source: stack[stack.length - 1],
|
|
2944
|
-
target: nodeId,
|
|
2945
|
-
kind: 'contains',
|
|
2946
|
-
});
|
|
2947
|
-
stack.push(nodeId);
|
|
2948
|
-
continue;
|
|
2949
|
-
}
|
|
2950
|
-
// Event handler
|
|
2951
|
-
const eventMatch = line.match(eventPattern);
|
|
2952
|
-
if (eventMatch) {
|
|
2953
|
-
const [, , methodName] = eventMatch;
|
|
2954
|
-
this.unresolvedReferences.push({
|
|
2955
|
-
fromNodeId: stack[stack.length - 1],
|
|
2956
|
-
referenceName: methodName,
|
|
2957
|
-
referenceKind: 'references',
|
|
2958
|
-
line: lineNum,
|
|
2959
|
-
column: 0,
|
|
2960
|
-
});
|
|
2961
|
-
continue;
|
|
2962
|
-
}
|
|
2963
|
-
// Block end
|
|
2964
|
-
if (endPattern.test(line)) {
|
|
2965
|
-
if (stack.length > 1)
|
|
2966
|
-
stack.pop();
|
|
2967
|
-
}
|
|
2968
|
-
}
|
|
2969
|
-
}
|
|
2970
|
-
}
|
|
2971
|
-
exports.DfmExtractor = DfmExtractor;
|
|
2972
2169
|
/**
|
|
2973
2170
|
* Extract nodes and edges from source code
|
|
2974
2171
|
*/
|
|
2975
2172
|
function extractFromSource(filePath, source, language) {
|
|
2976
|
-
const detectedLanguage = language || (0, grammars_1.detectLanguage)(filePath);
|
|
2173
|
+
const detectedLanguage = language || (0, grammars_1.detectLanguage)(filePath, source);
|
|
2977
2174
|
const fileExtension = path.extname(filePath).toLowerCase();
|
|
2978
2175
|
// Use custom extractor for Svelte
|
|
2979
2176
|
if (detectedLanguage === 'svelte') {
|
|
2980
|
-
const extractor = new SvelteExtractor(filePath, source);
|
|
2177
|
+
const extractor = new svelte_extractor_1.SvelteExtractor(filePath, source);
|
|
2981
2178
|
return extractor.extract();
|
|
2982
2179
|
}
|
|
2983
2180
|
// Use custom extractor for Liquid
|
|
2984
2181
|
if (detectedLanguage === 'liquid') {
|
|
2985
|
-
const extractor = new LiquidExtractor(filePath, source);
|
|
2182
|
+
const extractor = new liquid_extractor_1.LiquidExtractor(filePath, source);
|
|
2986
2183
|
return extractor.extract();
|
|
2987
2184
|
}
|
|
2988
2185
|
// Use custom extractor for DFM/FMX form files
|
|
2989
2186
|
if (detectedLanguage === 'pascal' &&
|
|
2990
2187
|
(fileExtension === '.dfm' || fileExtension === '.fmx')) {
|
|
2991
|
-
const extractor = new DfmExtractor(filePath, source);
|
|
2188
|
+
const extractor = new dfm_extractor_1.DfmExtractor(filePath, source);
|
|
2992
2189
|
return extractor.extract();
|
|
2993
2190
|
}
|
|
2994
2191
|
const extractor = new TreeSitterExtractor(filePath, source, detectedLanguage);
|