ucn 4.1.1 → 4.1.3

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/languages/go.js CHANGED
@@ -678,6 +678,8 @@ function findCallsInCode(code, parser, options = {}) {
678
678
  const closureScopes = new Map();
679
679
  // Track variable -> type mappings per function scope (scopeStartLine -> Map<varName, typeName>)
680
680
  const scopeTypes = new Map();
681
+ // Names whose scope type is a New*-prefix GUESS (fix #266) — per scope
682
+ const scopeGuesses = new Map();
681
683
  // Track function-typed parameter names per scope (scopeStartLine -> Set<name>)
682
684
  const funcParamScopes = new Map();
683
685
 
@@ -822,6 +824,21 @@ function findCallsInCode(code, parser, options = {}) {
822
824
  }
823
825
  return undefined;
824
826
  };
827
+ // Is the FIRST scope-chain hit for this variable a New*-prefix GUESS
828
+ // (fix #266, viper-measured)? `registry := NewCodecRegistry()` types
829
+ // registry as 'CodecRegistry' by NAME CONVENTION — the actual return
830
+ // annotation says *DefaultCodecRegistry. Guess-grade types help
831
+ // resolution but must never be exclusion evidence; findCallers lets
832
+ // the compiler-checked return-type flow map override them.
833
+ const isGuessedType = (varName) => {
834
+ for (let i = functionStack.length - 1; i >= 0; i--) {
835
+ const typeMap = scopeTypes.get(functionStack[i].startLine);
836
+ if (typeMap?.has(varName)) {
837
+ return !!scopeGuesses.get(functionStack[i].startLine)?.has(varName);
838
+ }
839
+ }
840
+ return false;
841
+ };
825
842
 
826
843
  // fix #203 (Go): is a bare-identifier function REFERENCE shadowed by an
827
844
  // enclosing func-literal/function parameter, method receiver, range/init
@@ -957,6 +974,7 @@ function findCallsInCode(code, parser, options = {}) {
957
974
  for (let vi = 0; vi < Math.min(names.length, values.length); vi++) {
958
975
  const val = values[vi];
959
976
  let typeName = null;
977
+ let typeGuessed = false;
960
978
  // &Type{...} or Type{...}
961
979
  if (val.type === 'composite_literal') {
962
980
  typeName = extractTypeName(val.childForFieldName('type'));
@@ -978,8 +996,14 @@ function findCallsInCode(code, parser, options = {}) {
978
996
  ? callFuncNode.childForFieldName('field')?.text
979
997
  : null;
980
998
  if (callName && /^New[A-Z]/.test(callName)) {
999
+ // NAME CONVENTION, not compiler truth (fix
1000
+ // #266): NewCodecRegistry() returns
1001
+ // *DefaultCodecRegistry. Marked a guess so
1002
+ // the return-type flow map can override it
1003
+ // and exclusions never trust it.
981
1004
  typeName = callName.slice(3);
982
1005
  if (!typeName || !/^[A-Z]/.test(typeName)) typeName = null;
1006
+ if (typeName) typeGuessed = true;
983
1007
  } else if (callFuncNode.type === 'identifier' &&
984
1008
  callFuncNode.text === 'new') {
985
1009
  // buf := new(bytes.Buffer) — the builtin
@@ -1000,7 +1024,16 @@ function findCallsInCode(code, parser, options = {}) {
1000
1024
  }
1001
1025
  }
1002
1026
  }
1003
- if (typeName) typeMap.set(names[vi], typeName);
1027
+ if (typeName) {
1028
+ typeMap.set(names[vi], typeName);
1029
+ let gset = scopeGuesses.get(scopeKey);
1030
+ if (typeGuessed) {
1031
+ if (!gset) { gset = new Set(); scopeGuesses.set(scopeKey, gset); }
1032
+ gset.add(names[vi]);
1033
+ } else if (gset) {
1034
+ gset.delete(names[vi]); // compiler-true retype clears the guess
1035
+ }
1036
+ }
1004
1037
  }
1005
1038
  }
1006
1039
  }
@@ -1021,7 +1054,10 @@ function findCallsInCode(code, parser, options = {}) {
1021
1054
  if (!typeName) return;
1022
1055
  for (let j = 0; j < spec.namedChildCount; j++) {
1023
1056
  const id = spec.namedChild(j);
1024
- if (id.type === 'identifier') varTypeMap.set(id.text, typeName);
1057
+ if (id.type === 'identifier') {
1058
+ varTypeMap.set(id.text, typeName);
1059
+ scopeGuesses.get(scopeKey)?.delete(id.text); // declared type clears any guess
1060
+ }
1025
1061
  }
1026
1062
  };
