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/README.md +6 -2
- package/cli/index.js +145 -43
- package/core/imports.js +153 -4
- package/core/output.js +129 -147
- package/core/project.js +365 -122
- package/languages/go.js +21 -10
- package/languages/java.js +25 -9
- package/languages/javascript.js +56 -37
- package/languages/python.js +39 -10
- package/languages/rust.js +36 -8
- package/package.json +1 -1
- package/test/parser.test.js +967 -7
- package/test/reliability-test-prompt.md +58 -0
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
|
|
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
|
|
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
|
|
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
|
|
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
|
|
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
|
|
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
|
|
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
|
|
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
|
|
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
|
-
|
|
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
|
|
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
|
|
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
|
|
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
|
|
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
|
|
747
|
+
const tree = parseTree(parser, code);
|
|
732
748
|
const usages = [];
|
|
733
749
|
|
|
734
750
|
traverseTree(tree.rootNode, (node) => {
|
package/languages/javascript.js
CHANGED
|
@@ -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
|
|
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
|
|
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
|
|
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
|
|
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
|
|
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
|
|
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
|
|
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
|
-
|
|
1096
|
-
|
|
1097
|
-
|
|
1111
|
+
modulePath = firstArg.text.slice(1, -1);
|
|
1112
|
+
} else {
|
|
1113
|
+
dynamic = true;
|
|
1114
|
+
modulePath = firstArg ? firstArg.text : null;
|
|
1115
|
+
}
|
|
1098
1116
|
|
|
1099
|
-
|
|
1100
|
-
|
|
1101
|
-
|
|
1102
|
-
|
|
1103
|
-
|
|
1104
|
-
|
|
1105
|
-
|
|
1106
|
-
|
|
1107
|
-
|
|
1108
|
-
|
|
1109
|
-
|
|
1110
|
-
|
|
1111
|
-
|
|
1112
|
-
|
|
1113
|
-
|
|
1114
|
-
|
|
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
|
-
|
|
1134
|
-
|
|
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
|
|
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
|
|
1310
|
+
const tree = parseTree(parser, code);
|
|
1292
1311
|
const usages = [];
|
|
1293
1312
|
|
|
1294
1313
|
traverseTree(tree.rootNode, (node) => {
|
package/languages/python.js
CHANGED
|
@@ -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
|
|
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
|
|
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
|
|
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
|
|
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
|
|
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
|
|
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
|
|
622
|
+
const tree = parseTree(parser, code);
|
|
594
623
|
const usages = [];
|
|
595
624
|
|
|
596
625
|
traverseTree(tree.rootNode, (node) => {
|