gitnexus 1.6.3 → 1.6.4-rc.10

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 (98) hide show
  1. package/dist/_shared/index.d.ts +1 -1
  2. package/dist/_shared/index.d.ts.map +1 -1
  3. package/dist/_shared/index.js +1 -1
  4. package/dist/_shared/index.js.map +1 -1
  5. package/dist/_shared/scope-resolution/finalize-algorithm.d.ts +22 -14
  6. package/dist/_shared/scope-resolution/finalize-algorithm.d.ts.map +1 -1
  7. package/dist/_shared/scope-resolution/finalize-algorithm.js +298 -37
  8. package/dist/_shared/scope-resolution/finalize-algorithm.js.map +1 -1
  9. package/dist/_shared/scope-resolution/scope-tree.d.ts +23 -1
  10. package/dist/_shared/scope-resolution/scope-tree.d.ts.map +1 -1
  11. package/dist/_shared/scope-resolution/scope-tree.js +36 -2
  12. package/dist/_shared/scope-resolution/scope-tree.js.map +1 -1
  13. package/dist/_shared/scope-resolution/types.d.ts +47 -3
  14. package/dist/_shared/scope-resolution/types.d.ts.map +1 -1
  15. package/dist/_shared/scope-resolution/types.js +10 -2
  16. package/dist/_shared/scope-resolution/types.js.map +1 -1
  17. package/dist/core/embeddings/embedder.js +2 -1
  18. package/dist/core/ingestion/call-processor.js +2 -2
  19. package/dist/core/ingestion/constants.d.ts +4 -3
  20. package/dist/core/ingestion/constants.js +8 -3
  21. package/dist/core/ingestion/finalize-orchestrator.js +6 -3
  22. package/dist/core/ingestion/heritage-processor.js +2 -2
  23. package/dist/core/ingestion/import-processor.js +1 -1
  24. package/dist/core/ingestion/languages/csharp/captures.js +4 -1
  25. package/dist/core/ingestion/languages/csharp/namespace-siblings.d.ts +14 -13
  26. package/dist/core/ingestion/languages/csharp/namespace-siblings.js +62 -50
  27. package/dist/core/ingestion/languages/python/captures.js +9 -1
  28. package/dist/core/ingestion/languages/python/index.d.ts +1 -1
  29. package/dist/core/ingestion/languages/python/index.js +1 -1
  30. package/dist/core/ingestion/languages/python/simple-hooks.d.ts +3 -1
  31. package/dist/core/ingestion/languages/python/simple-hooks.js +8 -0
  32. package/dist/core/ingestion/languages/python.js +2 -1
  33. package/dist/core/ingestion/languages/typescript/arity-metadata.d.ts +59 -0
  34. package/dist/core/ingestion/languages/typescript/arity-metadata.js +103 -0
  35. package/dist/core/ingestion/languages/typescript/arity.d.ts +37 -0
  36. package/dist/core/ingestion/languages/typescript/arity.js +54 -0
  37. package/dist/core/ingestion/languages/typescript/cache-stats.d.ts +17 -0
  38. package/dist/core/ingestion/languages/typescript/cache-stats.js +28 -0
  39. package/dist/core/ingestion/languages/typescript/captures.d.ts +28 -0
  40. package/dist/core/ingestion/languages/typescript/captures.js +451 -0
  41. package/dist/core/ingestion/languages/typescript/import-decomposer.d.ts +49 -0
  42. package/dist/core/ingestion/languages/typescript/import-decomposer.js +371 -0
  43. package/dist/core/ingestion/languages/typescript/import-target.d.ts +50 -0
  44. package/dist/core/ingestion/languages/typescript/import-target.js +61 -0
  45. package/dist/core/ingestion/languages/typescript/index.d.ts +94 -0
  46. package/dist/core/ingestion/languages/typescript/index.js +94 -0
  47. package/dist/core/ingestion/languages/typescript/interpret.d.ts +35 -0
  48. package/dist/core/ingestion/languages/typescript/interpret.js +317 -0
  49. package/dist/core/ingestion/languages/typescript/merge-bindings.d.ts +62 -0
  50. package/dist/core/ingestion/languages/typescript/merge-bindings.js +158 -0
  51. package/dist/core/ingestion/languages/typescript/query.d.ts +77 -0
  52. package/dist/core/ingestion/languages/typescript/query.js +778 -0
  53. package/dist/core/ingestion/languages/typescript/receiver-binding.d.ts +59 -0
  54. package/dist/core/ingestion/languages/typescript/receiver-binding.js +171 -0
  55. package/dist/core/ingestion/languages/typescript/scope-resolver.d.ts +16 -0
  56. package/dist/core/ingestion/languages/typescript/scope-resolver.js +113 -0
  57. package/dist/core/ingestion/languages/typescript/simple-hooks.d.ts +71 -0
  58. package/dist/core/ingestion/languages/typescript/simple-hooks.js +131 -0
  59. package/dist/core/ingestion/languages/typescript.js +19 -0
  60. package/dist/core/ingestion/model/scope-resolution-indexes.d.ts +14 -1
  61. package/dist/core/ingestion/parsing-processor.js +3 -3
  62. package/dist/core/ingestion/registry-primary-flag.d.ts +3 -1
  63. package/dist/core/ingestion/registry-primary-flag.js +4 -1
  64. package/dist/core/ingestion/scope-extractor-bridge.d.ts +5 -2
  65. package/dist/core/ingestion/scope-extractor-bridge.js +7 -2
  66. package/dist/core/ingestion/scope-extractor.js +19 -18
  67. package/dist/core/ingestion/scope-resolution/contract/scope-resolver.d.ts +73 -11
  68. package/dist/core/ingestion/scope-resolution/contract/scope-resolver.js +48 -10
  69. package/dist/core/ingestion/scope-resolution/passes/compound-receiver.js +283 -14
  70. package/dist/core/ingestion/scope-resolution/passes/imported-return-types.d.ts +23 -2
  71. package/dist/core/ingestion/scope-resolution/passes/imported-return-types.js +109 -37
  72. package/dist/core/ingestion/scope-resolution/passes/mro.js +3 -1
  73. package/dist/core/ingestion/scope-resolution/passes/receiver-bound-calls.js +13 -5
  74. package/dist/core/ingestion/scope-resolution/pipeline/phase.js +11 -2
  75. package/dist/core/ingestion/scope-resolution/pipeline/registry.js +2 -0
  76. package/dist/core/ingestion/scope-resolution/pipeline/run.d.ts +8 -0
  77. package/dist/core/ingestion/scope-resolution/pipeline/run.js +21 -5
  78. package/dist/core/ingestion/scope-resolution/pipeline/validate-bindings-immutability.d.ts +39 -0
  79. package/dist/core/ingestion/scope-resolution/pipeline/validate-bindings-immutability.js +65 -0
  80. package/dist/core/ingestion/scope-resolution/scope/walkers.d.ts +54 -11
  81. package/dist/core/ingestion/scope-resolution/scope/walkers.js +105 -30
  82. package/dist/core/ingestion/utils/ast-helpers.d.ts +2 -0
  83. package/dist/core/ingestion/utils/ast-helpers.js +12 -0
  84. package/dist/core/ingestion/utils/env.d.ts +10 -0
  85. package/dist/core/ingestion/utils/env.js +14 -0
  86. package/dist/core/ingestion/workers/parse-worker.js +3 -3
  87. package/dist/core/lbug/lbug-adapter.d.ts +3 -4
  88. package/dist/core/lbug/lbug-adapter.js +6 -9
  89. package/dist/core/run-analyze.js +4 -6
  90. package/dist/core/search/bm25-index.d.ts +0 -17
  91. package/dist/core/search/bm25-index.js +10 -118
  92. package/dist/core/search/fts-indexes.d.ts +1 -0
  93. package/dist/core/search/fts-indexes.js +7 -0
  94. package/dist/core/search/fts-schema.d.ts +6 -0
  95. package/dist/core/search/fts-schema.js +7 -0
  96. package/dist/mcp/core/embedder.js +3 -1
  97. package/package.json +1 -1
  98. package/skills/gitnexus-cli.md +1 -1
