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,667 @@
1
+ /**
2
+ * languages/python.js - Tree-sitter based Python parsing
3
+ *
4
+ * Handles: function definitions (regular, async, decorated),
5
+ * class definitions, and state objects (constants).
6
+ */
7
+
8
+ const {
9
+ traverseTree,
10
+ nodeToLocation,
11
+ parseStructuredParams,
12
+ extractPythonDocstring
13
+ } = require('./utils');
14
+ const { PARSE_OPTIONS } = require('./index');
15
+
16
+ /**
17
+ * Extract return type annotation from Python function
18
+ * @param {object} node - Function definition node
19
+ * @returns {string|null} Return type or null
20
+ */
21
+ function extractReturnType(node) {
22
+ const returnTypeNode = node.childForFieldName('return_type');
23
+ if (returnTypeNode) {
24
+ let text = returnTypeNode.text.trim();
25
+ if (text.startsWith('->')) {
26
+ text = text.slice(2).trim();
27
+ }
28
+ return text || null;
29
+ }
30
+ return null;
31
+ }
32
+
33
+ /**
34
+ * Find the actual def line (not decorator) for docstring extraction
35
+ */
36
+ function getDefLine(node) {
37
+ return node.startPosition.row + 1;
38
+ }
39
+
40
+ /**
41
+ * Get indentation of a node
42
+ */
43
+ function getIndent(node, code) {
44
+ const lines = code.split('\n');
45
+ const firstLine = lines[node.startPosition.row] || '';
46
+ const indentMatch = firstLine.match(/^(\s*)/);
47
+ return indentMatch ? indentMatch[1].length : 0;
48
+ }
49
+
50
+ /**
51
+ * Extract Python parameters
52
+ */
53
+ function extractPythonParams(paramsNode) {
54
+ if (!paramsNode) return '...';
55
+ const text = paramsNode.text;
56
+ let params = text.replace(/^\(|\)$/g, '').trim();
57
+ if (!params) return '...';
58
+ return params;
59
+ }
60
+
61
+ /**
62
+ * Find all functions in Python code using tree-sitter
63
+ */
64
+ function findFunctions(code, parser) {
65
+ const tree = parser.parse(code, undefined, PARSE_OPTIONS);
66
+ const functions = [];
67
+ const processedRanges = new Set();
68
+
69
+ traverseTree(tree.rootNode, (node) => {
70
+ const rangeKey = `${node.startIndex}-${node.endIndex}`;
71
+
72
+ if (node.type === 'function_definition') {
73
+ if (processedRanges.has(rangeKey)) return true;
74
+ processedRanges.add(rangeKey);
75
+
76
+ const nameNode = node.childForFieldName('name');
77
+ const paramsNode = node.childForFieldName('parameters');
78
+
79
+ if (nameNode) {
80
+ // Check for decorators
81
+ let startLine = node.startPosition.row + 1;
82
+ let decoratorStartLine = startLine;
83
+
84
+ if (node.parent && node.parent.type === 'decorated_definition') {
85
+ decoratorStartLine = node.parent.startPosition.row + 1;
86
+ }
87
+
88
+ const endLine = node.endPosition.row + 1;
89
+ const indent = getIndent(node, code);
90
+ const returnType = extractReturnType(node);
91
+ const defLine = getDefLine(node);
92
+ const docstring = extractPythonDocstring(code, defLine);
93
+
94
+ // Check for async
95
+ const isAsync = node.text.trimStart().startsWith('async ');
96
+
97
+ // Extract decorators
98
+ const decorators = extractDecorators(node);
99
+
100
+ functions.push({
101
+ name: nameNode.text,
102
+ params: extractPythonParams(paramsNode),
103
+ paramsStructured: parseStructuredParams(paramsNode, 'python'),
104
+ startLine: decoratorStartLine,
105
+ endLine,
106
+ indent,
107
+ isAsync,
108
+ modifiers: isAsync ? ['async'] : [],
109
+ ...(returnType && { returnType }),
110
+ ...(docstring && { docstring }),
111
+ ...(decorators.length > 0 && { decorators })
112
+ });
113
+ }
114
+ return true;
115
+ }
116
+
117
+ if (node.type === 'decorated_definition') {
118
+ return true; // Continue traversing into decorated definitions
119
+ }
120
+
121
+ return true;
122
+ });
123
+
124
+ functions.sort((a, b) => a.startLine - b.startLine);
125
+ return functions;
126
+ }
127
+
128
+ /**
129
+ * Extract decorators from a function/class node
130
+ */
131
+ function extractDecorators(node) {
132
+ const decorators = [];
133
+ if (node.parent && node.parent.type === 'decorated_definition') {
134
+ for (let i = 0; i < node.parent.namedChildCount; i++) {
135
+ const child = node.parent.namedChild(i);
136
+ if (child.type === 'decorator') {
137
+ decorators.push(child.text.replace('@', ''));
138
+ }
139
+ }
140
+ }
141
+ return decorators;
142
+ }
143
+
144
+ /**
145
+ * Find all classes in Python code using tree-sitter
146
+ */
147
+ function findClasses(code, parser) {
148
+ const tree = parser.parse(code, undefined, PARSE_OPTIONS);
149
+ const classes = [];
150
+ const processedRanges = new Set();
151
+
152
+ traverseTree(tree.rootNode, (node) => {
153
+ const rangeKey = `${node.startIndex}-${node.endIndex}`;
154
+
155
+ if (node.type === 'class_definition') {
156
+ if (processedRanges.has(rangeKey)) return true;
157
+ processedRanges.add(rangeKey);
158
+
159
+ const nameNode = node.childForFieldName('name');
160
+
161
+ if (nameNode) {
162
+ // Check for decorators
163
+ let startLine = node.startPosition.row + 1;
164
+ if (node.parent && node.parent.type === 'decorated_definition') {
165
+ startLine = node.parent.startPosition.row + 1;
166
+ }
167
+
168
+ const endLine = node.endPosition.row + 1;
169
+ const members = extractClassMembers(node, code);
170
+ const defLine = getDefLine(node);
171
+ const docstring = extractPythonDocstring(code, defLine);
172
+ const decorators = extractDecorators(node);
173
+ const bases = extractBases(node);
174
+
175
+ classes.push({
176
+ name: nameNode.text,
177
+ startLine,
178
+ endLine,
179
+ type: 'class',
180
+ members,
181
+ ...(docstring && { docstring }),
182
+ ...(decorators.length > 0 && { decorators }),
183
+ ...(bases.length > 0 && { extends: bases.join(', ') })
184
+ });
185
+ }
186
+ return false; // Don't traverse into class body
187
+ }
188
+
189
+ return true;
190
+ });
191
+
192
+ classes.sort((a, b) => a.startLine - b.startLine);
193
+ return classes;
194
+ }
195
+
196
+ /**
197
+ * Extract base classes from class definition
198
+ */
199
+ function extractBases(classNode) {
200
+ const bases = [];
201
+ const argsNode = classNode.childForFieldName('superclasses');
202
+ if (argsNode) {
203
+ for (let i = 0; i < argsNode.namedChildCount; i++) {
204
+ const arg = argsNode.namedChild(i);
205
+ if (arg.type === 'identifier' || arg.type === 'attribute') {
206
+ bases.push(arg.text);
207
+ }
208
+ }
209
+ }
210
+ return bases;
211
+ }
212
+
213
+ /**
214
+ * Extract class members (methods)
215
+ */
216
+ function extractClassMembers(classNode, code) {
217
+ const members = [];
218
+ const bodyNode = classNode.childForFieldName('body');
219
+ if (!bodyNode) return members;
220
+
221
+ for (let i = 0; i < bodyNode.namedChildCount; i++) {
222
+ const child = bodyNode.namedChild(i);
223
+
224
+ let funcNode = child;
225
+ let decoratorStart = null;
226
+ const memberDecorators = [];
227
+
228
+ if (child.type === 'decorated_definition') {
229
+ decoratorStart = child.startPosition.row + 1;
230
+ // Collect decorators
231
+ for (let j = 0; j < child.namedChildCount; j++) {
232
+ const inner = child.namedChild(j);
233
+ if (inner.type === 'decorator') {
234
+ memberDecorators.push(inner.text.replace('@', ''));
235
+ }
236
+ if (inner.type === 'function_definition') {
237
+ funcNode = inner;
238
+ }
239
+ }
240
+ }
241
+
242
+ if (funcNode.type === 'function_definition') {
243
+ const nameNode = funcNode.childForFieldName('name');
244
+ const paramsNode = funcNode.childForFieldName('parameters');
245
+
246
+ if (nameNode) {
247
+ const name = nameNode.text;
248
+ const startLine = decoratorStart || funcNode.startPosition.row + 1;
249
+ const endLine = funcNode.endPosition.row + 1;
250
+
251
+ // Determine member type
252
+ let memberType = 'method';
253
+ if (name === '__init__') {
254
+ memberType = 'constructor';
255
+ } else if (name.startsWith('__') && name.endsWith('__')) {
256
+ memberType = 'special';
257
+ } else if (name.startsWith('_')) {
258
+ memberType = 'private';
259
+ }
260
+
261
+ // Check decorators
262
+ for (const dec of memberDecorators) {
263
+ if (dec.includes('staticmethod')) {
264
+ memberType = 'static';
265
+ } else if (dec.includes('classmethod')) {
266
+ memberType = 'classmethod';
267
+ } else if (dec.includes('property')) {
268
+ memberType = 'property';
269
+ }
270
+ }
271
+
272
+ const isAsync = funcNode.text.trimStart().startsWith('async ');
273
+ const returnType = extractReturnType(funcNode);
274
+ const defLine = getDefLine(funcNode);
275
+ const docstring = extractPythonDocstring(code, defLine);
276
+
277
+ members.push({
278
+ name,
279
+ params: extractPythonParams(paramsNode),
280
+ paramsStructured: parseStructuredParams(paramsNode, 'python'),
281
+ startLine,
282
+ endLine,
283
+ memberType,
284
+ isAsync,
285
+ ...(returnType && { returnType }),
286
+ ...(docstring && { docstring }),
287
+ ...(memberDecorators.length > 0 && { decorators: memberDecorators })
288
+ });
289
+ }
290
+ }
291
+ }
292
+
293
+ return members;
294
+ }
295
+
296
+ /**
297
+ * Find state objects (constants) in Python code
298
+ */
299
+ function findStateObjects(code, parser) {
300
+ const tree = parser.parse(code, undefined, PARSE_OPTIONS);
301
+ const objects = [];
302
+
303
+ const statePattern = /^(CONFIG|SETTINGS|[A-Z][A-Z0-9_]+|[A-Z][a-zA-Z]*(?:Config|Settings|Options|State|Store|Context))$/;
304
+
305
+ traverseTree(tree.rootNode, (node) => {
306
+ if (node.type === 'expression_statement' && node.parent === tree.rootNode) {
307
+ const child = node.namedChild(0);
308
+ if (child && child.type === 'assignment') {
309
+ const leftNode = child.childForFieldName('left');
310
+ const rightNode = child.childForFieldName('right');
311
+
312
+ if (leftNode && leftNode.type === 'identifier' && rightNode) {
313
+ const name = leftNode.text;
314
+ const isObject = rightNode.type === 'dictionary';
315
+ const isArray = rightNode.type === 'list';
316
+
317
+ if ((isObject || isArray) && statePattern.test(name)) {
318
+ const { startLine, endLine } = nodeToLocation(node, code);
319
+ objects.push({ name, startLine, endLine });
320
+ }
321
+ }
322
+ }
323
+ }
324
+ return true;
325
+ });
326
+
327
+ objects.sort((a, b) => a.startLine - b.startLine);
328
+ return objects;
329
+ }
330
+
331
+ /**
332
+ * Parse a Python file completely
333
+ */
334
+ function parse(code, parser) {
335
+ return {
336
+ language: 'python',
337
+ totalLines: code.split('\n').length,
338
+ functions: findFunctions(code, parser),
339
+ classes: findClasses(code, parser),
340
+ stateObjects: findStateObjects(code, parser),
341
+ imports: [],
342
+ exports: []
343
+ };
344
+ }
345
+
346
+ /**
347
+ * Find all function calls in Python code using tree-sitter AST
348
+ * @param {string} code - Source code to analyze
349
+ * @param {object} parser - Tree-sitter parser instance
350
+ * @returns {Array<{name: string, line: number, isMethod: boolean, receiver?: string}>}
351
+ */
352
+ function findCallsInCode(code, parser) {
353
+ const tree = parser.parse(code, undefined, PARSE_OPTIONS);
354
+ const calls = [];
355
+ const functionStack = []; // Stack of { name, startLine, endLine }
356
+
357
+ // Helper to check if a node creates a function scope
358
+ const isFunctionNode = (node) => {
359
+ return ['function_definition', 'async_function_definition', 'lambda'].includes(node.type);
360
+ };
361
+
362
+ // Helper to extract function name from a function node
363
+ const extractFunctionName = (node) => {
364
+ if (node.type === 'function_definition' || node.type === 'async_function_definition') {
365
+ const nameNode = node.childForFieldName('name');
366
+ return nameNode?.text || '<anonymous>';
367
+ }
368
+ if (node.type === 'lambda') {
369
+ return '<lambda>';
370
+ }
371
+ return '<anonymous>';
372
+ };
373
+
374
+ // Helper to get current enclosing function
375
+ const getCurrentEnclosingFunction = () => {
376
+ return functionStack.length > 0
377
+ ? { ...functionStack[functionStack.length - 1] }
378
+ : null;
379
+ };
380
+
381
+ traverseTree(tree.rootNode, (node) => {
382
+ // Track function entry
383
+ if (isFunctionNode(node)) {
384
+ functionStack.push({
385
+ name: extractFunctionName(node),
386
+ startLine: node.startPosition.row + 1,
387
+ endLine: node.endPosition.row + 1
388
+ });
389
+ }
390
+
391
+ // Handle function calls: foo(), obj.foo()
392
+ if (node.type === 'call') {
393
+ const funcNode = node.childForFieldName('function');
394
+ if (!funcNode) return true;
395
+
396
+ const enclosingFunction = getCurrentEnclosingFunction();
397
+
398
+ if (funcNode.type === 'identifier') {
399
+ // Direct call: foo()
400
+ calls.push({
401
+ name: funcNode.text,
402
+ line: node.startPosition.row + 1,
403
+ isMethod: false,
404
+ enclosingFunction
405
+ });
406
+ } else if (funcNode.type === 'attribute') {
407
+ // Method/attribute call: obj.foo()
408
+ const attrNode = funcNode.childForFieldName('attribute');
409
+ const objNode = funcNode.childForFieldName('object');
410
+
411
+ if (attrNode) {
412
+ calls.push({
413
+ name: attrNode.text,
414
+ line: node.startPosition.row + 1,
415
+ isMethod: true,
416
+ receiver: objNode?.type === 'identifier' ? objNode.text : undefined,
417
+ enclosingFunction
418
+ });
419
+ }
420
+ }
421
+ return true;
422
+ }
423
+
424
+ return true;
425
+ }, {
426
+ onLeave: (node) => {
427
+ if (isFunctionNode(node)) {
428
+ functionStack.pop();
429
+ }
430
+ }
431
+ });
432
+
433
+ return calls;
434
+ }
435
+
436
+ /**
437
+ * Find all imports in Python code using tree-sitter AST
438
+ * @param {string} code - Source code to analyze
439
+ * @param {object} parser - Tree-sitter parser instance
440
+ * @returns {Array<{module: string, names: string[], type: string, line: number}>}
441
+ */
442
+ function findImportsInCode(code, parser) {
443
+ const tree = parser.parse(code, undefined, PARSE_OPTIONS);
444
+ const imports = [];
445
+
446
+ traverseTree(tree.rootNode, (node) => {
447
+ // import statement: import os, import sys as system
448
+ if (node.type === 'import_statement') {
449
+ const line = node.startPosition.row + 1;
450
+
451
+ for (let i = 0; i < node.namedChildCount; i++) {
452
+ const child = node.namedChild(i);
453
+ if (child.type === 'dotted_name') {
454
+ // import os
455
+ imports.push({
456
+ module: child.text,
457
+ names: [child.text.split('.').pop()],
458
+ type: 'import',
459
+ line
460
+ });
461
+ } else if (child.type === 'aliased_import') {
462
+ // import sys as system
463
+ const nameNode = child.namedChild(0);
464
+ const aliasNode = child.namedChild(1);
465
+ if (nameNode) {
466
+ imports.push({
467
+ module: nameNode.text,
468
+ names: [aliasNode ? aliasNode.text : nameNode.text.split('.').pop()],
469
+ type: 'import',
470
+ line
471
+ });
472
+ }
473
+ }
474
+ }
475
+ return true;
476
+ }
477
+
478
+ // from ... import statement
479
+ if (node.type === 'import_from_statement') {
480
+ const line = node.startPosition.row + 1;
481
+ let modulePath = '';
482
+ const names = [];
483
+
484
+ for (let i = 0; i < node.namedChildCount; i++) {
485
+ const child = node.namedChild(i);
486
+
487
+ // Module path (first dotted_name or relative_import)
488
+ if (i === 0 && (child.type === 'dotted_name' || child.type === 'relative_import')) {
489
+ modulePath = child.text;
490
+ }
491
+ // Imported names
492
+ else if (child.type === 'dotted_name') {
493
+ names.push(child.text);
494
+ } else if (child.type === 'aliased_import') {
495
+ const nameNode = child.namedChild(0);
496
+ if (nameNode) names.push(nameNode.text);
497
+ } else if (child.type === 'wildcard_import') {
498
+ names.push('*');
499
+ }
500
+ }
501
+
502
+ if (modulePath) {
503
+ const isRelative = modulePath.startsWith('.');
504
+ imports.push({
505
+ module: modulePath,
506
+ names,
507
+ type: isRelative ? 'relative' : 'from',
508
+ line
509
+ });
510
+ }
511
+ return true;
512
+ }
513
+
514
+ return true;
515
+ });
516
+
517
+ return imports;
518
+ }
519
+
520
+ /**
521
+ * Find all exports in Python code using tree-sitter AST
522
+ * Looks for __all__ assignments
523
+ * @param {string} code - Source code to analyze
524
+ * @param {object} parser - Tree-sitter parser instance
525
+ * @returns {Array<{name: string, type: string, line: number}>}
526
+ */
527
+ function findExportsInCode(code, parser) {
528
+ const tree = parser.parse(code, undefined, PARSE_OPTIONS);
529
+ const exports = [];
530
+
531
+ traverseTree(tree.rootNode, (node) => {
532
+ // Look for __all__ = [...]
533
+ if (node.type === 'expression_statement') {
534
+ const child = node.namedChild(0);
535
+ if (child && child.type === 'assignment') {
536
+ const leftNode = child.childForFieldName('left');
537
+ const rightNode = child.childForFieldName('right');
538
+
539
+ if (leftNode && leftNode.type === 'identifier' && leftNode.text === '__all__') {
540
+ const line = node.startPosition.row + 1;
541
+
542
+ if (rightNode && rightNode.type === 'list') {
543
+ for (let i = 0; i < rightNode.namedChildCount; i++) {
544
+ const item = rightNode.namedChild(i);
545
+ if (item.type === 'string') {
546
+ // Extract string content
547
+ const contentNode = item.childForFieldName('content') ||
548
+ item.namedChild(0);
549
+ if (contentNode && contentNode.type === 'string_content') {
550
+ exports.push({ name: contentNode.text, type: '__all__', line });
551
+ } else {
552
+ // Fallback: remove quotes
553
+ const text = item.text;
554
+ const name = text.slice(1, -1);
555
+ exports.push({ name, type: '__all__', line });
556
+ }
557
+ }
558
+ }
559
+ }
560
+ }
561
+ }
562
+ }
563
+
564
+ return true;
565
+ });
566
+
567
+ return exports;
568
+ }
569
+
570
+ /**
571
+ * Find all usages of a name in code using AST
572
+ * @param {string} code - Source code
573
+ * @param {string} name - Symbol name to find
574
+ * @param {object} parser - Tree-sitter parser instance
575
+ * @returns {Array<{line: number, column: number, usageType: string}>}
576
+ */
577
+ function findUsagesInCode(code, name, parser) {
578
+ const tree = parser.parse(code, undefined, PARSE_OPTIONS);
579
+ const usages = [];
580
+
581
+ traverseTree(tree.rootNode, (node) => {
582
+ // Only look for identifiers with the matching name
583
+ if (node.type !== 'identifier' || node.text !== name) {
584
+ return true;
585
+ }
586
+
587
+ const line = node.startPosition.row + 1;
588
+ const column = node.startPosition.column;
589
+ const parent = node.parent;
590
+
591
+ let usageType = 'reference';
592
+
593
+ if (parent) {
594
+ // Import: from x import name, import name
595
+ if (parent.type === 'aliased_import' ||
596
+ parent.type === 'dotted_name' && parent.parent?.type === 'import_statement') {
597
+ usageType = 'import';
598
+ }
599
+ // Import: from x import name (in import_from_statement)
600
+ else if (parent.type === 'dotted_name' && parent.parent?.type === 'import_from_statement') {
601
+ usageType = 'import';
602
+ }
603
+ // Import: direct identifier in import
604
+ else if (parent.type === 'import_from_statement') {
605
+ usageType = 'import';
606
+ }
607
+ // Call: name()
608
+ else if (parent.type === 'call' &&
609
+ parent.childForFieldName('function') === node) {
610
+ usageType = 'call';
611
+ }
612
+ // Definition: def name(...):
613
+ else if (parent.type === 'function_definition' &&
614
+ parent.childForFieldName('name') === node) {
615
+ usageType = 'definition';
616
+ }
617
+ // Definition: class name:
618
+ else if (parent.type === 'class_definition' &&
619
+ parent.childForFieldName('name') === node) {
620
+ usageType = 'definition';
621
+ }
622
+ // Definition: parameter
623
+ else if (parent.type === 'parameter' ||
624
+ parent.type === 'default_parameter' ||
625
+ parent.type === 'typed_parameter' ||
626
+ parent.type === 'typed_default_parameter') {
627
+ usageType = 'definition';
628
+ }
629
+ // Definition: assignment target (x = ...)
630
+ else if (parent.type === 'assignment' &&
631
+ parent.childForFieldName('left') === node) {
632
+ usageType = 'definition';
633
+ }
634
+ // Definition: for loop variable
635
+ else if (parent.type === 'for_statement' &&
636
+ parent.childForFieldName('left') === node) {
637
+ usageType = 'definition';
638
+ }
639
+ // Method call: obj.name()
640
+ else if (parent.type === 'attribute' &&
641
+ parent.childForFieldName('attribute') === node) {
642
+ const grandparent = parent.parent;
643
+ if (grandparent && grandparent.type === 'call') {
644
+ usageType = 'call';
645
+ } else {
646
+ usageType = 'reference';
647
+ }
648
+ }
649
+ }
650
+
651
+ usages.push({ line, column, usageType });
652
+ return true;
653
+ });
654
+
655
+ return usages;
656
+ }
657
+
658
+ module.exports = {
659
+ findFunctions,
660
+ findClasses,
661
+ findStateObjects,
662
+ findCallsInCode,
663
+ findImportsInCode,
664
+ findExportsInCode,
665
+ findUsagesInCode,
666
+ parse
667
+ };