@optave/codegraph 2.0.0 → 2.1.1-dev.00f091c

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