ucn 4.1.0 → 4.1.2
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/README.md +10 -8
- package/core/build-worker.js +21 -7
- package/core/cache.js +16 -1
- package/core/callers.js +1210 -98
- package/core/deadcode.js +59 -8
- package/core/entrypoints.js +13 -2
- package/core/imports.js +98 -6
- package/core/project.js +45 -13
- package/languages/go.js +56 -4
- package/languages/java.js +5 -1
- package/languages/javascript.js +95 -9
- package/languages/python.js +23 -1
- package/languages/rust.js +42 -8
- package/package.json +2 -1
package/languages/javascript.js
CHANGED
|
@@ -528,6 +528,18 @@ function _processFunction(node, functions, processedRanges, lines) {
|
|
|
528
528
|
isArrow,
|
|
529
529
|
isGenerator: isGen,
|
|
530
530
|
modifiers: [],
|
|
531
|
+
// A property-assignment def (Reply.prototype.serialize
|
|
532
|
+
// = function, exports.h = () => ...) creates NO
|
|
533
|
+
// lexical name — a bare call in the file can never
|
|
534
|
+
// bind it (fix #269, fastify-measured: the prototype
|
|
535
|
+
// def stole the module-scope binding from the free
|
|
536
|
+
// `function serialize(...)` below it). Prototype
|
|
537
|
+
// assignments carry their class so typed-receiver
|
|
538
|
+
// method resolution reaches them.
|
|
539
|
+
...(leftNode.type === 'member_expression' && { memberAssigned: true }),
|
|
540
|
+
...(leftNode.type === 'member_expression' &&
|
|
541
|
+
/^([A-Za-z_$][\w$]*)\.prototype\.[A-Za-z_$][\w$]*$/.test(leftNode.text) &&
|
|
542
|
+
{ className: leftNode.text.split('.')[0], isMethod: true }),
|
|
531
543
|
...typeAnno,
|
|
532
544
|
...(generics && { generics }),
|
|
533
545
|
...(docstring && { docstring })
|
|
@@ -752,7 +764,14 @@ function _processClass(node, classes, processedRanges, lines) {
|
|
|
752
764
|
// TypeScript namespace/module declarations
|
|
753
765
|
if (node.type === 'internal_module' || node.type === 'module') {
|
|
754
766
|
const nameNode = node.childForFieldName('name');
|
|
755
|
-
|
|
767
|
+
// A STRING-named module declaration (`declare module '../vanilla'`)
|
|
768
|
+
// is a module AUGMENTATION/shape declaration, not a nameable symbol
|
|
769
|
+
// (fix #267, zustand-measured): it declares no identifier project
|
|
770
|
+
// code can reference, so indexing it as a namespace made deadcode
|
|
771
|
+
// claim every augmentation block dead (5 FALSE-DEADs on zustand's
|
|
772
|
+
// StoreMutators augmentations). The compiler merges it into the
|
|
773
|
+
// TARGET module — never claimable, never importable by this "name".
|
|
774
|
+
if (nameNode && nameNode.type !== 'string') {
|
|
756
775
|
const { startLine, endLine } = nodeToLocation(node, lines);
|
|
757
776
|
const docstring = extractJSDocstring(lines, startLine);
|
|
758
777
|
|
|
@@ -1280,6 +1299,21 @@ const JS_LITERAL_RECEIVER_TYPES = {
|
|
|
1280
1299
|
number: 'Number',
|
|
1281
1300
|
};
|
|
1282
1301
|
|
|
1302
|
+
// Literal ASSIGNMENTS type the variable (fix #262, the #218d Python rule):
|
|
1303
|
+
// `const lines = []` → lines is Array, so lines.push() is Array.push. Object
|
|
1304
|
+
// literals are deliberately absent — `const obj = {}` is the mutable
|
|
1305
|
+
// property-bag / namespace idiom (obj.render = fn happens later), so typing
|
|
1306
|
+
// it 'Object' would falsely externalize its assigned methods. A DIRECT
|
|
1307
|
+
// literal receiver (`{}.hasOwnProperty()`) has no such future, hence the
|
|
1308
|
+
// separate map above.
|
|
1309
|
+
const JS_LITERAL_ASSIGN_TYPES = {
|
|
1310
|
+
array: 'Array',
|
|
1311
|
+
string: 'String',
|
|
1312
|
+
template_string: 'String',
|
|
1313
|
+
regex: 'RegExp',
|
|
1314
|
+
number: 'Number',
|
|
1315
|
+
};
|
|
1316
|
+
|
|
1283
1317
|
// Predefined TS types that pin a receiver; any/unknown/object say nothing.
|
|
1284
1318
|
const TS_PREDEFINED_RECEIVER_TYPES = new Set(['string', 'number', 'boolean', 'bigint', 'symbol']);
|
|
1285
1319
|
|
|
@@ -1367,6 +1401,12 @@ function findCallsInCode(code, parser) {
|
|
|
1367
1401
|
const aliases = new Map(); // Track local aliases: aliasName -> originalName (string or string[])
|
|
1368
1402
|
const nonCallableNames = new Set(); // Track names assigned non-callable values
|
|
1369
1403
|
const localVarTypes = new Map(); // Track local variable types: varName -> typeName (for receiverType inference)
|
|
1404
|
+
// Names whose type came from a DECLARED annotation (TS `x: Foo` / typed
|
|
1405
|
+
// params). The compiler enforces assignability for these, so reassignment
|
|
1406
|
+
// never stales them; inferred types (literal/new) DO stale and are
|
|
1407
|
+
// deleted on untyped reassignment (fix #262, #218d semantics).
|
|
1408
|
+
const declaredTypeVars = new Set();
|
|
1409
|
+
const declaredTypeVarsStack = [];
|
|
1370
1410
|
const moduleAliases = new Set(); // Names bound to MODULES (import * as ns / const pkg = require(...))
|
|
1371
1411
|
const localVarTypesStack = []; // Stack for function-scoped save/restore of localVarTypes
|
|
1372
1412
|
|
|
@@ -1642,6 +1682,7 @@ function findCallsInCode(code, parser) {
|
|
|
1642
1682
|
});
|
|
1643
1683
|
// Save localVarTypes so inner declarations don't leak to sibling functions
|
|
1644
1684
|
localVarTypesStack.push(new Map(localVarTypes));
|
|
1685
|
+
declaredTypeVarsStack.push(new Set(declaredTypeVars));
|
|
1645
1686
|
}
|
|
1646
1687
|
|
|
1647
1688
|
// Track local aliases: const myParse = parse, const { parse: csvParse } = ...
|
|
@@ -1706,7 +1747,13 @@ function findCallsInCode(code, parser) {
|
|
|
1706
1747
|
const typeName = tsTypeName(typeId);
|
|
1707
1748
|
if (typeName) {
|
|
1708
1749
|
localVarTypes.set(nameNode.text, typeName);
|
|
1750
|
+
declaredTypeVars.add(nameNode.text);
|
|
1709
1751
|
}
|
|
1752
|
+
} else if (initNode && JS_LITERAL_ASSIGN_TYPES[initNode.type]) {
|
|
1753
|
+
// Literal declaration types the variable (fix #262):
|
|
1754
|
+
// `const lines = []` → Array. Annotation, when present,
|
|
1755
|
+
// wins (the branch above).
|
|
1756
|
+
localVarTypes.set(nameNode.text, JS_LITERAL_ASSIGN_TYPES[initNode.type]);
|
|
1710
1757
|
}
|
|
1711
1758
|
}
|
|
1712
1759
|
}
|
|
@@ -1720,6 +1767,7 @@ function findCallsInCode(code, parser) {
|
|
|
1720
1767
|
const typeName = tsTypeName(inner);
|
|
1721
1768
|
if (typeName) {
|
|
1722
1769
|
localVarTypes.set(pat.text, typeName);
|
|
1770
|
+
declaredTypeVars.add(pat.text);
|
|
1723
1771
|
}
|
|
1724
1772
|
}
|
|
1725
1773
|
}
|
|
@@ -1728,11 +1776,26 @@ function findCallsInCode(code, parser) {
|
|
|
1728
1776
|
if (node.type === 'assignment_expression') {
|
|
1729
1777
|
const left = node.childForFieldName('left');
|
|
1730
1778
|
const right = node.childForFieldName('right');
|
|
1731
|
-
if (left?.type === 'identifier'
|
|
1732
|
-
|
|
1733
|
-
|
|
1734
|
-
|
|
1735
|
-
|
|
1779
|
+
if (left?.type === 'identifier') {
|
|
1780
|
+
if (right?.type === 'new_expression') {
|
|
1781
|
+
nonCallableNames.add(left.text);
|
|
1782
|
+
const ctorName = jsConstructorTypeName(right.childForFieldName('constructor'));
|
|
1783
|
+
if (ctorName) {
|
|
1784
|
+
localVarTypes.set(left.text, ctorName);
|
|
1785
|
+
} else if (!declaredTypeVars.has(left.text)) {
|
|
1786
|
+
localVarTypes.delete(left.text);
|
|
1787
|
+
}
|
|
1788
|
+
} else if (right && JS_LITERAL_ASSIGN_TYPES[right.type]) {
|
|
1789
|
+
// Literal reassignment re-types the variable (fix #262)
|
|
1790
|
+
if (!declaredTypeVars.has(left.text)) {
|
|
1791
|
+
localVarTypes.set(left.text, JS_LITERAL_ASSIGN_TYPES[right.type]);
|
|
1792
|
+
}
|
|
1793
|
+
} else if (localVarTypes.has(left.text) && !declaredTypeVars.has(left.text)) {
|
|
1794
|
+
// Rebinding without a known type makes any previously
|
|
1795
|
+
// INFERRED type stale — nearest-preceding-assignment
|
|
1796
|
+
// semantics (#218d). Annotation-declared types survive:
|
|
1797
|
+
// the TS compiler enforces assignability for those.
|
|
1798
|
+
localVarTypes.delete(left.text);
|
|
1736
1799
|
}
|
|
1737
1800
|
}
|
|
1738
1801
|
// Handler-registration references (fix #252, the #221 family's
|
|
@@ -1878,7 +1941,7 @@ function findCallsInCode(code, parser) {
|
|
|
1878
1941
|
// parseAsync(args).catch(...) — record the producer so
|
|
1879
1942
|
// findCallers can type the receiver from its declared
|
|
1880
1943
|
// return annotation (Promise<...> → Promise).
|
|
1881
|
-
let receiverCall, receiverCallIsMethod, receiverCallAwaited;
|
|
1944
|
+
let receiverCall, receiverCallIsMethod, receiverCallAwaited, receiverCallLine;
|
|
1882
1945
|
{
|
|
1883
1946
|
let recvNode = objNode;
|
|
1884
1947
|
if (recvNode && recvNode.type === 'parenthesized_expression') {
|
|
@@ -1892,11 +1955,17 @@ function findCallsInCode(code, parser) {
|
|
|
1892
1955
|
const prodFunc = recvNode.childForFieldName('function');
|
|
1893
1956
|
if (prodFunc?.type === 'identifier') {
|
|
1894
1957
|
receiverCall = prodFunc.text;
|
|
1958
|
+
// Producer link (fix #258): plain-call
|
|
1959
|
+
// records carry the call node's start line
|
|
1960
|
+
receiverCallLine = recvNode.startPosition.row + 1;
|
|
1895
1961
|
} else if (prodFunc?.type === 'member_expression') {
|
|
1896
1962
|
const prodProp = prodFunc.childForFieldName('property');
|
|
1897
1963
|
if (prodProp) {
|
|
1898
1964
|
receiverCall = prodProp.text;
|
|
1899
1965
|
receiverCallIsMethod = true;
|
|
1966
|
+
// Method records report the property
|
|
1967
|
+
// node's own line
|
|
1968
|
+
receiverCallLine = prodProp.startPosition.row + 1;
|
|
1900
1969
|
}
|
|
1901
1970
|
}
|
|
1902
1971
|
}
|
|
@@ -1930,6 +1999,7 @@ function findCallsInCode(code, parser) {
|
|
|
1930
1999
|
...(receiverCall && { receiverCall }),
|
|
1931
2000
|
...(receiverCallIsMethod && { receiverCallIsMethod: true }),
|
|
1932
2001
|
...(receiverCallAwaited && { receiverCallAwaited: true }),
|
|
2002
|
+
...(receiverCallLine && { receiverCallLine }),
|
|
1933
2003
|
...(assignedTo && { assignedTo }),
|
|
1934
2004
|
enclosingFunction,
|
|
1935
2005
|
uncertain,
|
|
@@ -2165,6 +2235,11 @@ function findCallsInCode(code, parser) {
|
|
|
2165
2235
|
localVarTypes.clear();
|
|
2166
2236
|
for (const [k, v] of saved) localVarTypes.set(k, v);
|
|
2167
2237
|
}
|
|
2238
|
+
const savedDeclared = declaredTypeVarsStack.pop();
|
|
2239
|
+
if (savedDeclared) {
|
|
2240
|
+
declaredTypeVars.clear();
|
|
2241
|
+
for (const k of savedDeclared) declaredTypeVars.add(k);
|
|
2242
|
+
}
|
|
2168
2243
|
}
|
|
2169
2244
|
}
|
|
2170
2245
|
});
|
|
@@ -2340,6 +2415,7 @@ function findImportsInCode(code, parser) {
|
|
|
2340
2415
|
const line = node.startPosition.row + 1;
|
|
2341
2416
|
let modulePath = null;
|
|
2342
2417
|
const names = [];
|
|
2418
|
+
const esmRenames = [];
|
|
2343
2419
|
let importType = 'named';
|
|
2344
2420
|
|
|
2345
2421
|
// Find the module path (string node)
|
|
@@ -2385,6 +2461,7 @@ function findImportsInCode(code, parser) {
|
|
|
2385
2461
|
if (nameNode && aliasNode && aliasNode.text !== nameNode.text) {
|
|
2386
2462
|
if (!importAliases) importAliases = [];
|
|
2387
2463
|
importAliases.push({ original: nameNode.text, local: aliasNode.text });
|
|
2464
|
+
esmRenames.push({ original: nameNode.text, local: aliasNode.text });
|
|
2388
2465
|
}
|
|
2389
2466
|
}
|
|
2390
2467
|
}
|
|
@@ -2405,7 +2482,8 @@ function findImportsInCode(code, parser) {
|
|
|
2405
2482
|
// Side-effect import: import 'x'
|
|
2406
2483
|
importType = 'side-effect';
|
|
2407
2484
|
}
|
|
2408
|
-
imports.push({ module: modulePath, names, type: importType, line
|
|
2485
|
+
imports.push({ module: modulePath, names, type: importType, line,
|
|
2486
|
+
...(esmRenames.length > 0 && { renames: esmRenames }) });
|
|
2409
2487
|
}
|
|
2410
2488
|
return true;
|
|
2411
2489
|
}
|
|
@@ -2451,6 +2529,7 @@ function findImportsInCode(code, parser) {
|
|
|
2451
2529
|
const firstArg = argsNode.namedChild(0);
|
|
2452
2530
|
const line = node.startPosition.row + 1;
|
|
2453
2531
|
const names = [];
|
|
2532
|
+
const renames = [];
|
|
2454
2533
|
let modulePath;
|
|
2455
2534
|
let dynamic = false;
|
|
2456
2535
|
|
|
@@ -2482,6 +2561,7 @@ function findImportsInCode(code, parser) {
|
|
|
2482
2561
|
if (key && val && val.text !== key.text) {
|
|
2483
2562
|
if (!importAliases) importAliases = [];
|
|
2484
2563
|
importAliases.push({ original: key.text, local: val.text });
|
|
2564
|
+
renames.push({ original: key.text, local: val.text });
|
|
2485
2565
|
}
|
|
2486
2566
|
}
|
|
2487
2567
|
}
|
|
@@ -2490,7 +2570,13 @@ function findImportsInCode(code, parser) {
|
|
|
2490
2570
|
}
|
|
2491
2571
|
|
|
2492
2572
|
if (modulePath) {
|
|
2493
|
-
imports.push({ module: modulePath, names, type: 'require', line, dynamic
|
|
2573
|
+
imports.push({ module: modulePath, names, type: 'require', line, dynamic,
|
|
2574
|
+
// Per-import rename pairing (fix #269): the flat
|
|
2575
|
+
// importAliases list loses WHICH module a renamed
|
|
2576
|
+
// name came from — `{ validate: validateSchema }`
|
|
2577
|
+
// must pin to its own require, not any module
|
|
2578
|
+
// exporting the source name.
|
|
2579
|
+
...(renames.length > 0 && { renames }) });
|
|
2494
2580
|
}
|
|
2495
2581
|
}
|
|
2496
2582
|
}
|
package/languages/python.js
CHANGED
|
@@ -138,6 +138,7 @@ function _processFunction(node, functions, processedRanges, lines, code) {
|
|
|
138
138
|
...(paramTypes && { paramTypes }),
|
|
139
139
|
...(docstring && { docstring }),
|
|
140
140
|
...(decorators.length > 0 && { decorators }),
|
|
141
|
+
...(isOverloadDecorated(decorators) && { isSignature: true }),
|
|
141
142
|
...(nameLine !== decoratorStartLine && { nameLine })
|
|
142
143
|
});
|
|
143
144
|
}
|
|
@@ -285,6 +286,19 @@ function findFunctions(code, parser) {
|
|
|
285
286
|
return functions;
|
|
286
287
|
}
|
|
287
288
|
|
|
289
|
+
/**
|
|
290
|
+
* A typing @overload-decorated def is a SIGNATURE of the implementation that
|
|
291
|
+
* follows, not a callable of its own (fix #265 — TS overload parity): the
|
|
292
|
+
* runtime discards the stub bodies, so pin identity must close over the
|
|
293
|
+
* group and resolution must prefer the implementation.
|
|
294
|
+
*/
|
|
295
|
+
function isOverloadDecorated(decorators) {
|
|
296
|
+
return decorators.some(d => {
|
|
297
|
+
const head = d.split('(')[0].trim();
|
|
298
|
+
return head === 'overload' || head.endsWith('.overload');
|
|
299
|
+
});
|
|
300
|
+
}
|
|
301
|
+
|
|
288
302
|
/**
|
|
289
303
|
* Extract decorators from a function/class node
|
|
290
304
|
*/
|
|
@@ -472,6 +486,7 @@ function extractClassMembers(classNode, code) {
|
|
|
472
486
|
...(paramTypes && { paramTypes }),
|
|
473
487
|
...(docstring && { docstring }),
|
|
474
488
|
...(memberDecorators.length > 0 && { decorators: memberDecorators }),
|
|
489
|
+
...(isOverloadDecorated(memberDecorators) && { isSignature: true }),
|
|
475
490
|
...(nameLine !== startLine && { nameLine })
|
|
476
491
|
});
|
|
477
492
|
}
|
|
@@ -1073,7 +1088,7 @@ function findCallsInCode(code, parser) {
|
|
|
1073
1088
|
// annotation. `(await f()).m()` unwraps to the call and
|
|
1074
1089
|
// marks awaited (an un-awaited async producer's value is a
|
|
1075
1090
|
// coroutine, not the annotation's type).
|
|
1076
|
-
let receiverCall, receiverCallIsMethod, receiverCallAwaited;
|
|
1091
|
+
let receiverCall, receiverCallIsMethod, receiverCallAwaited, receiverCallLine;
|
|
1077
1092
|
|
|
1078
1093
|
// Detect super().method() pattern
|
|
1079
1094
|
if (objNode?.type === 'call') {
|
|
@@ -1093,11 +1108,17 @@ function findCallsInCode(code, parser) {
|
|
|
1093
1108
|
const prodFunc = recvNode.childForFieldName('function');
|
|
1094
1109
|
if (prodFunc?.type === 'identifier') {
|
|
1095
1110
|
receiverCall = prodFunc.text;
|
|
1111
|
+
// Producer link (fix #258): plain-call records
|
|
1112
|
+
// carry the call node's start line
|
|
1113
|
+
receiverCallLine = recvNode.startPosition.row + 1;
|
|
1096
1114
|
} else if (prodFunc?.type === 'attribute') {
|
|
1097
1115
|
const prodAttr = prodFunc.childForFieldName('attribute');
|
|
1098
1116
|
if (prodAttr) {
|
|
1099
1117
|
receiverCall = prodAttr.text;
|
|
1100
1118
|
receiverCallIsMethod = true;
|
|
1119
|
+
// Method records report the attribute
|
|
1120
|
+
// node's own line
|
|
1121
|
+
receiverCallLine = prodAttr.startPosition.row + 1;
|
|
1101
1122
|
}
|
|
1102
1123
|
}
|
|
1103
1124
|
}
|
|
@@ -1140,6 +1161,7 @@ function findCallsInCode(code, parser) {
|
|
|
1140
1161
|
...(receiverCall && { receiverCall }),
|
|
1141
1162
|
...(receiverCallIsMethod && { receiverCallIsMethod: true }),
|
|
1142
1163
|
...(receiverCallAwaited && { receiverCallAwaited: true }),
|
|
1164
|
+
...(receiverCallLine && { receiverCallLine }),
|
|
1143
1165
|
...(assignedTo && { assignedTo }),
|
|
1144
1166
|
argCount,
|
|
1145
1167
|
...(argSpread && { argSpread: true }),
|
package/languages/rust.js
CHANGED
|
@@ -1311,13 +1311,18 @@ function findCallsInCode(code, parser) {
|
|
|
1311
1311
|
// chain to its root: if the chain originates at Router::new() or
|
|
1312
1312
|
// any Router-typed call, set a synthetic receiver string so the
|
|
1313
1313
|
// bridge layer can recognize this as a Router method invocation.
|
|
1314
|
+
let receiverIsChainRoot;
|
|
1314
1315
|
if (!receiver && valueNode?.type === 'call_expression') {
|
|
1315
1316
|
const rootType = _findRustChainRootType(valueNode);
|
|
1316
1317
|
if (rootType) {
|
|
1317
1318
|
// Synthetic marker — ROUTER_CHAIN:<RootTypeName>. The
|
|
1318
1319
|
// <RootTypeName> portion lets the bridge match
|
|
1319
|
-
// /^router/i case-insensitively.
|
|
1320
|
+
// /^router/i case-insensitively. receiverIsChainRoot
|
|
1321
|
+
// tells caller physics this is NOT an identifier in
|
|
1322
|
+
// the code (fix #258) — the chain fold types it from
|
|
1323
|
+
// the producer link instead.
|
|
1320
1324
|
receiver = rootType;
|
|
1325
|
+
receiverIsChainRoot = true;
|
|
1321
1326
|
}
|
|
1322
1327
|
}
|
|
1323
1328
|
// fix #202: one-hop declared-field receivers — self.dent.path(),
|
|
@@ -1354,16 +1359,43 @@ function findCallsInCode(code, parser) {
|
|
|
1354
1359
|
// Chained receiver (fix #220): the receiver IS a call —
|
|
1355
1360
|
// self.as_u8().as_color() — record the producer so
|
|
1356
1361
|
// findCallers can type it from the declared return.
|
|
1357
|
-
//
|
|
1358
|
-
//
|
|
1359
|
-
|
|
1360
|
-
|
|
1361
|
-
|
|
1362
|
+
// Fix #258 (clap-measured): receiverCallLine links to the
|
|
1363
|
+
// producer's OWN record (per-record line convention:
|
|
1364
|
+
// field identifier for method producers, call-node start
|
|
1365
|
+
// for plain/path producers) so the chain fold can walk
|
|
1366
|
+
// Command::new("x").a(...).b(...) hop by hop; path
|
|
1367
|
+
// producers (Config::load().x()) are captured now, and
|
|
1368
|
+
// rooted chains keep their synthetic receiver marker for
|
|
1369
|
+
// the bridge but get the link too.
|
|
1370
|
+
let receiverCall, receiverCallIsMethod, receiverCallLine;
|
|
1371
|
+
if ((!receiver || receiverIsChainRoot) && !receiverField &&
|
|
1372
|
+
valueNode?.type === 'call_expression') {
|
|
1373
|
+
let prodFunc = valueNode.childForFieldName('function');
|
|
1374
|
+
if (prodFunc?.type === 'generic_function') {
|
|
1375
|
+
prodFunc = prodFunc.childForFieldName('function') || prodFunc;
|
|
1376
|
+
}
|
|
1362
1377
|
if (prodFunc?.type === 'identifier') {
|
|
1363
1378
|
receiverCall = prodFunc.text;
|
|
1379
|
+
receiverCallLine = valueNode.startPosition.row + 1;
|
|
1364
1380
|
} else if (prodFunc?.type === 'field_expression') {
|
|
1365
1381
|
const pf = prodFunc.childForFieldName('field');
|
|
1366
|
-
if (pf) {
|
|
1382
|
+
if (pf) {
|
|
1383
|
+
receiverCall = pf.text;
|
|
1384
|
+
receiverCallIsMethod = true;
|
|
1385
|
+
receiverCallLine = pf.startPosition.row + 1;
|
|
1386
|
+
}
|
|
1387
|
+
} else if (prodFunc?.type === 'scoped_identifier') {
|
|
1388
|
+
// Path producer: Command::new(...).arg(...) — the
|
|
1389
|
+
// producer record's name is the last path segment
|
|
1390
|
+
// (turbofish segments dropped, matching the path
|
|
1391
|
+
// record's own derivation) at the call node's line.
|
|
1392
|
+
const segs = prodFunc.text.split('::');
|
|
1393
|
+
const prodName = segs[segs.length - 1];
|
|
1394
|
+
if (prodName && !prodName.startsWith('<')) {
|
|
1395
|
+
receiverCall = prodName;
|
|
1396
|
+
receiverCallIsMethod = true;
|
|
1397
|
+
receiverCallLine = valueNode.startPosition.row + 1;
|
|
1398
|
+
}
|
|
1367
1399
|
}
|
|
1368
1400
|
}
|
|
1369
1401
|
// Literal receivers carry their builtin type (fix #220,
|
|
@@ -1374,7 +1406,7 @@ function findCallsInCode(code, parser) {
|
|
|
1374
1406
|
? ({ string_literal: 'str', raw_string_literal: 'str',
|
|
1375
1407
|
char_literal: 'char', boolean_literal: 'bool' })[valueNode.type]
|
|
1376
1408
|
: undefined;
|
|
1377
|
-
const receiverType = (receiver && receiver !== 'self')
|
|
1409
|
+
const receiverType = (receiver && receiver !== 'self' && !receiverIsChainRoot)
|
|
1378
1410
|
? getReceiverType(receiver)
|
|
1379
1411
|
: literalReceiverType;
|
|
1380
1412
|
const firstArg = getFirstStringArg(node);
|
|
@@ -1389,10 +1421,12 @@ function findCallsInCode(code, parser) {
|
|
|
1389
1421
|
isMethod: true,
|
|
1390
1422
|
receiver,
|
|
1391
1423
|
...(receiverType && { receiverType }),
|
|
1424
|
+
...(receiverIsChainRoot && { receiverIsChainRoot: true }),
|
|
1392
1425
|
...(receiverField && { receiverRoot, receiverField }),
|
|
1393
1426
|
...(receiverField && receiverRootType && { receiverRootType }),
|
|
1394
1427
|
...(receiverCall && { receiverCall }),
|
|
1395
1428
|
...(receiverCallIsMethod && { receiverCallIsMethod: true }),
|
|
1429
|
+
...(receiverCallLine && { receiverCallLine }),
|
|
1396
1430
|
argCount,
|
|
1397
1431
|
...(assigned && { assignedTo: assigned.assignedTo }),
|
|
1398
1432
|
...(assigned?.unwrapped && { assignedUnwrap: true }),
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "ucn",
|
|
3
|
-
"version": "4.1.
|
|
3
|
+
"version": "4.1.2",
|
|
4
4
|
"mcpName": "io.github.mleoca/ucn",
|
|
5
5
|
"description": "Code intelligence toolkit for AI agents — extract functions, trace call chains, find callers, detect dead code without reading entire files. Works as MCP server, CLI, or agent skill. Supports JS/TS, Python, Go, Rust, Java.",
|
|
6
6
|
"main": "index.js",
|
|
@@ -9,6 +9,7 @@
|
|
|
9
9
|
"ucn-mcp": "mcp/server.js"
|
|
10
10
|
},
|
|
11
11
|
"scripts": {
|
|
12
|
+
"version": "node scripts/sync-server-version.js && git add server.json",
|
|
12
13
|
"test": "node --test test/parser-unit.test.js test/integration.test.js test/cache.test.js test/formatter.test.js test/interactive.test.js test/feature.test.js test/regression-js.test.js test/regression-py.test.js test/regression-go.test.js test/regression-java.test.js test/regression-rust.test.js test/regression-cross.test.js test/regression-mcp.test.js test/regression-parser.test.js test/regression-commands.test.js test/regression-fixes.test.js test/regression-bugfixes.test.js test/cross-language.test.js test/accuracy.test.js test/command-coverage.test.js test/perf-optimizations.test.js test/systematic-test.js test/mcp-edge-cases.js test/conservation.test.js test/parity-test.js",
|
|
13
14
|
"benchmark:agent": "node test/agent-understanding-benchmark.js",
|
|
14
15
|
"eval:conservation": "node eval/conservation-real.js",
|