ucn 3.8.25 → 4.0.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.
@@ -12,7 +12,8 @@ const {
12
12
  extractParams,
13
13
  parseStructuredParams,
14
14
  extractJSDocstring,
15
- buildTypeAnnotations
15
+ buildTypeAnnotations,
16
+ visitNameNodes,
16
17
  } = require('./utils');
17
18
  const { PARSE_OPTIONS, safeParse } = require('./index');
18
19
 
@@ -38,6 +39,27 @@ function extractReturnType(node) {
38
39
  return null;
39
40
  }
40
41
 
42
+ /**
43
+ * Base type name from a type-alias target (fix #208, TS): ZodType<any, any>
44
+ * → ZodType, ns.Type → Type, (T) → T. Unions, intersections, object/function
45
+ * types, mapped/conditional types, and predefined types return null — they
46
+ * are not single-type identities a method receiver can be resolved through.
47
+ */
48
+ function aliasBaseTypeName(typeNode) {
49
+ if (!typeNode) return null;
50
+ if (typeNode.type === 'type_identifier') return typeNode.text;
51
+ if (typeNode.type === 'nested_type_identifier') {
52
+ return typeNode.childForFieldName('name')?.text || null;
53
+ }
54
+ if (typeNode.type === 'generic_type') {
55
+ return aliasBaseTypeName(typeNode.childForFieldName('name') || typeNode.namedChild(0));
56
+ }
57
+ if (typeNode.type === 'parenthesized_type') {
58
+ return aliasBaseTypeName(typeNode.namedChild(0));
59
+ }
60
+ return null;
61
+ }
62
+
41
63
  /**
42
64
  * Check if function is a generator
43
65
  * @param {object} node - Function node
@@ -622,6 +644,11 @@ function _processClass(node, classes, processedRanges, lines) {
622
644
  if (nameNode) {
623
645
  const { startLine, endLine } = nodeToLocation(node, lines);
624
646
  const docstring = extractJSDocstring(lines, startLine);
647
+ // `type ZodTypeAny = ZodType<any, any, any>;` — the alias IS the
648
+ // aliased type. Record the base name so receivers annotated with
649
+ // the alias validate against the base type's methods (fix #208,
650
+ // TS parity with Rust/Go).
651
+ const aliasOf = aliasBaseTypeName(node.childForFieldName('value'));
625
652
 
626
653
  classes.push({
627
654
  name: nameNode.text,
@@ -629,6 +656,7 @@ function _processClass(node, classes, processedRanges, lines) {
629
656
  endLine,
630
657
  type: 'type',
631
658
  members: [],
659
+ ...(aliasOf && { aliasOf }),
632
660
  ...(docstring && { docstring })
633
661
  });
634
662
  }
@@ -818,11 +846,18 @@ function extractInterfaceMembers(interfaceNode, code) {
818
846
  const nameNode = child.childForFieldName('name');
819
847
  if (nameNode) {
820
848
  const { startLine, endLine } = nodeToLocation(child, code);
849
+ // Declared property type (fix #219): raw annotation text —
850
+ // findCallers hops field receivers to it (this._map.has()),
851
+ // and function-typed properties ((arg) => T) count as
852
+ // callable owners in the dispatch tiering.
853
+ const typeNode = child.childForFieldName('type');
854
+ const fieldType = typeNode ? typeNode.text.replace(/^:\s*/, '').trim() : undefined;
821
855
  members.push({
822
856
  name: nameNode.text,
823
857
  startLine,
824
858
  endLine,
825
- memberType: 'field'
859
+ memberType: 'field',
860
+ ...(fieldType && { fieldType })
826
861
  });
827
862
  }
828
863
  }
@@ -1030,11 +1065,18 @@ function extractClassMembers(classNode, codeOrLines) {
1030
1065
  ...(fieldDecorators.length > 0 && { decorators: fieldDecorators })
1031
1066
  });
