ucn 3.0.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.

Potentially problematic release.


This version of ucn might be problematic. Click here for more details.

Files changed (45) hide show
  1. package/.claude/skills/ucn/SKILL.md +77 -0
  2. package/LICENSE +21 -0
  3. package/README.md +135 -0
  4. package/cli/index.js +2437 -0
  5. package/core/discovery.js +513 -0
  6. package/core/imports.js +558 -0
  7. package/core/output.js +1274 -0
  8. package/core/parser.js +279 -0
  9. package/core/project.js +3261 -0
  10. package/index.js +52 -0
  11. package/languages/go.js +653 -0
  12. package/languages/index.js +267 -0
  13. package/languages/java.js +826 -0
  14. package/languages/javascript.js +1346 -0
  15. package/languages/python.js +667 -0
  16. package/languages/rust.js +950 -0
  17. package/languages/utils.js +457 -0
  18. package/package.json +42 -0
  19. package/test/fixtures/go/go.mod +3 -0
  20. package/test/fixtures/go/main.go +257 -0
  21. package/test/fixtures/go/service.go +187 -0
  22. package/test/fixtures/java/DataService.java +279 -0
  23. package/test/fixtures/java/Main.java +287 -0
  24. package/test/fixtures/java/Utils.java +199 -0
  25. package/test/fixtures/java/pom.xml +6 -0
  26. package/test/fixtures/javascript/main.js +109 -0
  27. package/test/fixtures/javascript/package.json +1 -0
  28. package/test/fixtures/javascript/service.js +88 -0
  29. package/test/fixtures/javascript/utils.js +67 -0
  30. package/test/fixtures/python/main.py +198 -0
  31. package/test/fixtures/python/pyproject.toml +3 -0
  32. package/test/fixtures/python/service.py +166 -0
  33. package/test/fixtures/python/utils.py +118 -0
  34. package/test/fixtures/rust/Cargo.toml +3 -0
  35. package/test/fixtures/rust/main.rs +253 -0
  36. package/test/fixtures/rust/service.rs +210 -0
  37. package/test/fixtures/rust/utils.rs +154 -0
  38. package/test/fixtures/typescript/main.ts +154 -0
  39. package/test/fixtures/typescript/package.json +1 -0
  40. package/test/fixtures/typescript/repository.ts +149 -0
  41. package/test/fixtures/typescript/types.ts +114 -0
  42. package/test/parser.test.js +3661 -0
  43. package/test/public-repos-test.js +477 -0
  44. package/test/systematic-test.js +619 -0
  45. package/ucn.js +8 -0
