@optave/codegraph 1.1.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/LICENSE +190 -0
- package/README.md +311 -0
- package/grammars/tree-sitter-hcl.wasm +0 -0
- package/grammars/tree-sitter-javascript.wasm +0 -0
- package/grammars/tree-sitter-python.wasm +0 -0
- package/grammars/tree-sitter-tsx.wasm +0 -0
- package/grammars/tree-sitter-typescript.wasm +0 -0
- package/package.json +69 -0
- package/src/builder.js +547 -0
- package/src/cli.js +224 -0
- package/src/config.js +55 -0
- package/src/constants.js +28 -0
- package/src/cycles.js +104 -0
- package/src/db.js +117 -0
- package/src/embedder.js +330 -0
- package/src/export.js +138 -0
- package/src/index.js +39 -0
- package/src/logger.js +20 -0
- package/src/mcp.js +139 -0
- package/src/parser.js +573 -0
- package/src/queries.js +616 -0
- package/src/watcher.js +213 -0
package/src/parser.js
ADDED
|
@@ -0,0 +1,573 @@
|
|
|
1
|
+
import { fileURLToPath } from 'node:url';
|
|
2
|
+
import path from 'node:path';
|
|
3
|
+
import { Parser, Language } from 'web-tree-sitter';
|
|
4
|
+
import { warn, debug } from './logger.js';
|
|
5
|
+
|
|
6
|
+
const __dirname = path.dirname(fileURLToPath(import.meta.url));
|
|
7
|
+
|
|
8
|
+
function grammarPath(name) {
|
|
9
|
+
return path.join(__dirname, '..', 'grammars', name);
|
|
10
|
+
}
|
|
11
|
+
|
|
12
|
+
let _initialized = false;
|
|
13
|
+
|
|
14
|
+
export async function createParsers() {
|
|
15
|
+
if (!_initialized) {
|
|
16
|
+
await Parser.init();
|
|
17
|
+
_initialized = true;
|
|
18
|
+
}
|
|
19
|
+
|
|
20
|
+
const JavaScript = await Language.load(grammarPath('tree-sitter-javascript.wasm'));
|
|
21
|
+
const TypeScript = await Language.load(grammarPath('tree-sitter-typescript.wasm'));
|
|
22
|
+
const TSX = await Language.load(grammarPath('tree-sitter-tsx.wasm'));
|
|
23
|
+
|
|
24
|
+
const jsParser = new Parser();
|
|
25
|
+
jsParser.setLanguage(JavaScript);
|
|
26
|
+
|
|
27
|
+
const tsParser = new Parser();
|
|
28
|
+
tsParser.setLanguage(TypeScript);
|
|
29
|
+
|
|
30
|
+
const tsxParser = new Parser();
|
|
31
|
+
tsxParser.setLanguage(TSX);
|
|
32
|
+
|
|
33
|
+
let hclParser = null;
|
|
34
|
+
try {
|
|
35
|
+
const HCL = await Language.load(grammarPath('tree-sitter-hcl.wasm'));
|
|
36
|
+
hclParser = new Parser();
|
|
37
|
+
hclParser.setLanguage(HCL);
|
|
38
|
+
} catch (e) {
|
|
39
|
+
warn(`HCL parser failed to initialize: ${e.message}. HCL files will be skipped.`);
|
|
40
|
+
}
|
|
41
|
+
|
|
42
|
+
let pyParser = null;
|
|
43
|
+
try {
|
|
44
|
+
const Python = await Language.load(grammarPath('tree-sitter-python.wasm'));
|
|
45
|
+
pyParser = new Parser();
|
|
46
|
+
pyParser.setLanguage(Python);
|
|
47
|
+
} catch (e) {
|
|
48
|
+
warn(`Python parser failed to initialize: ${e.message}. Python files will be skipped.`);
|
|
49
|
+
}
|
|
50
|
+
|
|
51
|
+
return { jsParser, tsParser, tsxParser, hclParser, pyParser };
|
|
52
|
+
}
|
|
53
|
+
|
|
54
|
+
export function getParser(parsers, filePath) {
|
|
55
|
+
if (filePath.endsWith('.tsx')) return parsers.tsxParser;
|
|
56
|
+
if (filePath.endsWith('.ts') || filePath.endsWith('.d.ts')) return parsers.tsParser;
|
|
57
|
+
if (filePath.endsWith('.js') || filePath.endsWith('.jsx') || filePath.endsWith('.mjs') || filePath.endsWith('.cjs'))
|
|
58
|
+
return parsers.jsParser;
|
|
59
|
+
if (filePath.endsWith('.py') && parsers.pyParser) return parsers.pyParser;
|
|
60
|
+
if ((filePath.endsWith('.tf') || filePath.endsWith('.hcl')) && parsers.hclParser)
|
|
61
|
+
return parsers.hclParser;
|
|
62
|
+
return null;
|
|
63
|
+
}
|
|
64
|
+
|
|
65
|
+
function nodeEndLine(node) {
|
|
66
|
+
return node.endPosition.row + 1;
|
|
67
|
+
}
|
|
68
|
+
|
|
69
|
+
/**
|
|
70
|
+
* Extract symbols from a JS/TS parsed AST.
|
|
71
|
+
*/
|
|
72
|
+
export function extractSymbols(tree, filePath) {
|
|
73
|
+
const definitions = [];
|
|
74
|
+
const calls = [];
|
|
75
|
+
const imports = [];
|
|
76
|
+
const classes = [];
|
|
77
|
+
const exports = [];
|
|
78
|
+
|
|
79
|
+
function walk(node) {
|
|
80
|
+
switch (node.type) {
|
|
81
|
+
case 'function_declaration': {
|
|
82
|
+
const nameNode = node.childForFieldName('name');
|
|
83
|
+
if (nameNode) {
|
|
84
|
+
definitions.push({ name: nameNode.text, kind: 'function', line: node.startPosition.row + 1, endLine: nodeEndLine(node) });
|
|
85
|
+
}
|
|
86
|
+
break;
|
|
87
|
+
}
|
|
88
|
+
|
|
89
|
+
case 'class_declaration': {
|
|
90
|
+
const nameNode = node.childForFieldName('name');
|
|
91
|
+
if (nameNode) {
|
|
92
|
+
const cls = { name: nameNode.text, kind: 'class', line: node.startPosition.row + 1, endLine: nodeEndLine(node) };
|
|
93
|
+
definitions.push(cls);
|
|
94
|
+
const heritage = node.childForFieldName('heritage') || findChild(node, 'class_heritage');
|
|
95
|
+
if (heritage) {
|
|
96
|
+
const superName = extractSuperclass(heritage);
|
|
97
|
+
if (superName) {
|
|
98
|
+
classes.push({ name: nameNode.text, extends: superName, line: node.startPosition.row + 1 });
|
|
99
|
+
}
|
|
100
|
+
const implementsList = extractImplements(heritage);
|
|
101
|
+
for (const iface of implementsList) {
|
|
102
|
+
classes.push({ name: nameNode.text, implements: iface, line: node.startPosition.row + 1 });
|
|
103
|
+
}
|
|
104
|
+
}
|
|
105
|
+
}
|
|
106
|
+
break;
|
|
107
|
+
}
|
|
108
|
+
|
|
109
|
+
case 'method_definition': {
|
|
110
|
+
const nameNode = node.childForFieldName('name');
|
|
111
|
+
if (nameNode) {
|
|
112
|
+
let parentClass = findParentClass(node);
|
|
113
|
+
const fullName = parentClass ? `${parentClass}.${nameNode.text}` : nameNode.text;
|
|
114
|
+
definitions.push({ name: fullName, kind: 'method', line: node.startPosition.row + 1, endLine: nodeEndLine(node) });
|
|
115
|
+
}
|
|
116
|
+
break;
|
|
117
|
+
}
|
|
118
|
+
|
|
119
|
+
case 'interface_declaration': {
|
|
120
|
+
const nameNode = node.childForFieldName('name');
|
|
121
|
+
if (nameNode) {
|
|
122
|
+
definitions.push({ name: nameNode.text, kind: 'interface', line: node.startPosition.row + 1, endLine: nodeEndLine(node) });
|
|
123
|
+
const body = node.childForFieldName('body') || findChild(node, 'interface_body') || findChild(node, 'object_type');
|
|
124
|
+
if (body) {
|
|
125
|
+
extractInterfaceMethods(body, nameNode.text, definitions);
|
|
126
|
+
}
|
|
127
|
+
}
|
|
128
|
+
break;
|
|
129
|
+
}
|
|
130
|
+
|
|
131
|
+
case 'type_alias_declaration': {
|
|
132
|
+
const nameNode = node.childForFieldName('name');
|
|
133
|
+
if (nameNode) {
|
|
134
|
+
definitions.push({ name: nameNode.text, kind: 'type', line: node.startPosition.row + 1, endLine: nodeEndLine(node) });
|
|
135
|
+
}
|
|
136
|
+
break;
|
|
137
|
+
}
|
|
138
|
+
|
|
139
|
+
case 'lexical_declaration':
|
|
140
|
+
case 'variable_declaration': {
|
|
141
|
+
for (let i = 0; i < node.childCount; i++) {
|
|
142
|
+
const declarator = node.child(i);
|
|
143
|
+
if (declarator && declarator.type === 'variable_declarator') {
|
|
144
|
+
const nameN = declarator.childForFieldName('name');
|
|
145
|
+
const valueN = declarator.childForFieldName('value');
|
|
146
|
+
if (nameN && valueN && (valueN.type === 'arrow_function' || valueN.type === 'function_expression' || valueN.type === 'function')) {
|
|
147
|
+
definitions.push({ name: nameN.text, kind: 'function', line: node.startPosition.row + 1, endLine: nodeEndLine(valueN) });
|
|
148
|
+
}
|
|
149
|
+
}
|
|
150
|
+
}
|
|
151
|
+
break;
|
|
152
|
+
}
|
|
153
|
+
|
|
154
|
+
case 'call_expression': {
|
|
155
|
+
const fn = node.childForFieldName('function');
|
|
156
|
+
if (fn) {
|
|
157
|
+
const callInfo = extractCallInfo(fn, node);
|
|
158
|
+
if (callInfo) {
|
|
159
|
+
calls.push(callInfo);
|
|
160
|
+
}
|
|
161
|
+
}
|
|
162
|
+
break;
|
|
163
|
+
}
|
|
164
|
+
|
|
165
|
+
case 'import_statement': {
|
|
166
|
+
const isTypeOnly = node.text.startsWith('import type');
|
|
167
|
+
const source = node.childForFieldName('source') || findChild(node, 'string');
|
|
168
|
+
if (source) {
|
|
169
|
+
const modPath = source.text.replace(/['"]/g, '');
|
|
170
|
+
const names = extractImportNames(node);
|
|
171
|
+
imports.push({ source: modPath, names, line: node.startPosition.row + 1, typeOnly: isTypeOnly });
|
|
172
|
+
}
|
|
173
|
+
break;
|
|
174
|
+
}
|
|
175
|
+
|
|
176
|
+
case 'export_statement': {
|
|
177
|
+
const decl = node.childForFieldName('declaration');
|
|
178
|
+
if (decl) {
|
|
179
|
+
if (decl.type === 'function_declaration') {
|
|
180
|
+
const n = decl.childForFieldName('name');
|
|
181
|
+
if (n) exports.push({ name: n.text, kind: 'function', line: node.startPosition.row + 1 });
|
|
182
|
+
} else if (decl.type === 'class_declaration') {
|
|
183
|
+
const n = decl.childForFieldName('name');
|
|
184
|
+
if (n) exports.push({ name: n.text, kind: 'class', line: node.startPosition.row + 1 });
|
|
185
|
+
} else if (decl.type === 'interface_declaration') {
|
|
186
|
+
const n = decl.childForFieldName('name');
|
|
187
|
+
if (n) exports.push({ name: n.text, kind: 'interface', line: node.startPosition.row + 1 });
|
|
188
|
+
} else if (decl.type === 'type_alias_declaration') {
|
|
189
|
+
const n = decl.childForFieldName('name');
|
|
190
|
+
if (n) exports.push({ name: n.text, kind: 'type', line: node.startPosition.row + 1 });
|
|
191
|
+
}
|
|
192
|
+
}
|
|
193
|
+
const source = node.childForFieldName('source') || findChild(node, 'string');
|
|
194
|
+
if (source && !decl) {
|
|
195
|
+
const modPath = source.text.replace(/['"]/g, '');
|
|
196
|
+
const reexportNames = extractImportNames(node);
|
|
197
|
+
const isWildcard = node.text.includes('export *') || node.text.includes('export*');
|
|
198
|
+
imports.push({ source: modPath, names: reexportNames, line: node.startPosition.row + 1, reexport: true, wildcardReexport: isWildcard && reexportNames.length === 0 });
|
|
199
|
+
}
|
|
200
|
+
break;
|
|
201
|
+
}
|
|
202
|
+
|
|
203
|
+
case 'expression_statement': {
|
|
204
|
+
const expr = node.child(0);
|
|
205
|
+
if (expr && expr.type === 'assignment_expression') {
|
|
206
|
+
const left = expr.childForFieldName('left');
|
|
207
|
+
const right = expr.childForFieldName('right');
|
|
208
|
+
if (left && right) {
|
|
209
|
+
const leftText = left.text;
|
|
210
|
+
if (leftText.startsWith('module.exports') || leftText === 'exports') {
|
|
211
|
+
if (right.type === 'call_expression') {
|
|
212
|
+
const fn = right.childForFieldName('function');
|
|
213
|
+
const args = right.childForFieldName('arguments') || findChild(right, 'arguments');
|
|
214
|
+
if (fn && fn.text === 'require' && args) {
|
|
215
|
+
const strArg = findChild(args, 'string');
|
|
216
|
+
if (strArg) {
|
|
217
|
+
const modPath = strArg.text.replace(/['"]/g, '');
|
|
218
|
+
imports.push({ source: modPath, names: [], line: node.startPosition.row + 1, reexport: true, wildcardReexport: true });
|
|
219
|
+
}
|
|
220
|
+
}
|
|
221
|
+
}
|
|
222
|
+
if (right.type === 'object') {
|
|
223
|
+
for (let ci = 0; ci < right.childCount; ci++) {
|
|
224
|
+
const child = right.child(ci);
|
|
225
|
+
if (child && child.type === 'spread_element') {
|
|
226
|
+
const spreadExpr = child.child(1) || child.childForFieldName('value');
|
|
227
|
+
if (spreadExpr && spreadExpr.type === 'call_expression') {
|
|
228
|
+
const fn2 = spreadExpr.childForFieldName('function');
|
|
229
|
+
const args2 = spreadExpr.childForFieldName('arguments') || findChild(spreadExpr, 'arguments');
|
|
230
|
+
if (fn2 && fn2.text === 'require' && args2) {
|
|
231
|
+
const strArg2 = findChild(args2, 'string');
|
|
232
|
+
if (strArg2) {
|
|
233
|
+
const modPath2 = strArg2.text.replace(/['"]/g, '');
|
|
234
|
+
imports.push({ source: modPath2, names: [], line: node.startPosition.row + 1, reexport: true, wildcardReexport: true });
|
|
235
|
+
}
|
|
236
|
+
}
|
|
237
|
+
}
|
|
238
|
+
}
|
|
239
|
+
}
|
|
240
|
+
}
|
|
241
|
+
}
|
|
242
|
+
}
|
|
243
|
+
}
|
|
244
|
+
break;
|
|
245
|
+
}
|
|
246
|
+
}
|
|
247
|
+
|
|
248
|
+
for (let i = 0; i < node.childCount; i++) {
|
|
249
|
+
walk(node.child(i));
|
|
250
|
+
}
|
|
251
|
+
}
|
|
252
|
+
|
|
253
|
+
walk(tree.rootNode);
|
|
254
|
+
return { definitions, calls, imports, classes, exports };
|
|
255
|
+
}
|
|
256
|
+
|
|
257
|
+
function extractInterfaceMethods(bodyNode, interfaceName, definitions) {
|
|
258
|
+
for (let i = 0; i < bodyNode.childCount; i++) {
|
|
259
|
+
const child = bodyNode.child(i);
|
|
260
|
+
if (!child) continue;
|
|
261
|
+
if (child.type === 'method_signature' || child.type === 'property_signature') {
|
|
262
|
+
const nameNode = child.childForFieldName('name');
|
|
263
|
+
if (nameNode) {
|
|
264
|
+
definitions.push({
|
|
265
|
+
name: `${interfaceName}.${nameNode.text}`,
|
|
266
|
+
kind: 'method',
|
|
267
|
+
line: child.startPosition.row + 1,
|
|
268
|
+
endLine: child.endPosition.row + 1
|
|
269
|
+
});
|
|
270
|
+
}
|
|
271
|
+
}
|
|
272
|
+
}
|
|
273
|
+
}
|
|
274
|
+
|
|
275
|
+
function extractImplements(heritage) {
|
|
276
|
+
const interfaces = [];
|
|
277
|
+
for (let i = 0; i < heritage.childCount; i++) {
|
|
278
|
+
const child = heritage.child(i);
|
|
279
|
+
if (!child) continue;
|
|
280
|
+
if (child.text === 'implements') {
|
|
281
|
+
for (let j = i + 1; j < heritage.childCount; j++) {
|
|
282
|
+
const next = heritage.child(j);
|
|
283
|
+
if (!next) continue;
|
|
284
|
+
if (next.type === 'identifier') interfaces.push(next.text);
|
|
285
|
+
else if (next.type === 'type_identifier') interfaces.push(next.text);
|
|
286
|
+
if (next.childCount > 0) interfaces.push(...extractImplementsFromNode(next));
|
|
287
|
+
}
|
|
288
|
+
break;
|
|
289
|
+
}
|
|
290
|
+
if (child.type === 'implements_clause') {
|
|
291
|
+
interfaces.push(...extractImplementsFromNode(child));
|
|
292
|
+
}
|
|
293
|
+
}
|
|
294
|
+
return interfaces;
|
|
295
|
+
}
|
|
296
|
+
|
|
297
|
+
function extractImplementsFromNode(node) {
|
|
298
|
+
const result = [];
|
|
299
|
+
for (let i = 0; i < node.childCount; i++) {
|
|
300
|
+
const child = node.child(i);
|
|
301
|
+
if (!child) continue;
|
|
302
|
+
if (child.type === 'identifier' || child.type === 'type_identifier') result.push(child.text);
|
|
303
|
+
if (child.childCount > 0) result.push(...extractImplementsFromNode(child));
|
|
304
|
+
}
|
|
305
|
+
return result;
|
|
306
|
+
}
|
|
307
|
+
|
|
308
|
+
function extractCallInfo(fn, callNode) {
|
|
309
|
+
if (fn.type === 'identifier') {
|
|
310
|
+
return { name: fn.text, line: callNode.startPosition.row + 1 };
|
|
311
|
+
}
|
|
312
|
+
|
|
313
|
+
if (fn.type === 'member_expression') {
|
|
314
|
+
const obj = fn.childForFieldName('object');
|
|
315
|
+
const prop = fn.childForFieldName('property');
|
|
316
|
+
if (!prop) return null;
|
|
317
|
+
|
|
318
|
+
if (prop.text === 'call' || prop.text === 'apply' || prop.text === 'bind') {
|
|
319
|
+
if (obj && obj.type === 'identifier') return { name: obj.text, line: callNode.startPosition.row + 1, dynamic: true };
|
|
320
|
+
if (obj && obj.type === 'member_expression') {
|
|
321
|
+
const innerProp = obj.childForFieldName('property');
|
|
322
|
+
if (innerProp) return { name: innerProp.text, line: callNode.startPosition.row + 1, dynamic: true };
|
|
323
|
+
}
|
|
324
|
+
}
|
|
325
|
+
|
|
326
|
+
if (prop.type === 'string' || prop.type === 'string_fragment') {
|
|
327
|
+
const methodName = prop.text.replace(/['"]/g, '');
|
|
328
|
+
if (methodName) return { name: methodName, line: callNode.startPosition.row + 1, dynamic: true };
|
|
329
|
+
}
|
|
330
|
+
|
|
331
|
+
return { name: prop.text, line: callNode.startPosition.row + 1 };
|
|
332
|
+
}
|
|
333
|
+
|
|
334
|
+
if (fn.type === 'subscript_expression') {
|
|
335
|
+
const index = fn.childForFieldName('index');
|
|
336
|
+
if (index && (index.type === 'string' || index.type === 'template_string')) {
|
|
337
|
+
const methodName = index.text.replace(/['"`]/g, '');
|
|
338
|
+
if (methodName && !methodName.includes('$')) return { name: methodName, line: callNode.startPosition.row + 1, dynamic: true };
|
|
339
|
+
}
|
|
340
|
+
}
|
|
341
|
+
|
|
342
|
+
return null;
|
|
343
|
+
}
|
|
344
|
+
|
|
345
|
+
function findChild(node, type) {
|
|
346
|
+
for (let i = 0; i < node.childCount; i++) {
|
|
347
|
+
if (node.child(i).type === type) return node.child(i);
|
|
348
|
+
}
|
|
349
|
+
return null;
|
|
350
|
+
}
|
|
351
|
+
|
|
352
|
+
function extractSuperclass(heritage) {
|
|
353
|
+
for (let i = 0; i < heritage.childCount; i++) {
|
|
354
|
+
const child = heritage.child(i);
|
|
355
|
+
if (child.type === 'identifier') return child.text;
|
|
356
|
+
if (child.type === 'member_expression') return child.text;
|
|
357
|
+
const found = extractSuperclass(child);
|
|
358
|
+
if (found) return found;
|
|
359
|
+
}
|
|
360
|
+
return null;
|
|
361
|
+
}
|
|
362
|
+
|
|
363
|
+
function findParentClass(node) {
|
|
364
|
+
let current = node.parent;
|
|
365
|
+
while (current) {
|
|
366
|
+
if (current.type === 'class_declaration' || current.type === 'class') {
|
|
367
|
+
const nameNode = current.childForFieldName('name');
|
|
368
|
+
return nameNode ? nameNode.text : null;
|
|
369
|
+
}
|
|
370
|
+
current = current.parent;
|
|
371
|
+
}
|
|
372
|
+
return null;
|
|
373
|
+
}
|
|
374
|
+
|
|
375
|
+
function extractImportNames(node) {
|
|
376
|
+
const names = [];
|
|
377
|
+
function scan(n) {
|
|
378
|
+
if (n.type === 'import_specifier' || n.type === 'export_specifier') {
|
|
379
|
+
const nameNode = n.childForFieldName('name') || n.childForFieldName('alias');
|
|
380
|
+
if (nameNode) names.push(nameNode.text);
|
|
381
|
+
else names.push(n.text);
|
|
382
|
+
} else if (n.type === 'identifier' && n.parent && n.parent.type === 'import_clause') {
|
|
383
|
+
names.push(n.text);
|
|
384
|
+
} else if (n.type === 'namespace_import') {
|
|
385
|
+
names.push(n.text);
|
|
386
|
+
}
|
|
387
|
+
for (let i = 0; i < n.childCount; i++) scan(n.child(i));
|
|
388
|
+
}
|
|
389
|
+
scan(node);
|
|
390
|
+
return names;
|
|
391
|
+
}
|
|
392
|
+
|
|
393
|
+
/**
|
|
394
|
+
* Extract symbols from HCL (Terraform) files.
|
|
395
|
+
*/
|
|
396
|
+
export function extractHCLSymbols(tree, filePath) {
|
|
397
|
+
const definitions = [];
|
|
398
|
+
const imports = [];
|
|
399
|
+
|
|
400
|
+
function walk(node) {
|
|
401
|
+
if (node.type === 'block') {
|
|
402
|
+
const children = [];
|
|
403
|
+
for (let i = 0; i < node.childCount; i++) children.push(node.child(i));
|
|
404
|
+
|
|
405
|
+
const identifiers = children.filter(c => c.type === 'identifier');
|
|
406
|
+
const strings = children.filter(c => c.type === 'string_lit');
|
|
407
|
+
|
|
408
|
+
if (identifiers.length > 0) {
|
|
409
|
+
const blockType = identifiers[0].text;
|
|
410
|
+
let name = '';
|
|
411
|
+
|
|
412
|
+
if (blockType === 'resource' && strings.length >= 2) {
|
|
413
|
+
name = `${strings[0].text.replace(/"/g, '')}.${strings[1].text.replace(/"/g, '')}`;
|
|
414
|
+
} else if (blockType === 'data' && strings.length >= 2) {
|
|
415
|
+
name = `data.${strings[0].text.replace(/"/g, '')}.${strings[1].text.replace(/"/g, '')}`;
|
|
416
|
+
} else if ((blockType === 'variable' || blockType === 'output' || blockType === 'module') && strings.length >= 1) {
|
|
417
|
+
name = `${blockType}.${strings[0].text.replace(/"/g, '')}`;
|
|
418
|
+
} else if (blockType === 'locals') {
|
|
419
|
+
name = 'locals';
|
|
420
|
+
} else if (blockType === 'terraform' || blockType === 'provider') {
|
|
421
|
+
name = blockType;
|
|
422
|
+
if (strings.length >= 1) name += `.${strings[0].text.replace(/"/g, '')}`;
|
|
423
|
+
}
|
|
424
|
+
|
|
425
|
+
if (name) {
|
|
426
|
+
definitions.push({ name, kind: blockType, line: node.startPosition.row + 1, endLine: nodeEndLine(node) });
|
|
427
|
+
}
|
|
428
|
+
|
|
429
|
+
if (blockType === 'module') {
|
|
430
|
+
const body = children.find(c => c.type === 'body');
|
|
431
|
+
if (body) {
|
|
432
|
+
for (let i = 0; i < body.childCount; i++) {
|
|
433
|
+
const attr = body.child(i);
|
|
434
|
+
if (attr && attr.type === 'attribute') {
|
|
435
|
+
const key = attr.childForFieldName('key') || attr.child(0);
|
|
436
|
+
const val = attr.childForFieldName('val') || attr.child(2);
|
|
437
|
+
if (key && key.text === 'source' && val) {
|
|
438
|
+
const src = val.text.replace(/"/g, '');
|
|
439
|
+
if (src.startsWith('./') || src.startsWith('../')) {
|
|
440
|
+
imports.push({ source: src, names: [], line: attr.startPosition.row + 1 });
|
|
441
|
+
}
|
|
442
|
+
}
|
|
443
|
+
}
|
|
444
|
+
}
|
|
445
|
+
}
|
|
446
|
+
}
|
|
447
|
+
}
|
|
448
|
+
}
|
|
449
|
+
|
|
450
|
+
for (let i = 0; i < node.childCount; i++) walk(node.child(i));
|
|
451
|
+
}
|
|
452
|
+
|
|
453
|
+
walk(tree.rootNode);
|
|
454
|
+
return { definitions, calls: [], imports, classes: [], exports: [] };
|
|
455
|
+
}
|
|
456
|
+
|
|
457
|
+
/**
|
|
458
|
+
* Extract symbols from Python files.
|
|
459
|
+
*/
|
|
460
|
+
export function extractPythonSymbols(tree, filePath) {
|
|
461
|
+
const definitions = [];
|
|
462
|
+
const calls = [];
|
|
463
|
+
const imports = [];
|
|
464
|
+
const classes = [];
|
|
465
|
+
const exports = [];
|
|
466
|
+
|
|
467
|
+
function walk(node) {
|
|
468
|
+
switch (node.type) {
|
|
469
|
+
case 'function_definition': {
|
|
470
|
+
const nameNode = node.childForFieldName('name');
|
|
471
|
+
if (nameNode) {
|
|
472
|
+
let decorators = [];
|
|
473
|
+
if (node.previousSibling && node.previousSibling.type === 'decorator') {
|
|
474
|
+
decorators.push(node.previousSibling.text);
|
|
475
|
+
}
|
|
476
|
+
const parentClass = findPythonParentClass(node);
|
|
477
|
+
const fullName = parentClass ? `${parentClass}.${nameNode.text}` : nameNode.text;
|
|
478
|
+
const kind = parentClass ? 'method' : 'function';
|
|
479
|
+
definitions.push({ name: fullName, kind, line: node.startPosition.row + 1, endLine: nodeEndLine(node), decorators });
|
|
480
|
+
}
|
|
481
|
+
break;
|
|
482
|
+
}
|
|
483
|
+
|
|
484
|
+
case 'class_definition': {
|
|
485
|
+
const nameNode = node.childForFieldName('name');
|
|
486
|
+
if (nameNode) {
|
|
487
|
+
definitions.push({ name: nameNode.text, kind: 'class', line: node.startPosition.row + 1, endLine: nodeEndLine(node) });
|
|
488
|
+
const superclasses = node.childForFieldName('superclasses') || findChild(node, 'argument_list');
|
|
489
|
+
if (superclasses) {
|
|
490
|
+
for (let i = 0; i < superclasses.childCount; i++) {
|
|
491
|
+
const child = superclasses.child(i);
|
|
492
|
+
if (child && child.type === 'identifier') {
|
|
493
|
+
classes.push({ name: nameNode.text, extends: child.text, line: node.startPosition.row + 1 });
|
|
494
|
+
}
|
|
495
|
+
}
|
|
496
|
+
}
|
|
497
|
+
}
|
|
498
|
+
break;
|
|
499
|
+
}
|
|
500
|
+
|
|
501
|
+
case 'decorated_definition': {
|
|
502
|
+
for (let i = 0; i < node.childCount; i++) walk(node.child(i));
|
|
503
|
+
return;
|
|
504
|
+
}
|
|
505
|
+
|
|
506
|
+
case 'call': {
|
|
507
|
+
const fn = node.childForFieldName('function');
|
|
508
|
+
if (fn) {
|
|
509
|
+
let callName = null;
|
|
510
|
+
if (fn.type === 'identifier') callName = fn.text;
|
|
511
|
+
else if (fn.type === 'attribute') {
|
|
512
|
+
const attr = fn.childForFieldName('attribute');
|
|
513
|
+
if (attr) callName = attr.text;
|
|
514
|
+
}
|
|
515
|
+
if (callName) calls.push({ name: callName, line: node.startPosition.row + 1 });
|
|
516
|
+
}
|
|
517
|
+
break;
|
|
518
|
+
}
|
|
519
|
+
|
|
520
|
+
case 'import_statement': {
|
|
521
|
+
const names = [];
|
|
522
|
+
for (let i = 0; i < node.childCount; i++) {
|
|
523
|
+
const child = node.child(i);
|
|
524
|
+
if (child && (child.type === 'dotted_name' || child.type === 'aliased_import')) {
|
|
525
|
+
const name = child.type === 'aliased_import' ?
|
|
526
|
+
(child.childForFieldName('alias') || child.childForFieldName('name'))?.text :
|
|
527
|
+
child.text;
|
|
528
|
+
if (name) names.push(name);
|
|
529
|
+
}
|
|
530
|
+
}
|
|
531
|
+
if (names.length > 0) imports.push({ source: names[0], names, line: node.startPosition.row + 1, pythonImport: true });
|
|
532
|
+
break;
|
|
533
|
+
}
|
|
534
|
+
|
|
535
|
+
case 'import_from_statement': {
|
|
536
|
+
let source = '';
|
|
537
|
+
const names = [];
|
|
538
|
+
for (let i = 0; i < node.childCount; i++) {
|
|
539
|
+
const child = node.child(i);
|
|
540
|
+
if (!child) continue;
|
|
541
|
+
if (child.type === 'dotted_name' || child.type === 'relative_import') {
|
|
542
|
+
if (!source) source = child.text;
|
|
543
|
+
else names.push(child.text);
|
|
544
|
+
}
|
|
545
|
+
if (child.type === 'aliased_import') {
|
|
546
|
+
const n = child.childForFieldName('name') || child.child(0);
|
|
547
|
+
if (n) names.push(n.text);
|
|
548
|
+
}
|
|
549
|
+
if (child.type === 'wildcard_import') names.push('*');
|
|
550
|
+
}
|
|
551
|
+
if (source) imports.push({ source, names, line: node.startPosition.row + 1, pythonImport: true });
|
|
552
|
+
break;
|
|
553
|
+
}
|
|
554
|
+
}
|
|
555
|
+
|
|
556
|
+
for (let i = 0; i < node.childCount; i++) walk(node.child(i));
|
|
557
|
+
}
|
|
558
|
+
|
|
559
|
+
function findPythonParentClass(node) {
|
|
560
|
+
let current = node.parent;
|
|
561
|
+
while (current) {
|
|
562
|
+
if (current.type === 'class_definition') {
|
|
563
|
+
const nameNode = current.childForFieldName('name');
|
|
564
|
+
return nameNode ? nameNode.text : null;
|
|
565
|
+
}
|
|
566
|
+
current = current.parent;
|
|
567
|
+
}
|
|
568
|
+
return null;
|
|
569
|
+
}
|
|
570
|
+
|
|
571
|
+
walk(tree.rootNode);
|
|
572
|
+
return { definitions, calls, imports, classes, exports };
|
|
573
|
+
}
|