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,826 @@
1
+ /**
2
+ * languages/java.js - Tree-sitter based Java parsing
3
+ *
4
+ * Handles: method declarations, constructors, class/interface/enum/record
5
+ * declarations, and static final constants.
6
+ */
7
+
8
+ const {
9
+ traverseTree,
10
+ nodeToLocation,
11
+ parseStructuredParams,
12
+ extractJavaDocstring
13
+ } = require('./utils');
14
+ const { PARSE_OPTIONS } = require('./index');
15
+
16
+ /**
17
+ * Extract Java parameters
18
+ */
19
+ function extractJavaParams(paramsNode) {
20
+ if (!paramsNode) return '...';
21
+ const text = paramsNode.text;
22
+ let params = text.replace(/^\(|\)$/g, '').trim();
23
+ if (!params) return '...';
24
+ return params;
25
+ }
26
+
27
+ /**
28
+ * Extract modifiers from a node
29
+ */
30
+ function extractModifiers(node) {
31
+ const modifiers = [];
32
+ const modifiersNode = node.childForFieldName('modifiers');
33
+
34
+ if (modifiersNode) {
35
+ for (let i = 0; i < modifiersNode.namedChildCount; i++) {
36
+ const mod = modifiersNode.namedChild(i);
37
+ if (mod.type === 'marker_annotation' || mod.type === 'annotation') {
38
+ continue; // Skip annotations for modifiers
39
+ }
40
+ modifiers.push(mod.text);
41
+ }
42
+ }
43
+
44
+ // Also check first line for modifiers
45
+ const text = node.text;
46
+ const firstLine = text.split('\n')[0];
47
+ const keywords = ['public', 'private', 'protected', 'static', 'final', 'abstract', 'synchronized', 'native', 'default'];
48
+ for (const kw of keywords) {
49
+ if (firstLine.includes(kw + ' ') && !modifiers.includes(kw)) {
50
+ modifiers.push(kw);
51
+ }
52
+ }
53
+
54
+ return [...new Set(modifiers)];
55
+ }
56
+
57
+ /**
58
+ * Extract annotations from a node
59
+ */
60
+ function extractAnnotations(node) {
61
+ const annotations = [];
62
+ const modifiersNode = node.childForFieldName('modifiers');
63
+
64
+ if (modifiersNode) {
65
+ for (let i = 0; i < modifiersNode.namedChildCount; i++) {
66
+ const mod = modifiersNode.namedChild(i);
67
+ if (mod.type === 'marker_annotation' || mod.type === 'annotation') {
68
+ annotations.push(mod.text);
69
+ }
70
+ }
71
+ }
72
+
73
+ return annotations;
74
+ }
75
+
76
+ /**
77
+ * Extract return type from method
78
+ */
79
+ function extractReturnType(node) {
80
+ const typeNode = node.childForFieldName('type');
81
+ if (typeNode) {
82
+ return typeNode.text;
83
+ }
84
+ return null;
85
+ }
86
+
87
+ /**
88
+ * Extract generics/type parameters
89
+ */
90
+ function extractGenerics(node) {
91
+ const typeParamsNode = node.childForFieldName('type_parameters');
92
+ if (typeParamsNode) {
93
+ return typeParamsNode.text;
94
+ }
95
+ return null;
96
+ }
97
+
98
+ /**
99
+ * Find all methods/constructors in Java code using tree-sitter
100
+ */
101
+ function findFunctions(code, parser) {
102
+ const tree = parser.parse(code, undefined, PARSE_OPTIONS);
103
+ const functions = [];
104
+ const processedRanges = new Set();
105
+
106
+ traverseTree(tree.rootNode, (node) => {
107
+ const rangeKey = `${node.startIndex}-${node.endIndex}`;
108
+
109
+ // Method declarations
110
+ if (node.type === 'method_declaration') {
111
+ if (processedRanges.has(rangeKey)) return true;
112
+ processedRanges.add(rangeKey);
113
+
114
+ const nameNode = node.childForFieldName('name');
115
+ const paramsNode = node.childForFieldName('parameters');
116
+
117
+ if (nameNode) {
118
+ const { startLine, endLine, indent } = nodeToLocation(node, code);
119
+ const modifiers = extractModifiers(node);
120
+ const annotations = extractAnnotations(node);
121
+ const returnType = extractReturnType(node);
122
+ const generics = extractGenerics(node);
123
+ const docstring = extractJavaDocstring(code, startLine);
124
+
125
+ functions.push({
126
+ name: nameNode.text,
127
+ params: extractJavaParams(paramsNode),
128
+ paramsStructured: parseStructuredParams(paramsNode, 'java'),
129
+ startLine,
130
+ endLine,
131
+ indent,
132
+ modifiers,
133
+ ...(returnType && { returnType }),
134
+ ...(generics && { generics }),
135
+ ...(docstring && { docstring }),
136
+ ...(annotations.length > 0 && { annotations })
137
+ });
138
+ }
139
+ return true;
140
+ }
141
+
142
+ // Constructor declarations
143
+ if (node.type === 'constructor_declaration') {
144
+ if (processedRanges.has(rangeKey)) return true;
145
+ processedRanges.add(rangeKey);
146
+
147
+ const nameNode = node.childForFieldName('name');
148
+ const paramsNode = node.childForFieldName('parameters');
149
+
150
+ if (nameNode) {
151
+ const { startLine, endLine, indent } = nodeToLocation(node, code);
152
+ const modifiers = extractModifiers(node);
153
+ const annotations = extractAnnotations(node);
154
+ const docstring = extractJavaDocstring(code, startLine);
155
+
156
+ functions.push({
157
+ name: nameNode.text,
158
+ params: extractJavaParams(paramsNode),
159
+ paramsStructured: parseStructuredParams(paramsNode, 'java'),
160
+ startLine,
161
+ endLine,
162
+ indent,
163
+ modifiers,
164
+ isConstructor: true,
165
+ ...(docstring && { docstring }),
166
+ ...(annotations.length > 0 && { annotations })
167
+ });
168
+ }
169
+ return true;
170
+ }
171
+
172
+ return true;
173
+ });
174
+
175
+ functions.sort((a, b) => a.startLine - b.startLine);
176
+ return functions;
177
+ }
178
+
179
+ /**
180
+ * Find all classes, interfaces, enums, records in Java code
181
+ */
182
+ function findClasses(code, parser) {
183
+ const tree = parser.parse(code, undefined, PARSE_OPTIONS);
184
+ const classes = [];
185
+ const processedRanges = new Set();
186
+
187
+ traverseTree(tree.rootNode, (node) => {
188
+ const rangeKey = `${node.startIndex}-${node.endIndex}`;
189
+
190
+ // Class declarations
191
+ if (node.type === 'class_declaration') {
192
+ if (processedRanges.has(rangeKey)) return true;
193
+ processedRanges.add(rangeKey);
194
+
195
+ const nameNode = node.childForFieldName('name');
196
+ if (nameNode) {
197
+ const { startLine, endLine } = nodeToLocation(node, code);
198
+ const members = extractClassMembers(node, code);
199
+ const modifiers = extractModifiers(node);
200
+ const annotations = extractAnnotations(node);
201
+ const docstring = extractJavaDocstring(code, startLine);
202
+ const generics = extractGenerics(node);
203
+ const extendsInfo = extractExtends(node);
204
+ const implementsInfo = extractImplements(node);
205
+
206
+ classes.push({
207
+ name: nameNode.text,
208
+ startLine,
209
+ endLine,
210
+ type: 'class',
211
+ members,
212
+ modifiers,
213
+ ...(docstring && { docstring }),
214
+ ...(generics && { generics }),
215
+ ...(annotations.length > 0 && { annotations }),
216
+ ...(extendsInfo && { extends: extendsInfo }),
217
+ ...(implementsInfo.length > 0 && { implements: implementsInfo })
218
+ });
219
+ }
220
+ return false; // Don't traverse into class body
221
+ }
222
+
223
+ // Interface declarations
224
+ if (node.type === 'interface_declaration') {
225
+ if (processedRanges.has(rangeKey)) return true;
226
+ processedRanges.add(rangeKey);
227
+
228
+ const nameNode = node.childForFieldName('name');
229
+ if (nameNode) {
230
+ const { startLine, endLine } = nodeToLocation(node, code);
231
+ const modifiers = extractModifiers(node);
232
+ const annotations = extractAnnotations(node);
233
+ const docstring = extractJavaDocstring(code, startLine);
234
+ const generics = extractGenerics(node);
235
+ const extendsInfo = extractInterfaceExtends(node);
236
+
237
+ classes.push({
238
+ name: nameNode.text,
239
+ startLine,
240
+ endLine,
241
+ type: 'interface',
242
+ members: [],
243
+ modifiers,
244
+ ...(docstring && { docstring }),
245
+ ...(generics && { generics }),
246
+ ...(annotations.length > 0 && { annotations }),
247
+ ...(extendsInfo.length > 0 && { extends: extendsInfo.join(', ') })
248
+ });
249
+ }
250
+ return false;
251
+ }
252
+
253
+ // Enum declarations
254
+ if (node.type === 'enum_declaration') {
255
+ if (processedRanges.has(rangeKey)) return true;
256
+ processedRanges.add(rangeKey);
257
+
258
+ const nameNode = node.childForFieldName('name');
259
+ if (nameNode) {
260
+ const { startLine, endLine } = nodeToLocation(node, code);
261
+ const modifiers = extractModifiers(node);
262
+ const annotations = extractAnnotations(node);
263
+ const docstring = extractJavaDocstring(code, startLine);
264
+
265
+ classes.push({
266
+ name: nameNode.text,
267
+ startLine,
268
+ endLine,
269
+ type: 'enum',
270
+ members: [],
271
+ modifiers,
272
+ ...(docstring && { docstring }),
273
+ ...(annotations.length > 0 && { annotations })
274
+ });
275
+ }
276
+ return false;
277
+ }
278
+
279
+ // Record declarations (Java 14+)
280
+ if (node.type === 'record_declaration') {
281
+ if (processedRanges.has(rangeKey)) return true;
282
+ processedRanges.add(rangeKey);
283
+
284
+ const nameNode = node.childForFieldName('name');
285
+ if (nameNode) {
286
+ const { startLine, endLine } = nodeToLocation(node, code);
287
+ const modifiers = extractModifiers(node);
288
+ const annotations = extractAnnotations(node);
289
+ const docstring = extractJavaDocstring(code, startLine);
290
+ const generics = extractGenerics(node);
291
+
292
+ classes.push({
293
+ name: nameNode.text,
294
+ startLine,
295
+ endLine,
296
+ type: 'record',
297
+ members: [],
298
+ modifiers,
299
+ ...(docstring && { docstring }),
300
+ ...(generics && { generics }),
301
+ ...(annotations.length > 0 && { annotations })
302
+ });
303
+ }
304
+ return false;
305
+ }
306
+
307
+ return true;
308
+ });
309
+
310
+ classes.sort((a, b) => a.startLine - b.startLine);
311
+ return classes;
312
+ }
313
+
314
+ /**
315
+ * Extract extends clause from class
316
+ */
317
+ function extractExtends(classNode) {
318
+ const superclassNode = classNode.childForFieldName('superclass');
319
+ if (superclassNode) {
320
+ return superclassNode.text;
321
+ }
322
+ return null;
323
+ }
324
+
325
+ /**
326
+ * Extract implements clause from class
327
+ */
328
+ function extractImplements(classNode) {
329
+ const interfacesNode = classNode.childForFieldName('interfaces');
330
+ if (interfacesNode) {
331
+ const interfaces = [];
332
+ for (let i = 0; i < interfacesNode.namedChildCount; i++) {
333
+ const iface = interfacesNode.namedChild(i);
334
+ if (iface.type === 'type_identifier' || iface.type === 'generic_type') {
335
+ interfaces.push(iface.text);
336
+ }
337
+ }
338
+ return interfaces;
339
+ }
340
+ return [];
341
+ }
342
+
343
+ /**
344
+ * Extract extends from interface
345
+ */
346
+ function extractInterfaceExtends(interfaceNode) {
347
+ const extendsNode = interfaceNode.childForFieldName('extends');
348
+ if (extendsNode) {
349
+ const interfaces = [];
350
+ for (let i = 0; i < extendsNode.namedChildCount; i++) {
351
+ const iface = extendsNode.namedChild(i);
352
+ interfaces.push(iface.text);
353
+ }
354
+ return interfaces;
355
+ }
356
+ return [];
357
+ }
358
+
359
+ /**
360
+ * Extract class members (methods, constructors)
361
+ */
362
+ function extractClassMembers(classNode, code) {
363
+ const members = [];
364
+ const bodyNode = classNode.childForFieldName('body');
365
+ if (!bodyNode) return members;
366
+
367
+ for (let i = 0; i < bodyNode.namedChildCount; i++) {
368
+ const child = bodyNode.namedChild(i);
369
+
370
+ // Method declarations
371
+ if (child.type === 'method_declaration') {
372
+ const nameNode = child.childForFieldName('name');
373
+ const paramsNode = child.childForFieldName('parameters');
374
+
375
+ if (nameNode) {
376
+ const { startLine, endLine } = nodeToLocation(child, code);
377
+ const modifiers = extractModifiers(child);
378
+ const returnType = extractReturnType(child);
379
+ const docstring = extractJavaDocstring(code, startLine);
380
+
381
+ let memberType = 'method';
382
+ if (modifiers.includes('static')) {
383
+ memberType = 'static';
384
+ } else if (modifiers.includes('abstract')) {
385
+ memberType = 'abstract';
386
+ }
387
+
388
+ members.push({
389
+ name: nameNode.text,
390
+ params: extractJavaParams(paramsNode),
391
+ paramsStructured: parseStructuredParams(paramsNode, 'java'),
392
+ startLine,
393
+ endLine,
394
+ memberType,
395
+ modifiers,
396
+ ...(returnType && { returnType }),
397
+ ...(docstring && { docstring })
398
+ });
399
+ }
400
+ }
401
+
402
+ // Constructor declarations
403
+ if (child.type === 'constructor_declaration') {
404
+ const nameNode = child.childForFieldName('name');
405
+ const paramsNode = child.childForFieldName('parameters');
406
+
407
+ if (nameNode) {
408
+ const { startLine, endLine } = nodeToLocation(child, code);
409
+ const modifiers = extractModifiers(child);
410
+ const docstring = extractJavaDocstring(code, startLine);
411
+
412
+ members.push({
413
+ name: nameNode.text,
414
+ params: extractJavaParams(paramsNode),
415
+ paramsStructured: parseStructuredParams(paramsNode, 'java'),
416
+ startLine,
417
+ endLine,
418
+ memberType: 'constructor',
419
+ modifiers,
420
+ ...(docstring && { docstring })
421
+ });
422
+ }
423
+ }
424
+ }
425
+
426
+ return members;
427
+ }
428
+
429
+ /**
430
+ * Find state objects (static final constants) in Java code
431
+ */
432
+ function findStateObjects(code, parser) {
433
+ const tree = parser.parse(code, undefined, PARSE_OPTIONS);
434
+ const objects = [];
435
+
436
+ const statePattern = /^([A-Z][A-Z0-9_]+|[A-Z][a-zA-Z]*(?:CONFIG|SETTINGS|OPTIONS))$/;
437
+
438
+ traverseTree(tree.rootNode, (node) => {
439
+ if (node.type === 'field_declaration') {
440
+ const modifiers = extractModifiers(node);
441
+ if (modifiers.includes('static') && modifiers.includes('final')) {
442
+ for (let i = 0; i < node.namedChildCount; i++) {
443
+ const child = node.namedChild(i);
444
+ if (child.type === 'variable_declarator') {
445
+ const nameNode = child.childForFieldName('name');
446
+ const valueNode = child.childForFieldName('value');
447
+
448
+ if (nameNode && valueNode) {
449
+ const name = nameNode.text;
450
+ if (statePattern.test(name)) {
451
+ const { startLine, endLine } = nodeToLocation(node, code);
452
+ objects.push({ name, startLine, endLine });
453
+ }
454
+ }
455
+ }
456
+ }
457
+ }
458
+ return true;
459
+ }
460
+
461
+ return true;
462
+ });
463
+
464
+ objects.sort((a, b) => a.startLine - b.startLine);
465
+ return objects;
466
+ }
467
+
468
+ /**
469
+ * Parse a Java file completely
470
+ */
471
+ function parse(code, parser) {
472
+ return {
473
+ language: 'java',
474
+ totalLines: code.split('\n').length,
475
+ functions: findFunctions(code, parser),
476
+ classes: findClasses(code, parser),
477
+ stateObjects: findStateObjects(code, parser),
478
+ imports: [],
479
+ exports: []
480
+ };
481
+ }
482
+
483
+ /**
484
+ * Find all function/method calls in Java code using tree-sitter AST
485
+ * @param {string} code - Source code to analyze
486
+ * @param {object} parser - Tree-sitter parser instance
487
+ * @returns {Array<{name: string, line: number, isMethod: boolean, receiver?: string, isConstructor?: boolean}>}
488
+ */
489
+ function findCallsInCode(code, parser) {
490
+ const tree = parser.parse(code, undefined, PARSE_OPTIONS);
491
+ const calls = [];
492
+ const functionStack = []; // Stack of { name, startLine, endLine }
493
+
494
+ // Helper to check if a node creates a function scope
495
+ const isFunctionNode = (node) => {
496
+ return ['method_declaration', 'constructor_declaration', 'lambda_expression'].includes(node.type);
497
+ };
498
+
499
+ // Helper to extract function name from a function node
500
+ const extractFunctionName = (node) => {
501
+ if (node.type === 'method_declaration') {
502
+ const nameNode = node.childForFieldName('name');
503
+ return nameNode?.text || '<anonymous>';
504
+ }
505
+ if (node.type === 'constructor_declaration') {
506
+ const nameNode = node.childForFieldName('name');
507
+ return nameNode?.text || '<constructor>';
508
+ }
509
+ if (node.type === 'lambda_expression') {
510
+ return '<lambda>';
511
+ }
512
+ return '<anonymous>';
513
+ };
514
+
515
+ // Helper to get current enclosing function
516
+ const getCurrentEnclosingFunction = () => {
517
+ return functionStack.length > 0
518
+ ? { ...functionStack[functionStack.length - 1] }
519
+ : null;
520
+ };
521
+
522
+ traverseTree(tree.rootNode, (node) => {
523
+ // Track function entry
524
+ if (isFunctionNode(node)) {
525
+ functionStack.push({
526
+ name: extractFunctionName(node),
527
+ startLine: node.startPosition.row + 1,
528
+ endLine: node.endPosition.row + 1
529
+ });
530
+ }
531
+
532
+ // Handle method invocations: foo(), obj.foo(), this.foo()
533
+ if (node.type === 'method_invocation') {
534
+ const nameNode = node.childForFieldName('name');
535
+ const objNode = node.childForFieldName('object');
536
+
537
+ if (nameNode) {
538
+ const enclosingFunction = getCurrentEnclosingFunction();
539
+ calls.push({
540
+ name: nameNode.text,
541
+ line: node.startPosition.row + 1,
542
+ isMethod: !!objNode,
543
+ receiver: objNode?.type === 'identifier' ? objNode.text : undefined,
544
+ enclosingFunction
545
+ });
546
+ }
547
+ return true;
548
+ }
549
+
550
+ // Handle constructor calls: new Foo(), new pkg.Bar()
551
+ if (node.type === 'object_creation_expression') {
552
+ const typeNode = node.childForFieldName('type');
553
+ if (typeNode) {
554
+ let typeName = typeNode.text;
555
+ // Handle generic types like List<String>
556
+ const genericIdx = typeName.indexOf('<');
557
+ if (genericIdx > 0) {
558
+ typeName = typeName.substring(0, genericIdx);
559
+ }
560
+ // Handle qualified names like pkg.Class
561
+ const dotIdx = typeName.lastIndexOf('.');
562
+ if (dotIdx > 0) {
563
+ typeName = typeName.substring(dotIdx + 1);
564
+ }
565
+
566
+ const enclosingFunction = getCurrentEnclosingFunction();
567
+ calls.push({
568
+ name: typeName,
569
+ line: node.startPosition.row + 1,
570
+ isMethod: false,
571
+ isConstructor: true,
572
+ enclosingFunction
573
+ });
574
+ }
575
+ return true;
576
+ }
577
+
578
+ return true;
579
+ }, {
580
+ onLeave: (node) => {
581
+ if (isFunctionNode(node)) {
582
+ functionStack.pop();
583
+ }
584
+ }
585
+ });
586
+
587
+ return calls;
588
+ }
589
+
590
+ /**
591
+ * Find all imports in Java code using tree-sitter AST
592
+ * @param {string} code - Source code to analyze
593
+ * @param {object} parser - Tree-sitter parser instance
594
+ * @returns {Array<{module: string, names: string[], type: string, line: number}>}
595
+ */
596
+ function findImportsInCode(code, parser) {
597
+ const tree = parser.parse(code, undefined, PARSE_OPTIONS);
598
+ const imports = [];
599
+
600
+ traverseTree(tree.rootNode, (node) => {
601
+ if (node.type === 'import_declaration') {
602
+ const line = node.startPosition.row + 1;
603
+ let modulePath = null;
604
+ let isStatic = node.text.includes('import static');
605
+ let isWildcard = false;
606
+
607
+ for (let i = 0; i < node.namedChildCount; i++) {
608
+ const child = node.namedChild(i);
609
+ if (child.type === 'scoped_identifier' || child.type === 'identifier') {
610
+ modulePath = child.text;
611
+ } else if (child.type === 'asterisk') {
612
+ isWildcard = true;
613
+ }
614
+ }
615
+
616
+ if (modulePath) {
617
+ const segments = modulePath.split('.');
618
+ const name = isWildcard ? '*' : segments[segments.length - 1];
619
+ imports.push({
620
+ module: modulePath + (isWildcard ? '.*' : ''),
621
+ names: [name],
622
+ type: isStatic ? 'static' : 'import',
623
+ line
624
+ });
625
+ }
626
+ return true;
627
+ }
628
+
629
+ return true;
630
+ });
631
+
632
+ return imports;
633
+ }
634
+
635
+ /**
636
+ * Find all exports in Java code using tree-sitter AST
637
+ * In Java, public classes/interfaces/enums are exports
638
+ * @param {string} code - Source code to analyze
639
+ * @param {object} parser - Tree-sitter parser instance
640
+ * @returns {Array<{name: string, type: string, line: number}>}
641
+ */
642
+ function findExportsInCode(code, parser) {
643
+ const tree = parser.parse(code, undefined, PARSE_OPTIONS);
644
+ const exports = [];
645
+
646
+ function isPublic(node) {
647
+ for (let i = 0; i < node.namedChildCount; i++) {
648
+ const child = node.namedChild(i);
649
+ if (child.type === 'modifiers' && child.text.includes('public')) {
650
+ return true;
651
+ }
652
+ }
653
+ return false;
654
+ }
655
+
656
+ traverseTree(tree.rootNode, (node) => {
657
+ // Public classes
658
+ if (node.type === 'class_declaration' && isPublic(node)) {
659
+ const nameNode = node.childForFieldName('name');
660
+ if (nameNode) {
661
+ exports.push({
662
+ name: nameNode.text,
663
+ type: 'class',
664
+ line: node.startPosition.row + 1
665
+ });
666
+ }
667
+ return false; // Don't descend into class body
668
+ }
669
+
670
+ // Public interfaces
671
+ if (node.type === 'interface_declaration' && isPublic(node)) {
672
+ const nameNode = node.childForFieldName('name');
673
+ if (nameNode) {
674
+ exports.push({
675
+ name: nameNode.text,
676
+ type: 'interface',
677
+ line: node.startPosition.row + 1
678
+ });
679
+ }
680
+ return false;
681
+ }
682
+
683
+ // Public enums
684
+ if (node.type === 'enum_declaration' && isPublic(node)) {
685
+ const nameNode = node.childForFieldName('name');
686
+ if (nameNode) {
687
+ exports.push({
688
+ name: nameNode.text,
689
+ type: 'enum',
690
+ line: node.startPosition.row + 1
691
+ });
692
+ }
693
+ return false;
694
+ }
695
+
696
+ // Public records (Java 14+)
697
+ if (node.type === 'record_declaration' && isPublic(node)) {
698
+ const nameNode = node.childForFieldName('name');
699
+ if (nameNode) {
700
+ exports.push({
701
+ name: nameNode.text,
702
+ type: 'record',
703
+ line: node.startPosition.row + 1
704
+ });
705
+ }
706
+ return false;
707
+ }
708
+
709
+ return true;
710
+ });
711
+
712
+ return exports;
713
+ }
714
+
715
+ /**
716
+ * Find all usages of a name in code using AST
717
+ * @param {string} code - Source code
718
+ * @param {string} name - Symbol name to find
719
+ * @param {object} parser - Tree-sitter parser instance
720
+ * @returns {Array<{line: number, column: number, usageType: string}>}
721
+ */
722
+ function findUsagesInCode(code, name, parser) {
723
+ const tree = parser.parse(code, undefined, PARSE_OPTIONS);
724
+ const usages = [];
725
+
726
+ traverseTree(tree.rootNode, (node) => {
727
+ // Only look for identifiers with the matching name
728
+ if (node.type !== 'identifier' || node.text !== name) {
729
+ return true;
730
+ }
731
+
732
+ const line = node.startPosition.row + 1;
733
+ const column = node.startPosition.column;
734
+ const parent = node.parent;
735
+
736
+ let usageType = 'reference';
737
+
738
+ if (parent) {
739
+ // Import: part of import declaration
740
+ if (parent.type === 'scoped_identifier' ||
741
+ parent.type === 'import_declaration') {
742
+ // Check if we're inside an import
743
+ let n = parent;
744
+ while (n) {
745
+ if (n.type === 'import_declaration') {
746
+ usageType = 'import';
747
+ break;
748
+ }
749
+ n = n.parent;
750
+ }
751
+ }
752
+ // Call: method_invocation with name field
753
+ else if (parent.type === 'method_invocation' &&
754
+ parent.childForFieldName('name') === node) {
755
+ usageType = 'call';
756
+ }
757
+ // Definition: method name
758
+ else if (parent.type === 'method_declaration' &&
759
+ parent.childForFieldName('name') === node) {
760
+ usageType = 'definition';
761
+ }
762
+ // Definition: class name
763
+ else if (parent.type === 'class_declaration' &&
764
+ parent.childForFieldName('name') === node) {
765
+ usageType = 'definition';
766
+ }
767
+ // Definition: interface name
768
+ else if (parent.type === 'interface_declaration' &&
769
+ parent.childForFieldName('name') === node) {
770
+ usageType = 'definition';
771
+ }
772
+ // Definition: enum name
773
+ else if (parent.type === 'enum_declaration' &&
774
+ parent.childForFieldName('name') === node) {
775
+ usageType = 'definition';
776
+ }
777
+ // Definition: constructor
778
+ else if (parent.type === 'constructor_declaration' &&
779
+ parent.childForFieldName('name') === node) {
780
+ usageType = 'definition';
781
+ }
782
+ // Definition: local variable
783
+ else if (parent.type === 'variable_declarator' &&
784
+ parent.childForFieldName('name') === node) {
785
+ usageType = 'definition';
786
+ }
787
+ // Definition: parameter
788
+ else if (parent.type === 'formal_parameter' ||
789
+ parent.type === 'spread_parameter') {
790
+ usageType = 'definition';
791
+ }
792
+ // Definition: field
793
+ else if (parent.type === 'field_declaration') {
794
+ usageType = 'definition';
795
+ }
796
+ // Object creation: new ClassName()
797
+ else if (parent.type === 'object_creation_expression') {
798
+ const typeNode = parent.childForFieldName('type');
799
+ if (typeNode?.text === name) {
800
+ usageType = 'call';
801
+ }
802
+ }
803
+ // Field access: obj.field
804
+ else if (parent.type === 'field_access' &&
805
+ parent.childForFieldName('field') === node) {
806
+ usageType = 'reference';
807
+ }
808
+ }
809
+
810
+ usages.push({ line, column, usageType });
811
+ return true;
812
+ });
813
+
814
+ return usages;
815
+ }
816
+
817
+ module.exports = {
818
+ findFunctions,
819
+ findClasses,
820
+ findStateObjects,
821
+ findCallsInCode,
822
+ findImportsInCode,
823
+ findExportsInCode,
824
+ findUsagesInCode,
825
+ parse
826
+ };