@optave/codegraph 1.1.0 → 1.4.1

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/src/parser.js CHANGED
@@ -1,573 +1,2214 @@
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
- }
1
+ import fs from 'node:fs';
2
+ import path from 'node:path';
3
+ import { fileURLToPath } from 'node:url';
4
+ import { Language, Parser } from 'web-tree-sitter';
5
+ import { normalizePath } from './constants.js';
6
+ import { warn } from './logger.js';
7
+ import { loadNative } from './native.js';
8
+
9
+ const __dirname = path.dirname(fileURLToPath(import.meta.url));
10
+
11
+ function grammarPath(name) {
12
+ return path.join(__dirname, '..', 'grammars', name);
13
+ }
14
+
15
+ let _initialized = false;
16
+
17
+ export async function createParsers() {
18
+ if (!_initialized) {
19
+ await Parser.init();
20
+ _initialized = true;
21
+ }
22
+
23
+ const parsers = new Map();
24
+ for (const entry of LANGUAGE_REGISTRY) {
25
+ try {
26
+ const lang = await Language.load(grammarPath(entry.grammarFile));
27
+ const parser = new Parser();
28
+ parser.setLanguage(lang);
29
+ parsers.set(entry.id, parser);
30
+ } catch (e) {
31
+ if (entry.required) throw e;
32
+ warn(
33
+ `${entry.id} parser failed to initialize: ${e.message}. ${entry.id} files will be skipped.`,
34
+ );
35
+ parsers.set(entry.id, null);
36
+ }
37
+ }
38
+ return parsers;
39
+ }
40
+
41
+ export function getParser(parsers, filePath) {
42
+ const ext = path.extname(filePath);
43
+ const entry = _extToLang.get(ext);
44
+ if (!entry) return null;
45
+ return parsers.get(entry.id) || null;
46
+ }
47
+
48
+ function nodeEndLine(node) {
49
+ return node.endPosition.row + 1;
50
+ }
51
+
52
+ /**
53
+ * Extract symbols from a JS/TS parsed AST.
54
+ */
55
+ export function extractSymbols(tree, _filePath) {
56
+ const definitions = [];
57
+ const calls = [];
58
+ const imports = [];
59
+ const classes = [];
60
+ const exports = [];
61
+
62
+ function walk(node) {
63
+ switch (node.type) {
64
+ case 'function_declaration': {
65
+ const nameNode = node.childForFieldName('name');
66
+ if (nameNode) {
67
+ definitions.push({
68
+ name: nameNode.text,
69
+ kind: 'function',
70
+ line: node.startPosition.row + 1,
71
+ endLine: nodeEndLine(node),
72
+ });
73
+ }
74
+ break;
75
+ }
76
+
77
+ case 'class_declaration': {
78
+ const nameNode = node.childForFieldName('name');
79
+ if (nameNode) {
80
+ const cls = {
81
+ name: nameNode.text,
82
+ kind: 'class',
83
+ line: node.startPosition.row + 1,
84
+ endLine: nodeEndLine(node),
85
+ };
86
+ definitions.push(cls);
87
+ const heritage = node.childForFieldName('heritage') || findChild(node, 'class_heritage');
88
+ if (heritage) {
89
+ const superName = extractSuperclass(heritage);
90
+ if (superName) {
91
+ classes.push({
92
+ name: nameNode.text,
93
+ extends: superName,
94
+ line: node.startPosition.row + 1,
95
+ });
96
+ }
97
+ const implementsList = extractImplements(heritage);
98
+ for (const iface of implementsList) {
99
+ classes.push({
100
+ name: nameNode.text,
101
+ implements: iface,
102
+ line: node.startPosition.row + 1,
103
+ });
104
+ }
105
+ }
106
+ }
107
+ break;
108
+ }
109
+
110
+ case 'method_definition': {
111
+ const nameNode = node.childForFieldName('name');
112
+ if (nameNode) {
113
+ const parentClass = findParentClass(node);
114
+ const fullName = parentClass ? `${parentClass}.${nameNode.text}` : nameNode.text;
115
+ definitions.push({
116
+ name: fullName,
117
+ kind: 'method',
118
+ line: node.startPosition.row + 1,
119
+ endLine: nodeEndLine(node),
120
+ });
121
+ }
122
+ break;
123
+ }
124
+
125
+ case 'interface_declaration': {
126
+ const nameNode = node.childForFieldName('name');
127
+ if (nameNode) {
128
+ definitions.push({
129
+ name: nameNode.text,
130
+ kind: 'interface',
131
+ line: node.startPosition.row + 1,
132
+ endLine: nodeEndLine(node),
133
+ });
134
+ const body =
135
+ node.childForFieldName('body') ||
136
+ findChild(node, 'interface_body') ||
137
+ findChild(node, 'object_type');
138
+ if (body) {
139
+ extractInterfaceMethods(body, nameNode.text, definitions);
140
+ }
141
+ }
142
+ break;
143
+ }
144
+
145
+ case 'type_alias_declaration': {
146
+ const nameNode = node.childForFieldName('name');
147
+ if (nameNode) {
148
+ definitions.push({
149
+ name: nameNode.text,
150
+ kind: 'type',
151
+ line: node.startPosition.row + 1,
152
+ endLine: nodeEndLine(node),
153
+ });
154
+ }
155
+ break;
156
+ }
157
+
158
+ case 'lexical_declaration':
159
+ case 'variable_declaration': {
160
+ for (let i = 0; i < node.childCount; i++) {
161
+ const declarator = node.child(i);
162
+ if (declarator && declarator.type === 'variable_declarator') {
163
+ const nameN = declarator.childForFieldName('name');
164
+ const valueN = declarator.childForFieldName('value');
165
+ if (
166
+ nameN &&
167
+ valueN &&
168
+ (valueN.type === 'arrow_function' ||
169
+ valueN.type === 'function_expression' ||
170
+ valueN.type === 'function')
171
+ ) {
172
+ definitions.push({
173
+ name: nameN.text,
174
+ kind: 'function',
175
+ line: node.startPosition.row + 1,
176
+ endLine: nodeEndLine(valueN),
177
+ });
178
+ }
179
+ }
180
+ }
181
+ break;
182
+ }
183
+
184
+ case 'call_expression': {
185
+ const fn = node.childForFieldName('function');
186
+ if (fn) {
187
+ const callInfo = extractCallInfo(fn, node);
188
+ if (callInfo) {
189
+ calls.push(callInfo);
190
+ }
191
+ }
192
+ break;
193
+ }
194
+
195
+ case 'import_statement': {
196
+ const isTypeOnly = node.text.startsWith('import type');
197
+ const source = node.childForFieldName('source') || findChild(node, 'string');
198
+ if (source) {
199
+ const modPath = source.text.replace(/['"]/g, '');
200
+ const names = extractImportNames(node);
201
+ imports.push({
202
+ source: modPath,
203
+ names,
204
+ line: node.startPosition.row + 1,
205
+ typeOnly: isTypeOnly,
206
+ });
207
+ }
208
+ break;
209
+ }
210
+
211
+ case 'export_statement': {
212
+ const decl = node.childForFieldName('declaration');
213
+ if (decl) {
214
+ if (decl.type === 'function_declaration') {
215
+ const n = decl.childForFieldName('name');
216
+ if (n)
217
+ exports.push({ name: n.text, kind: 'function', line: node.startPosition.row + 1 });
218
+ } else if (decl.type === 'class_declaration') {
219
+ const n = decl.childForFieldName('name');
220
+ if (n) exports.push({ name: n.text, kind: 'class', line: node.startPosition.row + 1 });
221
+ } else if (decl.type === 'interface_declaration') {
222
+ const n = decl.childForFieldName('name');
223
+ if (n)
224
+ exports.push({ name: n.text, kind: 'interface', line: node.startPosition.row + 1 });
225
+ } else if (decl.type === 'type_alias_declaration') {
226
+ const n = decl.childForFieldName('name');
227
+ if (n) exports.push({ name: n.text, kind: 'type', line: node.startPosition.row + 1 });
228
+ }
229
+ }
230
+ const source = node.childForFieldName('source') || findChild(node, 'string');
231
+ if (source && !decl) {
232
+ const modPath = source.text.replace(/['"]/g, '');
233
+ const reexportNames = extractImportNames(node);
234
+ const isWildcard = node.text.includes('export *') || node.text.includes('export*');
235
+ imports.push({
236
+ source: modPath,
237
+ names: reexportNames,
238
+ line: node.startPosition.row + 1,
239
+ reexport: true,
240
+ wildcardReexport: isWildcard && reexportNames.length === 0,
241
+ });
242
+ }
243
+ break;
244
+ }
245
+
246
+ case 'expression_statement': {
247
+ const expr = node.child(0);
248
+ if (expr && expr.type === 'assignment_expression') {
249
+ const left = expr.childForFieldName('left');
250
+ const right = expr.childForFieldName('right');
251
+ if (left && right) {
252
+ const leftText = left.text;
253
+ if (leftText.startsWith('module.exports') || leftText === 'exports') {
254
+ if (right.type === 'call_expression') {
255
+ const fn = right.childForFieldName('function');
256
+ const args = right.childForFieldName('arguments') || findChild(right, 'arguments');
257
+ if (fn && fn.text === 'require' && args) {
258
+ const strArg = findChild(args, 'string');
259
+ if (strArg) {
260
+ const modPath = strArg.text.replace(/['"]/g, '');
261
+ imports.push({
262
+ source: modPath,
263
+ names: [],
264
+ line: node.startPosition.row + 1,
265
+ reexport: true,
266
+ wildcardReexport: true,
267
+ });
268
+ }
269
+ }
270
+ }
271
+ if (right.type === 'object') {
272
+ for (let ci = 0; ci < right.childCount; ci++) {
273
+ const child = right.child(ci);
274
+ if (child && child.type === 'spread_element') {
275
+ const spreadExpr = child.child(1) || child.childForFieldName('value');
276
+ if (spreadExpr && spreadExpr.type === 'call_expression') {
277
+ const fn2 = spreadExpr.childForFieldName('function');
278
+ const args2 =
279
+ spreadExpr.childForFieldName('arguments') ||
280
+ findChild(spreadExpr, 'arguments');
281
+ if (fn2 && fn2.text === 'require' && args2) {
282
+ const strArg2 = findChild(args2, 'string');
283
+ if (strArg2) {
284
+ const modPath2 = strArg2.text.replace(/['"]/g, '');
285
+ imports.push({
286
+ source: modPath2,
287
+ names: [],
288
+ line: node.startPosition.row + 1,
289
+ reexport: true,
290
+ wildcardReexport: true,
291
+ });
292
+ }
293
+ }
294
+ }
295
+ }
296
+ }
297
+ }
298
+ }
299
+ }
300
+ }
301
+ break;
302
+ }
303
+ }
304
+
305
+ for (let i = 0; i < node.childCount; i++) {
306
+ walk(node.child(i));
307
+ }
308
+ }
309
+
310
+ walk(tree.rootNode);
311
+ return { definitions, calls, imports, classes, exports };
312
+ }
313
+
314
+ function extractInterfaceMethods(bodyNode, interfaceName, definitions) {
315
+ for (let i = 0; i < bodyNode.childCount; i++) {
316
+ const child = bodyNode.child(i);
317
+ if (!child) continue;
318
+ if (child.type === 'method_signature' || child.type === 'property_signature') {
319
+ const nameNode = child.childForFieldName('name');
320
+ if (nameNode) {
321
+ definitions.push({
322
+ name: `${interfaceName}.${nameNode.text}`,
323
+ kind: 'method',
324
+ line: child.startPosition.row + 1,
325
+ endLine: child.endPosition.row + 1,
326
+ });
327
+ }
328
+ }
329
+ }
330
+ }
331
+
332
+ function extractImplements(heritage) {
333
+ const interfaces = [];
334
+ for (let i = 0; i < heritage.childCount; i++) {
335
+ const child = heritage.child(i);
336
+ if (!child) continue;
337
+ if (child.text === 'implements') {
338
+ for (let j = i + 1; j < heritage.childCount; j++) {
339
+ const next = heritage.child(j);
340
+ if (!next) continue;
341
+ if (next.type === 'identifier') interfaces.push(next.text);
342
+ else if (next.type === 'type_identifier') interfaces.push(next.text);
343
+ if (next.childCount > 0) interfaces.push(...extractImplementsFromNode(next));
344
+ }
345
+ break;
346
+ }
347
+ if (child.type === 'implements_clause') {
348
+ interfaces.push(...extractImplementsFromNode(child));
349
+ }
350
+ }
351
+ return interfaces;
352
+ }
353
+
354
+ function extractImplementsFromNode(node) {
355
+ const result = [];
356
+ for (let i = 0; i < node.childCount; i++) {
357
+ const child = node.child(i);
358
+ if (!child) continue;
359
+ if (child.type === 'identifier' || child.type === 'type_identifier') result.push(child.text);
360
+ if (child.childCount > 0) result.push(...extractImplementsFromNode(child));
361
+ }
362
+ return result;
363
+ }
364
+
365
+ function extractCallInfo(fn, callNode) {
366
+ if (fn.type === 'identifier') {
367
+ return { name: fn.text, line: callNode.startPosition.row + 1 };
368
+ }
369
+
370
+ if (fn.type === 'member_expression') {
371
+ const obj = fn.childForFieldName('object');
372
+ const prop = fn.childForFieldName('property');
373
+ if (!prop) return null;
374
+
375
+ if (prop.text === 'call' || prop.text === 'apply' || prop.text === 'bind') {
376
+ if (obj && obj.type === 'identifier')
377
+ return { name: obj.text, line: callNode.startPosition.row + 1, dynamic: true };
378
+ if (obj && obj.type === 'member_expression') {
379
+ const innerProp = obj.childForFieldName('property');
380
+ if (innerProp)
381
+ return { name: innerProp.text, line: callNode.startPosition.row + 1, dynamic: true };
382
+ }
383
+ }
384
+
385
+ if (prop.type === 'string' || prop.type === 'string_fragment') {
386
+ const methodName = prop.text.replace(/['"]/g, '');
387
+ if (methodName)
388
+ return { name: methodName, line: callNode.startPosition.row + 1, dynamic: true };
389
+ }
390
+
391
+ return { name: prop.text, line: callNode.startPosition.row + 1 };
392
+ }
393
+
394
+ if (fn.type === 'subscript_expression') {
395
+ const index = fn.childForFieldName('index');
396
+ if (index && (index.type === 'string' || index.type === 'template_string')) {
397
+ const methodName = index.text.replace(/['"`]/g, '');
398
+ if (methodName && !methodName.includes('$'))
399
+ return { name: methodName, line: callNode.startPosition.row + 1, dynamic: true };
400
+ }
401
+ }
402
+
403
+ return null;
404
+ }
405
+
406
+ function findChild(node, type) {
407
+ for (let i = 0; i < node.childCount; i++) {
408
+ if (node.child(i).type === type) return node.child(i);
409
+ }
410
+ return null;
411
+ }
412
+
413
+ function extractSuperclass(heritage) {
414
+ for (let i = 0; i < heritage.childCount; i++) {
415
+ const child = heritage.child(i);
416
+ if (child.type === 'identifier') return child.text;
417
+ if (child.type === 'member_expression') return child.text;
418
+ const found = extractSuperclass(child);
419
+ if (found) return found;
420
+ }
421
+ return null;
422
+ }
423
+
424
+ function findParentClass(node) {
425
+ let current = node.parent;
426
+ while (current) {
427
+ if (current.type === 'class_declaration' || current.type === 'class') {
428
+ const nameNode = current.childForFieldName('name');
429
+ return nameNode ? nameNode.text : null;
430
+ }
431
+ current = current.parent;
432
+ }
433
+ return null;
434
+ }
435
+
436
+ function extractImportNames(node) {
437
+ const names = [];
438
+ function scan(n) {
439
+ if (n.type === 'import_specifier' || n.type === 'export_specifier') {
440
+ const nameNode = n.childForFieldName('name') || n.childForFieldName('alias');
441
+ if (nameNode) names.push(nameNode.text);
442
+ else names.push(n.text);
443
+ } else if (n.type === 'identifier' && n.parent && n.parent.type === 'import_clause') {
444
+ names.push(n.text);
445
+ } else if (n.type === 'namespace_import') {
446
+ names.push(n.text);
447
+ }
448
+ for (let i = 0; i < n.childCount; i++) scan(n.child(i));
449
+ }
450
+ scan(node);
451
+ return names;
452
+ }
453
+
454
+ /**
455
+ * Extract symbols from HCL (Terraform) files.
456
+ */
457
+ export function extractHCLSymbols(tree, _filePath) {
458
+ const definitions = [];
459
+ const imports = [];
460
+
461
+ function walk(node) {
462
+ if (node.type === 'block') {
463
+ const children = [];
464
+ for (let i = 0; i < node.childCount; i++) children.push(node.child(i));
465
+
466
+ const identifiers = children.filter((c) => c.type === 'identifier');
467
+ const strings = children.filter((c) => c.type === 'string_lit');
468
+
469
+ if (identifiers.length > 0) {
470
+ const blockType = identifiers[0].text;
471
+ let name = '';
472
+
473
+ if (blockType === 'resource' && strings.length >= 2) {
474
+ name = `${strings[0].text.replace(/"/g, '')}.${strings[1].text.replace(/"/g, '')}`;
475
+ } else if (blockType === 'data' && strings.length >= 2) {
476
+ name = `data.${strings[0].text.replace(/"/g, '')}.${strings[1].text.replace(/"/g, '')}`;
477
+ } else if (
478
+ (blockType === 'variable' || blockType === 'output' || blockType === 'module') &&
479
+ strings.length >= 1
480
+ ) {
481
+ name = `${blockType}.${strings[0].text.replace(/"/g, '')}`;
482
+ } else if (blockType === 'locals') {
483
+ name = 'locals';
484
+ } else if (blockType === 'terraform' || blockType === 'provider') {
485
+ name = blockType;
486
+ if (strings.length >= 1) name += `.${strings[0].text.replace(/"/g, '')}`;
487
+ }
488
+
489
+ if (name) {
490
+ definitions.push({
491
+ name,
492
+ kind: blockType,
493
+ line: node.startPosition.row + 1,
494
+ endLine: nodeEndLine(node),
495
+ });
496
+ }
497
+
498
+ if (blockType === 'module') {
499
+ const body = children.find((c) => c.type === 'body');
500
+ if (body) {
501
+ for (let i = 0; i < body.childCount; i++) {
502
+ const attr = body.child(i);
503
+ if (attr && attr.type === 'attribute') {
504
+ const key = attr.childForFieldName('key') || attr.child(0);
505
+ const val = attr.childForFieldName('val') || attr.child(2);
506
+ if (key && key.text === 'source' && val) {
507
+ const src = val.text.replace(/"/g, '');
508
+ if (src.startsWith('./') || src.startsWith('../')) {
509
+ imports.push({ source: src, names: [], line: attr.startPosition.row + 1 });
510
+ }
511
+ }
512
+ }
513
+ }
514
+ }
515
+ }
516
+ }
517
+ }
518
+
519
+ for (let i = 0; i < node.childCount; i++) walk(node.child(i));
520
+ }
521
+
522
+ walk(tree.rootNode);
523
+ return { definitions, calls: [], imports, classes: [], exports: [] };
524
+ }
525
+
526
+ /**
527
+ * Extract symbols from Python files.
528
+ */
529
+ export function extractPythonSymbols(tree, _filePath) {
530
+ const definitions = [];
531
+ const calls = [];
532
+ const imports = [];
533
+ const classes = [];
534
+ const exports = [];
535
+
536
+ function walk(node) {
537
+ switch (node.type) {
538
+ case 'function_definition': {
539
+ const nameNode = node.childForFieldName('name');
540
+ if (nameNode) {
541
+ const decorators = [];
542
+ if (node.previousSibling && node.previousSibling.type === 'decorator') {
543
+ decorators.push(node.previousSibling.text);
544
+ }
545
+ const parentClass = findPythonParentClass(node);
546
+ const fullName = parentClass ? `${parentClass}.${nameNode.text}` : nameNode.text;
547
+ const kind = parentClass ? 'method' : 'function';
548
+ definitions.push({
549
+ name: fullName,
550
+ kind,
551
+ line: node.startPosition.row + 1,
552
+ endLine: nodeEndLine(node),
553
+ decorators,
554
+ });
555
+ }
556
+ break;
557
+ }
558
+
559
+ case 'class_definition': {
560
+ const nameNode = node.childForFieldName('name');
561
+ if (nameNode) {
562
+ definitions.push({
563
+ name: nameNode.text,
564
+ kind: 'class',
565
+ line: node.startPosition.row + 1,
566
+ endLine: nodeEndLine(node),
567
+ });
568
+ const superclasses =
569
+ node.childForFieldName('superclasses') || findChild(node, 'argument_list');
570
+ if (superclasses) {
571
+ for (let i = 0; i < superclasses.childCount; i++) {
572
+ const child = superclasses.child(i);
573
+ if (child && child.type === 'identifier') {
574
+ classes.push({
575
+ name: nameNode.text,
576
+ extends: child.text,
577
+ line: node.startPosition.row + 1,
578
+ });
579
+ }
580
+ }
581
+ }
582
+ }
583
+ break;
584
+ }
585
+
586
+ case 'decorated_definition': {
587
+ for (let i = 0; i < node.childCount; i++) walk(node.child(i));
588
+ return;
589
+ }
590
+
591
+ case 'call': {
592
+ const fn = node.childForFieldName('function');
593
+ if (fn) {
594
+ let callName = null;
595
+ if (fn.type === 'identifier') callName = fn.text;
596
+ else if (fn.type === 'attribute') {
597
+ const attr = fn.childForFieldName('attribute');
598
+ if (attr) callName = attr.text;
599
+ }
600
+ if (callName) calls.push({ name: callName, line: node.startPosition.row + 1 });
601
+ }
602
+ break;
603
+ }
604
+
605
+ case 'import_statement': {
606
+ const names = [];
607
+ for (let i = 0; i < node.childCount; i++) {
608
+ const child = node.child(i);
609
+ if (child && (child.type === 'dotted_name' || child.type === 'aliased_import')) {
610
+ const name =
611
+ child.type === 'aliased_import'
612
+ ? (child.childForFieldName('alias') || child.childForFieldName('name'))?.text
613
+ : child.text;
614
+ if (name) names.push(name);
615
+ }
616
+ }
617
+ if (names.length > 0)
618
+ imports.push({
619
+ source: names[0],
620
+ names,
621
+ line: node.startPosition.row + 1,
622
+ pythonImport: true,
623
+ });
624
+ break;
625
+ }
626
+
627
+ case 'import_from_statement': {
628
+ let source = '';
629
+ const names = [];
630
+ for (let i = 0; i < node.childCount; i++) {
631
+ const child = node.child(i);
632
+ if (!child) continue;
633
+ if (child.type === 'dotted_name' || child.type === 'relative_import') {
634
+ if (!source) source = child.text;
635
+ else names.push(child.text);
636
+ }
637
+ if (child.type === 'aliased_import') {
638
+ const n = child.childForFieldName('name') || child.child(0);
639
+ if (n) names.push(n.text);
640
+ }
641
+ if (child.type === 'wildcard_import') names.push('*');
642
+ }
643
+ if (source)
644
+ imports.push({ source, names, line: node.startPosition.row + 1, pythonImport: true });
645
+ break;
646
+ }
647
+ }
648
+
649
+ for (let i = 0; i < node.childCount; i++) walk(node.child(i));
650
+ }
651
+
652
+ function findPythonParentClass(node) {
653
+ let current = node.parent;
654
+ while (current) {
655
+ if (current.type === 'class_definition') {
656
+ const nameNode = current.childForFieldName('name');
657
+ return nameNode ? nameNode.text : null;
658
+ }
659
+ current = current.parent;
660
+ }
661
+ return null;
662
+ }
663
+
664
+ walk(tree.rootNode);
665
+ return { definitions, calls, imports, classes, exports };
666
+ }
667
+
668
+ /**
669
+ * Extract symbols from Go files.
670
+ */
671
+ export function extractGoSymbols(tree, _filePath) {
672
+ const definitions = [];
673
+ const calls = [];
674
+ const imports = [];
675
+ const classes = [];
676
+ const exports = [];
677
+
678
+ function walk(node) {
679
+ switch (node.type) {
680
+ case 'function_declaration': {
681
+ const nameNode = node.childForFieldName('name');
682
+ if (nameNode) {
683
+ definitions.push({
684
+ name: nameNode.text,
685
+ kind: 'function',
686
+ line: node.startPosition.row + 1,
687
+ endLine: nodeEndLine(node),
688
+ });
689
+ }
690
+ break;
691
+ }
692
+
693
+ case 'method_declaration': {
694
+ const nameNode = node.childForFieldName('name');
695
+ const receiver = node.childForFieldName('receiver');
696
+ if (nameNode) {
697
+ let receiverType = null;
698
+ if (receiver) {
699
+ // receiver is a parameter_list like (r *Foo) or (r Foo)
700
+ for (let i = 0; i < receiver.childCount; i++) {
701
+ const param = receiver.child(i);
702
+ if (!param) continue;
703
+ const typeNode = param.childForFieldName('type');
704
+ if (typeNode) {
705
+ receiverType =
706
+ typeNode.type === 'pointer_type'
707
+ ? typeNode.text.replace(/^\*/, '')
708
+ : typeNode.text;
709
+ break;
710
+ }
711
+ }
712
+ }
713
+ const fullName = receiverType ? `${receiverType}.${nameNode.text}` : nameNode.text;
714
+ definitions.push({
715
+ name: fullName,
716
+ kind: 'method',
717
+ line: node.startPosition.row + 1,
718
+ endLine: nodeEndLine(node),
719
+ });
720
+ }
721
+ break;
722
+ }
723
+
724
+ case 'type_declaration': {
725
+ for (let i = 0; i < node.childCount; i++) {
726
+ const spec = node.child(i);
727
+ if (!spec || spec.type !== 'type_spec') continue;
728
+ const nameNode = spec.childForFieldName('name');
729
+ const typeNode = spec.childForFieldName('type');
730
+ if (nameNode && typeNode) {
731
+ if (typeNode.type === 'struct_type') {
732
+ definitions.push({
733
+ name: nameNode.text,
734
+ kind: 'class',
735
+ line: node.startPosition.row + 1,
736
+ endLine: nodeEndLine(node),
737
+ });
738
+ } else if (typeNode.type === 'interface_type') {
739
+ definitions.push({
740
+ name: nameNode.text,
741
+ kind: 'interface',
742
+ line: node.startPosition.row + 1,
743
+ endLine: nodeEndLine(node),
744
+ });
745
+ for (let j = 0; j < typeNode.childCount; j++) {
746
+ const member = typeNode.child(j);
747
+ if (member && member.type === 'method_elem') {
748
+ const methName = member.childForFieldName('name');
749
+ if (methName) {
750
+ definitions.push({
751
+ name: `${nameNode.text}.${methName.text}`,
752
+ kind: 'method',
753
+ line: member.startPosition.row + 1,
754
+ endLine: member.endPosition.row + 1,
755
+ });
756
+ }
757
+ }
758
+ }
759
+ } else {
760
+ definitions.push({
761
+ name: nameNode.text,
762
+ kind: 'type',
763
+ line: node.startPosition.row + 1,
764
+ endLine: nodeEndLine(node),
765
+ });
766
+ }
767
+ }
768
+ }
769
+ break;
770
+ }
771
+
772
+ case 'import_declaration': {
773
+ for (let i = 0; i < node.childCount; i++) {
774
+ const child = node.child(i);
775
+ if (!child) continue;
776
+ if (child.type === 'import_spec') {
777
+ const pathNode = child.childForFieldName('path');
778
+ if (pathNode) {
779
+ const importPath = pathNode.text.replace(/"/g, '');
780
+ const nameNode = child.childForFieldName('name');
781
+ const alias = nameNode ? nameNode.text : importPath.split('/').pop();
782
+ imports.push({
783
+ source: importPath,
784
+ names: [alias],
785
+ line: child.startPosition.row + 1,
786
+ goImport: true,
787
+ });
788
+ }
789
+ }
790
+ if (child.type === 'import_spec_list') {
791
+ for (let j = 0; j < child.childCount; j++) {
792
+ const spec = child.child(j);
793
+ if (spec && spec.type === 'import_spec') {
794
+ const pathNode = spec.childForFieldName('path');
795
+ if (pathNode) {
796
+ const importPath = pathNode.text.replace(/"/g, '');
797
+ const nameNode = spec.childForFieldName('name');
798
+ const alias = nameNode ? nameNode.text : importPath.split('/').pop();
799
+ imports.push({
800
+ source: importPath,
801
+ names: [alias],
802
+ line: spec.startPosition.row + 1,
803
+ goImport: true,
804
+ });
805
+ }
806
+ }
807
+ }
808
+ }
809
+ }
810
+ break;
811
+ }
812
+
813
+ case 'call_expression': {
814
+ const fn = node.childForFieldName('function');
815
+ if (fn) {
816
+ if (fn.type === 'identifier') {
817
+ calls.push({ name: fn.text, line: node.startPosition.row + 1 });
818
+ } else if (fn.type === 'selector_expression') {
819
+ const field = fn.childForFieldName('field');
820
+ if (field) calls.push({ name: field.text, line: node.startPosition.row + 1 });
821
+ }
822
+ }
823
+ break;
824
+ }
825
+ }
826
+
827
+ for (let i = 0; i < node.childCount; i++) walk(node.child(i));
828
+ }
829
+
830
+ walk(tree.rootNode);
831
+ return { definitions, calls, imports, classes, exports };
832
+ }
833
+
834
+ /**
835
+ * Extract symbols from Rust files.
836
+ */
837
+ export function extractRustSymbols(tree, _filePath) {
838
+ const definitions = [];
839
+ const calls = [];
840
+ const imports = [];
841
+ const classes = [];
842
+ const exports = [];
843
+
844
+ function findCurrentImpl(node) {
845
+ let current = node.parent;
846
+ while (current) {
847
+ if (current.type === 'impl_item') {
848
+ const typeNode = current.childForFieldName('type');
849
+ return typeNode ? typeNode.text : null;
850
+ }
851
+ current = current.parent;
852
+ }
853
+ return null;
854
+ }
855
+
856
+ function walk(node) {
857
+ switch (node.type) {
858
+ case 'function_item': {
859
+ const nameNode = node.childForFieldName('name');
860
+ if (nameNode) {
861
+ const implType = findCurrentImpl(node);
862
+ const fullName = implType ? `${implType}.${nameNode.text}` : nameNode.text;
863
+ const kind = implType ? 'method' : 'function';
864
+ definitions.push({
865
+ name: fullName,
866
+ kind,
867
+ line: node.startPosition.row + 1,
868
+ endLine: nodeEndLine(node),
869
+ });
870
+ }
871
+ break;
872
+ }
873
+
874
+ case 'struct_item': {
875
+ const nameNode = node.childForFieldName('name');
876
+ if (nameNode) {
877
+ definitions.push({
878
+ name: nameNode.text,
879
+ kind: 'class',
880
+ line: node.startPosition.row + 1,
881
+ endLine: nodeEndLine(node),
882
+ });
883
+ }
884
+ break;
885
+ }
886
+
887
+ case 'enum_item': {
888
+ const nameNode = node.childForFieldName('name');
889
+ if (nameNode) {
890
+ definitions.push({
891
+ name: nameNode.text,
892
+ kind: 'class',
893
+ line: node.startPosition.row + 1,
894
+ endLine: nodeEndLine(node),
895
+ });
896
+ }
897
+ break;
898
+ }
899
+
900
+ case 'trait_item': {
901
+ const nameNode = node.childForFieldName('name');
902
+ if (nameNode) {
903
+ definitions.push({
904
+ name: nameNode.text,
905
+ kind: 'interface',
906
+ line: node.startPosition.row + 1,
907
+ endLine: nodeEndLine(node),
908
+ });
909
+ const body = node.childForFieldName('body');
910
+ if (body) {
911
+ for (let i = 0; i < body.childCount; i++) {
912
+ const child = body.child(i);
913
+ if (
914
+ child &&
915
+ (child.type === 'function_signature_item' || child.type === 'function_item')
916
+ ) {
917
+ const methName = child.childForFieldName('name');
918
+ if (methName) {
919
+ definitions.push({
920
+ name: `${nameNode.text}.${methName.text}`,
921
+ kind: 'method',
922
+ line: child.startPosition.row + 1,
923
+ endLine: child.endPosition.row + 1,
924
+ });
925
+ }
926
+ }
927
+ }
928
+ }
929
+ }
930
+ break;
931
+ }
932
+
933
+ case 'impl_item': {
934
+ const typeNode = node.childForFieldName('type');
935
+ const traitNode = node.childForFieldName('trait');
936
+ if (typeNode && traitNode) {
937
+ classes.push({
938
+ name: typeNode.text,
939
+ implements: traitNode.text,
940
+ line: node.startPosition.row + 1,
941
+ });
942
+ }
943
+ break;
944
+ }
945
+
946
+ case 'use_declaration': {
947
+ const argNode = node.child(1);
948
+ if (argNode) {
949
+ const usePaths = extractRustUsePath(argNode);
950
+ for (const imp of usePaths) {
951
+ imports.push({
952
+ source: imp.source,
953
+ names: imp.names,
954
+ line: node.startPosition.row + 1,
955
+ rustUse: true,
956
+ });
957
+ }
958
+ }
959
+ break;
960
+ }
961
+
962
+ case 'call_expression': {
963
+ const fn = node.childForFieldName('function');
964
+ if (fn) {
965
+ if (fn.type === 'identifier') {
966
+ calls.push({ name: fn.text, line: node.startPosition.row + 1 });
967
+ } else if (fn.type === 'field_expression') {
968
+ const field = fn.childForFieldName('field');
969
+ if (field) calls.push({ name: field.text, line: node.startPosition.row + 1 });
970
+ } else if (fn.type === 'scoped_identifier') {
971
+ const name = fn.childForFieldName('name');
972
+ if (name) calls.push({ name: name.text, line: node.startPosition.row + 1 });
973
+ }
974
+ }
975
+ break;
976
+ }
977
+
978
+ case 'macro_invocation': {
979
+ const macroNode = node.child(0);
980
+ if (macroNode) {
981
+ calls.push({ name: `${macroNode.text}!`, line: node.startPosition.row + 1 });
982
+ }
983
+ break;
984
+ }
985
+ }
986
+
987
+ for (let i = 0; i < node.childCount; i++) walk(node.child(i));
988
+ }
989
+
990
+ walk(tree.rootNode);
991
+ return { definitions, calls, imports, classes, exports };
992
+ }
993
+
994
+ function extractRustUsePath(node) {
995
+ if (!node) return [];
996
+
997
+ if (node.type === 'use_list') {
998
+ const results = [];
999
+ for (let i = 0; i < node.childCount; i++) {
1000
+ results.push(...extractRustUsePath(node.child(i)));
1001
+ }
1002
+ return results;
1003
+ }
1004
+
1005
+ if (node.type === 'scoped_use_list') {
1006
+ const pathNode = node.childForFieldName('path');
1007
+ const listNode = node.childForFieldName('list');
1008
+ const prefix = pathNode ? pathNode.text : '';
1009
+ if (listNode) {
1010
+ const names = [];
1011
+ for (let i = 0; i < listNode.childCount; i++) {
1012
+ const child = listNode.child(i);
1013
+ if (
1014
+ child &&
1015
+ (child.type === 'identifier' || child.type === 'use_as_clause' || child.type === 'self')
1016
+ ) {
1017
+ const name =
1018
+ child.type === 'use_as_clause'
1019
+ ? (child.childForFieldName('alias') || child.childForFieldName('name'))?.text
1020
+ : child.text;
1021
+ if (name) names.push(name);
1022
+ }
1023
+ }
1024
+ return [{ source: prefix, names }];
1025
+ }
1026
+ return [{ source: prefix, names: [] }];
1027
+ }
1028
+
1029
+ if (node.type === 'use_as_clause') {
1030
+ const name = node.childForFieldName('alias') || node.childForFieldName('name');
1031
+ return [{ source: node.text, names: name ? [name.text] : [] }];
1032
+ }
1033
+
1034
+ if (node.type === 'use_wildcard') {
1035
+ const pathNode = node.childForFieldName('path');
1036
+ return [{ source: pathNode ? pathNode.text : '*', names: ['*'] }];
1037
+ }
1038
+
1039
+ if (node.type === 'scoped_identifier' || node.type === 'identifier') {
1040
+ const text = node.text;
1041
+ const lastName = text.split('::').pop();
1042
+ return [{ source: text, names: [lastName] }];
1043
+ }
1044
+
1045
+ return [];
1046
+ }
1047
+
1048
+ /**
1049
+ * Extract symbols from Java files.
1050
+ */
1051
+ export function extractJavaSymbols(tree, _filePath) {
1052
+ const definitions = [];
1053
+ const calls = [];
1054
+ const imports = [];
1055
+ const classes = [];
1056
+ const exports = [];
1057
+
1058
+ function findJavaParentClass(node) {
1059
+ let current = node.parent;
1060
+ while (current) {
1061
+ if (
1062
+ current.type === 'class_declaration' ||
1063
+ current.type === 'enum_declaration' ||
1064
+ current.type === 'interface_declaration'
1065
+ ) {
1066
+ const nameNode = current.childForFieldName('name');
1067
+ return nameNode ? nameNode.text : null;
1068
+ }
1069
+ current = current.parent;
1070
+ }
1071
+ return null;
1072
+ }
1073
+
1074
+ function walk(node) {
1075
+ switch (node.type) {
1076
+ case 'class_declaration': {
1077
+ const nameNode = node.childForFieldName('name');
1078
+ if (nameNode) {
1079
+ definitions.push({
1080
+ name: nameNode.text,
1081
+ kind: 'class',
1082
+ line: node.startPosition.row + 1,
1083
+ endLine: nodeEndLine(node),
1084
+ });
1085
+
1086
+ const superclass = node.childForFieldName('superclass');
1087
+ if (superclass) {
1088
+ for (let i = 0; i < superclass.childCount; i++) {
1089
+ const child = superclass.child(i);
1090
+ if (
1091
+ child &&
1092
+ (child.type === 'type_identifier' ||
1093
+ child.type === 'identifier' ||
1094
+ child.type === 'generic_type')
1095
+ ) {
1096
+ const superName = child.type === 'generic_type' ? child.child(0)?.text : child.text;
1097
+ if (superName)
1098
+ classes.push({
1099
+ name: nameNode.text,
1100
+ extends: superName,
1101
+ line: node.startPosition.row + 1,
1102
+ });
1103
+ break;
1104
+ }
1105
+ }
1106
+ }
1107
+
1108
+ const interfaces = node.childForFieldName('interfaces');
1109
+ if (interfaces) {
1110
+ for (let i = 0; i < interfaces.childCount; i++) {
1111
+ const child = interfaces.child(i);
1112
+ if (
1113
+ child &&
1114
+ (child.type === 'type_identifier' ||
1115
+ child.type === 'identifier' ||
1116
+ child.type === 'type_list' ||
1117
+ child.type === 'generic_type')
1118
+ ) {
1119
+ if (child.type === 'type_list') {
1120
+ for (let j = 0; j < child.childCount; j++) {
1121
+ const t = child.child(j);
1122
+ if (
1123
+ t &&
1124
+ (t.type === 'type_identifier' ||
1125
+ t.type === 'identifier' ||
1126
+ t.type === 'generic_type')
1127
+ ) {
1128
+ const ifaceName = t.type === 'generic_type' ? t.child(0)?.text : t.text;
1129
+ if (ifaceName)
1130
+ classes.push({
1131
+ name: nameNode.text,
1132
+ implements: ifaceName,
1133
+ line: node.startPosition.row + 1,
1134
+ });
1135
+ }
1136
+ }
1137
+ } else {
1138
+ const ifaceName =
1139
+ child.type === 'generic_type' ? child.child(0)?.text : child.text;
1140
+ if (ifaceName)
1141
+ classes.push({
1142
+ name: nameNode.text,
1143
+ implements: ifaceName,
1144
+ line: node.startPosition.row + 1,
1145
+ });
1146
+ }
1147
+ }
1148
+ }
1149
+ }
1150
+ }
1151
+ break;
1152
+ }
1153
+
1154
+ case 'interface_declaration': {
1155
+ const nameNode = node.childForFieldName('name');
1156
+ if (nameNode) {
1157
+ definitions.push({
1158
+ name: nameNode.text,
1159
+ kind: 'interface',
1160
+ line: node.startPosition.row + 1,
1161
+ endLine: nodeEndLine(node),
1162
+ });
1163
+ const body = node.childForFieldName('body');
1164
+ if (body) {
1165
+ for (let i = 0; i < body.childCount; i++) {
1166
+ const child = body.child(i);
1167
+ if (child && child.type === 'method_declaration') {
1168
+ const methName = child.childForFieldName('name');
1169
+ if (methName) {
1170
+ definitions.push({
1171
+ name: `${nameNode.text}.${methName.text}`,
1172
+ kind: 'method',
1173
+ line: child.startPosition.row + 1,
1174
+ endLine: child.endPosition.row + 1,
1175
+ });
1176
+ }
1177
+ }
1178
+ }
1179
+ }
1180
+ }
1181
+ break;
1182
+ }
1183
+
1184
+ case 'enum_declaration': {
1185
+ const nameNode = node.childForFieldName('name');
1186
+ if (nameNode) {
1187
+ definitions.push({
1188
+ name: nameNode.text,
1189
+ kind: 'class',
1190
+ line: node.startPosition.row + 1,
1191
+ endLine: nodeEndLine(node),
1192
+ });
1193
+ }
1194
+ break;
1195
+ }
1196
+
1197
+ case 'method_declaration': {
1198
+ const nameNode = node.childForFieldName('name');
1199
+ if (nameNode) {
1200
+ const parentClass = findJavaParentClass(node);
1201
+ const fullName = parentClass ? `${parentClass}.${nameNode.text}` : nameNode.text;
1202
+ definitions.push({
1203
+ name: fullName,
1204
+ kind: 'method',
1205
+ line: node.startPosition.row + 1,
1206
+ endLine: nodeEndLine(node),
1207
+ });
1208
+ }
1209
+ break;
1210
+ }
1211
+
1212
+ case 'constructor_declaration': {
1213
+ const nameNode = node.childForFieldName('name');
1214
+ if (nameNode) {
1215
+ const parentClass = findJavaParentClass(node);
1216
+ const fullName = parentClass ? `${parentClass}.${nameNode.text}` : nameNode.text;
1217
+ definitions.push({
1218
+ name: fullName,
1219
+ kind: 'method',
1220
+ line: node.startPosition.row + 1,
1221
+ endLine: nodeEndLine(node),
1222
+ });
1223
+ }
1224
+ break;
1225
+ }
1226
+
1227
+ case 'import_declaration': {
1228
+ for (let i = 0; i < node.childCount; i++) {
1229
+ const child = node.child(i);
1230
+ if (child && (child.type === 'scoped_identifier' || child.type === 'identifier')) {
1231
+ const fullPath = child.text;
1232
+ const lastName = fullPath.split('.').pop();
1233
+ imports.push({
1234
+ source: fullPath,
1235
+ names: [lastName],
1236
+ line: node.startPosition.row + 1,
1237
+ javaImport: true,
1238
+ });
1239
+ }
1240
+ if (child && child.type === 'asterisk') {
1241
+ const lastImport = imports[imports.length - 1];
1242
+ if (lastImport) lastImport.names = ['*'];
1243
+ }
1244
+ }
1245
+ break;
1246
+ }
1247
+
1248
+ case 'method_invocation': {
1249
+ const nameNode = node.childForFieldName('name');
1250
+ if (nameNode) {
1251
+ calls.push({ name: nameNode.text, line: node.startPosition.row + 1 });
1252
+ }
1253
+ break;
1254
+ }
1255
+
1256
+ case 'object_creation_expression': {
1257
+ const typeNode = node.childForFieldName('type');
1258
+ if (typeNode) {
1259
+ const typeName =
1260
+ typeNode.type === 'generic_type' ? typeNode.child(0)?.text : typeNode.text;
1261
+ if (typeName) calls.push({ name: typeName, line: node.startPosition.row + 1 });
1262
+ }
1263
+ break;
1264
+ }
1265
+ }
1266
+
1267
+ for (let i = 0; i < node.childCount; i++) walk(node.child(i));
1268
+ }
1269
+
1270
+ walk(tree.rootNode);
1271
+ return { definitions, calls, imports, classes, exports };
1272
+ }
1273
+
1274
+ /**
1275
+ * Extract symbols from C# files.
1276
+ */
1277
+ export function extractCSharpSymbols(tree, _filePath) {
1278
+ const definitions = [];
1279
+ const calls = [];
1280
+ const imports = [];
1281
+ const classes = [];
1282
+ const exports = [];
1283
+
1284
+ function findCSharpParentType(node) {
1285
+ let current = node.parent;
1286
+ while (current) {
1287
+ if (
1288
+ current.type === 'class_declaration' ||
1289
+ current.type === 'struct_declaration' ||
1290
+ current.type === 'interface_declaration' ||
1291
+ current.type === 'enum_declaration' ||
1292
+ current.type === 'record_declaration'
1293
+ ) {
1294
+ const nameNode = current.childForFieldName('name');
1295
+ return nameNode ? nameNode.text : null;
1296
+ }
1297
+ current = current.parent;
1298
+ }
1299
+ return null;
1300
+ }
1301
+
1302
+ function walk(node) {
1303
+ switch (node.type) {
1304
+ case 'class_declaration': {
1305
+ const nameNode = node.childForFieldName('name');
1306
+ if (nameNode) {
1307
+ definitions.push({
1308
+ name: nameNode.text,
1309
+ kind: 'class',
1310
+ line: node.startPosition.row + 1,
1311
+ endLine: nodeEndLine(node),
1312
+ });
1313
+ extractCSharpBaseTypes(node, nameNode.text, classes);
1314
+ }
1315
+ break;
1316
+ }
1317
+
1318
+ case 'struct_declaration': {
1319
+ const nameNode = node.childForFieldName('name');
1320
+ if (nameNode) {
1321
+ definitions.push({
1322
+ name: nameNode.text,
1323
+ kind: 'class',
1324
+ line: node.startPosition.row + 1,
1325
+ endLine: nodeEndLine(node),
1326
+ });
1327
+ extractCSharpBaseTypes(node, nameNode.text, classes);
1328
+ }
1329
+ break;
1330
+ }
1331
+
1332
+ case 'record_declaration': {
1333
+ const nameNode = node.childForFieldName('name');
1334
+ if (nameNode) {
1335
+ definitions.push({
1336
+ name: nameNode.text,
1337
+ kind: 'class',
1338
+ line: node.startPosition.row + 1,
1339
+ endLine: nodeEndLine(node),
1340
+ });
1341
+ extractCSharpBaseTypes(node, nameNode.text, classes);
1342
+ }
1343
+ break;
1344
+ }
1345
+
1346
+ case 'interface_declaration': {
1347
+ const nameNode = node.childForFieldName('name');
1348
+ if (nameNode) {
1349
+ definitions.push({
1350
+ name: nameNode.text,
1351
+ kind: 'interface',
1352
+ line: node.startPosition.row + 1,
1353
+ endLine: nodeEndLine(node),
1354
+ });
1355
+ const body = node.childForFieldName('body');
1356
+ if (body) {
1357
+ for (let i = 0; i < body.childCount; i++) {
1358
+ const child = body.child(i);
1359
+ if (child && child.type === 'method_declaration') {
1360
+ const methName = child.childForFieldName('name');
1361
+ if (methName) {
1362
+ definitions.push({
1363
+ name: `${nameNode.text}.${methName.text}`,
1364
+ kind: 'method',
1365
+ line: child.startPosition.row + 1,
1366
+ endLine: child.endPosition.row + 1,
1367
+ });
1368
+ }
1369
+ }
1370
+ }
1371
+ }
1372
+ }
1373
+ break;
1374
+ }
1375
+
1376
+ case 'enum_declaration': {
1377
+ const nameNode = node.childForFieldName('name');
1378
+ if (nameNode) {
1379
+ definitions.push({
1380
+ name: nameNode.text,
1381
+ kind: 'class',
1382
+ line: node.startPosition.row + 1,
1383
+ endLine: nodeEndLine(node),
1384
+ });
1385
+ }
1386
+ break;
1387
+ }
1388
+
1389
+ case 'method_declaration': {
1390
+ const nameNode = node.childForFieldName('name');
1391
+ if (nameNode) {
1392
+ const parentType = findCSharpParentType(node);
1393
+ const fullName = parentType ? `${parentType}.${nameNode.text}` : nameNode.text;
1394
+ definitions.push({
1395
+ name: fullName,
1396
+ kind: 'method',
1397
+ line: node.startPosition.row + 1,
1398
+ endLine: nodeEndLine(node),
1399
+ });
1400
+ }
1401
+ break;
1402
+ }
1403
+
1404
+ case 'constructor_declaration': {
1405
+ const nameNode = node.childForFieldName('name');
1406
+ if (nameNode) {
1407
+ const parentType = findCSharpParentType(node);
1408
+ const fullName = parentType ? `${parentType}.${nameNode.text}` : nameNode.text;
1409
+ definitions.push({
1410
+ name: fullName,
1411
+ kind: 'method',
1412
+ line: node.startPosition.row + 1,
1413
+ endLine: nodeEndLine(node),
1414
+ });
1415
+ }
1416
+ break;
1417
+ }
1418
+
1419
+ case 'property_declaration': {
1420
+ const nameNode = node.childForFieldName('name');
1421
+ if (nameNode) {
1422
+ const parentType = findCSharpParentType(node);
1423
+ const fullName = parentType ? `${parentType}.${nameNode.text}` : nameNode.text;
1424
+ definitions.push({
1425
+ name: fullName,
1426
+ kind: 'method',
1427
+ line: node.startPosition.row + 1,
1428
+ endLine: nodeEndLine(node),
1429
+ });
1430
+ }
1431
+ break;
1432
+ }
1433
+
1434
+ case 'using_directive': {
1435
+ // using System.Collections.Generic;
1436
+ const nameNode =
1437
+ node.childForFieldName('name') ||
1438
+ findChild(node, 'qualified_name') ||
1439
+ findChild(node, 'identifier');
1440
+ if (nameNode) {
1441
+ const fullPath = nameNode.text;
1442
+ const lastName = fullPath.split('.').pop();
1443
+ imports.push({
1444
+ source: fullPath,
1445
+ names: [lastName],
1446
+ line: node.startPosition.row + 1,
1447
+ csharpUsing: true,
1448
+ });
1449
+ }
1450
+ break;
1451
+ }
1452
+
1453
+ case 'invocation_expression': {
1454
+ const fn = node.childForFieldName('function') || node.child(0);
1455
+ if (fn) {
1456
+ if (fn.type === 'identifier') {
1457
+ calls.push({ name: fn.text, line: node.startPosition.row + 1 });
1458
+ } else if (fn.type === 'member_access_expression') {
1459
+ const name = fn.childForFieldName('name');
1460
+ if (name) calls.push({ name: name.text, line: node.startPosition.row + 1 });
1461
+ } else if (fn.type === 'generic_name' || fn.type === 'member_binding_expression') {
1462
+ const name = fn.childForFieldName('name') || fn.child(0);
1463
+ if (name) calls.push({ name: name.text, line: node.startPosition.row + 1 });
1464
+ }
1465
+ }
1466
+ break;
1467
+ }
1468
+
1469
+ case 'object_creation_expression': {
1470
+ const typeNode = node.childForFieldName('type');
1471
+ if (typeNode) {
1472
+ const typeName =
1473
+ typeNode.type === 'generic_name'
1474
+ ? typeNode.childForFieldName('name')?.text || typeNode.child(0)?.text
1475
+ : typeNode.text;
1476
+ if (typeName) calls.push({ name: typeName, line: node.startPosition.row + 1 });
1477
+ }
1478
+ break;
1479
+ }
1480
+ }
1481
+
1482
+ for (let i = 0; i < node.childCount; i++) walk(node.child(i));
1483
+ }
1484
+
1485
+ walk(tree.rootNode);
1486
+ return { definitions, calls, imports, classes, exports };
1487
+ }
1488
+
1489
+ function extractCSharpBaseTypes(node, className, classes) {
1490
+ const baseList = node.childForFieldName('bases');
1491
+ if (!baseList) return;
1492
+ for (let i = 0; i < baseList.childCount; i++) {
1493
+ const child = baseList.child(i);
1494
+ if (!child) continue;
1495
+ if (child.type === 'identifier' || child.type === 'qualified_name') {
1496
+ classes.push({ name: className, extends: child.text, line: node.startPosition.row + 1 });
1497
+ } else if (child.type === 'generic_name') {
1498
+ const name = child.childForFieldName('name') || child.child(0);
1499
+ if (name)
1500
+ classes.push({ name: className, extends: name.text, line: node.startPosition.row + 1 });
1501
+ } else if (child.type === 'base_list') {
1502
+ for (let j = 0; j < child.childCount; j++) {
1503
+ const base = child.child(j);
1504
+ if (base && (base.type === 'identifier' || base.type === 'qualified_name')) {
1505
+ classes.push({ name: className, extends: base.text, line: node.startPosition.row + 1 });
1506
+ } else if (base && base.type === 'generic_name') {
1507
+ const name = base.childForFieldName('name') || base.child(0);
1508
+ if (name)
1509
+ classes.push({ name: className, extends: name.text, line: node.startPosition.row + 1 });
1510
+ }
1511
+ }
1512
+ }
1513
+ }
1514
+ }
1515
+
1516
+ /**
1517
+ * Extract symbols from Ruby files.
1518
+ */
1519
+ export function extractRubySymbols(tree, _filePath) {
1520
+ const definitions = [];
1521
+ const calls = [];
1522
+ const imports = [];
1523
+ const classes = [];
1524
+ const exports = [];
1525
+
1526
+ function findRubyParentClass(node) {
1527
+ let current = node.parent;
1528
+ while (current) {
1529
+ if (current.type === 'class') {
1530
+ const nameNode = current.childForFieldName('name');
1531
+ return nameNode ? nameNode.text : null;
1532
+ }
1533
+ if (current.type === 'module') {
1534
+ const nameNode = current.childForFieldName('name');
1535
+ return nameNode ? nameNode.text : null;
1536
+ }
1537
+ current = current.parent;
1538
+ }
1539
+ return null;
1540
+ }
1541
+
1542
+ function walk(node) {
1543
+ switch (node.type) {
1544
+ case 'class': {
1545
+ const nameNode = node.childForFieldName('name');
1546
+ if (nameNode) {
1547
+ definitions.push({
1548
+ name: nameNode.text,
1549
+ kind: 'class',
1550
+ line: node.startPosition.row + 1,
1551
+ endLine: nodeEndLine(node),
1552
+ });
1553
+ const superclass = node.childForFieldName('superclass');
1554
+ if (superclass) {
1555
+ // superclass wraps the < token and class name
1556
+ for (let i = 0; i < superclass.childCount; i++) {
1557
+ const child = superclass.child(i);
1558
+ if (child && (child.type === 'constant' || child.type === 'scope_resolution')) {
1559
+ classes.push({
1560
+ name: nameNode.text,
1561
+ extends: child.text,
1562
+ line: node.startPosition.row + 1,
1563
+ });
1564
+ break;
1565
+ }
1566
+ }
1567
+ // Direct superclass node may be a constant
1568
+ if (superclass.type === 'superclass') {
1569
+ for (let i = 0; i < superclass.childCount; i++) {
1570
+ const child = superclass.child(i);
1571
+ if (child && (child.type === 'constant' || child.type === 'scope_resolution')) {
1572
+ classes.push({
1573
+ name: nameNode.text,
1574
+ extends: child.text,
1575
+ line: node.startPosition.row + 1,
1576
+ });
1577
+ break;
1578
+ }
1579
+ }
1580
+ }
1581
+ }
1582
+ }
1583
+ break;
1584
+ }
1585
+
1586
+ case 'module': {
1587
+ const nameNode = node.childForFieldName('name');
1588
+ if (nameNode) {
1589
+ definitions.push({
1590
+ name: nameNode.text,
1591
+ kind: 'class',
1592
+ line: node.startPosition.row + 1,
1593
+ endLine: nodeEndLine(node),
1594
+ });
1595
+ }
1596
+ break;
1597
+ }
1598
+
1599
+ case 'method': {
1600
+ const nameNode = node.childForFieldName('name');
1601
+ if (nameNode) {
1602
+ const parentClass = findRubyParentClass(node);
1603
+ const fullName = parentClass ? `${parentClass}.${nameNode.text}` : nameNode.text;
1604
+ definitions.push({
1605
+ name: fullName,
1606
+ kind: 'method',
1607
+ line: node.startPosition.row + 1,
1608
+ endLine: nodeEndLine(node),
1609
+ });
1610
+ }
1611
+ break;
1612
+ }
1613
+
1614
+ case 'singleton_method': {
1615
+ const nameNode = node.childForFieldName('name');
1616
+ if (nameNode) {
1617
+ const parentClass = findRubyParentClass(node);
1618
+ const fullName = parentClass ? `${parentClass}.${nameNode.text}` : nameNode.text;
1619
+ definitions.push({
1620
+ name: fullName,
1621
+ kind: 'function',
1622
+ line: node.startPosition.row + 1,
1623
+ endLine: nodeEndLine(node),
1624
+ });
1625
+ }
1626
+ break;
1627
+ }
1628
+
1629
+ case 'call': {
1630
+ const methodNode = node.childForFieldName('method');
1631
+ if (methodNode) {
1632
+ // Check for require/require_relative
1633
+ if (methodNode.text === 'require' || methodNode.text === 'require_relative') {
1634
+ const args = node.childForFieldName('arguments');
1635
+ if (args) {
1636
+ for (let i = 0; i < args.childCount; i++) {
1637
+ const arg = args.child(i);
1638
+ if (arg && (arg.type === 'string' || arg.type === 'string_content')) {
1639
+ const strContent = arg.text.replace(/^['"]|['"]$/g, '');
1640
+ imports.push({
1641
+ source: strContent,
1642
+ names: [strContent.split('/').pop()],
1643
+ line: node.startPosition.row + 1,
1644
+ rubyRequire: true,
1645
+ });
1646
+ break;
1647
+ }
1648
+ // Look inside string for string_content
1649
+ if (arg && arg.type === 'string') {
1650
+ const content = findChild(arg, 'string_content');
1651
+ if (content) {
1652
+ imports.push({
1653
+ source: content.text,
1654
+ names: [content.text.split('/').pop()],
1655
+ line: node.startPosition.row + 1,
1656
+ rubyRequire: true,
1657
+ });
1658
+ break;
1659
+ }
1660
+ }
1661
+ }
1662
+ }
1663
+ } else if (
1664
+ methodNode.text === 'include' ||
1665
+ methodNode.text === 'extend' ||
1666
+ methodNode.text === 'prepend'
1667
+ ) {
1668
+ // Module inclusion — treated like implements
1669
+ const parentClass = findRubyParentClass(node);
1670
+ if (parentClass) {
1671
+ const args = node.childForFieldName('arguments');
1672
+ if (args) {
1673
+ for (let i = 0; i < args.childCount; i++) {
1674
+ const arg = args.child(i);
1675
+ if (arg && (arg.type === 'constant' || arg.type === 'scope_resolution')) {
1676
+ classes.push({
1677
+ name: parentClass,
1678
+ implements: arg.text,
1679
+ line: node.startPosition.row + 1,
1680
+ });
1681
+ }
1682
+ }
1683
+ }
1684
+ }
1685
+ } else {
1686
+ calls.push({ name: methodNode.text, line: node.startPosition.row + 1 });
1687
+ }
1688
+ }
1689
+ break;
1690
+ }
1691
+ }
1692
+
1693
+ for (let i = 0; i < node.childCount; i++) walk(node.child(i));
1694
+ }
1695
+
1696
+ walk(tree.rootNode);
1697
+ return { definitions, calls, imports, classes, exports };
1698
+ }
1699
+
1700
+ /**
1701
+ * Extract symbols from PHP files.
1702
+ */
1703
+ export function extractPHPSymbols(tree, _filePath) {
1704
+ const definitions = [];
1705
+ const calls = [];
1706
+ const imports = [];
1707
+ const classes = [];
1708
+ const exports = [];
1709
+
1710
+ function findPHPParentClass(node) {
1711
+ let current = node.parent;
1712
+ while (current) {
1713
+ if (
1714
+ current.type === 'class_declaration' ||
1715
+ current.type === 'trait_declaration' ||
1716
+ current.type === 'enum_declaration'
1717
+ ) {
1718
+ const nameNode = current.childForFieldName('name');
1719
+ return nameNode ? nameNode.text : null;
1720
+ }
1721
+ current = current.parent;
1722
+ }
1723
+ return null;
1724
+ }
1725
+
1726
+ function walk(node) {
1727
+ switch (node.type) {
1728
+ case 'function_definition': {
1729
+ const nameNode = node.childForFieldName('name');
1730
+ if (nameNode) {
1731
+ definitions.push({
1732
+ name: nameNode.text,
1733
+ kind: 'function',
1734
+ line: node.startPosition.row + 1,
1735
+ endLine: nodeEndLine(node),
1736
+ });
1737
+ }
1738
+ break;
1739
+ }
1740
+
1741
+ case 'class_declaration': {
1742
+ const nameNode = node.childForFieldName('name');
1743
+ if (nameNode) {
1744
+ definitions.push({
1745
+ name: nameNode.text,
1746
+ kind: 'class',
1747
+ line: node.startPosition.row + 1,
1748
+ endLine: nodeEndLine(node),
1749
+ });
1750
+
1751
+ // Check base clause (extends)
1752
+ const baseClause =
1753
+ node.childForFieldName('base_clause') || findChild(node, 'base_clause');
1754
+ if (baseClause) {
1755
+ for (let i = 0; i < baseClause.childCount; i++) {
1756
+ const child = baseClause.child(i);
1757
+ if (child && (child.type === 'name' || child.type === 'qualified_name')) {
1758
+ classes.push({
1759
+ name: nameNode.text,
1760
+ extends: child.text,
1761
+ line: node.startPosition.row + 1,
1762
+ });
1763
+ break;
1764
+ }
1765
+ }
1766
+ }
1767
+
1768
+ // Check class interface clause (implements)
1769
+ const interfaceClause = findChild(node, 'class_interface_clause');
1770
+ if (interfaceClause) {
1771
+ for (let i = 0; i < interfaceClause.childCount; i++) {
1772
+ const child = interfaceClause.child(i);
1773
+ if (child && (child.type === 'name' || child.type === 'qualified_name')) {
1774
+ classes.push({
1775
+ name: nameNode.text,
1776
+ implements: child.text,
1777
+ line: node.startPosition.row + 1,
1778
+ });
1779
+ }
1780
+ }
1781
+ }
1782
+ }
1783
+ break;
1784
+ }
1785
+
1786
+ case 'interface_declaration': {
1787
+ const nameNode = node.childForFieldName('name');
1788
+ if (nameNode) {
1789
+ definitions.push({
1790
+ name: nameNode.text,
1791
+ kind: 'interface',
1792
+ line: node.startPosition.row + 1,
1793
+ endLine: nodeEndLine(node),
1794
+ });
1795
+ const body = node.childForFieldName('body');
1796
+ if (body) {
1797
+ for (let i = 0; i < body.childCount; i++) {
1798
+ const child = body.child(i);
1799
+ if (child && child.type === 'method_declaration') {
1800
+ const methName = child.childForFieldName('name');
1801
+ if (methName) {
1802
+ definitions.push({
1803
+ name: `${nameNode.text}.${methName.text}`,
1804
+ kind: 'method',
1805
+ line: child.startPosition.row + 1,
1806
+ endLine: child.endPosition.row + 1,
1807
+ });
1808
+ }
1809
+ }
1810
+ }
1811
+ }
1812
+ }
1813
+ break;
1814
+ }
1815
+
1816
+ case 'trait_declaration': {
1817
+ const nameNode = node.childForFieldName('name');
1818
+ if (nameNode) {
1819
+ definitions.push({
1820
+ name: nameNode.text,
1821
+ kind: 'interface',
1822
+ line: node.startPosition.row + 1,
1823
+ endLine: nodeEndLine(node),
1824
+ });
1825
+ }
1826
+ break;
1827
+ }
1828
+
1829
+ case 'enum_declaration': {
1830
+ const nameNode = node.childForFieldName('name');
1831
+ if (nameNode) {
1832
+ definitions.push({
1833
+ name: nameNode.text,
1834
+ kind: 'class',
1835
+ line: node.startPosition.row + 1,
1836
+ endLine: nodeEndLine(node),
1837
+ });
1838
+ }
1839
+ break;
1840
+ }
1841
+
1842
+ case 'method_declaration': {
1843
+ const nameNode = node.childForFieldName('name');
1844
+ if (nameNode) {
1845
+ const parentClass = findPHPParentClass(node);
1846
+ const fullName = parentClass ? `${parentClass}.${nameNode.text}` : nameNode.text;
1847
+ definitions.push({
1848
+ name: fullName,
1849
+ kind: 'method',
1850
+ line: node.startPosition.row + 1,
1851
+ endLine: nodeEndLine(node),
1852
+ });
1853
+ }
1854
+ break;
1855
+ }
1856
+
1857
+ case 'namespace_use_declaration': {
1858
+ // use App\Models\User;
1859
+ for (let i = 0; i < node.childCount; i++) {
1860
+ const child = node.child(i);
1861
+ if (child && child.type === 'namespace_use_clause') {
1862
+ const nameNode = findChild(child, 'qualified_name') || findChild(child, 'name');
1863
+ if (nameNode) {
1864
+ const fullPath = nameNode.text;
1865
+ const lastName = fullPath.split('\\').pop();
1866
+ const alias = child.childForFieldName('alias');
1867
+ imports.push({
1868
+ source: fullPath,
1869
+ names: [alias ? alias.text : lastName],
1870
+ line: node.startPosition.row + 1,
1871
+ phpUse: true,
1872
+ });
1873
+ }
1874
+ }
1875
+ // Single use clause without wrapper
1876
+ if (child && (child.type === 'qualified_name' || child.type === 'name')) {
1877
+ const fullPath = child.text;
1878
+ const lastName = fullPath.split('\\').pop();
1879
+ imports.push({
1880
+ source: fullPath,
1881
+ names: [lastName],
1882
+ line: node.startPosition.row + 1,
1883
+ phpUse: true,
1884
+ });
1885
+ }
1886
+ }
1887
+ break;
1888
+ }
1889
+
1890
+ case 'function_call_expression': {
1891
+ const fn = node.childForFieldName('function') || node.child(0);
1892
+ if (fn) {
1893
+ if (fn.type === 'name' || fn.type === 'identifier') {
1894
+ calls.push({ name: fn.text, line: node.startPosition.row + 1 });
1895
+ } else if (fn.type === 'qualified_name') {
1896
+ const parts = fn.text.split('\\');
1897
+ calls.push({ name: parts[parts.length - 1], line: node.startPosition.row + 1 });
1898
+ }
1899
+ }
1900
+ break;
1901
+ }
1902
+
1903
+ case 'member_call_expression': {
1904
+ const name = node.childForFieldName('name');
1905
+ if (name) {
1906
+ calls.push({ name: name.text, line: node.startPosition.row + 1 });
1907
+ }
1908
+ break;
1909
+ }
1910
+
1911
+ case 'scoped_call_expression': {
1912
+ const name = node.childForFieldName('name');
1913
+ if (name) {
1914
+ calls.push({ name: name.text, line: node.startPosition.row + 1 });
1915
+ }
1916
+ break;
1917
+ }
1918
+
1919
+ case 'object_creation_expression': {
1920
+ const classNode = node.child(1); // skip 'new' keyword
1921
+ if (classNode && (classNode.type === 'name' || classNode.type === 'qualified_name')) {
1922
+ const parts = classNode.text.split('\\');
1923
+ calls.push({ name: parts[parts.length - 1], line: node.startPosition.row + 1 });
1924
+ }
1925
+ break;
1926
+ }
1927
+ }
1928
+
1929
+ for (let i = 0; i < node.childCount; i++) walk(node.child(i));
1930
+ }
1931
+
1932
+ walk(tree.rootNode);
1933
+ return { definitions, calls, imports, classes, exports };
1934
+ }
1935
+
1936
+ // ── Unified API ──────────────────────────────────────────────────────────────
1937
+
1938
+ function resolveEngine(opts = {}) {
1939
+ const pref = opts.engine || 'auto';
1940
+ if (pref === 'wasm') return { name: 'wasm', native: null };
1941
+ if (pref === 'native' || pref === 'auto') {
1942
+ const native = loadNative();
1943
+ if (native) return { name: 'native', native };
1944
+ if (pref === 'native') {
1945
+ warn('Native engine requested but unavailable — falling back to WASM');
1946
+ }
1947
+ }
1948
+ return { name: 'wasm', native: null };
1949
+ }
1950
+
1951
+ /**
1952
+ * Normalize native engine output to match the camelCase convention
1953
+ * used by the WASM extractors.
1954
+ */
1955
+ function normalizeNativeSymbols(result) {
1956
+ return {
1957
+ definitions: (result.definitions || []).map((d) => ({
1958
+ name: d.name,
1959
+ kind: d.kind,
1960
+ line: d.line,
1961
+ endLine: d.endLine ?? d.end_line ?? null,
1962
+ decorators: d.decorators,
1963
+ })),
1964
+ calls: (result.calls || []).map((c) => ({
1965
+ name: c.name,
1966
+ line: c.line,
1967
+ dynamic: c.dynamic,
1968
+ })),
1969
+ imports: (result.imports || []).map((i) => ({
1970
+ source: i.source,
1971
+ names: i.names || [],
1972
+ line: i.line,
1973
+ typeOnly: i.typeOnly ?? i.type_only,
1974
+ reexport: i.reexport,
1975
+ wildcardReexport: i.wildcardReexport ?? i.wildcard_reexport,
1976
+ pythonImport: i.pythonImport ?? i.python_import,
1977
+ goImport: i.goImport ?? i.go_import,
1978
+ rustUse: i.rustUse ?? i.rust_use,
1979
+ javaImport: i.javaImport ?? i.java_import,
1980
+ csharpUsing: i.csharpUsing ?? i.csharp_using,
1981
+ rubyRequire: i.rubyRequire ?? i.ruby_require,
1982
+ phpUse: i.phpUse ?? i.php_use,
1983
+ })),
1984
+ classes: (result.classes || []).map((c) => ({
1985
+ name: c.name,
1986
+ extends: c.extends,
1987
+ implements: c.implements,
1988
+ line: c.line,
1989
+ })),
1990
+ exports: (result.exports || []).map((e) => ({
1991
+ name: e.name,
1992
+ kind: e.kind,
1993
+ line: e.line,
1994
+ })),
1995
+ };
1996
+ }
1997
+
1998
+ /**
1999
+ * Declarative registry of all supported languages.
2000
+ * Adding a new language requires only a new entry here + its extractor function.
2001
+ */
2002
+ export const LANGUAGE_REGISTRY = [
2003
+ {
2004
+ id: 'javascript',
2005
+ extensions: ['.js', '.jsx', '.mjs', '.cjs'],
2006
+ grammarFile: 'tree-sitter-javascript.wasm',
2007
+ extractor: extractSymbols,
2008
+ required: true,
2009
+ },
2010
+ {
2011
+ id: 'typescript',
2012
+ extensions: ['.ts'],
2013
+ grammarFile: 'tree-sitter-typescript.wasm',
2014
+ extractor: extractSymbols,
2015
+ required: true,
2016
+ },
2017
+ {
2018
+ id: 'tsx',
2019
+ extensions: ['.tsx'],
2020
+ grammarFile: 'tree-sitter-tsx.wasm',
2021
+ extractor: extractSymbols,
2022
+ required: true,
2023
+ },
2024
+ {
2025
+ id: 'hcl',
2026
+ extensions: ['.tf', '.hcl'],
2027
+ grammarFile: 'tree-sitter-hcl.wasm',
2028
+ extractor: extractHCLSymbols,
2029
+ required: false,
2030
+ },
2031
+ {
2032
+ id: 'python',
2033
+ extensions: ['.py'],
2034
+ grammarFile: 'tree-sitter-python.wasm',
2035
+ extractor: extractPythonSymbols,
2036
+ required: false,
2037
+ },
2038
+ {
2039
+ id: 'go',
2040
+ extensions: ['.go'],
2041
+ grammarFile: 'tree-sitter-go.wasm',
2042
+ extractor: extractGoSymbols,
2043
+ required: false,
2044
+ },
2045
+ {
2046
+ id: 'rust',
2047
+ extensions: ['.rs'],
2048
+ grammarFile: 'tree-sitter-rust.wasm',
2049
+ extractor: extractRustSymbols,
2050
+ required: false,
2051
+ },
2052
+ {
2053
+ id: 'java',
2054
+ extensions: ['.java'],
2055
+ grammarFile: 'tree-sitter-java.wasm',
2056
+ extractor: extractJavaSymbols,
2057
+ required: false,
2058
+ },
2059
+ {
2060
+ id: 'csharp',
2061
+ extensions: ['.cs'],
2062
+ grammarFile: 'tree-sitter-c_sharp.wasm',
2063
+ extractor: extractCSharpSymbols,
2064
+ required: false,
2065
+ },
2066
+ {
2067
+ id: 'ruby',
2068
+ extensions: ['.rb'],
2069
+ grammarFile: 'tree-sitter-ruby.wasm',
2070
+ extractor: extractRubySymbols,
2071
+ required: false,
2072
+ },
2073
+ {
2074
+ id: 'php',
2075
+ extensions: ['.php'],
2076
+ grammarFile: 'tree-sitter-php.wasm',
2077
+ extractor: extractPHPSymbols,
2078
+ required: false,
2079
+ },
2080
+ ];
2081
+
2082
+ const _extToLang = new Map();
2083
+ for (const entry of LANGUAGE_REGISTRY) {
2084
+ for (const ext of entry.extensions) {
2085
+ _extToLang.set(ext, entry);
2086
+ }
2087
+ }
2088
+
2089
+ export const SUPPORTED_EXTENSIONS = new Set(_extToLang.keys());
2090
+
2091
+ /**
2092
+ * WASM extraction helper: picks the right extractor based on file extension.
2093
+ */
2094
+ function wasmExtractSymbols(parsers, filePath, code) {
2095
+ const parser = getParser(parsers, filePath);
2096
+ if (!parser) return null;
2097
+
2098
+ let tree;
2099
+ try {
2100
+ tree = parser.parse(code);
2101
+ } catch (e) {
2102
+ warn(`Parse error in ${filePath}: ${e.message}`);
2103
+ return null;
2104
+ }
2105
+
2106
+ const ext = path.extname(filePath);
2107
+ const entry = _extToLang.get(ext);
2108
+ return entry ? entry.extractor(tree, filePath) : null;
2109
+ }
2110
+
2111
+ /**
2112
+ * Parse a single file and return normalized symbols.
2113
+ *
2114
+ * @param {string} filePath Absolute path to the file.
2115
+ * @param {string} source Source code string.
2116
+ * @param {object} [opts] Options: { engine: 'native'|'wasm'|'auto' }
2117
+ * @returns {Promise<{definitions, calls, imports, classes, exports}|null>}
2118
+ */
2119
+ export async function parseFileAuto(filePath, source, opts = {}) {
2120
+ const { native } = resolveEngine(opts);
2121
+
2122
+ if (native) {
2123
+ const result = native.parseFile(filePath, source);
2124
+ return result ? normalizeNativeSymbols(result) : null;
2125
+ }
2126
+
2127
+ // WASM path
2128
+ const parsers = await createParsers();
2129
+ return wasmExtractSymbols(parsers, filePath, source);
2130
+ }
2131
+
2132
+ /**
2133
+ * Parse multiple files in bulk and return a Map<relPath, symbols>.
2134
+ *
2135
+ * @param {string[]} filePaths Absolute paths to files.
2136
+ * @param {string} rootDir Project root for computing relative paths.
2137
+ * @param {object} [opts] Options: { engine: 'native'|'wasm'|'auto' }
2138
+ * @returns {Promise<Map<string, {definitions, calls, imports, classes, exports}>>}
2139
+ */
2140
+ export async function parseFilesAuto(filePaths, rootDir, opts = {}) {
2141
+ const { native } = resolveEngine(opts);
2142
+ const result = new Map();
2143
+
2144
+ if (native) {
2145
+ const nativeResults = native.parseFiles(filePaths, rootDir);
2146
+ for (const r of nativeResults) {
2147
+ if (!r) continue;
2148
+ const relPath = normalizePath(path.relative(rootDir, r.file));
2149
+ result.set(relPath, normalizeNativeSymbols(r));
2150
+ }
2151
+ return result;
2152
+ }
2153
+
2154
+ // WASM path
2155
+ const parsers = await createParsers();
2156
+ for (const filePath of filePaths) {
2157
+ let code;
2158
+ try {
2159
+ code = fs.readFileSync(filePath, 'utf-8');
2160
+ } catch (err) {
2161
+ warn(`Skipping ${path.relative(rootDir, filePath)}: ${err.message}`);
2162
+ continue;
2163
+ }
2164
+ const symbols = wasmExtractSymbols(parsers, filePath, code);
2165
+ if (symbols) {
2166
+ const relPath = normalizePath(path.relative(rootDir, filePath));
2167
+ result.set(relPath, symbols);
2168
+ }
2169
+ }
2170
+ return result;
2171
+ }
2172
+
2173
+ /**
2174
+ * Report which engine is active.
2175
+ *
2176
+ * @param {object} [opts] Options: { engine: 'native'|'wasm'|'auto' }
2177
+ * @returns {{ name: 'native'|'wasm', version: string|null }}
2178
+ */
2179
+ export function getActiveEngine(opts = {}) {
2180
+ const { name, native } = resolveEngine(opts);
2181
+ const version = native
2182
+ ? typeof native.engineVersion === 'function'
2183
+ ? native.engineVersion()
2184
+ : null
2185
+ : null;
2186
+ return { name, version };
2187
+ }
2188
+
2189
+ /**
2190
+ * Create a native ParseTreeCache for incremental parsing.
2191
+ * Returns null if the native engine is unavailable (WASM fallback).
2192
+ */
2193
+ export function createParseTreeCache() {
2194
+ const native = loadNative();
2195
+ if (!native || !native.ParseTreeCache) return null;
2196
+ return new native.ParseTreeCache();
2197
+ }
2198
+
2199
+ /**
2200
+ * Parse a file incrementally using the cache, or fall back to full parse.
2201
+ *
2202
+ * @param {object|null} cache ParseTreeCache instance (or null for full parse)
2203
+ * @param {string} filePath Absolute path to the file
2204
+ * @param {string} source Source code string
2205
+ * @param {object} [opts] Options forwarded to parseFileAuto on fallback
2206
+ * @returns {Promise<{definitions, calls, imports, classes, exports}|null>}
2207
+ */
2208
+ export async function parseFileIncremental(cache, filePath, source, opts = {}) {
2209
+ if (cache) {
2210
+ const result = cache.parseFile(filePath, source);
2211
+ return result ? normalizeNativeSymbols(result) : null;
2212
+ }
2213
+ return parseFileAuto(filePath, source, opts);
2214
+ }