ucn 5.5.1 → 5.5.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.
Files changed (97) hide show
  1. package/.claude/skills/ucn/SKILL.md +35 -9
  2. package/.claude/skills/ucn/references/commands.md +40 -18
  3. package/.claude/skills/ucn/references/resolution-rules.md +126 -20
  4. package/.claude/skills/ucn/references/trust-contract.md +5 -3
  5. package/README.md +32 -21
  6. package/cli/index.js +2 -1
  7. package/core/accessors.js +341 -54
  8. package/core/account.js +74 -20
  9. package/core/analysis.js +257 -125
  10. package/core/ast-analysis.js +12 -3
  11. package/core/bridge.js +15 -65
  12. package/core/brief.js +8 -4
  13. package/core/build-worker.js +2 -1
  14. package/core/cache.js +137 -6
  15. package/core/call-name-cache.js +134 -0
  16. package/core/callable-scope.js +21 -0
  17. package/core/callers.js +4726 -940
  18. package/core/check.js +22 -5
  19. package/core/class-identity.js +399 -12
  20. package/core/client-receivers.js +189 -0
  21. package/core/constructor-calls.js +831 -0
  22. package/core/constructor-hierarchy.js +147 -0
  23. package/core/constructor-protocol.js +196 -0
  24. package/core/contract-membership.js +346 -140
  25. package/core/cpp-conversion-constraints.js +123 -0
  26. package/core/cpp-conversion-syntax.js +216 -0
  27. package/core/cpp-conversions.js +414 -0
  28. package/core/cpp-scope.js +245 -44
  29. package/core/deadcode.js +61 -16
  30. package/core/decorator-binding.js +137 -3
  31. package/core/enum-reference-syntax.js +134 -0
  32. package/core/execute.js +17 -1
  33. package/core/external-macros.js +42 -10
  34. package/core/go-packages.js +171 -0
  35. package/core/graph-build.js +2 -0
  36. package/core/graph.js +75 -59
  37. package/core/ignore-rules.js +2 -1
  38. package/core/imports.js +145 -19
  39. package/core/index-ir.js +41 -4
  40. package/core/inprocess-clients.js +41 -15
  41. package/core/ir.js +27 -6
  42. package/core/macro-expansion.js +38 -2
  43. package/core/out-of-class-members.js +182 -0
  44. package/core/output/analysis-ext.js +4 -2
  45. package/core/output/analysis.js +89 -20
  46. package/core/output/graph.js +12 -3
  47. package/core/output/lines.js +4 -3
  48. package/core/output/public.js +2 -0
  49. package/core/output/reporting.js +12 -1
  50. package/core/output/shared.js +2 -0
  51. package/core/output/tracing.js +28 -22
  52. package/core/output-budget.js +2 -2
  53. package/core/parallel-build.js +52 -1
  54. package/core/project.js +217 -112
  55. package/core/provenance-constructor-base.js +161 -0
  56. package/core/provenance-facts.js +257 -27
  57. package/core/provenance-overload.js +458 -54
  58. package/core/provenance-type.js +487 -0
  59. package/core/provenance.js +234 -15
  60. package/core/public-command.js +17 -2
  61. package/core/query-bodies.js +159 -0
  62. package/core/reflection.js +270 -2
  63. package/core/rename-token-syntax.js +111 -0
  64. package/core/reporting.js +13 -7
  65. package/core/request-helpers.js +63 -7
  66. package/core/rust-macro-expansion.js +151 -19
  67. package/core/rust-modules.js +1 -1
  68. package/core/rust-names.js +607 -0
  69. package/core/search.js +70 -17
  70. package/core/shared.js +98 -1
  71. package/core/source-memo.js +201 -0
  72. package/core/text-usage-syntax.js +38 -0
  73. package/core/tracing.js +7 -1
  74. package/core/type-denotation.js +124 -37
  75. package/core/type-references.js +615 -246
  76. package/core/value-references.js +2777 -0
  77. package/core/verify.js +1097 -281
  78. package/languages/adapter.js +28 -4
  79. package/languages/c-family.js +1118 -164
  80. package/languages/constructor-bases.js +105 -0
  81. package/languages/cpp-platform.js +26 -0
  82. package/languages/cpp-site-syntax.js +83 -0
  83. package/languages/csharp-scope.js +439 -0
  84. package/languages/csharp.js +428 -68
  85. package/languages/go.js +385 -207
  86. package/languages/html.js +4 -2
  87. package/languages/index.js +397 -2
  88. package/languages/java-platform.js +226 -0
  89. package/languages/java-unicode.js +269 -0
  90. package/languages/java.js +461 -117
  91. package/languages/javascript.js +418 -66
  92. package/languages/lexical-scope.js +190 -23
  93. package/languages/python.js +404 -54
  94. package/languages/rust.js +510 -82
  95. package/languages/utils.js +67 -8
  96. package/mcp/server.js +8 -2
  97. package/package.json +3 -3
@@ -131,6 +131,9 @@ const FUNCTION_NODE_TYPES = new Set([
131
131
  'method_definition', 'generator_function', 'generator_function_declaration',
132
132
  ]);
133
133
 
