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,950 @@
1
+ /**
2
+ * languages/rust.js - Tree-sitter based Rust parsing
3
+ *
4
+ * Handles: function definitions, struct/enum/trait/impl blocks,
5
+ * modules, macros, and const/static declarations.
6
+ */
7
+
8
+ const {
9
+ traverseTree,
10
+ nodeToLocation,
11
+ parseStructuredParams,
12
+ extractRustDocstring
13
+ } = require('./utils');
14
+ const { PARSE_OPTIONS } = require('./index');
15
+
16
+ /**
17
+ * Extract return type from Rust function
18
+ */
19
+ function extractReturnType(node) {
20
+ const returnTypeNode = node.childForFieldName('return_type');
21
+ if (returnTypeNode) {
22
+ let text = returnTypeNode.text.trim();
23
+ if (text.startsWith('->')) {
24
+ text = text.slice(2).trim();
25
+ }
26
+ return text || null;
27
+ }
28
+ return null;
29
+ }
30
+
31
+ /**
32
+ * Extract Rust parameters
33
+ */
34
+ function extractRustParams(paramsNode) {
35
+ if (!paramsNode) return '...';
36
+ const text = paramsNode.text;
37
+ let params = text.replace(/^\(|\)$/g, '').trim();
38
+ if (!params) return '...';
39
+ return params;
40
+ }
41
+
42
+ /**
43
+ * Extract visibility modifier
44
+ */
45
+ function extractVisibility(text) {
46
+ const firstLine = text.split('\n')[0];
47
+ if (firstLine.includes('pub(crate)')) return 'pub(crate)';
48
+ if (firstLine.includes('pub(self)')) return 'pub(self)';
49
+ if (firstLine.includes('pub(super)')) return 'pub(super)';
50
+ if (firstLine.includes('pub ')) return 'pub';
51
+ return null;
52
+ }
53
+
54
+ /**
55
+ * Find all functions in Rust code using tree-sitter
56
+ */
57
+ function findFunctions(code, parser) {
58
+ const tree = parser.parse(code, undefined, PARSE_OPTIONS);
59
+ const functions = [];
60
+ const processedRanges = new Set();
61
+
62
+ traverseTree(tree.rootNode, (node) => {
63
+ const rangeKey = `${node.startIndex}-${node.endIndex}`;
64
+
65
+ if (node.type === 'function_item') {
66
+ if (processedRanges.has(rangeKey)) return true;
67
+ processedRanges.add(rangeKey);
68
+
69
+ const nameNode = node.childForFieldName('name');
70
+ const paramsNode = node.childForFieldName('parameters');
71
+
72
+ if (nameNode) {
73
+ const { startLine, endLine, indent } = nodeToLocation(node, code);
74
+ const text = node.text;
75
+ const firstLine = text.split('\n')[0];
76
+
77
+ const isAsync = firstLine.includes('async ');
78
+ const isUnsafe = firstLine.includes('unsafe ');
79
+ const isConst = firstLine.includes('const fn');
80
+ const visibility = extractVisibility(text);
81
+ const returnType = extractReturnType(node);
82
+ const docstring = extractRustDocstring(code, startLine);
83
+ const generics = extractGenerics(node);
84
+
85
+ const modifiers = [];
86
+ if (visibility) modifiers.push(visibility);
87
+ if (isAsync) modifiers.push('async');
88
+ if (isUnsafe) modifiers.push('unsafe');
89
+ if (isConst) modifiers.push('const');
90
+
91
+ functions.push({
92
+ name: nameNode.text,
93
+ params: extractRustParams(paramsNode),
94
+ paramsStructured: parseStructuredParams(paramsNode, 'rust'),
95
+ startLine,
96
+ endLine,
97
+ indent,
98
+ modifiers,
99
+ ...(returnType && { returnType }),
100
+ ...(docstring && { docstring }),
101
+ ...(generics && { generics })
102
+ });
103
+ }
104
+ return true;
105
+ }
106
+
107
+ return true;
108
+ });
109
+
110
+ functions.sort((a, b) => a.startLine - b.startLine);
111
+ return functions;
112
+ }
113
+
114
+ /**
115
+ * Extract generics from a node
116
+ */
117
+ function extractGenerics(node) {
118
+ const typeParamsNode = node.childForFieldName('type_parameters');
119
+ if (typeParamsNode) {
120
+ return typeParamsNode.text;
121
+ }
122
+ return null;
123
+ }
124
+
125
+ /**
126
+ * Find all types (structs, enums, traits, impls) in Rust code
127
+ */
128
+ function findClasses(code, parser) {
129
+ const tree = parser.parse(code, undefined, PARSE_OPTIONS);
130
+ const types = [];
131
+ const processedRanges = new Set();
132
+
133
+ traverseTree(tree.rootNode, (node) => {
134
+ const rangeKey = `${node.startIndex}-${node.endIndex}`;
135
+
136
+ // Struct items
137
+ if (node.type === 'struct_item') {
138
+ if (processedRanges.has(rangeKey)) return true;
139
+ processedRanges.add(rangeKey);
140
+
141
+ const nameNode = node.childForFieldName('name');
142
+ if (nameNode) {
143
+ const { startLine, endLine } = nodeToLocation(node, code);
144
+ const docstring = extractRustDocstring(code, startLine);
145
+ const visibility = extractVisibility(node.text);
146
+ const generics = extractGenerics(node);
147
+ const members = extractStructFields(node, code);
148
+
149
+ types.push({
150
+ name: nameNode.text,
151
+ startLine,
152
+ endLine,
153
+ type: 'struct',
154
+ members,
155
+ modifiers: visibility ? [visibility] : [],
156
+ ...(docstring && { docstring }),
157
+ ...(generics && { generics })
158
+ });
159
+ }
160
+ return true;
161
+ }
162
+
163
+ // Enum items
164
+ if (node.type === 'enum_item') {
165
+ if (processedRanges.has(rangeKey)) return true;
166
+ processedRanges.add(rangeKey);
167
+
168
+ const nameNode = node.childForFieldName('name');
169
+ if (nameNode) {
170
+ const { startLine, endLine } = nodeToLocation(node, code);
171
+ const docstring = extractRustDocstring(code, startLine);
172
+ const visibility = extractVisibility(node.text);
173
+ const generics = extractGenerics(node);
174
+
175
+ types.push({
176
+ name: nameNode.text,
177
+ startLine,
178
+ endLine,
179
+ type: 'enum',
180
+ members: [],
181
+ modifiers: visibility ? [visibility] : [],
182
+ ...(docstring && { docstring }),
183
+ ...(generics && { generics })
184
+ });
185
+ }
186
+ return true;
187
+ }
188
+
189
+ // Trait items
190
+ if (node.type === 'trait_item') {
191
+ if (processedRanges.has(rangeKey)) return true;
192
+ processedRanges.add(rangeKey);
193
+
194
+ const nameNode = node.childForFieldName('name');
195
+ if (nameNode) {
196
+ const { startLine, endLine } = nodeToLocation(node, code);
197
+ const docstring = extractRustDocstring(code, startLine);
198
+ const visibility = extractVisibility(node.text);
199
+ const generics = extractGenerics(node);
200
+
201
+ types.push({
202
+ name: nameNode.text,
203
+ startLine,
204
+ endLine,
205
+ type: 'trait',
206
+ members: [],
207
+ modifiers: visibility ? [visibility] : [],
208
+ ...(docstring && { docstring }),
209
+ ...(generics && { generics })
210
+ });
211
+ }
212
+ return true;
213
+ }
214
+
215
+ // Impl items
216
+ if (node.type === 'impl_item') {
217
+ if (processedRanges.has(rangeKey)) return true;
218
+ processedRanges.add(rangeKey);
219
+
220
+ const { startLine, endLine } = nodeToLocation(node, code);
221
+ const implInfo = extractImplInfo(node);
222
+ const docstring = extractRustDocstring(code, startLine);
223
+
224
+ types.push({
225
+ name: implInfo.name,
226
+ startLine,
227
+ endLine,
228
+ type: 'impl',
229
+ traitName: implInfo.traitName,
230
+ typeName: implInfo.typeName,
231
+ members: extractImplMembers(node, code),
232
+ modifiers: [],
233
+ ...(docstring && { docstring })
234
+ });
235
+ return false; // Don't traverse into impl body
236
+ }
237
+
238
+ // Module items
239
+ if (node.type === 'mod_item') {
240
+ if (processedRanges.has(rangeKey)) return true;
241
+ processedRanges.add(rangeKey);
242
+
243
+ const nameNode = node.childForFieldName('name');
244
+ if (nameNode) {
245
+ const { startLine, endLine } = nodeToLocation(node, code);
246
+ const docstring = extractRustDocstring(code, startLine);
247
+ const visibility = extractVisibility(node.text);
248
+
249
+ types.push({
250
+ name: nameNode.text,
251
+ startLine,
252
+ endLine,
253
+ type: 'module',
254
+ members: [],
255
+ modifiers: visibility ? [visibility] : [],
256
+ ...(docstring && { docstring })
257
+ });
258
+ }
259
+ return true;
260
+ }
261
+
262
+ // Macro definitions
263
+ if (node.type === 'macro_definition') {
264
+ if (processedRanges.has(rangeKey)) return true;
265
+ processedRanges.add(rangeKey);
266
+
267
+ const nameNode = node.childForFieldName('name');
268
+ if (nameNode) {
269
+ const { startLine, endLine } = nodeToLocation(node, code);
270
+ const docstring = extractRustDocstring(code, startLine);
271
+
272
+ types.push({
273
+ name: nameNode.text,
274
+ startLine,
275
+ endLine,
276
+ type: 'macro',
277
+ members: [],
278
+ modifiers: [],
279
+ ...(docstring && { docstring })
280
+ });
281
+ }
282
+ return true;
283
+ }
284
+
285
+ // Type aliases (only top-level, not inside traits/impls)
286
+ if (node.type === 'type_item') {
287
+ if (processedRanges.has(rangeKey)) return true;
288
+
289
+ // Skip if inside trait or impl
290
+ let parent = node.parent;
291
+ while (parent) {
292
+ if (parent.type === 'trait_item' || parent.type === 'impl_item') {
293
+ return true; // Skip this one
294
+ }
295
+ parent = parent.parent;
296
+ }
297
+
298
+ processedRanges.add(rangeKey);
299
+
300
+ const nameNode = node.childForFieldName('name');
301
+ if (nameNode) {
302
+ const { startLine, endLine } = nodeToLocation(node, code);
303
+ const docstring = extractRustDocstring(code, startLine);
304
+ const visibility = extractVisibility(node.text);
305
+
306
+ types.push({
307
+ name: nameNode.text,
308
+ startLine,
309
+ endLine,
310
+ type: 'type',
311
+ members: [],
312
+ modifiers: visibility ? [visibility] : [],
313
+ ...(docstring && { docstring })
314
+ });
315
+ }
316
+ return true;
317
+ }
318
+
319
+ return true;
320
+ });
321
+
322
+ types.sort((a, b) => a.startLine - b.startLine);
323
+ return types;
324
+ }
325
+
326
+ /**
327
+ * Extract struct fields
328
+ */
329
+ function extractStructFields(structNode, code) {
330
+ const fields = [];
331
+ const bodyNode = structNode.childForFieldName('body');
332
+ if (!bodyNode) return fields;
333
+
334
+ for (let i = 0; i < bodyNode.namedChildCount; i++) {
335
+ const field = bodyNode.namedChild(i);
336
+ if (field.type === 'field_declaration') {
337
+ const { startLine, endLine } = nodeToLocation(field, code);
338
+ const nameNode = field.childForFieldName('name');
339
+ const typeNode = field.childForFieldName('type');
340
+
341
+ if (nameNode) {
342
+ fields.push({
343
+ name: nameNode.text,
344
+ startLine,
345
+ endLine,
346
+ memberType: 'field',
347
+ ...(typeNode && { fieldType: typeNode.text })
348
+ });
349
+ }
350
+ }
351
+ }
352
+
353
+ return fields;
354
+ }
355
+
356
+ /**
357
+ * Extract impl block info
358
+ */
359
+ function extractImplInfo(implNode) {
360
+ let traitName = null;
361
+ let typeName = null;
362
+ const typeParamsNode = implNode.childForFieldName('type_parameters');
363
+ const typeParams = typeParamsNode ? typeParamsNode.text.trim() : '';
364
+
365
+ const traitNode = implNode.childForFieldName('trait');
366
+ const typeNode = implNode.childForFieldName('type');
367
+
368
+ if (traitNode) {
369
+ traitName = traitNode.text;
370
+ }
371
+
372
+ if (typeNode) {
373
+ typeName = typeNode.text;
374
+ }
375
+
376
+ const prefix = typeParams ? `${typeParams} ` : '';
377
+ let name;
378
+ if (traitName && typeName) {
379
+ name = `${prefix}${traitName} for ${typeName}`;
380
+ } else if (typeName) {
381
+ name = `${prefix}${typeName}`;
382
+ } else {
383
+ const text = implNode.text;
384
+ const match = text.match(/impl\s*(?:<[^>]+>\s*)?(\w+(?:\s+for\s+\w+)?)/);
385
+ name = match ? `${prefix}${match[1]}` : 'impl';
386
+ }
387
+
388
+ return { name, traitName, typeName };
389
+ }
390
+
391
+ /**
392
+ * Extract impl block members (functions)
393
+ */
394
+ function extractImplMembers(implNode, code) {
395
+ const members = [];
396
+ const bodyNode = implNode.childForFieldName('body');
397
+ if (!bodyNode) return members;
398
+
399
+ for (let i = 0; i < bodyNode.namedChildCount; i++) {
400
+ const child = bodyNode.namedChild(i);
401
+
402
+ if (child.type === 'function_item') {
403
+ const nameNode = child.childForFieldName('name');
404
+ const paramsNode = child.childForFieldName('parameters');
405
+
406
+ if (nameNode) {
407
+ const { startLine, endLine } = nodeToLocation(child, code);
408
+ const text = child.text;
409
+ const firstLine = text.split('\n')[0];
410
+ const returnType = extractReturnType(child);
411
+ const docstring = extractRustDocstring(code, startLine);
412
+ const visibility = extractVisibility(text);
413
+
414
+ members.push({
415
+ name: nameNode.text,
416
+ params: extractRustParams(paramsNode),
417
+ paramsStructured: parseStructuredParams(paramsNode, 'rust'),
418
+ startLine,
419
+ endLine,
420
+ memberType: visibility ? 'public' : 'method',
421
+ isAsync: firstLine.includes('async '),
422
+ ...(returnType && { returnType }),
423
+ ...(docstring && { docstring })
424
+ });
425
+ }
426
+ }
427
+ }
428
+
429
+ return members;
430
+ }
431
+
432
+ /**
433
+ * Find state objects (const/static) in Rust code
434
+ */
435
+ function findStateObjects(code, parser) {
436
+ const tree = parser.parse(code, undefined, PARSE_OPTIONS);
437
+ const objects = [];
438
+
439
+ const statePattern = /^([A-Z][A-Z0-9_]+|DEFAULT_[A-Z_]+)$/;
440
+
441
+ traverseTree(tree.rootNode, (node) => {
442
+ // Handle const items (only top-level)
443
+ if (node.type === 'const_item') {
444
+ if (!node.parent || node.parent.type !== 'source_file') return true;
445
+ const nameNode = node.childForFieldName('name');
446
+ if (nameNode) {
447
+ const name = nameNode.text;
448
+ if (statePattern.test(name)) {
449
+ const { startLine, endLine } = nodeToLocation(node, code);
450
+ objects.push({ name, startLine, endLine });
451
+ }
452
+ }
453
+ return true;
454
+ }
455
+
456
+ // Handle static items (only top-level)
457
+ if (node.type === 'static_item') {
458
+ if (!node.parent || node.parent.type !== 'source_file') return true;
459
+ const nameNode = node.childForFieldName('name');
460
+ if (nameNode) {
461
+ const name = nameNode.text;
462
+ if (statePattern.test(name)) {
463
+ const { startLine, endLine } = nodeToLocation(node, code);
464
+ objects.push({ name, startLine, endLine });
465
+ }
466
+ }
467
+ return true;
468
+ }
469
+
470
+ return true;
471
+ });
472
+
473
+ objects.sort((a, b) => a.startLine - b.startLine);
474
+ return objects;
475
+ }
476
+
477
+ /**
478
+ * Parse a Rust file completely
479
+ */
480
+ function parse(code, parser) {
481
+ return {
482
+ language: 'rust',
483
+ totalLines: code.split('\n').length,
484
+ functions: findFunctions(code, parser),
485
+ classes: findClasses(code, parser),
486
+ stateObjects: findStateObjects(code, parser),
487
+ imports: [],
488
+ exports: []
489
+ };
490
+ }
491
+
492
+ /**
493
+ * Find all function calls in Rust code using tree-sitter AST
494
+ * @param {string} code - Source code to analyze
495
+ * @param {object} parser - Tree-sitter parser instance
496
+ * @returns {Array<{name: string, line: number, isMethod: boolean, receiver?: string, isMacro?: boolean}>}
497
+ */
498
+ function findCallsInCode(code, parser) {
499
+ const tree = parser.parse(code, undefined, PARSE_OPTIONS);
500
+ const calls = [];
501
+ const functionStack = []; // Stack of { name, startLine, endLine }
502
+
503
+ // Helper to check if a node creates a function scope
504
+ const isFunctionNode = (node) => {
505
+ return ['function_item', 'closure_expression'].includes(node.type);
506
+ };
507
+
508
+ // Helper to extract function name from a function node
509
+ const extractFunctionName = (node) => {
510
+ if (node.type === 'function_item') {
511
+ const nameNode = node.childForFieldName('name');
512
+ return nameNode?.text || '<anonymous>';
513
+ }
514
+ if (node.type === 'closure_expression') {
515
+ return '<closure>';
516
+ }
517
+ return '<anonymous>';
518
+ };
519
+
520
+ // Helper to get current enclosing function
521
+ const getCurrentEnclosingFunction = () => {
522
+ return functionStack.length > 0
523
+ ? { ...functionStack[functionStack.length - 1] }
524
+ : null;
525
+ };
526
+
527
+ traverseTree(tree.rootNode, (node) => {
528
+ // Track function entry
529
+ if (isFunctionNode(node)) {
530
+ functionStack.push({
531
+ name: extractFunctionName(node),
532
+ startLine: node.startPosition.row + 1,
533
+ endLine: node.endPosition.row + 1
534
+ });
535
+ }
536
+
537
+ // Handle function calls: foo(), obj.method(), Type::func()
538
+ if (node.type === 'call_expression') {
539
+ const funcNode = node.childForFieldName('function');
540
+ if (!funcNode) return true;
541
+
542
+ const enclosingFunction = getCurrentEnclosingFunction();
543
+
544
+ if (funcNode.type === 'identifier') {
545
+ // Direct call: foo()
546
+ calls.push({
547
+ name: funcNode.text,
548
+ line: node.startPosition.row + 1,
549
+ isMethod: false,
550
+ enclosingFunction
551
+ });
552
+ } else if (funcNode.type === 'field_expression') {
553
+ // Method call: obj.method()
554
+ const fieldNode = funcNode.childForFieldName('field');
555
+ const valueNode = funcNode.childForFieldName('value');
556
+
557
+ if (fieldNode) {
558
+ calls.push({
559
+ name: fieldNode.text,
560
+ line: node.startPosition.row + 1,
561
+ isMethod: true,
562
+ receiver: valueNode?.type === 'identifier' ? valueNode.text : undefined,
563
+ enclosingFunction
564
+ });
565
+ }
566
+ } else if (funcNode.type === 'scoped_identifier') {
567
+ // Path call: Type::func() or module::func()
568
+ // Get the last segment of the path
569
+ const pathText = funcNode.text;
570
+ const segments = pathText.split('::');
571
+ const name = segments[segments.length - 1];
572
+ calls.push({
573
+ name: name,
574
+ line: node.startPosition.row + 1,
575
+ isMethod: segments.length > 1,
576
+ receiver: segments.length > 1 ? segments.slice(0, -1).join('::') : undefined,
577
+ enclosingFunction
578
+ });
579
+ }
580
+ return true;
581
+ }
582
+
583
+ // Handle macro invocations: println!(), vec![]
584
+ if (node.type === 'macro_invocation') {
585
+ const macroNode = node.childForFieldName('macro');
586
+ if (macroNode) {
587
+ let macroName = macroNode.text;
588
+ // Remove the trailing ! if present in the name
589
+ if (macroName.endsWith('!')) {
590
+ macroName = macroName.slice(0, -1);
591
+ }
592
+ const enclosingFunction = getCurrentEnclosingFunction();
593
+ calls.push({
594
+ name: macroName,
595
+ line: node.startPosition.row + 1,
596
+ isMethod: false,
597
+ isMacro: true,
598
+ enclosingFunction
599
+ });
600
+ }
601
+ return true;
602
+ }
603
+
604
+ return true;
605
+ }, {
606
+ onLeave: (node) => {
607
+ if (isFunctionNode(node)) {
608
+ functionStack.pop();
609
+ }
610
+ }
611
+ });
612
+
613
+ return calls;
614
+ }
615
+
616
+ /**
617
+ * Find all imports in Rust code using tree-sitter AST
618
+ * @param {string} code - Source code to analyze
619
+ * @param {object} parser - Tree-sitter parser instance
620
+ * @returns {Array<{module: string, names: string[], type: string, line: number}>}
621
+ */
622
+ function findImportsInCode(code, parser) {
623
+ const tree = parser.parse(code, undefined, PARSE_OPTIONS);
624
+ const imports = [];
625
+
626
+ traverseTree(tree.rootNode, (node) => {
627
+ // use declarations
628
+ if (node.type === 'use_declaration') {
629
+ const line = node.startPosition.row + 1;
630
+
631
+ for (let i = 0; i < node.namedChildCount; i++) {
632
+ const child = node.namedChild(i);
633
+
634
+ if (child.type === 'scoped_identifier' || child.type === 'identifier') {
635
+ // use std::io or use foo
636
+ const path = child.text;
637
+ const segments = path.split('::');
638
+ imports.push({
639
+ module: path,
640
+ names: [segments[segments.length - 1]],
641
+ type: 'use',
642
+ line
643
+ });
644
+ } else if (child.type === 'use_wildcard') {
645
+ // use std::collections::*
646
+ const scopedId = child.namedChild(0);
647
+ if (scopedId) {
648
+ imports.push({
649
+ module: scopedId.text,
650
+ names: ['*'],
651
+ type: 'use-glob',
652
+ line
653
+ });
654
+ }
655
+ } else if (child.type === 'use_list' || child.type === 'scoped_use_list') {
656
+ // use std::{io, fs} or use foo::{bar, baz}
657
+ // Extract the base path and names
658
+ const pathNode = child.childForFieldName('path');
659
+ const listNode = child.childForFieldName('list');
660
+
661
+ if (pathNode && listNode) {
662
+ const basePath = pathNode.text;
663
+ const names = [];
664
+ for (let j = 0; j < listNode.namedChildCount; j++) {
665
+ const item = listNode.namedChild(j);
666
+ if (item.type === 'identifier') {
667
+ names.push(item.text);
668
+ } else if (item.type === 'use_as_clause') {
669
+ const nameNode = item.namedChild(0);
670
+ if (nameNode) names.push(nameNode.text);
671
+ }
672
+ }
673
+ imports.push({
674
+ module: basePath,
675
+ names,
676
+ type: 'use',
677
+ line
678
+ });
679
+ }
680
+ }
681
+ }
682
+ return true;
683
+ }
684
+
685
+ // mod declarations (external module imports)
686
+ if (node.type === 'mod_item') {
687
+ const line = node.startPosition.row + 1;
688
+ const nameNode = node.childForFieldName('name');
689
+
690
+ // Only count mod declarations without body (file-based modules)
691
+ const hasBody = node.namedChildren.some(c => c.type === 'declaration_list');
692
+
693
+ if (nameNode && !hasBody) {
694
+ imports.push({
695
+ module: nameNode.text,
696
+ names: [nameNode.text],
697
+ type: 'mod',
698
+ line
699
+ });
700
+ }
701
+ return true;
702
+ }
703
+
704
+ return true;
705
+ });
706
+
707
+ return imports;
708
+ }
709
+
710
+ /**
711
+ * Find all exports in Rust code using tree-sitter AST
712
+ * In Rust, exports are pub items
713
+ * @param {string} code - Source code to analyze
714
+ * @param {object} parser - Tree-sitter parser instance
715
+ * @returns {Array<{name: string, type: string, line: number}>}
716
+ */
717
+ function findExportsInCode(code, parser) {
718
+ const tree = parser.parse(code, undefined, PARSE_OPTIONS);
719
+ const exports = [];
720
+
721
+ function hasVisibility(node) {
722
+ for (let i = 0; i < node.namedChildCount; i++) {
723
+ const child = node.namedChild(i);
724
+ if (child.type === 'visibility_modifier') {
725
+ return true;
726
+ }
727
+ }
728
+ return false;
729
+ }
730
+
731
+ traverseTree(tree.rootNode, (node) => {
732
+ // Public functions
733
+ if (node.type === 'function_item' && hasVisibility(node)) {
734
+ const nameNode = node.childForFieldName('name');
735
+ if (nameNode) {
736
+ exports.push({
737
+ name: nameNode.text,
738
+ type: 'function',
739
+ line: node.startPosition.row + 1
740
+ });
741
+ }
742
+ return true;
743
+ }
744
+
745
+ // Public structs
746
+ if (node.type === 'struct_item' && hasVisibility(node)) {
747
+ const nameNode = node.childForFieldName('name');
748
+ if (nameNode) {
749
+ exports.push({
750
+ name: nameNode.text,
751
+ type: 'struct',
752
+ line: node.startPosition.row + 1
753
+ });
754
+ }
755
+ return true;
756
+ }
757
+
758
+ // Public enums
759
+ if (node.type === 'enum_item' && hasVisibility(node)) {
760
+ const nameNode = node.childForFieldName('name');
761
+ if (nameNode) {
762
+ exports.push({
763
+ name: nameNode.text,
764
+ type: 'enum',
765
+ line: node.startPosition.row + 1
766
+ });
767
+ }
768
+ return true;
769
+ }
770
+
771
+ // Public traits
772
+ if (node.type === 'trait_item' && hasVisibility(node)) {
773
+ const nameNode = node.childForFieldName('name');
774
+ if (nameNode) {
775
+ exports.push({
776
+ name: nameNode.text,
777
+ type: 'trait',
778
+ line: node.startPosition.row + 1
779
+ });
780
+ }
781
+ return true;
782
+ }
783
+
784
+ // Public modules
785
+ if (node.type === 'mod_item' && hasVisibility(node)) {
786
+ const nameNode = node.childForFieldName('name');
787
+ if (nameNode) {
788
+ exports.push({
789
+ name: nameNode.text,
790
+ type: 'module',
791
+ line: node.startPosition.row + 1
792
+ });
793
+ }
794
+ return true;
795
+ }
796
+
797
+ // Public type aliases
798
+ if (node.type === 'type_item' && hasVisibility(node)) {
799
+ const nameNode = node.childForFieldName('name');
800
+ if (nameNode) {
801
+ exports.push({
802
+ name: nameNode.text,
803
+ type: 'type',
804
+ line: node.startPosition.row + 1
805
+ });
806
+ }
807
+ return true;
808
+ }
809
+
810
+ // Public const
811
+ if (node.type === 'const_item' && hasVisibility(node)) {
812
+ const nameNode = node.childForFieldName('name');
813
+ if (nameNode) {
814
+ exports.push({
815
+ name: nameNode.text,
816
+ type: 'const',
817
+ line: node.startPosition.row + 1
818
+ });
819
+ }
820
+ return true;
821
+ }
822
+
823
+ // Public static
824
+ if (node.type === 'static_item' && hasVisibility(node)) {
825
+ const nameNode = node.childForFieldName('name');
826
+ if (nameNode) {
827
+ exports.push({
828
+ name: nameNode.text,
829
+ type: 'static',
830
+ line: node.startPosition.row + 1
831
+ });
832
+ }
833
+ return true;
834
+ }
835
+
836
+ return true;
837
+ });
838
+
839
+ return exports;
840
+ }
841
+
842
+ /**
843
+ * Find all usages of a name in code using AST
844
+ * @param {string} code - Source code
845
+ * @param {string} name - Symbol name to find
846
+ * @param {object} parser - Tree-sitter parser instance
847
+ * @returns {Array<{line: number, column: number, usageType: string}>}
848
+ */
849
+ function findUsagesInCode(code, name, parser) {
850
+ const tree = parser.parse(code, undefined, PARSE_OPTIONS);
851
+ const usages = [];
852
+
853
+ traverseTree(tree.rootNode, (node) => {
854
+ // Only look for identifiers with the matching name
855
+ if (node.type !== 'identifier' || node.text !== name) {
856
+ return true;
857
+ }
858
+
859
+ const line = node.startPosition.row + 1;
860
+ const column = node.startPosition.column;
861
+ const parent = node.parent;
862
+
863
+ let usageType = 'reference';
864
+
865
+ if (parent) {
866
+ // Import: use path::name
867
+ if (parent.type === 'use_declaration' ||
868
+ parent.type === 'use_as_clause' ||
869
+ parent.type === 'scoped_identifier' && parent.parent?.type === 'use_declaration') {
870
+ usageType = 'import';
871
+ }
872
+ // Call: name()
873
+ else if (parent.type === 'call_expression' &&
874
+ parent.childForFieldName('function') === node) {
875
+ usageType = 'call';
876
+ }
877
+ // Macro invocation: name!
878
+ else if (parent.type === 'macro_invocation') {
879
+ const macroNode = parent.childForFieldName('macro');
880
+ if (macroNode === node) {
881
+ usageType = 'call';
882
+ }
883
+ }
884
+ // Definition: fn name
885
+ else if (parent.type === 'function_item' &&
886
+ parent.childForFieldName('name') === node) {
887
+ usageType = 'definition';
888
+ }
889
+ // Definition: struct name
890
+ else if (parent.type === 'struct_item' &&
891
+ parent.childForFieldName('name') === node) {
892
+ usageType = 'definition';
893
+ }
894
+ // Definition: enum name
895
+ else if (parent.type === 'enum_item' &&
896
+ parent.childForFieldName('name') === node) {
897
+ usageType = 'definition';
898
+ }
899
+ // Definition: impl for Type
900
+ else if (parent.type === 'impl_item') {
901
+ usageType = 'definition';
902
+ }
903
+ // Definition: type alias
904
+ else if (parent.type === 'type_item' &&
905
+ parent.childForFieldName('name') === node) {
906
+ usageType = 'definition';
907
+ }
908
+ // Definition: let binding
909
+ else if (parent.type === 'let_declaration' &&
910
+ parent.childForFieldName('pattern')?.text === name) {
911
+ usageType = 'definition';
912
+ }
913
+ // Definition: const/static
914
+ else if ((parent.type === 'const_item' || parent.type === 'static_item') &&
915
+ parent.childForFieldName('name') === node) {
916
+ usageType = 'definition';
917
+ }
918
+ // Definition: parameter
919
+ else if (parent.type === 'parameter') {
920
+ usageType = 'definition';
921
+ }
922
+ // Method call: obj.name()
923
+ else if (parent.type === 'field_expression' &&
924
+ parent.childForFieldName('field') === node) {
925
+ const grandparent = parent.parent;
926
+ if (grandparent && grandparent.type === 'call_expression') {
927
+ usageType = 'call';
928
+ } else {
929
+ usageType = 'reference';
930
+ }
931
+ }
932
+ }
933
+
934
+ usages.push({ line, column, usageType });
935
+ return true;
936
+ });
937
+
938
+ return usages;
939
+ }
940
+
941
+ module.exports = {
942
+ findFunctions,
943
+ findClasses,
944
+ findStateObjects,
945
+ findCallsInCode,
946
+ findImportsInCode,
947
+ findExportsInCode,
948
+ findUsagesInCode,
949
+ parse
950
+ };