ucn 5.5.1 → 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 +30 -21
  6. package/cli/index.js +1 -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/brief.js +8 -4
  12. package/core/build-worker.js +2 -1
  13. package/core/cache.js +137 -6
  14. package/core/call-name-cache.js +134 -0
  15. package/core/callable-scope.js +21 -0
  16. package/core/callers.js +4726 -940
  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 +217 -112
  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 +151 -19
  63. package/core/rust-modules.js +1 -1
  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 +124 -37
  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 +510 -82
  91. package/languages/utils.js +67 -8
  92. package/mcp/server.js +8 -2
  93. package/package.json +2 -2
@@ -0,0 +1,2777 @@
1
+ 'use strict';
2
+
3
+ const { langTraits, LANGUAGES, getParser, safeParse } = require('../languages');
4
+ const { sameNode, visitNameNodes } = require('../languages/utils');
5
+ const { referenceScope, pythonClassBodyOrder } = require('../languages/lexical-scope');
6
+ const { TypeReferenceResolver, referenceRole, referenceBindingScope, referenceTypeParameterScope,
7
+ typeReferenceSites, referenceImportVerdict } = require('./type-references');
8
+ const { ownerRefOf, classDefsNamed, findDeclaringClass, relationToTargets, descendsFromTargets, shareDescendant,
9
+ parentRefsOf, supertypeRefsOf, lexicalClassRefOf, completeForwardDeclaration } = require('./class-identity');
10
+ const { isValueDefinition } = require('./shared');
11
+ const TYPE_BODIES = new Set(['class_declaration', 'class_definition', 'class',
12
+ 'abstract_class_declaration', 'struct_specifier', 'class_specifier', 'union_specifier',
13
+ 'struct_item', 'enum_item', 'enum_declaration', 'enum_specifier',
14
+ 'struct_declaration', 'record_declaration', 'interface_declaration', 'union_item', 'type_spec',
15
+ 'annotation_type_declaration', 'record_struct_declaration']);
16
+ const MEMBER_NODES = new Set(['attribute', 'member_expression', 'member_access_expression',
17
+ 'member_binding_expression', 'field_expression', 'field_access', 'selector_expression']);
18
+ const ROOTS = new Set(['program', 'module', 'source_file', 'translation_unit', 'compilation_unit']);
19
+ const TYPE_NAMES = new Set(['identifier', 'type_identifier', 'namespace_identifier']);
20
+ const TYPE_WRAPPERS = new Set(['type', 'type_annotation', 'reference_type', 'pointer_type',
21
+ 'nullable_type', 'parenthesized_type', 'type_descriptor']);
22
+ const DECLARATIONS = new Set(['parameter', 'formal_parameter', 'spread_parameter',
23
+ 'required_parameter', 'optional_parameter', 'typed_parameter', 'parameter_declaration',
24
+ 'variable_declarator', 'let_declaration', 'assignment', 'var_spec', 'const_spec',
25
+ 'declaration', 'local_declaration_statement', 'field_declaration',
26
+ 'field_definition', 'public_field_definition', 'short_var_declaration']);
27
+ const GRAMMAR_LANGUAGES = new Map(Object.values(LANGUAGES).map(config =>
28
+ [config.treeSitterLang, config.name]));
29
+ const TYPE_KINDS = new Set(['class', 'struct', 'interface', 'trait', 'enum', 'record',
30
+ 'union', 'type', 'namespace', 'impl']);
31
+ const FUNCTION_NODES = new Set(['function', 'function_expression', 'function_declaration',
32
+ 'generator_function', 'generator_function_declaration']);
33
+ const CALLABLE_SCOPES = new Set([...FUNCTION_NODES, 'function_definition', 'method_definition',
34
+ 'method_declaration', 'constructor_declaration', 'lambda', 'lambda_expression',
35
+ 'arrow_function', 'closure_expression', 'function_item']);
36
+ const WRITE_EXPRESSIONS = new Set(['assignment', 'assignment_expression', 'assignment_statement',
37
+ 'augmented_assignment', 'augmented_assignment_expression', 'compound_assignment_expr',
38
+ 'update_expression', 'inc_statement', 'dec_statement']);
39
+
40
+ /** One answer for several bindings of a name (jsBindingHolds). */
41
+ function mergeHolds(holds) {
42
+ if (holds.length > 0 && holds.every(hold => hold === 'value')) return 'value';
43
+ if (holds.length > 0 && holds.every(hold => hold?.modules)) {
44
+ return { modules: [...new Set(holds.flatMap(hold => hold.modules))] };
45
+ }
46
+ return 'unknown';
47
+ }
48
+
49
+ /** Reads of an instance member while evaluating the RHS of its first
50
+ * recorded write. The write has not happened yet, so the old value may be
51
+ * inherited or supplied by an earlier runtime initialization. */
52
+ function instanceAttributeInitializerReads(index, definition) {
53
+ if (!definition?.instanceAttribute) return [];
54
+ const memo = index._opMemo?.('instanceAttributeInitializerReads', () => new WeakMap());
55
+ if (memo?.has(definition)) return memo.get(definition);
56
+ const file = definition.file;
57
+ let language = index.files.get(file)?.language;
58
+ let source;
59
+ try {
60
+ source = index._readFile(file);
61
+ } catch {
62
+ // A file removed since indexing is no longer in the observed
63
+ // source universe, as in computeGroundSet.
64
+ return [];
65
+ }
66
+ const embedded = langTraits(language)?.embeddedSyntax;
67
+ if (embedded) {
68
+ source = embedded.source(source, getParser(language));
69
+ language = embedded.language;
70
+ }
71
+ const tree = embedded ? require('./source-memo').parsedSource(language, source)
72
+ : index._getParsedTree(file, source, language) || safeParse(getParser(language), source);
73
+ const reads = [];
74
+ if (tree) visitNameNodes(tree, source, definition.name, node => {
75
+ if (node.startPosition.row + 1 !== (definition.nameLine || definition.startLine)) return true;
76
+ const member = node.parent;
77
+ if (!['attribute', 'member_expression'].includes(member?.type)) return true;
78
+ const name = member.childForFieldName('attribute') || member.childForFieldName('property');
79
+ const receiver = member.childForFieldName('object');
80
+ if (!name || !sameNode(name, node) || !receiver) return true;
81
+ const assignment = member.parent;
82
+ if (!['assignment', 'assignment_expression'].includes(assignment?.type) ||
83
+ !sameNode(assignment.childForFieldName('left'), member)) return true;
84
+ const right = assignment.childForFieldName('right');
85
+ const stack = right ? [right] : [];
86
+ while (stack.length) {
87
+ const current = stack.pop();
88
+ if (CALLABLE_SCOPES.has(current.type) || TYPE_BODIES.has(current.type) ||
89
+ current.type === 'generator_expression') continue;
90
+ if (['attribute', 'member_expression'].includes(current.type)) {
91
+ const property = current.childForFieldName('attribute') || current.childForFieldName('property');
92
+ const object = current.childForFieldName('object');
93
+ if (property?.text === definition.name && object?.type === receiver.type &&
94
+ object.text === receiver.text) reads.push({ line: property.startPosition.row + 1,
95
+ column: property.startPosition.column, start: property.startIndex, end: property.endIndex });
96
+ }
97
+ stack.push(...current.namedChildren);
98
+ }
99
+ return true;
100
+ });
101
+ reads.sort((a, b) => a.start - b.start);
102
+ memo?.set(definition, reads);
103
+ return reads;
104
+ }
105
+
106
+ /** The template-id a name token heads (`v` in `v<T>`), else the token. */
107
+ function templateIdOf(node) {
108
+ const parent = node?.parent;
109
+ return parent && ['template_function', 'template_method'].includes(parent.type) &&
110
+ sameNode(parent.childForFieldName('name'), node) ? parent : node;
111
+ }
112
+
113
+ /** The Python class whose body a node is directly in (not in a nested
114
+ * function, lambda or comprehension body), or null. */
115
+ function pythonClassBodyOf(node) {
116
+ let child = node;
117
+ for (let scope = node.parent; scope; child = scope, scope = scope.parent) {
118
+ if (scope.type === 'function_definition' || scope.type === 'lambda') {
119
+ if (sameNode(scope.childForFieldName('body'), child)) return null;
120
+ continue;
121
+ }
122
+ if (['list_comprehension', 'set_comprehension', 'dictionary_comprehension', 'generator_expression']
123
+ .includes(scope.type)) {
124
+ const first = scope.namedChildren.find(part => part.type === 'for_in_clause')?.childForFieldName('right');
125
+ if (!first || !contains(first, node)) return null;
126
+ continue;
127
+ }
128
+ if (scope.type === 'class_definition') {
129
+ return sameNode(scope.childForFieldName('body'), child) ? scope : null;
130
+ }
131
+ }
132
+ return null;
133
+ }
134
+
135
+ function declarationTrees(index, file, content, language) {
136
+ const memo = index.isProvenanceFactIndex ? null
137
+ : index._opMemo?.('valueDeclarationTrees', () => new Map());
138
+ if (memo?.has(file)) return memo.get(file);
139
+ const parser = getParser(language);
140
+ const literal = index._getParsedTree(file, content, language) || safeParse(parser, content);
141
+ const entry = index.files.get(file);
142
+ let recovered = null;
143
+ if (langTraits(language)?.textualIncludes &&
144
+ (literal?.rootNode.hasError || entry?.recoveryBlanks?.length)) {
145
+ const blanks = Array.isArray(entry?.recoveryBlanks) && entry.hash &&
146
+ require('crypto').createHash('md5').update(content).digest('hex') === entry.hash
147
+ ? entry.recoveryBlanks : null;
148
+ const module = LANGUAGES[language]?.module();
149
+ if (module?.recoveredTree) recovered = module.recoveredTree(content, parser, blanks);
150
+ }
151
+ const trees = [recovered, literal].filter(Boolean);
152
+ memo?.set(file, trees);
153
+ return trees;
154
+ }
155
+
156
+ function isWrittenBinding(node) {
157
+ for (let current = node; current?.parent; current = current.parent) {
158
+ const parent = current.parent;
159
+ if (MEMBER_NODES.has(parent.type) || parent.type === 'scoped_identifier') return false;
160
+ if (WRITE_EXPRESSIONS.has(parent.type)) {
161
+ const left = parent.childForFieldName('left') || parent.childForFieldName('argument');
162
+ return left ? contains(left, node) : parent.namedChildCount === 1;
163
+ }
164
+ if (!['expression_list', 'object_pattern', 'array_pattern', 'pair_pattern',
165
+ 'parenthesized_expression', 'tuple_pattern'].includes(parent.type)) return false;
166
+ }
167
+ return false;
168
+ }
169
+
170
+ /** Whether a member name token is the name of an assignment target
171
+ * (`self.x = v`, `self.x += v`, `self.a, self.b = ..`). */
172
+ function isWrittenMember(node) {
173
+ const member = node.parent;
174
+ if (!member || !MEMBER_NODES.has(member.type)) return false;
175
+ for (let current = member; current?.parent; current = current.parent) {
176
+ const parent = current.parent;
177
+ if (WRITE_EXPRESSIONS.has(parent.type)) {
178
+ const left = parent.childForFieldName('left');
179
+ return !!left && contains(left, member);
180
+ }
181
+ if (!['expression_list', 'pattern_list', 'tuple_pattern', 'list_pattern', 'tuple', 'list',
182
+ 'parenthesized_expression'].includes(parent.type)) return false;
183
+ }
184
+ return false;
185
+ }
186
+
187
+ function contains(scope, node) {
188
+ return !scope || scope.startIndex <= node.startIndex && scope.endIndex >= node.endIndex;
189
+ }
190
+
191
+ /** Whether a Go identifier is the key of a keyed composite-literal element
192
+ * (`limit` in `Box{limit: 5}`), fix #434. */
193
+ function goKeyedFieldKey(node) {
194
+ const key = node?.parent;
195
+ const keyed = key?.parent;
196
+ return node?.type === 'identifier' && key?.type === 'literal_element' && keyed?.type === 'keyed_element' &&
197
+ key.namedChildCount === 1 && sameNode(keyed.namedChild(0), key);
198
+ }
199
+
200
+ /**
201
+ * The struct type a Go keyed element's literal builds (fix #434): its
202
+ * composite literal's written type, through pointers, or for an elided
203
+ * inner literal (`[]Box{{limit: 3}}`, `map[K]Box{k: {..}}`) the element or
204
+ * value type of the enclosing literal's type. Null when not written.
205
+ */
206
+ function goCompositeLiteralType(keyNode) {
207
+ const literalValue = keyNode.parent.parent.parent;
208
+ if (literalValue?.type !== 'literal_value') return null;
209
+ const holder = literalValue.parent;
210
+ if (holder?.type === 'composite_literal') return unwrapGoType(holder.childForFieldName('type'));
211
+ if (holder?.type !== 'literal_element') return null;
212
+ const element = holder.parent;
213
+ const outerValue = element?.type === 'keyed_element' ? element.parent : element;
214
+ const outerType = goCompositeLiteralTypeOfValue(outerValue);
215
+ if (!outerType) return null;
216
+ if (outerType.type === 'slice_type' || outerType.type === 'array_type' || outerType.type === 'implicit_length_array_type') {
217
+ return unwrapGoType(outerType.childForFieldName('element'));
218
+ }
219
+ if (outerType.type === 'map_type') {
220
+ const isKey = element?.type === 'keyed_element' && sameNode(element.namedChild(0), holder);
221
+ return unwrapGoType(outerType.childForFieldName(isKey ? 'key' : 'value'));
222
+ }
223
+ return null;
224
+ }
225
+
226
+ function goCompositeLiteralTypeOfValue(literalValue) {
227
+ if (literalValue?.type !== 'literal_value') return null;
228
+ const holder = literalValue.parent;
229
+ if (holder?.type === 'composite_literal') return holder.childForFieldName('type');
230
+ if (holder?.type !== 'literal_element') return null;
231
+ const element = holder.parent;
232
+ const outerValue = element?.type === 'keyed_element' ? element.parent : element;
233
+ const outerType = goCompositeLiteralTypeOfValue(outerValue);
234
+ if (!outerType) return null;
235
+ if (['slice_type', 'array_type', 'implicit_length_array_type'].includes(outerType.type)) {
236
+ return unwrapGoType(outerType.childForFieldName('element'));
237
+ }
238
+ if (outerType.type === 'map_type') {
239
+ const isKey = element?.type === 'keyed_element' && sameNode(element.namedChild(0), holder);
240
+ return unwrapGoType(outerType.childForFieldName(isKey ? 'key' : 'value'));
241
+ }
242
+ return null;
243
+ }
244
+
245
+ function unwrapGoType(node) {
246
+ let current = node;
247
+ while (current?.type === 'pointer_type' || current?.type === 'parenthesized_type') {
248
+ current = current.type === 'pointer_type' ? current.namedChild(0) : current.namedChild(0);
249
+ }
250
+ return current?.type === 'type_identifier' || current?.type === 'qualified_type' ||
251
+ current?.type === 'generic_type' ? current : current?.type === 'slice_type' || current?.type === 'map_type' ||
252
+ current?.type === 'array_type' ? current : null;
253
+ }
254
+
255
+ function typeSyntax(node) {
256
+ if (!node) return null;
257
+ if (TYPE_NAMES.has(node.type)) return { name: node.text, node };
258
+ if (node.type === 'qualified_identifier') {
259
+ const tail = typeSyntax(node.childForFieldName('name'));
260
+ const scope = node.childForFieldName('scope');
261
+ if (!tail) return null;
262
+ if (!scope) return node.children.some(child => child.type === '::')
263
+ ? { ...tail, qualifierText: `::${tail.qualifierText || ''}` } : tail;
264
+ return { ...tail, qualifier: tail.qualifier || scope,
265
+ qualifierText: [scope.text, tail.qualifierText].filter(Boolean).join('::') };
266
+ }
267
+ if (TYPE_WRAPPERS.has(node.type)) {
268
+ return typeSyntax(node.childForFieldName('type') || node.namedChild(0));
269
+ }
270
+ if (node.type === 'generic_type' || node.type === 'generic_name') {
271
+ return typeSyntax(node.childForFieldName('type') || node.childForFieldName('name') || node.namedChild(0));
272
+ }
273
+ const name = node.childForFieldName('name') || (node.type === 'scoped_type_identifier'
274
+ ? node.namedChild(node.namedChildCount - 1) : null);
275
+ const qualifier = node.childForFieldName('scope') || node.childForFieldName('qualifier') ||
276
+ node.childForFieldName('package') || node.childForFieldName('module') ||
277
+ node.childForFieldName('path') || (node.type === 'scoped_type_identifier' ? node.namedChild(0) : null);
278
+ if (name && TYPE_NAMES.has(name.type)) return {
279
+ name: name.text, node: name,
280
+ ...(qualifier && { qualifier, qualifierText: qualifier.text }),
281
+ };
282
+ return null;
283
+ }
284
+
285
+ function declarationType(binding) {
286
+ for (let node = binding; node; node = node.parent) {
287
+ if (!DECLARATIONS.has(node.type)) continue;
288
+ const type = node.childForFieldName('type');
289
+ if (type) return { syntax: typeSyntax(type), origin: 'annotation' };
290
+ let value = node.childForFieldName('value') || node.childForFieldName('right');
291
+ // Go `b, p := Box{..}, &Box{..}` (fix #434): the value at the
292
+ // binding's position, through the address operator.
293
+ if (value?.type === 'expression_list') {
294
+ const left = node.childForFieldName('left');
295
+ const names = left?.type === 'expression_list' ? left.namedChildren : left ? [left] : [];
296
+ const at = names.findIndex(name => sameNode(name, binding));
297
+ value = at >= 0 && value.namedChildCount === names.length ? value.namedChild(at) : null;
298
+ }
299
+ if (value?.type === 'unary_expression' && value.childForFieldName('operator')?.type === '&') {
300
+ value = value.childForFieldName('operand');
301
+ }
302
+ if (value?.type === 'new_expression' || value?.type === 'object_creation_expression') {
303
+ return { syntax: typeSyntax(value.childForFieldName('constructor') ||
304
+ value.childForFieldName('type')), origin: 'constructor' };
305
+ }
306
+ if (value?.type === 'struct_expression' || value?.type === 'composite_literal') {
307
+ return { syntax: typeSyntax(value.childForFieldName('name') ||
308
+ value.childForFieldName('type')), origin: 'constructor' };
309
+ }
310
+ if (node.type === 'variable_declarator' && node.parent?.childForFieldName('type')) {
311
+ return { syntax: typeSyntax(node.parent.childForFieldName('type')), origin: 'annotation' };
312
+ }
313
+ if (node.type !== 'variable_declarator') break;
314
+ }
315
+ return null;
316
+ }
317
+
318
+ function memberReceiver(node, role) {
319
+ if (role.role === 'member' && role.receiver) return role.receiver;
320
+ const parent = node.parent;
321
+ if (!MEMBER_NODES.has(parent?.type)) return null;
322
+ const name = parent.childForFieldName('field') || parent.childForFieldName('property') ||
323
+ parent.childForFieldName('name') || parent.childForFieldName('attribute');
324
+ if (!sameNode(name, node)) return null;
325
+ return parent.childForFieldName('object') || parent.childForFieldName('value') ||
326
+ parent.childForFieldName('argument') || parent.childForFieldName('operand') ||
327
+ parent.childForFieldName('expression');
328
+ }
329
+
330
+ function enclosingEnum(node) {
331
+ for (let parent = node.parent; parent; parent = parent.parent) {
332
+ if (parent.type === 'enum_specifier') return parent;
333
+ }
334
+ return null;
335
+ }
336
+
337
+ function enumValueBindingInfo(index, definition) {
338
+ const memo = index._opMemo?.('enumValueBindingInfo', () => new WeakMap());
339
+ if (memo?.has(definition)) return memo.get(definition);
340
+ if (definition.className && ownerRefOf(index, definition)?.def?.type !== 'enum') return null;
341
+ const file = definition.file, content = index._readFile(file);
342
+ const language = index.files.get(file)?.language;
343
+ const tree = index._getParsedTree(file, content, language) || safeParse(getParser(language), content);
344
+ const matches = [];
345
+ if (tree) visitNameNodes(tree, content, definition.name, node => {
346
+ if (node.text !== definition.name || node.startPosition.row + 1 !==
347
+ (definition.nameLine || definition.startLine) || node.parent?.type !== 'enumerator' ||
348
+ !sameNode(node.parent.childForFieldName('name'), node)) return true;
349
+ let owner = node.parent;
350
+ while (owner && owner.type !== 'enum_specifier') owner = owner.parent;
351
+ if (owner) {
352
+ matches.push({ node, enumNode: owner,
353
+ scope: referenceBindingScope(node, language, referenceRole(node, language)),
354
+ unscoped: !owner.children.some(child => child.type === 'class' || child.type === 'struct') });
355
+ }
356
+ return true;
357
+ });
358
+ const result = matches.length === 1 ? matches[0] : null;
359
+ memo?.set(definition, result);
360
+ return result;
361
+ }
362
+
363
+ // Tree -> Map(`${language}\0${name}` -> the name's tokens with their roles
364
+ // and binding scopes); entries die with their trees.
365
+ const SYNTAX_NODES_BY_TREE = new WeakMap();
366
+
367
+ class ValueReferenceResolver extends TypeReferenceResolver {
368
+ constructor(index, def, name) {
369
+ super(index, def, name);
370
+ this.family = langTraits(this.language)?.nameReferenceFamily || this.family;
371
+ this.declarationKinds = new Set(['state', 'constant']);
372
+ this.all = (index.symbols.get(name) || []).filter(d => isValueDefinition(d) &&
373
+ (def.className || !d.className));
374
+ this.group = new Set([def]);
375
+ this.valueReferences = true;
376
+ this.owner = def.className ? ownerRefOf(index, def) : null;
377
+ this.references = [];
378
+ this.syntaxMemo = new Map();
379
+ this.lexicalScopeMemos = new WeakMap();
380
+ this.typeMemo = new Map();
381
+ this.memberMemo = new Map();
382
+ this.enumMemo = new Map();
383
+ this.scopeMemo = new Map();
384
+ this.localFileModes = new Map();
385
+ this.enumReferenceSyntax = this.owner?.def?.type === 'enum' &&
386
+ langTraits(this.language)?.staticEnumReferenceSyntax
387
+ ? new (require('./enum-reference-syntax').EnumReferenceSyntax)(this) : null;
388
+ this.patternConstant = false;
389
+ this.selectedDeclarations = 0;
390
+ this.declarationStorages = new Set();
391
+ this.declarationRegions = new Set();
392
+ if (langTraits(this.language)?.valuePatternConstants) {
393
+ const content = index._readFile(def.file);
394
+ const tree = index._getParsedTree(def.file, content, this.language) ||
395
+ safeParse(getParser(this.language), content);
396
+ if (tree) visitNameNodes(tree, content, name, node => {
397
+ if (node.startPosition.row + 1 === (def.nameLine || def.startLine) &&
398
+ ['const_item', 'enum_variant'].includes(node.parent?.type)) this.patternConstant = true;
399
+ return true;
400
+ });
401
+ }
402
+ // A Python module variable is one variable however many statements
403
+ // bind it (fix #420): every module-scope binding of the name in the
404
+ // pin's file is renamed with it, as the run-time alternatives of a
405
+ // callable are (fix #376), so `x = x` and `import json; json = json`
406
+ // keep reading the variable they read before.
407
+ this.moduleVariable = !!langTraits(this.language)?.assignmentDeclaresLocal &&
408
+ !def.className && def.lexicalScopeStartLine == null;
409
+ if (this.moduleVariable) {
410
+ for (const other of this.all) {
411
+ if (other.file === def.file && !other.className && other.lexicalScopeStartLine == null) this.group.add(other);
412
+ }
413
+ }
414
+ this.linkAlternatives = null;
415
+ if (langTraits(this.language)?.textualIncludes) this.groupLinkage();
416
+ this.members = (index.symbols.get(name) || []).filter(d => d.className &&
417
+ !TYPE_KINDS.has(d.type) &&
418
+ (!langTraits(this.language)?.fieldsAndMethodsSeparate || isValueDefinition(d)));
419
+ // A member value's slot (fix #420): where member lookup is by name
420
+ // through the class chain (structural languages), the same-name
421
+ // fields of the declaring ancestors, of the interfaces a class body
422
+ // implements and of overriding descendants are one member: TS checks
423
+ // `implements`, and a subclass field shadows the renamed one at run
424
+ // time. Accessors filling the slot are reviewed, not token-renamed.
425
+ this.targetStorage = this.valueStorage(def);
426
+ this.slotAccessors = [];
427
+ const traits = langTraits(this.language);
428
+ // A private member (`#x`, a mangled `__x`) is lexical to its class,
429
+ // and static and instance storage are separate slots.
430
+ const privateMember = member => member.type === 'private field' ||
431
+ !!traits?.privateMemberName?.(member.name);
432
+ if (this.owner && traits?.typeSystem === 'structural' && !privateMember(def)) {
433
+ const closure = require('./contract-membership').hierarchySlotClosure(index, def, null, { values: true });
434
+ for (const entry of closure ? [...closure.ancestors, ...closure.descendants] : []) {
435
+ for (const member of entry.members) {
436
+ if (member === def || privateMember(member) ||
437
+ traits.separateStaticStorage && this.valueStorage(member) !== this.targetStorage) continue;
438
+ if (isValueDefinition(member)) this.group.add(member);
439
+ else if (!this.slotAccessors.includes(member)) this.slotAccessors.push(member);
440
+ }
441
+ }
442
+ }
443
+ this.memberCandidates = langTraits(this.language)?.separateStaticStorage
444
+ ? this.members.filter(member => this.valueStorage(member) === this.targetStorage) : this.members;
445
+ }
446
+
447
+ /**
448
+ * C/C++ external linkage (fix #420): every file-scope declaration of the
449
+ * name without `static` in one namespace denotes one object of the
450
+ * program, so `extern int g;` in a header and `int g = 0;` in a source
451
+ * file are renamed together, as functions are by their linkage (#361).
452
+ * Several external definitions belong to different programs or
453
+ * configurations: their declarations and the uses that see them are
454
+ * review items (`link-alternatives`).
455
+ */
456
+ groupLinkage() {
457
+ const def = this.def;
458
+ const { effectiveNamespace } = require('./cpp-scope');
459
+ const namespace = d => effectiveNamespace(this.index, d) || '';
460
+ const linked = d => !d.className && !d.enclosingType && d.lexicalScopeStartLine == null &&
461
+ !(d.modifiers || []).includes('static') &&
462
+ langTraits(this.index.files.get(d.file)?.language)?.textualIncludes;
463
+ if (!linked(def)) return;
464
+ const ownNamespace = namespace(def);
465
+ const family = this.all.filter(d => linked(d) && namespace(d) === ownNamespace);
466
+ const definitions = family.filter(d => !(d.modifiers || []).includes('extern'));
467
+ if (definitions.length <= 1) {
468
+ for (const member of family) this.group.add(member);
469
+ return;
470
+ }
471
+ this.linkAlternatives = new Set(family.filter(member => member !== def));
472
+ }
473
+
474
+ judge(defs, reason) {
475
+ if (this.linkAlternatives && defs.some(d => this.linkAlternatives.has(d))) {
476
+ return { verdict: 'unknown', reason: 'link-alternatives' };
477
+ }
478
+ return super.judge(defs, reason);
479
+ }
480
+
481
+ /**
482
+ * Another declaration of the renamed entity declared by this token: a
483
+ * linkage declaration (C/C++), a module-variable binding statement
484
+ * (Python) or a member of the slot closure (fix #420). 'group' renames
485
+ * it, 'alternative' leaves it for review.
486
+ */
487
+ groupMemberAt(file, node, otherOwner = null) {
488
+ const line = node.startPosition.row + 1;
489
+ const at = d => d !== this.def && d.file === file && (d.nameLine || d.startLine) === line &&
490
+ (!otherOwner || ownerRefOf(this.index, d)?.key === otherOwner.key);
491
+ if ([...this.group].some(at)) return 'group';
492
+ if (this.linkAlternatives && [...this.linkAlternatives].some(at)) return 'alternative';
493
+ return null;
494
+ }
495
+
496
+ sourceLocalBinding(file, binding, reference = binding.node) {
497
+ if (!binding.scope || ROOTS.has(binding.scope.type) || TYPE_BODIES.has(binding.scope.type) ||
498
+ ['class_body', 'declaration_list', 'field_declaration_list'].includes(binding.scope.type) ||
499
+ binding.role?.role === 'import') return false;
500
+ const traits = langTraits(this.index.files.get(file)?.language);
501
+ if (traits.assignmentDeclaresLocal && !referenceScope(reference,
502
+ this.index.files.get(file)?.language, new Map(), this.name)?.local) return false;
503
+ if (traits.textualIncludes && binding.role?.why === 'function') return false;
504
+ for (let parent = binding.node.parent; parent; parent = parent.parent) {
505
+ if (parent.type === 'ERROR' || TYPE_BODIES.has(parent.type) || ROOTS.has(parent.type) ||
506
+ ['using_declaration', 'using_directive', 'namespace_alias_declaration'].includes(parent.type)) return false;
507
+ if (parent.type === 'declaration' && parent.namedChildren.some(child =>
508
+ child.type === 'storage_class_specifier' && child.text === 'extern')) return false;
509
+ if (CALLABLE_SCOPES.has(parent.type) ||
510
+ ['accessor_declaration', 'local_function_statement', 'func_literal'].includes(parent.type)) return true;
511
+ }
512
+ return false;
513
+ }
514
+
515
+ readLocalFile(file, entry, language, rows, content) {
516
+ const enumSyntax = this.enumReferenceSyntax?.read(file, entry, language, rows, content);
517
+ if (enumSyntax) return enumSyntax;
518
+ if (file === this.def.file || this.patternConstant || !entry.hash ||
519
+ !this.index._usageResultCache || this.index.isProvenanceFactIndex) return null;
520
+ const crypto = require('crypto');
521
+ const { indexedContent, syntaxMode } = require('./source-memo');
522
+ if (!indexedContent(this.index, file, entry, content)) return null;
523
+ // Source generations also cover macro definitions in other files.
524
+ // These facts never retain a project-dependent ownership verdict.
525
+ const generation = this.index._opMemo('valueLocalSourceGeneration', () => {
526
+ const hash = crypto.createHash('sha256');
527
+ for (const [path, source] of [...this.index.files].sort((a, b) =>
528
+ require('./shared').codeUnitCompare(a[0], b[0]))) hash.update(path + '\0' + source.hash + '\0' + source.language + '\0');
529
+ return hash.update(JSON.stringify(this.index.config)).digest('hex');
530
+ });
531
+ const mode = syntaxMode('valueLocal', [generation, language,
532
+ entry.recoveryBlanks, entry.conditionalViews, entry.externalMacroKey, rows]);
533
+ this.localFileModes.set(file, { mode, referenceStart: this.references.length });
534
+ const value = require('./cache').getUsageResult(this.index, file, this.name, mode);
535
+ if (!Array.isArray(value)) return null;
536
+ const lines = new Map(), references = [], wanted = new Set(rows);
537
+ for (const record of value) {
538
+ if (!Array.isArray(record) || !wanted.has(record[1])) return null;
539
+ if (record[0] === 'line') {
540
+ const [, line, skips, columns, text, tokens] = record;
541
+ if (lines.has(line) || !Number.isInteger(skips) || !Number.isInteger(tokens) ||
542
+ !Array.isArray(columns) || columns.some(c => !Number.isInteger(c) || c < 0) ||
543
+ typeof text !== 'boolean') return null;
544
+ lines.set(line, { edits: [], reviews: [], skips, skipColumns: columns.slice(), text, tokens, macroBody: false });
545
+ } else if (record[0] === 'reference') {
546
+ const [, line, column, accessKind, reason, receiver] = record;
547
+ if (!Number.isInteger(column) || column < 0 || typeof accessKind !== 'string' ||
548
+ typeof reason !== 'string' || receiver != null && typeof receiver !== 'string') return null;
549
+ references.push({ file: entry.relativePath, absoluteFile: file, line, column,
550
+ expression: this.index.getLineContent(file, line).trim(), accessKind,
551
+ verdict: 'no', reason, ...(receiver != null && { receiver }) });
552
+ } else return null;
553
+ }
554
+ if (lines.size !== rows.length) return null;
555
+ this.references.push(...references);
556
+ return lines;
557
+ }
558
+
559
+ rememberLocalFile(file, lines, localOnly) {
560
+ this.enumReferenceSyntax?.remember(file, lines);
561
+ const state = this.localFileModes.get(file);
562
+ if (!state || !localOnly || [...lines.values()].some(slot =>
563
+ slot.edits.length || slot.reviews.length || slot.macroBody || slot.stringAnnotation)) return;
564
+ const references = this.references.slice(state.referenceStart);
565
+ if (references.some(ref => ref.absoluteFile !== file || ref.verdict !== 'no')) return;
566
+ const value = [...lines].map(([line, slot]) =>
567
+ ['line', line, slot.skips, slot.skipColumns.slice(), slot.text, slot.tokens]);
568
+ for (const ref of references) value.push(['reference', ref.line, ref.column,
569
+ ref.accessKind, ref.reason, ref.receiver ?? null]);
570
+ require('./cache').storeUsageResult(this.index, file, this.name, state.mode, value);
571
+ }
572
+
573
+ siteArity() { return null; }
574
+
575
+ valueScope(file, line, column = null) {
576
+ const language = this.index.files.get(file)?.language;
577
+ const reader = langTraits(language)?.valueScopes;
578
+ if (!reader) return null;
579
+ if (!this.scopeMemo.has(file)) this.scopeMemo.set(file,
580
+ reader(this.index._readFile(file), getParser(language)));
581
+ const scopes = this.scopeMemo.get(file).filter(scope => line - 1 >= scope.startRow &&
582
+ line - 1 <= scope.endRow && (column == null ||
583
+ (line - 1 !== scope.startRow || column >= scope.startCol) &&
584
+ (line - 1 !== scope.endRow || column < scope.endCol)));
585
+ const identities = new Set(scopes.map(scope => scope.module
586
+ ? `module:${scope.startRow}:${scope.startCol}` : 'classic'));
587
+ return identities.size === 1 ? [...identities][0] : 'unknown';
588
+ }
589
+
590
+ scopeRelation(file, definitionLine, node, definitionColumn = null) {
591
+ const declared = this.valueScope(file, definitionLine, definitionColumn);
592
+ if (declared == null) return 'yes';
593
+ const referenced = this.valueScope(file, node.startPosition.row + 1, node.startPosition.column);
594
+ if (declared === 'unknown' || referenced === 'unknown') return 'unknown';
595
+ if (declared === referenced) return 'yes';
596
+ return declared === 'classic' ? 'unknown' : 'no';
597
+ }
598
+
599
+ valueStorage(definition) {
600
+ return definition.modifiers?.includes('static') ||
601
+ ownerRefOf(this.index, definition)?.def?.type === 'enum' ? 'class' : 'instance';
602
+ }
603
+
604
+ thisStorage(node) {
605
+ for (let scope = node.parent; scope; scope = scope.parent) {
606
+ if (scope.type === 'class_static_block') return 'class';
607
+ if (['method_definition', 'field_definition', 'public_field_definition'].includes(scope.type) &&
608
+ scope.parent?.type === 'class_body') {
609
+ return scope.children.some(child => child.type === 'static') ? 'class' : 'instance';
610
+ }
611
+ }
612
+ return null;
613
+ }
614
+
615
+ patternReference(file, node, role) {
616
+ if (!this.patternConstant || !role.binding) return null;
617
+ for (let current = node; current?.parent; current = current.parent) {
618
+ const parent = current.parent;
619
+ const pattern = parent.childForFieldName('pattern');
620
+ if (pattern && contains(pattern, node)) {
621
+ return this.resolveName(file, node.startPosition.row + 1);
622
+ }
623
+ if (CALLABLE_SCOPES.has(parent.type) || ROOTS.has(parent.type)) break;
624
+ }
625
+ return null;
626
+ }
627
+
628
+ enumInfo(definition) {
629
+ if (this.enumMemo.has(definition)) return this.enumMemo.get(definition);
630
+ const result = enumValueBindingInfo(this.index, definition);
631
+ this.enumMemo.set(definition, result);
632
+ return result;
633
+ }
634
+
635
+ fileTypes(file, name = this.name) {
636
+ return (this.index.symbols.get(name) || []).filter(d => d.file === file &&
637
+ isValueDefinition(d) && !d.className && (!this.siteNode ||
638
+ this.scopeRelation(file, d.nameLine || d.startLine, this.siteNode) !== 'no'));
639
+ }
640
+
641
+ syntaxNodes(file, tree, name) {
642
+ const key = `${file}\0${tree.rootNode.id}\0${name}`;
643
+ if (this.syntaxMemo.has(key)) return this.syntaxMemo.get(key);
644
+ const language = GRAMMAR_LANGUAGES.get(tree.language?.name) || this.index.files.get(file)?.language;
645
+ // The tokens of a name in a tree are syntax alone: every resolver of
646
+ // the process shares them while the tree lives (fix #436: each
647
+ // query re-classified every occurrence of each receiver's name).
648
+ // A witness view records its reads, so it derives its own.
649
+ let shared = null;
650
+ if (!this.index.isProvenanceFactIndex) {
651
+ shared = SYNTAX_NODES_BY_TREE.get(tree);
652
+ if (!shared) SYNTAX_NODES_BY_TREE.set(tree, shared = new Map());
653
+ const nodes = shared.get(`${language}\0${name}`);
654
+ if (nodes) {
655
+ this.syntaxMemo.set(key, nodes);
656
+ return nodes;
657
+ }
658
+ }
659
+ const source = typeof tree.input === 'string' ? tree.input : this.index._readFile(file);
660
+ const nodes = [];
661
+ visitNameNodes(tree, source, name, node => {
662
+ if (node.text !== name) return true;
663
+ const role = referenceRole(node, language);
664
+ const declarationName = role.binding && role.why === 'function' &&
665
+ CALLABLE_SCOPES.has(node.parent?.type);
666
+ // Ordinary references need their token and role; only a binding
667
+ // needs the scope in which it declares the name.
668
+ let scope = role.binding
669
+ ? referenceBindingScope(declarationName ? node.parent : node, language, role) : null;
670
+ if (role.genericParameter) scope = referenceTypeParameterScope(node);
671
+ if (role.binding && node.parent?.parent?.type === 'template_parameter_list') {
672
+ scope = node.parent.parent.parent;
673
+ }
674
+ nodes.push({ node, role, scope });
675
+ return true;
676
+ });
677
+ this.syntaxMemo.set(key, nodes);
678
+ if (shared) {
679
+ if (shared.size >= 64) shared.delete(shared.keys().next().value);
680
+ shared.set(`${language}\0${name}`, nodes);
681
+ }
682
+ return nodes;
683
+ }
684
+
685
+ enumSyntaxFact(file, entry, token, receiver, qualifier, position, shape) {
686
+ const { node, role } = token;
687
+ if (['macro', 'template'].includes(role.role)) return {
688
+ line: node.startPosition.row + 1, column: node.startPosition.column, at: node.startIndex,
689
+ kind: 'opaque', sourceRole: role.role, accessKind: this.references.at(-1).accessKind,
690
+ };
691
+ const syntax = receiver || qualifier;
692
+ if (position || shape?.uncertainRole || shape?.kind === 'initializer' ||
693
+ !syntax || !TYPE_NAMES.has(syntax.type) ||
694
+ !(receiver && role.role === 'member' || qualifier && role.role === 'value')) return null;
695
+ for (let scope = node; scope; scope = scope.parent) {
696
+ if (scope.type === 'ERROR' || CALLABLE_SCOPES.has(scope.type) && scope.hasError) return null;
697
+ }
698
+ if (receiver && this.localBinding(file, syntax, syntax.text, true)) return null;
699
+ if (this.syntaxNodes(file, node.tree, syntax.text).some(binding => binding.role.typeParam &&
700
+ contains(referenceTypeParameterScope(binding.node), syntax))) return null;
701
+ let owner = null;
702
+ for (let parent = syntax.parent; parent; parent = parent.parent) {
703
+ if (!TYPE_BODIES.has(parent.type)) continue;
704
+ const name = parent.childForFieldName('name');
705
+ if (!name || !TYPE_NAMES.has(name.type)) return null;
706
+ owner = { name: name.text, startLine: parent.startPosition.row + 1,
707
+ endLine: parent.endPosition.row + 1 };
708
+ break;
709
+ }
710
+ const context = langTraits(entry.language).enumReferenceContext?.(syntax) || null;
711
+ return { line: node.startPosition.row + 1, column: node.startPosition.column,
712
+ at: node.startIndex, receiver: syntax.text, kind: receiver ? 'member' : 'type', owner, context,
713
+ accessKind: this.references.at(-1).accessKind };
714
+ }
715
+
716
+ selectedBinding(file, node) {
717
+ if (this.owner || file !== this.def.file || node.startPosition.row + 1 !==
718
+ (this.def.nameLine || this.def.startLine)) return false;
719
+ const bindings = this.syntaxNodes(file, node.tree, this.name).filter(token =>
720
+ token.node.startPosition.row === node.startPosition.row && token.role.binding &&
721
+ token.role.why !== 'parameter' && !TYPE_BODIES.has(token.scope?.type) &&
722
+ !['class_body', 'declaration_list', 'field_declaration_list'].includes(token.scope?.type));
723
+ return bindings.length === 1 && sameNode(bindings[0].node, node);
724
+ }
725
+
726
+ lexicalMemo(node) {
727
+ let memo = this.lexicalScopeMemos.get(node.tree);
728
+ if (!memo) { memo = new Map(); this.lexicalScopeMemos.set(node.tree, memo); }
729
+ return memo;
730
+ }
731
+
732
+ /** Python scope lookup (LEGB, `global`/`nonlocal`, defaults and
733
+ * decorators in the enclosing scope) of a name at `node`. A read
734
+ * directly in a class body that binds the name too follows the body's
735
+ * statement order: before every binding it reads the scope around the
736
+ * class (`limit = limit`), after an unconditional one the class
737
+ * namespace ('class'). */
738
+ pythonScope(node, name) {
739
+ const lexical = referenceScope(node, 'python', this.lexicalMemo(node), name, { globalBindsModule: true });
740
+ if (lexical !== 'unknown') return lexical;
741
+ const classNode = pythonClassBodyOf(node);
742
+ const order = classNode && pythonClassBodyOrder(classNode, node, name);
743
+ if (order === 'outer') return this.pythonScope(classNode, name);
744
+ if (order === 'class') return { local: true, defRows: null, classBody: classNode };
745
+ return lexical;
746
+ }
747
+
748
+ /**
749
+ * The binding a Python name reference resolves to (fix #420). Python
750
+ * binds per scope, not per statement: the lookup's own scope verdict
751
+ * decides, and a binding of the name in an enclosing function never
752
+ * shadows a reference its rules send elsewhere (a `global` declaration,
753
+ * a default value evaluated in the enclosing scope).
754
+ */
755
+ pythonLocalBinding(file, node, name, lexical, includeModule) {
756
+ if (lexical === 'none') return { verdict: 'no', reason: 'non-value-position' };
757
+ if (!lexical || lexical === 'unknown') return { verdict: 'unknown', reason: 'lexical-binding-unresolved' };
758
+ if (lexical === 'module' && !includeModule) return null;
759
+ if (lexical.classBody) return this.owner ? null : { verdict: 'no', reason: 'class-binding' };
760
+ const scope = lexical === 'module' ? node.tree.rootNode : lexical.scopeNode;
761
+ const tokens = this.syntaxNodes(file, node.tree, name);
762
+ if (lexical === 'module' && tokens.some(token => token.role.scopeDeclaration === 'global_statement')) {
763
+ return { verdict: 'unknown', reason: 'module-rebinding' };
764
+ }
765
+ const bindings = scope ? tokens.filter(token => token.role.binding && token.scope &&
766
+ sameNode(token.scope, scope)) : [];
767
+ if (bindings.length === 1 && bindings[0].node.startIndex < node.startIndex) {
768
+ return { binding: { ...bindings[0], scope } };
769
+ }
770
+ if (lexical === 'module') {
771
+ return bindings.length ? { verdict: 'unknown', reason: 'module-rebinding' } : null;
772
+ }
773
+ return { verdict: 'no', reason: 'local-binding' };
774
+ }
775
+
776
+ localBinding(file, node, name, includeModule = false, contextualValue = false) {
777
+ const language = GRAMMAR_LANGUAGES.get(node.tree.language?.name) || this.index.files.get(file)?.language;
778
+ const memo = this.lexicalMemo(node);
779
+ if (langTraits(language)?.assignmentDeclaresLocal) {
780
+ return this.pythonLocalBinding(file, node, name, this.pythonScope(node, name), includeModule);
781
+ }
782
+ const lexical = referenceScope(node, language, memo, name);
783
+ if (lexical === 'none' && !contextualValue) return { verdict: 'no', reason: 'non-value-position' };
784
+ let bindings = this.syntaxNodes(file, node.tree, name).filter(binding =>
785
+ binding.role.binding &&
786
+ this.scopeRelation(file, binding.node.startPosition.row + 1, node,
787
+ binding.node.startPosition.column) !== 'no' &&
788
+ !(name === this.name && this.patternReference(file, binding.node, binding.role)?.verdict === 'yes') &&
789
+ (includeModule || binding.scope && !ROOTS.has(binding.scope.type)) &&
790
+ (binding.role.genericParameter || !TYPE_BODIES.has(binding.scope?.type)) &&
791
+ !['class_body', 'declaration_list', 'field_declaration_list'].includes(binding.scope?.type) &&
792
+ contains(binding.scope, node)).map(binding => ({ ...binding,
793
+ scope: binding.scope || node.tree.rootNode }));
794
+ // A pattern variable is in scope where its pattern's flow puts it
795
+ // (fix #420: Java `instanceof`/switch patterns), not in its block.
796
+ const patternReach = langTraits(language)?.patternBindingReach;
797
+ if (patternReach && bindings.some(binding => binding.role.why === 'pattern')) {
798
+ const reached = [];
799
+ for (const binding of bindings) {
800
+ const verdict = binding.role.why === 'pattern' ? patternReach(binding.node, node) : 'in';
801
+ if (verdict === 'unknown') return { verdict: 'unknown', reason: 'pattern-variable-scope' };
802
+ if (verdict === 'in') reached.push(binding);
803
+ }
804
+ bindings = reached;
805
+ }
806
+ if (!bindings.length) {
807
+ if (lexical?.local && name === this.name && langTraits(language)?.moduleValueLookup &&
808
+ this.resolveName(file, node.startPosition.row + 1).verdict === 'yes') return null;
809
+ return lexical === 'unknown' || lexical?.local
810
+ ? { verdict: 'unknown', reason: 'lexical-binding-unresolved' } : null;
811
+ }
812
+ bindings.sort((a, b) => (a.scope.endIndex - a.scope.startIndex) -
813
+ (b.scope.endIndex - b.scope.startIndex) || b.node.startIndex - a.node.startIndex);
814
+ const nearest = bindings[0];
815
+ if (this.scopeRelation(file, nearest.node.startPosition.row + 1, node,
816
+ nearest.node.startPosition.column) === 'unknown') {
817
+ return { verdict: 'unknown', reason: 'script-binding-scope' };
818
+ }
819
+ if (name === this.name && this.patternReference(file, nearest.node, nearest.role)?.verdict === 'unknown') {
820
+ return { verdict: 'unknown', reason: 'constant-pattern-binding' };
821
+ }
822
+ const sameScope = bindings.filter(binding => sameNode(binding.scope, nearest.scope));
823
+ if (sameScope.length !== 1 || nearest.node.startIndex >= node.startIndex) {
824
+ return { verdict: 'unknown', reason: 'lexical-binding-unresolved' };
825
+ }
826
+ return { binding: nearest,
827
+ ...(name === this.name && this.selectedBinding(file, nearest.node) && { pin: true }) };
828
+ }
829
+
830
+ /**
831
+ * A Python token that binds or declares a name (an assignment or loop
832
+ * target, a def or class name, an import, a `global` declaration):
833
+ * whether it binds the pinned module variable (fix #420). Null for
834
+ * tokens that bind nothing. An unaliased import of the name binds the
835
+ * variable too; renaming it gives the import an alias (`importAlias`).
836
+ */
837
+ /**
838
+ * A Python read run at module load before the pin's variable is first
839
+ * bound reads what a preceding star import binds (fix #434): `from m
840
+ * import *` then `X = LIMIT + 1` above `LIMIT = 5` reads m's LIMIT. A
841
+ * read in a function body runs later and reads the variable.
842
+ */
843
+ pythonStarBindingAt(file, node) {
844
+ const entry = this.index.files.get(file);
845
+ const line = node.startPosition.row + 1;
846
+ const stars = (entry?.importDetails || []).filter(detail => detail.names?.includes('*') && detail.line <= line);
847
+ if (stars.length === 0) return null;
848
+ for (let parent = node.parent; parent; parent = parent.parent) {
849
+ if (parent.type === 'function_definition' || parent.type === 'lambda') return null;
850
+ }
851
+ const lines = [...this.group].filter(def => def.file === file).map(def => def.nameLine || def.startLine);
852
+ if (lines.length === 0 || line > Math.min(...lines)) return null;
853
+ const binds = stars.map(star => {
854
+ const target = this.moduleFile(entry, star.module);
855
+ const other = target && this.index.files.get(target);
856
+ if (!other) return 'unknown';
857
+ const exported = (other.exportDetails || []).some(detail => (detail.alias || detail.name) === this.name);
858
+ if (this.name.startsWith('_') && !exported) return 'no';
859
+ if (exported || (other.symbols || []).some(def => def.name === this.name && !def.className &&
860
+ def.lexicalScopeStartLine == null) ||
861
+ (other.importBindings || []).some(binding => (binding.alias || binding.name) === this.name) ||
862
+ (other.moduleAssignedNames || []).includes(this.name)) return 'yes';
863
+ return (other.importNames || []).includes('*') ? 'unknown' : 'no';
864
+ });
865
+ if (binds.includes('yes')) return { verdict: 'no', reason: 'star-import-binding' };
866
+ return binds.includes('unknown') ? { verdict: 'unknown', reason: 'star-import-binding' } : null;
867
+ }
868
+
869
+ pythonBindingAt(file, token) {
870
+ const { node, role } = token;
871
+ const parent = node.parent;
872
+ const inPinFile = file === this.def.file;
873
+ const moduleVerdict = () => {
874
+ if (inPinFile) return { verdict: 'yes' };
875
+ // Another module's variable that its import binds to the pin is
876
+ // renamed as a whole there; one some other import also binds
877
+ // stays for review.
878
+ const importer = this.pythonImporterVariable(file);
879
+ return importer === true ? { verdict: 'yes' } : importer === 'mixed'
880
+ ? { verdict: 'unknown', reason: 'module-rebinding' } : { verdict: 'no', reason: 'other-module' };
881
+ };
882
+ if (role.scopeDeclaration) {
883
+ return role.scopeDeclaration === 'global_statement' ? moduleVerdict()
884
+ : { verdict: 'no', reason: 'nonlocal-binding' };
885
+ }
886
+ // Another module's imports of the pin are import references.
887
+ const importStatement = inPinFile &&
888
+ (role.role === 'import' || ['module-path', 'import-alias'].includes(role.why));
889
+ const classDecl = role.role === 'decl' && parent?.type === 'class_definition';
890
+ if (!role.binding && !importStatement && !classDecl) return null;
891
+ if (role.why === 'parameter') return { verdict: 'no', reason: 'local-binding' };
892
+ let edit = { verdict: 'yes' };
893
+ if (importStatement) {
894
+ if (role.why === 'module-path') {
895
+ // `import json` binds its first segment; `from pkg import x`
896
+ // names a module path and binds nothing of that spelling.
897
+ const dotted = parent?.type === 'dotted_name' ? parent : null;
898
+ if (!dotted || dotted.parent?.type !== 'import_statement' ||
899
+ !sameNode(dotted.namedChild(0), node)) return { verdict: 'no', reason: 'module-path' };
900
+ if (dotted.namedChildCount > 1) edit = { verdict: 'unknown', reason: 'dotted-import-binding' };
901
+ else edit = { verdict: 'yes', importAlias: true };
902
+ } else if (role.role === 'import') {
903
+ if (role.aliased) return { verdict: 'no', reason: 'import-alias' };
904
+ // The alias token of `from m import x as x` binds; a plain
905
+ // `from m import x` binds the name it imports.
906
+ if (parent?.type !== 'aliased_import') edit = { verdict: 'yes', importAlias: true };
907
+ }
908
+ }
909
+ const scope = referenceBindingScope(role.why === 'function' || classDecl ? parent : node, 'python', role);
910
+ if (scope?.type === 'class_definition') return { verdict: 'no', reason: 'class-binding' };
911
+ const lexical = this.pythonScope(node, this.name);
912
+ if (lexical === 'module') {
913
+ const verdict = moduleVerdict();
914
+ return verdict.verdict === 'yes' ? edit : verdict;
915
+ }
916
+ if (lexical && typeof lexical === 'object') return { verdict: 'no', reason: 'local-binding' };
917
+ // An import binds in the function holding it, a lookup the scope
918
+ // rules leave open only for imported names.
919
+ if (importStatement) {
920
+ for (let current = node.parent; current; current = current.parent) {
921
+ if (current.type === 'function_definition' || current.type === 'lambda') {
922
+ return { verdict: 'no', reason: 'local-binding' };
923
+ }
924
+ }
925
+ }
926
+ return { verdict: 'unknown', reason: 'lexical-binding-unresolved' };
927
+ }
928
+
929
+ /**
930
+ * A Python module whose module variable of the pin's name its own
931
+ * imports bind to the pin (fix #420): that variable is the pin there
932
+ * (true), so its rebindings, `global` writes and reads are renamed with
933
+ * the import. 'mixed' when another import binds it to something else,
934
+ * false when no import of the module binds it to the pin.
935
+ */
936
+ pythonImporterVariable(file) {
937
+ if (!this._importerVariables) this._importerVariables = new Map();
938
+ if (this._importerVariables.has(file)) return this._importerVariables.get(file);
939
+ const entry = this.index.files.get(file);
940
+ let result = false;
941
+ if (file !== this.def.file && !this.owner && entry && langTraits(entry.language)?.assignmentDeclaresLocal) {
942
+ // An import inside a function binds a local of it.
943
+ const bindings = (entry.importBindings || []).filter(binding => binding.module != null &&
944
+ binding.name !== '*' && (binding.alias || binding.name) === this.name &&
945
+ !(Number.isInteger(binding.line) && this.index.findEnclosingFunction(file, binding.line, true)));
946
+ const reaches = bindings.map(binding => binding.kind !== 'import' && binding.name === this.name &&
947
+ this.importBindingVerdict(file, entry, { ...binding, alias: undefined }, this.name).verdict === 'yes');
948
+ if (reaches.length > 0 && reaches.every(Boolean)) result = true;
949
+ else if (reaches.some(Boolean)) result = 'mixed';
950
+ }
951
+ this._importerVariables.set(file, result);
952
+ return result;
953
+ }
954
+
955
+ /**
956
+ * What a JS/TS import binding holds (fix #420): 'namespace' (the
957
+ * imported module's own namespace or exports object), `{ modules }`
958
+ * (the namespace of other modules it re-exports: `export * as ns`,
959
+ * `import * as ns ..; export { ns }`), 'value' (an exported declaration:
960
+ * its members are properties of that value) or 'unknown' (a re-export
961
+ * chain the index cannot follow).
962
+ */
963
+ jsBindingHolds(file, binding, depth = 0) {
964
+ const entry = this.index.files.get(file);
965
+ if (binding.kind === 'namespace' || binding.kind === 'require') {
966
+ if (depth === 0) return 'namespace';
967
+ const module = entry && this.moduleFile(entry, binding.module);
968
+ return module ? { modules: [module] } : 'unknown';
969
+ }
970
+ if (depth > 4) return 'unknown';
971
+ const target = entry && this.moduleFile(entry, binding.module);
972
+ if (!target) {
973
+ const { bindingIsExternal } = require('./type-denotation');
974
+ return bindingIsExternal(this.index, entry, file, binding) === true ? 'value' : 'unknown';
975
+ }
976
+ return this.jsExportHolds(target, binding.kind === 'default' ? null : binding.name, depth) || 'unknown';
977
+ }
978
+
979
+ /**
980
+ * What module `target` exports under `name` (null: its default export),
981
+ * as jsBindingHolds answers; null when it exports no such name. Names a
982
+ * module re-exports with `export * from` are its exports too (fix #431).
983
+ */
984
+ jsExportHolds(target, name, depth, seen = new Set()) {
985
+ if (depth > 4 || seen.has(target)) return 'unknown';
986
+ seen.add(target);
987
+ const targetEntry = this.index.files.get(target);
988
+ const exports = targetEntry?.exportDetails || [];
989
+ const details = exports.filter(detail => name == null ? detail.type === 'default'
990
+ : detail.type !== 'default' && detail.type !== 're-export-all' && (detail.alias || detail.name) === name ||
991
+ detail.type === 're-export-all' && detail.alias === name);
992
+ if (details.length === 0) {
993
+ if (name == null) return null;
994
+ const holds = [];
995
+ for (const star of exports) {
996
+ if (star.type !== 're-export-all' || star.alias) continue;
997
+ const module = star.source && this.moduleFile(targetEntry, star.source);
998
+ const hold = module ? this.jsExportHolds(module, name, depth + 1, seen) : 'unknown';
999
+ if (hold) holds.push(hold);
1000
+ }
1001
+ return holds.length > 0 ? mergeHolds(holds) : null;
1002
+ }
1003
+ const holds = details.map(detail => {
1004
+ if (detail.type === 're-export-all') {
1005
+ const module = detail.source && this.moduleFile(targetEntry, detail.source);
1006
+ return module ? { modules: [module] } : 'unknown';
1007
+ }
1008
+ if (detail.type === 're-export') {
1009
+ return detail.source ? this.jsBindingHolds(target, { name: detail.name, module: detail.source,
1010
+ kind: detail.name === 'default' ? 'default' : 'named' }, depth + 1) : 'unknown';
1011
+ }
1012
+ if (detail.type === 'exports' || detail.type === 'module.exports') return 'value';
1013
+ const imported = (targetEntry?.importBindings || []).filter(other =>
1014
+ (other.alias || other.name) === detail.name && other.module != null);
1015
+ if (imported.length === 0) return 'value';
1016
+ return mergeHolds(imported.map(other => this.jsBindingHolds(target, other, depth + 1)));
1017
+ });
1018
+ return mergeHolds(holds);
1019
+ }
1020
+
1021
+ /**
1022
+ * The top-level declaration module `target` exports under `name` (null:
1023
+ * its default export), followed through re-exports, imports and `export
1024
+ * *` (fix #434): `{ file, name }`, 'unknown' for a link the index cannot
1025
+ * follow or several answers, null when the module exports no such name.
1026
+ */
1027
+ jsExportedDeclaration(target, name, depth = 0, seen = new Set()) {
1028
+ const key = `${target}\0${name ?? ''}`;
1029
+ if (depth > 4 || seen.has(key)) return 'unknown';
1030
+ seen.add(key);
1031
+ const entry = this.index.files.get(target);
1032
+ const exports = entry?.exportDetails || [];
1033
+ const single = answers => {
1034
+ if (answers.some(answer => answer === 'unknown')) return 'unknown';
1035
+ const found = answers.filter(Boolean);
1036
+ if (found.length === 0) return null;
1037
+ return found.every(answer => answer.file === found[0].file && answer.name === found[0].name)
1038
+ ? found[0] : 'unknown';
1039
+ };
1040
+ const details = exports.filter(detail => name == null ? detail.type === 'default'
1041
+ : detail.type !== 'default' && detail.type !== 're-export-all' && (detail.alias || detail.name) === name);
1042
+ if (details.length === 0) {
1043
+ if (name == null) return null;
1044
+ return single(exports.filter(star => star.type === 're-export-all' && !star.alias).map(star => {
1045
+ const module = star.source && this.moduleFile(entry, star.source);
1046
+ return module ? this.jsExportedDeclaration(module, name, depth + 1, seen) : 'unknown';
1047
+ }));
1048
+ }
1049
+ return single(details.map(detail => {
1050
+ if (detail.type === 're-export') {
1051
+ const module = detail.source && this.moduleFile(entry, detail.source);
1052
+ return module ? this.jsExportedDeclaration(module, detail.name === 'default' ? null : detail.name,
1053
+ depth + 1, seen) : 'unknown';
1054
+ }
1055
+ if (detail.type === 'exports' || detail.type === 'module.exports') return 'unknown';
1056
+ const local = detail.localName || detail.name;
1057
+ if (!local || local === 'default') return 'unknown';
1058
+ const imported = (entry.importBindings || []).filter(binding =>
1059
+ (binding.alias || binding.name) === local && binding.module != null);
1060
+ if (imported.length === 0) return { file: target, name: local };
1061
+ return single(imported.map(binding => {
1062
+ if (binding.kind !== 'named' && binding.kind !== 'default') return 'unknown';
1063
+ const module = this.moduleFile(entry, binding.module);
1064
+ return module ? this.jsExportedDeclaration(module, binding.kind === 'default' ? null : binding.name,
1065
+ depth + 1, seen) : 'unknown';
1066
+ }));
1067
+ }));
1068
+ }
1069
+
1070
+ /**
1071
+ * The class, enum or interface a JS/TS receiver names through an import
1072
+ * the receiver's name alone does not spell (fix #434): an aliased or
1073
+ * default import (`import { Color as C }` then `C.Red`), a module
1074
+ * namespace chain (`ns.Color.Red`), or a type its namespace declares
1075
+ * (`E.Shade.Red`). Null when the link is no such import or undecided.
1076
+ */
1077
+ jsImportedOwner(file, receiver) {
1078
+ const entry = this.index.files.get(file);
1079
+ const language = entry?.language;
1080
+ const syntax = language === 'html' ? 'javascript' : language;
1081
+ let declaration = null;
1082
+ if (receiver.type === 'identifier') {
1083
+ const bindings = (entry.importBindings || []).filter(binding =>
1084
+ (binding.alias || binding.name) === receiver.text && binding.module != null);
1085
+ if (bindings.length !== 1) return null;
1086
+ const [binding] = bindings;
1087
+ if (binding.kind !== 'default' && !(binding.kind === 'named' && binding.alias && binding.alias !== binding.name)) return null;
1088
+ if (referenceScope(receiver, syntax, this.lexicalMemo(receiver), receiver.text) !== 'module') return null;
1089
+ const target = this.moduleFile(entry, binding.module);
1090
+ declaration = target ? this.jsExportedDeclaration(target, binding.kind === 'default' ? null : binding.name) : null;
1091
+ } else if (receiver.type === 'member_expression') {
1092
+ const property = receiver.childForFieldName('property');
1093
+ const object = receiver.childForFieldName('object');
1094
+ if (property?.type !== 'property_identifier' || !object) return null;
1095
+ const holds = this.jsNamespaceHolds(file, object);
1096
+ if (holds?.modules) {
1097
+ const answers = holds.modules.map(module => this.jsExportedDeclaration(module, property.text));
1098
+ declaration = answers.length === 1 ? answers[0] : 'unknown';
1099
+ } else if (!holds && object.type === 'identifier') {
1100
+ // A type a TS namespace declares (`E.Shade`), the namespace
1101
+ // named in this module.
1102
+ const space = this.jsImportedOwner(file, object) || this.classForType(file, { name: object.text, node: object }, true);
1103
+ const named = space?.def?.type === 'namespace' ? space.def
1104
+ : (this.index.symbols.get(object.text) || []).filter(def => def.type === 'namespace' && def.file === file &&
1105
+ !def.className && referenceScope(object, syntax, this.lexicalMemo(object), object.text) === 'module');
1106
+ const spaceDef = Array.isArray(named) ? (named.length === 1 ? named[0] : null) : named;
1107
+ if (!spaceDef) return null;
1108
+ const inner = classDefsNamed(this.index, property.text).entries.filter(candidate =>
1109
+ candidate.def.file === spaceDef.file && candidate.def.startLine > spaceDef.startLine &&
1110
+ candidate.def.endLine <= spaceDef.endLine &&
1111
+ !(this.index.files.get(spaceDef.file)?.symbols || []).some(other => other !== spaceDef &&
1112
+ other.type === 'namespace' && other.startLine > spaceDef.startLine && other.endLine <= spaceDef.endLine &&
1113
+ other.startLine < candidate.def.startLine && other.endLine >= candidate.def.endLine));
1114
+ return inner.length === 1 ? { name: property.text, key: inner[0].key, def: inner[0].def } : null;
1115
+ }
1116
+ }
1117
+ if (!declaration || declaration === 'unknown') return null;
1118
+ const entries = classDefsNamed(this.index, declaration.name).entries.filter(candidate =>
1119
+ candidate.def.file === declaration.file && !candidate.def.className && candidate.def.lexicalScopeStartLine == null);
1120
+ return entries.length === 1 ? { name: declaration.name, key: entries[0].key, def: entries[0].def } : null;
1121
+ }
1122
+
1123
+ /**
1124
+ * The modules a namespace receiver chain denotes (fix #431): an import
1125
+ * binding of a module namespace, then each `.name` the modules export as
1126
+ * a namespace (`z.core` through `export * as core from`). 'value' when a
1127
+ * link holds an ordinary value, 'unknown' when a link cannot be
1128
+ * followed, null when the head is no module-scope import binding.
1129
+ */
1130
+ jsNamespaceHolds(file, expression, depth = 0) {
1131
+ if (depth > 4) return 'unknown';
1132
+ if (expression?.type === 'identifier') {
1133
+ const language = this.index.files.get(file)?.language;
1134
+ const lexical = referenceScope(expression, language === 'html' ? 'javascript' : language,
1135
+ this.lexicalMemo(expression), expression.text);
1136
+ if (lexical !== 'module') return lexical === 'unknown' ? 'unknown' : null;
1137
+ const entry = this.index.files.get(file);
1138
+ const bindings = (entry?.importBindings || []).filter(binding =>
1139
+ (binding.alias || binding.name) === expression.text && binding.module != null);
1140
+ if (bindings.length === 0) return null;
1141
+ return mergeHolds(bindings.map(binding => {
1142
+ if (binding.kind !== 'namespace' && binding.kind !== 'require') return this.jsBindingHolds(file, binding);
1143
+ const module = entry && this.moduleFile(entry, binding.module);
1144
+ return module ? { modules: [module] } : 'unknown';
1145
+ }));
1146
+ }
1147
+ if (expression?.type !== 'member_expression') return null;
1148
+ const property = expression.childForFieldName('property');
1149
+ if (property?.type !== 'property_identifier') return null;
1150
+ const inner = this.jsNamespaceHolds(file, expression.childForFieldName('object'), depth + 1);
1151
+ if (!inner?.modules) return inner;
1152
+ return mergeHolds(inner.modules.map(module => this.jsExportHolds(module, property.text, 1) || 'unknown'));
1153
+ }
1154
+
1155
+ /**
1156
+ * An import binding of a module's whole value at this token (fix #431):
1157
+ * `const req = require('./request')` of a module whose `module.exports`
1158
+ * is the pin, `import req from './request'` of one whose default export
1159
+ * is the pin. The access verdict, or null.
1160
+ */
1161
+ jsWholeModuleImport(file, entry, node) {
1162
+ const line = node.startPosition.row + 1;
1163
+ const binding = (entry.importBindings || []).find(candidate => candidate.line === line &&
1164
+ (candidate.alias || candidate.name) === node.text && candidate.module != null &&
1165
+ (candidate.kind === 'default' || candidate.kind === 'require' && candidate.defaultLike));
1166
+ const access = binding && this.wholeModuleValue(entry, binding);
1167
+ return access ? { ...access, accessKind: 'import' } : null;
1168
+ }
1169
+
1170
+ /** The access verdict when `binding` imports a module whose whole value
1171
+ * (`module.exports`, the default export) is the pin, else null. */
1172
+ wholeModuleValue(entry, binding) {
1173
+ const module = this.moduleFile(entry, binding.module);
1174
+ if (!module || module !== this.def.file || this.def.className || this.def.lexicalScopeStartLine != null) return null;
1175
+ const whole = (this.index.files.get(module)?.exportDetails || []).filter(detail =>
1176
+ binding.kind === 'default' ? detail.type === 'default'
1177
+ : detail.type === 'module.exports' && detail.defaultLike);
1178
+ if (whole.length === 0) return null;
1179
+ const names = whole.map(detail => detail.localName || detail.name);
1180
+ if (names.every(name => name === this.def.name)) return { verdict: 'yes' };
1181
+ return names.some(name => name === this.def.name)
1182
+ ? { verdict: 'unknown', reason: 'module-value-alternatives' } : null;
1183
+ }
1184
+
1185
+ /** A whole-module import binds the module's value under a name of the
1186
+ * importer's own (fix #431): its reads depend on the pin, a rename keeps
1187
+ * them. */
1188
+ importBindingVerdict(file, entry, binding, name) {
1189
+ if (this.family === 'js' && !this.owner && binding.module != null &&
1190
+ (binding.kind === 'default' || binding.kind === 'require' && binding.defaultLike)) {
1191
+ const access = this.wholeModuleValue(entry, binding);
1192
+ if (access) return { verdict: 'no', reason: 'import-alias', access };
1193
+ }
1194
+ return super.importBindingVerdict(file, entry, binding, name);
1195
+ }
1196
+
1197
+ /**
1198
+ * A JS/TS member access `R.name` against a module-scope value (fix
1199
+ * #420): only a module namespace or exports object holds the module's
1200
+ * bindings as members. A receiver a local binding shadows, or an
1201
+ * imported value (named or default import of a declaration), holds
1202
+ * ordinary properties. A CommonJS export key (`exports.name = ..`) is
1203
+ * a property of the exports object, not the module's binding. Null when
1204
+ * the module-attribute lookup decides.
1205
+ */
1206
+ jsModuleMemberAt(file, receiver) {
1207
+ // `require('./m').name` reads what module m binds (fix #434): its
1208
+ // own declaration, or a CommonJS export key of its exports object.
1209
+ if (receiver.type === 'call_expression') {
1210
+ const callee = receiver.childForFieldName('function');
1211
+ const args = (receiver.childForFieldName('arguments')?.namedChildren || [])
1212
+ .filter(arg => !arg.type.endsWith('comment'));
1213
+ if (callee?.type !== 'identifier' || callee.text !== 'require' || args.length !== 1 ||
1214
+ args[0].type !== 'string') return null;
1215
+ const language = this.index.files.get(file)?.language;
1216
+ const entry = this.index.files.get(file);
1217
+ // The module scope's `require`, never a local or a module's own.
1218
+ if (referenceScope(callee, language === 'html' ? 'javascript' : language, this.lexicalMemo(callee),
1219
+ 'require') !== 'module' || (entry?.symbols || []).some(def => def.name === 'require' && !def.className) ||
1220
+ (entry?.importBindings || []).some(binding => (binding.alias || binding.name) === 'require')) {
1221
+ return { verdict: 'unknown', reason: 'receiver-scope' };
1222
+ }
1223
+ const spec = args[0].namedChildren.find(child => child.type === 'string_fragment')?.text ?? '';
1224
+ const target = this.moduleFile(entry, spec);
1225
+ if (!target) {
1226
+ const { _unresolvedModuleIsGap } = require('./callers');
1227
+ return _unresolvedModuleIsGap(this.index, spec) ? { verdict: 'unknown', reason: 'module-attribute' }
1228
+ : { verdict: 'no', reason: 'other-module' };
1229
+ }
1230
+ const own = (this.index.files.get(target)?.exportDetails || []).filter(detail =>
1231
+ (detail.alias || detail.name) === this.name);
1232
+ const keyed = own.filter(detail => detail.type === 'exports' || detail.type === 'module.exports');
1233
+ if (own.length > 0 && keyed.length === own.length) return { verdict: 'no', reason: 'export-key' };
1234
+ if (keyed.length > 0) return { verdict: 'unknown', reason: 'export-key' };
1235
+ const reach = this.chase(target, this.name);
1236
+ return reach === 'yes' ? { verdict: 'yes' } : reach === 'no' ? { verdict: 'no', reason: 'other-module' }
1237
+ : { verdict: 'unknown', reason: 'module-attribute' };
1238
+ }
1239
+ if (receiver.type === 'member_expression') {
1240
+ // `z.core.name` through a re-exported namespace (fix #431).
1241
+ const holds = this.jsNamespaceHolds(file, receiver);
1242
+ if (!holds) return null;
1243
+ if (holds === 'value') return { verdict: 'no', reason: 'member' };
1244
+ if (!holds.modules) return { verdict: 'unknown', reason: 'module-attribute' };
1245
+ const reach = holds.modules.map(module => this.chase(module, this.name));
1246
+ return reach.every(verdict => verdict === 'yes') ? { verdict: 'yes' }
1247
+ : reach.every(verdict => verdict === 'no') ? { verdict: 'no', reason: 'other-module' }
1248
+ : { verdict: 'unknown', reason: 'module-attribute' };
1249
+ }
1250
+ if (receiver.type !== 'identifier') return null;
1251
+ const language = this.index.files.get(file)?.language;
1252
+ const lexical = referenceScope(receiver, language === 'html' ? 'javascript' : language,
1253
+ this.lexicalMemo(receiver), receiver.text);
1254
+ if (lexical === 'none') return null;
1255
+ if (!lexical || lexical === 'unknown') return { verdict: 'unknown', reason: 'receiver-scope' };
1256
+ if (lexical !== 'module') return { verdict: 'no', reason: 'member' };
1257
+ const entry = this.index.files.get(file);
1258
+ const bindings = (entry?.importBindings || []).filter(binding =>
1259
+ (binding.alias || binding.name) === receiver.text && binding.module != null);
1260
+ if (bindings.length === 0) return null;
1261
+ const holds = bindings.map(binding => this.jsBindingHolds(file, binding));
1262
+ if (holds.every(hold => hold === 'value')) return { verdict: 'no', reason: 'member' };
1263
+ // A re-exported namespace names the modules it re-exports.
1264
+ if (holds.every(hold => hold?.modules)) {
1265
+ const reach = holds.flatMap(hold => hold.modules).map(module => this.chase(module, this.name));
1266
+ return reach.every(verdict => verdict === 'yes') ? { verdict: 'yes' }
1267
+ : reach.every(verdict => verdict === 'no') ? { verdict: 'no', reason: 'other-module' }
1268
+ : { verdict: 'unknown', reason: 'module-attribute' };
1269
+ }
1270
+ if (holds.some(hold => hold !== 'namespace')) return { verdict: 'unknown', reason: 'import-binding-value' };
1271
+ const keyed = bindings.filter(binding => binding.kind === 'require').map(binding => {
1272
+ const target = this.moduleFile(entry, binding.module);
1273
+ const exports = target ? this.index.files.get(target)?.exportDetails || [] : [];
1274
+ const own = exports.filter(detail => (detail.alias || detail.name) === this.name);
1275
+ return own.length > 0 && own.every(detail => detail.type === 'exports' || detail.type === 'module.exports');
1276
+ });
1277
+ if (keyed.length > 0 && keyed.every(Boolean)) return { verdict: 'no', reason: 'export-key' };
1278
+ if (keyed.some(Boolean)) return { verdict: 'unknown', reason: 'export-key' };
1279
+ return null;
1280
+ }
1281
+
1282
+ /**
1283
+ * A bare name that provably never denotes a class or enum member here
1284
+ * (fix #420): a language without implicit `this` reaches member values
1285
+ * by name only inside the declaring enum or namespace (JS/TS), directly
1286
+ * in a class body (Python), through imported variants (Rust), or never.
1287
+ */
1288
+ bareMemberVerdict(file, node, language) {
1289
+ const traits = langTraits(language);
1290
+ const miss = { verdict: 'no', reason: 'bare-name-not-member' };
1291
+ if (!traits || traits.bareCallReachesMethods) return null;
1292
+ switch (traits.bareValueMemberScope) {
1293
+ case 'none': return miss;
1294
+ case 'imports': return this.def.type !== 'variant' ? miss : null;
1295
+ case 'class-body': {
1296
+ const lexical = this.pythonScope(node, this.name);
1297
+ // A class body read after its binding reads that class's
1298
+ // attribute (statement order).
1299
+ if (lexical?.classBody) return this.memberAt(this.enclosingOwner(file, node));
1300
+ return lexical === 'module' || (!!lexical && typeof lexical === 'object') ? miss : null;
1301
+ }
1302
+ case 'declaration-body':
1303
+ for (let parent = node.parent; parent; parent = parent.parent) {
1304
+ if (['enum_declaration', 'enum_body', 'internal_module', 'module'].includes(parent.type)) return null;
1305
+ }
1306
+ return miss;
1307
+ default: return null;
1308
+ }
1309
+ }
1310
+
1311
+ /**
1312
+ * The declared type of the object literal holding a JS/TS property
1313
+ * (fix #420): `const v: T = { key: .. }`, `{ .. } as T`, `{ .. }
1314
+ * satisfies T`. Its key is that type's member. Null when no written
1315
+ * type gives the literal its contract.
1316
+ */
1317
+ objectLiteralOwner(file, pair) {
1318
+ const object = pair.parent;
1319
+ if (object?.type !== 'object') return null;
1320
+ const holder = object.parent;
1321
+ let type = null;
1322
+ if (holder?.type === 'variable_declarator' && sameNode(holder.childForFieldName('value'), object)) {
1323
+ type = holder.childForFieldName('type');
1324
+ } else if ((holder?.type === 'as_expression' || holder?.type === 'satisfies_expression') &&
1325
+ sameNode(holder.namedChild(0), object)) {
1326
+ type = holder.namedChild(1);
1327
+ }
1328
+ const syntax = type && typeSyntax(type);
1329
+ return syntax ? this.classForType(file, syntax) : null;
1330
+ }
1331
+
1332
+ declarationNode(definition) {
1333
+ const file = definition.file, source = this.index._readFile(file);
1334
+ const language = this.index.files.get(file)?.language;
1335
+ for (const tree of declarationTrees(this.index, file, source, language)) {
1336
+ const declarations = this.syntaxNodes(file, tree, definition.name).filter(token =>
1337
+ token.node.startPosition.row + 1 === (definition.nameLine || definition.startLine) &&
1338
+ (token.role.role === 'decl' || token.role.binding ||
1339
+ ['field-name', 'member-name', 'property-name'].includes(token.role.why)));
1340
+ if (declarations.length > 1) return null;
1341
+ if (declarations.length === 1) return declarations[0].role.declNode || declarations[0].node.parent;
1342
+ }
1343
+ return null;
1344
+ }
1345
+
1346
+ typeVerdict(file, syntax, definition, useFile = file) {
1347
+ const resolver = new TypeReferenceResolver(this.index, definition, syntax.name);
1348
+ if (langTraits(this.index.files.get(file)?.language)?.textualIncludes &&
1349
+ TYPE_KINDS.has(definition.type) && !definition.aliasOf) {
1350
+ const canonical = completeForwardDeclaration(this.index, definition, useFile);
1351
+ resolver.group = new Set([canonical]);
1352
+ for (const other of resolver.all) {
1353
+ if (other.forwardDeclaration &&
1354
+ completeForwardDeclaration(this.index, other, useFile) === canonical) resolver.group.add(other);
1355
+ }
1356
+ resolver.pinFiles = new Set([...resolver.group].map(d => d.file));
1357
+ }
1358
+ resolver.siteNode = syntax.node;
1359
+ resolver.siteFile = file;
1360
+ return langTraits(this.index.files.get(file)?.language)?.textualIncludes
1361
+ ? resolver.resolveCFamily(file, syntax.node.startPosition.row + 1,
1362
+ syntax.name, syntax.qualifierText || null,
1363
+ referenceRole(syntax.node, this.index.files.get(file)?.language))
1364
+ : syntax.qualifierText
1365
+ ? resolver.resolveQualified(file, syntax.node.startPosition.row + 1,
1366
+ syntax.qualifier, syntax.qualifierText, { role: 'type' })
1367
+ : resolver.resolveName(file, syntax.node.startPosition.row + 1, syntax.name);
1368
+ }
1369
+
1370
+ classForType(file, syntax, runtime = false, useFile = file) {
1371
+ if (!syntax) return null;
1372
+ const unit = langTraits(this.language)?.textualIncludes ? useFile : '';
1373
+ const key = `${file}\0${syntax.node.startIndex}\0${syntax.name}\0${syntax.qualifierText || ''}\0${runtime}\0${unit}`;
1374
+ if (this.typeMemo.has(key)) return this.typeMemo.get(key);
1375
+ this.typeMemo.set(key, null); // Alias cycles never prove an owner.
1376
+ let localTypes = null;
1377
+ if (runtime && langTraits(this.index.files.get(file)?.language)?.typeSystem === 'structural') {
1378
+ const local = this.localBinding(file, syntax.node, syntax.name, true);
1379
+ if (local) {
1380
+ const scope = referenceScope(syntax.node,
1381
+ GRAMMAR_LANGUAGES.get(syntax.node.tree.language?.name) || this.index.files.get(file)?.language,
1382
+ this.lexicalMemo(syntax.node), syntax.name, { includeClasses: true });
1383
+ // A local class declaration is the constructor binding,
1384
+ // not a shadow of it. Parameters, assignments and mixed
1385
+ // bindings have no declaration-only scope proof.
1386
+ if (local.binding || !scope?.local || !scope.defRows?.length) return null;
1387
+ const rows = new Set(scope.defRows.map(row => row + 1));
1388
+ localTypes = new Set(classDefsNamed(this.index, syntax.name).entries
1389
+ .filter(entry => entry.def.file === file &&
1390
+ rows.has(entry.def.nameLine || entry.def.startLine)).map(entry => entry.def));
1391
+ if (localTypes.size !== 1 || rows.size !== 1) return null;
1392
+ }
1393
+ const rebound = this.syntaxNodes(file, syntax.node.tree, syntax.name).some(token =>
1394
+ isWrittenBinding(token.node) && !this.localBinding(file, token.node, syntax.name, true));
1395
+ if (rebound) return null;
1396
+ }
1397
+ const parameters = this.syntaxNodes(file, syntax.node.tree, syntax.name).some(token =>
1398
+ token.role.typeParam && contains(referenceTypeParameterScope(token.node), syntax.node));
1399
+ const matches = [];
1400
+ let uncertain = parameters;
1401
+ if (this.ambiguousClassFamily({ name: syntax.name })) uncertain = true;
1402
+ let classes = classDefsNamed(this.index, syntax.name).entries;
1403
+ let aliases = (this.index.symbols.get(syntax.name) || []).filter(d => d.type === 'type');
1404
+ let namedTypes = localTypes;
1405
+ if (localTypes) {
1406
+ classes = classes.filter(entry => localTypes.has(entry.def));
1407
+ aliases = [];
1408
+ }
1409
+ const language = this.index.files.get(file)?.language;
1410
+ if (!parameters && langTraits(language)?.itemScopeResolution) {
1411
+ const written = [syntax.qualifierText, syntax.name].filter(Boolean).join('::');
1412
+ const binding = LANGUAGES[language].module().typePathScopeBinding?.(syntax.node, written);
1413
+ const named = require('./rust-names').resolveRustPathQualifier(this.index, file,
1414
+ syntax.node.startPosition.row + 1, written, binding);
1415
+ // Type lookup follows import aliases; a rename of the original
1416
+ // type deliberately leaves those aliases alone. Reuse the name
1417
+ // resolver's binding here instead of its rename-token verdict.
1418
+ if (named) {
1419
+ namedTypes = new Set(named.defs || []);
1420
+ classes = [...namedTypes].flatMap(def => classDefsNamed(this.index, def.name).entries
1421
+ .filter(entry => entry.def === def));
1422
+ aliases = [...namedTypes].filter(def => def.type === 'type');
1423
+ } else if (binding) return null;
1424
+ }
1425
+ if (!parameters) for (const entry of classes) {
1426
+ if (entry.def.file === file && this.scopeRelation(file,
1427
+ entry.def.nameLine || entry.def.startLine, syntax.node) !== 'yes') {
1428
+ uncertain = true;
1429
+ continue;
1430
+ }
1431
+ const verdict = namedTypes?.has(entry.def) ? { verdict: 'yes' }
1432
+ : this.typeVerdict(file, syntax, entry.def, useFile);
1433
+ if (entry.def.forwardDeclaration && verdict.verdict !== 'no') this.sawForwardDeclaration = true;
1434
+ if (verdict.verdict === 'yes') {
1435
+ const complete = completeForwardDeclaration(this.index, entry.def, useFile);
1436
+ const resolved = complete === entry.def ? entry :
1437
+ classDefsNamed(this.index, complete.name).entries.find(candidate => candidate.def === complete);
1438
+ if (resolved) matches.push(resolved);
1439
+ else uncertain = true;
1440
+ }
1441
+ if (verdict.verdict === 'unknown') uncertain = true;
1442
+ }
1443
+ if (!parameters) for (const alias of aliases) {
1444
+ const verdict = namedTypes?.has(alias) ? { verdict: 'yes' }
1445
+ : this.typeVerdict(file, syntax, alias, useFile);
1446
+ if (verdict.verdict === 'unknown') uncertain = true;
1447
+ if (verdict.verdict !== 'yes') continue;
1448
+ const declaration = this.declarationNode(alias);
1449
+ const target = declaration?.childForFieldName('type') || declaration?.childForFieldName('right');
1450
+ const owner = this.classForType(alias.file, typeSyntax(target), false, useFile);
1451
+ if (owner) matches.push(owner);
1452
+ else uncertain = true;
1453
+ }
1454
+ const keys = new Set(matches.map(entry => entry.key));
1455
+ const result = !uncertain && keys.size === 1
1456
+ ? { name: matches[0].def.name, key: matches[0].key, def: matches[0].def } : null;
1457
+ this.typeMemo.set(key, result);
1458
+ return result;
1459
+ }
1460
+
1461
+ enclosingOwner(file, node, fallback = true) {
1462
+ const implementation = langTraits(this.index.files.get(file)?.language)?.implementationOwnerSyntax;
1463
+ for (let current = node.parent; current; current = current.parent) {
1464
+ if (implementation && current.type === implementation.node) {
1465
+ // A separately declared implementation may name an imported
1466
+ // or aliased type. A project namesake is no owner evidence.
1467
+ const owner = this.classForType(file, typeSyntax(current.childForFieldName(implementation.field)));
1468
+ return owner ? { ...owner, node: current } : null;
1469
+ }
1470
+ if (!TYPE_BODIES.has(current.type)) continue;
1471
+ const nameNode = current.childForFieldName('name');
1472
+ const name = typeSyntax(nameNode)?.name || nameNode?.text;
1473
+ if (!name) {
1474
+ // Anonymous typedef records and class expressions acquire
1475
+ // their indexed name from the declaration that binds them.
1476
+ // Only the exact indexed body span proves their owner.
1477
+ const owners = this.members.filter(member => member.file === file &&
1478
+ member.startLine >= current.startPosition.row + 1 &&
1479
+ member.endLine <= current.endPosition.row + 1)
1480
+ .map(member => ownerRefOf(this.index, member)).filter(owner =>
1481
+ owner?.def?.startLine === current.startPosition.row + 1 &&
1482
+ owner.def.endLine === current.endPosition.row + 1);
1483
+ if (new Set(owners.map(owner => owner.key)).size === 1) return { ...owners[0], node: current };
1484
+ continue;
1485
+ }
1486
+ const matches = classDefsNamed(this.index, name).entries.filter(entry =>
1487
+ entry.def.file === file && entry.def.startLine <= current.startPosition.row + 1 &&
1488
+ entry.def.endLine >= current.endPosition.row + 1);
1489
+ if (new Set(matches.map(entry => entry.key)).size === 1) return {
1490
+ name, key: matches[0].key, def: matches[0].def, node: current,
1491
+ };
1492
+ return null;
1493
+ }
1494
+ if (!fallback) return null;
1495
+ const member = this.index.findEnclosingFunction(file, node.startPosition.row + 1, true);
1496
+ return member?.className ? ownerRefOf(this.index, member) : null;
1497
+ }
1498
+
1499
+ declarationOwner(file, node) {
1500
+ for (let current = node.parent; current; current = current.parent) {
1501
+ if (CALLABLE_SCOPES.has(current.type)) return null;
1502
+ if (current.type === 'object_type' || current.type === 'struct_type' &&
1503
+ current.parent?.type !== 'type_spec') return null;
1504
+ if (TYPE_BODIES.has(current.type)) return this.enclosingOwner(file, node);
1505
+ }
1506
+ return null;
1507
+ }
1508
+
1509
+ privateValueAt(file, node) {
1510
+ let position = node;
1511
+ const visited = new Set();
1512
+ while (position) {
1513
+ const owner = this.enclosingOwner(file, position, false);
1514
+ if (!owner?.key || !owner.node || visited.has(owner.node.startIndex)) break;
1515
+ visited.add(owner.node.startIndex);
1516
+ const declared = this.members.filter(member => ownerRefOf(this.index, member)?.key === owner.key);
1517
+ if (declared.length) return this.judge(declared, 'other-private-owner');
1518
+ position = owner.node;
1519
+ }
1520
+ return { verdict: 'unknown', reason: 'private-member-owner-unresolved' };
1521
+ }
1522
+
1523
+ memberAt(owner) {
1524
+ if (langTraits(this.language)?.typeArgumentValues === 'ordinary') return this.cppMemberAt(owner);
1525
+ if (!owner?.key || !this.owner?.key) return { verdict: 'unknown', reason: 'member-owner-unresolved' };
1526
+ if (this.ambiguousClassFamily(owner)) return { verdict: 'unknown', reason: 'value-owner-alternatives' };
1527
+ if (langTraits(this.language)?.separateStaticStorage && owner.valueStorage &&
1528
+ owner.valueStorage !== this.targetStorage) return { verdict: 'no', reason: 'other-value-storage' };
1529
+ if (this.memberMemo.has(owner.key)) return this.memberMemo.get(owner.key);
1530
+ const declaring = findDeclaringClass(this.index, owner, this.memberCandidates);
1531
+ if (!declaring || declaring.uncertain) return { verdict: 'unknown', reason: 'member-owner-unresolved' };
1532
+ if (declaring.members.length !== 1) return { verdict: 'unknown', reason: 'ambiguous-value-declaration' };
1533
+ const result = this.group.has(declaring.members[0])
1534
+ ? declaring.members[0] === this.def ? { verdict: 'yes' }
1535
+ : { verdict: 'yes', access: this.slotAccess(owner, declaring.members[0]) }
1536
+ : langTraits(this.language)?.typeSystem === 'structural' &&
1537
+ (relationToTargets(this.index, owner, [this.def]) !== 'unrelated' ||
1538
+ relationToTargets(this.index, this.owner, declaring.members) !== 'unrelated')
1539
+ ? { verdict: 'unknown', reason: 'inherited-value-shadow' }
1540
+ : { verdict: 'no', reason: 'other-member-owner' };
1541
+ this.memberMemo.set(owner.key, result);
1542
+ return result;
1543
+ }
1544
+
1545
+ /**
1546
+ * The field a keyword argument names (fix #431), Python: the keyword of
1547
+ * a construction `C(.., name=v)` binds a parameter of the initializer
1548
+ * the call runs. A class the language's field initializers synthesize
1549
+ * (trait `fieldInitializers`: a dataclass, a NamedTuple or TypedDict
1550
+ * subclass) takes its own fields as parameters, so the keyword names
1551
+ * the field C's member lookup finds; an explicit `__init__` takes
1552
+ * parameters of its own. An initializer C inherits, a class decorator
1553
+ * that may replace C, an external base or a `cls(..)` construction
1554
+ * (a subclass may run) leave the binding for review.
1555
+ */
1556
+ keywordFieldAt(file, entry, node) {
1557
+ const other = { verdict: 'no', reason: 'keyword-argument' };
1558
+ const list = node.parent?.parent;
1559
+ const call = list?.type === 'argument_list' ? list.parent : null;
1560
+ const callee = call?.type === 'call' ? call.childForFieldName('function') : null;
1561
+ if (!callee) return other;
1562
+ const traits = langTraits(entry.language);
1563
+ let owner;
1564
+ let receiverClass = false;
1565
+ if (callee.type === 'identifier' && traits.selfParam?.includes(callee.text)) {
1566
+ owner = this.enclosingOwner(file, callee);
1567
+ receiverClass = true;
1568
+ } else {
1569
+ // Only a class of the slot, or one below it, can take the field.
1570
+ const calleeName = callee.type === 'attribute' ? callee.childForFieldName('attribute')?.text : callee.text;
1571
+ if (!this.keywordOwnerNames().has(calleeName) && !this.keywordAliasNames().has(calleeName)) return other;
1572
+ const syntax = callee.type === 'attribute'
1573
+ ? (() => {
1574
+ const attribute = callee.childForFieldName('attribute');
1575
+ const object = callee.childForFieldName('object');
1576
+ return attribute && object ? { name: attribute.text, node: attribute, qualifier: object,
1577
+ qualifierText: object.text } : null;
1578
+ })()
1579
+ : typeSyntax(callee);
1580
+ owner = syntax ? this.classForType(file, syntax, true) : null;
1581
+ // A module value naming a class (`Seg = Segment`, fix #434).
1582
+ if (!owner && callee.type === 'identifier' && this.keywordAliasNames().has(callee.text) &&
1583
+ this.pythonScope(callee, callee.text) === 'module') {
1584
+ const aliased = require('./callers')._moduleValueAliasType(this.index, file, callee.text);
1585
+ const entries = aliased ? classDefsNamed(this.index, aliased.type).entries
1586
+ .filter(candidate => candidate.def.file === aliased.fromFile && !candidate.def.className) : [];
1587
+ if (entries.length !== 1) return { verdict: 'unknown', reason: 'construction-alias' };
1588
+ owner = { name: aliased.type, key: entries[0].key, def: entries[0].def };
1589
+ }
1590
+ }
1591
+ if (!owner?.key || !owner.def) return { verdict: 'unknown', reason: 'construction-owner-unresolved' };
1592
+ const declaring = findDeclaringClass(this.index, owner, this.memberCandidates);
1593
+ if (!declaring) return other;
1594
+ if (declaring.uncertain || declaring.members.length !== 1) {
1595
+ return { verdict: 'unknown', reason: 'member-owner-unresolved' };
1596
+ }
1597
+ const member = declaring.members[0];
1598
+ if (!this.group.has(member)) return { verdict: 'no', reason: 'other-member-owner' };
1599
+ const initializer = this.synthesizedInitializer(owner);
1600
+ if (initializer === 'explicit') return other;
1601
+ if (initializer !== 'own') return { verdict: 'unknown', reason: initializer };
1602
+ // A field a base declares reaches the synthesized parameter through
1603
+ // the base's fields.
1604
+ if (declaring.ref?.key !== owner.key) return { verdict: 'unknown', reason: 'inherited-field' };
1605
+ if (receiverClass) return { verdict: 'unknown', reason: 'receiver-class-call' };
1606
+ return member === this.def ? { verdict: 'yes' }
1607
+ : { verdict: 'yes', access: this.slotAccess(owner, member) };
1608
+ }
1609
+
1610
+ /** Value aliases of the classes keywordOwnerNames lists (fix #434). */
1611
+ keywordAliasNames() {
1612
+ if (this._keywordAliasNames) return this._keywordAliasNames;
1613
+ const classes = [...this.keywordOwnerNames()].flatMap(name => classDefsNamed(this.index, name).entries
1614
+ .map(entry => entry.def));
1615
+ this._keywordAliasNames = classes.length > 0
1616
+ ? require('./constructor-calls').constructionAliasNames(this.index, classes) : new Set();
1617
+ return this._keywordAliasNames;
1618
+ }
1619
+
1620
+ /** Names of the classes owning a member of the pin's slot and of their
1621
+ * descendants (by name): the only constructions whose keywords can name
1622
+ * the field. */
1623
+ keywordOwnerNames() {
1624
+ if (this._keywordOwnerNames) return this._keywordOwnerNames;
1625
+ const names = new Set();
1626
+ const queue = [];
1627
+ for (const member of this.group) {
1628
+ if (member.className && !names.has(member.className)) { names.add(member.className); queue.push(member.className); }
1629
+ }
1630
+ while (queue.length) {
1631
+ for (const child of this.index.extendedByGraph?.get(queue.shift()) || []) {
1632
+ const name = typeof child === 'string' ? child : child?.name;
1633
+ if (name && !names.has(name)) { names.add(name); queue.push(name); }
1634
+ }
1635
+ }
1636
+ this._keywordOwnerNames = names;
1637
+ return names;
1638
+ }
1639
+
1640
+ /**
1641
+ * Who builds the initializer a construction of `owner` runs (fix #431):
1642
+ * 'own' (synthesized from the class's own fields), 'explicit' (a
1643
+ * project `__init__` the class or a base defines, or none at all), or
1644
+ * the review reason ('inherited-initializer', 'class-decorator',
1645
+ * 'external-initializer').
1646
+ */
1647
+ synthesizedInitializer(owner) {
1648
+ const memo = this._initializers ||= new Map();
1649
+ if (memo.has(owner.key)) return memo.get(owner.key);
1650
+ const result = this.synthesizedInitializerUncached(owner);
1651
+ memo.set(owner.key, result);
1652
+ return result;
1653
+ }
1654
+
1655
+ synthesizedInitializerUncached(owner) {
1656
+ const vocabulary = langTraits(this.index.files.get(owner.def.file)?.language)?.fieldInitializers;
1657
+ const decorators = langTraits(this.index.files.get(owner.def.file)?.language)?.callableDecorators || {};
1658
+ if (!vocabulary) return 'external-initializer';
1659
+ const { resolveDecorator } = require('./async-producers');
1660
+ const classInfo = ref => {
1661
+ const fileEntry = this.index.files.get(ref.def.file);
1662
+ const node = this.declarationNode(ref.def);
1663
+ const decorated = node?.parent?.type === 'decorated_definition' ? node.parent : null;
1664
+ let synthesizes = false, replaces = false;
1665
+ for (const decorator of decorated ? decorated.namedChildren.filter(child => child.type === 'decorator') : []) {
1666
+ let expression = decorator.namedChild(0);
1667
+ const called = expression?.type === 'call';
1668
+ const args = called ? expression.childForFieldName('arguments') : null;
1669
+ if (called) expression = expression.childForFieldName('function');
1670
+ const resolved = expression && ['identifier', 'attribute'].includes(expression.type)
1671
+ ? resolveDecorator(expression.text, fileEntry, decorators) : null;
1672
+ const key = resolved?.name;
1673
+ if (key && vocabulary.decorators.includes(key)) {
1674
+ // `@dataclass(init=False)` generates no initializer.
1675
+ const noInit = (args?.namedChildren || []).some(arg => arg.type === 'keyword_argument' &&
1676
+ arg.childForFieldName('name')?.text === 'init' &&
1677
+ arg.childForFieldName('value')?.type === 'false');
1678
+ if (!noInit) synthesizes = true;
1679
+ } else if ((!key || ![...(decorators.transparent || []), ...(decorators.namePreserving || [])]
1680
+ .some(known => known === key || known === `${key}()`)) &&
1681
+ !require('./decorator-binding').classPreservingDecorator(this.index, ref.def, decorator.namedChild(0)?.text || '')) {
1682
+ replaces = true;
1683
+ }
1684
+ }
1685
+ const bases = ref.def.extends ? String(ref.def.extends).split(',').map(base => base.trim()) : [];
1686
+ if (bases.some(base => {
1687
+ const resolved = resolveDecorator(base.replace(/\[.*$/s, ''), fileEntry, {});
1688
+ return resolved && vocabulary.bases.includes(resolved.name);
1689
+ })) synthesizes = true;
1690
+ const explicit = (this.index.symbols.get('__init__') || []).some(definition =>
1691
+ definition.file === ref.def.file && ownerRefOf(this.index, definition)?.key === ref.key);
1692
+ return { synthesizes, replaces, explicit };
1693
+ };
1694
+ // A class's own `__init__` takes its parameters, whatever its
1695
+ // decorators (a dataclass keeps a defined initializer).
1696
+ const own = classInfo(owner);
1697
+ if (own.explicit) return 'explicit';
1698
+ if (own.replaces) return 'class-decorator';
1699
+ if (own.synthesizes) return 'own';
1700
+ // The initializer comes from a base: the first class of the chain
1701
+ // that defines or synthesizes one. `object` takes no keywords.
1702
+ const queue = parentRefsOf(this.index, owner).map(parent => ({ parent, from: owner.def.file }));
1703
+ const seen = new Set([owner.key]);
1704
+ while (queue.length) {
1705
+ const { parent, from } = queue.shift();
1706
+ if (parent.external || !parent.key || !parent.def) {
1707
+ const resolved = resolveDecorator(String(parent.name || ''), this.index.files.get(from),
1708
+ { builtins: ['object'] });
1709
+ if (resolved?.name === 'builtins.object') continue;
1710
+ if (resolved && vocabulary.bases.includes(resolved.name)) return 'inherited-initializer';
1711
+ return 'external-initializer';
1712
+ }
1713
+ if (seen.has(parent.key)) continue;
1714
+ seen.add(parent.key);
1715
+ const info = classInfo(parent);
1716
+ if (info.explicit) return 'explicit';
1717
+ if (info.replaces) return 'class-decorator';
1718
+ if (info.synthesizes) return 'inherited-initializer';
1719
+ queue.push(...parentRefsOf(this.index, parent).map(next => ({ parent: next, from: parent.def.file })));
1720
+ }
1721
+ return 'explicit';
1722
+ }
1723
+
1724
+ /**
1725
+ * What an access whose lookup finds another member of the pin's slot
1726
+ * reaches (fix #431). A rename edits the whole slot; impact tiers it as
1727
+ * the caller contract does: a receiver of an ancestor or interface class
1728
+ * may hold an instance of the target's class (possible dispatch), an
1729
+ * override below the target and an unrelated class's own member are
1730
+ * other members (excluded), unless a project class descends from both.
1731
+ */
1732
+ slotAccess(owner, member) {
1733
+ if (!owner?.key || !this.owner?.key) return { verdict: 'unknown', reason: 'member-owner-unresolved' };
1734
+ if (owner.key === this.owner.key) return { verdict: 'unknown', reason: 'value-declaration-identity' };
1735
+ const below = descendsFromTargets(this.index, owner, [this.def]);
1736
+ if (below === 'yes') {
1737
+ // A Python instance attribute shadows the class attribute only
1738
+ // once assigned.
1739
+ return member.instanceAttribute || this.def.instanceAttribute
1740
+ ? { verdict: 'unknown', reason: 'instance-attribute-shadow' }
1741
+ : { verdict: 'no', reason: 'slot-override' };
1742
+ }
1743
+ const relation = this.supertypeOfOwner(owner) || relationToTargets(this.index, owner, [this.def]);
1744
+ if (relation === 'ancestor') return { verdict: 'unknown', reason: 'possible-dispatch' };
1745
+ if (relation === 'maybe' || below === 'maybe') return { verdict: 'unknown', reason: 'member-owner-unresolved' };
1746
+ return shareDescendant(this.index, owner, [this.def])
1747
+ ? { verdict: 'unknown', reason: 'possible-dispatch' }
1748
+ : { verdict: 'no', reason: 'other-slot-member' };
1749
+ }
1750
+
1751
+ /** 'ancestor' when `owner` is a supertype of the pin's class through its
1752
+ * base and implements clauses, 'maybe' when an unresolved supertype of
1753
+ * that name is on the way, else null. */
1754
+ supertypeOfOwner(owner) {
1755
+ const queue = [...supertypeRefsOf(this.index, this.owner, { implements: true })];
1756
+ const seen = new Set([this.owner.key]);
1757
+ let maybe = false;
1758
+ while (queue.length) {
1759
+ const parent = queue.shift();
1760
+ if (parent.key && parent.key === owner.key) return 'ancestor';
1761
+ if (!parent.key || parent.external) {
1762
+ if (parent.name === owner.name) maybe = true;
1763
+ continue;
1764
+ }
1765
+ if (seen.has(parent.key)) continue;
1766
+ seen.add(parent.key);
1767
+ queue.push(...supertypeRefsOf(this.index, parent, { implements: true }));
1768
+ }
1769
+ return maybe ? 'maybe' : null;
1770
+ }
1771
+
1772
+ /** The access verdict of another slot member's declaration token. */
1773
+ slotDeclarationAccess(file, node, otherOwner) {
1774
+ if (!this.owner?.key || !otherOwner?.key || otherOwner.key === this.owner.key) return null;
1775
+ return { verdict: 'no', reason: 'slot-member-declaration' };
1776
+ }
1777
+
1778
+ receiverOwner(file, receiver) {
1779
+ if (!receiver) return null;
1780
+ const traits = langTraits(this.index.files.get(file)?.language);
1781
+ if (receiver.type === 'base' || receiver.type === 'super') {
1782
+ if (traits.separateStaticStorage) return null;
1783
+ const owner = this.enclosingOwner(file, receiver);
1784
+ const bases = owner && parentRefsOf(this.index, owner).filter(ref =>
1785
+ ref.key && ['class', 'struct', 'record'].includes(ref.def?.type));
1786
+ return bases?.length === 1 ? bases[0] : null;
1787
+ }
1788
+ if (receiver.type === 'this' || traits.selfParam?.includes(receiver.text)) {
1789
+ if (traits.assignmentDeclaresLocal && traits.ownClassMethod) {
1790
+ const local = this.localBinding(file, receiver, receiver.text, true);
1791
+ const scope = local?.binding?.scope;
1792
+ const method = scope && (this.index.files.get(file)?.symbols || []).find(def =>
1793
+ def.className && def.name === scope.childForFieldName('name')?.text &&
1794
+ def.startLine <= scope.startPosition.row + 1 && def.endLine === scope.endPosition.row + 1);
1795
+ if (!method || method.memberType === 'static' || !traits.ownClassMethod(method,
1796
+ method.type === 'classmethod' ? 'class' : 'instance', receiver.text)) return null;
1797
+ }
1798
+ if (traits.typeSystem === 'structural' && receiver.type === 'this') {
1799
+ for (let scope = receiver.parent; scope && scope.type !== 'class_body'; scope = scope.parent) {
1800
+ if (FUNCTION_NODES.has(scope.type) ||
1801
+ scope.type === 'method_definition' && scope.parent?.type !== 'class_body') return null;
1802
+ }
1803
+ }
1804
+ const owner = this.enclosingOwner(file, receiver);
1805
+ if (traits.separateStaticStorage) {
1806
+ const storage = this.thisStorage(receiver);
1807
+ return owner && storage ? { ...owner, valueStorage: storage } : null;
1808
+ }
1809
+ return owner;
1810
+ }
1811
+ // An aliased, default or namespace-chain import (fix #434).
1812
+ if (traits.nameReferenceFamily === 'js' && ['identifier', 'member_expression'].includes(receiver.type)) {
1813
+ const imported = this.jsImportedOwner(file, receiver);
1814
+ if (imported) return traits.separateStaticStorage ? { ...imported, valueStorage: 'class' } : imported;
1815
+ }
1816
+ if (!['identifier', 'type_identifier', 'package_identifier'].includes(receiver.type)) return null;
1817
+ const local = this.localBinding(file, receiver, receiver.text, true);
1818
+ if (local?.binding) {
1819
+ const type = declarationType(local.binding.node);
1820
+ if (!type?.syntax) return null;
1821
+ if (type.origin === 'constructor' && this.syntaxNodes(file, receiver.tree, receiver.text)
1822
+ .some(token => isWrittenBinding(token.node) && sameNode(
1823
+ this.localBinding(file, token.node, receiver.text, true)?.binding?.node,
1824
+ local.binding.node))) return null;
1825
+ const owner = this.classForType(file, type.syntax, type.origin === 'constructor');
1826
+ return owner && traits.separateStaticStorage ? { ...owner, valueStorage: 'instance' } : owner;
1827
+ }
1828
+ if (local) return null;
1829
+ if (traits.bareCallReachesMethods) {
1830
+ const owner = this.enclosingOwner(file, receiver);
1831
+ const members = (this.index.symbols.get(receiver.text) || []).filter(d => d.className);
1832
+ if (owner && findDeclaringClass(this.index, owner, members)) return null;
1833
+ }
1834
+ const owner = this.classForType(file, { name: receiver.text, node: receiver }, true);
1835
+ return owner && traits.separateStaticStorage ? { ...owner, valueStorage: 'class' } : owner;
1836
+ }
1837
+
1838
+ ambiguousClassFamily(owner) {
1839
+ if (langTraits(this.language)?.typeArgumentValues !== 'ordinary' || !owner?.name) return false;
1840
+ const info = classDefsNamed(this.index, owner.name);
1841
+ // A forward declaration of the class is no alternative of it (fix #427).
1842
+ return info.unique && info.entries.filter(entry => !entry.def.forwardDeclaration).length > 1;
1843
+ }
1844
+
1845
+ classValueMembers() {
1846
+ if (this._classValueMembers) return this._classValueMembers;
1847
+ const members = [], originals = new Map(), unresolved = [];
1848
+ for (const definition of this.index.symbols.get(this.name) || []) {
1849
+ if (TYPE_KINDS.has(definition.type) ||
1850
+ langTraits(this.index.files.get(definition.file)?.language)?.typeArgumentValues !== 'ordinary') continue;
1851
+ const info = isValueDefinition(definition) && this.enumInfo(definition);
1852
+ if (definition.className) members.push(definition);
1853
+ if (info?.unscoped) {
1854
+ if (info.scope?.type !== 'field_declaration_list') continue;
1855
+ let parent = this.enclosingOwner(definition.file, info.scope);
1856
+ if (!parent) parent = lexicalClassRefOf(this.index, ownerRefOf(this.index, definition)?.def);
1857
+ const body = info.scope.parent;
1858
+ if (!parent?.def || parent.def.startLine > body.startPosition.row + 1 ||
1859
+ parent.def.endLine < body.endPosition.row + 1) {
1860
+ unresolved.push(definition);
1861
+ continue;
1862
+ }
1863
+ const member = { ...definition, className: parent.name, namespace: parent.def.namespace };
1864
+ members.push(member);
1865
+ originals.set(member, definition);
1866
+ }
1867
+ }
1868
+ this._classValueMembers = { members, originals, unresolved };
1869
+ return this._classValueMembers;
1870
+ }
1871
+
1872
+ classBindingsVerdict(members, originals) {
1873
+ const definitions = members.map(member => originals.get(member) || member);
1874
+ if (!this.owner && !definitions.some(definition => this.group.has(definition)) &&
1875
+ !this.declarationNode(this.def)) return { verdict: 'unknown', reason: 'member-declaration-identity' };
1876
+ return this.judge(definitions, 'other-member-owner');
1877
+ }
1878
+
1879
+ /** Whether a member of another path through unnamed structs or unions
1880
+ * of the pin's class is declared at this token (fix #430). */
1881
+ otherPathMemberAt(file, node) {
1882
+ const line = node.startPosition.row + 1;
1883
+ const paths = JSON.stringify(this.def.memberPaths || null);
1884
+ return this.members.some(member => member !== this.def && member.file === file &&
1885
+ (member.nameLine || member.startLine) === line && member.className === this.def.className &&
1886
+ JSON.stringify(member.memberPaths || null) !== paths);
1887
+ }
1888
+
1889
+ /**
1890
+ * The member a `receiver.name` access names when a member of an unnamed
1891
+ * struct or union has the name (fix #430): such a member is reached
1892
+ * through the declarators of its type (`e->data.stream_start.encoding`),
1893
+ * so the receiver must end with one of its member paths, and the object
1894
+ * that path starts from must be of the enclosing class. Null when no
1895
+ * member of the name has a path (the ordinary member lookup decides).
1896
+ */
1897
+ pathMemberAt(file, receiver) {
1898
+ const ordinary = langTraits(this.language)?.typeArgumentValues === 'ordinary';
1899
+ const bindings = ordinary ? this.classValueMembers() : null;
1900
+ const candidates = ordinary ? bindings.members : this.memberCandidates;
1901
+ if (!candidates.some(member => member.memberPaths)) return null;
1902
+ const names = [], bases = [receiver];
1903
+ for (let base = receiver; base?.type === 'field_expression' && names.length < 16;) {
1904
+ names.unshift(base.childForFieldName('field')?.text);
1905
+ base = base.childForFieldName('argument');
1906
+ bases.unshift(base);
1907
+ }
1908
+ // A bare name starting the chain inside a member function is an
1909
+ // implicit `this->name` unless a local binds it.
1910
+ const root = bases[0];
1911
+ const implicitOwner = root?.type === 'identifier' && ordinary &&
1912
+ !this.localBinding(file, root, root.text, true) ? this.enclosingOwner(file, root) : null;
1913
+ if (implicitOwner) {
1914
+ names.unshift(root.text);
1915
+ bases.unshift(null);
1916
+ }
1917
+ const endsWith = path => path.length <= names.length &&
1918
+ path.every((part, i) => names[names.length - path.length + i] === part);
1919
+ const byDepth = new Map();
1920
+ for (const member of candidates) {
1921
+ for (const path of member.memberPaths || [[]]) {
1922
+ if (!endsWith(path)) continue;
1923
+ if (!byDepth.has(path.length)) byDepth.set(path.length, new Set());
1924
+ byDepth.get(path.length).add(member);
1925
+ }
1926
+ }
1927
+ const reached = new Set();
1928
+ let unresolved = false;
1929
+ for (const [depth, members] of byDepth) {
1930
+ const base = bases[names.length - depth];
1931
+ const owner = base === null ? implicitOwner : this.receiverOwner(file, base);
1932
+ if (!owner?.key || this.ambiguousClassFamily(owner)) { unresolved = true; continue; }
1933
+ const declaring = findDeclaringClass(this.index, owner, [...members]);
1934
+ if (!declaring || declaring.uncertain) { unresolved = true; continue; }
1935
+ for (const member of declaring.members) reached.add(member);
1936
+ }
1937
+ if (unresolved) return { verdict: 'unknown', reason: 'member-owner-unresolved' };
1938
+ if (reached.size === 0) return { verdict: 'no', reason: 'other-member-owner' };
1939
+ if (ordinary) return this.classBindingsVerdict([...reached], bindings.originals);
1940
+ if (reached.size !== 1) return { verdict: 'unknown', reason: 'ambiguous-value-declaration' };
1941
+ return this.group.has([...reached][0]) ? { verdict: 'yes' } : { verdict: 'no', reason: 'other-member-owner' };
1942
+ }
1943
+
1944
+ cppMemberAt(owner) {
1945
+ if (!owner?.key || this.ambiguousClassFamily(owner)) return { verdict: 'unknown', reason: 'member-owner-unresolved' };
1946
+ const bindings = this.classValueMembers();
1947
+ const declaring = findDeclaringClass(this.index, owner, bindings.members);
1948
+ return !declaring || declaring.uncertain ? { verdict: 'unknown', reason: 'member-owner-unresolved' }
1949
+ : this.classBindingsVerdict(declaring.members, bindings.originals);
1950
+ }
1951
+
1952
+ qualifiedValueAt(file, qualifier, role) {
1953
+ const scopes = [];
1954
+ let global = false;
1955
+ for (let current = templateIdOf(this.siteNode); current?.parent?.type === 'qualified_identifier' &&
1956
+ sameNode(current.parent.childForFieldName('name'), current); current = current.parent) {
1957
+ const scope = current.parent.childForFieldName('scope');
1958
+ if (scope) scopes.unshift(scope);
1959
+ else if (current.parent.children.some(child => child.type === '::')) global = true;
1960
+ }
1961
+ let syntax = typeSyntax(qualifier), text = qualifier.text;
1962
+ if (scopes.length && sameNode(scopes[scopes.length - 1], qualifier)) {
1963
+ text = `${global ? '::' : ''}${scopes.map(scope => scope.text).join('::')}`;
1964
+ if (syntax && (global || scopes.length > 1)) syntax = { ...syntax,
1965
+ qualifier: syntax.qualifier || scopes[scopes.length - 2],
1966
+ qualifierText: `${global ? '::' : ''}${[...scopes.slice(0, -1).map(scope => scope.text),
1967
+ syntax.qualifierText].filter(Boolean).join('::')}` };
1968
+ }
1969
+ const owner = this.classForType(file, syntax);
1970
+ return owner ? this.cppMemberAt(owner)
1971
+ : this.resolveQualified(file, this.siteNode.startPosition.row + 1, qualifier, text, role);
1972
+ }
1973
+
1974
+ classValueAt(file, node) {
1975
+ let owner = this.enclosingOwner(file, node);
1976
+ if (!owner) {
1977
+ for (let scope = node.parent; scope; scope = scope.parent) {
1978
+ if (TYPE_BODIES.has(scope.type)) return { verdict: 'unknown', reason: 'member-owner-unresolved' };
1979
+ }
1980
+ return null;
1981
+ }
1982
+ const bindings = this.classValueMembers();
1983
+ if (bindings.unresolved.length) {
1984
+ const visible = require('./cpp-scope').includeClosure(this.index, file);
1985
+ if (bindings.unresolved.some(definition => definition.file === file || visible.has(definition.file))) {
1986
+ return { verdict: 'unknown', reason: 'enum-member-owner-unresolved' };
1987
+ }
1988
+ }
1989
+ const lexical = new Set();
1990
+ while (owner) {
1991
+ if (!owner.key || lexical.has(owner.key) || this.ambiguousClassFamily(owner)) {
1992
+ return { verdict: 'unknown', reason: 'value-owner-alternatives' };
1993
+ }
1994
+ lexical.add(owner.key);
1995
+ const declaring = findDeclaringClass(this.index, owner, bindings.members);
1996
+ if (declaring?.uncertain) return { verdict: 'unknown', reason: 'member-owner-unresolved' };
1997
+ if (declaring?.members.length) return this.classBindingsVerdict(declaring.members, bindings.originals);
1998
+ const queue = [owner], visited = new Set();
1999
+ while (queue.length) {
2000
+ const current = queue.pop();
2001
+ if (visited.has(current.key)) continue;
2002
+ visited.add(current.key);
2003
+ for (const parent of parentRefsOf(this.index, current)) {
2004
+ if (!parent.key || parent.external ||
2005
+ langTraits(this.index.files.get(parent.def?.file)?.language)?.typeArgumentValues !== 'ordinary') {
2006
+ return { verdict: 'unknown', reason: 'open-value-owner' };
2007
+ }
2008
+ queue.push(parent);
2009
+ }
2010
+ }
2011
+ // An owner known by key alone (no definition to read its
2012
+ // enclosing scope from) leaves the lookup undecided (fix #430).
2013
+ if (!owner.node && !owner.def) return { verdict: 'unknown', reason: 'member-owner-unresolved' };
2014
+ const body = owner.node || this.declarationNode(owner.def);
2015
+ const sourceFile = owner.def?.file || file;
2016
+ let outer = body && TYPE_BODIES.has(body.type) ? this.enclosingOwner(sourceFile, body, false) : null;
2017
+ if (!outer && body) {
2018
+ const qualifier = typeSyntax(body.childForFieldName('name'))?.qualifier;
2019
+ if (qualifier) outer = this.classForType(sourceFile, typeSyntax(qualifier));
2020
+ }
2021
+ if (!outer && owner.def?.enclosingType) return { verdict: 'unknown', reason: 'member-owner-unresolved' };
2022
+ owner = outer;
2023
+ }
2024
+ return null;
2025
+ }
2026
+
2027
+ /**
2028
+ * A body a namespace-scope macro invocation wraps (`TEST_F(Fixture,
2029
+ * Name) { .. }`) may be expanded as a member function of a class an
2030
+ * argument names (a fixture subclass), as the caller side reads it
2031
+ * (fix #396): a bare name the class chain of such an argument declares
2032
+ * may be that member (fix #431). Visible, never confirmed or excluded.
2033
+ */
2034
+ macroScopeValueAt(file, node) {
2035
+ if (!this.owner) return null;
2036
+ let body = null;
2037
+ for (let current = node.parent; current; current = current.parent) {
2038
+ if (current.type === 'function_definition') { body = current; break; }
2039
+ if (TYPE_BODIES.has(current.type) || current.type === 'lambda_expression') return null;
2040
+ }
2041
+ if (!body || body.parent && !['translation_unit', 'declaration_list'].includes(body.parent.type) &&
2042
+ !String(body.parent.type).startsWith('preproc_')) return null;
2043
+ const enclosing = this.index.findEnclosingFunction(file, node.startPosition.row + 1, true);
2044
+ const args = enclosing?.generatedByMacro?.args;
2045
+ if (!Array.isArray(args) || enclosing.className || enclosing.startLine !== body.startPosition.row + 1) return null;
2046
+ const declarator = body.childForFieldName('declarator');
2047
+ if (!declarator) return null;
2048
+ const names = new Set(args);
2049
+ const tokens = [];
2050
+ const visit = current => {
2051
+ if (['identifier', 'type_identifier'].includes(current.type) && names.has(current.text)) tokens.push(current);
2052
+ for (const child of current.namedChildren || []) visit(child);
2053
+ };
2054
+ visit(declarator);
2055
+ for (const token of tokens) {
2056
+ const owner = this.classForType(file, typeSyntax(token));
2057
+ if (!owner?.key) continue;
2058
+ const declaring = findDeclaringClass(this.index, owner, this.memberCandidates);
2059
+ if (declaring?.members.some(member => this.group.has(member))) {
2060
+ return { verdict: 'unknown', reason: 'macro-generated-scope' };
2061
+ }
2062
+ }
2063
+ return null;
2064
+ }
2065
+
2066
+ localUsingAt(file, node) {
2067
+ if (!this._usingMemo) this._usingMemo = new Map();
2068
+ const key = `${file}\0${node.tree.rootNode.id}`;
2069
+ if (!this._usingMemo.has(key)) this._usingMemo.set(key, node.tree.rootNode.descendantsOfType('using_declaration'));
2070
+ const visible = this._usingMemo.get(key).filter(declaration => declaration.startIndex < node.startIndex)
2071
+ .map(declaration => ({ declaration, scope: referenceBindingScope(declaration,
2072
+ this.index.files.get(file)?.language, { role: 'import' }) }))
2073
+ .filter(({ scope }) => scope && !ROOTS.has(scope.type) &&
2074
+ scope.type !== 'field_declaration_list' && contains(scope, node))
2075
+ .sort((a, b) => (a.scope.endIndex - a.scope.startIndex) -
2076
+ (b.scope.endIndex - b.scope.startIndex) || b.declaration.startIndex - a.declaration.startIndex);
2077
+ for (const { declaration } of visible) {
2078
+ const syntax = typeSyntax(declaration.namedChild(0));
2079
+ if (declaration.children.some(child => child.type === 'enum')) {
2080
+ const owner = this.classForType(file, syntax);
2081
+ if (!owner || owner.def.type !== 'enum') return { verdict: 'unknown', reason: 'using-enum-binding' };
2082
+ const declared = (this.index.symbols.get(this.name) || []).filter(definition =>
2083
+ isValueDefinition(definition) && ownerRefOf(this.index, definition)?.key === owner.key);
2084
+ if (declared.length) return this.judge(declared);
2085
+ } else if (syntax?.name === this.name) {
2086
+ if (this.ordinaryArgumentPosition(file, declaration.namedChild(0)) !== 'value') {
2087
+ return { verdict: 'unknown', reason: 'using-declaration-binding' };
2088
+ }
2089
+ return syntax.qualifier ? this.qualifiedValueAt(file, syntax.qualifier, { role: 'qualified' })
2090
+ : { verdict: 'unknown', reason: 'using-declaration-binding' };
2091
+ }
2092
+ }
2093
+ return null;
2094
+ }
2095
+
2096
+ globalAt(file, node, contextualValue = false) {
2097
+ const traits = langTraits(this.index.files.get(file)?.language);
2098
+ const scope = file === this.def.file && this.scopeRelation(file,
2099
+ this.def.nameLine || this.def.startLine, node);
2100
+ if (scope && scope !== 'yes') return { verdict: scope === 'no' ? 'no' : 'unknown',
2101
+ reason: 'script-binding-scope' };
2102
+ const local = this.localBinding(file, node, this.name, false, contextualValue);
2103
+ if (local) return local.pin ? { verdict: 'yes' }
2104
+ : local.binding ? { verdict: 'no', reason: 'local-binding' } : local;
2105
+ if (traits.typeArgumentValues === 'ordinary') {
2106
+ const imported = this.localUsingAt(file, node);
2107
+ if (imported) return imported;
2108
+ }
2109
+ if (traits.textualIncludes) {
2110
+ const enumScope = enclosingEnum(node);
2111
+ if (enumScope) {
2112
+ const earlier = (this.index.symbols.get(this.name) || []).filter(definition => {
2113
+ if (definition.file !== file || !isValueDefinition(definition)) return false;
2114
+ const info = this.enumInfo(definition);
2115
+ return info && sameNode(info.enumNode, enumScope) && info.node.startIndex < node.startIndex;
2116
+ });
2117
+ return earlier.length ? this.judge(earlier)
2118
+ : { verdict: 'unknown', reason: 'enum-initializer-binding' };
2119
+ }
2120
+ const locals = (this.index.symbols.get(this.name) || []).filter(definition =>
2121
+ isValueDefinition(definition) && definition.file === file)
2122
+ .map(definition => ({ definition, info: this.enumInfo(definition) }))
2123
+ .filter(choice => choice.info?.unscoped && choice.info.scope &&
2124
+ !ROOTS.has(choice.info.scope.type) && choice.info.scope.type !== 'field_declaration_list' &&
2125
+ contains(choice.info.scope, node) && choice.info.node.startIndex < node.startIndex);
2126
+ if (locals.length) {
2127
+ const width = choice => choice.info.scope.endIndex - choice.info.scope.startIndex;
2128
+ const closest = Math.min(...locals.map(width));
2129
+ return this.judge(locals.filter(choice => width(choice) === closest).map(choice => choice.definition));
2130
+ }
2131
+ }
2132
+ if (traits.typeArgumentValues === 'ordinary') {
2133
+ return this.classValueAt(file, node) || this.macroScopeValueAt(file, node) ||
2134
+ this.resolveName(file, node.startPosition.row + 1);
2135
+ }
2136
+ if (traits.bareCallReachesMethods) {
2137
+ const owner = this.enclosingOwner(file, node);
2138
+ const members = (this.index.symbols.get(this.name) || []).filter(d => d.className);
2139
+ if (owner && findDeclaringClass(this.index, owner, members)) {
2140
+ return { verdict: 'no', reason: 'member-binding' };
2141
+ }
2142
+ }
2143
+ const ownEnum = this.enumInfo(this.def)?.unscoped;
2144
+ const localEnums = traits.textualIncludes ? (this.index.symbols.get(this.name) || []).filter(definition =>
2145
+ isValueDefinition(definition) && definition.file === file)
2146
+ .map(definition => ({ definition, info: this.enumInfo(definition) }))
2147
+ .filter(choice => choice.info && !ROOTS.has(choice.info.scope?.type) &&
2148
+ contains(choice.info.scope, node) && choice.info.node.startIndex < node.startIndex) : [];
2149
+ if (ownEnum || localEnums.length) {
2150
+ const choices = ownEnum ? this.all.filter(definition => definition.file === file)
2151
+ .map(definition => ({ definition, info: this.enumInfo(definition) }))
2152
+ .filter(choice => choice.info && contains(choice.info.scope, node) &&
2153
+ choice.info.node.startIndex < node.startIndex) : localEnums;
2154
+ if (!choices.length) return { verdict: 'unknown', reason: 'enum-value-scope' };
2155
+ const width = choice => choice.info.scope
2156
+ ? choice.info.scope.endIndex - choice.info.scope.startIndex : Infinity;
2157
+ const closest = Math.min(...choices.map(width));
2158
+ return this.judge(choices.filter(choice => width(choice) === closest).map(choice => choice.definition));
2159
+ }
2160
+ if (traits?.moduleValueLookup || traits?.textualIncludes) {
2161
+ return this.resolveName(file, node.startPosition.row + 1);
2162
+ }
2163
+ const visible = this.visibleInFile(file, node.startPosition.row + 1);
2164
+ if (visible.length) return this.judge(visible);
2165
+ return this.resolveName(file, node.startPosition.row + 1);
2166
+ }
2167
+
2168
+ staticImportAt(file, node, sourceToken = false) {
2169
+ const traits = langTraits(this.index.files.get(file)?.language);
2170
+ const imports = traits.staticValueImportSyntax?.(node.tree) || [];
2171
+ const verdicts = [];
2172
+ for (const imported of imports) {
2173
+ if (imported.member !== '*' && imported.member !== this.name) continue;
2174
+ if (sourceToken && imported.token && !sameNode(imported.token, node)) continue;
2175
+ const owner = this.classForType(file, typeSyntax(imported.type));
2176
+ if (!owner) { verdicts.push({ verdict: 'unknown', reason: 'static-value-import' }); continue; }
2177
+ const declaring = findDeclaringClass(this.index, owner, this.members);
2178
+ if (!declaring) continue;
2179
+ if (!declaring.members.every(member => member.type === 'constant' ||
2180
+ ['enum', 'interface'].includes(declaring.ref?.def?.type) ||
2181
+ member.modifiers?.some(modifier => modifier === 'static' || modifier === 'const'))) continue;
2182
+ verdicts.push(this.memberAt(owner));
2183
+ }
2184
+ return verdicts.length ? this.combine(verdicts) : null;
2185
+ }
2186
+
2187
+ enumCaseAt(file, node) {
2188
+ if (!langTraits(this.index.files.get(file)?.language)?.implicitEnumCaseLabels) return null;
2189
+ let label = node.parent;
2190
+ while (label && label.type !== 'switch_label' && label.type !== 'block') label = label.parent;
2191
+ if (label?.type !== 'switch_label') return null;
2192
+ let statement = label.parent;
2193
+ while (statement && !['switch_expression', 'switch_statement'].includes(statement.type)) statement = statement.parent;
2194
+ let selector = statement?.childForFieldName('condition') || statement?.childForFieldName('value');
2195
+ while (selector?.type === 'parenthesized_expression') selector = selector.namedChild(0);
2196
+ const owner = selector && this.receiverOwner(file, selector);
2197
+ return owner?.def?.type === 'enum' ? this.memberAt(owner)
2198
+ : { verdict: 'unknown', reason: 'enum-selector-type' };
2199
+ }
2200
+
2201
+ ordinaryArgumentPosition(file, argument) {
2202
+ const syntax = typeSyntax(argument);
2203
+ if (!syntax) return 'unknown';
2204
+ for (const definition of (this.index.symbols.get(this.name) || []).filter(d => TYPE_KINDS.has(d.type) &&
2205
+ langTraits(this.index.files.get(d.file)?.language)?.nameReferenceFamily === 'c')) {
2206
+ if (this.typeVerdict(file, syntax, definition).verdict !== 'no') return 'unknown';
2207
+ }
2208
+ return 'value';
2209
+ }
2210
+
2211
+ initializerArgumentPosition(file, node, argument, role) {
2212
+ const parameter = argument.parent;
2213
+ if (parameter?.type !== 'parameter_declaration' || parameter.childForFieldName('declarator') ||
2214
+ !sameNode(parameter.childForFieldName('type'), argument)) return null;
2215
+ const fn = parameter.parent?.parent;
2216
+ if (parameter.parent?.type !== 'parameter_list' || fn?.type !== 'function_declarator') return null;
2217
+ let name = fn.childForFieldName('declarator');
2218
+ while (name?.type === 'parenthesized_declarator' && name.namedChildCount === 1) name = name.namedChild(0);
2219
+ if (!['identifier', 'qualified_identifier'].includes(name?.type)) return null;
2220
+ let declarator = fn;
2221
+ while (['pointer_declarator', 'reference_declarator', 'parenthesized_declarator',
2222
+ 'attributed_declarator'].includes(declarator.parent?.type)) declarator = declarator.parent;
2223
+ const declaration = declarator.parent;
2224
+ if (declaration?.type !== 'declaration') return null;
2225
+ const head = declaration.childForFieldName('type');
2226
+ if (head?.type === 'primitive_type' && head.text === 'void' && sameNode(declarator, fn)) return null;
2227
+ const syntax = typeSyntax(argument);
2228
+ if (!syntax || !sameNode(syntax.node, node)) return null;
2229
+ const binding = syntax.qualifier
2230
+ ? this.qualifiedValueAt(file, syntax.qualifier, role) : this.globalAt(file, node, true);
2231
+ if (binding.verdict === 'no') return 'value';
2232
+ return binding.verdict === 'yes' && this.ordinaryArgumentPosition(file, argument) === 'value'
2233
+ ? 'value' : 'initializer-unknown';
2234
+ }
2235
+
2236
+ typePosition(file, node, role) {
2237
+ const traits = langTraits(this.index.files.get(file)?.language);
2238
+ let argument = node;
2239
+ while (['qualified_identifier', 'scoped_type_identifier', 'type_descriptor'].includes(argument.parent?.type)) {
2240
+ const parent = argument.parent;
2241
+ if (parent.type === 'type_descriptor' && parent.namedChildCount !== 1) break;
2242
+ argument = parent;
2243
+ }
2244
+ const list = argument.parent;
2245
+ if (traits.typeArgumentValues === 'ordinary') {
2246
+ const initializer = this.initializerArgumentPosition(file, node, argument, role);
2247
+ if (initializer) return initializer;
2248
+ }
2249
+ if (traits.valuePatternConstants && this.def.type === 'variant' &&
2250
+ ['struct_expression', 'struct_pattern'].includes(list?.type) &&
2251
+ ['name', 'type'].some(field => {
2252
+ const part = list.childForFieldName(field);
2253
+ return part && contains(part, argument);
2254
+ })) return 'value';
2255
+ if (traits.typeArgumentValues === 'ordinary' &&
2256
+ ['sizeof_expression', 'alignof_expression', 'typeid_expression'].includes(list?.type)) {
2257
+ return this.ordinaryArgumentPosition(file, argument);
2258
+ }
2259
+ if (traits.typeArgumentValues && ['template_argument_list', 'type_arguments'].includes(list?.type)) {
2260
+ if (this.syntaxNodes(file, node.tree, this.name).some(token => token.role.typeParam &&
2261
+ contains(referenceTypeParameterScope(token.node), node))) return 'type';
2262
+ if (traits.typeArgumentValues === 'ordinary') {
2263
+ // A class/typedef hidden in the ordinary lookup can make
2264
+ // this grammar's type-like template argument a type.
2265
+ // Unsettled collisions remain review items.
2266
+ return this.ordinaryArgumentPosition(file, argument);
2267
+ }
2268
+ const head = list.parent;
2269
+ const syntax = typeSyntax(head?.childForFieldName('type') || head?.childForFieldName('function'));
2270
+ if (!syntax) return 'unknown';
2271
+ let definition = this.classForType(file, syntax)?.def;
2272
+ if (!definition && head.type === 'generic_function') {
2273
+ const functions = (this.index.symbols.get(syntax.name) || []).filter(d => d.type === 'function' && !d.className);
2274
+ const matches = [];
2275
+ let uncertain = false;
2276
+ for (const candidate of functions) {
2277
+ const resolver = new TypeReferenceResolver(this.index, candidate, syntax.name);
2278
+ resolver.all = functions;
2279
+ resolver.group = new Set([candidate]);
2280
+ resolver.declarationKinds = new Set(['function']);
2281
+ resolver.siteNode = syntax.node;
2282
+ resolver.siteFile = file;
2283
+ const verdict = syntax.qualifierText
2284
+ ? resolver.resolveQualified(file, node.startPosition.row + 1,
2285
+ syntax.qualifier, syntax.qualifierText, { role: 'value' })
2286
+ : resolver.resolveName(file, node.startPosition.row + 1, syntax.name);
2287
+ if (verdict.verdict === 'yes') matches.push(candidate);
2288
+ if (verdict.verdict === 'unknown') uncertain = true;
2289
+ }
2290
+ if (!uncertain && matches.length === 1) definition = matches[0];
2291
+ }
2292
+ if (!definition) return 'unknown';
2293
+ const parameters = this.declarationNode(definition)?.childForFieldName('type_parameters')?.namedChildren
2294
+ .filter(parameter => parameter.type !== 'lifetime_parameter');
2295
+ const args = list.namedChildren.filter(arg => arg.type !== 'lifetime');
2296
+ const at = args.findIndex(arg => sameNode(arg, argument));
2297
+ const parameter = at >= 0 && parameters?.[at];
2298
+ return parameter ? parameter.type === 'const_parameter' ? 'value' : 'type' : 'unknown';
2299
+ }
2300
+ if (traits.typeArgumentValues === 'ordinary' && role.cppQualified && !role.qualifierSegment) {
2301
+ const parent = argument.parent;
2302
+ if (parent?.type === 'using_declaration') return this.ordinaryArgumentPosition(file, argument);
2303
+ const declaredType = parent?.childForFieldName('type');
2304
+ if (declaredType && contains(declaredType, argument)) return 'type';
2305
+ if (parent && (parent.childForFieldName('value') && contains(parent.childForFieldName('value'), argument) ||
2306
+ parent.childForFieldName('default_value') && contains(parent.childForFieldName('default_value'), argument) ||
2307
+ ['return_statement', 'argument_list', 'binary_expression', 'assignment_expression',
2308
+ 'conditional_expression', 'unary_expression', 'expression_statement', 'initializer_list',
2309
+ 'case_statement', 'for_range_loop'].includes(parent.type) || parent.type.endsWith('_expression'))) return 'value';
2310
+ if (parent?.type === 'call_expression' && sameNode(parent.childForFieldName('function'), argument)) return 'value';
2311
+ return 'unknown';
2312
+ }
2313
+ return 'type';
2314
+ }
2315
+
2316
+ declarationRegion(file, node, role) {
2317
+ if (this.owner) {
2318
+ const owner = this.declarationOwner(file, node);
2319
+ if (owner?.node) return `type:${owner.node.startIndex}:${owner.node.endIndex}`;
2320
+ }
2321
+ const namespaces = [];
2322
+ for (let parent = node.parent; parent; parent = parent.parent) {
2323
+ if (CALLABLE_SCOPES.has(parent.type) || TYPE_BODIES.has(parent.type)) break;
2324
+ if (parent.type === 'namespace_definition' || parent.type === 'mod_item') {
2325
+ namespaces.unshift(parent.childForFieldName('name')?.text || '<anonymous>');
2326
+ }
2327
+ }
2328
+ if (namespaces.length) return `namespace:${namespaces.join('::')}`;
2329
+ const scope = referenceBindingScope(node, this.index.files.get(file)?.language, role) || node.tree.rootNode;
2330
+ return `scope:${scope.type}:${scope.startIndex}:${scope.endIndex}`;
2331
+ }
2332
+
2333
+ decideToken(file, entry, token) {
2334
+ const { node } = token;
2335
+ let { role } = token;
2336
+ if (role.selectorOperand && role.qualifier) {
2337
+ // `x[a.b]` read as an instantiation (fix #431): a value `a` (a
2338
+ // local, or any name no import binds) makes `a.b` a selector.
2339
+ const operand = role.qualifier;
2340
+ const scope = referenceScope(operand, entry.language, this.lexicalMemo(operand), operand.text);
2341
+ const imported = (entry.importBindings || []).some(binding =>
2342
+ (binding.alias || binding.name) === operand.text && binding.module);
2343
+ if (scope !== 'unknown' && (!imported || (scope && scope !== 'module' && scope !== 'none'))) {
2344
+ role = { role: 'member', receiver: operand };
2345
+ }
2346
+ }
2347
+ const position = role.role === 'type' ? this.typePosition(file, node, role) : null;
2348
+ const contextualValue = position === 'value';
2349
+ if (contextualValue) role = { ...role, role: 'value' };
2350
+ const receiver = memberReceiver(node, role);
2351
+ let declaration = false;
2352
+ let verdict, wholeModuleImport;
2353
+ const parent = node.parent;
2354
+ const privateToken = node.type === 'private_property_identifier';
2355
+ const privateDeclaration = privateToken &&
2356
+ ['field_definition', 'public_field_definition', 'method_definition'].includes(parent?.type) &&
2357
+ sameNode(parent.childForFieldName('name') || parent.childForFieldName('property'), node);
2358
+ const pattern = ['field_initializer', 'field_pattern', 'shorthand_field_initializer',
2359
+ 'field_designator'].includes(parent?.type);
2360
+ // A key the grammar reads as an expression (a Python dict display,
2361
+ // `{HEAVY: SQUARE}`) is a value read, not a property name (fix #431).
2362
+ const objectKey = (parent?.type === 'pair' || parent?.type === 'pair_pattern') &&
2363
+ sameNode(parent.childForFieldName('key'), node) && role.role !== 'value';
2364
+ const shorthand = ['shorthand_property_identifier_pattern',
2365
+ 'shorthand_property_identifier', 'shorthand_field_identifier'].includes(node.type);
2366
+ const declarationRole = role.role === 'skip' &&
2367
+ (role.binding || ['field-name', 'member-name', 'property-name', 'enumerator',
2368
+ 'enum-constant', 'enum-member', 'variant'].includes(role.why));
2369
+ const callableValue = role.role === 'skip' && role.why === 'method-call' &&
2370
+ !langTraits(entry.language)?.fieldsAndMethodsSeparate;
2371
+ // A template-id names the value it instantiates: `ns::v<T>` is
2372
+ // qualified as `ns::v` (fix #431).
2373
+ const nameExpression = templateIdOf(node);
2374
+ const qualified = nameExpression.parent?.type === 'qualified_identifier' &&
2375
+ sameNode(nameExpression.parent.childForFieldName('name'), nameExpression) ? nameExpression.parent : null;
2376
+ const qualifier = role.qualifier || qualified?.childForFieldName('scope');
2377
+ const globalQualified = langTraits(entry.language)?.typeArgumentValues === 'ordinary' &&
2378
+ qualified && !qualified.childForFieldName('scope') && qualified.children.some(child => child.type === '::');
2379
+ const shape = this.owner && !declarationRole
2380
+ ? langTraits(entry.language)?.accessorReferenceShape?.(node.tree, {
2381
+ line: node.startPosition.row + 1, column: node.startPosition.column, usageType: 'reference',
2382
+ }) : null;
2383
+ const patternReference = this.patternReference(file, node, role);
2384
+ const scriptScope = file === this.def.file && this.scopeRelation(file,
2385
+ this.def.nameLine || this.def.startLine, node);
2386
+ const pythonBinding = this.moduleVariable && !receiver && !(qualifier && role.role !== 'import') &&
2387
+ !(declarationRole && this.selectedBinding(file, node))
2388
+ ? this.pythonBindingAt(file, token) : null;
2389
+ if (pythonBinding) {
2390
+ verdict = pythonBinding;
2391
+ } else if (privateToken) {
2392
+ declaration = privateDeclaration;
2393
+ verdict = this.privateValueAt(file, node);
2394
+ } else if (position === 'type' && role.qualifier && this.owner?.def?.type === 'enum' &&
2395
+ langTraits(entry.language)?.enumMemberTypes) {
2396
+ // `Kind.Text` in a type names the enum member's type (fix #431);
2397
+ // `ns.Text` through a namespace import names a module export.
2398
+ const enumOwner = this.classForType(file, typeSyntax(role.qualifier));
2399
+ const namespaceQualifier = !enumOwner && !!this.jsNamespaceHolds(file, role.qualifier)?.modules;
2400
+ verdict = enumOwner ? this.memberAt(enumOwner)
2401
+ : namespaceQualifier ? { verdict: 'no', reason: 'non-value-position' }
2402
+ : { verdict: 'unknown', reason: 'member-owner-unresolved' };
2403
+ } else if (position === 'type' || role.role === 'decl') {
2404
+ verdict = { verdict: 'no', reason: 'non-value-position' };
2405
+ } else if (position === 'unknown' || position === 'initializer-unknown') {
2406
+ verdict = { verdict: 'unknown', reason: position === 'initializer-unknown'
2407
+ ? 'initializer-argument-binding' : 'generic-argument-namespace' };
2408
+ } else if (!this.owner && scriptScope === 'no') {
2409
+ declaration = declarationRole;
2410
+ verdict = { verdict: 'no', reason: 'other-script-scope' };
2411
+ } else if (scriptScope === 'unknown' && node.startPosition.row + 1 ===
2412
+ (this.def.nameLine || this.def.startLine)) {
2413
+ verdict = { verdict: 'unknown', reason: 'script-binding-scope' };
2414
+ } else if (patternReference && patternReference.verdict !== 'no') {
2415
+ verdict = patternReference;
2416
+ } else if (shorthand && !(role.patternWrite && !this.owner)) {
2417
+ // `({ a } = o)` writes the binding `a` in scope (fix #431); a
2418
+ // member target reads the source object's property.
2419
+ verdict = { verdict: 'unknown', reason: 'value-key-binding' };
2420
+ } else if (pattern || objectKey) {
2421
+ const literalOwner = this.owner && objectKey && parent.type === 'pair' &&
2422
+ this.family === 'js' ? this.objectLiteralOwner(file, parent) : null;
2423
+ verdict = literalOwner ? this.memberAt(literalOwner)
2424
+ : this.owner ? { verdict: 'unknown', reason: 'value-initializer-owner-unresolved' }
2425
+ : { verdict: 'no', reason: 'object-key' };
2426
+ } else if (shape?.uncertainRole) {
2427
+ verdict = { verdict: 'unknown', reason: 'value-role-unresolved' };
2428
+ } else if (shape?.kind === 'initializer') {
2429
+ verdict = this.memberAt(this.classForType(file, shape.type));
2430
+ } else if (receiver) {
2431
+ const receiverOwner = this.owner && langTraits(entry.language)?.typeArgumentValues !== 'ordinary'
2432
+ ? this.receiverOwner(file, receiver) : null;
2433
+ verdict = (this.owner && this.pathMemberAt(file, receiver)) ||
2434
+ (langTraits(entry.language)?.typeArgumentValues === 'ordinary'
2435
+ ? this.cppMemberAt(this.receiverOwner(file, receiver))
2436
+ : this.owner ? this.memberAt(receiverOwner)
2437
+ : (this.family === 'js' && this.jsModuleMemberAt(file, receiver)) ||
2438
+ this.resolveQualified(file, node.startPosition.row + 1, receiver,
2439
+ receiver.text, { role: 'member' }));
2440
+ // An attribute a method declares by assigning it on its receiver
2441
+ // (a Python instance attribute, fix #430): the write at the
2442
+ // pin's declaration line is the declaration, the first write of
2443
+ // another class in the slot is that member's declaration.
2444
+ if (verdict?.verdict === 'yes' && receiverOwner && isWrittenMember(node)) {
2445
+ if (file === this.def.file && node.startPosition.row + 1 === (this.def.nameLine || this.def.startLine)) {
2446
+ declaration = true;
2447
+ } else if (this.groupMemberAt(file, node, receiverOwner) === 'group') {
2448
+ const access = this.slotDeclarationAccess(file, node, receiverOwner);
2449
+ verdict = { verdict: 'yes', declarationOf: true, ...(access && { access }) };
2450
+ }
2451
+ }
2452
+ } else if (globalQualified) {
2453
+ // `::name` is looked up in the global namespace only (fix #420):
2454
+ // never a class member, and never a local or a class-scope
2455
+ // value the unqualified spelling would find first.
2456
+ verdict = this.owner ? { verdict: 'no', reason: 'global-qualified' }
2457
+ : this.resolveCFamily(file, node.startPosition.row + 1, this.name, '::');
2458
+ } else if (qualifier && role.role !== 'import' && this.owner && this.def.type === 'field' &&
2459
+ langTraits(entry.language)?.typePathReachesFields === false) {
2460
+ // A type path names an associated item, never a struct field
2461
+ // (fix #431: `Opt::command()` calls the derived constructor).
2462
+ verdict = { verdict: 'no', reason: 'associated-item-path' };
2463
+ } else if (qualifier && role.role !== 'import') {
2464
+ verdict = langTraits(entry.language)?.typeArgumentValues === 'ordinary'
2465
+ ? this.qualifiedValueAt(file, qualifier, role)
2466
+ : this.owner ? this.memberAt(this.classForType(file, typeSyntax(qualifier)))
2467
+ : this.resolveQualified(file, node.startPosition.row + 1, qualifier,
2468
+ qualifier.text, role);
2469
+ } else if (role.role === 'qualified') {
2470
+ verdict = { verdict: 'unknown', reason: 'qualified-value-unresolved' };
2471
+ } else if (role.role === 'import' && this.owner) {
2472
+ verdict = ['none', 'declaration-body', 'class-body'].includes(
2473
+ langTraits(entry.language)?.bareValueMemberScope) && !langTraits(entry.language)?.bareCallReachesMethods
2474
+ ? { verdict: 'no', reason: 'module-import' }
2475
+ : this.staticImportAt(file, node, true) ||
2476
+ { verdict: 'unknown', reason: 'value-import-unresolved' };
2477
+ } else if (role.role === 'import') {
2478
+ verdict = referenceImportVerdict(this, file, entry, token);
2479
+ } else if (this.owner && role.role === 'skip' && role.why === 'keyword-argument' &&
2480
+ langTraits(entry.language)?.fieldInitializers) {
2481
+ // `Segment(text, style=s)`: the keyword of a construction whose
2482
+ // initializer the class synthesizes from its fields names the
2483
+ // field (fix #431).
2484
+ verdict = this.keywordFieldAt(file, entry, node);
2485
+ } else if (this.family === 'js' && !this.owner && role.role === 'skip' &&
2486
+ (role.why === 'variable' || role.why === 'import-local') &&
2487
+ (wholeModuleImport = this.jsWholeModuleImport(file, entry, node))) {
2488
+ // `const req = require('./request')` / `import req from ..` of a
2489
+ // module whose value is the pin (fix #431): a dependency site;
2490
+ // the local binding keeps its name in a rename.
2491
+ verdict = { verdict: 'no', reason: 'import-alias', access: wholeModuleImport };
2492
+ } else if (!callableValue && (role.role === 'skip' || role.role === 'decl')) {
2493
+ if (!declarationRole) verdict = { verdict: 'no', reason: 'non-value-position' };
2494
+ else {
2495
+ declaration = true;
2496
+ const selectedLine = this.def.nameLine || this.def.startLine;
2497
+ const sameOwner = this.owner && this.declarationOwner(file, node)?.key === this.owner.key;
2498
+ const sameDeclaration = file === this.def.file && node.startPosition.row + 1 === selectedLine;
2499
+ const enumDeclaration = this.enumInfo(this.def)?.node;
2500
+ const selectedEnum = enumDeclaration && sameDeclaration &&
2501
+ node.startPosition.column === enumDeclaration.startPosition.column;
2502
+ const scope = referenceBindingScope(node, entry.language, role);
2503
+ const globalDeclaration = !this.owner && !this.declarationOwner(file, node) &&
2504
+ (!scope || ROOTS.has(scope.type));
2505
+ const otherOwner = this.owner && this.declarationOwner(file, node);
2506
+ const related = otherOwner && langTraits(this.language)?.typeSystem === 'structural' &&
2507
+ (relationToTargets(this.index, otherOwner, [this.def]) !== 'unrelated' ||
2508
+ relationToTargets(this.index, this.owner, this.members.filter(member =>
2509
+ ownerRefOf(this.index, member)?.key === otherOwner.key)) !== 'unrelated');
2510
+ verdict = sameDeclaration && (globalDeclaration || sameOwner || selectedEnum ||
2511
+ this.selectedBinding(file, node))
2512
+ ? { verdict: 'yes' } : { verdict: 'no', reason: 'other-declaration' };
2513
+ const member = verdict.verdict === 'no' && (globalDeclaration || otherOwner) &&
2514
+ this.groupMemberAt(file, node, otherOwner || null);
2515
+ if (member) {
2516
+ const access = member === 'group' && this.slotDeclarationAccess(file, node, otherOwner);
2517
+ verdict = member === 'group' ? { verdict: 'yes', declarationOf: true, ...(access && { access }) }
2518
+ : { verdict: 'unknown', reason: 'link-alternatives', declarationOf: true };
2519
+ declaration = false;
2520
+ }
2521
+ const declaredStorage = langTraits(this.language)?.separateStaticStorage && this.thisStorage(node);
2522
+ if (sameDeclaration && sameOwner && declaredStorage) this.declarationStorages.add(declaredStorage);
2523
+ if (verdict.verdict === 'no' && declaredStorage && declaredStorage !== this.targetStorage) {
2524
+ verdict = { verdict: 'no', reason: 'other-value-storage' };
2525
+ } else if (verdict.verdict === 'no' && sameOwner && !this.otherPathMemberAt(file, node)) {
2526
+ verdict = { verdict: 'unknown', reason: 'value-declaration-identity' };
2527
+ declaration = false;
2528
+ } else if (verdict.verdict === 'no' && related) {
2529
+ verdict = { verdict: 'unknown', reason: 'inherited-value-shadow' };
2530
+ declaration = false;
2531
+ } else if (verdict.verdict === 'no' && this.owner && !otherOwner &&
2532
+ ['field-name', 'member-name', 'property-name'].includes(role.why)) {
2533
+ verdict = { verdict: 'unknown', reason: 'value-member-owner-unresolved' };
2534
+ declaration = false;
2535
+ } else if (verdict.verdict === 'no' && !this.owner &&
2536
+ (sameDeclaration || globalDeclaration && file === this.def.file)) {
2537
+ const traits = langTraits(entry.language);
2538
+ const lookup = traits.moduleValueLookup || traits.textualIncludes
2539
+ ? this.resolveName(file, node.startPosition.row + 1) : null;
2540
+ if (lookup?.verdict !== 'no') {
2541
+ verdict = { verdict: 'unknown', reason: 'value-declaration-identity' };
2542
+ declaration = false;
2543
+ }
2544
+ }
2545
+ }
2546
+ } else if (callableValue || role.role === 'value' || role.role === 'pin-binding' || role.role === 'dunder-all') {
2547
+ const local = this.localBinding(file, node, this.name, false, contextualValue);
2548
+ const importer = !local && this.moduleVariable && file !== this.def.file &&
2549
+ this.pythonImporterVariable(file);
2550
+ if (local) verdict = local.pin ? { verdict: 'yes' }
2551
+ : local.binding ? { verdict: 'no', reason: 'local-binding',
2552
+ fileLocalOnly: this.sourceLocalBinding(file, local.binding, node) } : local;
2553
+ else if (importer) {
2554
+ verdict = importer === true ? { verdict: 'yes' } : { verdict: 'unknown', reason: 'module-rebinding' };
2555
+ }
2556
+ else if (langTraits(entry.language)?.textualIncludes && enclosingEnum(node)) {
2557
+ verdict = this.globalAt(file, node, contextualValue);
2558
+ }
2559
+ else if (langTraits(entry.language)?.typeArgumentValues === 'ordinary') {
2560
+ verdict = this.globalAt(file, node, contextualValue);
2561
+ }
2562
+ else if (this.enumInfo(this.def)?.unscoped) verdict = this.globalAt(file, node, contextualValue);
2563
+ else if (this.owner) {
2564
+ verdict = this.enumCaseAt(file, node);
2565
+ if (!verdict) verdict = this.bareMemberVerdict(file, node, entry.language);
2566
+ if (!verdict) {
2567
+ const owner = this.enclosingOwner(file, node);
2568
+ verdict = langTraits(entry.language)?.bareCallReachesMethods
2569
+ ? this.memberAt(owner) : null;
2570
+ if ((!verdict || verdict.verdict === 'unknown') &&
2571
+ (!owner || !findDeclaringClass(this.index, owner, this.members))) {
2572
+ verdict = this.staticImportAt(file, node) || verdict;
2573
+ }
2574
+ }
2575
+ verdict ||= { verdict: 'unknown', reason: 'implicit-value-owner-unresolved' };
2576
+ }
2577
+ else verdict = this.globalAt(file, node, contextualValue);
2578
+ } else if (this.owner && this.family === 'go' && goKeyedFieldKey(node)) {
2579
+ // `Box{limit: 5}` names Box's own field (fix #434): the
2580
+ // literal's written type, or the element type an enclosing
2581
+ // slice, array or map literal gives an elided one.
2582
+ const type = goCompositeLiteralType(node);
2583
+ const owner = type ? this.classForType(file, typeSyntax(type)) : null;
2584
+ verdict = owner ? this.memberAt(owner) : { verdict: 'unknown', reason: 'composite-literal-type-unresolved' };
2585
+ } else verdict = role.role === 'type'
2586
+ ? { verdict: 'no', reason: 'non-value-position' }
2587
+ : { verdict: 'unknown', reason: 'value-binding-unresolved' };
2588
+ if (!declaration && verdict.verdict === 'yes' && receiver) {
2589
+ const line = node.startPosition.row + 1;
2590
+ for (const member of this.group) {
2591
+ if (!member.instanceAttribute || member.file !== file ||
2592
+ line < member.startLine || line > member.endLine) continue;
2593
+ if (instanceAttributeInitializerReads(this.index, member).some(site =>
2594
+ site.line === line && site.column === node.startPosition.column)) {
2595
+ verdict = { verdict: 'unknown', reason: 'instance-attribute-before-write' };
2596
+ break;
2597
+ }
2598
+ }
2599
+ }
2600
+ if (!declaration && verdict.verdict === 'yes' && this.moduleVariable && file === this.def.file && !receiver &&
2601
+ !role.binding) {
2602
+ verdict = this.pythonStarBindingAt(file, node) || verdict;
2603
+ }
2604
+ if (declaration && verdict.verdict === 'yes') {
2605
+ this.selectedDeclarations++;
2606
+ if (privateToken) this.privateIdentifier = true;
2607
+ this.declarationRegions.add(this.declarationRegion(file, node, role));
2608
+ }
2609
+ if (!declaration) {
2610
+ const line = node.startPosition.row + 1;
2611
+ this.references.push({ file: entry.relativePath, absoluteFile: file, line,
2612
+ column: node.startPosition.column, expression: this.index.getLineContent(file, line).trim(),
2613
+ ...(receiver && { receiver: receiver.text }),
2614
+ // Another declaration of the renamed entity (a linkage
2615
+ // declaration, a slot member) is listed as a declaration.
2616
+ accessKind: verdict.access?.accessKind || (verdict.declarationOf ? 'declaration'
2617
+ : require('./accessors').referenceAccessKind(node.tree, { line, column: node.startPosition.column }, node)),
2618
+ // A rename decides the token (edit, review, skip); impact
2619
+ // lists it by what it reaches (fix #431): another member of
2620
+ // the slot is not the target's access, and an import of the
2621
+ // pin's module value is a dependency site the rename leaves.
2622
+ ...(verdict.access ? { verdict: verdict.access.verdict, reason: verdict.access.reason }
2623
+ : { verdict: verdict.verdict, reason: verdict.reason }) });
2624
+ if (this.enumReferenceSyntax?.collecting(file)) {
2625
+ this.enumReferenceSyntax.capture(file,
2626
+ this.enumSyntaxFact(file, entry, token, receiver, qualifier, position, shape));
2627
+ }
2628
+ }
2629
+ if (verdict.verdict === 'no' && !verdict.access && role.binding &&
2630
+ this.sourceLocalBinding(file, { node, role,
2631
+ scope: referenceBindingScope(node, entry.language, role) })) verdict.fileLocalOnly = true;
2632
+ return verdict;
2633
+ }
2634
+ }
2635
+
2636
+ function valueReferenceSites(index, def, name, groundSet) {
2637
+ if (!isValueDefinition(def)) return null;
2638
+ const resolver = new ValueReferenceResolver(index, def, name);
2639
+ const result = typeReferenceSites(index, def, name, groundSet, resolver);
2640
+ // A line handle cannot distinguish declarations in separate indexed
2641
+ // scopes, even when lookup has selected one of their owners internally.
2642
+ const selectedLine = def.nameLine || def.startLine;
2643
+ const indexedScopes = new Set((index.symbols.get(name) || []).filter(candidate =>
2644
+ isValueDefinition(candidate) && candidate.file === def.file &&
2645
+ (candidate.nameLine || candidate.startLine) === selectedLine &&
2646
+ (candidate.className || null) === (def.className || null))
2647
+ .map(candidate => JSON.stringify([candidate.namespace || null, candidate.enclosingType || null,
2648
+ candidate.lexicalScopeStartLine ?? null, candidate.lexicalScopeEndLine ?? null])));
2649
+ const ambiguityReason = resolver.declarationStorages.size > 1 ? 'storage'
2650
+ : resolver.declarationRegions.size > 1 || indexedScopes.size > 1 ? 'scope' : null;
2651
+ const ambiguousDeclaration = ambiguityReason !== null;
2652
+ if (ambiguousDeclaration) {
2653
+ for (const info of result.files.values()) for (const slot of info.lines.values()) {
2654
+ for (const column of slot.edits) slot.reviews.push({ column, reason: 'ambiguous-value-declaration' });
2655
+ slot.edits = [];
2656
+ }
2657
+ for (const reference of resolver.references) {
2658
+ if (reference.verdict === 'yes' || reference.reason === 'other-value-storage') {
2659
+ reference.verdict = 'unknown';
2660
+ reference.reason = 'ambiguous-value-declaration';
2661
+ }
2662
+ }
2663
+ }
2664
+ if (!resolver.selectedDeclarations) {
2665
+ const line = def.nameLine || def.startLine;
2666
+ const info = result.files.get(def.file);
2667
+ const slot = info?.lines.get(line);
2668
+ if (slot && !slot.reviews.some(review => review.reason === 'value-declaration-identity')) {
2669
+ slot.reviews.push({ column: null, reason: 'value-declaration-identity' });
2670
+ }
2671
+ }
2672
+ return { ...result, ambiguousDeclaration, ambiguityReason,
2673
+ privateIdentifier: resolver.privateIdentifier === true, references: resolver.references };
2674
+ }
2675
+
2676
+ function findValueReferences(index, name, def, options = {}) {
2677
+ if (!isValueDefinition(def)) return null;
2678
+ const { computeGroundSet } = require('./account');
2679
+ const result = valueReferenceSites(index, def, name, computeGroundSet(index, name));
2680
+ const byFile = new Map(), unverifiedSites = [], excluded = [];
2681
+ const compare = require('./shared').codeUnitCompare;
2682
+ const references = result.references.slice().sort((a, b) => compare(a.file, b.file) ||
2683
+ a.line - b.line || a.column - b.column);
2684
+ for (const ref of references) {
2685
+ if (!index.matchesFilters(ref.file, options)) continue;
2686
+ const { verdict, absoluteFile, ...site } = ref;
2687
+ if (options.files && !options.files.has(absoluteFile)) continue;
2688
+ if (verdict === 'yes') {
2689
+ if (!byFile.has(site.file)) byFile.set(site.file, []);
2690
+ byFile.get(site.file).push({ ...site, tier: 'confirmed' });
2691
+ } else if (verdict === 'unknown') unverifiedSites.push({ ...site, tier: 'unverified' });
2692
+ else excluded.push(site);
2693
+ }
2694
+ const sorted = [...byFile].sort((a, b) => compare(a[0], b[0]))
2695
+ .map(([file, sites]) => ({ file, count: sites.length, sites }));
2696
+ const confirmedCount = sorted.reduce((n, group) => n + group.count, 0);
2697
+ // Every name token the scan decided is in one bucket (fix #420): an
2698
+ // access (confirmed, unverified, excluded with a reason) or a declaration
2699
+ // token (the pin's own and other declarations of the name).
2700
+ let tokens = 0, lines = 0, textLines = 0;
2701
+ const scannedFiles = new Set();
2702
+ for (const [absoluteFile, info] of result.files) {
2703
+ if (!index.matchesFilters(info.relativePath, options) ||
2704
+ options.files && !options.files.has(absoluteFile)) continue;
2705
+ for (const slot of info.lines.values()) {
2706
+ tokens += slot.tokens;
2707
+ if (slot.tokens > 0) { lines++; scannedFiles.add(absoluteFile); } else if (slot.text) textLines++;
2708
+ }
2709
+ }
2710
+ const accessTokens = confirmedCount + unverifiedSites.length + excluded.length;
2711
+ return { confirmedCount,
2712
+ unverifiedCount: unverifiedSites.length,
2713
+ totalCandidates: confirmedCount + unverifiedSites.length,
2714
+ byFile: sorted, unverifiedSites, excluded: { total: excluded.length,
2715
+ byReason: excluded.reduce((out, site) => {
2716
+ out[site.reason] = (out[site.reason] || 0) + 1; return out;
2717
+ }, {}) },
2718
+ account: { tokens, lines, files: scannedFiles.size, textLines, confirmed: confirmedCount,
2719
+ unverified: unverifiedSites.length, excluded: excluded.length,
2720
+ declarations: tokens - accessTokens, conserved: tokens >= accessTokens } };
2721
+ }
2722
+
2723
+ /** The class a field annotation denotes, completed only in this use's unit. */
2724
+ function declaredFieldOwner(index, field, useFile) {
2725
+ const memo = index.isProvenanceFactIndex ? null
2726
+ : index._opMemo?.('declaredValueFieldOwners', () => new WeakMap());
2727
+ let byFile = memo?.get(field);
2728
+ if (byFile?.has(useFile)) return byFile.get(useFile);
2729
+ const resolver = new ValueReferenceResolver(index, field, field.name);
2730
+ const declaration = resolver.declarationNode(field);
2731
+ const type = declaration && declarationType(declaration);
2732
+ const owner = type?.syntax
2733
+ ? resolver.classForType(field.file, type.syntax, false, useFile || field.file) : null;
2734
+ const result = { owner, forward: !!resolver.sawForwardDeclaration };
2735
+ if (memo) {
2736
+ if (!byFile) memo.set(field, byFile = new Map());
2737
+ byFile.set(useFile, result);
2738
+ }
2739
+ return result;
2740
+ }
2741
+
2742
+ /** A literal function body declares callable code at this value binding.
2743
+ * Reading another value, including a function pointer, proves no such body. */
2744
+ function hasCallableInitializer(index, definition) {
2745
+ const memo = index._opMemo?.('valueCallableInitializers', () => new WeakMap());
2746
+ if (memo?.has(definition)) return memo.get(definition);
2747
+ const file = definition.file, source = index._readFile(file);
2748
+ const language = index.files.get(file)?.language;
2749
+ const tree = index._getParsedTree(file, source, language) || safeParse(getParser(language), source);
2750
+ const declarations = [];
2751
+ const literals = new Set(['function', 'function_expression', 'arrow_function',
2752
+ 'generator_function', 'func_literal', 'lambda', 'closure_expression', 'lambda_expression']);
2753
+ if (tree) visitNameNodes(tree, source, definition.name, node => {
2754
+ if (node.startPosition.row + 1 !== (definition.nameLine || definition.startLine)) return true;
2755
+ for (let parent = node.parent; parent; parent = parent.parent) {
2756
+ if (['variable_declarator', 'assignment', 'var_spec', 'const_spec',
2757
+ 'const_item', 'static_item', 'init_declarator'].includes(parent.type)) {
2758
+ const name = parent.childForFieldName('name') || parent.childForFieldName('left') ||
2759
+ parent.childForFieldName('declarator');
2760
+ if (!name || !contains(name, node)) break;
2761
+ let value = parent.childForFieldName('value') || parent.childForFieldName('right');
2762
+ while (['expression_list', 'parenthesized_expression'].includes(value?.type) &&
2763
+ value.namedChildCount === 1) value = value.namedChild(0);
2764
+ declarations.push(literals.has(value?.type));
2765
+ break;
2766
+ }
2767
+ if (CALLABLE_SCOPES.has(parent.type) || ROOTS.has(parent.type)) break;
2768
+ }
2769
+ return true;
2770
+ });
2771
+ const result = declarations.length === 1 && declarations[0];
2772
+ memo?.set(definition, result);
2773
+ return result;
2774
+ }
2775
+
2776
+ module.exports = { isValueDefinition, valueReferenceSites, findValueReferences, hasCallableInitializer,
2777
+ enumValueBindingInfo, declaredFieldOwner, pythonClassBodyOf, instanceAttributeInitializerReads };