ucn 3.2.0 → 3.3.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.

package/languages/go.js CHANGED
@@ -11,7 +11,11 @@ const {
11
11
  parseStructuredParams,
12
12
  extractGoDocstring
13
13
  } = require('./utils');
14
- const { PARSE_OPTIONS } = require('./index');
14
+ const { PARSE_OPTIONS, safeParse } = require('./index');
15
+
16
+ function parseTree(parser, code) {
17
+ return safeParse(parser, code, undefined, PARSE_OPTIONS);
18
+ }
15
19
 
16
20
  /**
17
21
  * Extract return type from Go function/method
@@ -49,7 +53,7 @@ function extractReceiver(receiverNode) {
49
53
  * Find all functions in Go code using tree-sitter
50
54
  */
51
55
  function findFunctions(code, parser) {
52
- const tree = parser.parse(code, undefined, PARSE_OPTIONS);
56
+ const tree = parseTree(parser, code);
53
57
  const functions = [];
54
58
  const processedRanges = new Set();
55
59
 
@@ -146,7 +150,7 @@ function extractTypeParams(node) {
146
150
  * Find all types (structs, interfaces) in Go code using tree-sitter
147
151
  */
148
152
  function findClasses(code, parser) {
149
- const tree = parser.parse(code, undefined, PARSE_OPTIONS);
153
+ const tree = parseTree(parser, code);
150
154
  const types = [];
151
155
  const processedRanges = new Set();
152
156
 
@@ -235,7 +239,7 @@ function extractStructFields(structNode, code) {
235
239
  * Find state objects (constants) in Go code
236
240
  */
237
241
  function findStateObjects(code, parser) {
238
- const tree = parser.parse(code, undefined, PARSE_OPTIONS);
242
+ const tree = parseTree(parser, code);
239
243
  const objects = [];
240
244
 
241
245
  const statePattern = /^(CONFIG|SETTINGS|[A-Z][A-Z0-9_]+|Default[A-Z][a-zA-Z]*|[A-Z][a-zA-Z]*(?:Config|Settings|Options))$/;
@@ -320,7 +324,7 @@ function parse(code, parser) {
320
324
  * @returns {Array<{name: string, line: number, isMethod: boolean, receiver?: string}>}
321
325
  */
322
326
  function findCallsInCode(code, parser) {
323
- const tree = parser.parse(code, undefined, PARSE_OPTIONS);
327
+ const tree = parseTree(parser, code);
324
328
  const calls = [];
325
329
  const functionStack = []; // Stack of { name, startLine, endLine }
326
330
 
@@ -368,6 +372,7 @@ function findCallsInCode(code, parser) {
368
372
  if (!funcNode) return true;
369
373
 
370
374
  const enclosingFunction = getCurrentEnclosingFunction();
375
+ let uncertain = false;
371
376
 
372
377
  if (funcNode.type === 'identifier') {
373
378
  // Direct call: foo()
@@ -375,7 +380,8 @@ function findCallsInCode(code, parser) {
375
380
  name: funcNode.text,
376
381
  line: node.startPosition.row + 1,
377
382
  isMethod: false,
378
- enclosingFunction
383
+ enclosingFunction,
384
+ uncertain
379
385
  });
380
386
  } else if (funcNode.type === 'selector_expression') {
381
387
  // Method or package call: obj.Method() or pkg.Func()
@@ -388,7 +394,8 @@ function findCallsInCode(code, parser) {
388
394
  line: node.startPosition.row + 1,
389
395
  isMethod: true,
390
396
  receiver: operandNode?.type === 'identifier' ? operandNode.text : undefined,
391
- enclosingFunction
397
+ enclosingFunction,
398
+ uncertain
392
399
  });
393
400
  }
394
401
  }
@@ -414,7 +421,7 @@ function findCallsInCode(code, parser) {
414
421
  * @returns {Array<{module: string, names: string[], type: string, line: number}>}
415
422
  */
416
423
  function findImportsInCode(code, parser) {
417
- const tree = parser.parse(code, undefined, PARSE_OPTIONS);
424
+ const tree = parseTree(parser, code);
418
425
  const imports = [];
419
426
 
420
427
  function processImportSpec(spec) {
@@ -422,6 +429,7 @@ function findImportsInCode(code, parser) {
422
429
  let modulePath = null;
423
430
  let alias = null;
424
431
  let importType = 'import';
432
+ let dynamic = false;
425
433
 
426
434
  for (let i = 0; i < spec.namedChildCount; i++) {
427
435
  const child = spec.namedChild(i);
@@ -433,9 +441,11 @@ function findImportsInCode(code, parser) {
433
441
  } else if (child.type === 'blank_identifier') {
434
442
  alias = '_';
435
443
  importType = 'side-effect';
444
+ dynamic = true; // treat side-effect imports as dynamic for completeness signals
436
445
  } else if (child.type === 'dot') {
437
446
  alias = '.';
438
447
  importType = 'dot-import';
448
+ dynamic = true;
439
449
  }
440
450
  }
441
451
 
@@ -446,6 +456,7 @@ function findImportsInCode(code, parser) {
446
456
  module: modulePath,
447
457
  names: [pkgName],
448
458
  type: importType,
459
+ dynamic,
449
460
  line
450
461
  });
451
462
  }
@@ -483,7 +494,7 @@ function findImportsInCode(code, parser) {
483
494
  * @returns {Array<{name: string, type: string, line: number}>}
484
495
  */
485
496
  function findExportsInCode(code, parser) {
486
- const tree = parser.parse(code, undefined, PARSE_OPTIONS);
497
+ const tree = parseTree(parser, code);
487
498
  const exports = [];
488
499
 
489
500
  traverseTree(tree.rootNode, (node) => {
@@ -563,7 +574,7 @@ function findExportsInCode(code, parser) {
563
574
  * @returns {Array<{line: number, column: number, usageType: string}>}
564
575
  */
565
576
  function findUsagesInCode(code, name, parser) {
566
- const tree = parser.parse(code, undefined, PARSE_OPTIONS);
577
+ const tree = parseTree(parser, code);
567
578
  const usages = [];
568
579
 
569
580
  traverseTree(tree.rootNode, (node) => {
package/languages/java.js CHANGED
@@ -11,7 +11,11 @@ const {
11
11
  parseStructuredParams,
12
12
  extractJavaDocstring
13
13
  } = require('./utils');
14
- const { PARSE_OPTIONS } = require('./index');
14
+ const { PARSE_OPTIONS, safeParse } = require('./index');
15
+
16
+ function parseTree(parser, code) {
17
+ return safeParse(parser, code, undefined, PARSE_OPTIONS);
18
+ }
15
19
 
16
20
  /**
17
21
  * Extract Java parameters
@@ -99,7 +103,7 @@ function extractGenerics(node) {
99
103
  * Find all methods/constructors in Java code using tree-sitter
100
104
  */
101
105
  function findFunctions(code, parser) {
102
- const tree = parser.parse(code, undefined, PARSE_OPTIONS);
106
+ const tree = parseTree(parser, code);
103
107
  const functions = [];
104
108
  const processedRanges = new Set();
105
109
 
@@ -150,6 +154,12 @@ function findFunctions(code, parser) {
150
154
  if (processedRanges.has(rangeKey)) return true;
151
155
  processedRanges.add(rangeKey);
152
156
 
157
+ // Skip constructors inside a class body (they're extracted as class members)
158
+ let parent = node.parent;
159
+ if (parent && parent.type === 'class_body') {
160
+ return true; // Skip - this is a class constructor
161
+ }
162
+
153
163
  const nameNode = node.childForFieldName('name');
154
164
  const paramsNode = node.childForFieldName('parameters');
155
165
 
@@ -186,7 +196,7 @@ function findFunctions(code, parser) {
186
196
  * Find all classes, interfaces, enums, records in Java code
187
197
  */
188
198
  function findClasses(code, parser) {
189
- const tree = parser.parse(code, undefined, PARSE_OPTIONS);
199
+ const tree = parseTree(parser, code);
190
200
  const classes = [];
191
201
  const processedRanges = new Set();
192
202
 
@@ -209,6 +219,10 @@ function findClasses(code, parser) {
209
219
  const extendsInfo = extractExtends(node);
210
220
  const implementsInfo = extractImplements(node);
211
221
 
222
+ // Check if this is a nested/inner class
223
+ let parentNode = node.parent;
224
+ const isNested = parentNode && parentNode.type === 'class_body';
225
+
212
226
  classes.push({
213
227
  name: nameNode.text,
214
228
  startLine,
@@ -216,6 +230,7 @@ function findClasses(code, parser) {
216
230
  type: 'class',
217
231
  members,
218
232
  modifiers,
233
+ ...(isNested && { isNested: true }),
219
234
  ...(docstring && { docstring }),
220
235
  ...(generics && { generics }),
221
236
  ...(annotations.length > 0 && { annotations }),
@@ -223,7 +238,8 @@ function findClasses(code, parser) {
223
238
  ...(implementsInfo.length > 0 && { implements: implementsInfo })
224
239
  });
225
240
  }
226
- return false; // Don't traverse into class body
241
+ // Continue traversal to find inner classes, but members are already extracted
242
+ return true;
227
243
  }
228
244
 
229
245
  // Interface declarations
@@ -438,7 +454,7 @@ function extractClassMembers(classNode, code) {
438
454
  * Find state objects (static final constants) in Java code
439
455
  */
440
456
  function findStateObjects(code, parser) {
441
- const tree = parser.parse(code, undefined, PARSE_OPTIONS);
457
+ const tree = parseTree(parser, code);
442
458
  const objects = [];
443
459
 
444
460
  const statePattern = /^([A-Z][A-Z0-9_]+|[A-Z][a-zA-Z]*(?:CONFIG|SETTINGS|OPTIONS))$/;
@@ -495,7 +511,7 @@ function parse(code, parser) {
495
511
  * @returns {Array<{name: string, line: number, isMethod: boolean, receiver?: string, isConstructor?: boolean}>}
496
512
  */
497
513
  function findCallsInCode(code, parser) {
498
- const tree = parser.parse(code, undefined, PARSE_OPTIONS);
514
+ const tree = parseTree(parser, code);
499
515
  const calls = [];
500
516
  const functionStack = []; // Stack of { name, startLine, endLine }
501
517
 
@@ -602,7 +618,7 @@ function findCallsInCode(code, parser) {
602
618
  * @returns {Array<{module: string, names: string[], type: string, line: number}>}
603
619
  */
604
620
  function findImportsInCode(code, parser) {
605
- const tree = parser.parse(code, undefined, PARSE_OPTIONS);
621
+ const tree = parseTree(parser, code);
606
622
  const imports = [];
607
623
 
608
624
  traverseTree(tree.rootNode, (node) => {
@@ -648,7 +664,7 @@ function findImportsInCode(code, parser) {
648
664
  * @returns {Array<{name: string, type: string, line: number}>}
649
665
  */
650
666
  function findExportsInCode(code, parser) {
651
- const tree = parser.parse(code, undefined, PARSE_OPTIONS);
667
+ const tree = parseTree(parser, code);
652
668
  const exports = [];
653
669
 
654
670
  function isPublic(node) {
@@ -728,7 +744,7 @@ function findExportsInCode(code, parser) {
728
744
  * @returns {Array<{line: number, column: number, usageType: string}>}
729
745
  */
730
746
  function findUsagesInCode(code, name, parser) {
731
- const tree = parser.parse(code, undefined, PARSE_OPTIONS);
747
+ const tree = parseTree(parser, code);
732
748
  const usages = [];
733
749
 
734
750
  traverseTree(tree.rootNode, (node) => {
@@ -12,7 +12,12 @@ const {
12
12
  parseStructuredParams,
13
13
  extractJSDocstring
14
14
  } = require('./utils');
15
- const { PARSE_OPTIONS } = require('./index');
15
+ const { PARSE_OPTIONS, safeParse } = require('./index');
16
+
17
+ // Helper to consistently parse with buffer retries
18
+ function parseTree(parser, code) {
19
+ return safeParse(parser, code, undefined, PARSE_OPTIONS);
20
+ }
16
21
 
17
22
  /**
18
23
  * Extract return type annotation from JS/TS function
@@ -88,7 +93,7 @@ function extractModifiers(text) {
88
93
  * @returns {Array}
89
94
  */
90
95
  function findFunctions(code, parser) {
91
- const tree = parser.parse(code, undefined, PARSE_OPTIONS);
96
+ const tree = parseTree(parser, code);
92
97
  const functions = [];
93
98
  const processedRanges = new Set();
94
99
 
@@ -327,7 +332,7 @@ function findFunctions(code, parser) {
327
332
  * @returns {Array}
328
333
  */
329
334
  function findClasses(code, parser) {
330
- const tree = parser.parse(code, undefined, PARSE_OPTIONS);
335
+ const tree = parseTree(parser, code);
331
336
  const classes = [];
332
337
 
333
338
  traverseTree(tree.rootNode, (node) => {
@@ -581,7 +586,7 @@ function extractClassMembers(classNode, code) {
581
586
  * Find state objects (CONFIG, constants, etc.)
582
587
  */
583
588
  function findStateObjects(code, parser) {
584
- const tree = parser.parse(code, undefined, PARSE_OPTIONS);
589
+ const tree = parseTree(parser, code);
585
590
  const objects = [];
586
591
 
587
592
  const statePattern = /^(CONFIG|[A-Z][a-zA-Z]*(?:State|Store|Context|Options|Settings)|[A-Z][A-Z_]+|Entities|Input)$/;
@@ -655,7 +660,7 @@ function parse(code, parser) {
655
660
  * @returns {Array<{name: string, line: number, isMethod: boolean, receiver?: string, isConstructor?: boolean}>}
656
661
  */
657
662
  function findCallsInCode(code, parser) {
658
- const tree = parser.parse(code, undefined, PARSE_OPTIONS);
663
+ const tree = parseTree(parser, code);
659
664
  const calls = [];
660
665
  const functionStack = []; // Stack of { name, startLine, endLine }
661
666
 
@@ -718,6 +723,9 @@ function findCallsInCode(code, parser) {
718
723
  if (!funcNode) return true;
719
724
 
720
725
  const enclosingFunction = getCurrentEnclosingFunction();
726
+ let uncertain = false;
727
+ // optional chaining implies possible non-call
728
+ if (node.type === 'call_expression' && node.text.includes('?.')) uncertain = true;
721
729
 
722
730
  if (funcNode.type === 'identifier') {
723
731
  // Direct call: foo()
@@ -725,7 +733,8 @@ function findCallsInCode(code, parser) {
725
733
  name: funcNode.text,
726
734
  line: node.startPosition.row + 1,
727
735
  isMethod: false,
728
- enclosingFunction
736
+ enclosingFunction,
737
+ uncertain
729
738
  });
730
739
  } else if (funcNode.type === 'member_expression') {
731
740
  // Method call: obj.foo() or foo.call/apply/bind()
@@ -755,7 +764,8 @@ function findCallsInCode(code, parser) {
755
764
  line: node.startPosition.row + 1,
756
765
  isMethod: true,
757
766
  receiver: innerObj?.text,
758
- enclosingFunction
767
+ enclosingFunction,
768
+ uncertain
759
769
  });
760
770
  }
761
771
  }
@@ -773,7 +783,8 @@ function findCallsInCode(code, parser) {
773
783
  line: node.startPosition.row + 1,
774
784
  isMethod: true,
775
785
  receiver,
776
- enclosingFunction
786
+ enclosingFunction,
787
+ uncertain
777
788
  });
778
789
  }
779
790
  }
@@ -877,7 +888,7 @@ function findCallsInCode(code, parser) {
877
888
  * @returns {Array<{line: number, context: string, pattern: string}>}
878
889
  */
879
890
  function findCallbackUsages(code, name, parser) {
880
- const tree = parser.parse(code, undefined, PARSE_OPTIONS);
891
+ const tree = parseTree(parser, code);
881
892
  const usages = [];
882
893
 
883
894
  traverseTree(tree.rootNode, (node) => {
@@ -974,7 +985,7 @@ function findCallbackUsages(code, name, parser) {
974
985
  * @returns {Array<{name: string, from: string, line: number}>}
975
986
  */
976
987
  function findReExports(code, parser) {
977
- const tree = parser.parse(code, undefined, PARSE_OPTIONS);
988
+ const tree = parseTree(parser, code);
978
989
  const reExports = [];
979
990
 
980
991
  traverseTree(tree.rootNode, (node) => {
@@ -1026,7 +1037,7 @@ function findReExports(code, parser) {
1026
1037
  * @returns {Array<{module: string, names: string[], type: string, line: number}>}
1027
1038
  */
1028
1039
  function findImportsInCode(code, parser) {
1029
- const tree = parser.parse(code, undefined, PARSE_OPTIONS);
1040
+ const tree = parseTree(parser, code);
1030
1041
  const imports = [];
1031
1042
 
1032
1043
  traverseTree(tree.rootNode, (node) => {
@@ -1091,35 +1102,41 @@ function findImportsInCode(code, parser) {
1091
1102
  const argsNode = node.childForFieldName('arguments');
1092
1103
  if (argsNode && argsNode.namedChildCount > 0) {
1093
1104
  const firstArg = argsNode.namedChild(0);
1105
+ const line = node.startPosition.row + 1;
1106
+ const names = [];
1107
+ let modulePath = null;
1108
+ let dynamic = false;
1109
+
1094
1110
  if (firstArg && firstArg.type === 'string') {
1095
- const modulePath = firstArg.text.slice(1, -1);
1096
- const line = node.startPosition.row + 1;
1097
- const names = [];
1111
+ modulePath = firstArg.text.slice(1, -1);
1112
+ } else {
1113
+ dynamic = true;
1114
+ modulePath = firstArg ? firstArg.text : null;
1115
+ }
1098
1116
 
1099
- // Check parent for variable name
1100
- let parent = node.parent;
1101
- if (parent && parent.type === 'variable_declarator') {
1102
- const nameNode = parent.childForFieldName('name');
1103
- if (nameNode) {
1104
- if (nameNode.type === 'identifier') {
1105
- names.push(nameNode.text);
1106
- } else if (nameNode.type === 'object_pattern') {
1107
- // Destructuring: const { a, b } = require('x')
1108
- for (let i = 0; i < nameNode.namedChildCount; i++) {
1109
- const prop = nameNode.namedChild(i);
1110
- if (prop.type === 'shorthand_property_identifier_pattern') {
1111
- names.push(prop.text);
1112
- } else if (prop.type === 'pair_pattern') {
1113
- const key = prop.childForFieldName('key');
1114
- if (key) names.push(key.text);
1115
- }
1117
+ // Check parent for variable name
1118
+ let parent = node.parent;
1119
+ if (parent && parent.type === 'variable_declarator') {
1120
+ const nameNode = parent.childForFieldName('name');
1121
+ if (nameNode) {
1122
+ if (nameNode.type === 'identifier') {
1123
+ names.push(nameNode.text);
1124
+ } else if (nameNode.type === 'object_pattern') {
1125
+ // Destructuring: const { a, b } = require('x')
1126
+ for (let i = 0; i < nameNode.namedChildCount; i++) {
1127
+ const prop = nameNode.namedChild(i);
1128
+ if (prop.type === 'shorthand_property_identifier_pattern') {
1129
+ names.push(prop.text);
1130
+ } else if (prop.type === 'pair_pattern') {
1131
+ const key = prop.childForFieldName('key');
1132
+ if (key) names.push(key.text);
1116
1133
  }
1117
1134
  }
1118
1135
  }
1119
1136
  }
1120
-
1121
- imports.push({ module: modulePath, names, type: 'require', line });
1122
1137
  }
1138
+
1139
+ imports.push({ module: modulePath, names, type: 'require', line, dynamic });
1123
1140
  }
1124
1141
  }
1125
1142
 
@@ -1128,10 +1145,12 @@ function findImportsInCode(code, parser) {
1128
1145
  const argsNode = node.childForFieldName('arguments');
1129
1146
  if (argsNode && argsNode.namedChildCount > 0) {
1130
1147
  const firstArg = argsNode.namedChild(0);
1148
+ const line = node.startPosition.row + 1;
1131
1149
  if (firstArg && firstArg.type === 'string') {
1132
1150
  const modulePath = firstArg.text.slice(1, -1);
1133
- const line = node.startPosition.row + 1;
1134
- imports.push({ module: modulePath, names: [], type: 'dynamic', line });
1151
+ imports.push({ module: modulePath, names: [], type: 'dynamic', line, dynamic: false });
1152
+ } else {
1153
+ imports.push({ module: firstArg ? firstArg.text : null, names: [], type: 'dynamic', line, dynamic: true });
1135
1154
  }
1136
1155
  }
1137
1156
  }
@@ -1151,7 +1170,7 @@ function findImportsInCode(code, parser) {
1151
1170
  * @returns {Array<{name: string, type: string, line: number, source?: string}>}
1152
1171
  */
1153
1172
  function findExportsInCode(code, parser) {
1154
- const tree = parser.parse(code, undefined, PARSE_OPTIONS);
1173
+ const tree = parseTree(parser, code);
1155
1174
  const exports = [];
1156
1175
 
1157
1176
  traverseTree(tree.rootNode, (node) => {
@@ -1288,7 +1307,7 @@ function findExportsInCode(code, parser) {
1288
1307
  * @returns {Array<{line: number, column: number, usageType: string}>}
1289
1308
  */
1290
1309
  function findUsagesInCode(code, name, parser) {
1291
- const tree = parser.parse(code, undefined, PARSE_OPTIONS);
1310
+ const tree = parseTree(parser, code);
1292
1311
  const usages = [];
1293
1312
 
1294
1313
  traverseTree(tree.rootNode, (node) => {
@@ -11,7 +11,11 @@ const {
11
11
  parseStructuredParams,
12
12
  extractPythonDocstring
13
13
  } = require('./utils');
14
- const { PARSE_OPTIONS } = require('./index');
14
+ const { PARSE_OPTIONS, safeParse } = require('./index');
15
+
16
+ function parseTree(parser, code) {
17
+ return safeParse(parser, code, undefined, PARSE_OPTIONS);
18
+ }
15
19
 
16
20
  /**
17
21
  * Extract return type annotation from Python function
@@ -62,7 +66,7 @@ function extractPythonParams(paramsNode) {
62
66
  * Find all functions in Python code using tree-sitter
63
67
  */
64
68
  function findFunctions(code, parser) {
65
- const tree = parser.parse(code, undefined, PARSE_OPTIONS);
69
+ const tree = parseTree(parser, code);
66
70
  const functions = [];
67
71
  const processedRanges = new Set();
68
72
 
@@ -159,7 +163,7 @@ function extractDecorators(node) {
159
163
  * Find all classes in Python code using tree-sitter
160
164
  */
161
165
  function findClasses(code, parser) {
162
- const tree = parser.parse(code, undefined, PARSE_OPTIONS);
166
+ const tree = parseTree(parser, code);
163
167
  const classes = [];
164
168
  const processedRanges = new Set();
165
169
 
@@ -312,7 +316,7 @@ function extractClassMembers(classNode, code) {
312
316
  * Find state objects (constants) in Python code
313
317
  */
314
318
  function findStateObjects(code, parser) {
315
- const tree = parser.parse(code, undefined, PARSE_OPTIONS);
319
+ const tree = parseTree(parser, code);
316
320
  const objects = [];
317
321
 
318
322
  const statePattern = /^(CONFIG|SETTINGS|[A-Z][A-Z0-9_]+|[A-Z][a-zA-Z]*(?:Config|Settings|Options|State|Store|Context))$/;
@@ -365,7 +369,7 @@ function parse(code, parser) {
365
369
  * @returns {Array<{name: string, line: number, isMethod: boolean, receiver?: string}>}
366
370
  */
367
371
  function findCallsInCode(code, parser) {
368
- const tree = parser.parse(code, undefined, PARSE_OPTIONS);
372
+ const tree = parseTree(parser, code);
369
373
  const calls = [];
370
374
  const functionStack = []; // Stack of { name, startLine, endLine }
371
375
 
@@ -409,6 +413,7 @@ function findCallsInCode(code, parser) {
409
413
  if (!funcNode) return true;
410
414
 
411
415
  const enclosingFunction = getCurrentEnclosingFunction();
416
+ let uncertain = false;
412
417
 
413
418
  if (funcNode.type === 'identifier') {
414
419
  // Direct call: foo()
@@ -416,7 +421,8 @@ function findCallsInCode(code, parser) {
416
421
  name: funcNode.text,
417
422
  line: node.startPosition.row + 1,
418
423
  isMethod: false,
419
- enclosingFunction
424
+ enclosingFunction,
425
+ uncertain
420
426
  });
421
427
  } else if (funcNode.type === 'attribute') {
422
428
  // Method/attribute call: obj.foo()
@@ -429,7 +435,8 @@ function findCallsInCode(code, parser) {
429
435
  line: node.startPosition.row + 1,
430
436
  isMethod: true,
431
437
  receiver: objNode?.type === 'identifier' ? objNode.text : undefined,
432
- enclosingFunction
438
+ enclosingFunction,
439
+ uncertain
433
440
  });
434
441
  }
435
442
  }
@@ -455,7 +462,7 @@ function findCallsInCode(code, parser) {
455
462
  * @returns {Array<{module: string, names: string[], type: string, line: number}>}
456
463
  */
457
464
  function findImportsInCode(code, parser) {
458
- const tree = parser.parse(code, undefined, PARSE_OPTIONS);
465
+ const tree = parseTree(parser, code);
459
466
  const imports = [];
460
467
 
461
468
  traverseTree(tree.rootNode, (node) => {
@@ -526,6 +533,28 @@ function findImportsInCode(code, parser) {
526
533
  return true;
527
534
  }
528
535
 
536
+ // Dynamic imports via importlib/import_module or __import__
537
+ if (node.type === 'call') {
538
+ const funcNode = node.childForFieldName('function');
539
+ const argsNode = node.childForFieldName('arguments');
540
+ if (funcNode && argsNode && argsNode.namedChildCount > 0) {
541
+ const funcName = funcNode.text;
542
+ const firstArg = argsNode.namedChild(0);
543
+ if ((funcName === 'importlib.import_module' || funcName === '__import__') && firstArg) {
544
+ const line = node.startPosition.row + 1;
545
+ const isLiteral = firstArg.type === 'string';
546
+ imports.push({
547
+ module: isLiteral ? firstArg.text.replace(/^['"]|['"]$/g, '') : firstArg.text,
548
+ names: [],
549
+ type: 'dynamic',
550
+ line,
551
+ dynamic: !isLiteral
552
+ });
553
+ }
554
+ }
555
+ return true;
556
+ }
557
+
529
558
  return true;
530
559
  });
531
560
 
@@ -540,7 +569,7 @@ function findImportsInCode(code, parser) {
540
569
  * @returns {Array<{name: string, type: string, line: number}>}
541
570
  */
542
571
  function findExportsInCode(code, parser) {
543
- const tree = parser.parse(code, undefined, PARSE_OPTIONS);
572
+ const tree = parseTree(parser, code);
544
573
  const exports = [];
545
574
 
546
575
  traverseTree(tree.rootNode, (node) => {
@@ -590,7 +619,7 @@ function findExportsInCode(code, parser) {
590
619
  * @returns {Array<{line: number, column: number, usageType: string}>}
591
620
  */
592
621
  function findUsagesInCode(code, name, parser) {
593
- const tree = parser.parse(code, undefined, PARSE_OPTIONS);
622
+ const tree = parseTree(parser, code);
594
623
  const usages = [];
595
624
 
596
625
  traverseTree(tree.rootNode, (node) => {