gitnexus 1.6.6-rc.2 → 1.6.6-rc.21

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 (112) 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.map +1 -1
  4. package/dist/_shared/scope-resolution/reference-site.d.ts +8 -0
  5. package/dist/_shared/scope-resolution/reference-site.d.ts.map +1 -1
  6. package/dist/_shared/scope-resolution/registries/context.d.ts +18 -1
  7. package/dist/_shared/scope-resolution/registries/context.d.ts.map +1 -1
  8. package/dist/_shared/scope-resolution/registries/context.js.map +1 -1
  9. package/dist/_shared/scope-resolution/registries/lookup-core.d.ts +4 -2
  10. package/dist/_shared/scope-resolution/registries/lookup-core.d.ts.map +1 -1
  11. package/dist/_shared/scope-resolution/registries/lookup-core.js +8 -23
  12. package/dist/_shared/scope-resolution/registries/lookup-core.js.map +1 -1
  13. package/dist/cli/analyze.js +65 -3
  14. package/dist/cli/eval-server.d.ts +14 -3
  15. package/dist/cli/eval-server.js +79 -9
  16. package/dist/cli/index.js +3 -1
  17. package/dist/cli/serve.js +6 -1
  18. package/dist/cli/setup.js +6 -4
  19. package/dist/cli/wiki.d.ts +1 -0
  20. package/dist/cli/wiki.js +32 -8
  21. package/dist/core/ingestion/languages/cpp/captures.js +165 -9
  22. package/dist/core/ingestion/languages/cpp/constraint-filter.js +102 -32
  23. package/dist/core/ingestion/languages/cpp/type-classifier.d.ts +9 -9
  24. package/dist/core/ingestion/languages/cpp/type-classifier.js +12 -8
  25. package/dist/core/ingestion/languages/java.js +35 -0
  26. package/dist/core/ingestion/languages/javascript/arity.d.ts +11 -0
  27. package/dist/core/ingestion/languages/javascript/arity.js +11 -0
  28. package/dist/core/ingestion/languages/javascript/captures.d.ts +30 -0
  29. package/dist/core/ingestion/languages/javascript/captures.js +655 -0
  30. package/dist/core/ingestion/languages/javascript/import-target.d.ts +29 -0
  31. package/dist/core/ingestion/languages/javascript/import-target.js +53 -0
  32. package/dist/core/ingestion/languages/javascript/index.d.ts +48 -0
  33. package/dist/core/ingestion/languages/javascript/index.js +48 -0
  34. package/dist/core/ingestion/languages/javascript/interpret.d.ts +29 -0
  35. package/dist/core/ingestion/languages/javascript/interpret.js +41 -0
  36. package/dist/core/ingestion/languages/javascript/merge-bindings.d.ts +16 -0
  37. package/dist/core/ingestion/languages/javascript/merge-bindings.js +18 -0
  38. package/dist/core/ingestion/languages/javascript/query.d.ts +52 -0
  39. package/dist/core/ingestion/languages/javascript/query.js +413 -0
  40. package/dist/core/ingestion/languages/javascript/scope-resolver.d.ts +37 -0
  41. package/dist/core/ingestion/languages/javascript/scope-resolver.js +73 -0
  42. package/dist/core/ingestion/languages/javascript/simple-hooks.d.ts +32 -0
  43. package/dist/core/ingestion/languages/javascript/simple-hooks.js +37 -0
  44. package/dist/core/ingestion/languages/typescript/simple-hooks.d.ts +10 -0
  45. package/dist/core/ingestion/languages/typescript/simple-hooks.js +4 -1
  46. package/dist/core/ingestion/languages/typescript.js +15 -0
  47. package/dist/core/ingestion/model/field-registry.d.ts +17 -3
  48. package/dist/core/ingestion/model/field-registry.js +19 -4
  49. package/dist/core/ingestion/model/owned-members-lookup.d.ts +19 -0
  50. package/dist/core/ingestion/model/owned-members-lookup.js +43 -0
  51. package/dist/core/ingestion/model/type-registry.d.ts +9 -0
  52. package/dist/core/ingestion/model/type-registry.js +18 -0
  53. package/dist/core/ingestion/registry-primary-flag.js +1 -0
  54. package/dist/core/ingestion/resolve-references.d.ts +3 -1
  55. package/dist/core/ingestion/resolve-references.js +5 -1
  56. package/dist/core/ingestion/scope-extractor.js +4 -0
  57. package/dist/core/ingestion/scope-resolution/passes/free-call-fallback.d.ts +1 -0
  58. package/dist/core/ingestion/scope-resolution/passes/free-call-fallback.js +37 -7
  59. package/dist/core/ingestion/scope-resolution/passes/overload-narrowing.d.ts +2 -0
  60. package/dist/core/ingestion/scope-resolution/passes/overload-narrowing.js +8 -1
  61. package/dist/core/ingestion/scope-resolution/passes/receiver-bound-calls.js +2 -0
  62. package/dist/core/ingestion/scope-resolution/pipeline/reconcile-ownership.d.ts +4 -2
  63. package/dist/core/ingestion/scope-resolution/pipeline/reconcile-ownership.js +48 -9
  64. package/dist/core/ingestion/scope-resolution/pipeline/registry.js +2 -0
  65. package/dist/core/ingestion/scope-resolution/pipeline/run.js +2 -0
  66. package/dist/core/lbug/lbug-adapter.d.ts +3 -1
  67. package/dist/core/lbug/lbug-adapter.js +33 -19
  68. package/dist/core/lbug/lbug-config.d.ts +1 -1
  69. package/dist/core/lbug/lbug-config.js +1 -1
  70. package/dist/core/lbug/pool-adapter.d.ts +19 -7
  71. package/dist/core/lbug/pool-adapter.js +70 -44
  72. package/dist/core/lbug/query-params.d.ts +1 -0
  73. package/dist/core/lbug/query-params.js +21 -0
  74. package/dist/core/search/bm25-index.js +4 -6
  75. package/dist/core/wiki/generator.d.ts +13 -0
  76. package/dist/core/wiki/generator.js +45 -4
  77. package/dist/core/wiki/llm-client.d.ts +1 -1
  78. package/dist/core/wiki/llm-client.js +23 -6
  79. package/dist/mcp/local/local-backend.d.ts +18 -3
  80. package/dist/mcp/local/local-backend.js +127 -11
  81. package/dist/mcp/tools.js +10 -0
  82. package/dist/server/api.d.ts +3 -0
  83. package/dist/server/api.js +58 -30
  84. package/package.json +3 -3
  85. package/web/assets/agent-SmDhIVj9.js +608 -0
  86. package/web/assets/{architectureDiagram-UL44E2DR-DvIkEGkp.js → architectureDiagram-UL44E2DR-ChS6kfgC.js} +1 -1
  87. package/web/assets/{chunk-LCXTWHL2-CqJ0oo1x.js → chunk-LCXTWHL2-CikfiTEP.js} +1 -1
  88. package/web/assets/{chunk-RG4AUYOV-D0M6vdq9.js → chunk-RG4AUYOV-D0pH0sP0.js} +1 -1
  89. package/web/assets/{classDiagram-KGZ6W3CR-BB-saqC1.js → classDiagram-KGZ6W3CR-VJjLBrQ3.js} +1 -1
  90. package/web/assets/{classDiagram-v2-72OJOZXJ-Bp2bAlS5.js → classDiagram-v2-72OJOZXJ-CL_v399n.js} +1 -1
  91. package/web/assets/{diagram-3NCE3AQN-BUxnDPOY.js → diagram-3NCE3AQN-DTTuOnDj.js} +1 -1
  92. package/web/assets/{diagram-GF46GFSD-BJTyqqvI.js → diagram-GF46GFSD-Di3e_png.js} +1 -1
  93. package/web/assets/{diagram-QXG6HAR7-DilMHamP.js → diagram-QXG6HAR7-CVQY1hhk.js} +1 -1
  94. package/web/assets/{diagram-WEQXMOUZ-8KUrarg1.js → diagram-WEQXMOUZ-BoxS7CN3.js} +1 -1
  95. package/web/assets/{erDiagram-L5TCEMPS-Doj9mSiW.js → erDiagram-L5TCEMPS-CLTuHusW.js} +1 -1
  96. package/web/assets/{flowDiagram-H6V6AXG4-BOSR-i3p.js → flowDiagram-H6V6AXG4-CQfVb2C4.js} +1 -1
  97. package/web/assets/{index-j4-NuleT.js → index-BuC2I_Cj.js} +5 -5
  98. package/web/assets/{infoDiagram-3YFTVSEB-Ce_TvtaV.js → infoDiagram-3YFTVSEB-BOk6LlXV.js} +1 -1
  99. package/web/assets/{ishikawaDiagram-BNXS4ZKH-4MXIDCSF.js → ishikawaDiagram-BNXS4ZKH-C0sASGB6.js} +1 -1
  100. package/web/assets/{kanban-definition-75IXJCU3-yYdfm8Kk.js → kanban-definition-75IXJCU3-DT9Lizpz.js} +1 -1
  101. package/web/assets/{mindmap-definition-2TDM6QVE-DFNEKM77.js → mindmap-definition-2TDM6QVE-BEYaB7hb.js} +1 -1
  102. package/web/assets/{pieDiagram-CU6KROY3-iZAqpvhd.js → pieDiagram-CU6KROY3-DCgHNkyY.js} +1 -1
  103. package/web/assets/{requirementDiagram-JXO7QTGE-CtDijhnf.js → requirementDiagram-JXO7QTGE-D6vDU9M2.js} +1 -1
  104. package/web/assets/{sequenceDiagram-VS2MUI6T-BdjvyCHM.js → sequenceDiagram-VS2MUI6T-CJrGCTXy.js} +1 -1
  105. package/web/assets/{stateDiagram-7D4R322I-e5odN9fG.js → stateDiagram-7D4R322I-BvxQnQyL.js} +1 -1
  106. package/web/assets/{stateDiagram-v2-36443NZ5-vPsvjR2D.js → stateDiagram-v2-36443NZ5-D8ziO9W2.js} +1 -1
  107. package/web/assets/{timeline-definition-O6YCAMPW-DPAksNQp.js → timeline-definition-O6YCAMPW-B45j2q3m.js} +1 -1
  108. package/web/assets/{vennDiagram-MWXL3ELB-Dtlfdt7J.js → vennDiagram-MWXL3ELB-CRkZRDhi.js} +1 -1
  109. package/web/assets/{wardleyDiagram-CUQ6CDDI-B23uRMUC.js → wardleyDiagram-CUQ6CDDI-BekUytZJ.js} +1 -1
  110. package/web/assets/{xychartDiagram-N2JHSOCM-DxwHeksW.js → xychartDiagram-N2JHSOCM-BeMqwSgr.js} +1 -1
  111. package/web/index.html +1 -1
  112. package/web/assets/agent-D2kLt6Dl.js +0 -597
