ucn 4.2.2 → 4.2.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.
@@ -429,6 +429,53 @@ function _processFunction(node, functions, processedRanges, lines) {
429
429
  return true;
430
430
  }
431
431
 
432
+ // Named function expressions used as callbacks have a real lexical
433
+ // definition even when no variable owns them: `test('x', function run()
434
+ // {})`. Property/variable assignments are handled by their binding
435
+ // branches below; indexing the expression name there as a second symbol
436
+ // would manufacture a duplicate public definition.
437
+ if (node.type === 'function_expression' || node.type === 'generator_function') {
438
+ const parent = node.parent;
439
+ const isBoundValue = (parent?.type === 'variable_declarator' &&
440
+ sameNode(parent.childForFieldName('value'), node)) ||
441
+ (parent?.type === 'assignment_expression' &&
442
+ sameNode(parent.childForFieldName('right'), node)) ||
443
+ (parent?.type === 'pair' && sameNode(parent.childForFieldName('value'), node));
444
+ const nameNode = node.childForFieldName('name');
445
+ if (!isBoundValue && nameNode && !processedRanges.has(rangeKey)) {
446
+ processedRanges.add(rangeKey);
447
+ const paramsNode = node.childForFieldName('parameters');
448
+ const { startLine, endLine, indent } = nodeToLocation(node, lines);
449
+ const returnType = extractReturnType(node);
450
+ const generics = extractGenerics(node);
451
+ const paramsStructured = parseStructuredParams(paramsNode, 'javascript');
452
+ const typeAnno = buildTypeAnnotations(paramsStructured, returnType, lines, startLine, true);
453
+ const docstring = extractJSDocstring(lines, startLine);
454
+ functions.push({
455
+ name: nameNode.text,
456
+ params: extractParams(paramsNode),
457
+ paramsStructured,
458
+ startLine,
459
+ endLine,
460
+ indent,
461
+ isArrow: false,
462
+ isGenerator: isGenerator(node),
463
+ isAsync: node.text.trimStart().startsWith('async '),
464
+ modifiers: [],
465
+ // ECMA-262: a FunctionExpression's BindingIdentifier is in
466
+ // scope only within its own body — the name creates no
467
+ // file-level binding (never enters the bindings table) and
468
+ // the expression is consumed where it appears (argument /
469
+ // value position), so deadcode never audits it.
470
+ bodyScopedName: true,
471
+ ...typeAnno,
472
+ ...(generics && { generics }),
473
+ ...(docstring && { docstring })
474
+ });
475
+ }
476
+ return true;
477
+ }
478
+
432
479
  // TypeScript function signatures (e.g., in .d.ts files)
433
480
  if (node.type === 'function_signature') {
434
481
  if (processedRanges.has(rangeKey)) return false;
@@ -613,7 +660,10 @@ function _processFunction(node, functions, processedRanges, lines) {
613
660
  }
614
661
  parent = parent.parent;
615
662
  }
616
- if (!isTopLevel) return true;
663
+ // A nested property assignment still defines that object's
664
+ // callable member (`reply.send = () => {}`). Only a nested bare
665
+ // assignment lacks a new symbol binding and stays excluded.
666
+ if (!isTopLevel && leftNode?.type !== 'member_expression') return true;
617
667
  }
618
668
 
619
669
  const rightNode = node.childForFieldName('right');
@@ -1388,6 +1438,87 @@ function parse(code, parser) {
1388
1438
  return true; // always continue, never skip subtrees
1389
1439
  });
1390
1440
 