@@ -0,0 +1,1346 @@
1
+ /**
2
+ * languages/javascript.js - Tree-sitter based JS/TS/TSX parsing
3
+ *
4
+ * Handles: function declarations, arrow functions, class declarations,
5
+ * interfaces, type aliases, enums, and state objects.
6
+ */
7
+
8
+ const {
9
+ traverseTree,
10
+ nodeToLocation,
11
+ extractParams,
12
+ parseStructuredParams,
13
+ extractJSDocstring
14
+ } = require('./utils');
15
+ const { PARSE_OPTIONS } = require('./index');
16
+
17
+ /**
18
+ * Extract return type annotation from JS/TS function
19
+ * @param {object} node - Function node
20
+ * @returns {string|null} Return type or null
21
+ */
22
+ function extractReturnType(node) {
23
+ const returnTypeNode = node.childForFieldName('return_type');
24
+ if (returnTypeNode) {
25
+ let text = returnTypeNode.text.trim();
26
+ if (text.startsWith(':')) {
27
+ text = text.slice(1).trim();
28
+ }
29
+ return text || null;
30
+ }
31
+ return null;
32
+ }
33
+
34
+ /**
35
+ * Check if function is a generator
36
+ * @param {object} node - Function node
37
+ * @returns {boolean}
38
+ */
39
+ function isGenerator(node) {
40
+ return node.type === 'generator_function_declaration' ||
41
+ node.type === 'generator_function';
42
+ }
43
+
44
+ /**
45
+ * Extract generics from a function node
46
+ * @param {object} node - Function node
47
+ * @returns {string|null}
48
+ */
49
+ function extractGenerics(node) {
50
+ const typeParamsNode = node.childForFieldName('type_parameters');
51
+ if (typeParamsNode) {
52
+ return typeParamsNode.text;
53
+ }
54
+ return null;
55
+ }
56
+
57
+ /**
58
+ * Get assignment name from left side of assignment
59
+ */
60
+ function getAssignmentName(leftNode) {
61
+ if (!leftNode) return null;
62
+ if (leftNode.type === 'identifier') return leftNode.text;
63
+ if (leftNode.type === 'member_expression') {
64
+ const propNode = leftNode.childForFieldName('property');
65
+ if (propNode && (propNode.type === 'property_identifier' || propNode.type === 'identifier')) {
66
+ return propNode.text;
67
+ }
68
+ }
69
+ return null;
70
+ }
71
+
72
+ /**
73
+ * Extract modifiers from function text
74
+ */
75
+ function extractModifiers(text) {
76
+ const mods = [];
77
+ const firstLine = text.split('\n')[0];
78
+ if (firstLine.includes('export ')) mods.push('export');
79
+ if (firstLine.includes('async ')) mods.push('async');
80
+ if (firstLine.includes('default ')) mods.push('default');
81
+ return mods;
82
+ }
83
+
84
+ /**
85
+ * Find all functions in JS/TS code using tree-sitter
86
+ * @param {string} code - Source code
87
+ * @param {object} parser - Tree-sitter parser instance
88
+ * @returns {Array}
89
+ */
90
+ function findFunctions(code, parser) {
91
+ const tree = parser.parse(code, undefined, PARSE_OPTIONS);
92
+ const functions = [];
93
+ const processedRanges = new Set();
94
+
95
+ traverseTree(tree.rootNode, (node) => {
96
+ const rangeKey = `${node.startIndex}-${node.endIndex}`;
97
+
98
+ // Function declarations
99
+ if (node.type === 'function_declaration' || node.type === 'generator_function_declaration') {
100
+ if (processedRanges.has(rangeKey)) return true;
101
+ processedRanges.add(rangeKey);
102
+
103
+ const nameNode = node.childForFieldName('name');
104
+ const paramsNode = node.childForFieldName('parameters');
105
+
106
+ if (nameNode) {
107
+ const { startLine, endLine, indent } = nodeToLocation(node, code);
108
+ const returnType = extractReturnType(node);
109
+ const generics = extractGenerics(node);
110
+ const docstring = extractJSDocstring(code, startLine);
111
+ const isGen = isGenerator(node);
112
+ const modifiers = extractModifiers(node.text);
113
+
114
+ functions.push({
115
+ name: nameNode.text,
116
+ params: extractParams(paramsNode),
117
+ paramsStructured: parseStructuredParams(paramsNode, 'javascript'),
118
+ startLine,
119
+ endLine,
120
+ indent,
121
+ isArrow: false,
122
+ isGenerator: isGen,
123
+ modifiers,
124
+ ...(returnType && { returnType }),
125
+ ...(generics && { generics }),
126
+ ...(docstring && { docstring })
127
+ });
128
+ }
129
+ return true;
130
+ }
131
+
132
+ // TypeScript function signatures (e.g., in .d.ts files)
133
+ if (node.type === 'function_signature') {
134
+ if (processedRanges.has(rangeKey)) return true;
135
+ processedRanges.add(rangeKey);
136
+
137
+ const nameNode = node.childForFieldName('name');
138
+ const paramsNode = node.childForFieldName('parameters');
139
+
140
+ if (nameNode) {
141
+ const { startLine, endLine, indent } = nodeToLocation(node, code);
142
+ const returnType = extractReturnType(node);
143
+ const generics = extractGenerics(node);
144
+ const docstring = extractJSDocstring(code, startLine);
145
+
146
+ functions.push({
147
+ name: nameNode.text,
148
+ params: extractParams(paramsNode),
149
+ paramsStructured: parseStructuredParams(paramsNode, 'typescript'),
150
+ startLine,
151
+ endLine,
152
+ indent,
153
+ isArrow: false,
154
+ isGenerator: false,
155
+ modifiers: [],
156
+ ...(returnType && { returnType }),
157
+ ...(generics && { generics }),
158
+ ...(docstring && { docstring })
159
+ });
160
+ }
161
+ return true;
162
+ }
163
+
164
+ // Variable declarations with arrow functions or function expressions
165
+ if (node.type === 'lexical_declaration' || node.type === 'variable_declaration') {
166
+ if (processedRanges.has(rangeKey)) return true;
167
+
168
+ for (let i = 0; i < node.namedChildCount; i++) {
169
+ const declarator = node.namedChild(i);
170
+ if (declarator.type === 'variable_declarator') {
171
+ const nameNode = declarator.childForFieldName('name');
172
+ const valueNode = declarator.childForFieldName('value');
173
+
174
+ if (nameNode && valueNode) {
175
+ const isArrow = valueNode.type === 'arrow_function';
176
+ const isFnExpr = valueNode.type === 'function_expression' ||
177
+ valueNode.type === 'generator_function';
178
+
179
+ if (isArrow || isFnExpr) {
180
+ processedRanges.add(rangeKey);
181
+ const paramsNode = valueNode.childForFieldName('parameters');
182
+ const { startLine, endLine, indent } = nodeToLocation(node, code);
183
+ const returnType = extractReturnType(valueNode);
184
+ const generics = extractGenerics(valueNode);
185
+ const docstring = extractJSDocstring(code, startLine);
186
+ const isGen = isGenerator(valueNode);
187
+ const modifiers = extractModifiers(node.text);
188
+
189
+ functions.push({
190
+ name: nameNode.text,
191
+ params: extractParams(paramsNode),
192
+ paramsStructured: parseStructuredParams(paramsNode, 'javascript'),
193
+ startLine,
194
+ endLine,
195
+ indent,
196
+ isArrow,
197
+ isGenerator: isGen,
198
+ modifiers,
199
+ ...(returnType && { returnType }),
200
+ ...(generics && { generics }),
201
+ ...(docstring && { docstring })
202
+ });
203
+ }
204
+ }
205
+ }
206
+ }
207
+ return true;
208
+ }
209
+
210
+ // Assignment expressions: obj.method = function() {} or prototype assignments
211
+ if (node.type === 'assignment_expression') {
212
+ if (processedRanges.has(rangeKey)) return true;
213
+
214
+ const leftNode = node.childForFieldName('left');
215
+ const isPrototypeAssignment = leftNode && leftNode.type === 'member_expression' &&
216
+ leftNode.text.includes('.prototype.');
217
+
218
+ // For non-prototype assignments, check if nested
219
+ if (!isPrototypeAssignment) {
220
+ let parent = node.parent;
221
+ let isTopLevel = true;
222
+ while (parent) {
223
+ const ptype = parent.type;
224
+ if (ptype === 'function_declaration' || ptype === 'arrow_function' ||
225
+ ptype === 'function_expression' || ptype === 'method_definition' ||
226
+ ptype === 'generator_function_declaration' || ptype === 'generator_function' ||
227
+ ptype === 'class_body') {
228
+ isTopLevel = false;
229
+ break;
230
+ }
231
+ if (ptype === 'program' || ptype === 'module') {
232
+ break;
233
+ }
234
+ parent = parent.parent;
235
+ }
236
+ if (!isTopLevel) return true;
237
+ }
238
+
239
+ const rightNode = node.childForFieldName('right');
240
+
241
+ if (leftNode && rightNode) {
242
+ const isArrow = rightNode.type === 'arrow_function';
243
+ const isFnExpr = rightNode.type === 'function_expression' ||
244
+ rightNode.type === 'generator_function';
245
+
246
+ if (isArrow || isFnExpr) {
247
+ const name = getAssignmentName(leftNode);
248
+ if (name) {
249
+ processedRanges.add(rangeKey);
250
+ const paramsNode = rightNode.childForFieldName('parameters');
251
+ const { startLine, endLine, indent } = nodeToLocation(node, code);
252
+ const returnType = extractReturnType(rightNode);
253
+ const generics = extractGenerics(rightNode);
254
+ const docstring = extractJSDocstring(code, startLine);
255
+ const isGen = isGenerator(rightNode);
256
+
257
+ functions.push({
258
+ name,
259
+ params: extractParams(paramsNode),
260
+ paramsStructured: parseStructuredParams(paramsNode, 'javascript'),
261
+ startLine,
262
+ endLine,
263
+ indent,
264
+ isArrow,
265
+ isGenerator: isGen,
266
+ modifiers: [],
267
+ ...(returnType && { returnType }),
268
+ ...(generics && { generics }),
269
+ ...(docstring && { docstring })
270
+ });
271
+ }
272
+ }
273
+ }
274
+ return true;
275
+ }
276
+
277
+ // Export statements with anonymous functions
278
+ if (node.type === 'export_statement') {
279
+ const declaration = node.childForFieldName('declaration');
280
+ if (!declaration) {
281
+ for (let i = 0; i < node.namedChildCount; i++) {
282
+ const child = node.namedChild(i);
283
+ if (child.type === 'arrow_function' || child.type === 'function_expression' ||
284
+ child.type === 'generator_function') {
285
+ if (processedRanges.has(rangeKey)) return true;
286
+ processedRanges.add(rangeKey);
287
+
288
+ const paramsNode = child.childForFieldName('parameters');
289
+ const { startLine, endLine, indent } = nodeToLocation(node, code);
290
+ const returnType = extractReturnType(child);
291
+ const generics = extractGenerics(child);
292
+ const docstring = extractJSDocstring(code, startLine);
293
+ const isGen = isGenerator(child);
294
+
295
+ functions.push({
296
+ name: 'default',
297
+ params: extractParams(paramsNode),
298
+ paramsStructured: parseStructuredParams(paramsNode, 'javascript'),
299
+ startLine,
300
+ endLine,
301
+ indent,
302
+ isArrow: child.type === 'arrow_function',
303
+ isGenerator: isGen,
304
+ modifiers: ['export', 'default'],
305
+ ...(returnType && { returnType }),
306
+ ...(generics && { generics }),
307
+ ...(docstring && { docstring })
308
+ });
309
+ return true;
310
+ }
311
+ }
312
+ }
313
+ return true;
314
+ }
315
+
316
+ return true;
317
+ });
318
+
319
+ functions.sort((a, b) => a.startLine - b.startLine);
320
+ return functions;
321
+ }
322
+
323
+ /**
324
+ * Find all classes, interfaces, types, and enums
325
+ * @param {string} code - Source code
326
+ * @param {object} parser - Tree-sitter parser instance
327
+ * @returns {Array}
328
+ */
329
+ function findClasses(code, parser) {
330
+ const tree = parser.parse(code, undefined, PARSE_OPTIONS);
331
+ const classes = [];
332
+
333
+ traverseTree(tree.rootNode, (node) => {
334
+ // Class declarations
335
+ if (node.type === 'class_declaration' || node.type === 'class') {
336
+ const nameNode = node.childForFieldName('name');
337
+ if (nameNode) {
338
+ const { startLine, endLine } = nodeToLocation(node, code);
339
+ const members = extractClassMembers(node, code);
340
+ const docstring = extractJSDocstring(code, startLine);
341
+ const extendsInfo = extractExtends(node);
342
+ const implementsInfo = extractImplements(node);
343
+
344
+ classes.push({
345
+ name: nameNode.text,
346
+ startLine,
347
+ endLine,
348
+ type: 'class',
349
+ members,
350
+ ...(docstring && { docstring }),
351
+ ...(extendsInfo && { extends: extendsInfo }),
352
+ ...(implementsInfo.length > 0 && { implements: implementsInfo })
353
+ });
354
+ }
355
+ return false;
356
+ }
357
+
358
+ // TypeScript interface declarations
359
+ if (node.type === 'interface_declaration') {
360
+ const nameNode = node.childForFieldName('name');
361
+ if (nameNode) {
362
+ const { startLine, endLine } = nodeToLocation(node, code);
363
+ const docstring = extractJSDocstring(code, startLine);
364
+ const extendsInfo = extractInterfaceExtends(node);
365
+
366
+ classes.push({
367
+ name: nameNode.text,
368
+ startLine,
369
+ endLine,
370
+ type: 'interface',
371
+ members: [],
372
+ ...(docstring && { docstring }),
373
+ ...(extendsInfo.length > 0 && { extends: extendsInfo.join(', ') })
374
+ });
375
+ }
376
+ return false;
377
+ }
378
+
379
+ // TypeScript type alias declarations
380
+ if (node.type === 'type_alias_declaration') {
381
+ const nameNode = node.childForFieldName('name');
382
+ if (nameNode) {
383
+ const { startLine, endLine } = nodeToLocation(node, code);
384
+ const docstring = extractJSDocstring(code, startLine);
385
+
386
+ classes.push({
387
+ name: nameNode.text,
388
+ startLine,
389
+ endLine,
390
+ type: 'type',
391
+ members: [],
392
+ ...(docstring && { docstring })
393
+ });
394
+ }
395
+ return false;
396
+ }
397
+
398
+ // TypeScript enum declarations
399
+ if (node.type === 'enum_declaration') {
400
+ const nameNode = node.childForFieldName('name');
401
+ if (nameNode) {
402
+ const { startLine, endLine } = nodeToLocation(node, code);
403
+ const docstring = extractJSDocstring(code, startLine);
404
+
405
+ classes.push({
406
+ name: nameNode.text,
407
+ startLine,
408
+ endLine,
409
+ type: 'enum',
410
+ members: [],
411
+ ...(docstring && { docstring })
412
+ });
413
+ }
414
+ return false;
415
+ }
416
+
417
+ return true;
418
+ });
419
+
420
+ classes.sort((a, b) => a.startLine - b.startLine);
421
+ return classes;
422
+ }
423
+
424
+ /**
425
+ * Extract extends clause from class
426
+ */
427
+ function extractExtends(classNode) {
428
+ for (let i = 0; i < classNode.namedChildCount; i++) {
429
+ const child = classNode.namedChild(i);
430
+ if (child.type === 'class_heritage') {
431
+ const extendsClause = child.text.match(/extends\s+(\w+)/);
432
+ if (extendsClause) return extendsClause[1];
433
+ }
434
+ }
435
+ return null;
436
+ }
437
+
438
+ /**
439
+ * Extract implements clause from class
440
+ */
441
+ function extractImplements(classNode) {
442
+ const implements_ = [];
443
+ for (let i = 0; i < classNode.namedChildCount; i++) {
444
+ const child = classNode.namedChild(i);
445
+ if (child.type === 'class_heritage') {
446
+ const implMatch = child.text.match(/implements\s+([^{]+)/);
447
+ if (implMatch) {
448
+ const names = implMatch[1].split(',').map(n => n.trim());
449
+ implements_.push(...names);
450
+ }
451
+ }
452
+ }
453
+ return implements_;
454
+ }
455
+
456
+ /**
457
+ * Extract extends from interface
458
+ */
459
+ function extractInterfaceExtends(interfaceNode) {
460
+ const extends_ = [];
461
+ for (let i = 0; i < interfaceNode.namedChildCount; i++) {
462
+ const child = interfaceNode.namedChild(i);
463
+ if (child.type === 'extends_type_clause') {
464
+ // Parse comma-separated type names
465
+ const text = child.text.replace(/^extends\s+/, '');
466
+ const names = text.split(',').map(n => n.trim());
467
+ extends_.push(...names);
468
+ }
469
+ }
470
+ return extends_;
471
+ }
472
+
473
+ /**
474
+ * Extract class members
475
+ */
476
+ function extractClassMembers(classNode, code) {
477
+ const members = [];
478
+ const bodyNode = classNode.childForFieldName('body');
479
+ if (!bodyNode) return members;
480
+
481
+ for (let i = 0; i < bodyNode.namedChildCount; i++) {
482
+ const child = bodyNode.namedChild(i);
483
+
484
+ // Method definitions
485
+ if (child.type === 'method_definition' || child.type === 'method_signature') {
486
+ const nameNode = child.childForFieldName('name');
487
+ const paramsNode = child.childForFieldName('parameters');
488
+ if (nameNode) {
489
+ const { startLine, endLine } = nodeToLocation(child, code);
490
+ let name = nameNode.text;
491
+ const text = child.text;
492
+
493
+ // Determine member type
494
+ let memberType = 'method';
495
+ const hasOverride = /^\s*(?:public\s+|private\s+|protected\s+)?(?:static\s+)?override\s/.test(text);
496
+ const isGen = /^\s*(?:public\s+|private\s+|protected\s+)?(?:static\s+)?(?:async\s+)?\*/.test(text);
497
+
498
+ if (name === 'static') {
499
+ const staticMatch = text.match(/^\s*static\s+(?:override\s+|readonly\s+|async\s+)?\*?\s*(?:get\s+|set\s+)?(\w+)/);
500
+ if (staticMatch) name = staticMatch[1];
501
+ }
502
+
503
+ if (name === 'constructor') {
504
+ memberType = 'constructor';
505
+ } else if (text.match(/^\s*(?:public\s+|private\s+|protected\s+)?(?:override\s+)?static\s+(?:override\s+)?get\s/)) {
506
+ memberType = hasOverride ? 'static override get' : 'static get';
507
+ } else if (text.match(/^\s*(?:public\s+|private\s+|protected\s+)?(?:override\s+)?static\s+(?:override\s+)?set\s/)) {
508
+ memberType = hasOverride ? 'static override set' : 'static set';
509
+ } else if (text.match(/^\s*(?:public\s+|private\s+|protected\s+)?(?:override\s+)?static\s/)) {
510
+ memberType = hasOverride ? 'static override' : 'static';
511
+ } else if (text.match(/^\s*(?:public\s+|private\s+|protected\s+)?(?:override\s+)?get\s/)) {
512
+ memberType = hasOverride ? 'override get' : 'get';
513
+ } else if (text.match(/^\s*(?:public\s+|private\s+|protected\s+)?(?:override\s+)?set\s/)) {
514
+ memberType = hasOverride ? 'override set' : 'set';
515
+ } else if (name.startsWith('#')) {
516
+ memberType = 'private';
517
+ } else if (hasOverride) {
518
+ memberType = 'override';
519
+ }
520
+
521
+ const isAsync = text.match(/^\s*(?:static\s+)?(?:override\s+)?async\s/) !== null;
522
+ const returnType = extractReturnType(child);
523
+ const docstring = extractJSDocstring(code, startLine);
524
+
525
+ members.push({
526
+ name,
527
+ params: extractParams(paramsNode),
528
+ paramsStructured: parseStructuredParams(paramsNode, 'javascript'),
529
+ startLine,
530
+ endLine,
531
+ memberType,
532
+ isAsync,
533
+ isGenerator: isGen,
534
+ ...(returnType && { returnType }),
535
+ ...(docstring && { docstring })
536
+ });
537
+ }
538
+ }
539
+
540
+ // Field definitions
541
+ if (child.type === 'field_definition' || child.type === 'public_field_definition') {
542
+ const nameNode = child.childForFieldName('name') || child.childForFieldName('property');
543
+ if (nameNode) {
544
+ const { startLine, endLine } = nodeToLocation(child, code);
545
+ const name = nameNode.text;
546
+ const valueNode = child.childForFieldName('value');
547
+ const isArrow = valueNode && valueNode.type === 'arrow_function';
548
+
549
+ if (isArrow) {
550
+ const paramsNode = valueNode.childForFieldName('parameters');
551
+ const returnType = extractReturnType(valueNode);
552
+ members.push({
553
+ name,
554
+ params: extractParams(paramsNode),
555
+ paramsStructured: parseStructuredParams(paramsNode, 'javascript'),
556
+ startLine,
557
+ endLine,
558
+ memberType: name.startsWith('#') ? 'private' : 'field',
559
+ isArrow: true,
560
+ ...(returnType && { returnType })
561
+ });
562
+ } else {
563
+ members.push({
564
+ name,
565
+ startLine,
566
+ endLine,
567
+ memberType: name.startsWith('#') ? 'private field' : 'field'
568
+ });
569
+ }
570
+ }
571
+ }
572
+ }
573
+
574
+ return members;
575
+ }
576
+
577
+ /**
578
+ * Find state objects (CONFIG, constants, etc.)
579
+ */
580
+ function findStateObjects(code, parser) {
581
+ const tree = parser.parse(code, undefined, PARSE_OPTIONS);
582
+ const objects = [];
583
+
584
+ const statePattern = /^(CONFIG|[A-Z][a-zA-Z]*(?:State|Store|Context|Options|Settings)|[A-Z][A-Z_]+|Entities|Input)$/;
585
+ const actionPattern = /^(action\w*|[a-z]+Action|[a-z]+State)$/;
586
+ const factoryFunctions = ['register', 'createAction', 'defineAction', 'makeAction'];
587
+
588
+ const isFactoryCall = (node) => {
589
+ if (node.type !== 'call_expression') return false;
590
+ const funcNode = node.childForFieldName('function');
591
+ if (!funcNode) return false;
592
+ const funcName = funcNode.type === 'identifier' ? funcNode.text : null;
593
+ return funcName && factoryFunctions.includes(funcName);
594
+ };
595
+
596
+ traverseTree(tree.rootNode, (node) => {
597
+ if (node.type === 'lexical_declaration' || node.type === 'variable_declaration') {
598
+ for (let i = 0; i < node.namedChildCount; i++) {
599
+ const declarator = node.namedChild(i);
600
+ if (declarator.type === 'variable_declarator') {
601
+ const nameNode = declarator.childForFieldName('name');
602
+ const valueNode = declarator.childForFieldName('value');
603
+
604
+ if (nameNode && valueNode) {
605
+ const name = nameNode.text;
606
+ const isObject = valueNode.type === 'object';
607
+ const isArray = valueNode.type === 'array';
608
+
609
+ if ((isObject || isArray) && statePattern.test(name)) {
610
+ const { startLine, endLine } = nodeToLocation(node, code);
611
+ objects.push({ name, startLine, endLine });
612
+ } else if (isFactoryCall(valueNode) && (actionPattern.test(name) || statePattern.test(name))) {
613
+ const { startLine, endLine } = nodeToLocation(node, code);
614
+ objects.push({ name, startLine, endLine });
615
+ }
616
+ }
617
+ }
618
+ }
619
+ }
620
+ return true;
621
+ });
622
+
623
+ objects.sort((a, b) => a.startLine - b.startLine);
624
+ return objects;
625
+ }
626
+
627
+ /**
628
+ * Parse a JavaScript/TypeScript file completely
629
+ * @param {string} code - Source code
630
+ * @param {object} parser - Tree-sitter parser instance
631
+ * @returns {ParseResult}
632
+ */
633
+ function parse(code, parser) {
634
+ return {
635
+ language: 'javascript',
636
+ totalLines: code.split('\n').length,
637
+ functions: findFunctions(code, parser),
638
+ classes: findClasses(code, parser),
639
+ stateObjects: findStateObjects(code, parser),
640
+ imports: [], // Handled by core/imports.js
641
+ exports: [] // Handled by core/imports.js
642
+ };
643
+ }
644
+
645
+ /**
646
+ * Find all function calls in code using tree-sitter AST
647
+ * Returns calls with their names and line numbers, properly excluding
648
+ * calls that appear in comments, strings, and regex literals.
649
+ *
650
+ * @param {string} code - Source code to analyze
651
+ * @param {object} parser - Tree-sitter parser instance
652
+ * @returns {Array<{name: string, line: number, isMethod: boolean, receiver?: string, isConstructor?: boolean}>}
653
+ */
654
+ function findCallsInCode(code, parser) {
655
+ const tree = parser.parse(code, undefined, PARSE_OPTIONS);
656
+ const calls = [];
657
+ const functionStack = []; // Stack of { name, startLine, endLine }
658
+
659
+ // Helper to check if a node creates a function scope
660
+ const isFunctionNode = (node) => {
661
+ return ['function_declaration', 'function_expression', 'arrow_function',
662
+ 'method_definition', 'generator_function_declaration', 'generator_function'].includes(node.type);
663
+ };
664
+
665
+ // Helper to extract function name from a function node
666
+ const extractFunctionName = (node) => {
667
+ if (node.type === 'function_declaration' || node.type === 'generator_function_declaration') {
668
+ const nameNode = node.childForFieldName('name');
669
+ return nameNode?.text || '<anonymous>';
670
+ }
671
+ if (node.type === 'method_definition') {
672
+ const nameNode = node.childForFieldName('name');
673
+ return nameNode?.text || '<anonymous>';
674
+ }
675
+ if (node.type === 'function_expression' || node.type === 'generator_function') {
676
+ const nameNode = node.childForFieldName('name');
677
+ return nameNode?.text || '<anonymous>';
678
+ }
679
+ if (node.type === 'arrow_function') {
680
+ // Arrow functions don't have names, but check parent for variable assignment
681
+ const parent = node.parent;
682
+ if (parent?.type === 'variable_declarator') {
683
+ const nameNode = parent.childForFieldName('name');
684
+ return nameNode?.text || '<anonymous>';
685
+ }
686
+ if (parent?.type === 'pair') {
687
+ const keyNode = parent.childForFieldName('key');
688
+ return keyNode?.text || '<anonymous>';
689
+ }
690
+ return '<anonymous>';
691
+ }
692
+ return '<anonymous>';
693
+ };
694
+
695
+ // Helper to get current enclosing function
696
+ const getCurrentEnclosingFunction = () => {
697
+ return functionStack.length > 0
698
+ ? { ...functionStack[functionStack.length - 1] }
699
+ : null;
700
+ };
701
+
702
+ traverseTree(tree.rootNode, (node) => {
703
+ // Track function entry
704
+ if (isFunctionNode(node)) {
705
+ functionStack.push({
706
+ name: extractFunctionName(node),
707
+ startLine: node.startPosition.row + 1,
708
+ endLine: node.endPosition.row + 1
709
+ });
710
+ }
711
+
712
+ // Handle regular function calls: foo(), obj.foo(), foo.call()
713
+ if (node.type === 'call_expression') {
714
+ const funcNode = node.childForFieldName('function');
715
+ if (!funcNode) return true;
716
+
717
+ const enclosingFunction = getCurrentEnclosingFunction();
718
+
719
+ if (funcNode.type === 'identifier') {
720
+ // Direct call: foo()
721
+ calls.push({
722
+ name: funcNode.text,
723
+ line: node.startPosition.row + 1,
724
+ isMethod: false,
725
+ enclosingFunction
726
+ });
727
+ } else if (funcNode.type === 'member_expression') {
728
+ // Method call: obj.foo() or foo.call/apply/bind()
729
+ const propNode = funcNode.childForFieldName('property');
730
+ const objNode = funcNode.childForFieldName('object');
731
+
732
+ if (propNode) {
733
+ const propName = propNode.text;
734
+
735
+ // Handle .call(), .apply(), .bind() - these are calls TO the object
736
+ if (['call', 'apply', 'bind'].includes(propName) && objNode) {
737
+ if (objNode.type === 'identifier') {
738
+ // foo.call() -> call to foo
739
+ calls.push({
740
+ name: objNode.text,
741
+ line: node.startPosition.row + 1,
742
+ isMethod: false,
743
+ enclosingFunction
744
+ });
745
+ } else if (objNode.type === 'member_expression') {
746
+ // obj.foo.call() -> method call to foo
747
+ const innerProp = objNode.childForFieldName('property');
748
+ const innerObj = objNode.childForFieldName('object');
749
+ if (innerProp) {
750
+ calls.push({
751
+ name: innerProp.text,
752
+ line: node.startPosition.row + 1,
753
+ isMethod: true,
754
+ receiver: innerObj?.text,
755
+ enclosingFunction
756
+ });
757
+ }
758
+ }
759
+ } else {
760
+ // Regular method call: obj.foo()
761
+ // Extract receiver: handles identifiers (obj), this, super
762
+ let receiver = undefined;
763
+ if (objNode) {
764
+ if (objNode.type === 'identifier' || objNode.type === 'this' || objNode.type === 'super') {
765
+ receiver = objNode.text;
766
+ }
767
+ }
768
+ calls.push({
769
+ name: propName,
770
+ line: node.startPosition.row + 1,
771
+ isMethod: true,
772
+ receiver,
773
+ enclosingFunction
774
+ });
775
+ }
776
+ }
777
+ }
778
+ return true;
779
+ }
780
+
781
+ // Handle constructor calls: new Foo()
782
+ if (node.type === 'new_expression') {
783
+ const ctorNode = node.childForFieldName('constructor');
784
+ if (ctorNode) {
785
+ const enclosingFunction = getCurrentEnclosingFunction();
786
+
787
+ if (ctorNode.type === 'identifier') {
788
+ calls.push({
789
+ name: ctorNode.text,
790
+ line: node.startPosition.row + 1,
791
+ isMethod: false,
792
+ isConstructor: true,
793
+ enclosingFunction
794
+ });
795
+ } else if (ctorNode.type === 'member_expression') {
796
+ // new obj.Foo() or new module.Class()
797
+ const propNode = ctorNode.childForFieldName('property');
798
+ if (propNode) {
799
+ calls.push({
800
+ name: propNode.text,
801
+ line: node.startPosition.row + 1,
802
+ isMethod: true,
803
+ isConstructor: true,
804
+ enclosingFunction
805
+ });
806
+ }
807
+ }
808
+ }
809
+ return true;
810
+ }
811
+
812
+ return true;
813
+ }, {
814
+ onLeave: (node) => {
815
+ if (isFunctionNode(node)) {
816
+ functionStack.pop();
817
+ }
818
+ }
819
+ });
820
+
821
+ return calls;
822
+ }
823
+
824
+ /**
825
+ * Find all callback usages - functions passed as arguments to other functions
826
+ * Detects patterns like: array.map(fn), addEventListener('click', handler), router.get('/path', handler)
827
+ * @param {string} code - Source code to analyze
828
+ * @param {string} name - Function name to look for
829
+ * @param {object} parser - Tree-sitter parser instance
830
+ * @returns {Array<{line: number, context: string, pattern: string}>}
831
+ */
832
+ function findCallbackUsages(code, name, parser) {
833
+ const tree = parser.parse(code, undefined, PARSE_OPTIONS);
834
+ const usages = [];
835
+
836
+ traverseTree(tree.rootNode, (node) => {
837
+ // Look for call expressions where our name is passed as an argument
838
+ if (node.type === 'call_expression') {
839
+ const argsNode = node.childForFieldName('arguments');
840
+ if (!argsNode) return true;
841
+
842
+ // Check each argument
843
+ for (let i = 0; i < argsNode.namedChildCount; i++) {
844
+ const arg = argsNode.namedChild(i);
845
+
846
+ // Direct identifier: map(fn), addEventListener('click', handler)
847
+ if (arg.type === 'identifier' && arg.text === name) {
848
+ const funcNode = node.childForFieldName('function');
849
+ let pattern = 'callback';
850
+
851
+ // Detect specific patterns
852
+ if (funcNode) {
853
+ if (funcNode.type === 'member_expression') {
854
+ const prop = funcNode.childForFieldName('property');
855
+ if (prop) {
856
+ const methodName = prop.text;
857
+ // Higher-order array methods
858
+ if (['map', 'filter', 'reduce', 'forEach', 'find', 'some', 'every', 'flatMap', 'sort'].includes(methodName)) {
859
+ pattern = 'array-method';
860
+ }
861
+ // Event listeners
862
+ else if (['addEventListener', 'removeEventListener', 'on', 'once', 'off', 'emit'].includes(methodName)) {
863
+ pattern = 'event-handler';
864
+ }
865
+ // Router/middleware
866
+ else if (['get', 'post', 'put', 'delete', 'patch', 'use', 'all', 'route'].includes(methodName)) {
867
+ pattern = 'route-handler';
868
+ }
869
+ // Promise methods
870
+ else if (['then', 'catch', 'finally'].includes(methodName)) {
871
+ pattern = 'promise-handler';
872
+ }
873
+ }
874
+ }
875
+ }
876
+
877
+ usages.push({
878
+ line: node.startPosition.row + 1,
879
+ context: node.text.substring(0, 80),
880
+ pattern
881
+ });
882
+ }
883
+
884
+ // Member expression: use obj.handler
885
+ if (arg.type === 'member_expression') {
886
+ const prop = arg.childForFieldName('property');
887
+ if (prop && prop.text === name) {
888
+ usages.push({
889
+ line: node.startPosition.row + 1,
890
+ context: node.text.substring(0, 80),
891
+ pattern: 'method-reference'
892
+ });
893
+ }
894
+ }
895
+ }
896
+ return true;
897
+ }
898
+
899
+ // Look for JSX event handlers: onClick={handler}
900
+ if (node.type === 'jsx_attribute') {
901
+ const valueNode = node.childForFieldName('value');
902
+ if (valueNode && valueNode.type === 'jsx_expression') {
903
+ for (let i = 0; i < valueNode.namedChildCount; i++) {
904
+ const expr = valueNode.namedChild(i);
905
+ if (expr.type === 'identifier' && expr.text === name) {
906
+ usages.push({
907
+ line: node.startPosition.row + 1,
908
+ context: node.text,
909
+ pattern: 'jsx-handler'
910
+ });
911
+ }
912
+ }
913
+ }
914
+ return true;
915
+ }
916
+
917
+ return true;
918
+ });
919
+
920
+ return usages;
921
+ }
922
+
923
+ /**
924
+ * Find re-exports: export { fn } from './module'
925
+ * @param {string} code - Source code to analyze
926
+ * @param {object} parser - Tree-sitter parser instance
927
+ * @returns {Array<{name: string, from: string, line: number}>}
928
+ */
929
+ function findReExports(code, parser) {
930
+ const tree = parser.parse(code, undefined, PARSE_OPTIONS);
931
+ const reExports = [];
932
+
933
+ traverseTree(tree.rootNode, (node) => {
934
+ // export { name } from './module'
935
+ if (node.type === 'export_statement') {
936
+ let hasFrom = false;
937
+ let fromModule = null;
938
+ const names = [];
939
+
940
+ for (let i = 0; i < node.namedChildCount; i++) {
941
+ const child = node.namedChild(i);
942
+ if (child.type === 'string') {
943
+ fromModule = child.text.slice(1, -1);
944
+ hasFrom = true;
945
+ }
946
+ if (child.type === 'export_clause') {
947
+ for (let j = 0; j < child.namedChildCount; j++) {
948
+ const specifier = child.namedChild(j);
949
+ if (specifier.type === 'export_specifier') {
950
+ const nameNode = specifier.childForFieldName('name') || specifier.namedChild(0);
951
+ if (nameNode) {
952
+ names.push(nameNode.text);
953
+ }
954
+ }
955
+ }
956
+ }
957
+ }
958
+
959
+ if (hasFrom && fromModule && names.length > 0) {
960
+ for (const name of names) {
961
+ reExports.push({
962
+ name,
963
+ from: fromModule,
964
+ line: node.startPosition.row + 1
965
+ });
966
+ }
967
+ }
968
+ }
969
+ return true;
970
+ });
971
+
972
+ return reExports;
973
+ }
974
+
975
+ /**
976
+ * Find all imports in JavaScript/TypeScript code using tree-sitter AST
977
+ * @param {string} code - Source code to analyze
978
+ * @param {object} parser - Tree-sitter parser instance
979
+ * @returns {Array<{module: string, names: string[], type: string, line: number}>}
980
+ */
981
+ function findImportsInCode(code, parser) {
982
+ const tree = parser.parse(code, undefined, PARSE_OPTIONS);
983
+ const imports = [];
984
+
985
+ traverseTree(tree.rootNode, (node) => {
986
+ // ES6 import statements
987
+ if (node.type === 'import_statement') {
988
+ const line = node.startPosition.row + 1;
989
+ let modulePath = null;
990
+ const names = [];
991
+ let importType = 'named';
992
+
993
+ // Find the module path (string node)
994
+ for (let i = 0; i < node.namedChildCount; i++) {
995
+ const child = node.namedChild(i);
996
+ if (child.type === 'string') {
997
+ // Extract text without quotes
998
+ const text = child.text;
999
+ modulePath = text.slice(1, -1);
1000
+ }
1001
+ if (child.type === 'import_clause') {
1002
+ // Process import clause
1003
+ for (let j = 0; j < child.namedChildCount; j++) {
1004
+ const clauseChild = child.namedChild(j);
1005
+ if (clauseChild.type === 'identifier') {
1006
+ // Default import: import foo from 'x'
1007
+ names.push(clauseChild.text);
1008
+ importType = 'default';
1009
+ } else if (clauseChild.type === 'named_imports') {
1010
+ // Named imports: import { a, b } from 'x'
1011
+ for (let k = 0; k < clauseChild.namedChildCount; k++) {
1012
+ const specifier = clauseChild.namedChild(k);
1013
+ if (specifier.type === 'import_specifier') {
1014
+ const nameNode = specifier.namedChild(0);
1015
+ if (nameNode) names.push(nameNode.text);
1016
+ }
1017
+ }
1018
+ importType = 'named';
1019
+ } else if (clauseChild.type === 'namespace_import') {
1020
+ // Namespace import: import * as foo from 'x'
1021
+ const nsName = clauseChild.childForFieldName('name') ||
1022
+ clauseChild.namedChild(0);
1023
+ if (nsName) names.push(nsName.text);
1024
+ importType = 'namespace';
1025
+ }
1026
+ }
1027
+ }
1028
+ }
1029
+
1030
+ if (modulePath) {
1031
+ if (names.length === 0) {
1032
+ // Side-effect import: import 'x'
1033
+ importType = 'side-effect';
1034
+ }
1035
+ imports.push({ module: modulePath, names, type: importType, line });
1036
+ }
1037
+ return true;
1038
+ }
1039
+
1040
+ // CommonJS require() calls
1041
+ if (node.type === 'call_expression') {
1042
+ const funcNode = node.childForFieldName('function');
1043
+ if (funcNode && funcNode.type === 'identifier' && funcNode.text === 'require') {
1044
+ const argsNode = node.childForFieldName('arguments');
1045
+ if (argsNode && argsNode.namedChildCount > 0) {
1046
+ const firstArg = argsNode.namedChild(0);
1047
+ if (firstArg && firstArg.type === 'string') {
1048
+ const modulePath = firstArg.text.slice(1, -1);
1049
+ const line = node.startPosition.row + 1;
1050
+ const names = [];
1051
+
1052
+ // Check parent for variable name
1053
+ let parent = node.parent;
1054
+ if (parent && parent.type === 'variable_declarator') {
1055
+ const nameNode = parent.childForFieldName('name');
1056
+ if (nameNode) {
1057
+ if (nameNode.type === 'identifier') {
1058
+ names.push(nameNode.text);
1059
+ } else if (nameNode.type === 'object_pattern') {
1060
+ // Destructuring: const { a, b } = require('x')
1061
+ for (let i = 0; i < nameNode.namedChildCount; i++) {
1062
+ const prop = nameNode.namedChild(i);
1063
+ if (prop.type === 'shorthand_property_identifier_pattern') {
1064
+ names.push(prop.text);
1065
+ } else if (prop.type === 'pair_pattern') {
1066
+ const key = prop.childForFieldName('key');
1067
+ if (key) names.push(key.text);
1068
+ }
1069
+ }
1070
+ }
1071
+ }
1072
+ }
1073
+
1074
+ imports.push({ module: modulePath, names, type: 'require', line });
1075
+ }
1076
+ }
1077
+ }
1078
+
1079
+ // Dynamic import: import('x')
1080
+ if (funcNode && funcNode.type === 'import') {
1081
+ const argsNode = node.childForFieldName('arguments');
1082
+ if (argsNode && argsNode.namedChildCount > 0) {
1083
+ const firstArg = argsNode.namedChild(0);
1084
+ if (firstArg && firstArg.type === 'string') {
1085
+ const modulePath = firstArg.text.slice(1, -1);
1086
+ const line = node.startPosition.row + 1;
1087
+ imports.push({ module: modulePath, names: [], type: 'dynamic', line });
1088
+ }
1089
+ }
1090
+ }
1091
+ return true;
1092
+ }
1093
+
1094
+ return true;
1095
+ });
1096
+
1097
+ return imports;
1098
+ }
1099
+
1100
+ /**
1101
+ * Find all exports in JavaScript/TypeScript code using tree-sitter AST
1102
+ * @param {string} code - Source code to analyze
1103
+ * @param {object} parser - Tree-sitter parser instance
1104
+ * @returns {Array<{name: string, type: string, line: number, source?: string}>}
1105
+ */
1106
+ function findExportsInCode(code, parser) {
1107
+ const tree = parser.parse(code, undefined, PARSE_OPTIONS);
1108
+ const exports = [];
1109
+
1110
+ traverseTree(tree.rootNode, (node) => {
1111
+ // ES6 export statements
1112
+ if (node.type === 'export_statement') {
1113
+ const line = node.startPosition.row + 1;
1114
+ let source = null;
1115
+
1116
+ // Check for re-export source
1117
+ for (let i = 0; i < node.namedChildCount; i++) {
1118
+ const child = node.namedChild(i);
1119
+ if (child.type === 'string') {
1120
+ source = child.text.slice(1, -1);
1121
+ }
1122
+ }
1123
+
1124
+ // Check for export * from 'x'
1125
+ if (node.text.includes('export *') && source) {
1126
+ exports.push({ name: '*', type: 're-export-all', line, source });
1127
+ return true;
1128
+ }
1129
+
1130
+ // Check for export clause: export { a, b } or export { a } from 'x'
1131
+ for (let i = 0; i < node.namedChildCount; i++) {
1132
+ const child = node.namedChild(i);
1133
+ if (child.type === 'export_clause') {
1134
+ for (let j = 0; j < child.namedChildCount; j++) {
1135
+ const specifier = child.namedChild(j);
1136
+ if (specifier.type === 'export_specifier') {
1137
+ const nameNode = specifier.namedChild(0);
1138
+ if (nameNode) {
1139
+ const exportType = source ? 're-export' : 'named';
1140
+ exports.push({ name: nameNode.text, type: exportType, line, ...(source && { source }) });
1141
+ }
1142
+ }
1143
+ }
1144
+ return true;
1145
+ }
1146
+ }
1147
+
1148
+ // Named exports: export function/class/const
1149
+ for (let i = 0; i < node.namedChildCount; i++) {
1150
+ const child = node.namedChild(i);
1151
+ if (child.type === 'function_declaration' || child.type === 'generator_function_declaration') {
1152
+ const nameNode = child.childForFieldName('name');
1153
+ if (nameNode) {
1154
+ exports.push({ name: nameNode.text, type: 'named', line });
1155
+ }
1156
+ } else if (child.type === 'class_declaration') {
1157
+ const nameNode = child.childForFieldName('name');
1158
+ if (nameNode) {
1159
+ exports.push({ name: nameNode.text, type: 'named', line });
1160
+ }
1161
+ } else if (child.type === 'lexical_declaration' || child.type === 'variable_declaration') {
1162
+ for (let j = 0; j < child.namedChildCount; j++) {
1163
+ const declarator = child.namedChild(j);
1164
+ if (declarator.type === 'variable_declarator') {
1165
+ const nameNode = declarator.childForFieldName('name');
1166
+ if (nameNode && nameNode.type === 'identifier') {
1167
+ exports.push({ name: nameNode.text, type: 'named', line });
1168
+ }
1169
+ }
1170
+ }
1171
+ } else if (child.type === 'function_expression' || child.type === 'arrow_function' ||
1172
+ child.type === 'class' || child.type === 'identifier') {
1173
+ // export default ...
1174
+ const name = child.type === 'identifier' ? child.text : 'default';
1175
+ exports.push({ name, type: 'default', line });
1176
+ }
1177
+ }
1178
+
1179
+ // Check for export default with no declaration child found
1180
+ if (node.text.startsWith('export default') && exports.filter(e => e.line === line).length === 0) {
1181
+ exports.push({ name: 'default', type: 'default', line });
1182
+ }
1183
+
1184
+ return true;
1185
+ }
1186
+
1187
+ // CommonJS module.exports
1188
+ if (node.type === 'assignment_expression') {
1189
+ const leftNode = node.childForFieldName('left');
1190
+ if (leftNode && leftNode.type === 'member_expression') {
1191
+ const objNode = leftNode.childForFieldName('object');
1192
+ const propNode = leftNode.childForFieldName('property');
1193
+
1194
+ if (objNode && propNode) {
1195
+ // module.exports = ...
1196
+ if (objNode.text === 'module' && propNode.text === 'exports') {
1197
+ const line = node.startPosition.row + 1;
1198
+ const rightNode = node.childForFieldName('right');
1199
+ if (rightNode && rightNode.type === 'object') {
1200
+ // module.exports = { a, b }
1201
+ for (let i = 0; i < rightNode.namedChildCount; i++) {
1202
+ const prop = rightNode.namedChild(i);
1203
+ if (prop.type === 'shorthand_property_identifier') {
1204
+ exports.push({ name: prop.text, type: 'module.exports', line });
1205
+ } else if (prop.type === 'pair') {
1206
+ const key = prop.childForFieldName('key');
1207
+ if (key) exports.push({ name: key.text, type: 'module.exports', line });
1208
+ }
1209
+ }
1210
+ } else if (rightNode && rightNode.type === 'identifier') {
1211
+ // module.exports = something
1212
+ exports.push({ name: rightNode.text, type: 'module.exports', line });
1213
+ } else {
1214
+ exports.push({ name: 'default', type: 'module.exports', line });
1215
+ }
1216
+ return true;
1217
+ }
1218
+
1219
+ // exports.name = ...
1220
+ if (objNode.text === 'exports') {
1221
+ const line = node.startPosition.row + 1;
1222
+ exports.push({ name: propNode.text, type: 'exports', line });
1223
+ return true;
1224
+ }
1225
+ }
1226
+ }
1227
+ return true;
1228
+ }
1229
+
1230
+ return true;
1231
+ });
1232
+
1233
+ return exports;
1234
+ }
1235
+
1236
+ /**
1237
+ * Find all usages of a name in code using AST
1238
+ * @param {string} code - Source code
1239
+ * @param {string} name - Symbol name to find
1240
+ * @param {object} parser - Tree-sitter parser instance
1241
+ * @returns {Array<{line: number, column: number, usageType: string}>}
1242
+ */
1243
+ function findUsagesInCode(code, name, parser) {
1244
+ const tree = parser.parse(code, undefined, PARSE_OPTIONS);
1245
+ const usages = [];
1246
+
1247
+ traverseTree(tree.rootNode, (node) => {
1248
+ // Only look for identifiers with the matching name
1249
+ if (node.type !== 'identifier' || node.text !== name) {
1250
+ return true;
1251
+ }
1252
+
1253
+ const line = node.startPosition.row + 1;
1254
+ const column = node.startPosition.column;
1255
+ const parent = node.parent;
1256
+
1257
+ // Classify based on parent node
1258
+ let usageType = 'reference';
1259
+
1260
+ if (parent) {
1261
+ // Import: identifier inside import_specifier or import_clause
1262
+ if (parent.type === 'import_specifier' ||
1263
+ parent.type === 'import_clause' ||
1264
+ parent.type === 'namespace_import') {
1265
+ usageType = 'import';
1266
+ }
1267
+ // Call: identifier is function in call_expression
1268
+ else if (parent.type === 'call_expression' &&
1269
+ parent.childForFieldName('function') === node) {
1270
+ usageType = 'call';
1271
+ }
1272
+ // New expression: identifier is constructor
1273
+ else if (parent.type === 'new_expression' &&
1274
+ parent.childForFieldName('constructor') === node) {
1275
+ usageType = 'call';
1276
+ }
1277
+ // Definition: function name in declaration
1278
+ else if ((parent.type === 'function_declaration' ||
1279
+ parent.type === 'generator_function_declaration') &&
1280
+ parent.childForFieldName('name') === node) {
1281
+ usageType = 'definition';
1282
+ }
1283
+ // Definition: variable name in declarator (left side of =)
1284
+ else if (parent.type === 'variable_declarator' &&
1285
+ parent.childForFieldName('name') === node) {
1286
+ usageType = 'definition';
1287
+ }
1288
+ // Definition: class name
1289
+ else if (parent.type === 'class_declaration' &&
1290
+ parent.childForFieldName('name') === node) {
1291
+ usageType = 'definition';
1292
+ }
1293
+ // Definition: method name
1294
+ else if (parent.type === 'method_definition' &&
1295
+ parent.childForFieldName('name') === node) {
1296
+ usageType = 'definition';
1297
+ }
1298
+ // Definition: function expression name (named function expressions)
1299
+ else if (parent.type === 'function' &&
1300
+ parent.childForFieldName('name') === node) {
1301
+ usageType = 'definition';
1302
+ }
1303
+ // Require: identifier is the name in require('...')
1304
+ else if (parent.type === 'call_expression') {
1305
+ const func = parent.childForFieldName('function');
1306
+ if (func && func.text === 'require') {
1307
+ // This is inside require(), check if it's the name being assigned
1308
+ const grandparent = parent.parent;
1309
+ if (grandparent && grandparent.type === 'variable_declarator' &&
1310
+ grandparent.childForFieldName('name')?.text === name) {
1311
+ usageType = 'import';
1312
+ }
1313
+ }
1314
+ }
1315
+ // Property access (method call): a.name() - the name after dot
1316
+ else if (parent.type === 'member_expression' &&
1317
+ parent.childForFieldName('property') === node) {
1318
+ // Check if this is a method call
1319
+ const grandparent = parent.parent;
1320
+ if (grandparent && grandparent.type === 'call_expression') {
1321
+ usageType = 'call';
1322
+ } else {
1323
+ usageType = 'reference';
1324
+ }
1325
+ }
1326
+ }
1327
+
1328
+ usages.push({ line, column, usageType });
1329
+ return true;
1330
+ });
1331
+
1332
+ return usages;
1333
+ }
1334
+
1335
+ module.exports = {
1336
+ findFunctions,
1337
+ findClasses,
1338
+ findStateObjects,
1339
+ findCallsInCode,
1340
+ findCallbackUsages,
1341
+ findReExports,
1342
+ findImportsInCode,
1343
+ findExportsInCode,
1344
+ findUsagesInCode,
1345
+ parse
1346
+ };