134
+ const JS_VALUE_WRAPPERS = new Set(['parenthesized_expression', 'as_expression', 'non_null_expression',
135
+ 'satisfies_expression', 'type_assertion']);
136
+
134
137
  function unwrapValueExpression(node) {
135
138
  while (node && (node.type === 'parenthesized_expression' || node.type === 'as_expression' ||
136
139
  node.type === 'non_null_expression' || node.type === 'satisfies_expression' ||
@@ -142,26 +145,26 @@ function unwrapValueExpression(node) {
142
145
  }
143
146
 
144
147
  /** Names a binding pattern declares (identifiers, shorthand, rest, defaults' targets). */
145
- function collectPatternNames(pattern, out) {
148
+ function collectPatternNames(pattern, out, withNodes = false) {
146
149
  if (!pattern) return;
147
150
  if (pattern.type === 'required_parameter' || pattern.type === 'optional_parameter') {
148
- collectPatternNames(pattern.childForFieldName('pattern') || pattern.childForFieldName('name'), out);
151
+ collectPatternNames(pattern.childForFieldName('pattern') || pattern.childForFieldName('name'), out, withNodes);
149
152
  return;
150
153
  }
151
154
  if (pattern.type === 'identifier' || pattern.type === 'shorthand_property_identifier_pattern') {
152
- out.push(pattern.text);
155
+ out.push(withNodes ? pattern : pattern.text);
153
156
  return;
154
157
  }
155
158
  if (pattern.type === 'pair_pattern' || pattern.type === 'pair') {
156
- collectPatternNames(pattern.childForFieldName('value'), out);
159
+ collectPatternNames(pattern.childForFieldName('value'), out, withNodes);
157
160
  return;
158
161
  }
159
162
  if (pattern.type === 'assignment_pattern') {
160
- collectPatternNames(pattern.childForFieldName('left') || pattern.childForFieldName('pattern'), out);
163
+ collectPatternNames(pattern.childForFieldName('left') || pattern.childForFieldName('pattern'), out, withNodes);
161
164
  return;
162
165
  }
163
166
  if (pattern.type === 'type_annotation') return;
164
- for (let i = 0; i < pattern.namedChildCount; i++) collectPatternNames(pattern.namedChild(i), out);
167
+ for (let i = 0; i < pattern.namedChildCount; i++) collectPatternNames(pattern.namedChild(i), out, withNodes);
165
168
  }
166
169
 
167
170
  // Module-level alias: findCallsInCode declares its own one-argument
@@ -449,6 +452,47 @@ function objectPropertyName(nameNode) {
449
452
  return null;
450
453
  }
451
454
 
455
+ // A non-computed method's key has the value of its identifier or string
456
+ // literal. Class constructor identity uses that value, including escapes.
457
+ function classMethodName(node) {
458
+ const raw = objectPropertyName(node);
459
+ if (raw == null || !raw.includes('\\')) return raw;
460
+ const string = node.type === 'string';
461
+ const escapes = { b: '\b', f: '\f', n: '\n', r: '\r', t: '\t', v: '\v', '0': '\0' };
462
+ let value = '';
463
+ for (let at = 0; at < raw.length; at++) {
464
+ if (raw[at] !== '\\') { value += raw[at]; continue; }
465
+ const escape = raw[++at];
466
+ if (escape === undefined) return null;
467
+ if (escape === 'u' || string && escape === 'x') {
468
+ const brace = escape === 'u' && raw[at + 1] === '{';
469
+ const start = at + (brace ? 2 : 1);
470
+ const end = brace ? raw.indexOf('}', start) : start + (escape === 'u' ? 4 : 2);
471
+ if (end <= start || end > raw.length) return null;
472
+ let code = 0;
473
+ for (let digit = start; digit < end; digit++) {
474
+ const point = raw.charCodeAt(digit);
475
+ const hex = point >= 48 && point <= 57 ? point - 48
476
+ : point >= 65 && point <= 70 ? point - 55
477
+ : point >= 97 && point <= 102 ? point - 87 : -1;
478
+ if (hex < 0) return null;
479
+ code = code * 16 + hex;
480
+ if (code > 0x10ffff) return null;
481
+ }
482
+ value += String.fromCodePoint(code);
483
+ at = brace ? end : end - 1;
484
+ } else if (!string) return null;
485
+ else if (escape === '\r' || escape === '\n' || escape === '\u2028' || escape === '\u2029') {
486
+ if (escape === '\r' && raw[at + 1] === '\n') at++;
487
+ } else {
488
+ if (escape >= '0' && escape <= '9' &&
489
+ (escape !== '0' || raw[at + 1] >= '0' && raw[at + 1] <= '9')) return null;
490
+ value += Object.hasOwn(escapes, escape) ? escapes[escape] : escape;
491
+ }
492
+ }
493
+ return value;
494
+ }
495
+
452
496
  /**
453
497
  * Index function-valued object properties. Module-scope objects are dynamic
454
498
  * dispatch surfaces (`HANDLERS[command](...)`); without symbols for their
@@ -486,7 +530,7 @@ function appendObjectFunctionMembers(objectNode, functions, lines, extraFields =
486
530
  }
487
531
 
488
532
  const paramsNode = fnNode.childForFieldName('parameters');
489
- const { startLine, endLine, indent } = nodeToLocation(prop, lines);
533
+ const { startLine, endLine, indent, sourceSpan } = nodeToLocation(prop, lines);
490
534
  const returnType = extractReturnType(fnNode);
491
535
  const generics = extractGenerics(fnNode);
492
536
  const docstring = extractJSDocstring(lines, startLine);
@@ -499,6 +543,7 @@ function appendObjectFunctionMembers(objectNode, functions, lines, extraFields =
499
543
  paramsStructured,
500
544
  startLine,
501
545
  endLine,
546
+ sourceSpan,
502
547
  indent,
503
548
  isArrow: fnNode.type === 'arrow_function',
504
549
  isGenerator: isGenerator(fnNode),
@@ -852,7 +897,7 @@ function _processFunction(node, functions, processedRanges, lines) {
852
897
  const paramsNode = node.childForFieldName('parameters');
853
898
 
854
899
  if (nameNode) {
855
- const { startLine, endLine, indent } = nodeToLocation(node, lines);
900
+ const { startLine, endLine, indent, sourceSpan } = nodeToLocation(node, lines);
856
901
  const returnType = extractReturnType(node);
857
902
  const returnedConcreteType = extractReturnedConcreteType(node);
858
903
  const generics = extractGenerics(node);
@@ -874,6 +919,7 @@ function _processFunction(node, functions, processedRanges, lines) {
874
919
  paramsStructured,
875
920
  startLine,
876
921
  endLine,
922
+ sourceSpan,
877
923
  indent,
878
924
  isArrow: false,
879
925
  isGenerator: isGen,
@@ -905,7 +951,7 @@ function _processFunction(node, functions, processedRanges, lines) {
905
951
  if (!isBoundValue && nameNode && !processedRanges.has(rangeKey)) {
906
952
  processedRanges.add(rangeKey);
907
953
  const paramsNode = node.childForFieldName('parameters');
908
- const { startLine, endLine, indent } = nodeToLocation(node, lines);
954
+ const { startLine, endLine, indent, sourceSpan } = nodeToLocation(node, lines);
909
955
  const returnType = extractReturnType(node);
910
956
  const generics = extractGenerics(node);
911
957
  const paramsStructured = parseStructuredParams(paramsNode, 'javascript');
@@ -917,6 +963,7 @@ function _processFunction(node, functions, processedRanges, lines) {
917
963
  paramsStructured,
918
964
  startLine,
919
965
  endLine,
966
+ sourceSpan,
920
967
  indent,
921
968
  isArrow: false,
922
969
  isGenerator: isGenerator(node),
@@ -945,7 +992,7 @@ function _processFunction(node, functions, processedRanges, lines) {
945
992
  const paramsNode = node.childForFieldName('parameters');
946
993
 
947
994
  if (nameNode) {
948
- const { startLine, endLine, indent } = nodeToLocation(node, lines);
995
+ const { startLine, endLine, indent, sourceSpan } = nodeToLocation(node, lines);
949
996
  const returnType = extractReturnType(node);
950
997
  const generics = extractGenerics(node);
951
998
  const docstring = extractJSDocstring(lines, startLine);
@@ -958,6 +1005,7 @@ function _processFunction(node, functions, processedRanges, lines) {
958
1005
  paramsStructured,
959
1006
  startLine,
960
1007
  endLine,
1008
+ sourceSpan,
961
1009
  indent,
962
1010
  isArrow: false,
963
1011
  isGenerator: false,
@@ -974,12 +1022,16 @@ function _processFunction(node, functions, processedRanges, lines) {
974
1022
  // Variable declarations with arrow functions or function expressions
975
1023
  if (node.type === 'lexical_declaration' || node.type === 'variable_declaration') {
976
1024
  if (processedRanges.has(rangeKey)) return false;
1025
+ const declarators = node.namedChildren.filter(child => child.type === 'variable_declarator');
977
1026
 
978
1027
  for (let i = 0; i < node.namedChildCount; i++) {
979
1028
  const declarator = node.namedChild(i);
980
1029
  if (declarator.type === 'variable_declarator') {
981
1030
  const nameNode = declarator.childForFieldName('name');
982
- const valueNode = declarator.childForFieldName('value');
1031
+ // A function wrapped in parentheses, a cast or a satisfies
1032
+ // clause (`((next) => ..) as Middleware`, fix #434) is the
1033
+ // value the declaration binds.
1034
+ const valueNode = unwrapValueExpression(declarator.childForFieldName('value'));
983
1035
 
984
1036
  if (nameNode && valueNode) {
985
1037
  const isArrow = valueNode.type === 'arrow_function';
@@ -990,7 +1042,10 @@ function _processFunction(node, functions, processedRanges, lines) {
990
1042
  processedRanges.add(rangeKey);
991
1043
  const paramsNode = valueNode.childForFieldName('parameters') ||
992
1044
  valueNode.childForFieldName('parameter');
993
- const { startLine, endLine, indent } = nodeToLocation(node, lines);
1045
+ // A comma-separated declaration binds distinct functions.
1046
+ // Its other declarators are outside this callable's extent.
1047
+ const { startLine, endLine, indent } = nodeToLocation(
1048
+ declarators.length === 1 ? node : declarator, lines);
994
1049
  const returnType = extractReturnType(valueNode);
995
1050
  const generics = extractGenerics(valueNode);
996
1051
  const docstring = extractJSDocstring(lines, startLine);
@@ -1014,7 +1069,8 @@ function _processFunction(node, functions, processedRanges, lines) {
1014
1069
  params: extractParams(paramsNode),
1015
1070
  paramsStructured,
1016
1071
  startLine,
1017
- endLine,
1072
+ nameLine: nameNode.startPosition.row + 1,
1073
+ endLine, sourceSpan: nodeToLocation(valueNode, lines).sourceSpan,
1018
1074
  indent,
1019
1075
  isArrow,
1020
1076
  isGenerator: isGen,
@@ -1051,7 +1107,8 @@ function _processFunction(node, functions, processedRanges, lines) {
1051
1107
  if (innerFn && (innerFn.type === 'arrow_function' || innerFn.type === 'function_expression')) {
1052
1108
  processedRanges.add(rangeKey);
1053
1109
  const paramsNode = innerFn.childForFieldName('parameters');
1054
- const { startLine, endLine, indent } = nodeToLocation(node, lines);
1110
+ const { startLine, endLine, indent } = nodeToLocation(
1111
+ declarators.length === 1 ? node : declarator, lines);
1055
1112
  const returnType = extractReturnType(innerFn);
1056
1113
  const generics = extractGenerics(innerFn);
1057
1114
  const docstring = extractJSDocstring(lines, startLine);
@@ -1066,11 +1123,14 @@ function _processFunction(node, functions, processedRanges, lines) {
1066
1123
  params: extractParams(paramsNode),
1067
1124
  paramsStructured,
1068
1125
  startLine,
1126
+ nameLine: nameNode.startPosition.row + 1,
1069
1127
  endLine,
1128
+ sourceSpan: nodeToLocation(innerFn, lines).sourceSpan,
1070
1129
  indent,
1071
1130
  isArrow: innerFn.type === 'arrow_function',
1072
1131
  isGenerator: false,
1073
1132
  modifiers,
1133
+ ...lexicalOwnerRange(node),
1074
1134
  ...typeAnno,
1075
1135
  ...(generics && { generics }),
1076
1136
  ...(docstring && { docstring })
@@ -1184,6 +1244,7 @@ function _processFunction(node, functions, processedRanges, lines) {
1184
1244
  paramsStructured,
1185
1245
  startLine,
1186
1246
  endLine,
1247
+ sourceSpan: nodeToLocation(rightNode, lines).sourceSpan,
1187
1248
  indent,
1188
1249
  isArrow,
1189
1250
  isGenerator: isGen,
@@ -1269,6 +1330,7 @@ function _processFunction(node, functions, processedRanges, lines) {
1269
1330
  paramsStructured,
1270
1331
  startLine,
1271
1332
  endLine,
1333
+ sourceSpan: nodeToLocation(child, lines).sourceSpan,
1272
1334
  indent,
1273
1335
  isArrow: child.type === 'arrow_function',
1274
1336
  isGenerator: isGen,
@@ -1311,12 +1373,38 @@ function findFunctions(code, parser) {
1311
1373
  * Process a node for class/interface/type/enum extraction (single-pass helper)
1312
1374
  * Returns true if node was matched, false otherwise
1313
1375
  */
1376
+ /**
1377
+ * The location of a class declaration from its first decorator (fix #423):
1378
+ * `@Component({..}) export class C` places the decorators on the export
1379
+ * statement, before `export`; the declaration starts there, as an
1380
+ * undecorated-export or non-exported decorated class starts at its first
1381
+ * token.
1382
+ */
1383
+ function _decoratedDeclarationLocation(node, lines) {
1384
+ const located = nodeToLocation(node, lines);
1385
+ const wrapper = node.parent?.type === 'export_statement' ? node.parent : null;
1386
+ let first = null;
1387
+ for (let i = 0; wrapper && i < wrapper.namedChildCount; i++) {
1388
+ const child = wrapper.namedChild(i);
1389
+ if (child.type !== 'decorator') continue;
1390
+ if (child.startIndex < node.startIndex) first = first || child;
1391
+ }
1392
+ if (!first) return located;
1393
+ const startLine = first.startPosition.row + 1;
1394
+ const indent = ((lines[first.startPosition.row] || '').match(/^(\s*)/) || ['', ''])[1].length;
1395
+ return {
1396
+ startLine, endLine: located.endLine, indent,
1397
+ sourceSpan: { ...located.sourceSpan, start: first.startIndex, startLine,
1398
+ startColumn: first.startPosition.column },
1399
+ };
1400
+ }
1401
+
1314
1402
  function _processClass(node, classes, processedRanges, lines) {
1315
1403
  // Class declarations (including abstract classes)
1316
1404
  if (node.type === 'class_declaration' || node.type === 'class' || node.type === 'abstract_class_declaration') {
1317
1405
  const nameNode = node.childForFieldName('name');
1318
1406
  if (nameNode) {
1319
- const { startLine, endLine } = nodeToLocation(node, lines);
1407
+ const { startLine, endLine, sourceSpan } = _decoratedDeclarationLocation(node, lines);
1320
1408
  const members = extractClassMembers(node, lines);
1321
1409
  const docstring = extractJSDocstring(lines, startLine);
1322
1410
  const generics = extractGenerics(node);
@@ -1326,10 +1414,14 @@ function _processClass(node, classes, processedRanges, lines) {
1326
1414
  const decoratorsWithArgs = extractDecoratorsWithArgs(node);
1327
1415
 
1328
1416
  const isAbstract = node.type === 'abstract_class_declaration';
1417
+ const nameLine = nameNode.startPosition.row + 1;
1329
1418
  classes.push({
1330
1419
  name: nameNode.text,
1331
1420
  startLine,
1332
1421
  endLine,
1422
+ sourceSpan,
1423
+ // A decorated class names itself below its first decorator.
1424
+ ...(nameLine !== startLine && { nameLine }),
1333
1425
  type: 'class',
1334
1426
  members,
1335
1427
  ...(isAbstract && { modifiers: ['abstract'] }),
@@ -1351,7 +1443,7 @@ function _processClass(node, classes, processedRanges, lines) {
1351
1443
  if (node.type === 'interface_declaration') {
1352
1444
  const nameNode = node.childForFieldName('name');
1353
1445
  if (nameNode) {
1354
- const { startLine, endLine } = nodeToLocation(node, lines);
1446
+ const { startLine, endLine, sourceSpan } = nodeToLocation(node, lines);
1355
1447
  const docstring = extractJSDocstring(lines, startLine);
1356
1448
  const generics = extractGenerics(node);
1357
1449
  const extendsInfo = extractInterfaceExtends(node);
@@ -1361,6 +1453,7 @@ function _processClass(node, classes, processedRanges, lines) {
1361
1453
  name: nameNode.text,
1362
1454
  startLine,
1363
1455
  endLine,
1456
+ sourceSpan,
1364
1457
  type: 'interface',
1365
1458
  members,
1366
1459
  ...(docstring && { docstring }),
@@ -1375,7 +1468,7 @@ function _processClass(node, classes, processedRanges, lines) {
1375
1468
  if (node.type === 'type_alias_declaration') {
1376
1469
  const nameNode = node.childForFieldName('name');
1377
1470
  if (nameNode) {
1378
- const { startLine, endLine } = nodeToLocation(node, lines);
1471
+ const { startLine, endLine, sourceSpan } = nodeToLocation(node, lines);
1379
1472
  const docstring = extractJSDocstring(lines, startLine);
1380
1473
  const valueNode = node.childForFieldName('value');
1381
1474
  // `type ZodTypeAny = ZodType<any, any, any>;` — the alias IS the
@@ -1395,6 +1488,7 @@ function _processClass(node, classes, processedRanges, lines) {
1395
1488
  name: nameNode.text,
1396
1489
  startLine,
1397
1490
  endLine,
1491
+ sourceSpan,
1398
1492
  type: 'type',
1399
1493
  members,
1400
1494
  ...(aliasOf && { aliasOf }),
@@ -1408,7 +1502,7 @@ function _processClass(node, classes, processedRanges, lines) {
1408
1502
  if (node.type === 'enum_declaration') {
1409
1503
  const nameNode = node.childForFieldName('name');
1410
1504
  if (nameNode) {
1411
- const { startLine, endLine } = nodeToLocation(node, lines);
1505
+ const { startLine, endLine, sourceSpan } = nodeToLocation(node, lines);
1412
1506
  const docstring = extractJSDocstring(lines, startLine);
1413
1507
  const members = extractEnumMembers(node, lines);
1414
1508
 
@@ -1416,6 +1510,7 @@ function _processClass(node, classes, processedRanges, lines) {
1416
1510
  name: nameNode.text,
1417
1511
  startLine,
1418
1512
  endLine,
1513
+ sourceSpan,
1419
1514
  type: 'enum',
1420
1515
  members,
1421
1516
  ...(docstring && { docstring })
@@ -1435,13 +1530,14 @@ function _processClass(node, classes, processedRanges, lines) {
1435
1530
  // StoreMutators augmentations). The compiler merges it into the
1436
1531
  // TARGET module — never claimable, never importable by this "name".
1437
1532
  if (nameNode && nameNode.type !== 'string') {
1438
- const { startLine, endLine } = nodeToLocation(node, lines);
1533
+ const { startLine, endLine, sourceSpan } = nodeToLocation(node, lines);
1439
1534
  const docstring = extractJSDocstring(lines, startLine);
1440
1535
 
1441
1536
  classes.push({
1442
1537
  name: nameNode.text,
1443
1538
  startLine,
1444
1539
  endLine,
1540
+ sourceSpan,
1445
1541
  type: 'namespace',
1446
1542
  members: [],
1447
1543
  ...(docstring && { docstring })
@@ -1578,7 +1674,7 @@ function extractTypeMembers(bodyNode, code) {
1578
1674
  const nameNode = child.childForFieldName('name');
1579
1675
  const paramsNode = child.childForFieldName('parameters');
1580
1676
  if (nameNode) {
1581
- const { startLine, endLine } = nodeToLocation(child, code);
1677
+ const { startLine, endLine, sourceSpan } = nodeToLocation(child, code);
1582
1678
  const returnType = extractReturnType(child);
1583
1679
  const paramsStructured = parseStructuredParams(paramsNode, 'javascript');
1584
1680
  const typeAnno = buildTypeAnnotations(paramsStructured, returnType, code, startLine, true);
@@ -1588,6 +1684,7 @@ function extractTypeMembers(bodyNode, code) {
1588
1684
  paramsStructured,
1589
1685
  startLine,
1590
1686
  endLine,
1687
+ sourceSpan,
1591
1688
  memberType: 'method',
1592
1689
  isMethod: true,
1593
1690
  ...typeAnno
@@ -1596,7 +1693,7 @@ function extractTypeMembers(bodyNode, code) {
1596
1693
  } else if (child.type === 'property_signature') {
1597
1694
  const nameNode = child.childForFieldName('name');
1598
1695
  if (nameNode) {
1599
- const { startLine, endLine } = nodeToLocation(child, code);
1696
+ const { startLine, endLine, sourceSpan } = nodeToLocation(child, code);
1600
1697
  // Declared property type (fix #219): raw annotation text —
1601
1698
  // findCallers hops field receivers to it (this._map.has()),
1602
1699
  // and function-typed properties ((arg) => T) count as
@@ -1607,6 +1704,7 @@ function extractTypeMembers(bodyNode, code) {
1607
1704
  name: nameNode.text,
1608
1705
  startLine,
1609
1706
  endLine,
1707
+ sourceSpan,
1610
1708
  memberType: 'field',
1611
1709
  ...(fieldType && { fieldType })
1612
1710
  });
@@ -1629,12 +1727,12 @@ function extractEnumMembers(enumNode, code) {
1629
1727
  if (child.type === 'enum_assignment') {
1630
1728
  const nameNode = child.childForFieldName('name');
1631
1729
  if (nameNode) {
1632
- const { startLine, endLine } = nodeToLocation(child, code);
1633
- members.push({ name: nameNode.text, startLine, endLine, memberType: 'field' });
1730
+ const { startLine, endLine, sourceSpan } = nodeToLocation(child, code);
1731
+ members.push({ name: nameNode.text, startLine, endLine, sourceSpan, memberType: 'field' });
1634
1732
  }
1635
1733
  } else if (child.type === 'property_identifier') {
1636
- const { startLine, endLine } = nodeToLocation(child, code);
1637
- members.push({ name: child.text, startLine, endLine, memberType: 'field' });
1734
+ const { startLine, endLine, sourceSpan } = nodeToLocation(child, code);
1735
+ members.push({ name: child.text, startLine, endLine, sourceSpan, memberType: 'field' });
1638
1736
  }
1639
1737
  }
1640
1738
  return members;
@@ -1657,9 +1755,10 @@ function extractClassMembers(classNode, codeOrLines) {
1657
1755
  const nameNode = child.childForFieldName('name');
1658
1756
  const paramsNode = child.childForFieldName('parameters');
1659
1757
  if (nameNode) {
1660
- const { startLine, endLine } = nodeToLocation(child, code);
1661
- let name = nameNode.text;
1758
+ const { startLine, endLine, sourceSpan } = nodeToLocation(child, code);
1759
+ let name = classMethodName(nameNode) === 'constructor' ? 'constructor' : nameNode.text;
1662
1760
  const text = child.text;
1761
+ const isStatic = child.children.some(part => part.type === 'static');
1663
1762
 
1664
1763
  // Collect decorators from preceding siblings in the class body
1665
1764
  const decorators = [];
@@ -1685,7 +1784,7 @@ function extractClassMembers(classNode, codeOrLines) {
1685
1784
  if (staticMatch) name = staticMatch[1];
1686
1785
  }
1687
1786
 
1688
- if (name === 'constructor') {
1787
+ if (name === 'constructor' && !isStatic && nameNode.type !== 'computed_property_name') {
1689
1788
  memberType = 'constructor';
1690
1789
  } else if (text.match(/^\s*(?:public\s+|private\s+|protected\s+)?(?:override\s+)?static\s+(?:override\s+)?get\s/)) {
1691
1790
  memberType = hasOverride ? 'static override get' : 'static get';
@@ -1726,6 +1825,7 @@ function extractClassMembers(classNode, codeOrLines) {
1726
1825
  paramsStructured,
1727
1826
  startLine,
1728
1827
  endLine,
1828
+ sourceSpan,
1729
1829
  memberType,
1730
1830
  ...(accessMatch && { modifiers: [accessMatch[1]] }),
1731
1831
  isAsync,
@@ -1783,7 +1883,7 @@ function extractClassMembers(classNode, codeOrLines) {
1783
1883
  const nameNode = child.childForFieldName('name');
1784
1884
  const paramsNode = child.childForFieldName('parameters');
1785
1885
  if (nameNode) {
1786
- const { startLine, endLine } = nodeToLocation(child, code);
1886
+ const { startLine, endLine, sourceSpan } = nodeToLocation(child, code);
1787
1887
  const returnType = extractReturnType(child);
1788
1888
  const docstring = extractJSDocstring(code, startLine);
1789
1889
  const paramsStructured = parseStructuredParams(paramsNode, 'javascript');
@@ -1805,6 +1905,7 @@ function extractClassMembers(classNode, codeOrLines) {
1805
1905
  paramsStructured,
1806
1906
  startLine,
1807
1907
  endLine,
1908
+ sourceSpan,
1808
1909
  memberType: 'abstract',
1809
1910
  isMethod: true,
1810
1911
  ...typeAnno,
@@ -1818,7 +1919,7 @@ function extractClassMembers(classNode, codeOrLines) {
1818
1919
  if (child.type === 'field_definition' || child.type === 'public_field_definition') {
1819
1920
  const nameNode = child.childForFieldName('name') || child.childForFieldName('property');
1820
1921
  if (nameNode) {
1821
- const { startLine, endLine } = nodeToLocation(child, code);
1922
+ const { startLine, endLine, sourceSpan } = nodeToLocation(child, code);
1822
1923
  const name = nameNode.text;
1823
1924
  // fix #390: a TS decorator is part of the field node; the
1824
1925
  // name's line is where definition edits go.
@@ -1865,6 +1966,7 @@ function extractClassMembers(classNode, codeOrLines) {
1865
1966
  paramsStructured,
1866
1967
  startLine,
1867
1968
  endLine,
1969
+ sourceSpan,
1868
1970
  ...fieldNameLine,
1869
1971
  memberType: name.startsWith('#') ? 'private' : 'field',
1870
1972
  ...(isStatic && { modifiers: ['static'] }),
@@ -1892,6 +1994,7 @@ function extractClassMembers(classNode, codeOrLines) {
1892
1994
  name,
1893
1995
  startLine,
1894
1996
  endLine,
1997
+ sourceSpan,
1895
1998
  ...fieldNameLine,
1896
1999
  memberType: name.startsWith('#') ? 'private field' : 'field',
1897
2000
  ...(isStatic && { modifiers: ['static'] }),
@@ -1905,9 +2008,48 @@ function extractClassMembers(classNode, codeOrLines) {
1905
2008
  }
1906
2009
  }
1907
2010
 
2011
+ members.push(...jsInstanceProperties(bodyNode, members.map(member => member.name)));
1908
2012
  return members;
1909
2013
  }
1910
2014
 
2015
+ const JS_OWN_THIS_SCOPES = new Set(['function_expression', 'function_declaration', 'function',
2016
+ 'generator_function', 'generator_function_declaration', 'class', 'class_declaration',
2017
+ 'abstract_class_declaration', 'method_definition']);
2018
+
2019
+ /**
2020
+ * Instance properties a class's methods create by assigning them on `this`
2021
+ * (fix #431, as Python instance attributes, fix #430): one per name, the
2022
+ * first assignment in source order, for names the class body does not
2023
+ * declare. A static method's `this` is the class; a nested function or
2024
+ * class has its own `this`; an arrow function shares it.
2025
+ */
2026
+ function jsInstanceProperties(bodyNode, declared) {
2027
+ const out = [];
2028
+ const seen = new Set(declared);
2029
+ for (let i = 0; i < bodyNode.namedChildCount; i++) {
2030
+ const member = bodyNode.namedChild(i);
2031
+ if (member.type !== 'method_definition' || member.children.some(part => part.type === 'static')) continue;
2032
+ const body = member.childForFieldName('body');
2033
+ if (!body) continue;
2034
+ traverseTree(body, node => {
2035
+ if (JS_OWN_THIS_SCOPES.has(node.type)) return false;
2036
+ if (node.type !== 'assignment_expression' && node.type !== 'augmented_assignment_expression') return true;
2037
+ const left = node.childForFieldName('left');
2038
+ const property = left?.type === 'member_expression' && left.childForFieldName('object')?.type === 'this'
2039
+ ? left.childForFieldName('property') : null;
2040
+ // Reading an inherited member on the RHS still writes an own
2041
+ // instance property, as in `this.m = this.m.bind(this)`.
2042
+ if (property?.type === 'property_identifier' && !seen.has(property.text)) {
2043
+ seen.add(property.text);
2044
+ out.push({ name: property.text, startLine: property.startPosition.row + 1,
2045
+ endLine: node.endPosition.row + 1, memberType: 'field', instanceAttribute: true });
2046
+ }
2047
+ return true;
2048
+ });
2049
+ }
2050
+ return out;
2051
+ }
2052
+
1911
2053
  // Module-level state detection helpers
1912
2054
  const _STATE_PATTERN = /^(CONFIG|[A-Z][a-zA-Z]*(?:State|Store|Context|Options|Settings)|[A-Z][A-Z_]+|Entities|Input)$/;
1913
2055
  const _ACTION_PATTERN = /^(action\w*|[a-z]+Action|[a-z]+State)$/;
@@ -1925,25 +2067,63 @@ function _isFactoryCall(node) {
1925
2067
  * Process a node for state object extraction (single-pass helper)
1926
2068
  * Returns true if node was matched, false otherwise
1927
2069
  */
2070
+ function moduleStateScope(declaration) {
2071
+ const namespaces = [];
2072
+ for (let scope = declaration.parent; scope; scope = scope.parent) {
2073
+ if (scope.type === 'program') return { namespace: namespaces.reverse().join('.') };
2074
+ if (scope.type === 'export_statement' || scope.type === 'ambient_declaration') continue;
2075
+ if (scope.type === 'internal_module' || scope.type === 'module') {
2076
+ const name = scope.childForFieldName('name');
2077
+ if (!name || name.type === 'string') return null;
2078
+ namespaces.push(name.text);
2079
+ continue;
2080
+ }
2081
+ if (scope.type === 'statement_block' &&
2082
+ ['internal_module', 'module'].includes(scope.parent?.type)) continue;
2083
+ if (FUNCTION_NODE_TYPES.has(scope.type) ||
2084
+ ['class_body', 'class_static_block'].includes(scope.type)) return null;
2085
+ if (declaration.type !== 'variable_declaration') return null;
2086
+ }
2087
+ return null;
2088
+ }
2089
+
1928
2090
  function _processState(node, objects, lines) {
1929
2091
  if (node.type === 'lexical_declaration' || node.type === 'variable_declaration') {
2092
+ const moduleScope = moduleStateScope(node);
1930
2093
  for (let i = 0; i < node.namedChildCount; i++) {
1931
2094
  const declarator = node.namedChild(i);
1932
2095
  if (declarator.type === 'variable_declarator') {
1933
2096
  const nameNode = declarator.childForFieldName('name');
1934
2097
  const valueNode = declarator.childForFieldName('value');
1935
2098
 
2099
+ const value = unwrapValueExpression(valueNode);
2100
+ if (moduleScope && nameNode && !isImportInitializerNode(valueNode) &&
2101
+ !['arrow_function', 'function', 'function_expression', 'generator_function',
2102
+ 'class', 'class_declaration'].includes(value?.type)) {
2103
+ const names = [];
2104
+ collectPatternNames(nameNode, names, true);
2105
+ for (const binding of names) {
2106
+ objects.push({ name: binding.text,
2107
+ startLine: binding.startPosition.row + 1,
2108
+ endLine: declarator.endPosition.row + 1,
2109
+ sourceSpan: nodeToLocation(declarator, lines).sourceSpan,
2110
+ ...(moduleScope.namespace && { namespace: moduleScope.namespace }),
2111
+ });
2112
+ }
2113
+ continue;
2114
+ }
2115
+
1936
2116
  if (nameNode && valueNode) {
1937
2117
  const name = nameNode.text;
1938
2118
  const isObject = valueNode.type === 'object';
1939
2119
  const isArray = valueNode.type === 'array';
1940
2120
 
1941
2121
  if ((isObject || isArray) && _STATE_PATTERN.test(name)) {
1942
- const { startLine, endLine } = nodeToLocation(node, lines);
1943
- objects.push({ name, startLine, endLine });
2122
+ const { startLine, endLine, sourceSpan } = nodeToLocation(node, lines);
2123
+ objects.push({ name, startLine, endLine, sourceSpan });
1944
2124
  } else if (_isFactoryCall(valueNode) && (_ACTION_PATTERN.test(name) || _STATE_PATTERN.test(name))) {
1945
- const { startLine, endLine } = nodeToLocation(node, lines);
1946
- objects.push({ name, startLine, endLine });
2125
+ const { startLine, endLine, sourceSpan } = nodeToLocation(node, lines);
2126
+ objects.push({ name, startLine, endLine, sourceSpan });
1947
2127
  }
1948
2128
  }
1949
2129
  }
@@ -2051,33 +2231,63 @@ function jsModuleValueAliases(root) {
2051
2231
  }
2052
2232
  }
2053
2233
  }
2054
- if (mutable.length > 0) {
2055
- // A `let`/`var` alias is the class only while nothing assigns the
2056
- // name again (fix #392): any write to that identifier anywhere in the
2057
- // module, or a second declaration, leaves it unbound.
2058
- const written = new Set();
2059
- const writeTargets = (node) => {
2060
- if (!node) return;
2061
- if (node.type === 'identifier') { written.add(node.text); return; }
2062
- if (/pattern$/.test(node.type) || node.type === 'pair_pattern' ||
2063
- node.type === 'shorthand_property_identifier_pattern') {
2064
- if (node.type === 'shorthand_property_identifier_pattern') written.add(node.text);
2065
- for (const child of node.namedChildren) writeTargets(child);
2066
- }
2067
- };
2068
- for (const node of root.descendantsOfType(['assignment_expression',
2069
- 'augmented_assignment_expression', 'update_expression', 'for_in_statement'])) {
2070
- if (node.type === 'update_expression') writeTargets(node.namedChild(0));
2071
- else if (node.type === 'for_in_statement') {
2072
- if (!node.childForFieldName('kind')) writeTargets(node.childForFieldName('left'));
2073
- } else writeTargets(node.childForFieldName('left'));
2234
+ // A `let`/`var` alias is the class only while nothing assigns the
2235
+ // name again (fix #392): any write to that identifier anywhere in the
2236
+ // module, or a second declaration, leaves it unbound.
2237
+ const written = new Set();
2238
+ const writeTargets = (node) => {
2239
+ if (!node) return;
2240
+ if (node.type === 'identifier') { written.add(node.text); return; }
2241
+ if (/pattern$/.test(node.type) || node.type === 'pair_pattern' ||
2242
+ node.type === 'shorthand_property_identifier_pattern') {
2243
+ if (node.type === 'shorthand_property_identifier_pattern') written.add(node.text);
2244
+ for (const child of node.namedChildren) writeTargets(child);
2074
2245
  }
2075
- for (const alias of mutable) {
2076
- if (!written.has(alias.name) && declared.get(alias.name) === 1) aliases.push(alias);
2246
+ };
2247
+ for (const node of root.descendantsOfType(['assignment_expression',
2248
+ 'augmented_assignment_expression', 'update_expression', 'for_in_statement'])) {
2249
+ if (node.type === 'update_expression') writeTargets(node.namedChild(0));
2250
+ else if (node.type === 'for_in_statement') {
2251
+ if (!node.childForFieldName('kind')) writeTargets(node.childForFieldName('left'));
2252
+ } else writeTargets(node.childForFieldName('left'));
2253
+ }
2254
+ for (const alias of mutable) {
2255
+ if (!written.has(alias.name) && declared.get(alias.name) === 1) aliases.push(alias);
2256
+ }
2257
+ aliases.sort((a, b) => a.line - b.line);
2258
+ return { aliases, assignedNames: [...written].sort() };
2259
+ }
2260
+
2261
+ /**
2262
+ * Declarations inside `declare global { .. }` and `declare module 'name' {
2263
+ * .. }` merge with declarations outside the project (fix #423: `interface
2264
+ * Window`, a router's `Register`): the checker reads them through the merged
2265
+ * declaration, so no use names them in project code. Marks every symbol
2266
+ * (and member) whose span lies in such a block `ambientAugmentation`.
2267
+ */
2268
+ function markAmbientAugmentations(root, groups) {
2269
+ const ranges = [];
2270
+ for (let i = 0; i < root.namedChildCount; i++) {
2271
+ let node = root.namedChild(i);
2272
+ if (node.type === 'export_statement') node = node.namedChild(0);
2273
+ if (node?.type !== 'ambient_declaration') continue;
2274
+ const inner = node.namedChild(0);
2275
+ const augments = inner?.type === 'statement_block' ||
2276
+ (inner?.type === 'module' && inner.childForFieldName('name')?.type === 'string');
2277
+ if (augments) ranges.push([node.startIndex, node.endIndex]);
2278
+ }
2279
+ if (ranges.length === 0) return;
2280
+ const inside = item => {
2281
+ const start = item?.sourceSpan?.start;
2282
+ return Number.isInteger(start) && ranges.some(([from, to]) => start >= from && start < to);
2283
+ };
2284
+ for (const group of groups) {
2285
+ for (const item of group) {
2286
+ if (!inside(item)) continue;
2287
+ item.ambientAugmentation = true;
2288
+ for (const member of item.members || []) member.ambientAugmentation = true;
2077
2289
  }
2078
- aliases.sort((a, b) => a.line - b.line);
2079
2290
  }
2080
- return aliases;
2081
2291
  }
2082
2292
 
2083
2293
  function parse(code, parser) {
@@ -2191,7 +2401,8 @@ function parse(code, parser) {
2191
2401
  classes.sort((a, b) => a.startLine - b.startLine);
2192
2402
  stateObjects.sort((a, b) => a.startLine - b.startLine);
2193
2403
  callableAliases.sort((a, b) => a.startLine - b.startLine);
2194
- const moduleValueAliases = jsModuleValueAliases(tree.rootNode);
2404
+ markAmbientAugmentations(tree.rootNode, [functions, classes, stateObjects]);
2405
+ const { aliases: moduleValueAliases, assignedNames: moduleAssignedNames } = jsModuleValueAliases(tree.rootNode);
2195
2406
 
2196
2407
  return {
2197
2408
  language: 'javascript',
@@ -2201,6 +2412,7 @@ function parse(code, parser) {
2201
2412
  stateObjects,
2202
2413
  callableAliases,
2203
2414
  ...(moduleValueAliases.length > 0 && { moduleValueAliases }),
2415
+ ...(moduleAssignedNames.length > 0 && { moduleAssignedNames }),
2204
2416
  ...(tree.rootNode.hasError && { parseRecovery: true, parseErrorRegions: parseErrorRegions(tree.rootNode) }),
2205
2417
  imports: [], // Handled by core/imports.js
2206
2418
  exports: [] // Handled by core/imports.js
@@ -2447,6 +2659,40 @@ function jsLiteralRequireModule(node) {
2447
2659
  return first?.type === 'string' ? first.text.slice(1, -1) : undefined;
2448
2660
  }
2449
2661
 
2662
+ // Functions with their own `this`; an arrow function reads its enclosing one.
2663
+ const JS_THIS_FUNCTIONS = new Set(['function_declaration', 'function_expression', 'function',
2664
+ 'generator_function', 'generator_function_declaration']);
2665
+
2666
+ /**
2667
+ * A construction through a method's own receiver (fix #434): `new this(..)`
2668
+ * in a static method and `new this.constructor(..)` in an instance method
2669
+ * build the method's class or a subclass of it. Returns the class (by its
2670
+ * name line), the method, the receiver binding and the callee spelling.
2671
+ */
2672
+ function jsOwnClassConstruction(newNode, ctorNode) {
2673
+ let binding;
2674
+ if (ctorNode.type === 'this') binding = 'class';
2675
+ else if (ctorNode.type === 'member_expression' && ctorNode.childForFieldName('object')?.type === 'this' &&
2676
+ ctorNode.childForFieldName('property')?.text === 'constructor') binding = 'instance';
2677
+ else return null;
2678
+ let method = null;
2679
+ for (let parent = newNode.parent; parent && !method; parent = parent.parent) {
2680
+ if (JS_THIS_FUNCTIONS.has(parent.type) || parent.type === 'class_body' || parent.type === 'program' ||
2681
+ parent.type === 'class_static_block') return null;
2682
+ if (parent.type === 'method_definition') method = parent;
2683
+ }
2684
+ const classNode = method?.parent?.type === 'class_body' ? method.parent.parent : null;
2685
+ const className = classNode && ['class_declaration', 'abstract_class_declaration'].includes(classNode.type)
2686
+ ? classNode.childForFieldName('name') : null;
2687
+ const methodName = method?.childForFieldName('name');
2688
+ if (!className || !methodName) return null;
2689
+ const isStatic = method.children.some(child => child.type === 'static');
2690
+ if ((binding === 'class') !== isStatic) return null;
2691
+ return { name: className.text, line: className.startPosition.row + 1, method: methodName.text,
2692
+ methodLine: methodName.startPosition.row + 1, receiver: 'this', binding,
2693
+ spelling: binding === 'class' ? 'this' : 'constructor' };
2694
+ }
2695
+
2450
2696
  function findCallsInCode(code, parser) {
2451
2697
  const tree = parseTree(parser, code);
2452
2698
  const calls = [];
@@ -2997,8 +3243,10 @@ function findCallsInCode(code, parser) {
2997
3243
  return nameNode?.text || '<anonymous>';
2998
3244
  }
2999
3245
  if (node.type === 'arrow_function') {
3000
- // Arrow functions don't have names, but check parent for variable assignment
3001
- const parent = node.parent;
3246
+ // Arrow functions don't have names, but check parent for variable
3247
+ // assignment, through parentheses and casts (fix #434).
3248
+ let parent = node.parent;
3249
+ while (parent && JS_VALUE_WRAPPERS.has(parent.type)) parent = parent.parent;
3002
3250
  if (parent?.type === 'variable_declarator') {
3003
3251
  const nameNode = parent.childForFieldName('name');
3004
3252
  return nameNode?.text || '<anonymous>';
@@ -3676,6 +3924,38 @@ function findCallsInCode(code, parser) {
3676
3924
  enclosingFunction: getCurrentEnclosingFunction(),
3677
3925
  });
3678
3926
  };
3927
+ // A method overwritten on a typed receiver (fix #423, commander tests:
3928
+ // `cmd.copyInheritedSettings = mock` with `cmd = new Command()`): the
3929
+ // written member names the method exactly like a stored read, and a
3930
+ // rename that leaves it assigns another property. Only values that can
3931
+ // hold a callable record a reference; a data write (`x.count = 0`)
3932
+ // records nothing, and `this` members are read by their own pass.
3933
+ const CALLABLE_VALUE_TYPES = new Set(['identifier', 'member_expression', 'arrow_function',
3934
+ 'function_expression', 'function', 'call_expression', 'conditional_expression']);
3935
+ const memberWriteReference = (leftNode, rightNode) => {
3936
+ if (leftNode?.type !== 'member_expression') return;
3937
+ const prop = leftNode.childForFieldName('property');
3938
+ const obj = leftNode.childForFieldName('object');
3939
+ if (prop?.type !== 'property_identifier' || obj?.type !== 'identifier' || SKIP_IDENTS.has(prop.text)) return;
3940
+ let value = rightNode;
3941
+ while (value && (value.type === 'parenthesized_expression' || value.type === 'as_expression' ||
3942
+ value.type === 'satisfies_expression' || value.type === 'non_null_expression')) {
3943
+ value = value.namedChild(0);
3944
+ }
3945
+ if (!value || !CALLABLE_VALUE_TYPES.has(value.type)) return;
3946
+ const facts = memberReceiverFacts(obj, prop.text);
3947
+ if (!facts.receiverType) return;
3948
+ calls.push({
3949
+ name: prop.text,
3950
+ line: prop.startPosition.row + 1,
3951
+ column: prop.startPosition.column,
3952
+ isMethod: true,
3953
+ ...facts,
3954
+ isFunctionReference: true,
3955
+ memberValue: true,
3956
+ enclosingFunction: getCurrentEnclosingFunction(),
3957
+ });
3958
+ };
3679
3959
  const destructuredFields = (refNode) => {
3680
3960
  const destructured = destructuredBindingOf(refNode, refNode.text);
3681
3961
  return destructured ? { destructured } : null;
@@ -3867,6 +4147,7 @@ function findCallsInCode(code, parser) {
3867
4147
  const left = node.childForFieldName('left');
3868
4148
  const right = node.childForFieldName('right');
3869
4149
  memberValueReference(right);
4150
+ memberWriteReference(left, right);
3870
4151
  if (left?.type === 'identifier') {
3871
4152
  if (right?.type === 'new_expression') {
3872
4153
  nonCallableNames.add(left.text);
@@ -4020,6 +4301,7 @@ function findCallsInCode(code, parser) {
4020
4301
  // the parent class's constructor definition.
4021
4302
  calls.push({
4022
4303
  name: 'constructor',
4304
+ constructorSyntax: 'delegation',
4023
4305
  line: node.startPosition.row + 1,
4024
4306
  callStart: node.startIndex,
4025
4307
  callEnd: node.endIndex,
@@ -4288,12 +4570,34 @@ function findCallsInCode(code, parser) {
4288
4570
  const ctorNode = node.childForFieldName('constructor');
4289
4571
  if (ctorNode) {
4290
4572
  const enclosingFunction = getCurrentEnclosingFunction();
4573
+ const args = (node.childForFieldName('arguments')?.namedChildren || [])
4574
+ .filter(arg => !arg.type.endsWith('comment'));
4575
+ const argumentsShape = {
4576
+ argCount: args.length,
4577
+ ...(args.some(arg => arg.type === 'spread_element') && { argSpread: true }),
4578
+ };
4291
4579
 
4292
4580
  // fix #366: `new Router({ prefix: '/api' })` (koa-router)
4293
4581
  // carries a constructor mount prefix.
4294
4582
  const ctorPrefix = hasObjectKeyArg(node, 'prefix');
4295
- if (ctorNode.type === 'identifier') {
4583
+ const ownClass = jsOwnClassConstruction(node, ctorNode);
4584
+ if (ctorNode.type === 'this' && ownClass) {
4585
+ // `new this(..)` in a static method builds the method's
4586
+ // class or a subclass of it (fix #434).
4587
+ calls.push({
4588
+ callSite: typeOrigin('call', ctorNode),
4589
+ ...argumentsShape,
4590
+ name: ownClass.name,
4591
+ line: node.startPosition.row + 1,
4592
+ isMethod: false,
4593
+ isConstructor: true,
4594
+ ownClass,
4595
+ enclosingFunction
4596
+ });
4597
+ } else if (ctorNode.type === 'identifier') {
4296
4598
  calls.push({
4599
+ callSite: typeOrigin('call', ctorNode),
4600
+ ...argumentsShape,
4297
4601
  name: ctorNode.text,
4298
4602
  line: node.startPosition.row + 1,
4299
4603
  isMethod: false,
@@ -4307,10 +4611,14 @@ function findCallsInCode(code, parser) {
4307
4611
  const propNode = ctorNode.childForFieldName('property');
4308
4612
  if (propNode) {
4309
4613
  calls.push({
4614
+ callSite: typeOrigin('call', propNode),
4615
+ ...argumentsShape,
4310
4616
  name: propNode.text,
4311
4617
  line: node.startPosition.row + 1,
4312
4618
  isMethod: true,
4313
4619
  isConstructor: true,
4620
+ ...memberReceiverFacts(ctorNode.childForFieldName('object'), propNode.text),
4621
+ ...(ownClass && { ownClass }),
4314
4622
  ...(ctorPrefix && { prefixOption: true }),
4315
4623
  enclosingFunction
4316
4624
  });
@@ -4982,8 +5290,39 @@ function findImportsInCode(code, parser) {
4982
5290
  const deferredFields = deferral ? { deferred: true, deferredReason: deferral } : {};
4983
5291
  if (firstArg && firstArg.type === 'string') {
4984
5292
  const modulePath = firstArg.text.slice(1, -1);
4985
- imports.push({ module: modulePath, names: [], type: 'dynamic', line, dynamic: false,
4986
- ...deferredFields });
5293
+ // `const { a, b: c } = await import('m')` binds the
5294
+ // module's named exports like a static import does
5295
+ // (fix #423, swr `const { preload } = await
5296
+ // import('swr')` in a test).
5297
+ const names = [], renames = [];
5298
+ // `{ default: x }` reads the module's default value,
5299
+ // as `import x from 'm'` (and `require('m')`) does.
5300
+ let defaultLocal = null;
5301
+ let holder = node.parent;
5302
+ while (holder && (holder.type === 'parenthesized_expression' ||
5303
+ holder.type === 'await_expression')) holder = holder.parent;
5304
+ const pattern = holder?.type === 'variable_declarator' ? holder.childForFieldName('name') : null;
5305
+ if (pattern?.type === 'object_pattern' && holder.childForFieldName('value') &&
5306
+ node.startIndex >= holder.childForFieldName('value').startIndex) {
5307
+ for (let i = 0; i < pattern.namedChildCount; i++) {
5308
+ const prop = pattern.namedChild(i);
5309
+ if (prop.type === 'shorthand_property_identifier_pattern') names.push(prop.text);
5310
+ else if (prop.type === 'pair_pattern') {
5311
+ const key = prop.childForFieldName('key');
5312
+ const val = prop.childForFieldName('value');
5313
+ if (key?.type !== 'property_identifier' || val?.type !== 'identifier') continue;
5314
+ if (key.text === 'default') { defaultLocal = val.text; continue; }
5315
+ names.push(key.text);
5316
+ if (val.text !== key.text) renames.push({ original: key.text, local: val.text });
5317
+ }
5318
+ }
5319
+ }
5320
+ imports.push({ module: modulePath, names, type: 'dynamic', line, dynamic: false,
5321
+ ...deferredFields, ...(renames.length > 0 && { renames }) });
5322
+ if (defaultLocal) {
5323
+ imports.push({ module: modulePath, names: [defaultLocal], type: 'dynamic', line,
5324
+ dynamic: false, defaultLike: true, ...deferredFields });
5325
+ }
4987
5326
  } else if (firstArg) {
4988
5327
  imports.push({ module: firstArg.text, names: [], type: 'dynamic', line, dynamic: true,
4989
5328
  ...deferredFields });
@@ -5376,6 +5715,16 @@ function findUsagesInCode(code, name, parser, tree, options = {}) {
5376
5715
  importAliasLocal = true;
5377
5716
  }
5378
5717
  }
5718
+ // `const { default: name } = await import('m')` / `require('m')`:
5719
+ // the local of the module's default value is an import (fix #423).
5720
+ else if (parent.type === 'pair_pattern' && sameNode(parent.childForFieldName('value'), node) &&
5721
+ parent.childForFieldName('key')?.text === 'default' &&
5722
+ parent.parent?.type === 'object_pattern' &&
5723
+ parent.parent.parent?.type === 'variable_declarator' &&
5724
+ sameNode(parent.parent.parent.childForFieldName('name'), parent.parent) &&
5725
+ isImportInitializerNode(parent.parent.parent.childForFieldName('value'))) {
5726
+ usageType = 'import';
5727
+ }
5379
5728
  // Call: identifier is function in call_expression
5380
5729
  else if (parent.type === 'call_expression' &&
5381
5730
  sameNode(parent.childForFieldName('function'), node)) {
@@ -5449,10 +5798,13 @@ function findUsagesInCode(code, name, parser, tree, options = {}) {
5449
5798
  // Check if the object_pattern is part of a variable_declarator with require()
5450
5799
  const declarator = parent.parent;
5451
5800
  if (declarator && declarator.type === 'variable_declarator') {
5452
- const value = declarator.childForFieldName('value');
5801
+ let value = declarator.childForFieldName('value');
5802
+ // `const { name } = await import('m')` (fix #423).
5803
+ while (value && (value.type === 'await_expression' ||
5804
+ value.type === 'parenthesized_expression')) value = value.namedChild(0);
5453
5805
  if (value && value.type === 'call_expression') {
5454
5806
  const func = value.childForFieldName('function');
5455
- if (func && func.text === 'require') {
5807
+ if (func && (func.text === 'require' || func.type === 'import')) {
5456
5808
  usageType = 'import';
5457
5809
  }
5458
5810
  }