ucn 4.2.2 → 5.0.1

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.
Files changed (72) hide show
  1. package/.claude/skills/ucn/SKILL.md +89 -77
  2. package/.claude/skills/ucn/references/commands.md +62 -68
  3. package/.claude/skills/ucn/references/trust-contract.md +31 -6
  4. package/README.md +445 -300
  5. package/assets/demo.svg +31 -0
  6. package/cli/index.js +430 -1385
  7. package/core/account.js +144 -34
  8. package/core/analysis.js +182 -72
  9. package/core/ast-analysis.js +279 -0
  10. package/core/bridge.js +205 -24
  11. package/core/brief.js +27 -58
  12. package/core/build-worker.js +21 -131
  13. package/core/cache.js +533 -11
  14. package/core/callers.js +5533 -494
  15. package/core/check.js +13 -4
  16. package/core/command-contracts.js +402 -0
  17. package/core/compilation-database.js +276 -0
  18. package/core/confidence.js +4 -1
  19. package/core/deadcode.js +421 -20
  20. package/core/discovery.js +359 -46
  21. package/core/entrypoints.js +204 -42
  22. package/core/execute.js +887 -81
  23. package/core/graph-build.js +162 -7
  24. package/core/graph.js +53 -77
  25. package/core/imports.js +65 -6
  26. package/core/index-ir.js +138 -0
  27. package/core/ir.js +195 -0
  28. package/core/output/analysis.js +216 -22
  29. package/core/output/brief.js +23 -0
  30. package/core/output/check.js +4 -0
  31. package/core/output/doctor.js +37 -6
  32. package/core/output/endpoints.js +5 -2
  33. package/core/output/extraction.js +24 -12
  34. package/core/output/find.js +141 -36
  35. package/core/output/graph.js +11 -5
  36. package/core/output/public.js +462 -0
  37. package/core/output/refactoring.js +42 -10
  38. package/core/output/reporting.js +97 -20
  39. package/core/output/search.js +24 -16
  40. package/core/output/shared.js +22 -1
  41. package/core/output/tracing.js +30 -15
  42. package/core/output-budget.js +295 -0
  43. package/core/output.js +1 -0
  44. package/core/parallel-build.js +44 -11
  45. package/core/parser.js +3 -3
  46. package/core/project.js +384 -177
  47. package/core/public-command.js +47 -0
  48. package/core/registry.js +247 -117
  49. package/core/reporting.js +312 -290
  50. package/core/search.js +371 -116
  51. package/core/semantic-provider.js +110 -0
  52. package/core/stacktrace.js +25 -0
  53. package/core/tracing.js +101 -51
  54. package/core/trust-matrix.js +19 -40
  55. package/core/verify.js +534 -37
  56. package/languages/adapter.js +218 -0
  57. package/languages/c-family.js +2791 -0
  58. package/languages/c.js +3 -0
  59. package/languages/cpp.js +3 -0
  60. package/languages/csharp.js +1402 -0
  61. package/languages/go.js +60 -21
  62. package/languages/html.js +2 -2
  63. package/languages/index.js +85 -7
  64. package/languages/java.js +428 -16
  65. package/languages/javascript.js +452 -49
  66. package/languages/python.js +1041 -32
  67. package/languages/rust.js +1415 -152
  68. package/languages/utils.js +40 -3
  69. package/mcp/server.js +254 -636
  70. package/package.json +41 -24
  71. package/eslint.config.js +0 -43
  72. package/jsconfig.json +0 -10
