ucn 5.1.1 → 5.2.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.
@@ -313,6 +313,7 @@ const allSourceTreeBySelected = new WeakMap();
313
313
  // name merely to rediscover the same handful of macro definitions. Trees are
314
314
  // immutable, so retain the exact AST node set without retaining dead trees.
315
315
  const macroDefinitionsByTree = new WeakMap();
316
+ const macroParamEffectsByDefinition = new WeakMap();
316
317
 
317
318
  function macroDefinitionNodes(tree) {
318
319
  const cached = macroDefinitionsByTree.get(tree);
@@ -512,27 +513,18 @@ function conditionalRecoverySources(code) {
512
513
  }
513
514
 
514
515
  function treeStructureScore(tree) {
515
- let declarations = 0;
516
- let calls = 0;
517
516
  const declarationTypes = new Set([
518
517
  'function_definition', 'class_specifier', 'struct_specifier',
519
518
  'union_specifier', 'enum_specifier', 'type_definition',
520
519
  ]);
521
- const cursor = tree.walk();
522
- let entered = true;
523
- while (entered) {
524
- const type = cursor.nodeType;
525
- if (declarationTypes.has(type)) declarations++;
526
- else if (type === 'call_expression') calls++;
527
- if (cursor.gotoFirstChild()) continue;
528
- while (!cursor.gotoNextSibling()) {
529
- if (!cursor.gotoParent()) {
530
- entered = false;
531
- break;
532
- }
533
- }
534
- }
535
- cursor.delete?.();
520
+ // `descendantsOfType` performs the filtering in tree-sitter's native
521
+ // cursor. Recovery can score the same large source under as many as 14
522
+ // bounded preprocessor views; walking every node through the JS bridge
523
+ // made scoring alone a material part of cold C/C++ build CPU. The native
524
+ // query returns the exact same node sets and therefore preserves the
525
+ // recovery ordering contract while avoiding thousands of wrapper calls.
526
+ const declarations = tree.rootNode.descendantsOfType([...declarationTypes]).length;
527
+ const calls = tree.rootNode.descendantsOfType('call_expression').length;
536
528
  return declarations * 1000 + calls;
537
529
  }
538
530
 
@@ -964,6 +956,24 @@ function isTemplateDependentCallable(node) {
964
956
  return false;
965
957
  }
966
958
 
959
+ // `template <> bool f<bool>(...)` is a FULL specialization: the same compiler
960
+ // symbol as its primary template, selected by substitution rather than
961
+ // overload resolution (fix #299). Every enclosing template head must be
962
+ // empty — one non-empty parameter list means a member of a class template
963
+ // (partial-specialization territory for classes; ordinary dependence here).
964
+ function isFullSpecializationCallable(node) {
965
+ let sawTemplateHead = false;
966
+ for (let parent = node?.parent; parent; parent = parent.parent) {
967
+ if (parent.type === 'template_declaration') {
968
+ const params = parent.childForFieldName('parameters');
969
+ if (!params || params.namedChildCount > 0) return false;
970
+ sawTemplateHead = true;
971
+ }
972
+ if (parent.type === 'translation_unit') break;
973
+ }
974
+ return sawTemplateHead;
975
+ }
976
+
967
977
  // Full type text for a NAMED parameter: the parameter's own text with the
968
978
  // name removed — `const char *s` → `const char *`, `int **pp` → `int **`,
969
979
  // `void (*cb)(int)` → `void (*)(int)`. Reading around the identifier keeps
@@ -1217,6 +1227,9 @@ function memberFromNode(node, className, access, lines, mode) {
1217
1227
  ...(mode === 'cpp' && isTemplateDependentCallable(node) && {
1218
1228
  templateDependent: true,
1219
1229
  }),
1230
+ ...(mode === 'cpp' && isFullSpecializationCallable(node) && {
1231
+ isSpecialization: true,
1232
+ }),
1220
1233
  ...(mode === 'cpp' && cLanguageLinkage(node) && {
1221
1234
  linkage: cLanguageLinkage(node),
1222
1235
  }),
@@ -1516,6 +1529,9 @@ function findFunctionsInTree(code, tree, mode, sourceLines = null) {
1516
1529
  ...(mode === 'cpp' && isTemplateDependentCallable(node) && {
1517
1530
  templateDependent: true,
1518
1531
  }),
1532
+ ...(mode === 'cpp' && isFullSpecializationCallable(node) && {
1533
+ isSpecialization: true,
1534
+ }),
1519
1535
  ...(returnedConcreteType && { returnedConcreteType }),
1520
1536
  ...(mode === 'cpp' && cLanguageLinkage(node) && {
1521
1537
  linkage: cLanguageLinkage(node),
@@ -1658,7 +1674,112 @@ function findStateObjects(code, parser) {
1658
1674
  item => `${item.name}:${item.startLine}`) : primary;
1659
1675
  }
1660
1676
 
1661
- function findMacrosInTree(tree, lines) {
1677
+ function collectMacroParameterEffects(nestedTree, parameters) {
1678
+ const effects = [];
1679
+ traverseTree(nestedTree.rootNode, node => {
1680
+ // Token-paste after a global qualifier (`::_##call`) is not C++
1681
+ // until preprocessing. tree-sitter preserves the `#call` token as
1682
+ // a preprocessor node next to an AST global qualified identifier;
1683
+ // that exact shape still proves the substituted callable is
1684
+ // globally qualified.
1685
+ if (node.type === 'preproc_directive' &&
1686
+ node.text.startsWith('#')) {
1687
+ const paramIndex = parameters.indexOf(node.text.slice(1));
1688
+ let globalSibling = false;
1689
+ let container = node.parent;
1690
+ for (let hops = 0; container && hops < 3 && !globalSibling;
1691
+ hops++, container = container.parent) {
1692
+ const stack = [...(container.namedChildren || [])];
1693
+ while (stack.length > 0 && !globalSibling) {
1694
+ const sibling = stack.pop();
1695
+ if (sibling === node) continue;
1696
+ if (sibling.type === 'qualified_identifier' &&
1697
+ sibling.children?.[0]?.type === '::') {
1698
+ globalSibling = true;
1699
+ break;
1700
+ }
1701
+ for (const child of sibling.namedChildren || []) {
1702
+ stack.push(child);
1703
+ }
1704
+ }
1705
+ }
1706
+ if (paramIndex >= 0 && globalSibling) {
1707
+ effects.push({
1708
+ paramIndex,
1709
+ kind: 'qualified',
1710
+ qualifier: 'global',
1711
+ });
1712
+ }
1713
+ return true;
1714
+ }
1715
+ if (node.type !== 'identifier') return true;
1716
+ const paramIndex = parameters.indexOf(node.text);
1717
+ if (paramIndex < 0) return true;
1718
+ const parent = node.parent;
1719
+ if (parent?.type === 'qualified_identifier' &&
1720
+ parent.childForFieldName('name') === node) {
1721
+ const scope = parent.childForFieldName('scope');
1722
+ effects.push({
1723
+ paramIndex,
1724
+ kind: 'qualified',
1725
+ qualifier: scope?.text || 'global',
1726
+ });
1727
+ return true;
1728
+ }
1729
+ if (parent?.type === 'argument_list' &&
1730
+ parent.parent?.type === 'call_expression') {
1731
+ const wrapper = callIdentity(
1732
+ parent.parent.childForFieldName('function'));
1733
+ if (!wrapper.name || !isMacroToken(wrapper.name)) return true;
1734
+ const args = (parent.namedChildren || [])
1735
+ .filter(child => !child.type.endsWith('comment'));
1736
+ const argIndex = args.findIndex(argument =>
1737
+ argument.startIndex <= node.startIndex &&
1738
+ node.endIndex <= argument.endIndex);
1739
+ if (argIndex >= 0) {
1740
+ effects.push({
1741
+ paramIndex,
1742
+ kind: 'forwarded',
1743
+ macro: wrapper.name,
1744
+ argIndex,
1745
+ });
1746
+ }
1747
+ }
1748
+ return true;
1749
+ });
1750
+ return effects;
1751
+ }
1752
+
1753
+ function macroParameterEffects(definitionNode, valueNode, paramsNode, parser) {
1754
+ if (definitionNode && macroParamEffectsByDefinition.has(definitionNode)) {
1755
+ return macroParamEffectsByDefinition.get(definitionNode);
1756
+ }
1757
+ if (!valueNode || !paramsNode || !parser) return [];
1758
+ const parameters = (paramsNode.namedChildren || [])
1759
+ .filter(child => child.type === 'identifier')
1760
+ .map(child => child.text);
1761
+ if (parameters.length === 0) return [];
1762
+ const body = valueNode.text.replace(/\\(?=\r?\n)/g, ' ');
1763
+ // Cheap parse-avoidance only; every semantic decision below comes from
1764
+ // the replacement list's tree. Ordinary value/punctuation macros need no
1765
+ // extra native tree.
1766
+ if (!body.includes('::') &&
1767
+ !/[A-Z_][A-Z0-9_]*\s*\(/.test(body)) return [];
1768
+ const prefix = 'void __ucn_macro_effect__(void) {\n';
1769
+ const synthetic = `${prefix}${body}\n;}`;
1770
+ const nestedTree = safeParse(parser, synthetic, undefined, PARSE_OPTIONS);
1771
+ try {
1772
+ const effects = collectMacroParameterEffects(nestedTree, parameters);
1773
+ if (definitionNode) {
1774
+ macroParamEffectsByDefinition.set(definitionNode, effects);
1775
+ }
1776
+ return effects;
1777
+ } finally {
1778
+ nestedTree.delete?.();
1779
+ }
1780
+ }
1781
+
1782
+ function findMacrosInTree(tree, lines, parser) {
1662
1783
  const macros = [];
1663
1784
  for (const node of macroDefinitionNodes(tree)) {
1664
1785
  const nameNode = node.childForFieldName('name') ||
@@ -1684,6 +1805,17 @@ function findMacrosInTree(tree, lines) {
1684
1805
  : undefined,
1685
1806
  modifiers: [],
1686
1807
  functionLike: node.type === 'preproc_function_def',
1808
+ ...(() => {
1809
+ const effects = macroParameterEffects(
1810
+ node,
1811
+ node.childForFieldName('value') ||
1812
+ (node.namedChildren || []).find(child =>
1813
+ child.type === 'preproc_arg'),
1814
+ paramsNode,
1815
+ parser);
1816
+ return effects.length > 0
1817
+ ? { macroParamEffects: effects } : {};
1818
+ })(),
1687
1819
  docstring: extractJSDocstring(lines, startLine),
1688
1820
  });
1689
1821
  }
@@ -1693,10 +1825,10 @@ function findMacrosInTree(tree, lines) {
1693
1825
  function findMacros(code, parser) {
1694
1826
  const tree = parseTree(parser, code);
1695
1827
  const lines = code.split('\n');
1696
- const primary = findMacrosInTree(tree, lines);
1828
+ const primary = findMacrosInTree(tree, lines, parser);
1697
1829
  const literal = literalRecoveryTree(parser, code, tree);
1698
1830
  return literal ? mergeExtracted(primary,
1699
- findMacrosInTree(literal, lines),
1831
+ findMacrosInTree(literal, lines, parser),
1700
1832
  item => `${item.name}:${item.startLine}:${item.functionLike ? 1 : 0}`) : primary;
1701
1833
  }
1702
1834
 
@@ -1789,6 +1921,23 @@ function buildVariableTypes(tree) {
1789
1921
  // block declaration.
1790
1922
  const memberReceiverUses = [];
1791
1923
  const ambiguousDirectInitializers = [];
1924
+ const declaratorStaticType = (type, declarator) => {
1925
+ // The declaration's type node carries only the base type. Preserve
1926
+ // array rank from the AST declarator for overload arguments:
1927
+ // `wchar_t format_str[]; runtime(format_str)` passes a wide-character
1928
+ // array (and decays to wchar_t*), not a scalar wchar_t. Receiver
1929
+ // typing continues to use the base `type`; only static argument shape
1930
+ // consumes this fuller spelling.
1931
+ let arrays = 0;
1932
+ const stack = [declarator];
1933
+ while (stack.length > 0) {
1934
+ const current = stack.pop();
1935
+ if (!current) continue;
1936
+ if (current.type === 'array_declarator') arrays++;
1937
+ for (const child of current.namedChildren || []) stack.push(child);
1938
+ }
1939
+ return arrays > 0 ? `${type}${'[]'.repeat(arrays)}` : type;
1940
+ };
1792
1941
  const addBindings = (node, type, pointeeType, scope, declarators) => {
1793
1942
  for (const declarator of declarators) {
1794
1943
  const identity = declaratorIdentity(declarator);
@@ -1796,6 +1945,7 @@ function buildVariableTypes(tree) {
1796
1945
  bindings.push({
1797
1946
  name: identity.name,
1798
1947
  type,
1948
+ staticType: declaratorStaticType(type, declarator),
1799
1949
  ...(pointeeType && { pointeeType }),
1800
1950
  declaredAt: node.type === 'parameter_declaration'
1801
1951
  ? scope.startIndex : declarator.startIndex,
@@ -1893,6 +2043,7 @@ function buildVariableTypes(tree) {
1893
2043
  };
1894
2044
  return {
1895
2045
  get: (name, atNode) => resolveBinding(name, atNode)?.type,
2046
+ getStatic: (name, atNode) => resolveBinding(name, atNode)?.staticType,
1896
2047
  getPointee: (name, atNode) =>
1897
2048
  resolveBinding(name, atNode)?.pointeeType,
1898
2049
  has: (name, atNode) => resolveBinding(name, atNode) !== undefined,
@@ -1963,7 +2114,8 @@ function staticArgumentKind(node, variableTypes) {
1963
2114
  if (node.type === 'true' || node.type === 'false') return 'bool';
1964
2115
  if (node.type === 'null' || node.type === 'nullptr') return 'null';
1965
2116
  if (node.type === 'identifier') {
1966
- const type = variableTypes?.get(node.text, node);
2117
+ const type = variableTypes?.getStatic(node.text, node) ||
2118
+ variableTypes?.get(node.text, node);
1967
2119
  return type ? `type:${type}` : 'expr';
1968
2120
  }
1969
2121
  if (node.type === 'compound_literal_expression') {
@@ -1998,6 +2150,18 @@ function staticArgumentKind(node, variableTypes) {
1998
2150
  const type = typeName(typeNode);
1999
2151
  if (type) return `type:${type}`;
2000
2152
  }
2153
+ // Bare-identifier producers are name-resolvable (fix #299B): mark
2154
+ // them `bcall:` so the overload discipline can type the argument
2155
+ // from the producer's declared return type. A local callable
2156
+ // variable shadows the project name — those record plain 'expr'
2157
+ // (the #203 localShadow rule at kind-recording time). Member,
2158
+ // qualified, and template-explicit producers keep `call:NAME`.
2159
+ if (identity.name && !identity.isMethod && !identity.isPathCall &&
2160
+ identity.nameNode && IDENTIFIER_NODES.has(identity.nameNode.type) &&
2161
+ fnNode && IDENTIFIER_NODES.has(fnNode.type)) {
2162
+ if (variableTypes?.get(identity.name, node)) return 'expr';
2163
+ return `bcall:${identity.name}`;
2164
+ }
2001
2165
  return identity.name ? `call:${identity.name}` : 'expr';
2002
2166
  }
2003
2167
  return 'expr';
@@ -2202,6 +2366,46 @@ function fieldReceiverPath(node) {
2202
2366
  return { root: base.root, fields: [...base.fields, field.text] };
2203
2367
  }
2204
2368
 
2369
+ /** Macro invocations whose argument expression contains this call node. */
2370
+ function enclosingMacroArguments(node) {
2371
+ const wrappers = [];
2372
+ let current = node;
2373
+ let hops = 0;
2374
+ while (current?.parent && hops++ < 24) {
2375
+ const argumentsNode = current.parent;
2376
+ const outerCall = argumentsNode?.type === 'argument_list'
2377
+ ? argumentsNode.parent : null;
2378
+ if (outerCall?.type === 'call_expression') {
2379
+ const wrapper = callIdentity(
2380
+ outerCall.childForFieldName('function'));
2381
+ if (wrapper.name && isMacroToken(wrapper.name)) {
2382
+ const args = (argumentsNode.namedChildren || [])
2383
+ .filter(child => !child.type.endsWith('comment'));
2384
+ const argIndex = args.findIndex(argument =>
2385
+ argument.startIndex <= node.startIndex &&
2386
+ node.endIndex <= argument.endIndex);
2387
+ if (argIndex >= 0) {
2388
+ wrappers.push({ name: wrapper.name, argIndex });
2389
+ }
2390
+ }
2391
+ current = outerCall;
2392
+ continue;
2393
+ }
2394
+ // Once the walk leaves an expression, no outer macro invocation can
2395
+ // contain this call. Stopping here keeps the common non-macro path
2396
+ // constant-depth instead of climbing every call to the translation
2397
+ // unit (material on template-heavy fmt headers).
2398
+ if (/(?:statement|declaration|definition)$/.test(
2399
+ argumentsNode.type) ||
2400
+ argumentsNode.type === 'translation_unit' ||
2401
+ argumentsNode.type === 'init_declarator') {
2402
+ break;
2403
+ }
2404
+ current = current.parent;
2405
+ }
2406
+ return wrappers;
2407
+ }
2408
+
2205
2409
  function findCallsInTree(code, parser, _options = {}, existingTree = null,
2206
2410
  includeMacroBodies = true) {
2207
2411
  const tree = existingTree || parseTree(parser, code);
@@ -2281,6 +2485,7 @@ function findCallsInTree(code, parser, _options = {}, existingTree = null,
2281
2485
  : undefined;
2282
2486
  const assignedTo = assignmentTargetOf(node);
2283
2487
  const compileTimeOnly = compileTimeOnlyContext(node);
2488
+ const macroArguments = enclosingMacroArguments(node);
2284
2489
  calls.push({
2285
2490
  name: identity.name,
2286
2491
  line: identity.nameNode?.startPosition.row + 1 || node.startPosition.row + 1,
@@ -2295,6 +2500,7 @@ function findCallsInTree(code, parser, _options = {}, existingTree = null,
2295
2500
  explicitTemplateCall: true,
2296
2501
  }),
2297
2502
  ...(compileTimeOnly && { compileTimeOnly }),
2503
+ ...(macroArguments.length > 0 && { macroArguments }),
2298
2504
  ...(directReceiverType && { receiverType: directReceiverType }),
2299
2505
  ...(receiverCall && {
2300
2506
  receiverCall,
@@ -2474,9 +2680,10 @@ function findMacroBodyCalls(tree, code, parser, onlyName = null) {
2474
2680
  if (onlyName && !valueNode.text.includes(onlyName)) continue;
2475
2681
  const paramsNode = node.childForFieldName('parameters') ||
2476
2682
  (node.namedChildren || []).find(child => child.type === 'preproc_params');
2477
- const parameters = new Set((paramsNode?.namedChildren || [])
2683
+ const parameterNames = (paramsNode?.namedChildren || [])
2478
2684
  .filter(child => child.type === 'identifier')
2479
- .map(child => child.text));
2685
+ .map(child => child.text);
2686
+ const parameters = new Set(parameterNames);
2480
2687
  // Replace only the continuation backslash. Keeping the newline and
2481
2688
  // every other byte makes source-line/column and span remapping exact.
2482
2689
  const body = valueNode.text.replace(/\\(?=\r?\n)/g, ' ');
@@ -2490,6 +2697,12 @@ function findMacroBodyCalls(tree, code, parser, onlyName = null) {
2490
2697
  const nestedTree = safeParse(parser, synthetic, undefined, PARSE_OPTIONS);
2491
2698
  let nested;
2492
2699
  try {
2700
+ macroParamEffectsByDefinition.set(
2701
+ node,
2702
+ body.includes('::') ||
2703
+ /[A-Z_][A-Z0-9_]*\s*\(/.test(body)
2704
+ ? collectMacroParameterEffects(nestedTree, parameterNames)
2705
+ : []);
2493
2706
  nested = findCallsInCode(synthetic, parser, {}, nestedTree, false);
2494
2707
  } finally {
2495
2708
  nestedTree.delete?.();
@@ -2725,10 +2938,10 @@ function parse(code, parser, mode, options = {}) {
2725
2938
  item => `${item.name}:${item.startLine}`)
2726
2939
  : findStateObjectsInTree(tree, lines),
2727
2940
  macros: literal
2728
- ? mergeExtracted(findMacrosInTree(tree, lines),
2729
- findMacrosInTree(literal, lines),
2941
+ ? mergeExtracted(findMacrosInTree(tree, lines, parser),
2942
+ findMacrosInTree(literal, lines, parser),
2730
2943
  item => `${item.name}:${item.startLine}:${item.functionLike ? 1 : 0}`)
2731
- : findMacrosInTree(tree, lines),
2944
+ : findMacrosInTree(tree, lines, parser),
2732
2945
  imports,
2733
2946
  exports: [
2734
2947
  ...functions
@@ -293,7 +293,7 @@ function propertyMember(node, lines) {
293
293
  endLine,
294
294
  indent,
295
295
  modifiers: modifiersOf(node),
296
- memberType: 'field',
296
+ memberType: 'property',
297
297
  fieldType: typeNode?.text || null,
298
298
  };
299
299
  }
@@ -1155,10 +1155,21 @@ function findCallsInCode(code, parser) {
1155
1155
  : functionNode?.type === 'conditional_access_expression'
1156
1156
  ? functionNode.childForFieldName('condition') || functionNode.namedChild(0)
1157
1157
  : null;
1158
- const receiverTypeInfo = receiverTypeFromNode(receiverNode, variableTypes) ||
1159
- normalizeReceiverType(receiverRoot && variableTypes.get(receiverRoot));
1160
- const receiverType = receiverTypeInfo?.name;
1161
1158
  const unwrappedReceiverNode = unwrapReceiverNode(receiverNode);
1159
+ // A root variable's type is the receiver type only for a direct
1160
+ // `value.Method()` call. For `value.Property.Method()` the static
1161
+ // receiver type is the property's declared type, not `value`'s
1162
+ // type. Preserve that shape as a field path for query-time
1163
+ // declaration walking; collapsing it to the root class falsely
1164
+ // confirms sibling overrides (Newtonsoft JProperty.Value is a
1165
+ // JToken, not a JProperty).
1166
+ const receiverTypeInfo = receiverTypeFromNode(
1167
+ receiverNode, variableTypes) ||
1168
+ (unwrappedReceiverNode?.type === 'identifier'
1169
+ ? normalizeReceiverType(
1170
+ receiverRoot && variableTypes.get(receiverRoot))
1171
+ : null);
1172
+ const receiverType = receiverTypeInfo?.name;
1162
1173
  const receiverCastThis = receiverCastIsThis(receiverNode);
1163
1174
  const receiverIsTypeQualified = !!(identity.isMethod &&
1164
1175
  unwrappedReceiverNode?.type === 'identifier' &&
@@ -1301,6 +1312,7 @@ function findImportsInCode(code, parser) {
1301
1312
  function findUsagesInCode(code, name, parser, existingTree) {
1302
1313
  const tree = existingTree || parseTree(parser, code);
1303
1314
  const usages = [];
1315
+ const variableTypesByScope = buildVariableTypes(tree, parser);
1304
1316
  visitNameNodes(tree, code, name, node => {
1305
1317
  if (!IDENTIFIER_NODES.has(node.type) || node.text !== name) return;
1306
1318
  let usageType = 'reference';
@@ -1309,6 +1321,8 @@ function findUsagesInCode(code, name, parser, existingTree) {
1309
1321
  if ((parent.type === 'method_declaration' ||
1310
1322
  parent.type === 'constructor_declaration' ||
1311
1323
  TYPE_DECLARATIONS.has(parent.type) ||
1324
+ parent.type === 'property_declaration' ||
1325
+ parent.type === 'event_declaration' ||
1312
1326
  parent.type === 'parameter' ||
1313
1327
  parent.type === 'variable_declarator') &&
1314
1328
  (sameNode(parent.childForFieldName('name'), node))) {
@@ -1319,6 +1333,28 @@ function findUsagesInCode(code, name, parser, existingTree) {
1319
1333
  } else if (parent.type === 'using_directive') {
1320
1334
  usageType = 'import';
1321
1335
  }
1336
+ if (parent.type === 'member_access_expression' &&
1337
+ sameNode(parent.childForFieldName('name'), node)) {
1338
+ const receiverNode = parent.childForFieldName('expression') ||
1339
+ parent.namedChild(0);
1340
+ const receiver = receiverNode?.text;
1341
+ const scopeTypes = variableTypesByScope.get(variableScopeKey(node)) ||
1342
+ variableTypesByScope.get('global');
1343
+ const declared = receiverNode?.type === 'identifier'
1344
+ ? normalizeReceiverType(scopeTypes?.get(receiver)) : null;
1345
+ const sameClass = ['this', 'base'].includes(receiver)
1346
+ ? enclosingClassName(node) : null;
1347
+ usages.push({
1348
+ line: node.startPosition.row + 1,
1349
+ column: node.startPosition.column,
1350
+ usageType,
1351
+ ...(receiver && { receiver }),
1352
+ ...((declared?.name || sameClass) && {
1353
+ receiverType: declared?.name || sameClass,
1354
+ }),
1355
+ });
1356
+ return true;
1357
+ }
1322
1358
  }
1323
1359
  usages.push({
1324
1360
  line: node.startPosition.row + 1,