1027
1063
  for (let i = 0; i < node.namedChildCount; i++) {
@@ -1150,7 +1186,7 @@ function findCallsInCode(code, parser, options = {}) {
1150
1186
  // fix #202: one-hop declared-field receivers — h.inner.Run().
1151
1187
  // receiverRoot/Field/RootType let findCallers hop to the
1152
1188
  // field's declared struct-field type cross-file.
1153
- let receiverRoot, receiverFieldName, receiverRootType;
1189
+ let receiverRoot, receiverFieldName, receiverRootType, receiverRootTypeGuessed;
1154
1190
  if (!receiver && operandNode?.type === 'selector_expression') {
1155
1191
  const rootNode = operandNode.childForFieldName('operand');
1156
1192
  const fldNode = operandNode.childForFieldName('field');
@@ -1159,6 +1195,9 @@ function findCallsInCode(code, parser, options = {}) {
1159
1195
  receiverRoot = rootNode.text;
1160
1196
  receiverFieldName = fldNode.text;
1161
1197
  receiverRootType = getReceiverType(rootNode.text);
1198
+ if (receiverRootType && isGuessedType(rootNode.text)) {
1199
+ receiverRootTypeGuessed = true;
1200
+ }
1162
1201
  }
1163
1202
  }
1164
1203
  // Chained receiver (fix #220, cobra-measured): the receiver
@@ -1167,16 +1206,22 @@ function findCallsInCode(code, parser, options = {}) {
1167
1206
  // declared return (*pflag.FlagSet → external → routed).
1168
1207
  // Package-qualified producers (os.CreateTemp().Name())
1169
1208
  // carry the qualifier for strict import-package resolution.
1170
- let receiverCall, receiverCallIsMethod, receiverCallReceiver;
1209
+ let receiverCall, receiverCallIsMethod, receiverCallReceiver, receiverCallLine;
1171
1210
  if (!receiver && !receiverFieldName && operandNode?.type === 'call_expression') {
1172
1211
  const prodFunc = operandNode.childForFieldName('function');
1173
1212
  if (prodFunc?.type === 'identifier') {
1174
1213
  receiverCall = prodFunc.text;
1214
+ // Producer link (fix #258): plain-call records carry
1215
+ // the call node's start line
1216
+ receiverCallLine = operandNode.startPosition.row + 1;
1175
1217
  } else if (prodFunc?.type === 'selector_expression') {
1176
1218
  const pf = prodFunc.childForFieldName('field');
1177
1219
  const po = prodFunc.childForFieldName('operand');
1178
1220
  if (pf) {
1179
1221
  receiverCall = pf.text;
1222
+ // Selector records report the FIELD's own line
1223
+ // (fix #223 name-node convention)
1224
+ receiverCallLine = pf.startPosition.row + 1;
1180
1225
  if (po?.type === 'identifier' && importAliases.has(po.text)) {
1181
1226
  receiverCallReceiver = po.text;
1182
1227
  } else {
@@ -1198,11 +1243,14 @@ function findCallsInCode(code, parser, options = {}) {
1198
1243
  isMethod: !isPkgCall,
1199
1244
  receiver,
1200
1245
  ...(receiverType && { receiverType }),
1246
+ ...(receiverType && isGuessedType(receiver) && { receiverTypeGuessed: true }),
1201
1247
  ...(receiverFieldName && { receiverRoot, receiverField: receiverFieldName }),
1202
1248
  ...(receiverFieldName && receiverRootType && { receiverRootType }),
1249
+ ...(receiverFieldName && receiverRootType && receiverRootTypeGuessed && { receiverRootTypeGuessed: true }),
1203
1250
  ...(receiverCall && { receiverCall }),
1204
1251
  ...(receiverCallIsMethod && { receiverCallIsMethod: true }),
1205
1252
  ...(receiverCallReceiver && { receiverCallReceiver }),
1253
+ ...(receiverCallLine && { receiverCallLine }),
1206
1254
  argCount,
1207
1255
  ...(argSpread && { argSpread: true }),
1208
1256
  ...(assigned && { assignedTo: assigned.assignedTo }),
@@ -1282,6 +1330,7 @@ function findCallsInCode(code, parser, options = {}) {
1282
1330
  isMethod: true,
1283
1331
  receiver,
1284
1332
  ...(receiverType && { receiverType }),
1333
+ ...(receiverType && isGuessedType(receiver) && { receiverTypeGuessed: true }),
1285
1334
  enclosingFunction,
1286
1335
  isPotentialCallback: true,
1287
1336
  uncertain: false
@@ -1337,6 +1386,7 @@ function findCallsInCode(code, parser, options = {}) {
1337
1386
  isMethod: true,
1338
1387
  receiver,
1339
1388
  ...(receiverType && { receiverType }),
1389
+ ...(receiverType && isGuessedType(receiver) && { receiverTypeGuessed: true }),
1340
1390
  enclosingFunction,
1341
1391
  isPotentialCallback: true,
1342
1392
  uncertain: false
@@ -1431,6 +1481,7 @@ function findCallsInCode(code, parser, options = {}) {
1431
1481
  isMethod: true,
1432
1482
  receiver,
1433
1483
  ...(receiverType && { receiverType }),
1484
+ ...(receiverType && isGuessedType(receiver) && { receiverTypeGuessed: true }),
1434
1485
  enclosingFunction,
1435
1486
  isPotentialCallback: true,
1436
1487
  uncertain: false,
@@ -1469,6 +1520,7 @@ function findCallsInCode(code, parser, options = {}) {
1469
1520
  if (leaving) {
1470
1521
  closureScopes.delete(leaving.startLine);
1471
1522
  scopeTypes.delete(leaving.startLine);
1523
+ scopeGuesses.delete(leaving.startLine);
1472
1524
  }
1473
1525
  }
1474
1526
  }
package/languages/java.js CHANGED
@@ -556,16 +556,26 @@ function extractImplements(classNode) {
556
556
  * Extract extends from interface
557
557
  */
558
558
  function extractInterfaceExtends(interfaceNode) {
559
- const extendsNode = interfaceNode.childForFieldName('extends');
560
- if (extendsNode) {
561
- const interfaces = [];
562
- for (let i = 0; i < extendsNode.namedChildCount; i++) {
563
- const iface = extendsNode.namedChild(i);
564
- interfaces.push(iface.text);
559
+ // The grammar exposes interface extends as an `extends_interfaces` child
560
+ // wrapping a type_list, not as an `extends` field — the field lookup
561
+ // returned null and interfaces silently never recorded their supertypes
562
+ // (fix #270: the deadcode heritage walk needs them).
563
+ const interfaces = [];
564
+ for (let i = 0; i < interfaceNode.namedChildCount; i++) {
565
+ const child = interfaceNode.namedChild(i);
566
+ if (child.type !== 'extends_interfaces') continue;
567
+ for (let j = 0; j < child.namedChildCount; j++) {
568
+ const entry = child.namedChild(j);
569
+ if (entry.type === 'type_list') {
570
+ for (let k = 0; k < entry.namedChildCount; k++) {
571
+ interfaces.push(entry.namedChild(k).text);
572
+ }
573
+ } else {
574
+ interfaces.push(entry.text);
575
+ }
565
576
  }
566
- return interfaces;
567
577
  }
568
- return [];
578
+ return interfaces;
569
579
  }
570
580
 
571
581
  /**
@@ -1131,11 +1141,14 @@ function findCallsInCode(code, parser) {
1131
1141
  // Chained receiver (fix #220): the receiver IS a call —
1132
1142
  // getConfig().validate() — record the producer so findCallers
1133
1143
  // can type it from the declared return annotation.
1134
- let receiverCall, receiverCallIsMethod;
1144
+ let receiverCall, receiverCallIsMethod, receiverCallLine;
1135
1145
  if (!receiver && !receiverFieldName && objNode?.type === 'method_invocation') {
1136
1146
  const prodName = objNode.childForFieldName('name');
1137
1147
  if (prodName) {
1138
1148
  receiverCall = prodName.text;
1149
+ // Producer link (fix #258): records report the name
1150
+ // node's own line
1151
+ receiverCallLine = prodName.startPosition.row + 1;
1139
1152
  if (objNode.childForFieldName('object')) receiverCallIsMethod = true;
1140
1153
  }
1141
1154
  }
@@ -1155,6 +1168,7 @@ function findCallsInCode(code, parser) {
1155
1168
  ...(receiverFieldName && receiverRootType && { receiverRootType }),
1156
1169
  ...(receiverCall && { receiverCall }),
1157
1170
  ...(receiverCallIsMethod && { receiverCallIsMethod: true }),
1171
+ ...(receiverCallLine && { receiverCallLine }),
1158
1172
  argCount: callArgs.argCount,
1159
1173
  ...(callArgs.argKinds && { argKinds: callArgs.argKinds }),
1160
1174
  ...(assignedTo && { assignedTo }),
@@ -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
- if (nameNode) {
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' && right?.type === 'new_expression') {
1732
- nonCallableNames.add(left.text);
1733
- const ctorName = jsConstructorTypeName(right.childForFieldName('constructor'));
1734
- if (ctorName) {
1735
- localVarTypes.set(left.text, ctorName);
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
  }
@@ -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
- // Path producers (Config::load().x()) stay uncaptured
1358
- // until a measured family justifies the path branch.
1359
- let receiverCall, receiverCallIsMethod;
1360
- if (!receiver && !receiverField && valueNode?.type === 'call_expression') {
1361
- const prodFunc = valueNode.childForFieldName('function');
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) { receiverCall = pf.text; receiverCallIsMethod = true; }
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.1",
3
+ "version": "4.1.3",
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",