ucn 5.5.0 → 5.5.2

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 (93) hide show
  1. package/.claude/skills/ucn/SKILL.md +35 -9
  2. package/.claude/skills/ucn/references/commands.md +38 -16
  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 +41 -27
  6. package/cli/index.js +6 -1
  7. package/core/accessors.js +341 -54
  8. package/core/account.js +106 -31
  9. package/core/analysis.js +266 -127
  10. package/core/ast-analysis.js +12 -3
  11. package/core/brief.js +8 -4
  12. package/core/build-worker.js +2 -1
  13. package/core/cache.js +144 -6
  14. package/core/call-name-cache.js +134 -0
  15. package/core/callable-scope.js +21 -0
  16. package/core/callers.js +5111 -1024
  17. package/core/check.js +22 -5
  18. package/core/class-identity.js +399 -12
  19. package/core/constructor-calls.js +831 -0
  20. package/core/constructor-hierarchy.js +147 -0
  21. package/core/constructor-protocol.js +196 -0
  22. package/core/contract-membership.js +346 -140
  23. package/core/cpp-conversion-constraints.js +123 -0
  24. package/core/cpp-conversion-syntax.js +216 -0
  25. package/core/cpp-conversions.js +414 -0
  26. package/core/cpp-scope.js +245 -44
  27. package/core/deadcode.js +35 -15
  28. package/core/decorator-binding.js +137 -3
  29. package/core/enum-reference-syntax.js +134 -0
  30. package/core/execute.js +17 -1
  31. package/core/external-macros.js +42 -10
  32. package/core/go-packages.js +171 -0
  33. package/core/graph-build.js +2 -0
  34. package/core/graph.js +75 -59
  35. package/core/ignore-rules.js +2 -1
  36. package/core/imports.js +145 -19
  37. package/core/index-ir.js +41 -4
  38. package/core/ir.js +27 -6
  39. package/core/macro-expansion.js +38 -2
  40. package/core/out-of-class-members.js +182 -0
  41. package/core/output/analysis-ext.js +4 -2
  42. package/core/output/analysis.js +89 -20
  43. package/core/output/graph.js +12 -3
  44. package/core/output/lines.js +4 -3
  45. package/core/output/public.js +1 -0
  46. package/core/output/reporting.js +6 -1
  47. package/core/output/shared.js +2 -0
  48. package/core/output/tracing.js +28 -22
  49. package/core/output-budget.js +2 -2
  50. package/core/parallel-build.js +52 -1
  51. package/core/project.js +228 -114
  52. package/core/provenance-constructor-base.js +161 -0
  53. package/core/provenance-facts.js +257 -27
  54. package/core/provenance-overload.js +458 -54
  55. package/core/provenance-type.js +487 -0
  56. package/core/provenance.js +234 -15
  57. package/core/public-command.js +17 -2
  58. package/core/query-bodies.js +159 -0
  59. package/core/reflection.js +255 -2
  60. package/core/rename-token-syntax.js +111 -0
  61. package/core/reporting.js +13 -7
  62. package/core/rust-macro-expansion.js +1202 -157
  63. package/core/rust-modules.js +21 -3
  64. package/core/rust-names.js +607 -0
  65. package/core/search.js +70 -17
  66. package/core/shared.js +98 -1
  67. package/core/source-memo.js +201 -0
  68. package/core/text-usage-syntax.js +38 -0
  69. package/core/tracing.js +7 -1
  70. package/core/type-denotation.js +157 -46
  71. package/core/type-references.js +615 -246
  72. package/core/value-references.js +2777 -0
  73. package/core/verify.js +1097 -281
  74. package/languages/adapter.js +28 -4
  75. package/languages/c-family.js +1118 -164
  76. package/languages/constructor-bases.js +105 -0
  77. package/languages/cpp-platform.js +26 -0
  78. package/languages/cpp-site-syntax.js +83 -0
  79. package/languages/csharp-scope.js +439 -0
  80. package/languages/csharp.js +428 -68
  81. package/languages/go.js +385 -207
  82. package/languages/html.js +4 -2
  83. package/languages/index.js +397 -2
  84. package/languages/java-platform.js +226 -0
  85. package/languages/java-unicode.js +269 -0
  86. package/languages/java.js +461 -117
  87. package/languages/javascript.js +418 -66
  88. package/languages/lexical-scope.js +190 -23
  89. package/languages/python.js +404 -54
  90. package/languages/rust.js +548 -89
  91. package/languages/utils.js +67 -8
  92. package/mcp/server.js +8 -2
  93. package/package.json +2 -2
