ucn 5.4.1 → 5.5.0

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 (87) hide show
  1. package/.claude/skills/ucn/SKILL.md +58 -236
  2. package/.claude/skills/ucn/references/commands.md +144 -68
  3. package/.claude/skills/ucn/references/resolution-rules.md +171 -0
  4. package/.claude/skills/ucn/references/trust-contract.md +27 -33
  5. package/README.md +18 -12
  6. package/cli/index.js +42 -16
  7. package/core/accessors.js +118 -3
  8. package/core/account.js +30 -0
  9. package/core/analysis.js +508 -264
  10. package/core/ast-analysis.js +76 -117
  11. package/core/async-producers.js +1729 -0
  12. package/core/bridge.js +1877 -268
  13. package/core/brief.js +6 -0
  14. package/core/build-worker.js +78 -14
  15. package/core/cache.js +522 -31
  16. package/core/callers.js +12053 -1047
  17. package/core/check.js +30 -38
  18. package/core/class-identity.js +1028 -0
  19. package/core/command-contracts.js +6 -6
  20. package/core/configuration-items.js +86 -0
  21. package/core/contract-membership.js +2262 -0
  22. package/core/cpp-scope.js +532 -0
  23. package/core/deadcode.js +171 -49
  24. package/core/decorator-binding.js +260 -0
  25. package/core/discovery.js +281 -164
  26. package/core/errors.js +52 -0
  27. package/core/execute.js +21 -9
  28. package/core/external-macros.js +326 -0
  29. package/core/graph-build.js +277 -8
  30. package/core/graph.js +35 -2
  31. package/core/ignore-rules.js +472 -0
  32. package/core/imports.js +504 -42
  33. package/core/index-ir.js +94 -11
  34. package/core/inprocess-clients.js +636 -0
  35. package/core/ir.js +87 -8
  36. package/core/macro-expansion.js +1416 -0
  37. package/core/output/analysis-ext.js +6 -6
  38. package/core/output/analysis.js +18 -2
  39. package/core/output/brief.js +3 -0
  40. package/core/output/doctor.js +4 -0
  41. package/core/output/endpoints.js +15 -6
  42. package/core/output/find.js +11 -2
  43. package/core/output/lines.js +8 -2
  44. package/core/output/refactoring.js +121 -14
  45. package/core/output/reporting.js +31 -3
  46. package/core/output/shared.js +14 -0
  47. package/core/output-budget.js +316 -122
  48. package/core/parallel-build.js +124 -47
  49. package/core/parser.js +3 -2
  50. package/core/project.js +457 -60
  51. package/core/provenance-facts.js +156 -12
  52. package/core/provenance-overload.js +30 -3
  53. package/core/provenance.js +104 -30
  54. package/core/pytest-fixtures.js +434 -0
  55. package/core/python-fixture-flow.js +17 -3
  56. package/core/python-modules.js +173 -0
  57. package/core/reflection.js +877 -0
  58. package/core/registry.js +1 -1
  59. package/core/reporting.js +234 -52
  60. package/core/request-helpers.js +561 -0
  61. package/core/route-graph.js +1922 -0
  62. package/core/route-lists.js +2414 -0
  63. package/core/rust-macro-expansion.js +2113 -0
  64. package/core/rust-modules.js +185 -0
  65. package/core/search.js +121 -29
  66. package/core/shared.js +19 -1
  67. package/core/test-name-bindings.js +256 -0
  68. package/core/type-denotation.js +1161 -0
  69. package/core/type-references.js +3229 -0
  70. package/core/verify.js +3360 -672
  71. package/languages/adapter.js +31 -5
  72. package/languages/c-family.js +4061 -212
  73. package/languages/c-macro-invocations.js +380 -0
  74. package/languages/c-preprocessor.js +254 -0
  75. package/languages/csharp.js +1406 -49
  76. package/languages/go.js +1243 -70
  77. package/languages/index.js +728 -6
  78. package/languages/java.js +473 -61
  79. package/languages/javascript.js +1270 -197
  80. package/languages/lexical-scope.js +1280 -0
  81. package/languages/python.js +1557 -72
  82. package/languages/rust-macro-rules.js +1020 -0
  83. package/languages/rust-value-flow.js +416 -0
  84. package/languages/rust.js +2237 -115
  85. package/languages/utils.js +298 -4
  86. package/mcp/server.js +25 -11
  87. package/package.json +1 -1
