ucn 4.0.2 → 4.1.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.
- package/.claude/skills/ucn/SKILL.md +31 -7
- package/README.md +89 -50
- package/cli/index.js +199 -94
- package/core/account.js +3 -1
- package/core/analysis.js +322 -304
- package/core/bridge.js +13 -8
- package/core/cache.js +109 -19
- package/core/callers.js +969 -77
- package/core/check.js +19 -8
- package/core/deadcode.js +368 -40
- package/core/discovery.js +31 -11
- package/core/entrypoints.js +149 -17
- package/core/execute.js +330 -43
- package/core/graph-build.js +61 -10
- package/core/graph.js +282 -61
- package/core/imports.js +72 -3
- package/core/output/analysis-ext.js +70 -10
- package/core/output/analysis.js +52 -33
- package/core/output/check.js +4 -1
- package/core/output/endpoints.js +8 -3
- package/core/output/extraction.js +12 -1
- package/core/output/find.js +23 -9
- package/core/output/graph.js +32 -9
- package/core/output/refactoring.js +147 -49
- package/core/output/reporting.js +104 -5
- package/core/output/search.js +30 -3
- package/core/output/shared.js +31 -4
- package/core/output/tracing.js +22 -11
- package/core/parser.js +8 -6
- package/core/project.js +167 -7
- package/core/registry.js +20 -16
- package/core/reporting.js +131 -13
- package/core/search.js +270 -55
- package/core/shared.js +240 -1
- package/core/stacktrace.js +23 -1
- package/core/tracing.js +278 -36
- package/core/verify.js +352 -349
- package/languages/go.js +29 -17
- package/languages/index.js +56 -0
- package/languages/java.js +133 -16
- package/languages/javascript.js +215 -27
- package/languages/python.js +113 -47
- package/languages/rust.js +89 -26
- package/languages/utils.js +35 -7
- package/mcp/server.js +69 -30
- package/package.json +4 -1
package/languages/go.js
CHANGED
|
@@ -12,6 +12,7 @@ const {
|
|
|
12
12
|
parseStructuredParams,
|
|
13
13
|
extractGoDocstring,
|
|
14
14
|
visitNameNodes,
|
|
15
|
+
sameNode,
|
|
15
16
|
} = require('./utils');
|
|
16
17
|
const { PARSE_OPTIONS, safeParse } = require('./index');
|
|
17
18
|
|
|
@@ -34,11 +35,13 @@ function extractReturnType(node) {
|
|
|
34
35
|
* Extract Go parameters
|
|
35
36
|
*/
|
|
36
37
|
function extractGoParams(paramsNode) {
|
|
38
|
+
// Distinguish "we have no node" (genuinely unknown) from "node is empty".
|
|
39
|
+
// Returning '...' for empty parens conflated zero-param functions with
|
|
40
|
+
// unknown signatures in JSON output (fix #238; the shared
|
|
41
|
+
// utils.extractParams already had this fix).
|
|
37
42
|
if (!paramsNode) return '...';
|
|
38
43
|
const text = paramsNode.text;
|
|
39
|
-
|
|
40
|
-
if (!params) return '...';
|
|
41
|
-
return params;
|
|
44
|
+
return text.replace(/^\(|\)$/g, '').trim();
|
|
42
45
|
}
|
|
43
46
|
|
|
44
47
|
/**
|
|
@@ -49,11 +52,15 @@ function extractReceiver(receiverNode) {
|
|
|
49
52
|
// receiverNode is a parameter_list: (r *Router)
|
|
50
53
|
// Find the parameter_declaration child
|
|
51
54
|
const param = receiverNode.namedChildren.find(c => c.type === 'parameter_declaration');
|
|
52
|
-
if (!param) return receiverNode.text.replace(/^\(|\)$/g, '').trim();
|
|
55
|
+
if (!param) return receiverNode.text.replace(/^\(|\)$/g, '').trim().replace(/\[.*\]$/, '');
|
|
53
56
|
// The type is the last named child (name is first for named receivers)
|
|
54
57
|
const typeNode = param.namedChildren[param.namedChildren.length - 1];
|
|
55
58
|
if (!typeNode) return null;
|
|
56
|
-
|
|
59
|
+
// Strip generic type parameters: `(p *Pair[K, V])` receives on the TYPE
|
|
60
|
+
// Pair — the bracket spelling made fn Pair.First/--class-name/
|
|
61
|
+
// findMethodsForType all miss (fix #248; Rust/Java already normalize,
|
|
62
|
+
// and a Go receiver type is a defined type, never a slice/map literal).
|
|
63
|
+
return typeNode.text.replace(/\[.*\]$/, '');
|
|
57
64
|
}
|
|
58
65
|
|
|
59
66
|
// --- Single-pass helpers: extracted from find* callbacks ---
|
|
@@ -1634,10 +1641,11 @@ function findExportsInCode(code, parser) {
|
|
|
1634
1641
|
* @param {string} code - Source code
|
|
1635
1642
|
* @param {string} name - Symbol name to find
|
|
1636
1643
|
* @param {object} parser - Tree-sitter parser instance
|
|
1644
|
+
* @param {object} [tree] - Pre-parsed tree (per-operation cache); parsed here when absent
|
|
1637
1645
|
* @returns {Array<{line: number, column: number, usageType: string}>}
|
|
1638
1646
|
*/
|
|
1639
|
-
function findUsagesInCode(code, name, parser) {
|
|
1640
|
-
|
|
1647
|
+
function findUsagesInCode(code, name, parser, tree) {
|
|
1648
|
+
tree = tree || parseTree(parser, code);
|
|
1641
1649
|
const usages = [];
|
|
1642
1650
|
|
|
1643
1651
|
visitNameNodes(tree, code, name, (node) => {
|
|
@@ -1662,52 +1670,52 @@ function findUsagesInCode(code, name, parser) {
|
|
|
1662
1670
|
}
|
|
1663
1671
|
// Call: identifier is function in call_expression
|
|
1664
1672
|
else if (parent.type === 'call_expression' &&
|
|
1665
|
-
parent.childForFieldName('function')
|
|
1673
|
+
sameNode(parent.childForFieldName('function'), node)) {
|
|
1666
1674
|
usageType = 'call';
|
|
1667
1675
|
}
|
|
1668
1676
|
// Definition: function name
|
|
1669
1677
|
else if (parent.type === 'function_declaration' &&
|
|
1670
|
-
parent.childForFieldName('name')
|
|
1678
|
+
sameNode(parent.childForFieldName('name'), node)) {
|
|
1671
1679
|
usageType = 'definition';
|
|
1672
1680
|
}
|
|
1673
1681
|
// Definition: method name
|
|
1674
1682
|
else if (parent.type === 'method_declaration' &&
|
|
1675
|
-
parent.childForFieldName('name')
|
|
1683
|
+
sameNode(parent.childForFieldName('name'), node)) {
|
|
1676
1684
|
usageType = 'definition';
|
|
1677
1685
|
}
|
|
1678
1686
|
// Definition: type name
|
|
1679
1687
|
else if (parent.type === 'type_spec' &&
|
|
1680
|
-
parent.childForFieldName('name')
|
|
1688
|
+
sameNode(parent.childForFieldName('name'), node)) {
|
|
1681
1689
|
usageType = 'definition';
|
|
1682
1690
|
}
|
|
1683
1691
|
// Definition: variable name in short var declaration
|
|
1684
1692
|
else if (parent.type === 'short_var_declaration') {
|
|
1685
1693
|
const left = parent.childForFieldName('left');
|
|
1686
|
-
if (left && (left
|
|
1694
|
+
if (left && (sameNode(left, node) || left.namedChildren?.some(c => sameNode(c, node)))) {
|
|
1687
1695
|
usageType = 'definition';
|
|
1688
1696
|
}
|
|
1689
1697
|
}
|
|
1690
1698
|
// Multi-var: x, err := foo() — identifier parent is expression_list
|
|
1691
1699
|
else if (parent.type === 'expression_list' &&
|
|
1692
1700
|
parent.parent?.type === 'short_var_declaration' &&
|
|
1693
|
-
parent.parent.childForFieldName('left')
|
|
1701
|
+
sameNode(parent.parent.childForFieldName('left'), parent)) {
|
|
1694
1702
|
usageType = 'definition';
|
|
1695
1703
|
}
|
|
1696
1704
|
// Definition: const/var spec
|
|
1697
1705
|
else if (parent.type === 'const_spec' || parent.type === 'var_spec') {
|
|
1698
1706
|
const nameNode = parent.childForFieldName('name');
|
|
1699
|
-
if (nameNode
|
|
1707
|
+
if (sameNode(nameNode, node)) {
|
|
1700
1708
|
usageType = 'definition';
|
|
1701
1709
|
}
|
|
1702
1710
|
}
|
|
1703
1711
|
// Definition: parameter name (not the type)
|
|
1704
1712
|
else if (parent.type === 'parameter_declaration' &&
|
|
1705
|
-
parent.childForFieldName('name')
|
|
1713
|
+
sameNode(parent.childForFieldName('name'), node)) {
|
|
1706
1714
|
usageType = 'definition';
|
|
1707
1715
|
}
|
|
1708
1716
|
// Composite literal: Type{} — type_identifier is the type of composite_literal
|
|
1709
1717
|
else if (parent.type === 'composite_literal' &&
|
|
1710
|
-
parent.childForFieldName('type')
|
|
1718
|
+
sameNode(parent.childForFieldName('type'), node)) {
|
|
1711
1719
|
usageType = 'call';
|
|
1712
1720
|
}
|
|
1713
1721
|
// Method call: selector_expression followed by call (field_identifier case)
|
|
@@ -1745,8 +1753,12 @@ function findUsagesInCode(code, name, parser) {
|
|
|
1745
1753
|
*/
|
|
1746
1754
|
function getEntryPointKind(symbol) {
|
|
1747
1755
|
const { name } = symbol;
|
|
1756
|
+
// Test* stays receiver-agnostic — testify suite METHODS (func (s *Suite)
|
|
1757
|
+
// TestFoo) are genuinely harness-invoked.
|
|
1748
1758
|
if (/^(Test|Benchmark|Example|Fuzz)[A-Z_]/.test(name)) return 'test';
|
|
1749
|
-
|
|
1759
|
+
// Only FREE functions main/init are runtime entries — a method named
|
|
1760
|
+
// main/init on a receiver is an ordinary method (fix #243).
|
|
1761
|
+
if ((name === 'main' || name === 'init') && !symbol.className && !symbol.receiver) return 'main';
|
|
1750
1762
|
return null;
|
|
1751
1763
|
}
|
|
1752
1764
|
|
package/languages/index.js
CHANGED
|
@@ -21,8 +21,18 @@ const STRUCTURAL_TRAITS = {
|
|
|
21
21
|
exportVisibility: 'keyword',
|
|
22
22
|
hasDynamicImports: true,
|
|
23
23
|
testDirs: [],
|
|
24
|
+
// Whether the language supports default parameter values in function
|
|
25
|
+
// signatures (JS/TS/Python: yes). Drives plan --add-param --default:
|
|
26
|
+
// without them, every call site needs the new argument and the rendered
|
|
27
|
+
// signature must not use `= value` syntax (Go/Java/Rust).
|
|
28
|
+
hasDefaultParams: true,
|
|
24
29
|
allMethodsVirtual: false,
|
|
25
30
|
hasArityOverloads: false,
|
|
31
|
+
// Whether `from pkg import name` can bind a SUBMODULE file as a plain
|
|
32
|
+
// name (fix #224). Python sets true: graph-build resolves the composed
|
|
33
|
+
// dotted specifier and records it in moduleResolved, making the receiver
|
|
34
|
+
// a module receiver at query time. JS from-imports bind values only.
|
|
35
|
+
submoduleImports: false,
|
|
26
36
|
// Class members are public unless marked otherwise (#name, _name,
|
|
27
37
|
// `private`). An exported class therefore exposes its non-private methods
|
|
28
38
|
// as public API — deadcode treats them as exported (fix #211). Languages
|
|
@@ -49,6 +59,8 @@ const NOMINAL_TRAITS = {
|
|
|
49
59
|
exportVisibility: 'keyword',
|
|
50
60
|
hasDynamicImports: true,
|
|
51
61
|
testDirs: [],
|
|
62
|
+
// Go/Java/Rust have no default parameter values — see STRUCTURAL_TRAITS.
|
|
63
|
+
hasDefaultParams: false,
|
|
52
64
|
// Whether ANY instance method call can dynamically dispatch to a subtype
|
|
53
65
|
// override (Java: all instance methods are virtual). Go struct method
|
|
54
66
|
// sets and Rust inherent methods bind statically — only interface/trait
|
|
@@ -153,6 +165,11 @@ const LANGUAGES = {
|
|
|
153
165
|
traits: {
|
|
154
166
|
...STRUCTURAL_TRAITS,
|
|
155
167
|
selfParam: ['self', 'cls'],
|
|
168
|
+
// fix #224: `from pkg import name` may bind a SUBMODULE file, not
|
|
169
|
+
// a symbol — graph-build resolves the composed dotted specifier
|
|
170
|
+
// and query code treats such receivers as module receivers. JS
|
|
171
|
+
// from-imports bind values only (`import * as ns` is parser-marked).
|
|
172
|
+
submoduleImports: true,
|
|
156
173
|
testFileCandidates: (base, ext) => [`test_${base}.py`, `${base}_test.py`],
|
|
157
174
|
testDirs: ['tests'],
|
|
158
175
|
},
|
|
@@ -244,10 +261,49 @@ function loadTreeSitter() {
|
|
|
244
261
|
{ cause: e }
|
|
245
262
|
);
|
|
246
263
|
}
|
|
264
|
+
_installNodeTypeCache(TreeSitter);
|
|
247
265
|
}
|
|
248
266
|
return TreeSitter;
|
|
249
267
|
}
|
|
250
268
|
|
|
269
|
+
/**
|
|
270
|
+
* Make SyntaxNode#type a cheap data-property read.
|
|
271
|
+
*
|
|
272
|
+
* The binding generates one node subclass per grammar type id and intends
|
|
273
|
+
* `nodeSubclass.prototype.type = typeName` to shadow the base getter — but
|
|
274
|
+
* SyntaxNode.prototype.type is a getter-only accessor, so that plain
|
|
275
|
+
* assignment silently no-ops and EVERY `.type` read marshals into native
|
|
276
|
+
* code. Hot walks read `.type` many times per node; on a ~300-file build
|
|
277
|
+
* this getter alone costs seconds.
|
|
278
|
+
*
|
|
279
|
+
* A node's type is immutable and each generated subclass maps to exactly
|
|
280
|
+
* one grammar type, so the first native read per CLASS plants the string as
|
|
281
|
+
* a data property on that class's prototype — all later reads on any node
|
|
282
|
+
* of that type are plain property hits. Nodes using the base class itself
|
|
283
|
+
* (anonymous tokens, ERROR) keep the native getter.
|
|
284
|
+
*/
|
|
285
|
+
function _installNodeTypeCache(TS) {
|
|
286
|
+
try {
|
|
287
|
+
const base = TS?.SyntaxNode?.prototype;
|
|
288
|
+
const desc = base && Object.getOwnPropertyDescriptor(base, 'type');
|
|
289
|
+
if (!desc?.get || desc.set) return; // shape changed upstream — leave it alone
|
|
290
|
+
const nativeGet = desc.get;
|
|
291
|
+
Object.defineProperty(base, 'type', {
|
|
292
|
+
configurable: true,
|
|
293
|
+
get() {
|
|
294
|
+
const t = nativeGet.call(this);
|
|
295
|
+
const ctor = this.constructor;
|
|
296
|
+
if (ctor && ctor !== TS.SyntaxNode && ctor.prototype instanceof TS.SyntaxNode) {
|
|
297
|
+
Object.defineProperty(ctor.prototype, 'type', {
|
|
298
|
+
value: t, configurable: true
|
|
299
|
+
});
|
|
300
|
+
}
|
|
301
|
+
return t;
|
|
302
|
+
}
|
|
303
|
+
});
|
|
304
|
+
} catch { /* stock getter keeps working */ }
|
|
305
|
+
}
|
|
306
|
+
|
|
251
307
|
/**
|
|
252
308
|
* Get or create parser for a language
|
|
253
309
|
* @param {string} language - Language name
|
package/languages/java.js
CHANGED
|
@@ -12,6 +12,7 @@ const {
|
|
|
12
12
|
parseStructuredParams,
|
|
13
13
|
extractJavaDocstring,
|
|
14
14
|
visitNameNodes,
|
|
15
|
+
sameNode,
|
|
15
16
|
} = require('./utils');
|
|
16
17
|
const { PARSE_OPTIONS, safeParse } = require('./index');
|
|
17
18
|
|
|
@@ -23,10 +24,13 @@ function parseTree(parser, code) {
|
|
|
23
24
|
* Extract Java parameters
|
|
24
25
|
*/
|
|
25
26
|
function extractJavaParams(paramsNode) {
|
|
27
|
+
// Distinguish "we have no node" (genuinely unknown) from "node is empty".
|
|
28
|
+
// Returning '...' for empty parens conflated zero-param methods with
|
|
29
|
+
// unknown signatures in JSON output (fix #241; go/rust got this in #238,
|
|
30
|
+
// the shared utils.extractParams already had it).
|
|
26
31
|
if (!paramsNode) return '...';
|
|
27
32
|
const text = paramsNode.text;
|
|
28
33
|
let params = text.replace(/^\(|\)$/g, '').trim();
|
|
29
|
-
if (!params) return '...';
|
|
30
34
|
return params;
|
|
31
35
|
}
|
|
32
36
|
|
|
@@ -585,6 +589,9 @@ function extractEnumConstants(enumNode, codeOrLines) {
|
|
|
585
589
|
startLine,
|
|
586
590
|
endLine,
|
|
587
591
|
memberType: 'constant',
|
|
592
|
+
// JLS: enum constants are implicitly public static final
|
|
593
|
+
// (fix #251 — api omitted them for lack of a modifier).
|
|
594
|
+
modifiers: ['public', 'static', 'final'],
|
|
588
595
|
...(argsNode && { params: argsNode.text.slice(1, -1) })
|
|
589
596
|
});
|
|
590
597
|
}
|
|
@@ -608,6 +615,7 @@ function extractEnumConstants(enumNode, codeOrLines) {
|
|
|
608
615
|
constants.push({
|
|
609
616
|
name: nameNode.text,
|
|
610
617
|
params: extractJavaParams(paramsNode),
|
|
618
|
+
paramsStructured: parseStructuredParams(paramsNode, 'java'),
|
|
611
619
|
startLine,
|
|
612
620
|
endLine,
|
|
613
621
|
memberType: modifiers.includes('static') ? 'static' : 'method',
|
|
@@ -625,6 +633,10 @@ function extractEnumConstants(enumNode, codeOrLines) {
|
|
|
625
633
|
constants.push({
|
|
626
634
|
name: nameNode.text,
|
|
627
635
|
params: extractJavaParams(paramsNode),
|
|
636
|
+
// paramsStructured drives verify's arg-check
|
|
637
|
+
// (fix #230 — enum constant args LOW(1) were
|
|
638
|
+
// checked against an empty list).
|
|
639
|
+
paramsStructured: parseStructuredParams(paramsNode, 'java'),
|
|
628
640
|
startLine,
|
|
629
641
|
endLine,
|
|
630
642
|
memberType: 'constructor',
|
|
@@ -681,6 +693,7 @@ function extractClassMembers(classNode, codeOrLines) {
|
|
|
681
693
|
memberType = 'abstract';
|
|
682
694
|
}
|
|
683
695
|
|
|
696
|
+
const memberGenerics = extractGenerics(child);
|
|
684
697
|
members.push({
|
|
685
698
|
name: nameNode.text,
|
|
686
699
|
params: extractJavaParams(paramsNode),
|
|
@@ -693,7 +706,10 @@ function extractClassMembers(classNode, codeOrLines) {
|
|
|
693
706
|
...(returnType && { returnType }),
|
|
694
707
|
...(docstring && { docstring }),
|
|
695
708
|
...(annotationsWithArgs.length > 0 && { annotationsWithArgs }),
|
|
696
|
-
...(nameLine !== startLine && { nameLine })
|
|
709
|
+
...(nameLine !== startLine && { nameLine }),
|
|
710
|
+
// Method-level type params (fix #229): generic-param receiver
|
|
711
|
+
// types inside the method resolve against this declaration.
|
|
712
|
+
...(memberGenerics && { generics: memberGenerics })
|
|
697
713
|
});
|
|
698
714
|
}
|
|
699
715
|
}
|
|
@@ -711,6 +727,11 @@ function extractClassMembers(classNode, codeOrLines) {
|
|
|
711
727
|
if (child.type === 'field_declaration') {
|
|
712
728
|
const typeNode = child.childForFieldName('type');
|
|
713
729
|
const fieldTypeText = typeNode ? typeNode.text : null;
|
|
730
|
+
// Visibility travels with the member (fix #251 — public
|
|
731
|
+
// instance fields were invisible to api/fileExports because
|
|
732
|
+
// the member had no modifiers for the #240 discipline to read;
|
|
733
|
+
// the #241 Rust-field twin).
|
|
734
|
+
const fieldModifiers = extractModifiers(child);
|
|
714
735
|
for (let j = 0; j < child.namedChildCount; j++) {
|
|
715
736
|
const decl = child.namedChild(j);
|
|
716
737
|
if (decl.type === 'variable_declarator') {
|
|
@@ -722,6 +743,7 @@ function extractClassMembers(classNode, codeOrLines) {
|
|
|
722
743
|
startLine,
|
|
723
744
|
endLine,
|
|
724
745
|
memberType: 'field',
|
|
746
|
+
...(fieldModifiers.length > 0 && { modifiers: fieldModifiers }),
|
|
725
747
|
fieldType: fieldTypeText
|
|
726
748
|
});
|
|
727
749
|
}
|
|
@@ -1144,6 +1166,80 @@ function findCallsInCode(code, parser) {
|
|
|
1144
1166
|
}
|
|
1145
1167
|
|
|
1146
1168
|
// Handle constructor calls: new Foo(), new pkg.Bar()
|
|
1169
|
+
// super(x) / this(x) — constructor delegation (fix #238: these
|
|
1170
|
+
// sites were invisible to every command). Resolve to the parent
|
|
1171
|
+
// class's constructor (super) or a same-class overload (this);
|
|
1172
|
+
// Java constructors are indexed under the CLASS name, so the
|
|
1173
|
+
// record carries the target class as its name.
|
|
1174
|
+
if (node.type === 'explicit_constructor_invocation') {
|
|
1175
|
+
let cls = node.parent;
|
|
1176
|
+
while (cls && cls.type !== 'class_declaration' && cls.type !== 'enum_declaration') {
|
|
1177
|
+
cls = cls.parent;
|
|
1178
|
+
}
|
|
1179
|
+
const isSuperCall = node.children.some(c => c.type === 'super');
|
|
1180
|
+
let targetClass = null;
|
|
1181
|
+
if (cls) {
|
|
1182
|
+
if (isSuperCall) {
|
|
1183
|
+
const sup = cls.childForFieldName('superclass');
|
|
1184
|
+
targetClass = sup?.namedChild(0)?.text || null;
|
|
1185
|
+
} else {
|
|
1186
|
+
targetClass = cls.childForFieldName('name')?.text || null;
|
|
1187
|
+
}
|
|
1188
|
+
}
|
|
1189
|
+
if (targetClass) {
|
|
1190
|
+
const genericIdx = targetClass.indexOf('<');
|
|
1191
|
+
if (genericIdx > 0) targetClass = targetClass.substring(0, genericIdx);
|
|
1192
|
+
const dotIdx = targetClass.lastIndexOf('.');
|
|
1193
|
+
if (dotIdx > 0) targetClass = targetClass.substring(dotIdx + 1);
|
|
1194
|
+
const enclosingFunction = getCurrentEnclosingFunction();
|
|
1195
|
+
const ctorArgs = getCallArgs(node);
|
|
1196
|
+
calls.push({
|
|
1197
|
+
name: targetClass,
|
|
1198
|
+
line: node.startPosition.row + 1,
|
|
1199
|
+
isMethod: false,
|
|
1200
|
+
isConstructor: true,
|
|
1201
|
+
// 'this' delegation names the ENCLOSING class by
|
|
1202
|
+
// construction — an intra-class mechanism, never a
|
|
1203
|
+
// caller edge for the class (jdtls-measured, fix #238).
|
|
1204
|
+
ctorDelegation: isSuperCall ? 'super' : 'this',
|
|
1205
|
+
argCount: ctorArgs.argCount,
|
|
1206
|
+
...(ctorArgs.argKinds && { argKinds: ctorArgs.argKinds }),
|
|
1207
|
+
enclosingFunction
|
|
1208
|
+
});
|
|
1209
|
+
}
|
|
1210
|
+
return true;
|
|
1211
|
+
}
|
|
1212
|
+
|
|
1213
|
+
// Enum constants with arguments (RED(1)) invoke the enum's own
|
|
1214
|
+
// constructor (fix #238: the constructor had no call records, so
|
|
1215
|
+
// search --unused / deadcode flagged it dead in every enum).
|
|
1216
|
+
// Argument-less constants still construct — they call the implicit
|
|
1217
|
+
// or 0-arg constructor.
|
|
1218
|
+
if (node.type === 'enum_constant') {
|
|
1219
|
+
let enclosingEnum = node.parent;
|
|
1220
|
+
while (enclosingEnum && enclosingEnum.type !== 'enum_declaration') {
|
|
1221
|
+
enclosingEnum = enclosingEnum.parent;
|
|
1222
|
+
}
|
|
1223
|
+
const enumName = enclosingEnum?.childForFieldName('name')?.text;
|
|
1224
|
+
if (enumName) {
|
|
1225
|
+
const ctorArgs = getCallArgs(node);
|
|
1226
|
+
calls.push({
|
|
1227
|
+
name: enumName,
|
|
1228
|
+
line: node.startPosition.row + 1,
|
|
1229
|
+
isMethod: false,
|
|
1230
|
+
isConstructor: true,
|
|
1231
|
+
// Part of the enum's own declaration — keeps the
|
|
1232
|
+
// constructor alive for deadcode/--unused, but never a
|
|
1233
|
+
// caller edge for the enum (jdtls-measured, fix #238).
|
|
1234
|
+
enumConstant: true,
|
|
1235
|
+
argCount: ctorArgs.argCount,
|
|
1236
|
+
...(ctorArgs.argKinds && { argKinds: ctorArgs.argKinds }),
|
|
1237
|
+
enclosingFunction: getCurrentEnclosingFunction()
|
|
1238
|
+
});
|
|
1239
|
+
}
|
|
1240
|
+
return true;
|
|
1241
|
+
}
|
|
1242
|
+
|
|
1147
1243
|
if (node.type === 'object_creation_expression') {
|
|
1148
1244
|
const typeNode = node.childForFieldName('type');
|
|
1149
1245
|
if (typeNode) {
|
|
@@ -1230,6 +1326,26 @@ function findCallsInCode(code, parser) {
|
|
|
1230
1326
|
}
|
|
1231
1327
|
}
|
|
1232
1328
|
|
|
1329
|
+
// Try-with-resources declarations are declared-type locals too (fix
|
|
1330
|
+
// #231): `try (Res r = new Res())` types r exactly like `Res r = ...`
|
|
1331
|
+
// — the resource node carries type/name/value fields directly.
|
|
1332
|
+
if (node.type === 'resource' && functionStack.length > 0) {
|
|
1333
|
+
const resTypeNode = node.childForFieldName('type');
|
|
1334
|
+
const resNameNode = node.childForFieldName('name');
|
|
1335
|
+
const resValueNode = node.childForFieldName('value');
|
|
1336
|
+
let typeName = resValueNode?.type === 'object_creation_expression'
|
|
1337
|
+
? extractTypeName(resValueNode.childForFieldName('type'))
|
|
1338
|
+
: null;
|
|
1339
|
+
if (!typeName && resTypeNode && resTypeNode.text !== 'var') {
|
|
1340
|
+
typeName = extractTypeName(resTypeNode);
|
|
1341
|
+
}
|
|
1342
|
+
if (resNameNode && typeName) {
|
|
1343
|
+
const scopeKey = functionStack[functionStack.length - 1].startLine;
|
|
1344
|
+
const typeMap = scopeTypes.get(scopeKey);
|
|
1345
|
+
if (typeMap) typeMap.set(resNameNode.text, typeName);
|
|
1346
|
+
}
|
|
1347
|
+
}
|
|
1348
|
+
|
|
1233
1349
|
return true;
|
|
1234
1350
|
}, {
|
|
1235
1351
|
onLeave: (node) => {
|
|
@@ -1375,10 +1491,11 @@ function findExportsInCode(code, parser) {
|
|
|
1375
1491
|
* @param {string} code - Source code
|
|
1376
1492
|
* @param {string} name - Symbol name to find
|
|
1377
1493
|
* @param {object} parser - Tree-sitter parser instance
|
|
1494
|
+
* @param {object} [tree] - Pre-parsed tree (per-operation cache); parsed here when absent
|
|
1378
1495
|
* @returns {Array<{line: number, column: number, usageType: string}>}
|
|
1379
1496
|
*/
|
|
1380
|
-
function findUsagesInCode(code, name, parser) {
|
|
1381
|
-
|
|
1497
|
+
function findUsagesInCode(code, name, parser, tree) {
|
|
1498
|
+
tree = tree || parseTree(parser, code);
|
|
1382
1499
|
const usages = [];
|
|
1383
1500
|
|
|
1384
1501
|
visitNameNodes(tree, code, name, (node) => {
|
|
@@ -1410,7 +1527,7 @@ function findUsagesInCode(code, name, parser) {
|
|
|
1410
1527
|
}
|
|
1411
1528
|
// Call: method_invocation with name field
|
|
1412
1529
|
else if (parent.type === 'method_invocation' &&
|
|
1413
|
-
parent.childForFieldName('name')
|
|
1530
|
+
sameNode(parent.childForFieldName('name'), node)) {
|
|
1414
1531
|
usageType = 'call';
|
|
1415
1532
|
// Track receiver for method invocations (obj.name() → receiver = 'obj')
|
|
1416
1533
|
const object = parent.childForFieldName('object');
|
|
@@ -1421,50 +1538,50 @@ function findUsagesInCode(code, name, parser) {
|
|
|
1421
1538
|
}
|
|
1422
1539
|
// Definition: method name
|
|
1423
1540
|
else if (parent.type === 'method_declaration' &&
|
|
1424
|
-
parent.childForFieldName('name')
|
|
1541
|
+
sameNode(parent.childForFieldName('name'), node)) {
|
|
1425
1542
|
usageType = 'definition';
|
|
1426
1543
|
}
|
|
1427
1544
|
// Definition: class name
|
|
1428
1545
|
else if (parent.type === 'class_declaration' &&
|
|
1429
|
-
parent.childForFieldName('name')
|
|
1546
|
+
sameNode(parent.childForFieldName('name'), node)) {
|
|
1430
1547
|
usageType = 'definition';
|
|
1431
1548
|
}
|
|
1432
1549
|
// Definition: interface name
|
|
1433
1550
|
else if (parent.type === 'interface_declaration' &&
|
|
1434
|
-
parent.childForFieldName('name')
|
|
1551
|
+
sameNode(parent.childForFieldName('name'), node)) {
|
|
1435
1552
|
usageType = 'definition';
|
|
1436
1553
|
}
|
|
1437
1554
|
// Definition: enum name
|
|
1438
1555
|
else if (parent.type === 'enum_declaration' &&
|
|
1439
|
-
parent.childForFieldName('name')
|
|
1556
|
+
sameNode(parent.childForFieldName('name'), node)) {
|
|
1440
1557
|
usageType = 'definition';
|
|
1441
1558
|
}
|
|
1442
1559
|
// Definition: constructor
|
|
1443
1560
|
else if (parent.type === 'constructor_declaration' &&
|
|
1444
|
-
parent.childForFieldName('name')
|
|
1561
|
+
sameNode(parent.childForFieldName('name'), node)) {
|
|
1445
1562
|
usageType = 'definition';
|
|
1446
1563
|
}
|
|
1447
1564
|
// Definition: local variable
|
|
1448
1565
|
else if (parent.type === 'variable_declarator' &&
|
|
1449
|
-
parent.childForFieldName('name')
|
|
1566
|
+
sameNode(parent.childForFieldName('name'), node)) {
|
|
1450
1567
|
usageType = 'definition';
|
|
1451
1568
|
}
|
|
1452
1569
|
// Definition: parameter (name field only, not the type)
|
|
1453
1570
|
else if ((parent.type === 'formal_parameter' ||
|
|
1454
1571
|
parent.type === 'spread_parameter') &&
|
|
1455
|
-
parent.childForFieldName('name')
|
|
1572
|
+
sameNode(parent.childForFieldName('name'), node)) {
|
|
1456
1573
|
usageType = 'definition';
|
|
1457
1574
|
}
|
|
1458
1575
|
// Definition: field (declarator name only, not the type)
|
|
1459
1576
|
else if (parent.type === 'field_declaration' &&
|
|
1460
1577
|
node.type === 'identifier' &&
|
|
1461
|
-
parent.descendantsOfType('variable_declarator').some(d => d.childForFieldName('name')
|
|
1578
|
+
parent.descendantsOfType('variable_declarator').some(d => sameNode(d.childForFieldName('name'), node))) {
|
|
1462
1579
|
usageType = 'definition';
|
|
1463
1580
|
}
|
|
1464
1581
|
// Object creation: new ClassName()
|
|
1465
1582
|
else if (parent.type === 'object_creation_expression') {
|
|
1466
1583
|
const typeNode = parent.childForFieldName('type');
|
|
1467
|
-
if (typeNode
|
|
1584
|
+
if (sameNode(typeNode, node) || typeNode?.text === name) {
|
|
1468
1585
|
usageType = 'call';
|
|
1469
1586
|
}
|
|
1470
1587
|
}
|
|
@@ -1472,12 +1589,12 @@ function findUsagesInCode(code, name, parser) {
|
|
|
1472
1589
|
// (variable or ClassName), referenced, not called. The call belongs
|
|
1473
1590
|
// to the name field, handled above.
|
|
1474
1591
|
else if (parent.type === 'method_invocation' &&
|
|
1475
|
-
parent.childForFieldName('object')
|
|
1592
|
+
sameNode(parent.childForFieldName('object'), node)) {
|
|
1476
1593
|
usageType = 'reference';
|
|
1477
1594
|
}
|
|
1478
1595
|
// Field access: obj.field
|
|
1479
1596
|
else if (parent.type === 'field_access' &&
|
|
1480
|
-
parent.childForFieldName('field')
|
|
1597
|
+
sameNode(parent.childForFieldName('field'), node)) {
|
|
1481
1598
|
usageType = 'reference';
|
|
1482
1599
|
// Track receiver for field access (obj.field → receiver = 'obj')
|
|
1483
1600
|
const object = parent.childForFieldName('object');
|