1441
+ // Some valid overload-heavy TypeScript files exceed the grammar's error
1442
+ // recovery budget. tree-sitter then returns a whole-file ERROR root and
1443
+ // flattens later declarations into unrelated type nodes without throwing.
1444
+ // Recover from AST tokens, not source patterns: top-level declaration
1445
+ // tokens define bounded fragments which are reparsed by the same grammar.
1446
+ // This keeps the AST-only contract while preventing a valid declaration
1447
+ // near the end of one difficult type file from disappearing silently.
1448
+ if (tree.rootNode.hasError) {
1449
+ const declarationTokens = [];
1450
+ const startsDeclaration = new Set([
1451
+ 'export', 'declare', 'async', 'function', 'class', 'abstract',
1452
+ 'interface', 'type', 'enum', 'namespace', 'module',
1453
+ 'const', 'let', 'var'
1454
+ ]);
1455
+ const stack = [tree.rootNode];
1456
+ while (stack.length > 0) {
1457
+ const node = stack.pop();
1458
+ if (node.childCount === 0) {
1459
+ // In severe recovery, a keyword itself can be downgraded to
1460
+ // an identifier token. Its AST position/text still supplies
1461
+ // a safe declaration boundary; semantic extraction remains
1462
+ // entirely delegated to the reparsed fragment.
1463
+ const recoveredKeyword = node.type === 'identifier' &&
1464
+ startsDeclaration.has(node.text);
1465
+ if (node.startPosition.column === 0 &&
1466
+ (startsDeclaration.has(node.type) || recoveredKeyword)) {
1467
+ declarationTokens.push(node);
1468
+ }
1469
+ continue;
1470
+ }
1471
+ const children = node.children;
1472
+ for (let i = children.length - 1; i >= 0; i--) stack.push(children[i]);
1473
+ }
1474
+ declarationTokens.sort((a, b) => a.startIndex - b.startIndex);
1475
+
1476
+ const recoveredFunctions = [], recoveredClasses = [], recoveredState = [];
1477
+ for (let i = 0; i < declarationTokens.length; i++) {
1478
+ const token = declarationTokens[i];
1479
+ const next = declarationTokens[i + 1];
1480
+ if (next && next.startIndex === token.startIndex) continue;
1481
+ const fragment = code.slice(token.startIndex, next?.startIndex ?? code.length);
1482
+ if (!fragment.trim()) continue;
1483
+ const fragmentTree = parseTree(parser, fragment);
1484
+ const fragmentLines = fragment.split('\n');
1485
+ const ff = [], fc = [], fs = [];
1486
+ const pf = new Set(), pc = new Set();
1487
+ traverseTreeCached(fragmentTree.rootNode, (node) => {
1488
+ _processFunction(node, ff, pf, fragmentLines);
1489
+ _processClass(node, fc, pc, fragmentLines);
1490
+ _processState(node, fs, fragmentLines);
1491
+ return true;
1492
+ });
1493
+ const lineOffset = token.startPosition.row;
1494
+ const shiftLines = (value) => {
1495
+ if (!value || typeof value !== 'object') return;
1496
+ if (Array.isArray(value)) {
1497
+ for (const item of value) shiftLines(item);
1498
+ return;
1499
+ }
1500
+ for (const [key, child] of Object.entries(value)) {
1501
+ if (Number.isInteger(child) && /Line$/.test(key)) value[key] = child + lineOffset;
1502
+ else if (child && typeof child === 'object') shiftLines(child);
1503
+ }
1504
+ };
1505
+ for (const item of ff) { shiftLines(item); recoveredFunctions.push(item); }
1506
+ for (const item of fc) { shiftLines(item); recoveredClasses.push(item); }
1507
+ for (const item of fs) { shiftLines(item); recoveredState.push(item); }
1508
+ }
1509
+
1510
+ const mergeUnique = (target, additions, kind) => {
1511
+ const seen = new Set(target.map(item => `${kind}\0${item.name}\0${item.startLine}`));
1512
+ for (const item of additions) {
1513
+ const key = `${kind}\0${item.name}\0${item.startLine}`;
1514
+ if (!seen.has(key)) { seen.add(key); target.push(item); }
1515
+ }
1516
+ };
1517
+ mergeUnique(functions, recoveredFunctions, 'function');
1518
+ mergeUnique(classes, recoveredClasses, 'class');
1519
+ mergeUnique(stateObjects, recoveredState, 'state');
1520
+ }
1521
+
1391
1522
  functions.sort((a, b) => a.startLine - b.startLine);