1032
1067
  } else {
1068
+ // Declared field type (fix #219): `_map: WeakMap<K,V> =
1069
+ // new WeakMap()` — the annotation is the compiler-true
1070
+ // contract for every receiver hop through this field.
1071
+ const fieldTypeNode = child.childForFieldName('type');
1072
+ const fieldType = fieldTypeNode
1073
+ ? fieldTypeNode.text.replace(/^:\s*/, '').trim() : undefined;
1033
1074
  members.push({
1034
1075
  name,
1035
1076
  startLine,
1036
1077
  endLine,
1037
1078
  memberType: name.startsWith('#') ? 'private field' : 'field',
1079
+ ...(fieldType && { fieldType }),
1038
1080
  ...(fieldDecorators.length > 0 && { decorators: fieldDecorators })
1039
1081
  // Not a method - regular field
1040
1082
  });
@@ -1149,6 +1191,97 @@ function parse(code, parser) {
1149
1191
  * @param {object} parser - Tree-sitter parser instance
1150
1192
  * @returns {Array<{name: string, line: number, isMethod: boolean, receiver?: string, isConstructor?: boolean}>}
1151
1193
  */
1194
+ // Builtin types for literal method receivers: [].map() is Array.map, never a
1195
+ // project class method. Keys are tree-sitter node types.
1196
+ const JS_LITERAL_RECEIVER_TYPES = {
1197
+ array: 'Array',
1198
+ string: 'String',
1199
+ template_string: 'String',
1200
+ object: 'Object',
1201
+ regex: 'RegExp',
1202
+ number: 'Number',
1203
+ };
1204
+
1205
+ // Predefined TS types that pin a receiver; any/unknown/object say nothing.
1206
+ const TS_PREDEFINED_RECEIVER_TYPES = new Set(['string', 'number', 'boolean', 'bigint', 'symbol']);
1207
+
1208
+ /**
1209
+ * Extract a single concrete type name from a TS type node. Conservative by
1210
+ * design: a wrong type would exclude true callers downstream
1211
+ * (receiver-type-mismatch), so anything ambiguous returns undefined.
1212
+ * Handles: Foo · ns.Foo · Foo | null · Store<string> · (Foo) · string
1213
+ */
1214
+ function tsTypeName(node) {
1215
+ if (!node) return undefined;
1216
+ switch (node.type) {
1217
+ case 'type_identifier':
1218
+ case 'identifier':
1219
+ return node.text;
1220
+ case 'nested_type_identifier': {
1221
+ // ns.Foo → classes match by name in the symbol table → last segment
1222
+ const last = node.namedChild(node.namedChildCount - 1);
1223
+ return last?.text;
1224
+ }
1225
+ case 'generic_type':
1226
+ // Store<string> → Store
1227
+ return tsTypeName(node.namedChild(0));
1228
+ case 'union_type': {
1229
+ // Foo | null / Foo | undefined → Foo; unions of two real types are ambiguous
1230
+ const real = [];
1231
+ for (let i = 0; i < node.namedChildCount; i++) {
1232
+ const c = node.namedChild(i);
1233
+ if (c.type === 'literal_type' ||
1234
+ (c.type === 'predefined_type' && !TS_PREDEFINED_RECEIVER_TYPES.has(c.text))) {
1235
+ continue;
1236
+ }
1237
+ real.push(c);
1238
+ }
1239
+ return real.length === 1 ? tsTypeName(real[0]) : undefined;
1240
+ }
1241
+ case 'parenthesized_type':
1242
+ return tsTypeName(node.namedChild(0));
1243
+ case 'predefined_type':
1244
+ return TS_PREDEFINED_RECEIVER_TYPES.has(node.text) ? node.text : undefined;
1245
+ default:
1246
+ return undefined;
1247
+ }
1248
+ }
1249
+
1250
+ /**
1251
+ * Variable receiving this call's result: `const x = foo()` / `x = await foo()`
1252
+ * → 'x'. Identifier targets only. Compared by node id — tree-sitter wrapper
1253
+ * objects are not identity-stable.
1254
+ */
1255
+ function jsAssignmentTargetOf(callNode) {
1256
+ let n = callNode;
1257
+ let p = n.parent;
1258
+ if (p && p.type === 'await_expression') { n = p; p = n.parent; }
1259
+ if (p && p.type === 'variable_declarator') {
1260
+ const value = p.childForFieldName('value');
1261
+ const nameNode = p.childForFieldName('name');
1262
+ if (value && value.id === n.id && nameNode?.type === 'identifier') return nameNode.text;
1263
+ }
1264
+ if (p && p.type === 'assignment_expression') {
1265
+ const right = p.childForFieldName('right');
1266
+ const left = p.childForFieldName('left');
1267
+ if (right && right.id === n.id && left?.type === 'identifier') return left.text;
1268
+ }
1269
+ return undefined;
1270
+ }
1271
+
1272
+ /**
1273
+ * Type name from a new-expression constructor node: new Foo() or new pkg.Foo().
1274
+ */
1275
+ function jsConstructorTypeName(ctorNode) {
1276
+ if (!ctorNode) return undefined;
1277
+ if (ctorNode.type === 'identifier') return ctorNode.text;
1278
+ if (ctorNode.type === 'member_expression') {
1279
+ const prop = ctorNode.childForFieldName('property');
1280
+ return prop?.text;
1281
+ }
1282
+ return undefined;
1283
+ }
1284
+
1152
1285
  function findCallsInCode(code, parser) {
1153
1286
  const tree = parseTree(parser, code);
1154
1287
  const calls = [];
@@ -1156,6 +1289,7 @@ function findCallsInCode(code, parser) {
1156
1289
  const aliases = new Map(); // Track local aliases: aliasName -> originalName (string or string[])
1157
1290
  const nonCallableNames = new Set(); // Track names assigned non-callable values
1158
1291
  const localVarTypes = new Map(); // Track local variable types: varName -> typeName (for receiverType inference)
1292
+ const moduleAliases = new Set(); // Names bound to MODULES (import * as ns / const pkg = require(...))
1159
1293
  const localVarTypesStack = []; // Stack for function-scoped save/restore of localVarTypes
1160
1294
 
1161
1295
  // Helper: extract first string-arg literal from a call_expression node.
@@ -1337,7 +1471,90 @@ function findCallsInCode(code, parser) {
1337
1471
  : null;
1338
1472
  };
1339
1473
 
1474
+ // fix #203: does a declaration node declare `name` (incl. shallow destructuring)?
1475
+ const _declaresName = (declNode, name) => {
1476
+ for (let i = 0; i < declNode.namedChildCount; i++) {
1477
+ const d = declNode.namedChild(i);
1478
+ if (d.type !== 'variable_declarator') continue;
1479
+ const nameNode = d.childForFieldName('name');
1480
+ if (nameNode?.type === 'identifier' && nameNode.text === name) return true;
1481
+ if (nameNode && (nameNode.type === 'object_pattern' || nameNode.type === 'array_pattern')) {
1482
+ for (let j = 0; j < nameNode.namedChildCount; j++) {
1483
+ const el = nameNode.namedChild(j);
1484
+ if ((el.type === 'identifier' || el.type === 'shorthand_property_identifier_pattern') &&
1485
+ el.text === name) return true;
1486
+ }
1487
+ }
1488
+ }
1489
+ return false;
1490
+ };
1491
+
1492
+ // fix #203: is a bare-identifier function REFERENCE shadowed by a
1493
+ // let/const/var local, for/catch binding, or inner-arrow param in an
1494
+ // enclosing lexical scope? Block-accurate, declaration-before-use.
1495
+ // The enclosing SYMBOL's params are checked at query time in
1496
+ // findCallers — let locals and non-symbol arrow params are only
1497
+ // visible here. Module-level (program) declarations are NOT shadows:
1498
+ // that's the module binding itself, owned by binding resolution.
1499
+ const isShadowedByLocal = (refNode, name) => {
1500
+ for (let p = refNode.parent; p; p = p.parent) {
1501
+ if (p.type === 'statement_block') {
1502
+ for (let i = 0; i < p.namedChildCount; i++) {
1503
+ const stmt = p.namedChild(i);
1504
+ // Nested function/class declarations are hoisted within
1505
+ // their block — they shadow regardless of position
1506
+ // (fix #218: `function getStyle() {}` after the ref).
1507
+ if ((stmt.type === 'function_declaration' ||
1508
+ stmt.type === 'generator_function_declaration' ||
1509
+ stmt.type === 'class_declaration') &&
1510
+ stmt.childForFieldName('name')?.text === name) return true;
1511
+ if (stmt.startIndex >= refNode.startIndex) continue; // declaration-before-use
1512
+ if ((stmt.type === 'lexical_declaration' || stmt.type === 'variable_declaration') &&
1513
+ _declaresName(stmt, name)) return true;
1514
+ }
1515
+ } else if (p.type === 'for_statement') {
1516
+ const init = p.childForFieldName('initializer');
1517
+ if (init && (init.type === 'lexical_declaration' || init.type === 'variable_declaration') &&
1518
+ _declaresName(init, name)) return true;
1519
+ } else if (p.type === 'for_in_statement') {
1520
+ const left = p.childForFieldName('left');
1521
+ if (left?.type === 'identifier' && left.text === name) return true;
1522
+ if (left && (left.type === 'lexical_declaration' || left.type === 'variable_declaration') &&
1523
+ _declaresName(left, name)) return true;
1524
+ } else if (p.type === 'catch_clause') {
1525
+ const param = p.childForFieldName('parameter');
1526
+ if (param?.type === 'identifier' && param.text === name) return true;
1527
+ } else if (p.type === 'arrow_function' || p.type === 'function_expression' ||
1528
+ p.type === 'function_declaration' || p.type === 'function' ||
1529
+ p.type === 'method_definition' || p.type === 'generator_function' ||
1530
+ p.type === 'generator_function_declaration') {
1531
+ const params = p.childForFieldName('parameters') || p.childForFieldName('parameter');
1532
+ if (params) {
1533
+ if (params.type === 'identifier' && params.text === name) return true; // x => ...
1534
+ for (let i = 0; i < params.namedChildCount; i++) {
1535
+ const prm = params.namedChild(i);
1536
+ if (prm.type === 'identifier' && prm.text === name) return true;
1537
+ if (prm.type === 'assignment_pattern' || prm.type === 'required_parameter' ||
1538
+ prm.type === 'optional_parameter') {
1539
+ const l = prm.childForFieldName('left') || prm.childForFieldName('pattern');
1540
+ if (l?.type === 'identifier' && l.text === name) return true;
1541
+ }
1542
+ }
1543
+ }
1544
+ }
1545
+ }
1546
+ return false;
1547
+ };
1548
+
1340
1549
  traverseTree(tree.rootNode, (node) => {
1550
+ // Track module-alias bindings: `import * as ns from "./m"` binds ns to a
1551
+ // MODULE — method calls through it dispatch to module exports, never to
1552
+ // class methods.
1553
+ if (node.type === 'namespace_import') {
1554
+ const id = node.namedChild(0);
1555
+ if (id?.type === 'identifier') moduleAliases.add(id.text);
1556
+ }
1557
+
1341
1558
  // Track function entry
1342
1559
  if (isFunctionNode(node)) {
1343
1560
  functionStack.push({
@@ -1353,6 +1570,13 @@ function findCallsInCode(code, parser) {
1353
1570
  if (node.type === 'variable_declarator') {
1354
1571
  const nameNode = node.childForFieldName('name');
1355
1572
  const initNode = node.childForFieldName('value');
1573
+ // const pkg = require("./lib") — pkg is a module namespace
1574
+ if (nameNode?.type === 'identifier' && initNode?.type === 'call_expression') {
1575
+ const fn = initNode.childForFieldName('function');
1576
+ if (fn?.type === 'identifier' && fn.text === 'require') {
1577
+ moduleAliases.add(nameNode.text);
1578
+ }
1579
+ }
1356
1580
  if (nameNode?.type === 'identifier' && initNode?.type === 'identifier') {
1357
1581
  // Simple alias: const p = parse
1358
1582
  aliases.set(nameNode.text, initNode.text);
@@ -1388,10 +1612,10 @@ function findCallsInCode(code, parser) {
1388
1612
  // Constructor results are object instances, not callable functions
1389
1613
  if (nameNode?.type === 'identifier' && initNode?.type === 'new_expression') {
1390
1614
  nonCallableNames.add(nameNode.text);
1391
- // Infer type: const x = new Foo() → x is Foo
1392
- const ctorNode = initNode.childForFieldName('constructor');
1393
- if (ctorNode?.type === 'identifier') {
1394
- localVarTypes.set(nameNode.text, ctorNode.text);
1615
+ // Infer type: const x = new Foo() / new pkg.Foo() → x is Foo
1616
+ const ctorName = jsConstructorTypeName(initNode.childForFieldName('constructor'));
1617
+ if (ctorName) {
1618
+ localVarTypes.set(nameNode.text, ctorName);
1395
1619
  }
1396
1620
  }
1397
1621
  // Track TypeScript type annotations: const x: Foo = ...
@@ -1401,28 +1625,36 @@ function findCallsInCode(code, parser) {
1401
1625
  // type_annotation → first named child is the type identifier
1402
1626
  const typeId = typeNode.type === 'type_annotation'
1403
1627
  ? typeNode.namedChild(0) : typeNode;
1404
- if (typeId?.type === 'type_identifier' || typeId?.type === 'identifier') {
1405
- localVarTypes.set(nameNode.text, typeId.text);
1406
- } else if (typeId?.type === 'generic_type') {
1407
- // Store<string> → generic_type has type_identifier as first child
1408
- const baseType = typeId.namedChild(0);
1409
- if (baseType?.type === 'type_identifier' || baseType?.type === 'identifier') {
1410
- localVarTypes.set(nameNode.text, baseType.text);
1411
- }
1628
+ const typeName = tsTypeName(typeId);
1629
+ if (typeName) {
1630
+ localVarTypes.set(nameNode.text, typeName);
1412
1631
  }
1413
1632
  }
1414
1633
  }
1415
1634
  }
1416
1635
 
1636
+ // Track TS parameter type annotations: function f(client: Client) → client is Client
1637
+ if (node.type === 'required_parameter' || node.type === 'optional_parameter') {
1638
+ const pat = node.childForFieldName('pattern') || node.namedChild(0);
1639
+ const typeNode = node.childForFieldName('type');
1640
+ if (pat?.type === 'identifier' && typeNode) {
1641
+ const inner = typeNode.type === 'type_annotation' ? typeNode.namedChild(0) : typeNode;
1642
+ const typeName = tsTypeName(inner);
1643
+ if (typeName) {
1644
+ localVarTypes.set(pat.text, typeName);
1645
+ }
1646
+ }
1647
+ }
1648
+
1417
1649
  // Track reassignment with new expression: x = new Bar() → update localVarTypes
1418
1650
  if (node.type === 'assignment_expression') {
1419
1651
  const left = node.childForFieldName('left');
1420
1652
  const right = node.childForFieldName('right');
1421
1653
  if (left?.type === 'identifier' && right?.type === 'new_expression') {
1422
1654
  nonCallableNames.add(left.text);
1423
- const ctorNode = right.childForFieldName('constructor');
1424
- if (ctorNode?.type === 'identifier') {
1425
- localVarTypes.set(left.text, ctorNode.text);
1655
+ const ctorName = jsConstructorTypeName(right.childForFieldName('constructor'));
1656
+ if (ctorName) {
1657
+ localVarTypes.set(left.text, ctorName);
1426
1658
  }
1427
1659
  }
1428
1660
  }
@@ -1445,6 +1677,7 @@ function findCallsInCode(code, parser) {
1445
1677
  const resolvedName = typeof alias === 'string' ? alias : undefined;
1446
1678
  const resolvedNames = Array.isArray(alias) ? alias : undefined;
1447
1679
  const firstArg = getFirstStringArg(node);
1680
+ const assignedTo = jsAssignmentTargetOf(node);
1448
1681
  // MEDIUM-5: capture explicit method for fetch(url, { method }).
1449
1682
  const optionsMethod = funcNode.text === 'fetch'
1450
1683
  ? getOptionsMethod(node, 1)
@@ -1455,6 +1688,7 @@ function findCallsInCode(code, parser) {
1455
1688
  ...(resolvedNames && { resolvedNames }),
1456
1689
  line: node.startPosition.row + 1,
1457
1690
  isMethod: false,
1691
+ ...(assignedTo && { assignedTo }),
1458
1692
  enclosingFunction,
1459
1693
  uncertain,
1460
1694
  ...(firstArg && { firstStringArg: firstArg.value, firstStringArgInterp: firstArg.interp }),
@@ -1468,7 +1702,12 @@ function findCallsInCode(code, parser) {
1468
1702
  if (propNode) {
1469
1703
  const propName = propNode.text;
1470
1704
 
1471
- // Handle .call(), .apply(), .bind() - these are calls TO the object
1705
+ // Handle .call(), .apply(), .bind() - these are calls TO the object.
1706
+ // boundCall marks the indirection (fix #221, family B): the line
1707
+ // establishes the call relationship through Function.prototype
1708
+ // rather than direct call syntax — the edge surfaces as
1709
+ // calledAs:'bound' so consumers know reference oracles see a
1710
+ // non-call reference here.
1472
1711
  if (['call', 'apply', 'bind'].includes(propName) && objNode) {
1473
1712
  if (objNode.type === 'identifier') {
1474
1713
  // foo.call() -> call to foo
@@ -1476,6 +1715,7 @@ function findCallsInCode(code, parser) {
1476
1715
  name: objNode.text,
1477
1716
  line: node.startPosition.row + 1,
1478
1717
  isMethod: false,
1718
+ boundCall: true,
1479
1719
  enclosingFunction
1480
1720
  });
1481
1721
  } else if (objNode.type === 'member_expression') {
@@ -1487,6 +1727,7 @@ function findCallsInCode(code, parser) {
1487
1727
  name: innerProp.text,
1488
1728
  line: node.startPosition.row + 1,
1489
1729
  isMethod: true,
1730
+ boundCall: true,
1490
1731
  receiver: innerObj?.text,
1491
1732
  enclosingFunction,
1492
1733
  uncertain
@@ -1502,15 +1743,82 @@ function findCallsInCode(code, parser) {
1502
1743
  receiver = objNode.text;
1503
1744
  }
1504
1745
  }
1505
- const receiverType = receiver ? localVarTypes.get(receiver) : undefined;
1746
+ // One-hop field receiver (fix #219 — #202's shape for
1747
+ // structural): this._map.has(x) / def.cache.get(k) —
1748
+ // receiverRoot/Field let findCallers hop to the
1749
+ // field's DECLARED type annotation. `this`-rooted hops
1750
+ // resolve their root type query-side (the enclosing
1751
+ // class); identifier roots type from local annotations.
1752
+ let receiverRoot, receiverFieldName, receiverRootType;
1753
+ if (!receiver && objNode && objNode.type === 'member_expression') {
1754
+ const rootNode = objNode.childForFieldName('object');
1755
+ const fldNode = objNode.childForFieldName('property');
1756
+ if (fldNode && rootNode &&
1757
+ (rootNode.type === 'identifier' || rootNode.type === 'this')) {
1758
+ receiverRoot = rootNode.text;
1759
+ receiverFieldName = fldNode.text;
1760
+ if (rootNode.type === 'identifier') {
1761
+ receiverRootType = localVarTypes.get(rootNode.text);
1762
+ }
1763
+ }
1764
+ }
1765
+ // Chained receiver (fix #219): the receiver IS a call —
1766
+ // parseAsync(args).catch(...) — record the producer so
1767
+ // findCallers can type the receiver from its declared
1768
+ // return annotation (Promise<...> → Promise).
1769
+ let receiverCall, receiverCallIsMethod, receiverCallAwaited;
1770
+ {
1771
+ let recvNode = objNode;
1772
+ if (recvNode && recvNode.type === 'parenthesized_expression') {
1773
+ recvNode = recvNode.namedChild(0);
1774
+ }
1775
+ if (recvNode && recvNode.type === 'await_expression') {
1776
+ receiverCallAwaited = true;
1777
+ recvNode = recvNode.namedChild(0);
1778
+ }
1779
+ if (recvNode && recvNode.type === 'call_expression') {
1780
+ const prodFunc = recvNode.childForFieldName('function');
1781
+ if (prodFunc?.type === 'identifier') {
1782
+ receiverCall = prodFunc.text;
1783
+ } else if (prodFunc?.type === 'member_expression') {
1784
+ const prodProp = prodFunc.childForFieldName('property');
1785
+ if (prodProp) {
1786
+ receiverCall = prodProp.text;
1787
+ receiverCallIsMethod = true;
1788
+ }
1789
+ }
1790
+ }
1791
+ if (!receiverCall) receiverCallAwaited = undefined;
1792
+ }
1793
+ // Literal receivers carry their builtin type: [].map() can
1794
+ // never be a project class method
1795
+ const receiverType = receiver
1796
+ ? localVarTypes.get(receiver)
1797
+ : (objNode ? JS_LITERAL_RECEIVER_TYPES[objNode.type] : undefined);
1798
+ // Module receiver (ns.helper()) — unless locally shadowed
1799
+ // by a typed instance binding
1800
+ const receiverIsModule = !!receiver && moduleAliases.has(receiver) &&
1801
+ !localVarTypes.has(receiver);
1506
1802
  const firstArg = getFirstStringArg(node);
1507
1803
  const argCount = getArgCount(node);
1804
+ const assignedTo = jsAssignmentTargetOf(node);
1508
1805
  calls.push({
1509
1806
  name: propName,
1510
- line: node.startPosition.row + 1,
1807
+ // Multi-line chains (builder.x()\n.y()) must report
1808
+ // each method's OWN name line, not the chain-start
1809
+ // line — the account's ground set is keyed by the
1810
+ // name's line
1811
+ line: propNode.startPosition.row + 1,
1511
1812
  isMethod: true,
1512
1813
  receiver,
1513
1814
  ...(receiverType && { receiverType }),
1815
+ ...(receiverIsModule && { receiverIsModule: true }),
1816
+ ...(receiverFieldName && { receiverRoot, receiverField: receiverFieldName }),
1817
+ ...(receiverFieldName && receiverRootType && { receiverRootType }),
1818
+ ...(receiverCall && { receiverCall }),
1819
+ ...(receiverCallIsMethod && { receiverCallIsMethod: true }),
1820
+ ...(receiverCallAwaited && { receiverCallAwaited: true }),
1821
+ ...(assignedTo && { assignedTo }),
1514
1822
  enclosingFunction,
1515
1823
  uncertain,
1516
1824
  ...(firstArg && { firstStringArg: firstArg.value, firstStringArgInterp: firstArg.interp }),
@@ -1551,6 +1859,7 @@ function findCallsInCode(code, parser) {
1551
1859
  line: arg.startPosition.row + 1,
1552
1860
  isMethod: false,
1553
1861
  isFunctionReference: true,
1862
+ ...(isShadowedByLocal(arg, arg.text) && { localShadow: true }),
1554
1863
  enclosingFunction
1555
1864
  });
1556
1865
  } else if (arg.type === 'member_expression') {
@@ -1589,6 +1898,7 @@ function findCallsInCode(code, parser) {
1589
1898
  isMethod: false,
1590
1899
  isFunctionReference: true,
1591
1900
  isPotentialCallback: true,
1901
+ ...(isShadowedByLocal(arg, arg.text) && { localShadow: true }),
1592
1902
  enclosingFunction
1593
1903
  });
1594
1904
  }
@@ -1606,6 +1916,7 @@ function findCallsInCode(code, parser) {
1606
1916
  isMethod: false,
1607
1917
  isFunctionReference: true,
1608
1918
  isPotentialCallback: true,
1919
+ ...(isShadowedByLocal(val, val.text) && { localShadow: true }),
1609
1920
  enclosingFunction
1610
1921
  });
1611
1922
  }
@@ -1708,6 +2019,7 @@ function findCallsInCode(code, parser) {
1708
2019
  isMethod: false,
1709
2020
  isFunctionReference: true,
1710
2021
  isPotentialCallback: true,
2022
+ ...(isShadowedByLocal(child, child.text) && { localShadow: true }),
1711
2023
  enclosingFunction
1712
2024
  });
1713
2025
  } else if (child.type === 'member_expression') {
@@ -2107,7 +2419,20 @@ function findExportsInCode(code, parser) {
2107
2419
 
2108
2420
  // Check for export * from 'x'
2109
2421
  if (node.text.includes('export *') && source) {
2110
- exports.push({ name: '*', type: 're-export-all', line, source });
2422
+ // `export * as ns from 'x'` exposes ONLY the single name `ns`
2423
+ // (a module namespace object), not x's flattened surface —
2424
+ // record the alias so name-level chases don't walk through it
2425
+ // (fix #218: zod's `export * as core` made z._default look
2426
+ // reachable from core). Name stays '*' for shape stability.
2427
+ let nsAlias = null;
2428
+ for (let i = 0; i < node.namedChildCount; i++) {
2429
+ const child = node.namedChild(i);
2430
+ if (child.type === 'namespace_export') {
2431
+ const id = child.namedChild(0);
2432
+ if (id) nsAlias = id.text;
2433
+ }
2434
+ }
2435
+ exports.push({ name: '*', type: 're-export-all', line, source, ...(nsAlias && { alias: nsAlias }) });
2111
2436
  return true;
2112
2437
  }
2113
2438
 
@@ -2118,10 +2443,18 @@ function findExportsInCode(code, parser) {
2118
2443
  for (let j = 0; j < child.namedChildCount; j++) {
2119
2444
  const specifier = child.namedChild(j);
2120
2445
  if (specifier.type === 'export_specifier') {
2121
- const nameNode = specifier.namedChild(0);
2446
+ const nameNode = specifier.childForFieldName('name') || specifier.namedChild(0);
2447
+ // Export rename: `export { _gt as gt }` — name keeps the
2448
+ // local/source symbol (deadcode and re-export resolution
2449
+ // key on it); alias carries the external name callers use.
2450
+ const aliasNode = specifier.childForFieldName('alias');
2122
2451
  if (nameNode) {
2123
2452
  const exportType = source ? 're-export' : 'named';
2124
- exports.push({ name: nameNode.text, type: exportType, line, ...(source && { source }) });
2453
+ exports.push({
2454
+ name: nameNode.text, type: exportType, line,
2455
+ ...(source && { source }),
2456
+ ...(aliasNode && aliasNode.text !== nameNode.text && { alias: aliasNode.text }),
2457
+ });
2125
2458
  }
2126
2459
  }
2127
2460
  }
@@ -2267,7 +2600,7 @@ function findUsagesInCode(code, name, parser) {
2267
2600
  const tree = parseTree(parser, code);
2268
2601
  const usages = [];
2269
2602
 
2270
- traverseTreeCached(tree.rootNode, (node) => {
2603
+ visitNameNodes(tree, code, name, (node) => {
2271
2604
  // Look for identifier, property_identifier (method names in obj.method() calls),
2272
2605
  // type_identifier (TypeScript type annotations), and shorthand_property_identifier_pattern
2273
2606
  // (destructured names in `const { name } = require(...)`)