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