@@ -0,0 +1,1280 @@
1
+ /**
2
+ * languages/lexical-scope.js - Where a bare name reference resolves (fix #392).
3
+ *
4
+ * A bare identifier in expression position names the innermost binding the
5
+ * language's own scoping rules make visible there. `referenceScope` walks from
6
+ * the identifier to the file root and answers:
7
+ *
8
+ * 'none' the identifier is not a variable reference (a keyword-argument
9
+ * name, an object key, a struct field key)
10
+ * 'module' no enclosing function or block binds the name: it resolves at
11
+ * file / module / package scope
12
+ * { local: true, defRows } an enclosing scope binds it; `defRows` lists the
13
+ * 0-based rows of the declared names when every binding of the
14
+ * name in that scope is a function declaration (else null)
15
+ * 'unknown' the rules cannot settle it here (a local import, a `with`
16
+ * statement, a class body read, a macro token tree, ...)
17
+ *
18
+ * Rules per language family:
19
+ * - Python: function scopes bind every assignment target, parameter, import,
20
+ * def/class, loop/with/except/match capture and walrus target of the whole
21
+ * body (position does not matter); `global` resolves at module scope and
22
+ * `nonlocal` in an enclosing function; class bodies are visible only to code
23
+ * directly in them; comprehensions and lambdas bind their own targets;
24
+ * decorators, defaults and annotations evaluate in the enclosing scope.
25
+ * - JS/TS: blocks bind let/const/class/function declarations, functions bind
26
+ * parameters and every `var` of the body; named function/class expressions
27
+ * bind their own name; `with` and direct `eval` make the answer unknown.
28
+ * - Go: a local declaration is visible from the end of its declaration to
29
+ * the end of its block; statement headers (if/for/switch/select) and case
30
+ * clauses are blocks; parameters, results and receivers bind the body.
31
+ * - Rust: block items are visible in the whole block, `let` bindings after
32
+ * their statement; closures, fn parameters, match arms, `if let`/`while let`
33
+ * and `for` patterns bind their bodies; an inline module sees only its own
34
+ * items and what it imports (`use super::*` reaches the parent).
35
+ * - C (fix #396): a block binds the declarations and typedefs before the
36
+ * reference, a `for` initializer its statement, parameters the function
37
+ * body; everything else resolves at file scope. A block-scope function
38
+ * declaration (`void f(int);`) names the file's function: unknown.
39
+ */
40
+
41
+ 'use strict';
42
+
43
+ const { sameNode, traverseTree } = require('./utils');
44
+
45
+ const FAMILY = {
46
+ python: 'python',
47
+ javascript: 'js', typescript: 'js', tsx: 'js',
48
+ go: 'go',
49
+ rust: 'rust',
50
+ c: 'c',
51
+ };
52
+
53
+ function familyOf(language) {
54
+ return FAMILY[language] || null;
55
+ }
56
+
57
+ function namedChildrenOf(node) {
58
+ return node ? node.namedChildren || [] : [];
59
+ }
60
+
61
+ function within(node, outer) {
62
+ return !!(node && outer && node.startIndex >= outer.startIndex && node.endIndex <= outer.endIndex);
63
+ }
64
+
65
+ function memoGet(memo, scope, name, compute) {
66
+ const key = `${scope.startIndex}:${scope.endIndex}:${scope.type}:${name}`;
67
+ if (memo.has(key)) return memo.get(key);
68
+ const value = compute();
69
+ memo.set(key, value);
70
+ return value;
71
+ }
72
+
73
+ // ── Python ──────────────────────────────────────────────────────────────
74
+
75
+ /** Identifiers a Python assignment/loop/with target binds. */
76
+ function pyTargetNames(target, out) {
77
+ if (!target) return;
78
+ switch (target.type) {
79
+ case 'identifier':
80
+ out.push(target);
81
+ return;
82
+ case 'attribute': case 'subscript':
83
+ return;
84
+ case 'tuple_pattern': case 'list_pattern': case 'pattern_list':
85
+ case 'tuple': case 'list': case 'expression_list':
86
+ case 'parenthesized_expression': case 'list_splat_pattern':
87
+ case 'list_splat': case 'as_pattern_target':
88
+ for (const child of namedChildrenOf(target)) pyTargetNames(child, out);
89
+ return;
90
+ default:
91
+ return;
92
+ }
93
+ }
94
+
95
+ function pyParameterNames(params, out) {
96
+ for (const param of namedChildrenOf(params)) {
97
+ switch (param.type) {
98
+ case 'identifier':
99
+ out.push(param);
100
+ break;
101
+ case 'default_parameter': case 'typed_default_parameter': {
102
+ const name = param.childForFieldName('name');
103
+ if (name?.type === 'identifier') out.push(name);
104
+ else pyTargetNames(name, out);
105
+ break;
106
+ }
107
+ case 'typed_parameter': {
108
+ const first = param.namedChild(0);
109
+ if (first?.type === 'identifier') out.push(first);
110
+ else if (first?.type === 'list_splat_pattern' || first?.type === 'dictionary_splat_pattern') {
111
+ pyTargetNames(first.namedChild(0), out);
112
+ }
113
+ break;
114
+ }
115
+ case 'list_splat_pattern': case 'dictionary_splat_pattern':
116
+ case 'tuple_pattern':
117
+ pyTargetNames(param.type === 'tuple_pattern' ? param : param.namedChild(0), out);
118
+ break;
119
+ default:
120
+ break;
121
+ }
122
+ }
123
+ }
124
+
125
+ /** Capture names of a `case` pattern (not class names, not keyword keys). */
126
+ function pyCaptureNames(pattern, out) {
127
+ traverseTree(pattern, node => {
128
+ if (node.type === 'class_pattern') {
129
+ // The class spelled first is a reference; its arguments capture.
130
+ const children = namedChildrenOf(node);
131
+ for (let i = 1; i < children.length; i++) pyCaptureNames(children[i], out);
132
+ return false;
133
+ }
134
+ if (node.type === 'keyword_pattern') {
135
+ const children = namedChildrenOf(node);
136
+ for (let i = 1; i < children.length; i++) pyCaptureNames(children[i], out);
137
+ return false;
138
+ }
139
+ if (node.type === 'dotted_name') {
140
+ // A single name captures; a dotted value pattern reads.
141
+ if (node.namedChildCount === 1) out.push(node.namedChild(0));
142
+ return false;
143
+ }
144
+ if (node.type === 'identifier') out.push(node);
145
+ return true;
146
+ });
147
+ }
148
+
149
+ /**
150
+ * Bindings of `name` in one Python function/class body (not nested scopes,
151
+ * but walrus targets inside comprehensions bind here).
152
+ */
153
+ function pyScopeBindings(scopeNode, body, name, isFunction, includeClasses = false, onBinding) {
154
+ const info = { bound: false, defRows: [], other: false, import: false,
155
+ global: false, nonlocal: false, starImport: false };
156
+ const note = (node, kind) => {
157
+ if (!node || node.text !== name) return;
158
+ onBinding?.(node, kind);
159
+ info.bound = true;
160
+ if (kind === 'def') info.defRows.push(node.startPosition.row);
161
+ else if (kind === 'import') info.import = true;
162
+ else info.other = true;
163
+ };
164
+ if (isFunction) {
165
+ const params = scopeNode.childForFieldName('parameters');
166
+ const names = [];
167
+ pyParameterNames(params, names);
168
+ for (const node of names) note(node, 'binding');
169
+ }
170
+ if (!body) return info;
171
+ traverseTree(body, node => {
172
+ if (sameNode(node, body)) return true;
173
+ switch (node.type) {
174
+ case 'function_definition':
175
+ note(node.childForFieldName('name'), 'def');
176
+ // Defaults and annotations evaluate here; walrus targets in
177
+ // them bind here too, but the body is its own scope.
178
+ for (const field of ['parameters', 'return_type']) {
179
+ const part = node.childForFieldName(field);
180
+ if (part) scanWalrus(part);
181
+ }
182
+ return false;
183
+ case 'class_definition':
184
+ note(node.childForFieldName('name'), includeClasses ? 'def' : 'binding');
185
+ return false;
186
+ case 'lambda':
187
+ scanWalrus(node.childForFieldName('parameters'));
188
+ return false;
189
+ case 'list_comprehension': case 'set_comprehension':
190
+ case 'dictionary_comprehension': case 'generator_expression':
191
+ scanWalrus(node);
192
+ return false;
193
+ case 'assignment': case 'augmented_assignment': {
194
+ const names = [];
195
+ pyTargetNames(node.childForFieldName('left'), names);
196
+ for (const target of names) note(target, 'binding');
197
+ return true;
198
+ }
199
+ case 'named_expression':
200
+ note(node.childForFieldName('name'), 'binding');
201
+ return true;
202
+ case 'for_statement': {
203
+ const names = [];
204
+ pyTargetNames(node.childForFieldName('left'), names);
205
+ for (const target of names) note(target, 'binding');
206
+ return true;
207
+ }
208
+ case 'as_pattern': {
209
+ const alias = node.childForFieldName('alias');
210
+ if (alias) {
211
+ const names = [];
212
+ pyTargetNames(alias, names);
213
+ for (const target of names) note(target, 'binding');
214
+ // A match-statement `as` capture is a bare identifier.
215
+ if (alias.type === 'identifier') note(alias, 'binding');
216
+ } else {
217
+ const last = node.namedChild(node.namedChildCount - 1);
218
+ if (last?.type === 'identifier' && node.parent?.type === 'case_pattern') {
219
+ note(last, 'binding');
220
+ }
221
+ }
222
+ return true;
223
+ }
224
+ case 'delete_statement': {
225
+ const names = [];
226
+ for (const child of namedChildrenOf(node)) pyTargetNames(child, names);
227
+ for (const target of names) note(target, 'binding');
228
+ return false;
229
+ }
230
+ case 'import_statement':
231
+ for (const part of namedChildrenOf(node)) {
232
+ if (part.type === 'aliased_import') note(part.childForFieldName('alias'), 'import');
233
+ else if (part.type === 'dotted_name') note(part.namedChild(0), 'import');
234
+ }
235
+ return false;
236
+ case 'import_from_statement': {
237
+ for (const part of namedChildrenOf(node)) {
238
+ if (part.type === 'wildcard_import') info.starImport = true;
239
+ if (sameNode(part, node.childForFieldName('module_name'))) continue;
240
+ if (part.type === 'aliased_import') note(part.childForFieldName('alias'), 'import');
241
+ else if (part.type === 'dotted_name') {
242
+ note(part.namedChild(part.namedChildCount - 1), 'import');
243
+ }
244
+ }
245
+ return false;
246
+ }
247
+ case 'future_import_statement':
248
+ return false;
249
+ case 'global_statement':
250
+ for (const part of namedChildrenOf(node)) if (part.text === name) info.global = true;
251
+ return false;
252
+ case 'nonlocal_statement':
253
+ for (const part of namedChildrenOf(node)) if (part.text === name) info.nonlocal = true;
254
+ return false;
255
+ case 'case_clause': {
256
+ for (const part of namedChildrenOf(node)) {
257
+ if (part.type !== 'case_pattern') continue;
258
+ const names = [];
259
+ pyCaptureNames(part, names);
260
+ for (const target of names) note(target, 'binding');
261
+ }
262
+ return true;
263
+ }
264
+ case 'type_alias_statement': {
265
+ const first = node.namedChild(0);
266
+ note(first?.type === 'type' ? first.namedChild(0) : first, 'binding');
267
+ return false;
268
+ }
269
+ case 'call': case 'attribute': case 'subscript':
270
+ case 'binary_operator': case 'boolean_operator': case 'comparison_operator':
271
+ case 'unary_operator': case 'not_operator': case 'conditional_expression':
272
+ case 'list': case 'set': case 'dictionary':
273
+ case 'string': case 'concatenated_string':
274
+ // Expressions bind in this scope only through a walrus.
275
+ // Skip their often-large value trees; the text check is only
276
+ // a prefilter, and the AST walk still decides every binding.
277
+ return node.text.includes(':=');
278
+ default:
279
+ return true;
280
+ }
281
+ });
282
+ return info;
283
+
284
+ function scanWalrus(root) {
285
+ if (!root || !root.text.includes(':=')) return;
286
+ traverseTree(root, inner => {
287
+ if (inner.type === 'function_definition' || inner.type === 'class_definition') return false;
288
+ if (inner.type === 'named_expression') note(inner.childForFieldName('name'), 'binding');
289
+ return true;
290
+ });
291
+ }
292
+ }
293
+
294
+ function pyVerdict(info) {
295
+ if (info.import || info.starImport) return 'unknown';
296
+ if (info.other) return { local: true, defRows: null };
297
+ return { local: true, defRows: info.defRows.slice() };
298
+ }
299
+
300
+ function pythonReferenceScope(node, name, memo, includeClasses) {
301
+ const parent = node.parent;
302
+ if (!parent) return 'unknown';
303
+ if (parent.type === 'keyword_argument' && sameNode(parent.childForFieldName('name'), node)) return 'none';
304
+ if (parent.type === 'attribute' && sameNode(parent.childForFieldName('attribute'), node)) return 'none';
305
+ if (parent.type === 'keyword_pattern' && sameNode(parent.namedChild(0), node)) return 'none';
306
+ let child = node;
307
+ let crossedFunction = false;
308
+ let viaNonlocal = false;
309
+ for (let scope = parent; scope; child = scope, scope = scope.parent) {
310
+ switch (scope.type) {
311
+ case 'function_definition': {
312
+ const body = scope.childForFieldName('body');
313
+ if (!sameNode(body, child)) continue; // defaults, annotations: enclosing scope
314
+ const info = memoGet(memo, scope, name, () => pyScopeBindings(scope, body, name, true, includeClasses));
315
+ if (info.starImport) return 'unknown';
316
+ if (info.global) {
317
+ // A function that assigns a `global` name rebinds the module
318
+ // binding at run time.
319
+ return info.other || info.defRows.length > 0 || info.import ? 'unknown' : 'module';
320
+ }
321
+ if (info.nonlocal) {
322
+ viaNonlocal = true;
323
+ crossedFunction = true;
324
+ continue;
325
+ }
326
+ if (info.bound) return pyVerdict(info);
327
+ crossedFunction = true;
328
+ continue;
329
+ }
330
+ case 'lambda': {
331
+ if (!sameNode(scope.childForFieldName('body'), child)) continue;
332
+ const names = [];
333
+ pyParameterNames(scope.childForFieldName('parameters'), names);
334
+ if (names.some(n => n.text === name)) return { local: true, defRows: null };
335
+ crossedFunction = true;
336
+ continue;
337
+ }
338
+ case 'class_definition': {
339
+ const body = scope.childForFieldName('body');
340
+ if (!sameNode(body, child)) continue; // bases, keywords: enclosing scope
341
+ if (crossedFunction) continue; // methods do not see the class body
342
+ const info = memoGet(memo, scope, name, () => pyScopeBindings(scope, body, name, false, includeClasses));
343
+ // A class body reads its own dict first and falls back to
344
+ // globals, in statement order: a name it also binds is unknown.
345
+ if (info.bound || info.starImport) return 'unknown';
346
+ continue;
347
+ }
348
+ case 'list_comprehension': case 'set_comprehension':
349
+ case 'dictionary_comprehension': case 'generator_expression': {
350
+ const clauses = namedChildrenOf(scope).filter(c => c.type === 'for_in_clause');
351
+ // The first iterable evaluates in the enclosing scope.
352
+ const firstIterable = clauses[0]?.childForFieldName('right');
353
+ if (within(node, firstIterable)) continue;
354
+ for (const clause of clauses) {
355
+ const names = [];
356
+ pyTargetNames(clause.childForFieldName('left'), names);
357
+ if (names.some(n => n.text === name)) return { local: true, defRows: null };
358
+ }
359
+ crossedFunction = true;
360
+ continue;
361
+ }
362
+ case 'module':
363
+ return viaNonlocal ? 'unknown' : 'module';
364
+ default:
365
+ continue;
366
+ }
367
+ }
368
+ return viaNonlocal ? 'unknown' : 'module';
369
+ }
370
+
371
+ // ── JavaScript / TypeScript ─────────────────────────────────────────────
372
+
373
+ const JS_FUNCTIONS = new Set(['function_declaration', 'function_expression', 'function',
374
+ 'arrow_function', 'method_definition', 'generator_function',
375
+ 'generator_function_declaration']);
376
+ const JS_BLOCKS = new Set(['statement_block', 'switch_body', 'class_static_block', 'program']);
377
+
378
+ function jsPatternNames(pattern, out) {
379
+ if (!pattern) return;
380
+ switch (pattern.type) {
381
+ case 'identifier': case 'shorthand_property_identifier_pattern':
382
+ out.push(pattern);
383
+ return;
384
+ case 'assignment_pattern':
385
+ jsPatternNames(pattern.childForFieldName('left'), out);
386
+ return;
387
+ case 'pair_pattern':
388
+ jsPatternNames(pattern.childForFieldName('value'), out);
389
+ return;
390
+ case 'object_pattern': case 'array_pattern': case 'rest_pattern':
391
+ for (const child of namedChildrenOf(pattern)) jsPatternNames(child, out);
392
+ return;
393
+ case 'required_parameter': case 'optional_parameter':
394
+ jsPatternNames(pattern.childForFieldName('pattern'), out);
395
+ return;
396
+ case 'formal_parameters':
397
+ for (const child of namedChildrenOf(pattern)) jsPatternNames(child, out);
398
+ return;
399
+ default:
400
+ return;
401
+ }
402
+ }
403
+
404
+ function jsDeclarationNames(statement, name, note, includeClasses) {
405
+ let decl = statement;
406
+ if (decl.type === 'export_statement') decl = decl.childForFieldName('declaration') || decl;
407
+ switch (decl.type) {
408
+ case 'function_declaration': case 'generator_function_declaration':
409
+ if (decl.childForFieldName('name')?.text === name) note(decl.childForFieldName('name'), 'def');
410
+ return;
411
+ case 'class_declaration': case 'abstract_class_declaration':
412
+ if (decl.childForFieldName('name')?.text === name) {
413
+ note(decl.childForFieldName('name'), includeClasses ? 'def' : 'binding');
414
+ }
415
+ return;
416
+ case 'enum_declaration': case 'internal_module': case 'module':
417
+ if (decl.childForFieldName('name')?.text === name) note(decl.childForFieldName('name'), 'binding');
418
+ return;
419
+ case 'lexical_declaration': case 'variable_declaration': {
420
+ // `var` also binds the enclosing function; counting it for the
421
+ // block too is harmless (the function scan sees it first or not).
422
+ for (const d of namedChildrenOf(decl)) {
423
+ if (d.type !== 'variable_declarator') continue;
424
+ const nameNode = d.childForFieldName('name');
425
+ const value = d.childForFieldName('value');
426
+ const names = [];
427
+ jsPatternNames(nameNode, names);
428
+ for (const n of names) {
429
+ if (n.text !== name) continue;
430
+ const fnValue = nameNode?.type === 'identifier' && value &&
431
+ (value.type === 'arrow_function' || value.type === 'function_expression' ||
432
+ value.type === 'function');
433
+ const classValue = includeClasses && nameNode?.type === 'identifier' && value?.type === 'class';
434
+ note(n, fnValue || classValue ? 'def' : 'binding');
435
+ }
436
+ }
437
+ return;
438
+ }
439
+ case 'import_statement':
440
+ traverseTree(decl, inner => {
441
+ if ((inner.type === 'identifier') && inner.text === name &&
442
+ inner.parent?.type !== 'string') note(inner, 'import');
443
+ return true;
444
+ });
445
+ return;
446
+ default:
447
+ return;
448
+ }
449
+ }
450
+
451
+ function jsBlockBindings(block, name, includeClasses) {
452
+ const info = { bound: false, defRows: [], other: false, import: false };
453
+ const note = (node, kind) => {
454
+ if (!node || node.text !== name) return;
455
+ info.bound = true;
456
+ if (kind === 'def') info.defRows.push(node.startPosition.row);
457
+ else if (kind === 'import') info.import = true;
458
+ else info.other = true;
459
+ };
460
+ const statements = block.type === 'switch_body'
461
+ ? namedChildrenOf(block).flatMap(c => namedChildrenOf(c))
462
+ : namedChildrenOf(block);
463
+ for (const statement of statements) jsDeclarationNames(statement, name, note, includeClasses);
464
+ if (includeClasses) {
465
+ // A class declaration is a mutable value binding in JavaScript.
466
+ // Reassignment prevents using its declaration as constructor proof.
467
+ // Ordinary scope queries still report only lexical declarations.
468
+ traverseTree(block, node => {
469
+ if (!sameNode(node, block) && (JS_FUNCTIONS.has(node.type) ||
470
+ node.type === 'class_body')) return false;
471
+ const target = ['assignment_expression', 'augmented_assignment_expression'].includes(node.type)
472
+ ? node.childForFieldName('left')
473
+ : node.type === 'update_expression' ? node.childForFieldName('argument') : null;
474
+ if (target) {
475
+ const names = [];
476
+ jsPatternNames(target, names);
477
+ for (const n of names) note(n, 'binding');
478
+ }
479
+ return true;
480
+ });
481
+ }
482
+ return info;
483
+ }
484
+
485
+ /** Parameters, every `var` of the body and block-nested function declarations. */
486
+ function jsFunctionBindings(fn, name) {
487
+ const info = { bound: false, defRows: [], other: false, import: false, unknown: false };
488
+ const note = (node, kind) => {
489
+ if (!node || node.text !== name) return;
490
+ info.bound = true;
491
+ if (kind === 'def') info.defRows.push(node.startPosition.row);
492
+ else info.other = true;
493
+ };
494
+ const params = fn.childForFieldName('parameters') || fn.childForFieldName('parameter');
495
+ const names = [];
496
+ if (params?.type === 'identifier') names.push(params);
497
+ else jsPatternNames(params, names);
498
+ for (const n of names) note(n, 'binding');
499
+ if (fn.type === 'function_expression' || fn.type === 'function' || fn.type === 'generator_function') {
500
+ const own = fn.childForFieldName('name');
501
+ if (own?.text === name) note(own, 'binding');
502
+ }
503
+ const body = fn.childForFieldName('body');
504
+ if (!body || body.type !== 'statement_block') return info;
505
+ traverseTree(body, node => {
506
+ if (sameNode(node, body)) return true;
507
+ if (JS_FUNCTIONS.has(node.type) || node.type === 'class_body' || node.type === 'class_static_block') {
508
+ // A function declaration nested in a block is also hoisted to the
509
+ // function in sloppy mode (Annex B): the answer is unknown.
510
+ if ((node.type === 'function_declaration' || node.type === 'generator_function_declaration') &&
511
+ !sameNode(node.parent, body) && node.childForFieldName('name')?.text === name) {
512
+ info.unknown = true;
513
+ }
514
+ return false;
515
+ }
516
+ if (node.type === 'variable_declaration') {
517
+ for (const d of namedChildrenOf(node)) {
518
+ if (d.type !== 'variable_declarator') continue;
519
+ const found = [];
520
+ jsPatternNames(d.childForFieldName('name'), found);
521
+ for (const n of found) note(n, 'binding');
522
+ }
523
+ return true;
524
+ }
525
+ if (node.type === 'call_expression') {
526
+ const callee = node.childForFieldName('function');
527
+ if (callee?.type === 'identifier' && callee.text === 'eval') info.unknown = true;
528
+ }
529
+ return true;
530
+ });
531
+ return info;
532
+ }
533
+
534
+ function jsVerdict(info) {
535
+ if (info.import || info.unknown) return 'unknown';
536
+ if (info.other) return { local: true, defRows: null };
537
+ return { local: true, defRows: info.defRows.slice() };
538
+ }
539
+
540
+ function jsReferenceScope(node, name, memo, includeClasses) {
541
+ if (node.type !== 'identifier' && node.type !== 'shorthand_property_identifier') {
542
+ // Keys, member properties and type names are not variable
543
+ // references. Object shorthand `{ name }` is both key and value:
544
+ // its value resolves like an identifier (fix #397; the rename keeps
545
+ // the key).
546
+ return 'none';
547
+ }
548
+ const parent = node.parent;
549
+ if (!parent) return 'unknown';
550
+ if ((parent.type === 'labeled_statement' || parent.type === 'break_statement' ||
551
+ parent.type === 'continue_statement')) return 'none';
552
+ if (parent.type === 'export_specifier' || parent.type === 'import_specifier') return 'unknown';
553
+ // The name of a function or class expression declares it.
554
+ if ((JS_FUNCTIONS.has(parent.type) || parent.type === 'class' ||
555
+ parent.type === 'class_declaration') && sameNode(parent.childForFieldName('name'), node)) return 'none';
556
+ let child = node;
557
+ for (let scope = parent; scope; child = scope, scope = scope.parent) {
558
+ if (scope.type === 'with_statement' && sameNode(scope.childForFieldName('body'), child)) return 'unknown';
559
+ if (JS_FUNCTIONS.has(scope.type)) {
560
+ const body = scope.childForFieldName('body');
561
+ if (sameNode(body, child)) {
562
+ const info = memoGet(memo, scope, name, () => jsFunctionBindings(scope, name));
563
+ if (info.bound || info.unknown) return jsVerdict(info);
564
+ } else {
565
+ // Parameter defaults see the parameters, not the body.
566
+ const params = scope.childForFieldName('parameters') || scope.childForFieldName('parameter');
567
+ const names = [];
568
+ if (params?.type === 'identifier') names.push(params);
569
+ else jsPatternNames(params, names);
570
+ if (names.some(n => n.text === name)) return { local: true, defRows: null };
571
+ }
572
+ continue;
573
+ }
574
+ if (scope.type === 'class_declaration' || scope.type === 'class' ||
575
+ scope.type === 'abstract_class_declaration') {
576
+ // The class name is bound inside its own body.
577
+ const own = scope.childForFieldName('name');
578
+ if (own?.text === name && sameNode(scope.childForFieldName('body'), child)) {
579
+ return { local: true, defRows: includeClasses ? [own.startPosition.row] : null };
580
+ }
581
+ continue;
582
+ }
583
+ if (scope.type === 'catch_clause') {
584
+ const names = [];
585
+ jsPatternNames(scope.childForFieldName('parameter'), names);
586
+ if (names.some(n => n.text === name)) return { local: true, defRows: null };
587
+ continue;
588
+ }
589
+ if (scope.type === 'for_statement' || scope.type === 'for_in_statement') {
590
+ const head = scope.childForFieldName('initializer') || scope.childForFieldName('left');
591
+ const names = [];
592
+ if (head?.type === 'lexical_declaration') {
593
+ for (const d of namedChildrenOf(head)) jsPatternNames(d.childForFieldName('name'), names);
594
+ } else if (scope.type === 'for_in_statement' && head &&
595
+ /^(let|const)$/.test(scope.childForFieldName('kind')?.text || '')) {
596
+ jsPatternNames(head, names);
597
+ }
598
+ if (names.some(n => n.text === name)) return { local: true, defRows: null };
599
+ continue;
600
+ }
601
+ if (scope.type === 'program') return 'module';
602
+ if (JS_BLOCKS.has(scope.type)) {
603
+ const info = memoGet(memo, scope, name, () => jsBlockBindings(scope, name, includeClasses));
604
+ if (info.bound) return jsVerdict(info);
605
+ continue;
606
+ }
607
+ }
608
+ return 'module';
609
+ }
610
+
611
+ // ── Go ──────────────────────────────────────────────────────────────────
612
+
613
+ function goSpecNames(spec, out) {
614
+ for (let i = 0; i < spec.childCount; i++) {
615
+ const child = spec.child(i);
616
+ if (child.type === 'identifier' && spec.fieldNameForChild(i) === 'name') out.push(child);
617
+ }
618
+ }
619
+
620
+ /** Names a Go statement declares in its block. */
621
+ function goStatementNames(statement) {
622
+ const out = [];
623
+ switch (statement.type) {
624
+ case 'short_var_declaration':
625
+ for (const n of namedChildrenOf(statement.childForFieldName('left'))) {
626
+ if (n.type === 'identifier') out.push(n);
627
+ }
628
+ break;
629
+ case 'var_declaration': case 'const_declaration':
630
+ traverseTree(statement, node => {
631
+ if (node.type === 'var_spec' || node.type === 'const_spec') {
632
+ goSpecNames(node, out);
633
+ return false;
634
+ }
635
+ return true;
636
+ });
637
+ break;
638
+ case 'type_declaration':
639
+ traverseTree(statement, node => {
640
+ if (node.type === 'type_spec' || node.type === 'type_alias') {
641
+ const n = node.childForFieldName('name');
642
+ if (n) out.push(n);
643
+ return false;
644
+ }
645
+ return true;
646
+ });
647
+ break;
648
+ case 'receive_statement':
649
+ if (/:=/.test(statement.text.split('<-')[0] || '')) {
650
+ for (const n of namedChildrenOf(statement.childForFieldName('left'))) {
651
+ if (n.type === 'identifier') out.push(n);
652
+ }
653
+ }
654
+ break;
655
+ default:
656
+ break;
657
+ }
658
+ return out;
659
+ }
660
+
661
+ function goParamNames(list, out) {
662
+ for (const decl of namedChildrenOf(list)) {
663
+ if (decl.type !== 'parameter_declaration' && decl.type !== 'variadic_parameter_declaration') continue;
664
+ for (let i = 0; i < decl.childCount; i++) {
665
+ const child = decl.child(i);
666
+ if (child.type === 'identifier' && decl.fieldNameForChild(i) === 'name') out.push(child);
667
+ }
668
+ }
669
+ }
670
+
671
+ function goReferenceScope(node, name) {
672
+ if (node.type !== 'identifier') return 'none';
673
+ const parent = node.parent;
674
+ if (!parent) return 'unknown';
675
+ // `T{field: v}`: a struct field name or a map key, undecidable here.
676
+ if (parent.type === 'literal_element' && parent.parent?.type === 'keyed_element' &&
677
+ sameNode(parent.parent.childForFieldName('key'), parent)) return 'unknown';
678
+ const start = node.startIndex;
679
+ let child = node;
680
+ for (let scope = parent; scope; child = scope, scope = scope.parent) {
681
+ switch (scope.type) {
682
+ case 'block': case 'expression_case': case 'default_case': case 'type_case':
683
+ case 'communication_case': {
684
+ for (const statement of namedChildrenOf(scope)) {
685
+ if (statement.endIndex > start) break;
686
+ if (statement.type === 'communication' || sameNode(statement, child)) continue;
687
+ const names = goStatementNames(statement);
688
+ if (names.some(n => n.text === name)) return { local: true, defRows: null };
689
+ }
690
+ if (scope.type === 'communication_case') {
691
+ const comm = scope.childForFieldName('communication');
692
+ if (comm && comm.endIndex <= start &&
693
+ goStatementNames(comm).some(n => n.text === name)) {
694
+ return { local: true, defRows: null };
695
+ }
696
+ }
697
+ if (scope.type === 'type_case' || scope.type === 'default_case' || scope.type === 'expression_case') {
698
+ const sw = scope.parent;
699
+ if (sw?.type === 'type_switch_statement') {
700
+ const alias = sw.childForFieldName('alias');
701
+ if (namedChildrenOf(alias).some(n => n.text === name)) return { local: true, defRows: null };
702
+ }
703
+ }
704
+ continue;
705
+ }
706
+ case 'if_statement': case 'expression_switch_statement': case 'type_switch_statement': {
707
+ const init = scope.childForFieldName('initializer');
708
+ if (init && init.endIndex <= start) {
709
+ const names = goStatementNames(init);
710
+ if (names.some(n => n.text === name)) return { local: true, defRows: null };
711
+ }
712
+ continue;
713
+ }
714
+ case 'for_statement': {
715
+ const clause = namedChildrenOf(scope).find(c => c.type === 'for_clause' || c.type === 'range_clause');
716
+ if (clause?.type === 'for_clause') {
717
+ const init = clause.childForFieldName('initializer');
718
+ if (init && init.endIndex <= start) {
719
+ const names = goStatementNames(init);
720
+ if (names.some(n => n.text === name)) return { local: true, defRows: null };
721
+ }
722
+ } else if (clause?.type === 'range_clause' && clause.text.includes(':=') &&
723
+ sameNode(scope.childForFieldName('body'), child)) {
724
+ if (namedChildrenOf(clause.childForFieldName('left')).some(n => n.text === name)) {
725
+ return { local: true, defRows: null };
726
+ }
727
+ }
728
+ continue;
729
+ }
730
+ case 'function_declaration': case 'method_declaration': case 'func_literal': {
731
+ if (!sameNode(scope.childForFieldName('body'), child)) continue;
732
+ const names = [];
733
+ goParamNames(scope.childForFieldName('parameters'), names);
734
+ const result = scope.childForFieldName('result');
735
+ if (result?.type === 'parameter_list') goParamNames(result, names);
736
+ if (scope.type === 'method_declaration') goParamNames(scope.childForFieldName('receiver'), names);
737
+ if (names.some(n => n.text === name)) return { local: true, defRows: null };
738
+ continue;
739
+ }
740
+ case 'source_file':
741
+ return 'module';
742
+ default:
743
+ continue;
744
+ }
745
+ }
746
+ return 'module';
747
+ }
748
+
749
+ // ── Rust ────────────────────────────────────────────────────────────────
750
+
751
+ function rustPatternNames(pattern, out) {
752
+ if (!pattern) return;
753
+ traverseTree(pattern, node => {
754
+ // `S { a, mut b }` binds a and b (shorthand field patterns).
755
+ if (node.type === 'shorthand_field_identifier') {
756
+ out.push(node);
757
+ return false;
758
+ }
759
+ if (node.type === 'identifier') {
760
+ const parent = node.parent;
761
+ // `Some(x)`: the path is a type/variant, not a binding.
762
+ if (parent && (parent.type === 'tuple_struct_pattern' || parent.type === 'struct_pattern') &&
763
+ sameNode(parent.childForFieldName('type'), node)) return false;
764
+ if (parent?.type === 'scoped_identifier') return false;
765
+ // `S { field: pat }`: the field name is not a binding.
766
+ if (parent?.type === 'field_pattern' && parent.childForFieldName('pattern') &&
767
+ sameNode(parent.childForFieldName('name'), node)) return false;
768
+ out.push(node);
769
+ return false;
770
+ }
771
+ if (node.type === 'scoped_identifier' || node.type === 'type_identifier' ||
772
+ node.type === 'generic_type') return false;
773
+ return true;
774
+ });
775
+ }
776
+
777
+ const RUST_VALUE_ITEMS = new Set(['function_item', 'const_item', 'static_item', 'struct_item',
778
+ 'enum_item', 'union_item', 'mod_item', 'extern_crate_declaration', 'macro_definition',
779
+ 'function_signature_item']);
780
+
781
+ function rustItemBindings(container, name) {
782
+ const info = { bound: false, defRows: [], other: false, use: false, superGlob: false,
783
+ otherGlob: false, superNamed: false };
784
+ for (const item of namedChildrenOf(container)) {
785
+ if (RUST_VALUE_ITEMS.has(item.type)) {
786
+ const n = item.childForFieldName('name');
787
+ if (n?.text === name) {
788
+ info.bound = true;
789
+ if (item.type === 'function_item') info.defRows.push(n.startPosition.row);
790
+ else info.other = true;
791
+ }
792
+ continue;
793
+ }
794
+ if (item.type === 'use_declaration') {
795
+ const arg = item.childForFieldName('argument');
796
+ if (!arg) continue;
797
+ if (arg.type === 'use_wildcard') {
798
+ const path = arg.namedChild(0);
799
+ if (path?.type === 'super' && arg.namedChildCount === 1) info.superGlob = true;
800
+ else info.otherGlob = true;
801
+ continue;
802
+ }
803
+ let hit = false;
804
+ traverseTree(arg, inner => {
805
+ if (inner.type === 'identifier' && inner.text === name) hit = true;
806
+ return !hit;
807
+ });
808
+ if (hit) {
809
+ info.bound = true;
810
+ // `use super::name;` re-binds the parent's item under its own name.
811
+ if (arg.type === 'scoped_identifier' && arg.childForFieldName('path')?.type === 'super' &&
812
+ arg.childForFieldName('name')?.text === name) info.superNamed = true;
813
+ else info.use = true;
814
+ }
815
+ }
816
+ }
817
+ return info;
818
+ }
819
+
820
+ // ── C ───────────────────────────────────────────────────────────────────
821
+
822
+ const C_DECLARATOR_WRAPPERS = new Set(['init_declarator', 'pointer_declarator', 'array_declarator',
823
+ 'parenthesized_declarator', 'attributed_declarator']);
824
+
825
+ /** The identifier a C declarator declares, and whether it declares a function. */
826
+ function cDeclaratorName(declarator) {
827
+ let current = declarator;
828
+ let isFunction = false;
829
+ for (let depth = 0; current && depth < 16; depth++) {
830
+ if (current.type === 'identifier') return { node: current, isFunction };
831
+ if (current.type === 'function_declarator') isFunction = true;
832
+ else if (!C_DECLARATOR_WRAPPERS.has(current.type)) return null;
833
+ current = current.childForFieldName('declarator') ||
834
+ namedChildrenOf(current).find(child => child.type === 'identifier' ||
835
+ child.type.endsWith('_declarator'));
836
+ }
837
+ return null;
838
+ }
839
+
840
+ /** 'local' / 'function' / null: how a declaration binds `name`. */
841
+ function cDeclarationBinds(statement, name) {
842
+ if (statement.type !== 'declaration' && statement.type !== 'type_definition') return null;
843
+ let verdict = null;
844
+ for (const child of namedChildrenOf(statement)) {
845
+ if (!child.type.endsWith('declarator') && child.type !== 'identifier' &&
846
+ child.type !== 'type_identifier') continue;
847
+ if (sameNode(child, statement.childForFieldName('type'))) continue;
848
+ const declared = child.type === 'type_identifier' ? { node: child, isFunction: false }
849
+ : cDeclaratorName(child);
850
+ if (!declared || declared.node.text !== name) continue;
851
+ if (declared.isFunction) return 'function';
852
+ verdict = 'local';
853
+ }
854
+ return verdict;
855
+ }
856
+
857
+ function cReferenceScope(node, name) {
858
+ if (node.type !== 'identifier') return 'none';
859
+ const parent = node.parent;
860
+ if (!parent) return 'unknown';
861
+ // The declared name of a variable or parameter is a binding, never a
862
+ // reference to a function of the same spelling.
863
+ for (let up = parent, child = node; up && C_DECLARATOR_WRAPPERS.has(up.type); child = up, up = up.parent) {
864
+ if (up.type === 'init_declarator' && !sameNode(up.childForFieldName('declarator'), child)) break;
865
+ const owner = up.parent;
866
+ if (owner && (owner.type === 'declaration' || owner.type === 'parameter_declaration' ||
867
+ owner.type === 'field_declaration')) return 'none';
868
+ }
869
+ if (parent.type === 'declaration' || parent.type === 'parameter_declaration') {
870
+ if (sameNode(parent.childForFieldName('declarator'), node)) return 'none';
871
+ }
872
+ const start = node.startIndex;
873
+ let child = node;
874
+ for (let scope = parent; scope; child = scope, scope = scope.parent) {
875
+ switch (scope.type) {
876
+ case 'ERROR':
877
+ return 'unknown';
878
+ case 'compound_statement': {
879
+ let bound = null;
880
+ for (const statement of namedChildrenOf(scope)) {
881
+ if (statement.startIndex >= start) break;
882
+ const verdict = cDeclarationBinds(statement, name);
883
+ if (verdict) bound = verdict;
884
+ }
885
+ if (bound === 'function') return 'unknown';
886
+ if (bound) return { local: true, defRows: null };
887
+ continue;
888
+ }
889
+ case 'for_statement': {
890
+ const init = scope.childForFieldName('initializer');
891
+ if (init && init.endIndex <= start) {
892
+ const verdict = cDeclarationBinds(init, name);
893
+ if (verdict === 'function') return 'unknown';
894
+ if (verdict) return { local: true, defRows: null };
895
+ }
896
+ continue;
897
+ }
898
+ case 'function_definition': {
899
+ if (!sameNode(scope.childForFieldName('body'), child)) continue;
900
+ const declarator = cDeclaratorOfFunction(scope);
901
+ const params = declarator?.childForFieldName('parameters');
902
+ for (const param of namedChildrenOf(params)) {
903
+ const declared = param.childForFieldName('declarator');
904
+ const identity = declared ? cDeclaratorName(declared) : null;
905
+ if (identity?.node.text === name) return { local: true, defRows: null };
906
+ }
907
+ continue;
908
+ }
909
+ case 'translation_unit':
910
+ return 'module';
911
+ default:
912
+ continue;
913
+ }
914
+ }
915
+ return 'unknown';
916
+ }
917
+
918
+ function cDeclaratorOfFunction(definition) {
919
+ let current = definition.childForFieldName('declarator');
920
+ for (let depth = 0; current && depth < 16; depth++) {
921
+ if (current.type === 'function_declarator') return current;
922
+ current = current.childForFieldName('declarator');
923
+ }
924
+ return null;
925
+ }
926
+
927
+ function rustReferenceScope(node, name, memo) {
928
+ if (node.type !== 'identifier') return 'none';
929
+ const parent = node.parent;
930
+ if (!parent) return 'unknown';
931
+ // A field init shorthand `S { name }` is the field key and a reference
932
+ // to the binding: its value resolves like any identifier (fix #397; a
933
+ // rename keeps the key).
934
+ // A path segment (`name::X`) names a module or type.
935
+ if ((parent.type === 'scoped_identifier' || parent.type === 'scoped_type_identifier') &&
936
+ !sameNode(parent.childForFieldName('name'), node)) return 'none';
937
+ if (parent.type === 'field_initializer' && sameNode(parent.childForFieldName('field'), node)) return 'none';
938
+ const start = node.startIndex;
939
+ let child = node;
940
+ for (let scope = parent; scope; child = scope, scope = scope.parent) {
941
+ switch (scope.type) {
942
+ case 'token_tree':
943
+ // A macro decides what its tokens mean.
944
+ return 'unknown';
945
+ case 'block': {
946
+ const items = memoGet(memo, scope, name, () => rustItemBindings(scope, name));
947
+ if (items.use || items.superNamed) return 'unknown';
948
+ // `let` bindings visible here: statements that end before us.
949
+ for (const statement of namedChildrenOf(scope)) {
950
+ if (statement.startIndex >= start) break;
951
+ if (statement.type !== 'let_declaration' || statement.endIndex > start) continue;
952
+ const names = [];
953
+ rustPatternNames(statement.childForFieldName('pattern'), names);
954
+ if (names.some(n => n.text === name)) return { local: true, defRows: null };
955
+ }
956
+ if (items.bound) {
957
+ return items.other ? { local: true, defRows: null } : { local: true, defRows: items.defRows.slice() };
958
+ }
959
+ if (items.otherGlob) return 'unknown';
960
+ continue;
961
+ }
962
+ case 'closure_expression': {
963
+ if (!sameNode(scope.childForFieldName('body'), child)) continue;
964
+ const names = [];
965
+ for (const p of namedChildrenOf(scope.childForFieldName('parameters'))) {
966
+ rustPatternNames(p.type === 'parameter' ? p.childForFieldName('pattern') : p, names);
967
+ }
968
+ if (names.some(n => n.text === name)) return { local: true, defRows: null };
969
+ continue;
970
+ }
971
+ case 'function_item': {
972
+ if (!sameNode(scope.childForFieldName('body'), child)) continue;
973
+ const names = [];
974
+ for (const p of namedChildrenOf(scope.childForFieldName('parameters'))) {
975
+ if (p.type === 'parameter') rustPatternNames(p.childForFieldName('pattern'), names);
976
+ }
977
+ if (names.some(n => n.text === name)) return { local: true, defRows: null };
978
+ continue;
979
+ }
980
+ case 'match_arm': {
981
+ if (sameNode(scope.childForFieldName('pattern'), child)) continue;
982
+ const names = [];
983
+ rustPatternNames(scope.childForFieldName('pattern'), names);
984
+ if (names.some(n => n.text === name)) return { local: true, defRows: null };
985
+ continue;
986
+ }
987
+ case 'if_expression': case 'while_expression': {
988
+ if (!sameNode(scope.childForFieldName('consequence') || scope.childForFieldName('body'), child)) continue;
989
+ const names = [];
990
+ const condition = scope.childForFieldName('condition');
991
+ if (condition) traverseTree(condition, inner => {
992
+ if (inner.type === 'let_condition') {
993
+ rustPatternNames(inner.childForFieldName('pattern'), names);
994
+ return false;
995
+ }
996
+ return inner.type === 'let_chain';
997
+ });
998
+ if (names.some(n => n.text === name)) return { local: true, defRows: null };
999
+ continue;
1000
+ }
1001
+ case 'for_expression': {
1002
+ if (!sameNode(scope.childForFieldName('body'), child)) continue;
1003
+ const names = [];
1004
+ rustPatternNames(scope.childForFieldName('pattern'), names);
1005
+ if (names.some(n => n.text === name)) return { local: true, defRows: null };
1006
+ continue;
1007
+ }
1008
+ case 'mod_item': {
1009
+ const body = scope.childForFieldName('body');
1010
+ if (!sameNode(body, child)) continue;
1011
+ const items = memoGet(memo, body, name, () => rustItemBindings(body, name));
1012
+ if (items.use || items.otherGlob) return 'unknown';
1013
+ if (items.bound && !items.superNamed) {
1014
+ return items.other ? { local: true, defRows: null } : { local: true, defRows: items.defRows.slice() };
1015
+ }
1016
+ // Only a parent re-exported into this module reaches the file scope.
1017
+ if (items.superNamed || items.superGlob) continue;
1018
+ return 'unknown';
1019
+ }
1020
+ case 'source_file':
1021
+ return 'module';
1022
+ default:
1023
+ continue;
1024
+ }
1025
+ }
1026
+ return 'module';
1027
+ }
1028
+
1029
+ /**
1030
+ * The `let` statement a Rust identifier reference binds to (fix #392), or
1031
+ * null when the nearest binding is anything else (a parameter, a closure or
1032
+ * match-arm pattern, an item) or none. Shadowing: the last `let` before the
1033
+ * reference in the innermost block that binds the name wins.
1034
+ */
1035
+ function rustLetBindingOf(node) {
1036
+ if (!node || node.type !== 'identifier') return null;
1037
+ const name = node.text;
1038
+ const start = node.startIndex;
1039
+ for (let scope = node.parent; scope; scope = scope.parent) {
1040
+ switch (scope.type) {
1041
+ case 'token_tree':
1042
+ return null;
1043
+ case 'block': {
1044
+ let found = null;
1045
+ for (const statement of namedChildrenOf(scope)) {
1046
+ if (statement.startIndex >= start) break;
1047
+ if (statement.type === 'let_declaration' && statement.endIndex <= start) {
1048
+ const names = [];
1049
+ rustPatternNames(statement.childForFieldName('pattern'), names);
1050
+ if (names.some(n => n.text === name)) found = statement;
1051
+ } else if (RUST_VALUE_ITEMS.has(statement.type) &&
1052
+ statement.childForFieldName('name')?.text === name) {
1053
+ return null;
1054
+ }
1055
+ }
1056
+ if (found) return found;
1057
+ continue;
1058
+ }
1059
+ case 'closure_expression': case 'function_item': case 'match_arm':
1060
+ case 'if_expression': case 'while_expression': case 'for_expression': {
1061
+ const verdict = rustReferenceScope(node, name, new Map());
1062
+ // Only a `let` of an enclosing block may bind it past here.
1063
+ if (typeof verdict === 'object' && scope.type !== 'function_item') {
1064
+ const inner = [];
1065
+ const pattern = scope.type === 'match_arm' ? scope.childForFieldName('pattern')
1066
+ : scope.type === 'for_expression' ? scope.childForFieldName('pattern') : null;
1067
+ if (pattern) rustPatternNames(pattern, inner);
1068
+ if (inner.some(n => n.text === name)) return null;
1069
+ if (scope.type === 'closure_expression') {
1070
+ for (const p of namedChildrenOf(scope.childForFieldName('parameters'))) {
1071
+ rustPatternNames(p.type === 'parameter' ? p.childForFieldName('pattern') : p, inner);
1072
+ }
1073
+ if (inner.some(n => n.text === name)) return null;
1074
+ }
1075
+ if (scope.type === 'if_expression' || scope.type === 'while_expression') {
1076
+ const condition = scope.childForFieldName('condition');
1077
+ if (condition) traverseTree(condition, n => {
1078
+ if (n.type === 'let_condition') {
1079
+ rustPatternNames(n.childForFieldName('pattern'), inner);
1080
+ return false;
1081
+ }
1082
+ return true;
1083
+ });
1084
+ if (inner.some(n => n.text === name)) return null;
1085
+ }
1086
+ }
1087
+ if (scope.type === 'function_item') {
1088
+ for (const p of namedChildrenOf(scope.childForFieldName('parameters'))) {
1089
+ const names = [];
1090
+ if (p.type === 'parameter') rustPatternNames(p.childForFieldName('pattern'), names);
1091
+ if (names.some(n => n.text === name)) return null;
1092
+ }
1093
+ return null;
1094
+ }
1095
+ continue;
1096
+ }
1097
+ default:
1098
+ continue;
1099
+ }
1100
+ }
1101
+ return null;
1102
+ }
1103
+
1104
+ /**
1105
+ * The struct field a Rust local is destructured from (fix #392): `let Self
1106
+ * { a, mut b, c: x } = self;` binds `a`/`b`/`x` to fields `a`/`b`/`c` of
1107
+ * `self`. Returns the field name, or null.
1108
+ */
1109
+ const selfDestructuringMemo = new WeakMap();
1110
+
1111
+ /** Names bound by `let <struct pattern> = self` in a fn body (memoized). */
1112
+ function selfDestructuredNames(fnNode) {
1113
+ const tree = fnNode.tree;
1114
+ let perTree = tree ? selfDestructuringMemo.get(tree) : null;
1115
+ if (tree && !perTree) {
1116
+ perTree = new Map();
1117
+ selfDestructuringMemo.set(tree, perTree);
1118
+ }
1119
+ const key = `${fnNode.startIndex}:${fnNode.endIndex}`;
1120
+ if (perTree?.has(key)) return perTree.get(key);
1121
+ const names = new Set();
1122
+ const body = fnNode.childForFieldName('body');
1123
+ if (body) {
1124
+ for (const statement of body.descendantsOfType('let_declaration')) {
1125
+ if (statement.childForFieldName('pattern')?.type !== 'struct_pattern') continue;
1126
+ const found = [];
1127
+ rustPatternNames(statement.childForFieldName('pattern'), found);
1128
+ for (const n of found) names.add(n.text);
1129
+ }
1130
+ }
1131
+ perTree?.set(key, names);
1132
+ return names;
1133
+ }
1134
+
1135
+ function rustSelfFieldBinding(identNode) {
1136
+ let fnNode = identNode.parent;
1137
+ while (fnNode && fnNode.type !== 'function_item') fnNode = fnNode.parent;
1138
+ if (!fnNode || !selfDestructuredNames(fnNode).has(identNode.text)) return null;
1139
+ const statement = rustLetBindingOf(identNode);
1140
+ if (!statement) return null;
1141
+ let value = statement.childForFieldName('value');
1142
+ while (value && (value.type === 'reference_expression' || value.type === 'unary_expression' ||
1143
+ value.type === 'parenthesized_expression')) value = value.namedChild(value.namedChildCount - 1);
1144
+ if (value?.type !== 'self') return null;
1145
+ const pattern = statement.childForFieldName('pattern');
1146
+ if (pattern?.type !== 'struct_pattern') return null;
1147
+ const typeNode = pattern.childForFieldName('type');
1148
+ if (!typeNode || (typeNode.type !== 'type_identifier' && typeNode.text !== 'Self')) return null;
1149
+ const name = identNode.text;
1150
+ for (const field of namedChildrenOf(pattern)) {
1151
+ if (field.type !== 'field_pattern') continue;
1152
+ const fieldName = field.childForFieldName('name');
1153
+ const sub = field.childForFieldName('pattern');
1154
+ if (!sub) {
1155
+ // Shorthand `a` / `mut a` / `ref a`: the binding is the field name.
1156
+ if (fieldName?.text === name) return name;
1157
+ continue;
1158
+ }
1159
+ const bound = sub.type === 'identifier' ? sub
1160
+ : sub.type === 'mut_pattern' ? sub.namedChild(0) : null;
1161
+ if (bound?.type === 'identifier' && bound.text === name && fieldName) return fieldName.text;
1162
+ }
1163
+ return null;
1164
+ }
1165
+
1166
+ /**
1167
+ * Where a bare name reference resolves (see the file header).
1168
+ * `memo` is a Map shared by the references of one name in one tree.
1169
+ * An explicit `name` asks about a binding in a container node's scope,
1170
+ * for example the root of a qualified type in a Python string annotation.
1171
+ * `includeClasses` exposes class declaration rows for constructor lookup;
1172
+ * ordinary references retain their function-only definition rows.
1173
+ */
1174
+ function referenceScope(node, language, memo = new Map(), name = node?.text, options = {}) {
1175
+ const family = familyOf(language);
1176
+ if (!family || !node) return null;
1177
+ try {
1178
+ const includeClasses = options.includeClasses === true;
1179
+ if (includeClasses) {
1180
+ let classMemo = memo.get('class-definitions');
1181
+ if (!classMemo) { classMemo = new Map(); memo.set('class-definitions', classMemo); }
1182
+ memo = classMemo;
1183
+ }
1184
+ switch (family) {
1185
+ case 'python': return pythonReferenceScope(node, name, memo, includeClasses);
1186
+ case 'js': return jsReferenceScope(node, name, memo, includeClasses);
1187
+ case 'go': return goReferenceScope(node, name, memo);
1188
+ case 'rust': return rustReferenceScope(node, name, memo);
1189
+ case 'c': return cReferenceScope(node, name);
1190
+ default: return null;
1191
+ }
1192
+ } catch {
1193
+ return 'unknown';
1194
+ }
1195
+ }
1196
+
1197
+ /** Usage-record fields for a scope verdict. */
1198
+ function scopeFields(verdict) {
1199
+ if (!verdict) return null;
1200
+ if (typeof verdict === 'string') return { scopeBinding: verdict };
1201
+ return {
1202
+ scopeBinding: 'local',
1203
+ ...(Array.isArray(verdict.defRows) && { scopeDefLines: verdict.defRows.map(r => r + 1) }),
1204
+ };
1205
+ }
1206
+
1207
+ /**
1208
+ * The Python scope node whose binding a reference to `name` resolves to
1209
+ * (fix #402): the function, lambda or comprehension that binds it, or the
1210
+ * module; null when that is not decidable (star imports, `global`/`nonlocal`
1211
+ * declarations, a class body that binds it too, non-reference positions).
1212
+ */
1213
+ function referenceScopeNode(node, name, memo = new Map()) {
1214
+ const parent = node?.parent;
1215
+ if (!parent) return null;
1216
+ if (parent.type === 'keyword_argument' && sameNode(parent.childForFieldName('name'), node)) return null;
1217
+ if (parent.type === 'attribute' && sameNode(parent.childForFieldName('attribute'), node)) return null;
1218
+ if (parent.type === 'keyword_pattern' && sameNode(parent.namedChild(0), node)) return null;
1219
+ let child = node;
1220
+ let crossedFunction = false;
1221
+ try {
1222
+ for (let scope = parent; scope; child = scope, scope = scope.parent) {
1223
+ switch (scope.type) {
1224
+ case 'function_definition': {
1225
+ const body = scope.childForFieldName('body');
1226
+ if (!sameNode(body, child)) continue;
1227
+ const info = memoGet(memo, scope, name, () => pyScopeBindings(scope, body, name, true));
1228
+ if (info.starImport || info.global || info.nonlocal) return null;
1229
+ if (info.bound) return scope;
1230
+ crossedFunction = true;
1231
+ continue;
1232
+ }
1233
+ case 'lambda': {
1234
+ if (!sameNode(scope.childForFieldName('body'), child)) continue;
1235
+ const names = [];
1236
+ pyParameterNames(scope.childForFieldName('parameters'), names);
1237
+ if (names.some(n => n.text === name)) return scope;
1238
+ crossedFunction = true;
1239
+ continue;
1240
+ }
1241
+ case 'class_definition': {
1242
+ const body = scope.childForFieldName('body');
1243
+ if (!sameNode(body, child) || crossedFunction) continue;
1244
+ const info = memoGet(memo, scope, name, () => pyScopeBindings(scope, body, name, false));
1245
+ if (info.bound || info.starImport) return null;
1246
+ continue;
1247
+ }
1248
+ case 'list_comprehension': case 'set_comprehension':
1249
+ case 'dictionary_comprehension': case 'generator_expression': {
1250
+ const clauses = namedChildrenOf(scope).filter(c => c.type === 'for_in_clause');
1251
+ if (within(node, clauses[0]?.childForFieldName('right'))) continue;
1252
+ for (const clause of clauses) {
1253
+ const names = [];
1254
+ pyTargetNames(clause.childForFieldName('left'), names);
1255
+ if (names.some(n => n.text === name)) return scope;
1256
+ }
1257
+ crossedFunction = true;
1258
+ continue;
1259
+ }
1260
+ case 'module':
1261
+ return scope;
1262
+ default:
1263
+ continue;
1264
+ }
1265
+ }
1266
+ } catch {
1267
+ return null;
1268
+ }
1269
+ return null;
1270
+ }
1271
+
1272
+ /** Declaration tokens binding one name in a Python function's own scope. */
1273
+ function pythonBindingSites(scope, name) {
1274
+ const sites = [];
1275
+ pyScopeBindings(scope, scope.childForFieldName('body'), name, true, false,
1276
+ node => sites.push(node));
1277
+ return sites;
1278
+ }
1279
+
1280
+ module.exports = { referenceScope, referenceScopeNode, scopeFields, familyOf, rustLetBindingOf, rustSelfFieldBinding, pythonBindingSites };