@@ -0,0 +1,655 @@
1
+ /**
2
+ * `emitScopeCaptures` for JavaScript.
3
+ *
4
+ * Adapts `emitTsScopeCaptures` for the JavaScript grammar:
5
+ *
6
+ * 1. **JS grammar** — uses `tree-sitter-javascript` instead of
7
+ * `tree-sitter-typescript`. The JS scope query is a subset of the
8
+ * TypeScript one (TypeScript-only node types dropped).
9
+ *
10
+ * 2. **CJS `require()` decomposition** — `const { X } = require('./m')`
11
+ * and `const X = require('./m')` are walked in a post-query pass and
12
+ * synthesized as `@import.kind/name/alias/source` markers so that
13
+ * `interpretJsImport` can recover a `ParsedImport` using the same
14
+ * shape as the TypeScript ESM decomposer.
15
+ *
16
+ * 3. **JSDoc type bindings** — JavaScript has no static type annotations
17
+ * so `@type-binding.parameter` / `@type-binding.return` must be
18
+ * inferred from leading JSDoc comments. A lightweight regex scanner
19
+ * (`parseJsDocParams` / `parseJsDocReturn`) extracts `@param {T} n`
20
+ * and `@returns {T}` tags and emits synthetic captures positioned on
21
+ * the annotated function node.
22
+ *
23
+ * 4. **Shared synthesis passes** — destructuring, for-of map-tuple, and
24
+ * instanceof narrowing passes are duplicated from `typescript/captures.ts`
25
+ * (they are pure AST operations with no grammar-specific logic).
26
+ *
27
+ * Pure given the input source text. No I/O, no globals consulted.
28
+ */
29
+ import { findNodeAtRange, nodeToCapture, syntheticCapture, } from '../../utils/ast-helpers.js';
30
+ import { splitImportStatement } from '../typescript/import-decomposer.js';
31
+ import { getJsParser, getJsScopeQuery, jsCachedTreeMatchesGrammar } from './query.js';
32
+ import { computeTsArityMetadata } from '../typescript/arity-metadata.js';
33
+ import { synthesizeTsReceiverBinding } from '../typescript/receiver-binding.js';
34
+ import { getTreeSitterBufferSize } from '../../constants.js';
35
+ import { parseSourceSafe } from '../../../tree-sitter/safe-parse.js';
36
+ /** JS function-like node types that may carry a synthesized `this` binding.
37
+ * Kept in sync with the `@scope.function` patterns in `query.ts`. */
38
+ const FUNCTION_NODE_TYPES = [
39
+ 'method_definition',
40
+ 'arrow_function',
41
+ 'function_expression',
42
+ 'function_declaration',
43
+ 'generator_function_declaration',
44
+ ];
45
+ /** Declaration anchors that carry function-like arity metadata. */
46
+ const FUNCTION_DECL_TAGS = ['@declaration.method', '@declaration.function'];
47
+ /** Callsite anchors that should carry `@reference.arity` + param types. */
48
+ const CALL_TAGS = [
49
+ '@reference.call.free',
50
+ '@reference.call.member',
51
+ '@reference.call.constructor',
52
+ ];
53
+ function pickFirstDefined(grouped, tags) {
54
+ for (const tag of tags) {
55
+ const cap = grouped[tag];
56
+ if (cap !== undefined)
57
+ return cap;
58
+ }
59
+ return undefined;
60
+ }
61
+ /** Filter `@reference.read.member` in non-read contexts (same logic as TS). */
62
+ function shouldEmitReadMember(memberNode) {
63
+ const parent = memberNode.parent;
64
+ if (parent === null)
65
+ return true;
66
+ switch (parent.type) {
67
+ case 'call_expression':
68
+ return parent.childForFieldName('function')?.id !== memberNode.id;
69
+ case 'new_expression':
70
+ return parent.childForFieldName('constructor')?.id !== memberNode.id;
71
+ case 'assignment_expression':
72
+ case 'augmented_assignment_expression':
73
+ return parent.childForFieldName('left')?.id !== memberNode.id;
74
+ case 'jsx_self_closing_element':
75
+ case 'jsx_opening_element':
76
+ return parent.childForFieldName('name')?.id !== memberNode.id;
77
+ default:
78
+ return true;
79
+ }
80
+ }
81
+ /** Find the first JS function-like node at the given range. */
82
+ function findFunctionNode(rootNode, range) {
83
+ for (const nodeType of FUNCTION_NODE_TYPES) {
84
+ const n = findNodeAtRange(rootNode, range, nodeType);
85
+ if (n !== null)
86
+ return n;
87
+ }
88
+ return null;
89
+ }
90
+ /** Infer a callsite argument's static type from literal shapes. */
91
+ function inferArgType(argNode) {
92
+ switch (argNode.type) {
93
+ case 'number':
94
+ return 'number';
95
+ case 'string':
96
+ case 'template_string':
97
+ return 'string';
98
+ case 'true':
99
+ case 'false':
100
+ return 'boolean';
101
+ case 'null':
102
+ return 'null';
103
+ case 'undefined':
104
+ return 'undefined';
105
+ case 'array':
106
+ return 'Array';
107
+ case 'object':
108
+ return 'object';
109
+ case 'regex':
110
+ return 'RegExp';
111
+ case 'new_expression': {
112
+ const ctor = argNode.childForFieldName('constructor');
113
+ return ctor?.text ?? '';
114
+ }
115
+ default:
116
+ return '';
117
+ }
118
+ }
119
+ // ─── CJS require() decomposition ─────────────────────────────────────────
120
+ /**
121
+ * Walk the AST and synthesize `@import.*` captures for CJS `require()` calls:
122
+ *
123
+ * - `const { X, Y } = require('./m')` → one match per destructured name,
124
+ * `@import.kind = 'named'`, `@import.name = X / Y`.
125
+ * - `const X = require('./m')` → `@import.kind = 'namespace'`,
126
+ * `@import.alias = X` (the whole module is bound to X).
127
+ * - `require('./m')` as a bare expression-statement → side-effect.
128
+ *
129
+ * CJS named-alias form (`const { X: alias } = require('./m')`) emits
130
+ * `@import.kind = 'named-alias'` with `@import.name = X` and
131
+ * `@import.alias = alias`.
132
+ *
133
+ * The synthesized markers are identical to those produced by
134
+ * `splitImportStatement` for ESM, so `interpretJsImport` can delegate
135
+ * unchanged to `interpretTsImport` for all cases.
136
+ */
137
+ function synthesizeCjsImports(root, out) {
138
+ const stack = [root];
139
+ for (;;) {
140
+ const node = stack.pop();
141
+ if (node === undefined)
142
+ break;
143
+ for (const child of node.namedChildren) {
144
+ if (child !== null)
145
+ stack.push(child);
146
+ }
147
+ if (node.type !== 'call_expression')
148
+ continue;
149
+ // Require call: function must be bare identifier "require".
150
+ const fn = node.childForFieldName('function');
151
+ if (fn === null || fn.type !== 'identifier' || fn.text !== 'require')
152
+ continue;
153
+ const argsNode = node.childForFieldName('arguments');
154
+ if (argsNode === null)
155
+ continue;
156
+ // Source must be a string literal.
157
+ const firstArg = argsNode.namedChild(0);
158
+ if (firstArg === null || firstArg.type !== 'string')
159
+ continue;
160
+ const rawSource = firstArg.text; // includes surrounding quotes
161
+ const source = firstArg.namedChild(0)?.text ?? rawSource.slice(1, -1);
162
+ const parent = node.parent;
163
+ // Case 1: const { X } = require('./m') OR const X = require('./m')
164
+ if (parent?.type === 'variable_declarator') {
165
+ const nameNode = parent.childForFieldName('name');
166
+ if (nameNode === null)
167
+ continue;
168
+ if (nameNode.type === 'object_pattern') {
169
+ // Destructured: emit one match per specifier.
170
+ for (const field of nameNode.namedChildren) {
171
+ if (field === null)
172
+ continue;
173
+ if (field.type === 'shorthand_property_identifier_pattern') {
174
+ const name = field.text;
175
+ out.push({
176
+ '@import.statement': syntheticCapture('@import.statement', node, rawSource),
177
+ '@import.kind': syntheticCapture('@import.kind', node, 'named'),
178
+ '@import.name': syntheticCapture('@import.name', field, name),
179
+ '@import.source': syntheticCapture('@import.source', firstArg, source),
180
+ });
181
+ }
182
+ else if (field.type === 'pair_pattern') {
183
+ const key = field.childForFieldName('key');
184
+ const value = field.childForFieldName('value');
185
+ if (key === null || value === null || value.type !== 'identifier')
186
+ continue;
187
+ out.push({
188
+ '@import.statement': syntheticCapture('@import.statement', node, rawSource),
189
+ '@import.kind': syntheticCapture('@import.kind', node, 'named-alias'),
190
+ '@import.name': syntheticCapture('@import.name', key, key.text),
191
+ '@import.alias': syntheticCapture('@import.alias', value, value.text),
192
+ '@import.source': syntheticCapture('@import.source', firstArg, source),
193
+ });
194
+ }
195
+ }
196
+ }
197
+ else if (nameNode.type === 'identifier') {
198
+ // Namespace-style: const X = require('./m') → bind whole module to X.
199
+ out.push({
200
+ '@import.statement': syntheticCapture('@import.statement', node, rawSource),
201
+ '@import.kind': syntheticCapture('@import.kind', node, 'namespace'),
202
+ '@import.alias': syntheticCapture('@import.alias', nameNode, nameNode.text),
203
+ '@import.source': syntheticCapture('@import.source', firstArg, source),
204
+ });
205
+ }
206
+ continue;
207
+ }
208
+ // Case 2: bare require('./m') — side-effect import.
209
+ if (parent?.type === 'expression_statement') {
210
+ out.push({
211
+ '@import.statement': syntheticCapture('@import.statement', node, rawSource),
212
+ '@import.kind': syntheticCapture('@import.kind', node, 'side-effect'),
213
+ '@import.source': syntheticCapture('@import.source', firstArg, source),
214
+ });
215
+ }
216
+ }
217
+ }
218
+ /** Extract `@param {Type} name` entries from a JSDoc comment block. */
219
+ function parseJsDocParams(text) {
220
+ const results = [];
221
+ // Match @param {Type} name or @param {Type} [name] (optional)
222
+ const re = /@param\s+\{([^}]+)\}\s+\[?(\w+)\]?/g;
223
+ let m;
224
+ while ((m = re.exec(text)) !== null) {
225
+ results.push({ type: m[1].trim(), name: m[2].trim() });
226
+ }
227
+ return results;
228
+ }
229
+ /** Extract `@returns {Type}` or `@return {Type}` from a JSDoc comment. */
230
+ function parseJsDocReturn(text) {
231
+ const m = /@returns?\s+\{([^}]+)\}/.exec(text);
232
+ return m ? m[1].trim() : null;
233
+ }
234
+ /** Extract `@type {Type}` from a JSDoc comment (variable-level annotation). */
235
+ function parseJsDocType(text) {
236
+ const m = /@type\s+\{([^}]+)\}/.exec(text);
237
+ return m ? m[1].trim() : null;
238
+ }
239
+ /**
240
+ * Walk the AST and synthesize `@type-binding.*` captures from JSDoc
241
+ * comments immediately preceding function declarations / expressions.
242
+ *
243
+ * Only `/** … *​/` block comments are scanned. Line comments (`//`) are
244
+ * intentionally excluded — JSDoc lives in block comments.
245
+ *
246
+ * Emits:
247
+ * - `@type-binding.parameter` for each `@param {T} n` tag.
248
+ * - `@type-binding.return` for `@returns {T}` / `@return {T}`.
249
+ * - `@type-binding.annotation` for `@type {T}` on `let`/`const`/`var`
250
+ * declarations — covers the common `/** @type {User} *​/ const u = …`
251
+ * pattern (ECMA-262 §14.3.1/§14.3.2 variable declarations).
252
+ *
253
+ * The binding is anchored on the function node so `tsBindingScopeFor`
254
+ * can hoist method return-type bindings to Module scope (matching the
255
+ * TypeScript path where `hoistTypeBindingsToModule: true`).
256
+ */
257
+ function synthesizeJsDocBindings(root, out) {
258
+ const stack = [root];
259
+ for (;;) {
260
+ const node = stack.pop();
261
+ if (node === undefined)
262
+ break;
263
+ for (const child of node.namedChildren) {
264
+ if (child !== null)
265
+ stack.push(child);
266
+ }
267
+ const isFnDecl = node.type === 'function_declaration' || node.type === 'generator_function_declaration';
268
+ const isMethodDef = node.type === 'method_definition';
269
+ // Also check lexical_declaration containing an arrow/fn-expression
270
+ const isLexDecl = node.type === 'lexical_declaration' || node.type === 'variable_declaration';
271
+ if (!isFnDecl && !isMethodDef && !isLexDecl)
272
+ continue;
273
+ // For `export function foo() { ... }`, the JSDoc comment precedes the
274
+ // wrapping export_statement, not the inner function_declaration.
275
+ // Walk up to the export_statement so the preceding-sibling search finds it.
276
+ const lookupNode = (isFnDecl || isLexDecl) && node.parent?.type === 'export_statement' ? node.parent : node;
277
+ // Find the preceding sibling comment.
278
+ let sibling = lookupNode.previousNamedSibling;
279
+ while (sibling !== null && sibling.type === 'comment') {
280
+ const text = sibling.text;
281
+ if (text.startsWith('/**')) {
282
+ // Found a JSDoc block.
283
+ const params = parseJsDocParams(text);
284
+ const retType = parseJsDocReturn(text);
285
+ const varType = isLexDecl ? parseJsDocType(text) : null;
286
+ // Determine the anchor node (the function-like node, for hoisting).
287
+ const anchor = node;
288
+ for (const p of params) {
289
+ out.push({
290
+ '@type-binding.name': syntheticCapture('@type-binding.name', anchor, p.name),
291
+ '@type-binding.type': syntheticCapture('@type-binding.type', anchor, p.type),
292
+ '@type-binding.parameter': syntheticCapture('@type-binding.parameter', anchor, '1'),
293
+ });
294
+ }
295
+ if (retType !== null) {
296
+ // For named functions, use the function name as the binding name so
297
+ // `hoistTypeBindingsToModule` knows which function's return type this is.
298
+ let fnName = null;
299
+ if (isFnDecl) {
300
+ fnName = node.childForFieldName('name')?.text ?? null;
301
+ }
302
+ else if (isMethodDef) {
303
+ // method_definition uses `name:` field for the method name
304
+ const nameNode = node.childForFieldName('name');
305
+ if (nameNode?.type === 'property_identifier')
306
+ fnName = nameNode.text;
307
+ }
308
+ else if (isLexDecl) {
309
+ const declarator = node.namedChild(0);
310
+ const nameNode = declarator?.childForFieldName('name');
311
+ if (nameNode?.type === 'identifier')
312
+ fnName = nameNode.text;
313
+ }
314
+ if (fnName !== null) {
315
+ out.push({
316
+ '@type-binding.name': syntheticCapture('@type-binding.name', anchor, fnName),
317
+ '@type-binding.type': syntheticCapture('@type-binding.type', anchor, retType),
318
+ '@type-binding.return': syntheticCapture('@type-binding.return', anchor, '1'),
319
+ });
320
+ }
321
+ }
322
+ // @type {T} on let/const/var: `/** @type {User} */ const u = getUser()`.
323
+ // Emits annotation-strength binding (source = 'annotation') so it
324
+ // overrides any weaker constructor/alias inference on the same name.
325
+ if (varType !== null) {
326
+ for (const declarator of node.namedChildren) {
327
+ if (declarator === null || declarator.type !== 'variable_declarator')
328
+ continue;
329
+ const nameNode = declarator.childForFieldName('name');
330
+ if (nameNode === null || nameNode.type !== 'identifier')
331
+ continue;
332
+ out.push({
333
+ '@type-binding.name': syntheticCapture('@type-binding.name', nameNode, nameNode.text),
334
+ '@type-binding.type': syntheticCapture('@type-binding.type', nameNode, varType),
335
+ '@type-binding.annotation': syntheticCapture('@type-binding.annotation', nameNode, '1'),
336
+ });
337
+ }
338
+ }
339
+ break;
340
+ }
341
+ sibling = sibling.previousNamedSibling;
342
+ }
343
+ }
344
+ }
345
+ // ─── Destructuring / for-of / instanceof (shared with TS captures) ───────
346
+ function synthesizeDestructuringBindings(root, out) {
347
+ const stack = [root];
348
+ for (;;) {
349
+ const node = stack.pop();
350
+ if (node === undefined)
351
+ break;
352
+ for (const child of node.namedChildren) {
353
+ if (child !== null)
354
+ stack.push(child);
355
+ }
356
+ if (node.type !== 'variable_declarator')
357
+ continue;
358
+ const nameNode = node.childForFieldName('name');
359
+ const valueNode = node.childForFieldName('value');
360
+ if (nameNode === null || valueNode === null)
361
+ continue;
362
+ if (nameNode.type !== 'object_pattern')
363
+ continue;
364
+ if (valueNode.type !== 'identifier')
365
+ continue;
366
+ const rhsName = valueNode.text;
367
+ for (const fieldNode of nameNode.namedChildren) {
368
+ if (fieldNode === null)
369
+ continue;
370
+ if (fieldNode.type === 'shorthand_property_identifier_pattern') {
371
+ const localName = fieldNode.text;
372
+ out.push({
373
+ '@type-binding.name': syntheticCapture('@type-binding.name', fieldNode, localName),
374
+ '@type-binding.type': syntheticCapture('@type-binding.type', fieldNode, `${rhsName}.${localName}`),
375
+ '@type-binding.destructured': syntheticCapture('@type-binding.destructured', fieldNode, fieldNode.text),
376
+ });
377
+ }
378
+ else if (fieldNode.type === 'pair_pattern') {
379
+ const key = fieldNode.childForFieldName('key');
380
+ const value = fieldNode.childForFieldName('value');
381
+ if (key === null || value === null || value.type !== 'identifier')
382
+ continue;
383
+ const fieldName = key.text;
384
+ const localName = value.text;
385
+ out.push({
386
+ '@type-binding.name': syntheticCapture('@type-binding.name', value, localName),
387
+ '@type-binding.type': syntheticCapture('@type-binding.type', fieldNode, `${rhsName}.${fieldName}`),
388
+ '@type-binding.destructured': syntheticCapture('@type-binding.destructured', fieldNode, fieldNode.text),
389
+ });
390
+ }
391
+ }
392
+ }
393
+ }
394
+ function synthesizeForOfMapTupleBindings(root, out) {
395
+ const stack = [root];
396
+ for (;;) {
397
+ const node = stack.pop();
398
+ if (node === undefined)
399
+ break;
400
+ for (const child of node.namedChildren) {
401
+ if (child !== null)
402
+ stack.push(child);
403
+ }
404
+ if (node.type !== 'for_in_statement')
405
+ continue;
406
+ const left = node.childForFieldName('left');
407
+ const right = node.childForFieldName('right');
408
+ if (left === null || right === null)
409
+ continue;
410
+ if (left.type !== 'array_pattern' || right.type !== 'identifier')
411
+ continue;
412
+ const rhs = right.text;
413
+ let slot = 0;
414
+ for (const child of left.namedChildren) {
415
+ if (child === null || child.type !== 'identifier')
416
+ continue;
417
+ const localName = child.text;
418
+ out.push({
419
+ '@type-binding.name': syntheticCapture('@type-binding.name', child, localName),
420
+ '@type-binding.type': syntheticCapture('@type-binding.type', child, `__MAP_TUPLE_${slot}__:${rhs}`),
421
+ '@type-binding.map-tuple-entry': syntheticCapture('@type-binding.map-tuple-entry', child, String(slot)),
422
+ });
423
+ slot++;
424
+ }
425
+ }
426
+ }
427
+ function synthesizeInstanceofNarrowings(root, out) {
428
+ const stack = [root];
429
+ for (;;) {
430
+ const node = stack.pop();
431
+ if (node === undefined)
432
+ break;
433
+ for (const child of node.namedChildren) {
434
+ if (child !== null)
435
+ stack.push(child);
436
+ }
437
+ if (node.type !== 'if_statement')
438
+ continue;
439
+ const cond = node.childForFieldName('condition');
440
+ if (cond === null)
441
+ continue;
442
+ const inner = cond.type === 'parenthesized_expression' ? cond.namedChildren[0] : cond;
443
+ if (inner === null || inner.type !== 'binary_expression')
444
+ continue;
445
+ const op = inner.childForFieldName('operator');
446
+ const left = inner.childForFieldName('left');
447
+ const right = inner.childForFieldName('right');
448
+ if (op === null || left === null || right === null)
449
+ continue;
450
+ if (op.type !== 'instanceof')
451
+ continue;
452
+ if (left.type !== 'identifier')
453
+ continue;
454
+ if (right.type !== 'identifier')
455
+ continue;
456
+ const varName = left.text;
457
+ const typeName = right.text;
458
+ const cons = node.childForFieldName('consequence');
459
+ if (cons === null)
460
+ continue;
461
+ out.push({
462
+ '@type-binding.name': syntheticCapture('@type-binding.name', cons, varName),
463
+ '@type-binding.type': syntheticCapture('@type-binding.type', right, typeName),
464
+ '@type-binding.instanceof-narrow': syntheticCapture('@type-binding.instanceof-narrow', cons, '1'),
465
+ });
466
+ }
467
+ }
468
+ // ─── Constructor field type bindings ─────────────────────────────────────
469
+ /**
470
+ * Synthesize class-scope type bindings from `this.X = new Y()` assignments
471
+ * inside constructor method bodies. Covers the traditional ES5+ OOP pattern:
472
+ *
473
+ * class User {
474
+ * constructor() {
475
+ * /** @type {Address} *\/
476
+ * this.address = new Address();
477
+ * }
478
+ * }
479
+ *
480
+ * The emitted `@type-binding.class-field` is hoisted to the Class scope by
481
+ * `tsBindingScopeFor` so that compound-receiver resolution can look up
482
+ * `User.address → Address` when resolving `user.address.save()`.
483
+ *
484
+ * Type source priority:
485
+ * 1. JSDoc `@type {T}` comment immediately preceding the statement
486
+ * 2. `new Y()` constructor inference
487
+ */
488
+ function synthesizeConstructorFieldBindings(root, out) {
489
+ const stack = [root];
490
+ for (;;) {
491
+ const node = stack.pop();
492
+ if (node === undefined)
493
+ break;
494
+ for (const child of node.namedChildren) {
495
+ if (child !== null)
496
+ stack.push(child);
497
+ }
498
+ // Only process constructor method definitions
499
+ if (node.type !== 'method_definition')
500
+ continue;
501
+ const nameNode = node.childForFieldName('name');
502
+ if (nameNode?.text !== 'constructor')
503
+ continue;
504
+ const body = node.childForFieldName('body');
505
+ if (body === null)
506
+ continue;
507
+ for (const stmt of body.namedChildren) {
508
+ if (stmt === null || stmt.type !== 'expression_statement')
509
+ continue;
510
+ const expr = stmt.namedChild(0);
511
+ if (expr === null || expr.type !== 'assignment_expression')
512
+ continue;
513
+ const left = expr.childForFieldName('left');
514
+ const right = expr.childForFieldName('right');
515
+ if (left === null || right === null)
516
+ continue;
517
+ if (left.type !== 'member_expression')
518
+ continue;
519
+ const obj = left.childForFieldName('object');
520
+ const prop = left.childForFieldName('property');
521
+ if (obj === null || prop === null)
522
+ continue;
523
+ if (obj.text !== 'this' || prop.type !== 'property_identifier')
524
+ continue;
525
+ const fieldName = prop.text;
526
+ // Prefer JSDoc @type annotation on the preceding sibling comment.
527
+ let typeName = null;
528
+ const prevSib = stmt.previousNamedSibling;
529
+ if (prevSib !== null && prevSib.type === 'comment') {
530
+ const m = /@type\s*\{([^}]+)\}/.exec(prevSib.text);
531
+ if (m?.[1])
532
+ typeName = m[1].trim();
533
+ }
534
+ // Fall back to constructor inference from `new Y()`.
535
+ if (typeName === null && right.type === 'new_expression') {
536
+ const ctor = right.childForFieldName('constructor');
537
+ if (ctor !== null && ctor.type === 'identifier')
538
+ typeName = ctor.text;
539
+ }
540
+ if (typeName === null)
541
+ continue;
542
+ out.push({
543
+ '@type-binding.name': syntheticCapture('@type-binding.name', prop, fieldName),
544
+ '@type-binding.type': syntheticCapture('@type-binding.type', prop, typeName),
545
+ // Anchor: positioned inside the constructor body so tsBindingScopeFor
546
+ // can walk up from the Function (constructor) scope to the Class scope.
547
+ '@type-binding.class-field': syntheticCapture('@type-binding.class-field', stmt, '1'),
548
+ });
549
+ }
550
+ }
551
+ }
552
+ // ─── Main emitter ──────────────────────────────────────────────────────────
553
+ export function emitJsScopeCaptures(sourceText, filePath, cachedTree) {
554
+ let tree = cachedTree;
555
+ if (tree !== undefined && !jsCachedTreeMatchesGrammar(tree)) {
556
+ tree = undefined;
557
+ }
558
+ if (tree === undefined) {
559
+ tree = parseSourceSafe(getJsParser(filePath), sourceText, undefined, {
560
+ bufferSize: getTreeSitterBufferSize(sourceText),
561
+ });
562
+ }
563
+ const rawMatches = getJsScopeQuery(filePath).matches(tree.rootNode);
564
+ const out = [];
565
+ for (const m of rawMatches) {
566
+ const grouped = {};
567
+ for (const c of m.captures) {
568
+ const tag = '@' + c.name;
569
+ grouped[tag] = nodeToCapture(tag, c.node);
570
+ }
571
+ if (Object.keys(grouped).length === 0)
572
+ continue;
573
+ // Decompose ESM import_statement / re-export export_statement.
574
+ if (grouped['@import.statement'] !== undefined) {
575
+ const stmtCapture = grouped['@import.statement'];
576
+ const stmtNode = findNodeAtRange(tree.rootNode, stmtCapture.range, 'import_statement') ??
577
+ findNodeAtRange(tree.rootNode, stmtCapture.range, 'export_statement');
578
+ if (stmtNode !== null) {
579
+ const decomposed = splitImportStatement(stmtNode);
580
+ for (const d of decomposed)
581
+ out.push(d);
582
+ }
583
+ continue;
584
+ }
585
+ // Decompose dynamic import() calls.
586
+ if (grouped['@import.dynamic'] !== undefined) {
587
+ const dynCapture = grouped['@import.dynamic'];
588
+ const callNode = findNodeAtRange(tree.rootNode, dynCapture.range, 'call_expression');
589
+ if (callNode !== null) {
590
+ const decomposed = splitImportStatement(callNode);
591
+ for (const d of decomposed)
592
+ out.push(d);
593
+ }
594
+ continue;
595
+ }
596
+ // Filter @reference.read.member false-positives.
597
+ if (grouped['@reference.read.member'] !== undefined) {
598
+ const anchor = grouped['@reference.read.member'];
599
+ const memberNode = findNodeAtRange(tree.rootNode, anchor.range, 'member_expression');
600
+ if (memberNode === null || !shouldEmitReadMember(memberNode)) {
601
+ continue;
602
+ }
603
+ }
604
+ // Synthesize arity metadata on function-like declarations.
605
+ const declAnchor = pickFirstDefined(grouped, FUNCTION_DECL_TAGS);
606
+ if (declAnchor !== undefined) {
607
+ const fnNode = findFunctionNode(tree.rootNode, declAnchor.range);
608
+ if (fnNode !== null) {
609
+ const arity = computeTsArityMetadata(fnNode);
610
+ if (arity.parameterCount !== undefined) {
611
+ grouped['@declaration.parameter-count'] = syntheticCapture('@declaration.parameter-count', fnNode, String(arity.parameterCount));
612
+ }
613
+ if (arity.requiredParameterCount !== undefined) {
614
+ grouped['@declaration.required-parameter-count'] = syntheticCapture('@declaration.required-parameter-count', fnNode, String(arity.requiredParameterCount));
615
+ }
616
+ if (arity.parameterTypes !== undefined) {
617
+ grouped['@declaration.parameter-types'] = syntheticCapture('@declaration.parameter-types', fnNode, JSON.stringify(arity.parameterTypes));
618
+ }
619
+ }
620
+ }
621
+ // Synthesize @reference.arity on callsites.
622
+ const callAnchor = pickFirstDefined(grouped, CALL_TAGS);
623
+ if (callAnchor !== undefined && grouped['@reference.arity'] === undefined) {
624
+ const callNode = findNodeAtRange(tree.rootNode, callAnchor.range, 'call_expression') ??
625
+ findNodeAtRange(tree.rootNode, callAnchor.range, 'new_expression');
626
+ if (callNode !== null) {
627
+ const argList = callNode.childForFieldName('arguments');
628
+ const args = argList === null
629
+ ? []
630
+ : argList.namedChildren.filter((c) => c !== null && c.type !== 'comment');
631
+ grouped['@reference.arity'] = syntheticCapture('@reference.arity', callNode, String(args.length));
632
+ grouped['@reference.parameter-types'] = syntheticCapture('@reference.parameter-types', callNode, JSON.stringify(args.map(inferArgType)));
633
+ }
634
+ }
635
+ out.push(grouped);
636
+ // Synthesize `this` receiver type-bindings on class member functions.
637
+ const scopeFnAnchor = grouped['@scope.function'];
638
+ if (scopeFnAnchor !== undefined) {
639
+ const fnNode = findFunctionNode(tree.rootNode, scopeFnAnchor.range);
640
+ if (fnNode !== null) {
641
+ const synth = synthesizeTsReceiverBinding(fnNode);
642
+ if (synth !== null)
643
+ out.push(synth);
644
+ }
645
+ }
646
+ }
647
+ // Post-query synthesis passes.
648
+ synthesizeCjsImports(tree.rootNode, out);
649
+ synthesizeJsDocBindings(tree.rootNode, out);
650
+ synthesizeConstructorFieldBindings(tree.rootNode, out);
651
+ synthesizeDestructuringBindings(tree.rootNode, out);
652
+ synthesizeForOfMapTupleBindings(tree.rootNode, out);
653
+ synthesizeInstanceofNarrowings(tree.rootNode, out);
654
+ return out;
655
+ }
@@ -0,0 +1,29 @@
1
+ /**
2
+ * Import-target resolver for JavaScript.
3
+ *
4
+ * Delegates to the TypeScript `resolveTsTarget` standard-strategy resolver
5
+ * with `language: SupportedLanguages.JavaScript` so the resolver tries
6
+ * `.js` / `.jsx` extensions in addition to (or instead of) `.ts` / `.tsx`.
7
+ *
8
+ * The `TsResolveContext.language` flag already exists in `import-target.ts`
9
+ * and the resolver (`resolveImportPath`) already branches on it — this
10
+ * adapter just wires the right value in.
11
+ *
12
+ * CJS `require()` calls reference the same module-path strings as ESM
13
+ * `import` statements, so the resolver handles them uniformly without any
14
+ * CJS-specific logic here.
15
+ *
16
+ * No `tsconfig.json` path-alias support (JavaScript projects don't use
17
+ * `tsconfig.json` compilerOptions.paths in general). Projects that DO use
18
+ * tsconfig-based aliases alongside JavaScript can still resolve via the
19
+ * standard extension-suffix fallback; the alias branch is a no-op when
20
+ * `tsconfigPaths` is null.
21
+ */
22
+ import { type TsResolveContext } from '../typescript/import-target.js';
23
+ export type JsResolveContext = TsResolveContext;
24
+ /**
25
+ * Build a memoized `resolveImportTarget` adapter for JavaScript.
26
+ * Caches the derived arrays and per-pass resolve cache across
27
+ * `resolveImportTarget` calls within a single workspace pass.
28
+ */
29
+ export declare function makeJsResolveImportTarget(): (targetRaw: string, fromFile: string, allFilePaths: ReadonlySet<string>, resolutionConfig?: unknown) => string | readonly string[] | null;