1392
1523
  classes.sort((a, b) => a.startLine - b.startLine);
1393
1524
  stateObjects.sort((a, b) => a.startLine - b.startLine);
@@ -1519,6 +1650,13 @@ function jsConstructorTypeName(ctorNode) {
1519
1650
  return undefined;
1520
1651
  }
1521
1652
 
1653
+ function jsConstructorTypeQualifier(ctorNode) {
1654
+ if (ctorNode?.type !== 'member_expression') return undefined;
1655
+ let root = ctorNode.childForFieldName('object');
1656
+ while (root?.type === 'member_expression') root = root.childForFieldName('object');
1657
+ return root?.type === 'identifier' ? root.text : undefined;
1658
+ }
1659
+
1522
1660
  function findCallsInCode(code, parser) {
1523
1661
  const tree = parseTree(parser, code);
1524
1662
  const calls = [];
@@ -1530,6 +1668,7 @@ function findCallsInCode(code, parser) {
1530
1668
  const aliases = new Map(); // aliasName -> [{ target, declarationIndex, scopeStart, scopeEnd }]
1531
1669
  const nonCallableNames = new Set(); // Track names assigned non-callable values
1532
1670
  const localVarTypes = new Map(); // Track local variable types: varName -> typeName (for receiverType inference)
1671
+ const localVarTypeQualifiers = new Map(); // qualifier provenance for new ns.Type()
1533
1672
  // Names whose type came from a DECLARED annotation (TS `x: Foo` / typed
1534
1673
  // params). The compiler enforces assignability for these, so reassignment
1535
1674
  // never stales them; inferred types (literal/new) DO stale and are
@@ -1538,6 +1677,7 @@ function findCallsInCode(code, parser) {
1538
1677
  const declaredTypeVarsStack = [];
1539
1678
  const moduleAliases = new Set(); // Names bound to MODULES (import * as ns / const pkg = require(...))
1540
1679
  const localVarTypesStack = []; // Stack for function-scoped save/restore of localVarTypes
1680
+ const localVarTypeQualifiersStack = [];
1541
1681
 
1542
1682
  // Helper: extract first string-arg literal from a call_expression node.
1543
1683
  // Used by route extraction to capture path arg of fetch('/path'), app.get('/path', handler) etc.
@@ -1750,20 +1890,31 @@ function findCallsInCode(code, parser) {
1750
1890
  return best && best.target;
1751
1891
  };
1752
1892
 
1753
- // fix #203: does a declaration node declare `name` (incl. shallow destructuring)?
1893
+ const _patternDeclaresName = (pattern, name) => {
1894
+ if (!pattern) return false;
1895
+ if ((pattern.type === 'identifier' ||
1896
+ pattern.type === 'shorthand_property_identifier_pattern') &&
1897
+ pattern.text === name) return true;
1898
+ if (pattern.type === 'pair_pattern' || pattern.type === 'pair') {
1899
+ return _patternDeclaresName(pattern.childForFieldName('value'), name);
1900
+ }
1901
+ if (pattern.type === 'assignment_pattern') {
1902
+ return _patternDeclaresName(
1903
+ pattern.childForFieldName('left') || pattern.childForFieldName('pattern'), name);
1904
+ }
1905
+ for (let i = 0; i < pattern.namedChildCount; i++) {
1906
+ if (_patternDeclaresName(pattern.namedChild(i), name)) return true;
1907
+ }
1908
+ return false;
1909
+ };
1910
+
1911
+ // fix #203: does a declaration node declare `name` (including nested destructuring)?
1754
1912
  const _declaresName = (declNode, name) => {
1755
1913
  for (let i = 0; i < declNode.namedChildCount; i++) {
1756
1914
  const d = declNode.namedChild(i);
1757
1915
  if (d.type !== 'variable_declarator') continue;
1758
1916
  const nameNode = d.childForFieldName('name');
1759
- if (nameNode?.type === 'identifier' && nameNode.text === name) return true;
1760
- if (nameNode && (nameNode.type === 'object_pattern' || nameNode.type === 'array_pattern')) {
1761
- for (let j = 0; j < nameNode.namedChildCount; j++) {
1762
- const el = nameNode.namedChild(j);
1763
- if ((el.type === 'identifier' || el.type === 'shorthand_property_identifier_pattern') &&
1764
- el.text === name) return true;
1765
- }
1766
- }
1917
+ if (_patternDeclaresName(nameNode, name)) return true;
1767
1918
  }
1768
1919
  return false;
1769
1920
  };
@@ -1797,27 +1948,22 @@ function findCallsInCode(code, parser) {
1797
1948
  _declaresName(init, name)) return true;
1798
1949
  } else if (p.type === 'for_in_statement') {
1799
1950
  const left = p.childForFieldName('left');
1800
- if (left?.type === 'identifier' && left.text === name) return true;
1951
+ if (_patternDeclaresName(left, name)) return true;
1801
1952
  if (left && (left.type === 'lexical_declaration' || left.type === 'variable_declaration') &&
1802
1953
  _declaresName(left, name)) return true;
1803
1954
  } else if (p.type === 'catch_clause') {
1804
1955
  const param = p.childForFieldName('parameter');
1805
- if (param?.type === 'identifier' && param.text === name) return true;
1956
+ if (_patternDeclaresName(param, name)) return true;
1806
1957
  } else if (p.type === 'arrow_function' || p.type === 'function_expression' ||
1807
1958
  p.type === 'function_declaration' || p.type === 'function' ||
1808
1959
  p.type === 'method_definition' || p.type === 'generator_function' ||
1809
1960
  p.type === 'generator_function_declaration') {
1810
1961
  const params = p.childForFieldName('parameters') || p.childForFieldName('parameter');
1811
1962
  if (params) {
1812
- if (params.type === 'identifier' && params.text === name) return true; // x => ...
1963
+ if (_patternDeclaresName(params, name)) return true;
1813
1964
  for (let i = 0; i < params.namedChildCount; i++) {
1814
1965
  const prm = params.namedChild(i);
1815
- if (prm.type === 'identifier' && prm.text === name) return true;
1816
- if (prm.type === 'assignment_pattern' || prm.type === 'required_parameter' ||
1817
- prm.type === 'optional_parameter') {
1818
- const l = prm.childForFieldName('left') || prm.childForFieldName('pattern');
1819
- if (l?.type === 'identifier' && l.text === name) return true;
1820
- }
1966
+ if (_patternDeclaresName(prm, name)) return true;
1821
1967
  }
1822
1968
  }
1823
1969
  }
@@ -1825,6 +1971,21 @@ function findCallsInCode(code, parser) {
1825
1971
  return false;
1826
1972
  };
1827
1973
 
1974
+ const isConditionalReassignment = node => {
1975
+ for (let p = node.parent; p && !isFunctionNode(p); p = p.parent) {
1976
+ if (p.type === 'if_statement') {
1977
+ const condition = p.childForFieldName('condition');
1978
+ if (!condition || node.startIndex < condition.startIndex ||
1979
+ node.endIndex > condition.endIndex) return true;
1980
+ }
1981
+ if (p.type === 'switch_case' || p.type === 'ternary_expression' ||
1982
+ p.type === 'for_statement' || p.type === 'for_in_statement' ||
1983
+ p.type === 'while_statement' || p.type === 'do_statement' ||
1984
+ p.type === 'catch_clause') return true;
1985
+ }
1986
+ return false;
1987
+ };
1988
+
1828
1989
  traverseTree(tree.rootNode, (node) => {
1829
1990
  // Track module-alias bindings: `import * as ns from "./m"` binds ns to a
1830
1991
  // MODULE — method calls through it dispatch to module exports, never to
@@ -1843,6 +2004,7 @@ function findCallsInCode(code, parser) {
1843
2004
  });
1844
2005
  // Save localVarTypes so inner declarations don't leak to sibling functions
1845
2006
  localVarTypesStack.push(new Map(localVarTypes));
2007
+ localVarTypeQualifiersStack.push(new Map(localVarTypeQualifiers));
1846
2008
  declaredTypeVarsStack.push(new Set(declaredTypeVars));
1847
2009
  }
1848
2010
 
@@ -1896,6 +2058,10 @@ function findCallsInCode(code, parser) {
1896
2058
  const ctorName = jsConstructorTypeName(initNode.childForFieldName('constructor'));
1897
2059
  if (ctorName) {
1898
2060
  localVarTypes.set(nameNode.text, ctorName);
2061
+ const qualifier = jsConstructorTypeQualifier(
2062
+ initNode.childForFieldName('constructor'));
2063
+ if (qualifier) localVarTypeQualifiers.set(nameNode.text, qualifier);
2064
+ else localVarTypeQualifiers.delete(nameNode.text);
1899
2065
  }
1900
2066
  }
1901
2067
  // Track TypeScript type annotations: const x: Foo = ...
@@ -1941,15 +2107,21 @@ function findCallsInCode(code, parser) {
1941
2107
  if (right?.type === 'new_expression') {
1942
2108
  nonCallableNames.add(left.text);
1943
2109
  const ctorName = jsConstructorTypeName(right.childForFieldName('constructor'));
1944
- if (ctorName) {
2110
+ if (ctorName && !isConditionalReassignment(node)) {
1945
2111
  localVarTypes.set(left.text, ctorName);
2112
+ const qualifier = jsConstructorTypeQualifier(
2113
+ right.childForFieldName('constructor'));
2114
+ if (qualifier) localVarTypeQualifiers.set(left.text, qualifier);
2115
+ else localVarTypeQualifiers.delete(left.text);
1946
2116
  } else if (!declaredTypeVars.has(left.text)) {
1947
2117
  localVarTypes.delete(left.text);
2118
+ localVarTypeQualifiers.delete(left.text);
1948
2119
  }
1949
2120
  } else if (right && JS_LITERAL_ASSIGN_TYPES[right.type]) {
1950
2121
  // Literal reassignment re-types the variable (fix #262)
1951
2122
  if (!declaredTypeVars.has(left.text)) {
1952
2123
  localVarTypes.set(left.text, JS_LITERAL_ASSIGN_TYPES[right.type]);
2124
+ localVarTypeQualifiers.delete(left.text);
1953
2125
  }
1954
2126
  } else if (localVarTypes.has(left.text) && !declaredTypeVars.has(left.text)) {
1955
2127
  // Rebinding without a known type makes any previously
@@ -1957,6 +2129,7 @@ function findCallsInCode(code, parser) {
1957
2129
  // semantics (#218d). Annotation-declared types survive:
1958
2130
  // the TS compiler enforces assignability for those.
1959
2131
  localVarTypes.delete(left.text);
2132
+ localVarTypeQualifiers.delete(left.text);
1960
2133
  }
1961
2134
  }
1962
2135
  // Handler-registration references (fix #252, the #221 family's
@@ -2012,9 +2185,17 @@ function findCallsInCode(code, parser) {
2012
2185
 
2013
2186
  // Handle regular function calls: foo(), obj.foo(), foo.call()
2014
2187
  if (node.type === 'call_expression') {
2015
- const funcNode = node.childForFieldName('function');
2188
+ let funcNode = node.childForFieldName('function');
2016
2189
  if (!funcNode) return true;
2017
2190
 
2191
+ // tree-sitter-typescript represents `await obj.method<T>()` with
2192
+ // the await_expression inside the call's function field. Unwrap
2193
+ // it so generic awaited calls use the same AST call path as every
2194
+ // other method invocation.
2195
+ if (funcNode.type === 'await_expression' && funcNode.namedChildCount === 1) {
2196
+ funcNode = funcNode.namedChild(0);
2197
+ }
2198
+
2018
2199
  const enclosingFunction = getCurrentEnclosingFunction();
2019
2200
  let uncertain = false;
2020
2201
  // optional chaining implies possible non-call
@@ -2089,12 +2270,25 @@ function findCallsInCode(code, parser) {
2089
2270
  const innerProp = objNode.childForFieldName('property');
2090
2271
  const innerObj = objNode.childForFieldName('object');
2091
2272
  if (innerProp) {
2273
+ const boundReceiver = innerObj?.type === 'identifier'
2274
+ ? innerObj.text : innerObj?.text;
2275
+ const boundReceiverType = innerObj?.type === 'identifier'
2276
+ ? localVarTypes.get(innerObj.text) : undefined;
2092
2277
  calls.push({
2093
2278
  name: innerProp.text,
2094
2279
  line: node.startPosition.row + 1,
2095
2280
  isMethod: true,
2096
2281
  boundCall: true,
2097
- receiver: innerObj?.text,
2282
+ receiver: boundReceiver,
2283
+ ...(boundReceiverType && { receiverType: boundReceiverType }),
2284
+ ...(innerObj?.type === 'identifier' &&
2285
+ localVarTypeQualifiers.has(innerObj.text) && {
2286
+ receiverTypeQualifier: localVarTypeQualifiers.get(innerObj.text),
2287
+ }),
2288
+ ...(innerObj?.type === 'identifier' &&
2289
+ isShadowedByLocal(innerObj, innerObj.text) && {
2290
+ receiverLocalBinding: true,
2291
+ }),
2098
2292
  enclosingFunction,
2099
2293
  uncertain
2100
2294
  });
@@ -2115,13 +2309,15 @@ function findCallsInCode(code, parser) {
2115
2309
  // field's DECLARED type annotation. `this`-rooted hops
2116
2310
  // resolve their root type query-side (the enclosing
2117
2311
  // class); identifier roots type from local annotations.
2118
- let receiverRoot, receiverFieldName, receiverRootType;
2312
+ let receiverRoot, receiverFieldName, receiverRootType, receiverBindingNode;
2313
+ if (receiver && objNode?.type === 'identifier') receiverBindingNode = objNode;
2119
2314
  if (!receiver && objNode && objNode.type === 'member_expression') {
2120
2315
  const rootNode = objNode.childForFieldName('object');
2121
2316
  const fldNode = objNode.childForFieldName('property');
2122
2317
  if (fldNode && rootNode &&
2123
2318
  (rootNode.type === 'identifier' || rootNode.type === 'this')) {
2124
2319
  receiverRoot = rootNode.text;
2320
+ receiverBindingNode = rootNode.type === 'identifier' ? rootNode : undefined;
2125
2321
  receiverFieldName = fldNode.text;
2126
2322
  if (rootNode.type === 'identifier') {
2127
2323
  receiverRootType = localVarTypes.get(rootNode.text);
@@ -2164,9 +2360,19 @@ function findCallsInCode(code, parser) {
2164
2360
  }
2165
2361
  // Literal receivers carry their builtin type: [].map() can
2166
2362
  // never be a project class method
2363
+ // A freshly constructed receiver has an exact runtime
2364
+ // type as well: new Service().start(). Recording it here
2365
+ // avoids treating the call as an untyped method dispatch.
2366
+ const constructedReceiverType = objNode?.type === 'new_expression'
2367
+ ? jsConstructorTypeName(objNode.childForFieldName('constructor'))
2368
+ : undefined;
2369
+ const constructedReceiverQualifier = objNode?.type === 'new_expression'
2370
+ ? jsConstructorTypeQualifier(objNode.childForFieldName('constructor'))
2371
+ : undefined;
2167
2372
  const receiverType = receiver
2168
2373
  ? localVarTypes.get(receiver)
2169
- : (objNode ? JS_LITERAL_RECEIVER_TYPES[objNode.type] : undefined);
2374
+ : (constructedReceiverType ||
2375
+ (objNode ? JS_LITERAL_RECEIVER_TYPES[objNode.type] : undefined));
2170
2376
  // Module receiver (ns.helper()) — unless locally shadowed
2171
2377
  // by a typed instance binding
2172
2378
  const receiverIsModule = !!receiver && moduleAliases.has(receiver) &&
@@ -2184,7 +2390,15 @@ function findCallsInCode(code, parser) {
2184
2390
  isMethod: true,
2185
2391
  receiver,
2186
2392
  ...(receiverType && { receiverType }),
2393
+ ...((constructedReceiverQualifier ||
2394
+ (receiver && localVarTypeQualifiers.get(receiver))) && {
2395
+ receiverTypeQualifier: constructedReceiverQualifier ||
2396
+ localVarTypeQualifiers.get(receiver),
2397
+ }),
2187
2398
  ...(receiverIsModule && { receiverIsModule: true }),
2399
+ ...(receiverBindingNode &&
2400
+ isShadowedByLocal(receiverBindingNode, receiverBindingNode.text) &&
2401
+ { receiverLocalBinding: true }),
2188
2402
  ...(receiverFieldName && { receiverRoot, receiverField: receiverFieldName }),
2189
2403
  ...(receiverFieldName && receiverRootType && { receiverRootType }),
2190
2404
  ...(receiverCall && { receiverCall }),
@@ -2315,6 +2529,7 @@ function findCallsInCode(code, parser) {
2315
2529
  line: node.startPosition.row + 1,
2316
2530
  isMethod: false,
2317
2531
  isConstructor: true,
2532
+ ...(isShadowedByLocal(ctorNode, ctorNode.text) && { localShadow: true }),
2318
2533
  enclosingFunction
2319
2534
  });
2320
2535
  } else if (ctorNode.type === 'member_expression') {
@@ -2426,6 +2641,11 @@ function findCallsInCode(code, parser) {
2426
2641
  localVarTypes.clear();
2427
2642
  for (const [k, v] of saved) localVarTypes.set(k, v);
2428
2643
  }
2644
+ const savedQualifiers = localVarTypeQualifiersStack.pop();
2645
+ if (savedQualifiers) {
2646
+ localVarTypeQualifiers.clear();
2647
+ for (const [k, v] of savedQualifiers) localVarTypeQualifiers.set(k, v);
2648
+ }
2429
2649
  const savedDeclared = declaredTypeVarsStack.pop();
2430
2650
  if (savedDeclared) {
2431
2651
  declaredTypeVars.clear();
@@ -2733,11 +2953,17 @@ function findImportsInCode(code, parser) {
2733
2953
 
2734
2954
  // Check parent for variable name
2735
2955
  let parent = node.parent;
2956
+ let defaultLike = false;
2736
2957
  if (parent && parent.type === 'variable_declarator') {
2737
2958
  const nameNode = parent.childForFieldName('name');
2738
2959
  if (nameNode) {
2739
2960
  if (nameNode.type === 'identifier') {
2740
2961
  names.push(nameNode.text);
2962
+ // `const app = require('./app')` binds the
2963
+ // value assigned to `module.exports`, not a
2964
+ // named property called `app`. Preserve that
2965
+ // distinction for exact import ownership.
2966
+ defaultLike = true;
2741
2967
  } else if (nameNode.type === 'object_pattern') {
2742
2968
  // Destructuring: const { a, b } = require('x')
2743
2969
  for (let i = 0; i < nameNode.namedChildCount; i++) {
@@ -2762,6 +2988,7 @@ function findImportsInCode(code, parser) {
2762
2988
 
2763
2989
  if (modulePath) {
2764
2990
  imports.push({ module: modulePath, names, type: 'require', line, dynamic,
2991
+ ...(defaultLike && { defaultLike: true }),
2765
2992
  // Per-import rename pairing (fix #269): the flat
2766
2993
  // importAliases list loses WHICH module a renamed
2767
2994
  // name came from — `{ validate: validateSchema }`
@@ -3046,12 +3273,14 @@ function findUsagesInCode(code, name, parser, tree) {
3046
3273
 
3047
3274
  visitNameNodes(tree, code, name, (node) => {
3048
3275
  // Look for identifier, property_identifier (method names in obj.method() calls),
3276
+ // private_property_identifier (#method definitions and calls),
3049
3277
  // type_identifier (TypeScript type annotations), shorthand_property_identifier_pattern
3050
3278
  // (destructured names in `const { name } = require(...)`), and
3051
3279
  // shorthand_property_identifier (value-position shorthand — CJS export
3052
3280
  // objects `module.exports = { helper }` and option objects `f({ helper })`
3053
3281
  // reference the symbol but produced no usage record at all, fix #241)
3054
3282
  const isIdentifier = node.type === 'identifier' || node.type === 'property_identifier' ||
3283
+ node.type === 'private_property_identifier' ||
3055
3284
  node.type === 'type_identifier' || node.type === 'shorthand_property_identifier_pattern' ||
3056
3285
  node.type === 'shorthand_property_identifier';
3057
3286
  if (!isIdentifier || node.text !== name) {
@@ -3155,17 +3384,11 @@ function findUsagesInCode(code, name, parser, tree) {
3155
3384
  // Property access (method call): a.name() - the name after dot
3156
3385
  else if (parent.type === 'member_expression' &&
3157
3386
  sameNode(parent.childForFieldName('property'), node)) {
3158
- // Skip built-in objects and common module names (JSON.parse, path.parse, etc.)
3387
+ // Preserve the receiver and let the project-aware usage layer
3388
+ // decide ownership. A spelling such as `util` or `path` can be
3389
+ // either a standard module or a local project namespace, which
3390
+ // cannot be decided correctly from this file's AST alone.
3159
3391
  const object = parent.childForFieldName('object');
3160
- const builtins = [
3161
- // JS built-in objects
3162
- 'JSON', 'Math', 'console', 'Object', 'Array', 'String', 'Number', 'Date', 'RegExp', 'Promise', 'Reflect', 'Proxy', 'Map', 'Set', 'WeakMap', 'WeakSet', 'Symbol', 'Intl', 'WebAssembly', 'Atomics', 'SharedArrayBuffer', 'ArrayBuffer', 'DataView', 'Int8Array', 'Uint8Array', 'Uint8ClampedArray', 'Int16Array', 'Uint16Array', 'Int32Array', 'Uint32Array', 'Float32Array', 'Float64Array', 'BigInt64Array', 'BigUint64Array', 'Error', 'EvalError', 'RangeError', 'ReferenceError', 'SyntaxError', 'TypeError', 'URIError', 'URL', 'URLSearchParams',
3163
- // Node.js core modules
3164
- 'path', 'fs', 'os', 'http', 'https', 'net', 'dgram', 'dns', 'tls', 'crypto', 'zlib', 'stream', 'util', 'events', 'buffer', 'child_process', 'cluster', 'readline', 'repl', 'vm', 'assert', 'querystring', 'url', 'punycode', 'string_decoder', 'timers', 'tty', 'v8', 'perf_hooks', 'worker_threads', 'inspector', 'trace_events', 'async_hooks', 'process'
3165
- ];
3166
- if (object && object.type === 'identifier' && builtins.includes(object.text)) {
3167
- return true; // Skip built-in method calls
3168
- }
3169
3392
  // Check if this is a method call
3170
3393
  const grandparent = parent.parent;
3171
3394
  if (grandparent && grandparent.type === 'call_expression') {