@@ -0,0 +1,105 @@
1
+ 'use strict';
2
+
3
+ const { typeOrigin } = require('./type-evidence');
4
+ const { sameNode, genericArityOf, nodeTextWithoutComments } = require('./utils');
5
+
6
+ /** A type's last AST name and its written qualifier, retaining global roots. */
7
+ function baseSpelling(node, separator) {
8
+ if (!node) return null;
9
+ if (['primary_constructor_base_type', 'generic_type', 'template_type', 'generic_name'].includes(node.type)) {
10
+ return baseSpelling(node.childForFieldName('name') || node.childForFieldName('type') || node.namedChild(0), separator);
11
+ }
12
+ if (['identifier', 'type_identifier', 'field_identifier'].includes(node.type)) return { name: node.text, qualifier: null };
13
+ if (['qualified_identifier', 'qualified_name', 'scoped_type_identifier', 'alias_qualified_name'].includes(node.type)) {
14
+ const last = node.childForFieldName('name') || node.namedChildren.at(-1);
15
+ const tail = baseSpelling(last, separator);
16
+ if (!tail) return null;
17
+ let prefix = node.text.slice(0, last.startIndex - node.startIndex).trim();
18
+ if (prefix.endsWith(separator) && prefix !== separator) prefix = prefix.slice(0, -separator.length).trim();
19
+ return { name: tail.name, qualifier: prefix || tail.qualifier };
20
+ }
21
+ return null;
22
+ }
23
+
24
+ const spellingKey = spelling => spelling && `${spelling.qualifier || ''}\0${spelling.name}`;
25
+
26
+ /** Calls the language supplies when a constructor omits its base initializer. */
27
+ function implicitBaseCalls(root, language, reaches, owners = null) {
28
+ const config = require('./index').langTraits(language)?.implicitBaseConstruction;
29
+ if (!config) return [];
30
+ const calls = [];
31
+ for (const owner of owners || root.descendantsOfType(config.classes)) {
32
+ if (owner.hasError || reaches && !reaches(owner) ||
33
+ owner.children.some(child => child.type === 'struct') && config.referenceTypesOnly) continue;
34
+ const nameNode = owner.childForFieldName('name');
35
+ const ownerName = baseSpelling(nameNode, config.separator)?.name;
36
+ if (!ownerName) continue;
37
+ const header = config.headerField ? owner.childForFieldName(config.headerField)
38
+ : owner.namedChildren.find(child => child.type === config.headerNode);
39
+ if (!header) continue;
40
+ let bases = header.namedChildren.filter(child => baseSpelling(child, config.separator));
41
+ if (config.firstBaseOnly) bases = bases.slice(0, 1);
42
+ if (!bases.length || header.namedChildren.some(child => child.type === 'argument_list') ||
43
+ bases.some(base => base.namedChildren.some(child => child.type === 'argument_list'))) continue;
44
+ const scope = config.ownerScope?.(owner) || {};
45
+ const body = owner.childForFieldName('body');
46
+ const constructors = (body?.namedChildren || []).filter(node => config.constructors.includes(node.type))
47
+ .map(node => {
48
+ if (config.constructorInfo) return config.constructorInfo(node, ownerName, owner);
49
+ const name = node.childForFieldName('name');
50
+ if (name?.text !== ownerName || node.namedChildren.some(child => child.type === 'modifier' && child.text === 'static')) return null;
51
+ const delegated = node.descendantsOfType(config.delegationNodes).some(call => {
52
+ for (let parent = call.parent; parent; parent = parent.parent) {
53
+ if (sameNode(parent, node)) return true;
54
+ if (config.classes.includes(parent.type) || config.constructors.includes(parent.type)) return false;
55
+ }
56
+ return false;
57
+ });
58
+ return { node, name, body: node.childForFieldName('body'), suppressed: delegated };
59
+ }).filter(Boolean);
60
+ const sources = constructors.length ? constructors : [{ node: owner, name: nameNode, body,
61
+ potential: !!config.defaultPotential, generated: true }];
62
+ for (const source of sources) {
63
+ if (source.suppressed || !source.body || reaches && !reaches(source.node)) continue;
64
+ const initialized = source.initialized || [];
65
+ for (const base of bases) {
66
+ const spelling = baseSpelling(base, config.separator);
67
+ if (initialized.some(entry => !entry.field && spellingKey(entry) === spellingKey(spelling))) continue;
68
+ const ambiguous = initialized.some(entry => !entry.field &&
69
+ !bases.some(other => spellingKey(entry) === spellingKey(baseSpelling(other, config.separator))));
70
+ const site = source.name;
71
+ const arity = genericArityOf(base.text);
72
+ const parameters = source.node.childForFieldName('parameters');
73
+ const params = source.params ?? (parameters
74
+ ? nodeTextWithoutComments(parameters).replace(/^\(|\)$/g, '').trim() : null);
75
+ calls.push({ callSite: typeOrigin('call', site), name: spelling.name,
76
+ line: site.startPosition.row + 1, isMethod: false, isConstructor: true,
77
+ constructorSyntax: 'implicit', argCount: 0,
78
+ ...(spelling.qualifier && { constructorQualifier: spelling.qualifier }),
79
+ ...(arity > 0 && { typeArgs: arity }),
80
+ implicitBase: { owner: ownerName, ownerLine: owner.startPosition.row + 1,
81
+ ownerEndLine: owner.endPosition.row + 1, scope,
82
+ generated: !!source.generated, params,
83
+ header: typeOrigin('type', base), arity, potential: !!(source.potential || ambiguous) },
84
+ enclosingFunction: { name: ownerName, startLine: source.node.startPosition.row + 1,
85
+ endLine: source.node.endPosition.row + 1 } });
86
+ }
87
+ }
88
+ }
89
+ return calls;
90
+ }
91
+
92
+ /**
93
+ * The omitted-initializer calls of the classes spanning exactly
94
+ * `ownerLine`..`ownerEndLine` (fix #421): what implicitBaseCalls yields for
95
+ * them in a whole-tree walk, without extracting every other call.
96
+ */
97
+ function implicitBaseCallsOfOwner(root, language, reaches, ownerLine, ownerEndLine) {
98
+ const config = require('./index').langTraits(language)?.implicitBaseConstruction;
99
+ if (!config || !root) return [];
100
+ const owners = root.descendantsOfType(config.classes).filter(node =>
101
+ node.startPosition.row + 1 === ownerLine && node.endPosition.row + 1 === ownerEndLine);
102
+ return owners.length ? implicitBaseCalls(root, language, reaches, owners) : [];
103
+ }
104
+
105
+ module.exports = { implicitBaseCalls, implicitBaseCallsOfOwner, baseSpelling };
@@ -0,0 +1,26 @@
1
+ 'use strict';
2
+
3
+ // Qualified standard-library contracts. Name lookup belongs to cpp-scope;
4
+ // none of these facts apply to a project declaration with the same name.
5
+ const STRING_ALIASES = new Map([
6
+ ['string', 'char'], ['wstring', 'wchar_t'], ['u8string', 'char8_t'],
7
+ ['u16string', 'char16_t'], ['u32string', 'char32_t'],
8
+ ]);
9
+ const CHARACTERS = new Set(['char', 'wchar_t', 'char8_t', 'char16_t', 'char32_t']);
10
+
11
+ function standardStringType(qualified, arguments_ = []) {
12
+ if (!qualified?.startsWith('std::')) return null;
13
+ const name = qualified.slice(5);
14
+ const view = name.endsWith('_view');
15
+ const alias = view ? name.slice(0, -5) : name;
16
+ if (STRING_ALIASES.has(alias) && arguments_.length === 0) {
17
+ return { family: view ? 'view' : 'string', character: STRING_ALIASES.get(alias) };
18
+ }
19
+ if (!['basic_string', 'basic_string_view'].includes(name) || !CHARACTERS.has(arguments_[0])) return null;
20
+ // A specialization with custom traits or allocator is outside this
21
+ // small platform model, as is a project-provided specialization.
22
+ if (arguments_.length > 1) return null;
23
+ return { family: name === 'basic_string' ? 'string' : 'view', character: arguments_[0] };
24
+ }
25
+
26
+ module.exports = { standardStringType, CHARACTERS };
@@ -0,0 +1,83 @@
1
+ 'use strict';
2
+
3
+ const C_TYPE_SPECIFIERS = new Set(['class_specifier', 'struct_specifier', 'union_specifier', 'enum_specifier']);
4
+ const textOf = node => node ? node.text.replace(/\s+/g, '') : '';
5
+ const contains = (outer, inner) => outer.startIndex <= inner.startIndex && outer.endIndex >= inner.endIndex;
6
+
7
+ // Only syntax belongs here. Namespace macros, recovered class owners and
8
+ // include visibility are resolved by the current index at query time.
9
+ function cppSiteSyntax(node) {
10
+ const namespaces = [];
11
+ const classes = [];
12
+ let memberOf = null;
13
+ let recovered = false;
14
+ for (let current = node.parent; current; current = current.parent) {
15
+ if (current.type === 'ERROR') recovered = true;
16
+ if (current.type === 'namespace_definition') {
17
+ const nameNode = current.childForFieldName('name');
18
+ if (nameNode) namespaces.unshift(textOf(nameNode));
19
+ } else if (C_TYPE_SPECIFIERS.has(current.type)) {
20
+ const body = current.childForFieldName('body');
21
+ const nameNode = current.childForFieldName('name');
22
+ if (body && contains(body, node) && nameNode) {
23
+ // Specializations keep their arguments (`Acc<double>`), as
24
+ // the index names them; an out-of-class nested definition
25
+ // (`struct List<K>::Node`) is in its owner's scope too
26
+ // (fix #396).
27
+ const segments = textOf(nameNode).split('::');
28
+ classes.unshift(segments.slice(-2).join('::'));
29
+ }
30
+ } else if (current.type === 'function_definition' && !memberOf && classes.length === 0) {
31
+ let declarator = current.childForFieldName('declarator');
32
+ while (declarator && declarator.type !== 'function_declarator' && declarator.childForFieldName?.('declarator')) {
33
+ declarator = declarator.childForFieldName('declarator');
34
+ }
35
+ const qualified = declarator?.childForFieldName?.('declarator');
36
+ if (qualified?.type === 'qualified_identifier') {
37
+ const scope = qualified.childForFieldName('scope');
38
+ // `List<K>::Node::f`: Node's scope, then List's (fix #396).
39
+ if (scope) memberOf = qualifiedScopeText(qualified) || textOf(scope).split('::').pop();
40
+ }
41
+ }
42
+ }
43
+ return { namespaces, classes, memberOf, recovered };
44
+ }
45
+
46
+ /** The class path a qualified declarator's scope names, without its
47
+ * leading namespace-looking segments: `ns::List<K>::Node::f` -> the scope
48
+ * segments `List<K>::Node` (the last two at most). */
49
+ function qualifiedScopeText(qualified) {
50
+ const scopes = [];
51
+ let current = qualified;
52
+ while (current?.type === 'qualified_identifier') {
53
+ const scope = current.childForFieldName('scope');
54
+ if (scope) scopes.push(textOf(scope));
55
+ current = current.childForFieldName('name');
56
+ }
57
+ return scopes.length > 0 ? scopes.slice(-2).join('::') : null;
58
+ }
59
+
60
+ // Locate the headers the recovered calls ask about in the raw tree.
61
+ // A null syntax records that the exact node is absent, not a binding verdict.
62
+ function cppBaseSyntax(source, parser, positions, tree = null) {
63
+ if (!positions.length) return [];
64
+ const owned = !tree;
65
+ tree ||= require('./index').safeParse(parser, source);
66
+ if (!tree) return null;
67
+ try {
68
+ const rows = [], seen = new Set();
69
+ for (const at of positions) {
70
+ const key = JSON.stringify([at.start, at.end, at.nodeType, at.line, at.column]);
71
+ if (seen.has(key)) continue;
72
+ seen.add(key);
73
+ let node = tree.rootNode.descendantForIndex(at.start);
74
+ while (node && !(node.startIndex === at.start && node.endIndex === at.end && node.type === at.nodeType)) node = node.parent;
75
+ const matches = node && node.startPosition.row + 1 === at.line && node.startPosition.column === at.column;
76
+ rows.push({ start: at.start, end: at.end, nodeType: at.nodeType, line: at.line, column: at.column,
77
+ syntax: matches ? cppSiteSyntax(node) : null });
78
+ }
79
+ return rows;
80
+ } finally { if (owned) tree.delete?.(); }
81
+ }
82
+
83
+ module.exports = { cppSiteSyntax, cppBaseSyntax };
@@ -0,0 +1,439 @@
1
+ 'use strict';
2
+
3
+ // C# lexical binding facts shared by member reference lookups.
4
+ const { sameNode } = require('./utils');
5
+
6
+ // Declarations whose `name` field binds a local of the member body.
7
+ const CSHARP_LOCAL_DECLARATION_NODES = new Set(['parameter', 'catch_declaration', 'declaration_pattern',
8
+ 'declaration_expression', 'variable_declarator', 'local_function_statement', 'from_clause',
9
+ 'join_clause', 'query_continuation', 'recursive_pattern', 'var_pattern']);
10
+
11
+ // A switch expression arm scopes its pattern variables; a while
12
+ // condition's variables are scoped to the statement (fix #419).
13
+ const CSHARP_BLOCK_SCOPES = new Set(['block', 'switch_section', 'for_statement', 'using_statement',
14
+ 'fixed_statement', 'lambda_expression', 'anonymous_method_expression', 'arrow_expression_clause',
15
+ 'switch_expression_arm', 'while_statement']);
16
+
17
+ /**
18
+ * The outermost node of a deconstruction designation holding `node`
19
+ * (fix #419): `var (a, (b, c))` in a declarator, foreach or pattern. The
20
+ * grammar reads `x is var (a, b)` as an invocation of `x is var`, whose
21
+ * arguments are then the designations. Null when `node` is no such name.
22
+ */
23
+ function deconstructionRoot(node) {
24
+ let current = node, root = null;
25
+ while (current.parent && (current.parent.type === 'tuple_pattern' ||
26
+ current.parent.type === 'parenthesized_variable_designation')) {
27
+ current = current.parent;
28
+ root = current;
29
+ }
30
+ if (root) return root;
31
+ // `x is var (a, (b, c))`: argument / tuple_expression nesting under the
32
+ // misread invocation's argument list.
33
+ current = node;
34
+ while (current.parent?.type === 'argument' && current.type !== 'argument' &&
35
+ (current.type === 'identifier' || current.type === 'tuple_expression')) {
36
+ const argument = current.parent, list = argument.parent;
37
+ if (list?.type === 'tuple_expression') { current = list; continue; }
38
+ const call = list?.type === 'argument_list' ? list.parent : null;
39
+ const callee = call?.type === 'invocation_expression' ? call.childForFieldName('function') : null;
40
+ if (callee?.type === 'is_expression' && callee.namedChildren.at(-1)?.type === 'implicit_type' &&
41
+ sameNode(call.childForFieldName('arguments'), list)) return call;
42
+ return null;
43
+ }
44
+ return null;
45
+ }
46
+
47
+ /**
48
+ * Where C# scopes a local named like the reference (spec 7.6.1): 'local' when
49
+ * a parameter, local variable, local function, pattern, lambda, foreach,
50
+ * catch or range variable of that name is in scope at `node`, 'none' when
51
+ * no declaration of the member reaches it, 'unknown' for an unmodelled
52
+ * declaration shape.
53
+ */
54
+ const localBindingsByTree = new WeakMap();
55
+ function bindingVerdict(facts, node) {
56
+ const contains = scope => scope && (scope.ranges ? scope.ranges.some(contains)
57
+ : scope.startIndex <= node.startIndex && node.endIndex <= scope.endIndex);
58
+ let unknown = false;
59
+ for (const fact of facts) {
60
+ if (sameNode(fact.node, node)) continue;
61
+ if (!fact.scope) unknown = true;
62
+ else if (contains(fact.scope)) return 'local';
63
+ }
64
+ return unknown ? 'unknown' : 'none';
65
+ }
66
+
67
+ function csharpLocalBinding(memberNode, node, name) {
68
+ let bindings = localBindingsByTree.get(memberNode.tree);
69
+ if (!bindings) {
70
+ bindings = new (require('../core/source-memo').BoundedMap)({ maxEntries: 512 });
71
+ localBindingsByTree.set(memberNode.tree, bindings);
72
+ }
73
+ const cacheKey = `${memberNode.id}\0${name}`;
74
+ if (bindings.has(cacheKey)) return bindingVerdict(bindings.get(cacheKey), node);
75
+ const contains = (scope, target) => scope && (scope.ranges
76
+ ? scope.ranges.some(range => contains(range, target))
77
+ : scope.startIndex <= target.startIndex && target.endIndex <= scope.endIndex);
78
+ const nearest = (from, kinds) => {
79
+ for (let current = from.parent; current && !sameNode(current, memberNode); current = current.parent) {
80
+ if (kinds.has(current.type)) return current;
81
+ }
82
+ return memberNode;
83
+ };
84
+ const facts = [];
85
+ const snapshot = scope => !scope ? null : scope.ranges
86
+ ? { ranges: scope.ranges.map(snapshot) }
87
+ : { startIndex: scope.startIndex, endIndex: scope.endIndex };
88
+ const queryRange = (from, start, end = null) => {
89
+ const query = nearest(from, new Set(['query_expression']));
90
+ const continuation = query.children?.find(child => child.type === 'into' && child.startIndex > start);
91
+ return { startIndex: start, endIndex: end ?? continuation?.startIndex ?? query.endIndex };
92
+ };
93
+ const scopeOf = (declared) => {
94
+ const parent = declared.parent;
95
+ switch (parent.type) {
96
+ case 'parameter': {
97
+ const owner = parent.parent?.parent;
98
+ return owner || memberNode;
99
+ }
100
+ case 'variable_declarator': case 'local_function_statement':
101
+ case 'declaration_pattern': case 'declaration_expression': case 'single_variable_designation':
102
+ case 'recursive_pattern': case 'var_pattern':
103
+ return nearest(parent, CSHARP_BLOCK_SCOPES);
104
+ case 'catch_declaration':
105
+ return parent.parent;
106
+ case 'foreach_statement':
107
+ return parent.childForFieldName('body');
108
+ case 'tuple_pattern': case 'parenthesized_variable_designation': case 'argument': {
109
+ // A deconstruction: the foreach body for its iteration
110
+ // variables, else the scope of the declaration or pattern
111
+ // that holds it (fix #419).
112
+ const root = deconstructionRoot(declared);
113
+ if (!root) return null;
114
+ const owner = root.parent;
115
+ if (owner?.type === 'foreach_statement' && contains(owner.childForFieldName('left'), declared)) {
116
+ return owner.childForFieldName('body');
117
+ }
118
+ return nearest(root, CSHARP_BLOCK_SCOPES);
119
+ }
120
+ case 'from_clause': case 'let_clause': case 'query_continuation':
121
+ return queryRange(parent, parent.endIndex);
122
+ case 'query_expression':
123
+ return queryRange(declared, declared.endIndex);
124
+ case 'join_into_clause':
125
+ return queryRange(parent, parent.parent.endIndex);
126
+ case 'join_clause': {
127
+ const equals = parent.children.find(child => child.type === 'equals');
128
+ const key = equals && parent.namedChildren.find(child => child.startIndex >= equals.endIndex);
129
+ const ranges = key ? [{ startIndex: key.startIndex, endIndex: key.endIndex }] : [];
130
+ if (!parent.namedChildren.some(child => child.type === 'join_into_clause')) {
131
+ ranges.push(queryRange(parent, parent.endIndex));
132
+ }
133
+ return { ranges };
134
+ }
135
+ default:
136
+ return null;
137
+ }
138
+ };
139
+ const visit = current => {
140
+ if (TYPE_SCOPES.has(current.type)) return;
141
+ if ((current.type === 'implicit_parameter' || current.type === 'identifier') && current.text === name) {
142
+ const parent = current.parent;
143
+ let declares = current.type === 'implicit_parameter';
144
+ if (!declares && parent) {
145
+ declares = (CSHARP_LOCAL_DECLARATION_NODES.has(parent.type) &&
146
+ sameNode(parent.childForFieldName('name'), current) && !sameNode(parent, memberNode)) ||
147
+ (parent.type === 'foreach_statement' && sameNode(parent.childForFieldName('left'), current)) ||
148
+ parent.type === 'single_variable_designation' ||
149
+ (parent.type === 'let_clause' && sameNode(parent.namedChild(0), current)) ||
150
+ rangeBindingToken(current) || !!deconstructionRoot(current);
151
+ }
152
+ if (declares) {
153
+ const scope = current.type === 'implicit_parameter' ? parent : scopeOf(current);
154
+ facts.push({ node: current, scope: snapshot(scope) });
155
+ } else if (parent && UNMODELED_DESIGNATION.test(parent.type)) {
156
+ // A same-name token in a pattern shape this lookup does not
157
+ // model may declare a local: every reference of the member
158
+ // is then undecided (fix #419).
159
+ facts.push({ node: current, scope: null });
160
+ }
161
+ return;
162
+ }
163
+ for (const child of current.namedChildren || []) visit(child);
164
+ };
165
+ // Name lookup needs only this member's occurrences of the identifier.
166
+ // The AST still decides the token's role and scope; nested types keep
167
+ // their own bindings, just as in the member walk above.
168
+ const source = memberNode.text;
169
+ for (let at = name ? source.indexOf(name) : -1; at >= 0; at = source.indexOf(name, at + name.length)) {
170
+ let token = memberNode.descendantForIndex(memberNode.startIndex + at,
171
+ memberNode.startIndex + at + name.length);
172
+ if (token?.parent?.type === 'implicit_parameter' && token.parent.text === name) token = token.parent;
173
+ if (!token || !['identifier', 'implicit_parameter'].includes(token.type) || token.text !== name) continue;
174
+ let nestedType = false;
175
+ for (let parent = token.parent; parent && !sameNode(parent, memberNode); parent = parent.parent) {
176
+ if (TYPE_SCOPES.has(parent.type)) { nestedType = true; break; }
177
+ }
178
+ if (!nestedType) visit(token);
179
+ }
180
+ bindings.set(cacheKey, facts);
181
+ return bindingVerdict(facts, node);
182
+ }
183
+
184
+ // Designation nodes: a token directly under one is either a modelled
185
+ // declaration or an undecided one. (A name directly under a subpattern is
186
+ // a member key or a constant, never a declaration.)
187
+ const UNMODELED_DESIGNATION = /_designation$/;
188
+
189
+ const TYPE_SCOPES = new Set(['class_declaration', 'struct_declaration', 'record_declaration',
190
+ 'record_struct_declaration', 'interface_declaration']);
191
+ const NON_VALUE_PARENTS = new Set(['using_directive', 'qualified_name', 'alias_qualified_name',
192
+ 'generic_name', 'type_argument_list', 'type_parameter', 'type_parameter_list', 'type_parameter_constraints_clause',
193
+ 'attribute', 'name_equals', 'name_colon', 'argument_name', 'labeled_statement', 'goto_statement']);
194
+ const VALUE_PARENTS = new Set(['binary_expression', 'return_statement', 'arrow_expression_clause', 'argument',
195
+ 'assignment_expression', 'initializer_expression', 'parenthesized_expression', 'conditional_expression',
196
+ 'prefix_unary_expression', 'postfix_unary_expression', 'checked_expression', 'unchecked_expression',
197
+ 'throw_expression', 'await_expression', 'interpolation', 'interpolation_alignment_clause',
198
+ 'expression_statement', 'yield_statement', 'equals_value_clause', 'variable_declarator',
199
+ 'anonymous_object_creation_expression', 'invocation_expression', 'member_access_expression',
200
+ 'conditional_access_expression', 'element_access_expression', 'select_clause', 'where_clause',
201
+ 'order_by_clause', 'group_clause', 'when_clause', 'switch_expression_arm',
202
+ 'from_clause', 'let_clause', 'join_clause', 'foreach_statement', 'lambda_expression']);
203
+
204
+ const usingScopesByTree = new WeakMap();
205
+ // A conditional can wrap an otherwise ordinary file-scoped namespace.
206
+ function fileScopedNamespace(root) {
207
+ for (const child of root?.namedChildren || []) {
208
+ if (child.type === 'file_scoped_namespace_declaration') return child;
209
+ if (child.type.startsWith('preproc_')) {
210
+ const nested = fileScopedNamespace(child);
211
+ if (nested) return nested;
212
+ }
213
+ }
214
+ return null;
215
+ }
216
+
217
+ /** The physical namespace bodies that supply this token's local usings. */
218
+ function usingScopesOf(node) {
219
+ if (!node?.tree) return [];
220
+ let state = usingScopesByTree.get(node.tree);
221
+ if (!state) { state = { nodes: new Map() }; usingScopesByTree.set(node.tree, state); }
222
+ const namespaces = [];
223
+ const types = [];
224
+ let scopes = [];
225
+ for (let current = node; current; current = current.parent) {
226
+ if (state.nodes.has(current.id)) { scopes = state.nodes.get(current.id); break; }
227
+ if (TYPE_SCOPES.has(current.type)) types.push(current);
228
+ if (current.type === 'namespace_declaration') namespaces.push(current);
229
+ if (current.type === 'compilation_unit') {
230
+ if (!Object.hasOwn(state, 'fileScoped')) {
231
+ const decl = fileScopedNamespace(current);
232
+ state.fileScoped = decl ? { namespace: decl.childForFieldName('name')?.text || '',
233
+ startIndex: decl.startIndex, endIndex: current.endIndex } : null;
234
+ }
235
+ if (state.fileScoped && node.startIndex >= state.fileScoped.startIndex) scopes = [state.fileScoped];
236
+ break;
237
+ }
238
+ }
239
+ for (const declaration of namespaces.reverse()) {
240
+ const name = declaration.childForFieldName('name')?.text;
241
+ if (!name) continue;
242
+ const outer = scopes.at(-1)?.namespace;
243
+ scopes = [...scopes, { namespace: outer ? outer + '.' + name : name,
244
+ startIndex: declaration.startIndex, endIndex: declaration.endIndex }];
245
+ state.nodes.set(declaration.id, scopes);
246
+ }
247
+ for (const type of types) state.nodes.set(type.id, scopes);
248
+ return scopes;
249
+ }
250
+
251
+ function namespaceOf(node) {
252
+ return usingScopesOf(node).at(-1)?.namespace || '';
253
+ }
254
+
255
+ const typeScopesByTree = new WeakMap();
256
+ function typeScope(node) {
257
+ let scopes = typeScopesByTree.get(node.tree);
258
+ if (!scopes) { scopes = new Map(); typeScopesByTree.set(node.tree, scopes); }
259
+ if (scopes.has(node.id)) return scopes.get(node.id);
260
+ const name = node.childForFieldName('name');
261
+ if (!name) return null;
262
+ const parents = [];
263
+ for (let p = node.parent; p; p = p.parent) {
264
+ if (TYPE_SCOPES.has(p.type)) parents.unshift(p.childForFieldName('name')?.text || '');
265
+ }
266
+ const list = node.namedChildren.find(child => child.type === 'type_parameter_list');
267
+ const scope = { name: name.text, line: name.startPosition.row + 1,
268
+ namespace: namespaceOf(node), enclosingType: parents.join('.'),
269
+ typeArity: list?.namedChildren.filter(child => child.type === 'type_parameter').length || 0 };
270
+ scopes.set(node.id, scope);
271
+ return scope;
272
+ }
273
+
274
+ /** Written named type at a construction, kept as AST facts for core lookup. */
275
+ function namedType(node, site) {
276
+ let current = node;
277
+ if (current?.type === 'nullable_type') current = current.namedChild(0);
278
+ if (!current) return null;
279
+ let qualifier = null;
280
+ if (current.type === 'qualified_name') {
281
+ qualifier = current.childForFieldName('qualifier');
282
+ current = current.childForFieldName('name');
283
+ } else if (current.type === 'alias_qualified_name') {
284
+ qualifier = current.childForFieldName('alias');
285
+ current = current.childForFieldName('name');
286
+ }
287
+ if (qualifier?.type === 'generic_name' || qualifier?.descendantsOfType('generic_name').length) return null;
288
+ if (current?.type === 'generic_name') current = current.namedChild(0);
289
+ if (current?.type !== 'identifier' || ['var', 'dynamic'].includes(current.text)) return null;
290
+ if (!qualifier) {
291
+ for (let scope = node.parent; scope; scope = scope.parent) {
292
+ const parameters = scope.namedChildren.find(child => child.type === 'type_parameter_list');
293
+ if (parameters?.namedChildren.some(parameter =>
294
+ parameter.childForFieldName('name')?.text === current.text || parameter.text === current.text)) return null;
295
+ }
296
+ }
297
+ return { name: current.text, node: current, text: node.text, qualifier,
298
+ qualifierText: qualifier?.text || null, namespace: namespaceOf(site) };
299
+ }
300
+
301
+ function rangeBindingToken(node) {
302
+ const parent = node.parent;
303
+ if (!parent) return false;
304
+ if (parent.type === 'query_expression' && node.previousSibling?.type === 'into') return true;
305
+ if (parent.type === 'join_into_clause') return sameNode(parent.namedChild(0), node);
306
+ if (parent.type === 'join_clause') {
307
+ const type = parent.childForFieldName('type');
308
+ return sameNode(parent.namedChildren.find(child => !sameNode(child, type)), node);
309
+ }
310
+ if (parent.type === 'tuple_pattern') {
311
+ for (let owner = parent.parent; owner; owner = owner.parent) {
312
+ if (owner.type === 'foreach_statement') {
313
+ const left = owner.childForFieldName('left');
314
+ return left && left.startIndex <= node.startIndex && node.endIndex <= left.endIndex;
315
+ }
316
+ }
317
+ }
318
+ return false;
319
+ }
320
+
321
+ function declaresToken(node) {
322
+ const parent = node.parent;
323
+ if (!parent) return false;
324
+ if (rangeBindingToken(node)) return true;
325
+ if (sameNode(parent.childForFieldName('type'), node) ||
326
+ sameNode(parent.childForFieldName('returns'), node)) return true;
327
+ if (sameNode(parent.childForFieldName('name'), node) &&
328
+ (parent.type.endsWith('_declaration') || CSHARP_LOCAL_DECLARATION_NODES.has(parent.type))) return true;
329
+ return parent.type === 'implicit_parameter' || parent.type === 'single_variable_designation' ||
330
+ (parent.type === 'foreach_statement' && sameNode(parent.childForFieldName('left'), node)) ||
331
+ (parent.type === 'let_clause' && sameNode(parent.namedChild(0), node)) ||
332
+ // Deconstruction designations declare locals (fix #419).
333
+ !!deconstructionRoot(node);
334
+ }
335
+
336
+ // A primary-constructor parameter supplies instance initializers and base
337
+ // arguments. Inside ordinary member bodies, the member lookup comes first.
338
+ function primaryParameterInInitializer(node, type, name) {
339
+ const list = type?.namedChildren.find(child => child.type === 'parameter_list');
340
+ if (!list?.namedChildren.some(parameter => parameter.childForFieldName('name')?.text === name)) return false;
341
+ for (let current = node; current?.parent && !sameNode(current.parent, type); current = current.parent) {
342
+ const parent = current.parent;
343
+ if (parent.type === 'base_list' || parent.type === 'primary_constructor_base_type') return true;
344
+ if (parent.type === 'property_declaration' &&
345
+ sameNode(parent.childForFieldName('value'), current) && current.type !== 'arrow_expression_clause') return true;
346
+ if (parent.type === 'variable_declarator' && parent.parent?.parent?.type === 'field_declaration' &&
347
+ !sameNode(parent.childForFieldName('name'), current)) return true;
348
+ }
349
+ return false;
350
+ }
351
+
352
+ // C# 14 makes a bare `field` in a property body a backing-field keyword.
353
+ // Earlier versions can bind the same AST identifier to an ordinary member.
354
+ function possibleBackingField(node) {
355
+ if (node.text !== 'field') return false;
356
+ let accessor = false;
357
+ for (let current = node.parent; current; current = current.parent) {
358
+ if (current.type === 'accessor_declaration') {
359
+ accessor = ['get', 'set', 'init'].includes(current.childForFieldName('name')?.text);
360
+ }
361
+ if (current.type === 'property_declaration') {
362
+ const value = current.childForFieldName('value');
363
+ return accessor || (value?.type === 'arrow_expression_clause' &&
364
+ value.startIndex <= node.startIndex && node.endIndex <= value.endIndex);
365
+ }
366
+ if (current.type === 'indexer_declaration' || TYPE_SCOPES.has(current.type)) return false;
367
+ }
368
+ return false;
369
+ }
370
+
371
+ /**
372
+ * Property-bearing expression shapes whose syntax has no simple receiver.
373
+ * The core resolves owners; this adapter reports only grammar and scopes.
374
+ */
375
+ function accessorReferenceShape(tree, usage, implicitCreationTarget) {
376
+ if (!tree || !Number.isInteger(usage.column)) return null;
377
+ const node = tree.rootNode.descendantForPosition({ row: usage.line - 1, column: usage.column });
378
+ if (!node || node.type !== 'identifier') return null;
379
+ const parent = node.parent;
380
+ if (!parent) return 'none';
381
+ if (parent.type === 'name_equals' || parent.type === 'attribute_argument' && node.nextSibling?.type === '=' ||
382
+ parent.type === 'assignment_expression' && sameNode(parent.childForFieldName('left'), node) &&
383
+ parent.parent?.type === 'attribute_argument') {
384
+ let owner = parent;
385
+ while (owner && owner.type !== 'attribute' && !TYPE_SCOPES.has(owner.type)) owner = owner.parent;
386
+ // An attribute's named field/property belongs to the attribute,
387
+ // never to the class carrying it. Its abbreviated type name and
388
+ // constructor binding need a separate ownership proof.
389
+ if (owner?.type === 'attribute') return { kind: 'unresolved', node, uncertainRole: true };
390
+ }
391
+ let pattern = parent;
392
+ while (pattern?.type === 'member_access_expression') pattern = pattern.parent;
393
+ if (pattern?.type === 'subpattern' && pattern.children.some(child => child.type === ':')) {
394
+ return { kind: 'pattern', accessKind: 'read', node };
395
+ }
396
+ if (parent.type === 'member_access_expression' && sameNode(parent.childForFieldName('name'), node)) {
397
+ return { kind: 'member', node };
398
+ }
399
+ if (parent.type === 'assignment_expression' && sameNode(parent.childForFieldName('left'), node) &&
400
+ parent.parent?.type === 'initializer_expression') {
401
+ const holder = parent.parent.parent;
402
+ const type = holder?.type === 'object_creation_expression' ? holder.childForFieldName('type')
403
+ : holder?.type === 'implicit_object_creation_expression' ? implicitCreationTarget(holder) : null;
404
+ const value = parent.childForFieldName('right');
405
+ return { kind: 'initializer', node, type: namedType(type, node),
406
+ accessKind: value?.type === 'initializer_expression' ? 'read' : 'write' };
407
+ }
408
+ if (parent.type === 'with_initializer' && node.nextSibling?.type === '=') {
409
+ return { kind: 'initializer', node, type: null, accessKind: 'write' };
410
+ }
411
+ if (parent.type === 'subpattern' && node.nextSibling?.type === ':') {
412
+ return { kind: 'pattern', node, accessKind: 'read' };
413
+ }
414
+ if (NON_VALUE_PARENTS.has(parent.type) || declaresToken(node)) return 'none';
415
+ if (possibleBackingField(node)) return { kind: 'unresolved', node, uncertainRole: true };
416
+ // Unknown member syntax carries no implicit-receiver evidence. In
417
+ // particular, a conditional member name cannot bind a local or `value`.
418
+ if (!VALUE_PARENTS.has(parent.type)) return { kind: 'unresolved', node, uncertainRole: true };
419
+ const types = [];
420
+ let member = null;
421
+ for (let current = parent; current; current = current.parent) {
422
+ if (TYPE_SCOPES.has(current.type)) types.push(current);
423
+ else if (types.length === 0 && current.parent?.type === 'declaration_list') member = current;
424
+ // Setter/init and event accessor parameters are implicit in the AST.
425
+ if (current.type === 'accessor_declaration' && node.text === 'value' &&
426
+ ['set', 'init', 'add', 'remove'].includes(current.childForFieldName('name')?.text)) return 'none';
427
+ }
428
+ if (types.length === 0 && node.text === 'args') return 'none';
429
+ const local = csharpLocalBinding(member || (types.length === 0 ? tree.rootNode : parent), node, node.text);
430
+ if (local === 'local' || primaryParameterInInitializer(node, types[0], node.text)) return 'none';
431
+ const projection = parent.type === 'anonymous_object_creation_expression';
432
+ if (projection && node.nextSibling?.type === '=') return 'none';
433
+ const scopes = types.map(typeScope).filter(Boolean);
434
+ return { kind: 'implicit', scopes, node, namespace: scopes[0]?.namespace ?? namespaceOf(node),
435
+ uncertainRole: local === 'unknown' || parent.type === 'member_access_expression',
436
+ inferredName: projection };
437
+ }
438
+
439
+ module.exports = { csharpLocalBinding, accessorReferenceShape, namedType, usingScopesOf, fileScopedNamespace, deconstructionRoot };