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.
- package/.claude/skills/ucn/SKILL.md +26 -5
- package/.claude/skills/ucn/references/commands.md +5 -5
- package/README.md +42 -15
- package/core/accessors.js +183 -0
- package/core/analysis.js +46 -0
- package/core/ast-analysis.js +104 -0
- package/core/cache.js +32 -1
- package/core/callers.js +1255 -64
- package/core/command-contracts.js +13 -13
- package/core/deadcode.js +41 -2
- package/core/execute.js +4 -1
- package/core/graph-build.js +6 -0
- package/core/graph.js +72 -5
- package/core/index-ir.js +13 -2
- package/core/ir.js +5 -3
- package/core/output/analysis.js +30 -1
- package/core/output/graph.js +28 -8
- package/core/output/public.js +4 -0
- package/core/output/refactoring.js +31 -2
- package/core/output/reporting.js +7 -0
- package/core/project.js +32 -0
- package/core/search.js +9 -0
- package/core/verify.js +1168 -38
- package/languages/c-family.js +239 -26
- package/languages/csharp.js +40 -4
- package/languages/go.js +473 -71
- package/languages/javascript.js +86 -4
- package/languages/python.js +392 -101
- package/languages/rust.js +87 -4
- package/languages/utils.js +11 -0
- package/mcp/server.js +99 -103
- package/mcp/stdio-server.js +296 -0
- package/package.json +10 -8
package/languages/c-family.js
CHANGED
|
@@ -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
|
-
|
|
522
|
-
|
|
523
|
-
|
|
524
|
-
|
|
525
|
-
|
|
526
|
-
|
|
527
|
-
|
|
528
|
-
|
|
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
|
|
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?.
|
|
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
|
|
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
|
package/languages/csharp.js
CHANGED
|
@@ -293,7 +293,7 @@ function propertyMember(node, lines) {
|
|
|
293
293
|
endLine,
|
|
294
294
|
indent,
|
|
295
295
|
modifiers: modifiersOf(node),
|
|
296
|
-
memberType: '
|
|
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,
|