@@ -379,6 +379,62 @@ function extractDecoratorsWithArgs(node) {
379
379
 
380
380
  // --- Single-pass helpers: extracted from find* callbacks ---
381
381
 
382
+ const FUNCTION_SCOPE_NODES = new Set([
383
+ 'function_declaration', 'generator_function_declaration',
384
+ 'function_expression', 'generator_function', 'arrow_function',
385
+ 'method_definition',
386
+ ]);
387
+
388
+ function lexicalOwnerRange(node) {
389
+ for (let parent = node?.parent; parent; parent = parent.parent) {
390
+ if (!FUNCTION_SCOPE_NODES.has(parent.type)) continue;
391
+ const body = parent.childForFieldName('body') || parent;
392
+ return {
393
+ lexicalScopeStartLine: body.startPosition.row + 1,
394
+ lexicalScopeEndLine: body.endPosition.row + 1,
395
+ };
396
+ }
397
+ return {};
398
+ }
399
+
400
+ /**
401
+ * Concrete runtime value returned by a function when every reachable return
402
+ * constructs the same class. Nested functions/classes are separate scopes.
403
+ * This complements (never guesses beyond) a declared interface return type.
404
+ */
405
+ function extractReturnedConcreteType(node) {
406
+ const body = node.childForFieldName('body');
407
+ if (!body) return null;
408
+ const types = [];
409
+ let incomplete = false;
410
+ const stack = [body];
411
+ while (stack.length > 0) {
412
+ const current = stack.pop();
413
+ if (current !== body && FUNCTION_SCOPE_NODES.has(current.type)) continue;
414
+ if (current.type === 'class_declaration' || current.type === 'class') continue;
415
+ if (current.type === 'return_statement') {
416
+ const value = current.namedChild(0);
417
+ if (value?.type !== 'new_expression') {
418
+ incomplete = true;
419
+ continue;
420
+ }
421
+ const constructor = value.childForFieldName('constructor');
422
+ if (!constructor ||
423
+ !['identifier', 'member_expression'].includes(constructor.type)) {
424
+ incomplete = true;
425
+ continue;
426
+ }
427
+ types.push(constructor.text.split('.').pop());
428
+ continue;
429
+ }
430
+ for (let i = current.namedChildCount - 1; i >= 0; i--) {
431
+ stack.push(current.namedChild(i));
432
+ }
433
+ }
434
+ return !incomplete && types.length > 0 && new Set(types).size === 1
435
+ ? types[0] : null;
436
+ }
437
+
382
438
  /**
383
439
  * Process a node for function extraction (single-pass helper)
384
440
  * Returns true if node was matched, false otherwise
@@ -397,6 +453,7 @@ function _processFunction(node, functions, processedRanges, lines) {
397
453
  if (nameNode) {
398
454
  const { startLine, endLine, indent } = nodeToLocation(node, lines);
399
455
  const returnType = extractReturnType(node);
456
+ const returnedConcreteType = extractReturnedConcreteType(node);
400
457
  const generics = extractGenerics(node);
401
458
  const docstring = extractJSDocstring(lines, startLine);
402
459
  const isGen = isGenerator(node);
@@ -421,6 +478,55 @@ function _processFunction(node, functions, processedRanges, lines) {
421
478
  isGenerator: isGen,
422
479
  isAsync,
423
480
  modifiers,
481
+ ...lexicalOwnerRange(node),
482
+ ...typeAnno,
483
+ ...(returnedConcreteType && { returnedConcreteType }),
484
+ ...(generics && { generics }),
485
+ ...(docstring && { docstring })
486
+ });
487
+ }
488
+ return true;
489
+ }
490
+
491
+ // Named function expressions used as callbacks have a real lexical
492
+ // definition even when no variable owns them: `test('x', function run()
493
+ // {})`. Property/variable assignments are handled by their binding
494
+ // branches below; indexing the expression name there as a second symbol
495
+ // would manufacture a duplicate public definition.
496
+ if (node.type === 'function_expression' || node.type === 'generator_function') {
497
+ const parent = node.parent;
498
+ const isBoundValue = (parent?.type === 'variable_declarator' &&
499
+ sameNode(parent.childForFieldName('value'), node)) ||
500
+ (parent?.type === 'assignment_expression' &&
501
+ sameNode(parent.childForFieldName('right'), node)) ||
502
+ (parent?.type === 'pair' && sameNode(parent.childForFieldName('value'), node));
503
+ const nameNode = node.childForFieldName('name');
504
+ if (!isBoundValue && nameNode && !processedRanges.has(rangeKey)) {
505
+ processedRanges.add(rangeKey);
506
+ const paramsNode = node.childForFieldName('parameters');
507
+ const { startLine, endLine, indent } = nodeToLocation(node, lines);
508
+ const returnType = extractReturnType(node);
509
+ const generics = extractGenerics(node);
510
+ const paramsStructured = parseStructuredParams(paramsNode, 'javascript');
511
+ const typeAnno = buildTypeAnnotations(paramsStructured, returnType, lines, startLine, true);
512
+ const docstring = extractJSDocstring(lines, startLine);
513
+ functions.push({
514
+ name: nameNode.text,
515
+ params: extractParams(paramsNode),
516
+ paramsStructured,
517
+ startLine,
518
+ endLine,
519
+ indent,
520
+ isArrow: false,
521
+ isGenerator: isGenerator(node),
522
+ isAsync: node.text.trimStart().startsWith('async '),
523
+ modifiers: [],
524
+ // ECMA-262: a FunctionExpression's BindingIdentifier is in
525
+ // scope only within its own body — the name creates no
526
+ // file-level binding (never enters the bindings table) and
527
+ // the expression is consumed where it appears (argument /
528
+ // value position), so deadcode never audits it.
529
+ bodyScopedName: true,
424
530
  ...typeAnno,
425
531
  ...(generics && { generics }),
426
532
  ...(docstring && { docstring })
@@ -481,7 +587,8 @@ function _processFunction(node, functions, processedRanges, lines) {
481
587
 
482
588
  if (isArrow || isFnExpr) {
483
589
  processedRanges.add(rangeKey);
484
- const paramsNode = valueNode.childForFieldName('parameters');
590
+ const paramsNode = valueNode.childForFieldName('parameters') ||
591
+ valueNode.childForFieldName('parameter');
485
592
  const { startLine, endLine, indent } = nodeToLocation(node, lines);
486
593
  const returnType = extractReturnType(valueNode);
487
594
  const generics = extractGenerics(valueNode);
@@ -613,7 +720,10 @@ function _processFunction(node, functions, processedRanges, lines) {
613
720
  }
614
721
  parent = parent.parent;
615
722
  }
616
- if (!isTopLevel) return true;
723
+ // A nested property assignment still defines that object's
724
+ // callable member (`reply.send = () => {}`). Only a nested bare
725
+ // assignment lacks a new symbol binding and stays excluded.
726
+ if (!isTopLevel && leftNode?.type !== 'member_expression') return true;
617
727
  }
618
728
 
619
729
  const rightNode = node.childForFieldName('right');
@@ -624,7 +734,16 @@ function _processFunction(node, functions, processedRanges, lines) {
624
734
  rightNode.type === 'generator_function';
625
735
 
626
736
  if (isArrow || isFnExpr) {
627
- const name = getAssignmentName(leftNode);
737
+ const isCommonJsDefault = leftNode.text === 'module.exports';
738
+ const expressionName = isFnExpr
739
+ ? rightNode.childForFieldName('name')?.text : null;
740
+ // `module.exports = function transformer(){}` exports the
741
+ // function expression, not a symbol called "exports". Keep
742
+ // its authored name when present; anonymous defaults use the
743
+ // same `default` identity exposed by the API surface.
744
+ const name = isCommonJsDefault
745
+ ? (expressionName || 'default')
746
+ : getAssignmentName(leftNode);
628
747
  if (name) {
629
748
  processedRanges.add(rangeKey);
630
749
  const paramsNode = rightNode.childForFieldName('parameters');
@@ -645,7 +764,7 @@ function _processFunction(node, functions, processedRanges, lines) {
645
764
  indent,
646
765
  isArrow,
647
766
  isGenerator: isGen,
648
- modifiers: [],
767
+ modifiers: isCommonJsDefault ? ['export'] : [],
649
768
  // A property-assignment def (Reply.prototype.serialize
650
769
  // = function, exports.h = () => ...) creates NO
651
770
  // lexical name — a bare call in the file can never
@@ -812,18 +931,26 @@ function _processClass(node, classes, processedRanges, lines) {
812
931
  if (nameNode) {
813
932
  const { startLine, endLine } = nodeToLocation(node, lines);
814
933
  const docstring = extractJSDocstring(lines, startLine);
934
+ const valueNode = node.childForFieldName('value');
815
935
  // `type ZodTypeAny = ZodType<any, any, any>;` — the alias IS the
816
936
  // aliased type. Record the base name so receivers annotated with
817
937
  // the alias validate against the base type's methods (fix #208,
818
938
  // TS parity with Rust/Go).
819
- const aliasOf = aliasBaseTypeName(node.childForFieldName('value'));
939
+ const aliasOf = aliasBaseTypeName(valueNode);
940
+ // Object type aliases are structural record declarations, not
941
+ // opaque labels. Index their declared fields just like interface
942
+ // fields so a compiler-typed hop such as
943
+ // `node: Node; node._source.unsubscribe()` can resolve Node's
944
+ // `_source: Signal` contract without name guessing.
945
+ const members = valueNode?.type === 'object_type'
946
+ ? extractTypeMembers(valueNode, lines) : [];
820
947
 
821
948
  classes.push({
822
949
  name: nameNode.text,
823
950
  startLine,
824
951
  endLine,
825
952
  type: 'type',
826
- members: [],
953
+ members,
827
954
  ...(aliasOf && { aliasOf }),
828
955
  ...(docstring && { docstring })
829
956
  });
@@ -991,10 +1118,13 @@ function extractInterfaceExtends(interfaceNode) {
991
1118
  * Extract interface members (method signatures, property signatures)
992
1119
  */
993
1120
  function extractInterfaceMembers(interfaceNode, code) {
994
- const members = [];
995
1121
  const bodyNode = interfaceNode.childForFieldName('body');
996
- if (!bodyNode) return members;
1122
+ if (!bodyNode) return [];
1123
+ return extractTypeMembers(bodyNode, code);
1124
+ }
997
1125
 
1126
+ function extractTypeMembers(bodyNode, code) {
1127
+ const members = [];
998
1128
  for (let i = 0; i < bodyNode.namedChildCount; i++) {
999
1129
  const child = bodyNode.namedChild(i);
1000
1130
 
@@ -1388,6 +1518,87 @@ function parse(code, parser) {
1388
1518
  return true; // always continue, never skip subtrees
1389
1519
  });
1390
1520
 
1521
+ // Some valid overload-heavy TypeScript files exceed the grammar's error
1522
+ // recovery budget. tree-sitter then returns a whole-file ERROR root and
1523
+ // flattens later declarations into unrelated type nodes without throwing.
1524
+ // Recover from AST tokens, not source patterns: top-level declaration
1525
+ // tokens define bounded fragments which are reparsed by the same grammar.
1526
+ // This keeps the AST-only contract while preventing a valid declaration
1527
+ // near the end of one difficult type file from disappearing silently.
1528
+ if (tree.rootNode.hasError) {
1529
+ const declarationTokens = [];
1530
+ const startsDeclaration = new Set([
1531
+ 'export', 'declare', 'async', 'function', 'class', 'abstract',
1532
+ 'interface', 'type', 'enum', 'namespace', 'module',
1533
+ 'const', 'let', 'var'
1534
+ ]);
1535
+ const stack = [tree.rootNode];
1536
+ while (stack.length > 0) {
1537
+ const node = stack.pop();
1538
+ if (node.childCount === 0) {
1539
+ // In severe recovery, a keyword itself can be downgraded to
1540
+ // an identifier token. Its AST position/text still supplies
1541
+ // a safe declaration boundary; semantic extraction remains
1542
+ // entirely delegated to the reparsed fragment.
1543
+ const recoveredKeyword = node.type === 'identifier' &&
1544
+ startsDeclaration.has(node.text);
1545
+ if (node.startPosition.column === 0 &&
1546
+ (startsDeclaration.has(node.type) || recoveredKeyword)) {
1547
+ declarationTokens.push(node);
1548
+ }
1549
+ continue;
1550
+ }
1551
+ const children = node.children;
1552
+ for (let i = children.length - 1; i >= 0; i--) stack.push(children[i]);
1553
+ }
1554
+ declarationTokens.sort((a, b) => a.startIndex - b.startIndex);
1555
+
1556
+ const recoveredFunctions = [], recoveredClasses = [], recoveredState = [];
1557
+ for (let i = 0; i < declarationTokens.length; i++) {
1558
+ const token = declarationTokens[i];
1559
+ const next = declarationTokens[i + 1];
1560
+ if (next && next.startIndex === token.startIndex) continue;
1561
+ const fragment = code.slice(token.startIndex, next?.startIndex ?? code.length);
1562
+ if (!fragment.trim()) continue;
1563
+ const fragmentTree = parseTree(parser, fragment);
1564
+ const fragmentLines = fragment.split('\n');
1565
+ const ff = [], fc = [], fs = [];
1566
+ const pf = new Set(), pc = new Set();
1567
+ traverseTreeCached(fragmentTree.rootNode, (node) => {
1568
+ _processFunction(node, ff, pf, fragmentLines);
1569
+ _processClass(node, fc, pc, fragmentLines);
1570
+ _processState(node, fs, fragmentLines);
1571
+ return true;
1572
+ });
1573
+ const lineOffset = token.startPosition.row;
1574
+ const shiftLines = (value) => {
1575
+ if (!value || typeof value !== 'object') return;
1576
+ if (Array.isArray(value)) {
1577
+ for (const item of value) shiftLines(item);
1578
+ return;
1579
+ }
1580
+ for (const [key, child] of Object.entries(value)) {
1581
+ if (Number.isInteger(child) && /Line$/.test(key)) value[key] = child + lineOffset;
1582
+ else if (child && typeof child === 'object') shiftLines(child);
1583
+ }
1584
+ };
1585
+ for (const item of ff) { shiftLines(item); recoveredFunctions.push(item); }
1586
+ for (const item of fc) { shiftLines(item); recoveredClasses.push(item); }
1587
+ for (const item of fs) { shiftLines(item); recoveredState.push(item); }
1588
+ }
1589
+
1590
+ const mergeUnique = (target, additions, kind) => {
1591
+ const seen = new Set(target.map(item => `${kind}\0${item.name}\0${item.startLine}`));
1592
+ for (const item of additions) {
1593
+ const key = `${kind}\0${item.name}\0${item.startLine}`;
1594
+ if (!seen.has(key)) { seen.add(key); target.push(item); }
1595
+ }
1596
+ };
1597
+ mergeUnique(functions, recoveredFunctions, 'function');
1598
+ mergeUnique(classes, recoveredClasses, 'class');
1599
+ mergeUnique(stateObjects, recoveredState, 'state');
1600
+ }
1601
+
1391
1602
  functions.sort((a, b) => a.startLine - b.startLine);
1392
1603
  classes.sort((a, b) => a.startLine - b.startLine);
1393
1604
  stateObjects.sort((a, b) => a.startLine - b.startLine);
@@ -1519,9 +1730,38 @@ function jsConstructorTypeName(ctorNode) {
1519
1730
  return undefined;
1520
1731
  }
1521
1732
 
1733
+ function jsConstructorTypeQualifier(ctorNode) {
1734
+ if (ctorNode?.type !== 'member_expression') return undefined;
1735
+ let root = ctorNode.childForFieldName('object');
1736
+ while (root?.type === 'member_expression') root = root.childForFieldName('object');
1737
+ return root?.type === 'identifier' ? root.text : undefined;
1738
+ }
1739
+
1740
+ // CommonJS permits direct namespace calls without a local alias:
1741
+ // `require('./output').formatContextJson(...)`. Preserve the literal module
1742
+ // specifier on the outer call so the index can apply the same export-ownership
1743
+ // rules as `const output = require('./output'); output.formatContextJson()`.
1744
+ function jsLiteralRequireModule(node) {
1745
+ if (node?.type !== 'call_expression') return undefined;
1746
+ const fn = node.childForFieldName('function');
1747
+ if (fn?.type !== 'identifier' || fn.text !== 'require') return undefined;
1748
+ const args = node.childForFieldName('arguments');
1749
+ if (!args || args.namedChildCount !== 1) return undefined;
1750
+ const first = args.namedChild(0);
1751
+ return first?.type === 'string' ? first.text.slice(1, -1) : undefined;
1752
+ }
1753
+
1522
1754
  function findCallsInCode(code, parser) {
1523
1755
  const tree = parseTree(parser, code);
1524
1756
  const calls = [];
1757
+ const assignedMembers = new Set();
1758
+ traverseTreeCached(tree.rootNode, node => {
1759
+ if (node.type !== 'assignment_expression' &&
1760
+ node.type !== 'augmented_assignment_expression') return true;
1761
+ const left = node.childForFieldName('left');
1762
+ if (left?.type === 'member_expression') assignedMembers.add(left.text);
1763
+ return true;
1764
+ });
1525
1765
  const functionStack = []; // Stack of { name, startLine, endLine }
1526
1766
  // Local aliases with lexical ownership. A flat aliasName→target map leaks
1527
1767
  // block locals into the rest of a module (`let effect = batchedEffect`
@@ -1530,6 +1770,7 @@ function findCallsInCode(code, parser) {
1530
1770
  const aliases = new Map(); // aliasName -> [{ target, declarationIndex, scopeStart, scopeEnd }]
1531
1771
  const nonCallableNames = new Set(); // Track names assigned non-callable values
1532
1772
  const localVarTypes = new Map(); // Track local variable types: varName -> typeName (for receiverType inference)
1773
+ const localVarTypeQualifiers = new Map(); // qualifier provenance for new ns.Type()
1533
1774
  // Names whose type came from a DECLARED annotation (TS `x: Foo` / typed
1534
1775
  // params). The compiler enforces assignability for these, so reassignment
1535
1776
  // never stales them; inferred types (literal/new) DO stale and are
@@ -1538,6 +1779,7 @@ function findCallsInCode(code, parser) {
1538
1779
  const declaredTypeVarsStack = [];
1539
1780
  const moduleAliases = new Set(); // Names bound to MODULES (import * as ns / const pkg = require(...))
1540
1781
  const localVarTypesStack = []; // Stack for function-scoped save/restore of localVarTypes
1782
+ const localVarTypeQualifiersStack = [];
1541
1783
 
1542
1784
  // Helper: extract first string-arg literal from a call_expression node.
1543
1785
  // Used by route extraction to capture path arg of fetch('/path'), app.get('/path', handler) etc.
@@ -1714,7 +1956,10 @@ function findCallsInCode(code, parser) {
1714
1956
  // Helper to get current enclosing function
1715
1957
  const getCurrentEnclosingFunction = () => {
1716
1958
  return functionStack.length > 0
1717
- ? { ...functionStack[functionStack.length - 1] }
1959
+ ? {
1960
+ ...functionStack[functionStack.length - 1],
1961
+ scopeChain: functionStack.map(scope => scope.startLine),
1962
+ }
1718
1963
  : null;
1719
1964
  };
1720
1965
 
@@ -1750,20 +1995,31 @@ function findCallsInCode(code, parser) {
1750
1995
  return best && best.target;
1751
1996
  };
1752
1997
 
1753
- // fix #203: does a declaration node declare `name` (incl. shallow destructuring)?
1998
+ const _patternDeclaresName = (pattern, name) => {
1999
+ if (!pattern) return false;
2000
+ if ((pattern.type === 'identifier' ||
2001
+ pattern.type === 'shorthand_property_identifier_pattern') &&
2002
+ pattern.text === name) return true;
2003
+ if (pattern.type === 'pair_pattern' || pattern.type === 'pair') {
2004
+ return _patternDeclaresName(pattern.childForFieldName('value'), name);
2005
+ }
2006
+ if (pattern.type === 'assignment_pattern') {
2007
+ return _patternDeclaresName(
2008
+ pattern.childForFieldName('left') || pattern.childForFieldName('pattern'), name);
2009
+ }
2010
+ for (let i = 0; i < pattern.namedChildCount; i++) {
2011
+ if (_patternDeclaresName(pattern.namedChild(i), name)) return true;
2012
+ }
2013
+ return false;
2014
+ };
2015
+
2016
+ // fix #203: does a declaration node declare `name` (including nested destructuring)?
1754
2017
  const _declaresName = (declNode, name) => {
1755
2018
  for (let i = 0; i < declNode.namedChildCount; i++) {
1756
2019
  const d = declNode.namedChild(i);
1757
2020
  if (d.type !== 'variable_declarator') continue;
1758
2021
  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
- }
2022
+ if (_patternDeclaresName(nameNode, name)) return true;
1767
2023
  }
1768
2024
  return false;
1769
2025
  };
@@ -1797,27 +2053,22 @@ function findCallsInCode(code, parser) {
1797
2053
  _declaresName(init, name)) return true;
1798
2054
  } else if (p.type === 'for_in_statement') {
1799
2055
  const left = p.childForFieldName('left');
1800
- if (left?.type === 'identifier' && left.text === name) return true;
2056
+ if (_patternDeclaresName(left, name)) return true;
1801
2057
  if (left && (left.type === 'lexical_declaration' || left.type === 'variable_declaration') &&
1802
2058
  _declaresName(left, name)) return true;
1803
2059
  } else if (p.type === 'catch_clause') {
1804
2060
  const param = p.childForFieldName('parameter');
1805
- if (param?.type === 'identifier' && param.text === name) return true;
2061
+ if (_patternDeclaresName(param, name)) return true;
1806
2062
  } else if (p.type === 'arrow_function' || p.type === 'function_expression' ||
1807
2063
  p.type === 'function_declaration' || p.type === 'function' ||
1808
2064
  p.type === 'method_definition' || p.type === 'generator_function' ||
1809
2065
  p.type === 'generator_function_declaration') {
1810
2066
  const params = p.childForFieldName('parameters') || p.childForFieldName('parameter');
1811
2067
  if (params) {
1812
- if (params.type === 'identifier' && params.text === name) return true; // x => ...
2068
+ if (_patternDeclaresName(params, name)) return true;
1813
2069
  for (let i = 0; i < params.namedChildCount; i++) {
1814
2070
  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
- }
2071
+ if (_patternDeclaresName(prm, name)) return true;
1821
2072
  }
1822
2073
  }
1823
2074
  }
@@ -1825,6 +2076,21 @@ function findCallsInCode(code, parser) {
1825
2076
  return false;
1826
2077
  };
1827
2078
 
2079
+ const isConditionalReassignment = node => {
2080
+ for (let p = node.parent; p && !isFunctionNode(p); p = p.parent) {
2081
+ if (p.type === 'if_statement') {
2082
+ const condition = p.childForFieldName('condition');
2083
+ if (!condition || node.startIndex < condition.startIndex ||
2084
+ node.endIndex > condition.endIndex) return true;
2085
+ }
2086
+ if (p.type === 'switch_case' || p.type === 'ternary_expression' ||
2087
+ p.type === 'for_statement' || p.type === 'for_in_statement' ||
2088
+ p.type === 'while_statement' || p.type === 'do_statement' ||
2089
+ p.type === 'catch_clause') return true;
2090
+ }
2091
+ return false;
2092
+ };
2093
+
1828
2094
  traverseTree(tree.rootNode, (node) => {
1829
2095
  // Track module-alias bindings: `import * as ns from "./m"` binds ns to a
1830
2096
  // MODULE — method calls through it dispatch to module exports, never to
@@ -1843,6 +2109,7 @@ function findCallsInCode(code, parser) {
1843
2109
  });
1844
2110
  // Save localVarTypes so inner declarations don't leak to sibling functions
1845
2111
  localVarTypesStack.push(new Map(localVarTypes));
2112
+ localVarTypeQualifiersStack.push(new Map(localVarTypeQualifiers));
1846
2113
  declaredTypeVarsStack.push(new Set(declaredTypeVars));
1847
2114
  }
1848
2115
 
@@ -1896,6 +2163,10 @@ function findCallsInCode(code, parser) {
1896
2163
  const ctorName = jsConstructorTypeName(initNode.childForFieldName('constructor'));
1897
2164
  if (ctorName) {
1898
2165
  localVarTypes.set(nameNode.text, ctorName);
2166
+ const qualifier = jsConstructorTypeQualifier(
2167
+ initNode.childForFieldName('constructor'));
2168
+ if (qualifier) localVarTypeQualifiers.set(nameNode.text, qualifier);
2169
+ else localVarTypeQualifiers.delete(nameNode.text);
1899
2170
  }
1900
2171
  }
1901
2172
  // Track TypeScript type annotations: const x: Foo = ...
@@ -1941,15 +2212,21 @@ function findCallsInCode(code, parser) {
1941
2212
  if (right?.type === 'new_expression') {
1942
2213
  nonCallableNames.add(left.text);
1943
2214
  const ctorName = jsConstructorTypeName(right.childForFieldName('constructor'));
1944
- if (ctorName) {
2215
+ if (ctorName && !isConditionalReassignment(node)) {
1945
2216
  localVarTypes.set(left.text, ctorName);
2217
+ const qualifier = jsConstructorTypeQualifier(
2218
+ right.childForFieldName('constructor'));
2219
+ if (qualifier) localVarTypeQualifiers.set(left.text, qualifier);
2220
+ else localVarTypeQualifiers.delete(left.text);
1946
2221
  } else if (!declaredTypeVars.has(left.text)) {
1947
2222
  localVarTypes.delete(left.text);
2223
+ localVarTypeQualifiers.delete(left.text);
1948
2224
  }
1949
2225
  } else if (right && JS_LITERAL_ASSIGN_TYPES[right.type]) {
1950
2226
  // Literal reassignment re-types the variable (fix #262)
1951
2227
  if (!declaredTypeVars.has(left.text)) {
1952
2228
  localVarTypes.set(left.text, JS_LITERAL_ASSIGN_TYPES[right.type]);
2229
+ localVarTypeQualifiers.delete(left.text);
1953
2230
  }
1954
2231
  } else if (localVarTypes.has(left.text) && !declaredTypeVars.has(left.text)) {
1955
2232
  // Rebinding without a known type makes any previously
@@ -1957,6 +2234,7 @@ function findCallsInCode(code, parser) {
1957
2234
  // semantics (#218d). Annotation-declared types survive:
1958
2235
  // the TS compiler enforces assignability for those.
1959
2236
  localVarTypes.delete(left.text);
2237
+ localVarTypeQualifiers.delete(left.text);
1960
2238
  }
1961
2239
  }
1962
2240
  // Handler-registration references (fix #252, the #221 family's
@@ -2012,9 +2290,17 @@ function findCallsInCode(code, parser) {
2012
2290
 
2013
2291
  // Handle regular function calls: foo(), obj.foo(), foo.call()
2014
2292
  if (node.type === 'call_expression') {
2015
- const funcNode = node.childForFieldName('function');
2293
+ let funcNode = node.childForFieldName('function');
2016
2294
  if (!funcNode) return true;
2017
2295
 
2296
+ // tree-sitter-typescript represents `await obj.method<T>()` with
2297
+ // the await_expression inside the call's function field. Unwrap
2298
+ // it so generic awaited calls use the same AST call path as every
2299
+ // other method invocation.
2300
+ if (funcNode.type === 'await_expression' && funcNode.namedChildCount === 1) {
2301
+ funcNode = funcNode.namedChild(0);
2302
+ }
2303
+
2018
2304
  const enclosingFunction = getCurrentEnclosingFunction();
2019
2305
  let uncertain = false;
2020
2306
  // optional chaining implies possible non-call
@@ -2038,6 +2324,8 @@ function findCallsInCode(code, parser) {
2038
2324
  ...(resolvedName && { resolvedName }),
2039
2325
  ...(resolvedNames && { resolvedNames }),
2040
2326
  line: node.startPosition.row + 1,
2327
+ callStart: node.startIndex,
2328
+ callEnd: node.endIndex,
2041
2329
  isMethod: false,
2042
2330
  ...(assignedTo && { assignedTo }),
2043
2331
  enclosingFunction,
@@ -2054,6 +2342,8 @@ function findCallsInCode(code, parser) {
2054
2342
  calls.push({
2055
2343
  name: 'constructor',
2056
2344
  line: node.startPosition.row + 1,
2345
+ callStart: node.startIndex,
2346
+ callEnd: node.endIndex,
2057
2347
  isMethod: true,
2058
2348
  receiver: 'super',
2059
2349
  argCount: node.childForFieldName('arguments')?.namedChildCount ?? 0,
@@ -2089,12 +2379,32 @@ function findCallsInCode(code, parser) {
2089
2379
  const innerProp = objNode.childForFieldName('property');
2090
2380
  const innerObj = objNode.childForFieldName('object');
2091
2381
  if (innerProp) {
2382
+ const prototypeOwner = innerObj?.type === 'member_expression' &&
2383
+ innerObj.childForFieldName('property')?.text === 'prototype' &&
2384
+ innerObj.childForFieldName('object')?.type === 'identifier'
2385
+ ? innerObj.childForFieldName('object').text
2386
+ : undefined;
2387
+ const boundReceiver = prototypeOwner ||
2388
+ (innerObj?.type === 'identifier'
2389
+ ? innerObj.text : innerObj?.text);
2390
+ const boundReceiverType = prototypeOwner ||
2391
+ (innerObj?.type === 'identifier'
2392
+ ? localVarTypes.get(innerObj.text) : undefined);
2092
2393
  calls.push({
2093
2394
  name: innerProp.text,
2094
2395
  line: node.startPosition.row + 1,
2095
2396
  isMethod: true,
2096
2397
  boundCall: true,
2097
- receiver: innerObj?.text,
2398
+ receiver: boundReceiver,
2399
+ ...(boundReceiverType && { receiverType: boundReceiverType }),
2400
+ ...(innerObj?.type === 'identifier' &&
2401
+ localVarTypeQualifiers.has(innerObj.text) && {
2402
+ receiverTypeQualifier: localVarTypeQualifiers.get(innerObj.text),
2403
+ }),
2404
+ ...(innerObj?.type === 'identifier' &&
2405
+ isShadowedByLocal(innerObj, innerObj.text) && {
2406
+ receiverLocalBinding: true,
2407
+ }),
2098
2408
  enclosingFunction,
2099
2409
  uncertain
2100
2410
  });
@@ -2115,17 +2425,26 @@ function findCallsInCode(code, parser) {
2115
2425
  // field's DECLARED type annotation. `this`-rooted hops
2116
2426
  // resolve their root type query-side (the enclosing
2117
2427
  // class); identifier roots type from local annotations.
2118
- let receiverRoot, receiverFieldName, receiverRootType;
2428
+ let receiverRoot, receiverFieldName, receiverRootType, receiverBindingNode;
2429
+ let receiverDeepPath = false;
2430
+ if (receiver && objNode?.type === 'identifier') receiverBindingNode = objNode;
2119
2431
  if (!receiver && objNode && objNode.type === 'member_expression') {
2120
2432
  const rootNode = objNode.childForFieldName('object');
2121
2433
  const fldNode = objNode.childForFieldName('property');
2122
2434
  if (fldNode && rootNode &&
2123
2435
  (rootNode.type === 'identifier' || rootNode.type === 'this')) {
2124
2436
  receiverRoot = rootNode.text;
2437
+ receiverBindingNode = rootNode.type === 'identifier' ? rootNode : undefined;
2125
2438
  receiverFieldName = fldNode.text;
2126
2439
  if (rootNode.type === 'identifier') {
2127
2440
  receiverRootType = localVarTypes.get(rootNode.text);
2128
2441
  }
2442
+ } else {
2443
+ // Preserve unresolved deeper member chains
2444
+ // (`client.req.query()`). Their terminal name
2445
+ // must not borrow a same-file method binding
2446
+ // while the root object's type is unknown.
2447
+ receiverDeepPath = true;
2129
2448
  }
2130
2449
  }
2131
2450
  // Chained receiver (fix #219): the receiver IS a call —
@@ -2133,6 +2452,7 @@ function findCallsInCode(code, parser) {
2133
2452
  // findCallers can type the receiver from its declared
2134
2453
  // return annotation (Promise<...> → Promise).
2135
2454
  let receiverCall, receiverCallIsMethod, receiverCallAwaited, receiverCallLine;
2455
+ let receiverCallStart, receiverCallEnd;
2136
2456
  {
2137
2457
  let recvNode = objNode;
2138
2458
  if (recvNode && recvNode.type === 'parenthesized_expression') {
@@ -2149,6 +2469,8 @@ function findCallsInCode(code, parser) {
2149
2469
  // Producer link (fix #258): plain-call
2150
2470
  // records carry the call node's start line
2151
2471
  receiverCallLine = recvNode.startPosition.row + 1;
2472
+ receiverCallStart = recvNode.startIndex;
2473
+ receiverCallEnd = recvNode.endIndex;
2152
2474
  } else if (prodFunc?.type === 'member_expression') {
2153
2475
  const prodProp = prodFunc.childForFieldName('property');
2154
2476
  if (prodProp) {
@@ -2157,6 +2479,8 @@ function findCallsInCode(code, parser) {
2157
2479
  // Method records report the property
2158
2480
  // node's own line
2159
2481
  receiverCallLine = prodProp.startPosition.row + 1;
2482
+ receiverCallStart = recvNode.startIndex;
2483
+ receiverCallEnd = recvNode.endIndex;
2160
2484
  }
2161
2485
  }
2162
2486
  }
@@ -2164,13 +2488,25 @@ function findCallsInCode(code, parser) {
2164
2488
  }
2165
2489
  // Literal receivers carry their builtin type: [].map() can
2166
2490
  // never be a project class method
2491
+ // A freshly constructed receiver has an exact runtime
2492
+ // type as well: new Service().start(). Recording it here
2493
+ // avoids treating the call as an untyped method dispatch.
2494
+ const constructedReceiverType = objNode?.type === 'new_expression'
2495
+ ? jsConstructorTypeName(objNode.childForFieldName('constructor'))
2496
+ : undefined;
2497
+ const constructedReceiverQualifier = objNode?.type === 'new_expression'
2498
+ ? jsConstructorTypeQualifier(objNode.childForFieldName('constructor'))
2499
+ : undefined;
2167
2500
  const receiverType = receiver
2168
2501
  ? localVarTypes.get(receiver)
2169
- : (objNode ? JS_LITERAL_RECEIVER_TYPES[objNode.type] : undefined);
2502
+ : (constructedReceiverType ||
2503
+ (objNode ? JS_LITERAL_RECEIVER_TYPES[objNode.type] : undefined));
2170
2504
  // Module receiver (ns.helper()) — unless locally shadowed
2171
2505
  // by a typed instance binding
2172
- const receiverIsModule = !!receiver && moduleAliases.has(receiver) &&
2173
- !localVarTypes.has(receiver);
2506
+ const receiverModuleSpecifier = jsLiteralRequireModule(objNode);
2507
+ const receiverIsModule = !!receiverModuleSpecifier ||
2508
+ (!!receiver && moduleAliases.has(receiver) &&
2509
+ !localVarTypes.has(receiver));
2174
2510
  const firstArg = getFirstStringArg(node);
2175
2511
  const argCount = getArgCount(node);
2176
2512
  const assignedTo = jsAssignmentTargetOf(node);
@@ -2181,16 +2517,33 @@ function findCallsInCode(code, parser) {
2181
2517
  // line — the account's ground set is keyed by the
2182
2518
  // name's line
2183
2519
  line: propNode.startPosition.row + 1,
2520
+ callStart: node.startIndex,
2521
+ callEnd: node.endIndex,
2184
2522
  isMethod: true,
2185
2523
  receiver,
2186
2524
  ...(receiverType && { receiverType }),
2525
+ ...((constructedReceiverQualifier ||
2526
+ (receiver && localVarTypeQualifiers.get(receiver))) && {
2527
+ receiverTypeQualifier: constructedReceiverQualifier ||
2528
+ localVarTypeQualifiers.get(receiver),
2529
+ }),
2187
2530
  ...(receiverIsModule && { receiverIsModule: true }),
2531
+ ...(receiverModuleSpecifier && { receiverModuleSpecifier }),
2532
+ ...(receiver && assignedMembers.has(`${receiver}.${propName}`) && {
2533
+ receiverMemberAssigned: true,
2534
+ }),
2535
+ ...(receiverBindingNode &&
2536
+ isShadowedByLocal(receiverBindingNode, receiverBindingNode.text) &&
2537
+ { receiverLocalBinding: true }),
2188
2538
  ...(receiverFieldName && { receiverRoot, receiverField: receiverFieldName }),
2189
2539
  ...(receiverFieldName && receiverRootType && { receiverRootType }),
2540
+ ...(receiverDeepPath && { receiverDeepPath: true }),
2190
2541
  ...(receiverCall && { receiverCall }),
2191
2542
  ...(receiverCallIsMethod && { receiverCallIsMethod: true }),
2192
2543
  ...(receiverCallAwaited && { receiverCallAwaited: true }),
2193
2544
  ...(receiverCallLine && { receiverCallLine }),
2545
+ ...(receiverCallStart != null && { receiverCallStart }),
2546
+ ...(receiverCallEnd != null && { receiverCallEnd }),
2194
2547
  ...(assignedTo && { assignedTo }),
2195
2548
  enclosingFunction,
2196
2549
  uncertain,
@@ -2315,6 +2668,7 @@ function findCallsInCode(code, parser) {
2315
2668
  line: node.startPosition.row + 1,
2316
2669
  isMethod: false,
2317
2670
  isConstructor: true,
2671
+ ...(isShadowedByLocal(ctorNode, ctorNode.text) && { localShadow: true }),
2318
2672
  enclosingFunction
2319
2673
  });
2320
2674
  } else if (ctorNode.type === 'member_expression') {
@@ -2426,6 +2780,11 @@ function findCallsInCode(code, parser) {
2426
2780
  localVarTypes.clear();
2427
2781
  for (const [k, v] of saved) localVarTypes.set(k, v);
2428
2782
  }
2783
+ const savedQualifiers = localVarTypeQualifiersStack.pop();
2784
+ if (savedQualifiers) {
2785
+ localVarTypeQualifiers.clear();
2786
+ for (const [k, v] of savedQualifiers) localVarTypeQualifiers.set(k, v);
2787
+ }
2429
2788
  const savedDeclared = declaredTypeVarsStack.pop();
2430
2789
  if (savedDeclared) {
2431
2790
  declaredTypeVars.clear();
@@ -2733,11 +3092,17 @@ function findImportsInCode(code, parser) {
2733
3092
 
2734
3093
  // Check parent for variable name
2735
3094
  let parent = node.parent;
3095
+ let defaultLike = false;
2736
3096
  if (parent && parent.type === 'variable_declarator') {
2737
3097
  const nameNode = parent.childForFieldName('name');
2738
3098
  if (nameNode) {
2739
3099
  if (nameNode.type === 'identifier') {
2740
3100
  names.push(nameNode.text);
3101
+ // `const app = require('./app')` binds the
3102
+ // value assigned to `module.exports`, not a
3103
+ // named property called `app`. Preserve that
3104
+ // distinction for exact import ownership.
3105
+ defaultLike = true;
2741
3106
  } else if (nameNode.type === 'object_pattern') {
2742
3107
  // Destructuring: const { a, b } = require('x')
2743
3108
  for (let i = 0; i < nameNode.namedChildCount; i++) {
@@ -2762,6 +3127,7 @@ function findImportsInCode(code, parser) {
2762
3127
 
2763
3128
  if (modulePath) {
2764
3129
  imports.push({ module: modulePath, names, type: 'require', line, dynamic,
3130
+ ...(defaultLike && { defaultLike: true }),
2765
3131
  // Per-import rename pairing (fix #269): the flat
2766
3132
  // importAliases list loses WHICH module a renamed
2767
3133
  // name came from — `{ validate: validateSchema }`
@@ -2985,10 +3351,16 @@ function findExportsInCode(code, parser) {
2985
3351
  for (let i = 0; i < rightNode.namedChildCount; i++) {
2986
3352
  const prop = rightNode.namedChild(i);
2987
3353
  if (prop.type === 'shorthand_property_identifier') {
2988
- exports.push({ name: prop.text, type: 'module.exports', line });
3354
+ exports.push({ name: prop.text, localName: prop.text, type: 'module.exports', line });
2989
3355
  } else if (prop.type === 'pair') {
2990
3356
  const key = prop.childForFieldName('key');
2991
- if (key) exports.push({ name: key.text, type: 'module.exports', line });
3357
+ const value = prop.childForFieldName('value');
3358
+ if (key) exports.push({
3359
+ name: key.text,
3360
+ ...(value?.type === 'identifier' && { localName: value.text }),
3361
+ type: 'module.exports',
3362
+ line,
3363
+ });
2992
3364
  } else if (prop.type === 'method_definition') {
2993
3365
  // Shorthand methods are exports too
2994
3366
  // (fix #252 — `module.exports =
@@ -2996,12 +3368,41 @@ function findExportsInCode(code, parser) {
2996
3368
  // export list, so deadcode audited a
2997
3369
  // require()-reachable function).
2998
3370
  const mName = prop.childForFieldName('name');
2999
- if (mName) exports.push({ name: mName.text, type: 'module.exports', line });
3371
+ if (mName) exports.push({ name: mName.text, localName: mName.text, type: 'module.exports', line });
3372
+ } else if (prop.type === 'spread_element') {
3373
+ // CommonJS barrel: `module.exports = {
3374
+ // ...require('./public') }`. This is the
3375
+ // CJS equivalent of `export * from` and
3376
+ // must retain its SOURCE so namespace
3377
+ // calls through the barrel can establish
3378
+ // name ownership. A dynamic spread stays
3379
+ // an explicitly unmodelable CJS surface.
3380
+ const value = prop.namedChild(0);
3381
+ const fn = value?.type === 'call_expression'
3382
+ ? value.childForFieldName('function') : null;
3383
+ const args = value?.type === 'call_expression'
3384
+ ? value.childForFieldName('arguments') : null;
3385
+ const first = args?.namedChild(0);
3386
+ if (fn?.type === 'identifier' && fn.text === 'require' &&
3387
+ first?.type === 'string') {
3388
+ exports.push({
3389
+ name: '*',
3390
+ type: 're-export-all',
3391
+ line,
3392
+ source: first.text.slice(1, -1),
3393
+ });
3394
+ } else {
3395
+ exports.push({
3396
+ name: '*',
3397
+ type: 'module.exports',
3398
+ line,
3399
+ });
3400
+ }
3000
3401
  }
3001
3402
  }
3002
3403
  } else if (rightNode && rightNode.type === 'identifier') {
3003
3404
  // module.exports = something
3004
- exports.push({ name: rightNode.text, type: 'module.exports', line });
3405
+ exports.push({ name: rightNode.text, localName: rightNode.text, type: 'module.exports', line });
3005
3406
  } else {
3006
3407
  exports.push({ name: 'default', type: 'module.exports', line });
3007
3408
  }
@@ -3011,7 +3412,13 @@ function findExportsInCode(code, parser) {
3011
3412
  // exports.name = ...
3012
3413
  if (objNode.text === 'exports') {
3013
3414
  const line = node.startPosition.row + 1;
3014
- exports.push({ name: propNode.text, type: 'exports', line });
3415
+ const rightNode = node.childForFieldName('right');
3416
+ exports.push({
3417
+ name: propNode.text,
3418
+ ...(rightNode?.type === 'identifier' && { localName: rightNode.text }),
3419
+ type: 'exports',
3420
+ line,
3421
+ });
3015
3422
  return true;
3016
3423
  }
3017
3424
 
@@ -3046,12 +3453,14 @@ function findUsagesInCode(code, name, parser, tree) {
3046
3453
 
3047
3454
  visitNameNodes(tree, code, name, (node) => {
3048
3455
  // Look for identifier, property_identifier (method names in obj.method() calls),
3456
+ // private_property_identifier (#method definitions and calls),
3049
3457
  // type_identifier (TypeScript type annotations), shorthand_property_identifier_pattern
3050
3458
  // (destructured names in `const { name } = require(...)`), and
3051
3459
  // shorthand_property_identifier (value-position shorthand — CJS export
3052
3460
  // objects `module.exports = { helper }` and option objects `f({ helper })`
3053
3461
  // reference the symbol but produced no usage record at all, fix #241)
3054
3462
  const isIdentifier = node.type === 'identifier' || node.type === 'property_identifier' ||
3463
+ node.type === 'private_property_identifier' ||
3055
3464
  node.type === 'type_identifier' || node.type === 'shorthand_property_identifier_pattern' ||
3056
3465
  node.type === 'shorthand_property_identifier';
3057
3466
  if (!isIdentifier || node.text !== name) {
@@ -3155,17 +3564,11 @@ function findUsagesInCode(code, name, parser, tree) {
3155
3564
  // Property access (method call): a.name() - the name after dot
3156
3565
  else if (parent.type === 'member_expression' &&
3157
3566
  sameNode(parent.childForFieldName('property'), node)) {
3158
- // Skip built-in objects and common module names (JSON.parse, path.parse, etc.)
3567
+ // Preserve the receiver and let the project-aware usage layer
3568
+ // decide ownership. A spelling such as `util` or `path` can be
3569
+ // either a standard module or a local project namespace, which
3570
+ // cannot be decided correctly from this file's AST alone.
3159
3571
  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
3572
  // Check if this is a method call
3170
3573
  const grandparent = parent.parent;
3171
3574
  if (grandparent && grandparent.type === 'call_expression') {