@@ -0,0 +1,451 @@
1
+ /**
2
+ * `emitScopeCaptures` for TypeScript.
3
+ *
4
+ * Drives the TypeScript scope query against tree-sitter-typescript and groups
5
+ * raw matches into `CaptureMatch[]` for the central extractor. Layers
6
+ * synthesized streams on top:
7
+ *
8
+ * 1. **Import decomposition** — each `import_statement` / re-export is
9
+ * re-emitted with `@import.kind/source/name/alias/typeOnly` markers so
10
+ * `interpretTsImport` can recover the `ParsedImport` shape without
11
+ * re-parsing raw text (see `import-decomposer.ts`). Unit 2 adds this;
12
+ * until then, raw `@import.statement` matches flow through as-is.
13
+ * 2. **Dynamic imports** — `import('./m')` is re-emitted as a
14
+ * decomposed `@import.statement` with `@import.kind=dynamic` so the
15
+ * central extractor treats it uniformly with static imports.
16
+ * 3. **Function-decl arity metadata** (Unit 5) — `@declaration.parameter-count`
17
+ * / `@declaration.required-parameter-count` / `@declaration.parameter-types`
18
+ * synthesized onto function-like declarations so the registry can narrow
19
+ * overloads.
20
+ * 4. **Callsite arity metadata** (Unit 5) — `@reference.arity` /
21
+ * `@reference.parameter-types` on every callsite.
22
+ * 5. **Receiver-binding synthesis** (Unit 3) — `this` type anchors on
23
+ * instance methods, with arrow-function lexical-this walk-up.
24
+ *
25
+ * Pure given the input source text. No I/O, no globals consulted.
26
+ */
27
+ import { findNodeAtRange, nodeToCapture, syntheticCapture, } from '../../utils/ast-helpers.js';
28
+ import { splitImportStatement } from './import-decomposer.js';
29
+ import { getTsParser, getTsScopeQuery, tsCachedTreeMatchesGrammar } from './query.js';
30
+ import { recordCacheHit, recordCacheMiss } from './cache-stats.js';
31
+ import { synthesizeTsReceiverBinding } from './receiver-binding.js';
32
+ import { computeTsArityMetadata } from './arity-metadata.js';
33
+ import { getTreeSitterBufferSize } from '../../constants.js';
34
+ /** tree-sitter-typescript node types for function-like scopes that may
35
+ * carry a synthesized `this` binding. Kept in sync with the
36
+ * `@scope.function` patterns in `query.ts`. */
37
+ const FUNCTION_NODE_TYPES = [
38
+ 'method_definition',
39
+ 'method_signature',
40
+ 'abstract_method_signature',
41
+ 'arrow_function',
42
+ 'function_expression',
43
+ 'function_declaration',
44
+ 'generator_function_declaration',
45
+ 'function_signature',
46
+ ];
47
+ /** Declaration anchors that carry function-like arity metadata. */
48
+ const FUNCTION_DECL_TAGS = ['@declaration.method', '@declaration.function'];
49
+ /** Callsite anchors that should carry `@reference.arity` + param types. */
50
+ const CALL_TAGS = [
51
+ '@reference.call.free',
52
+ '@reference.call.member',
53
+ '@reference.call.constructor',
54
+ ];
55
+ function pickFirstDefined(grouped, tags) {
56
+ for (const tag of tags) {
57
+ const cap = grouped[tag];
58
+ if (cap !== undefined)
59
+ return cap;
60
+ }
61
+ return undefined;
62
+ }
63
+ /**
64
+ * Drop `@reference.read.member` matches whose underlying `member_expression`
65
+ * is NOT actually a read context:
66
+ *
67
+ * 1. The member_expression is the `function:` of a `call_expression`
68
+ * (it's a call, already captured as `@reference.call.member`).
69
+ * 2. The member_expression is the `constructor:` of a `new_expression`
70
+ * (already captured as `@reference.call.constructor.qualified`).
71
+ * 3. The member_expression is the `left:` of an `assignment_expression` /
72
+ * `augmented_assignment_expression` (it's a write, already captured
73
+ * as `@reference.write.member`).
74
+ * 4. The member_expression is the `function:` of an `await_expression`
75
+ * being called (handled by the member-call capture).
76
+ *
77
+ * Returns `true` when the capture should be kept as a read reference,
78
+ * `false` when it should be dropped.
79
+ */
80
+ function shouldEmitReadMember(memberNode) {
81
+ const parent = memberNode.parent;
82
+ if (parent === null)
83
+ return true;
84
+ switch (parent.type) {
85
+ case 'call_expression':
86
+ return parent.childForFieldName('function')?.id !== memberNode.id;
87
+ case 'new_expression':
88
+ return parent.childForFieldName('constructor')?.id !== memberNode.id;
89
+ case 'assignment_expression':
90
+ case 'augmented_assignment_expression':
91
+ return parent.childForFieldName('left')?.id !== memberNode.id;
92
+ default:
93
+ return true;
94
+ }
95
+ }
96
+ export function emitTsScopeCaptures(sourceText, filePath, cachedTree) {
97
+ // Skip the parse when the caller (parse phase's scopeTreeCache) already
98
+ // produced a Tree for this source. Cache miss = re-parse, same as before.
99
+ // The cachedTree parameter is typed as `unknown` at the LanguageProvider
100
+ // contract layer; cast here at the use site.
101
+ //
102
+ // Grammar selection: `.tsx` files are parsed with the TSX grammar,
103
+ // `.ts` files with the TypeScript grammar. The two grammars have
104
+ // separate node-type id spaces, so a Query compiled against one
105
+ // cannot match a Tree produced by the other. We validate the cached
106
+ // tree's grammar against the file extension and fall back to a
107
+ // fresh parse if they disagree (e.g. a worker-mode parse landed
108
+ // with the wrong grammar pinned).
109
+ let tree = cachedTree;
110
+ if (tree !== undefined && !tsCachedTreeMatchesGrammar(tree, filePath)) {
111
+ tree = undefined;
112
+ }
113
+ if (tree === undefined) {
114
+ tree = getTsParser(filePath).parse(sourceText, undefined, {
115
+ bufferSize: getTreeSitterBufferSize(sourceText),
116
+ });
117
+ recordCacheMiss();
118
+ }
119
+ else {
120
+ recordCacheHit();
121
+ }
122
+ const rawMatches = getTsScopeQuery(filePath).matches(tree.rootNode);
123
+ const out = [];
124
+ for (const m of rawMatches) {
125
+ // Group captures by their tag name. Tree-sitter strips the leading
126
+ // `@`; we put it back so the central extractor's prefix lookups
127
+ // (`@scope.`, `@declaration.`, …) work.
128
+ const grouped = {};
129
+ for (const c of m.captures) {
130
+ const tag = '@' + c.name;
131
+ grouped[tag] = nodeToCapture(tag, c.node);
132
+ }
133
+ if (Object.keys(grouped).length === 0)
134
+ continue;
135
+ // Decompose each `import_statement` / re-export `export_statement`
136
+ // so `interpretTsImport` sees the kind/source/name/alias markers
137
+ // it consumes. The raw query anchor carries only @import.statement.
138
+ // Side-effect imports emit a non-binding marker so finalize can keep
139
+ // the file-level dependency.
140
+ if (grouped['@import.statement'] !== undefined) {
141
+ const stmtCapture = grouped['@import.statement'];
142
+ const stmtNode = findNodeAtRange(tree.rootNode, stmtCapture.range, 'import_statement') ??
143
+ findNodeAtRange(tree.rootNode, stmtCapture.range, 'export_statement');
144
+ if (stmtNode !== null) {
145
+ const decomposed = splitImportStatement(stmtNode);
146
+ for (const d of decomposed)
147
+ out.push(d);
148
+ }
149
+ // If decomposition yielded nothing (malformed/bare anchor), drop
150
+ // the match. Emitting a bare
151
+ // @import.statement without kind/source would confuse the
152
+ // central extractor.
153
+ continue;
154
+ }
155
+ // Dynamic imports — decompose via the same path. `@import.dynamic`
156
+ // is anchored on a `call_expression`, which the decomposer's
157
+ // `splitDynamicImport` branch consumes.
158
+ if (grouped['@import.dynamic'] !== undefined) {
159
+ const dynCapture = grouped['@import.dynamic'];
160
+ const callNode = findNodeAtRange(tree.rootNode, dynCapture.range, 'call_expression');
161
+ if (callNode !== null) {
162
+ const decomposed = splitImportStatement(callNode);
163
+ for (const d of decomposed)
164
+ out.push(d);
165
+ }
166
+ continue;
167
+ }
168
+ // Filter out `@reference.read.member` matches whose AST parent tells
169
+ // us they are actually calls / writes / constructor invocations. The
170
+ // tree-sitter pattern is context-free and matches every member_expression;
171
+ // we rely on this emit-side filter so the query stays simple.
172
+ if (grouped['@reference.read.member'] !== undefined) {
173
+ const anchor = grouped['@reference.read.member'];
174
+ const memberNode = findNodeAtRange(tree.rootNode, anchor.range, 'member_expression');
175
+ if (memberNode === null || !shouldEmitReadMember(memberNode)) {
176
+ continue;
177
+ }
178
+ }
179
+ // Synthesize arity metadata on function-like declaration anchors
180
+ // before pushing the match. The registry uses these to narrow
181
+ // overloads — TypeScript supports overload signatures via
182
+ // function_signature, so `parameterTypes` is populated when
183
+ // available.
184
+ const declAnchor = pickFirstDefined(grouped, FUNCTION_DECL_TAGS);
185
+ if (declAnchor !== undefined) {
186
+ const fnNode = findFunctionNode(tree.rootNode, declAnchor.range);
187
+ if (fnNode !== null) {
188
+ const arity = computeTsArityMetadata(fnNode);
189
+ if (arity.parameterCount !== undefined) {
190
+ grouped['@declaration.parameter-count'] = syntheticCapture('@declaration.parameter-count', fnNode, String(arity.parameterCount));
191
+ }
192
+ if (arity.requiredParameterCount !== undefined) {
193
+ grouped['@declaration.required-parameter-count'] = syntheticCapture('@declaration.required-parameter-count', fnNode, String(arity.requiredParameterCount));
194
+ }
195
+ if (arity.parameterTypes !== undefined) {
196
+ grouped['@declaration.parameter-types'] = syntheticCapture('@declaration.parameter-types', fnNode, JSON.stringify(arity.parameterTypes));
197
+ }
198
+ }
199
+ }
200
+ // Synthesize `@reference.arity` on every callsite so the registry's
201
+ // arity filter can narrow overloads. Count the `argument` named
202
+ // children of the backing `arguments` node. TypeScript constructor
203
+ // calls use `new_expression`; regular calls use `call_expression`.
204
+ const callAnchor = pickFirstDefined(grouped, CALL_TAGS);
205
+ if (callAnchor !== undefined && grouped['@reference.arity'] === undefined) {
206
+ const callNode = findNodeAtRange(tree.rootNode, callAnchor.range, 'call_expression') ??
207
+ findNodeAtRange(tree.rootNode, callAnchor.range, 'new_expression');
208
+ if (callNode !== null) {
209
+ const argList = callNode.childForFieldName('arguments');
210
+ const args = argList === null
211
+ ? []
212
+ : argList.namedChildren.filter((c) => c !== null && c.type !== 'comment');
213
+ grouped['@reference.arity'] = syntheticCapture('@reference.arity', callNode, String(args.length));
214
+ const argTypes = args.map((arg) => inferArgType(arg));
215
+ grouped['@reference.parameter-types'] = syntheticCapture('@reference.parameter-types', callNode, JSON.stringify(argTypes));
216
+ }
217
+ }
218
+ out.push(grouped);
219
+ // Synthesize `this` receiver type-bindings on every function-like
220
+ // scope that is structurally a class member. `receiver-binding.ts`
221
+ // handles the walk-up (method, method_signature, abstract
222
+ // signature, arrow/function-expression assigned to a class field).
223
+ // Arrow functions nested inside method bodies rely on scope-chain
224
+ // lookup instead of synthesis — covered by `tsReceiverBinding`.
225
+ const scopeFnAnchor = grouped['@scope.function'];
226
+ if (scopeFnAnchor !== undefined) {
227
+ const fnNode = findFunctionNode(tree.rootNode, scopeFnAnchor.range);
228
+ if (fnNode !== null) {
229
+ const synth = synthesizeTsReceiverBinding(fnNode);
230
+ if (synth !== null)
231
+ out.push(synth);
232
+ }
233
+ }
234
+ }
235
+ // Synthesize object-destructuring type bindings. The tree-sitter query
236
+ // alone can't express "give me the field NAME and the RHS identifier
237
+ // together" in a way that produces usable @type-binding.name /
238
+ // @type-binding.type captures, so we walk `variable_declarator` nodes
239
+ // whose `name:` is an `object_pattern` and synthesize per-field
240
+ // bindings keyed to the receiver-path `rhsName.fieldName`. The
241
+ // compound-receiver resolver's Case 3b then walks that path when the
242
+ // destructured local is used as a receiver (e.g. `address.save()`).
243
+ synthesizeDestructuringBindings(tree.rootNode, out);
244
+ synthesizeForOfMapTupleBindings(tree.rootNode, out);
245
+ synthesizeInstanceofNarrowings(tree.rootNode, out);
246
+ return out;
247
+ }
248
+ /**
249
+ * Walk the AST and synthesize type-binding captures for object
250
+ * destructuring of the form `const { field } = rhs` or
251
+ * `const { field: alias } = rhs`. Pushes one synthetic CaptureMatch
252
+ * per destructured identifier with:
253
+ *
254
+ * - `@type-binding.name` → the local identifier
255
+ * - `@type-binding.type` → the compound path `rhs.field`
256
+ * - `@type-binding.destructured` anchor
257
+ *
258
+ * Only fires when the RHS is a bare identifier — more complex RHS
259
+ * shapes (call_expression, member_expression) resolve via the normal
260
+ * type-alias + chain-follow paths on the RHS first, then the field
261
+ * walk catches the destructured identifier on a second fixpoint pass.
262
+ * Left as a follow-up optimization.
263
+ */
264
+ function synthesizeDestructuringBindings(root, out) {
265
+ const stack = [root];
266
+ for (;;) {
267
+ const node = stack.pop();
268
+ if (node === undefined)
269
+ break;
270
+ for (const child of node.namedChildren) {
271
+ if (child !== null)
272
+ stack.push(child);
273
+ }
274
+ if (node.type !== 'variable_declarator')
275
+ continue;
276
+ const nameNode = node.childForFieldName('name');
277
+ const valueNode = node.childForFieldName('value');
278
+ if (nameNode === null || valueNode === null)
279
+ continue;
280
+ if (nameNode.type !== 'object_pattern')
281
+ continue;
282
+ if (valueNode.type !== 'identifier')
283
+ continue;
284
+ const rhsName = valueNode.text;
285
+ for (const fieldNode of nameNode.namedChildren) {
286
+ if (fieldNode === null)
287
+ continue;
288
+ if (fieldNode.type === 'shorthand_property_identifier_pattern') {
289
+ // `const { address } = user`
290
+ const localName = fieldNode.text;
291
+ out.push({
292
+ '@type-binding.name': syntheticCapture('@type-binding.name', fieldNode, localName),
293
+ '@type-binding.type': syntheticCapture('@type-binding.type', fieldNode, `${rhsName}.${localName}`),
294
+ '@type-binding.destructured': syntheticCapture('@type-binding.destructured', fieldNode, fieldNode.text),
295
+ });
296
+ }
297
+ else if (fieldNode.type === 'pair_pattern') {
298
+ // `const { address: addr } = user`
299
+ const key = fieldNode.childForFieldName('key');
300
+ const value = fieldNode.childForFieldName('value');
301
+ if (key === null || value === null)
302
+ continue;
303
+ if (value.type !== 'identifier')
304
+ continue;
305
+ const fieldName = key.text;
306
+ const localName = value.text;
307
+ out.push({
308
+ '@type-binding.name': syntheticCapture('@type-binding.name', value, localName),
309
+ '@type-binding.type': syntheticCapture('@type-binding.type', fieldNode, `${rhsName}.${fieldName}`),
310
+ '@type-binding.destructured': syntheticCapture('@type-binding.destructured', fieldNode, fieldNode.text),
311
+ });
312
+ }
313
+ }
314
+ }
315
+ }
316
+ /**
317
+ * `for (const [k, v] of mapId)` over a `Map<K,V>` — synthesize per-slot
318
+ * type bindings so `v` resolves like a `Map` iterator tuple element.
319
+ * Uses sentinel `__MAP_TUPLE_i__:rhs` consumed by compound-receiver.
320
+ */
321
+ function synthesizeForOfMapTupleBindings(root, out) {
322
+ const stack = [root];
323
+ for (;;) {
324
+ const node = stack.pop();
325
+ if (node === undefined)
326
+ break;
327
+ for (const child of node.namedChildren) {
328
+ if (child !== null)
329
+ stack.push(child);
330
+ }
331
+ if (node.type !== 'for_in_statement')
332
+ continue;
333
+ const left = node.childForFieldName('left');
334
+ const right = node.childForFieldName('right');
335
+ if (left === null || right === null)
336
+ continue;
337
+ if (left.type !== 'array_pattern' || right.type !== 'identifier')
338
+ continue;
339
+ const rhs = right.text;
340
+ let slot = 0;
341
+ for (const child of left.namedChildren) {
342
+ if (child === null || child.type !== 'identifier')
343
+ continue;
344
+ const localName = child.text;
345
+ out.push({
346
+ '@type-binding.name': syntheticCapture('@type-binding.name', child, localName),
347
+ '@type-binding.type': syntheticCapture('@type-binding.type', child, `__MAP_TUPLE_${slot}__:${rhs}`),
348
+ '@type-binding.map-tuple-entry': syntheticCapture('@type-binding.map-tuple-entry', child, String(slot)),
349
+ });
350
+ slot++;
351
+ }
352
+ }
353
+ }
354
+ /**
355
+ * `if (x instanceof User) { x.save() }` — synthesize a `User` type binding
356
+ * for `x` anchored in the consequence block so scope-chain lookup inside
357
+ * the then-branch sees the narrowed type.
358
+ *
359
+ * **Known limitation:** the LHS must be a bare `identifier` and the RHS
360
+ * an `identifier`/`type_identifier`. Member-expression LHS such as
361
+ * `if (user.address instanceof Address)` is intentionally NOT synthesized
362
+ * — narrowing a property-access target requires a stable storage key
363
+ * the binding layer can hold, which member chains don't supply. Field-
364
+ * type resolution covers the common case for those receivers via
365
+ * declared types instead.
366
+ */
367
+ function synthesizeInstanceofNarrowings(root, out) {
368
+ const stack = [root];
369
+ for (;;) {
370
+ const node = stack.pop();
371
+ if (node === undefined)
372
+ break;
373
+ for (const child of node.namedChildren) {
374
+ if (child !== null)
375
+ stack.push(child);
376
+ }
377
+ if (node.type !== 'if_statement')
378
+ continue;
379
+ const cond = node.childForFieldName('condition');
380
+ if (cond === null)
381
+ continue;
382
+ const inner = cond.type === 'parenthesized_expression' ? cond.namedChildren[0] : cond;
383
+ if (inner === null || inner.type !== 'binary_expression')
384
+ continue;
385
+ const op = inner.childForFieldName('operator');
386
+ const left = inner.childForFieldName('left');
387
+ const right = inner.childForFieldName('right');
388
+ if (op === null || left === null || right === null)
389
+ continue;
390
+ if (op.type !== 'instanceof')
391
+ continue;
392
+ if (left.type !== 'identifier')
393
+ continue;
394
+ if (right.type !== 'identifier' && right.type !== 'type_identifier')
395
+ continue;
396
+ const varName = left.text;
397
+ const typeName = right.text;
398
+ const cons = node.childForFieldName('consequence');
399
+ if (cons === null)
400
+ continue;
401
+ out.push({
402
+ '@type-binding.name': syntheticCapture('@type-binding.name', cons, varName),
403
+ '@type-binding.type': syntheticCapture('@type-binding.type', right, typeName),
404
+ '@type-binding.instanceof-narrow': syntheticCapture('@type-binding.instanceof-narrow', cons, '1'),
405
+ });
406
+ }
407
+ }
408
+ /** Infer a TypeScript argument expression's static type from literal
409
+ * shapes. Returns `''` when the arg has no statically-derivable type
410
+ * (identifiers, member accesses, etc.) — consumers treat unknown as
411
+ * any-match during overload narrowing. */
412
+ function inferArgType(argNode) {
413
+ switch (argNode.type) {
414
+ case 'number':
415
+ return 'number';
416
+ case 'string':
417
+ case 'template_string':
418
+ return 'string';
419
+ case 'true':
420
+ case 'false':
421
+ return 'boolean';
422
+ case 'null':
423
+ return 'null';
424
+ case 'undefined':
425
+ return 'undefined';
426
+ case 'array':
427
+ return 'Array';
428
+ case 'object':
429
+ return 'object';
430
+ case 'regex':
431
+ return 'RegExp';
432
+ case 'new_expression': {
433
+ const ctor = argNode.childForFieldName('constructor');
434
+ return ctor?.text ?? '';
435
+ }
436
+ default:
437
+ return '';
438
+ }
439
+ }
440
+ /** Find the first TypeScript function-like node at the given range.
441
+ * The `@scope.function` anchor range covers the whole node, but the
442
+ * tag alone doesn't identify which node type among the many TS
443
+ * function-likes. */
444
+ function findFunctionNode(rootNode, range) {
445
+ for (const nodeType of FUNCTION_NODE_TYPES) {
446
+ const n = findNodeAtRange(rootNode, range, nodeType);
447
+ if (n !== null)
448
+ return n;
449
+ }
450
+ return null;
451
+ }
@@ -0,0 +1,49 @@
1
+ /**
2
+ * Decompose a TypeScript `import_statement` / re-export `export_statement` /
3
+ * dynamic `call_expression(import)` into one `CaptureMatch` per imported
4
+ * name.
5
+ *
6
+ * Why split here? The `LanguageProvider.interpretImport` contract is
7
+ * one `ParsedImport` per call. Tree-sitter delivers
8
+ *
9
+ * import D, { X as Y, type Z } from './m'
10
+ *
11
+ * as a single `import_statement` match, so without decomposition we'd
12
+ * lose names. The synthesized markers (`@import.kind` / `@import.name`
13
+ * / `@import.alias` / `@import.source`) carry everything
14
+ * `interpretTsImport` needs to recover the `ParsedImport` shape —
15
+ * see `interpret.ts`.
16
+ *
17
+ * Kinds we emit and how `interpret.ts` maps them to `ParsedImport`:
18
+ *
19
+ * - `default` : `import D from './m'` → alias (importedName=default)
20
+ * - `named` : `import { X } from './m'` → named
21
+ * - `named-alias` : `import { X as Y } from './m'` → alias
22
+ * - `namespace` : `import * as N from './m'` → namespace
23
+ * - `reexport` : `export { X } from './m'` → reexport
24
+ * - `reexport-alias` : `export { X as Y } from './m'` → reexport (with alias)
25
+ * - `reexport-wildcard` : `export * from './m'` → wildcard
26
+ * - `reexport-namespace` : `export * as ns from './m'` → namespace (local=ns,imported=source)
27
+ * - `dynamic` : `import('./m')` / `import(x)` → dynamic-resolved or dynamic-unresolved
28
+ *
29
+ * Type-only constructs (`import type { X }`, `import { type X }`,
30
+ * `export type { X }`) emit the same kinds as runtime forms — at the
31
+ * TypeScript scope-resolution layer, types and values share the same
32
+ * lookup; runtime-emission is a downstream concern.
33
+ *
34
+ * Side-effect imports (`import './polyfill'`) produce a single match
35
+ * with `kind: 'side-effect'`. The shared finalize algorithm resolves
36
+ * the target file and emits a file-level IMPORTS edge, but
37
+ * materializes no `BindingRef` (matching the legacy DAG, which counts
38
+ * `import './polyfill'` as a module-reachability dependency only).
39
+ */
40
+ import type { CaptureMatch } from '../../../../_shared/index.js';
41
+ import { type SyntaxNode } from '../../utils/ast-helpers.js';
42
+ /**
43
+ * Decompose an import anchor. Handles three node types:
44
+ *
45
+ * - `import_statement` : all static import forms (incl. side-effect)
46
+ * - `export_statement` (w/ source) : re-exports
47
+ * - `call_expression` (import fn) : dynamic `import()`
48
+ */
49
+ export declare function splitImportStatement(stmtNode: SyntaxNode): CaptureMatch[];