docsgov 0.1.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 (159) hide show
  1. package/README.md +242 -0
  2. package/dist/apispec/apispec.js +401 -0
  3. package/dist/apispec/apispec.test.js +444 -0
  4. package/dist/apispec/errors.js +17 -0
  5. package/dist/apispec/index.js +2 -0
  6. package/dist/check/doclinks.js +167 -0
  7. package/dist/check/index.js +8 -0
  8. package/dist/check/run.js +391 -0
  9. package/dist/check/run.test.js +513 -0
  10. package/dist/check/suggest.js +134 -0
  11. package/dist/check/suggest.test.js +92 -0
  12. package/dist/check/tokens.js +125 -0
  13. package/dist/cmd/main.js +330 -0
  14. package/dist/cmd/main.test.js +422 -0
  15. package/dist/codeq/cache.js +71 -0
  16. package/dist/codeq/cache.test.js +67 -0
  17. package/dist/codeq/errors.js +52 -0
  18. package/dist/codeq/grammars/tree-sitter-go.wasm +0 -0
  19. package/dist/codeq/grammars/tree-sitter-java.wasm +0 -0
  20. package/dist/codeq/grammars/tree-sitter-javascript.wasm +0 -0
  21. package/dist/codeq/grammars/tree-sitter-tsx.wasm +0 -0
  22. package/dist/codeq/grammars/tree-sitter-typescript.wasm +0 -0
  23. package/dist/codeq/index.js +11 -0
  24. package/dist/codeq/resolve.test.js +109 -0
  25. package/dist/codeq/resolver.js +128 -0
  26. package/dist/codeq/resolver.test.js +124 -0
  27. package/dist/codeq/resolvers/go.js +242 -0
  28. package/dist/codeq/resolvers/go.test.js +143 -0
  29. package/dist/codeq/resolvers/java.js +349 -0
  30. package/dist/codeq/resolvers/java.test.js +138 -0
  31. package/dist/codeq/resolvers/java_queries.js +63 -0
  32. package/dist/codeq/resolvers/javascript.js +412 -0
  33. package/dist/codeq/resolvers/javascript.test.js +125 -0
  34. package/dist/codeq/resolvers/javascript_queries.js +46 -0
  35. package/dist/codeq/resolvers/typescript.js +366 -0
  36. package/dist/codeq/resolvers/typescript.test.js +180 -0
  37. package/dist/codeq/resolvers/typescript_queries.js +78 -0
  38. package/dist/codeq/signature.js +50 -0
  39. package/dist/codeq/signature.test.js +50 -0
  40. package/dist/codeq/suggest.js +96 -0
  41. package/dist/codeq/treesitter.js +122 -0
  42. package/dist/codeq/treesitter.test.js +118 -0
  43. package/dist/config/config.js +74 -0
  44. package/dist/config/config.test.js +98 -0
  45. package/dist/config/fs.js +116 -0
  46. package/dist/config/glob.js +82 -0
  47. package/dist/config/glob.test.js +61 -0
  48. package/dist/config/index.js +4 -0
  49. package/dist/dedup/analyzer/analyzer.js +533 -0
  50. package/dist/dedup/analyzer/analyzer.test.js +530 -0
  51. package/dist/dedup/analyzer/canonical.js +74 -0
  52. package/dist/dedup/analyzer/canonical.test.js +70 -0
  53. package/dist/dedup/analyzer/cosine_clusters.js +169 -0
  54. package/dist/dedup/analyzer/cosine_clusters.test.js +131 -0
  55. package/dist/dedup/analyzer/distinctive.js +85 -0
  56. package/dist/dedup/analyzer/distinctive.test.js +49 -0
  57. package/dist/dedup/analyzer/exact_clusters.js +63 -0
  58. package/dist/dedup/analyzer/exact_clusters.test.js +81 -0
  59. package/dist/dedup/analyzer/index.js +14 -0
  60. package/dist/dedup/analyzer/multiplicity.js +110 -0
  61. package/dist/dedup/analyzer/multiplicity.test.js +123 -0
  62. package/dist/dedup/analyzer/order.js +22 -0
  63. package/dist/dedup/analyzer/partial_overlaps.js +65 -0
  64. package/dist/dedup/analyzer/partial_overlaps.test.js +161 -0
  65. package/dist/dedup/analyzer/preview.js +84 -0
  66. package/dist/dedup/analyzer/preview.test.js +46 -0
  67. package/dist/dedup/analyzer/safety.js +27 -0
  68. package/dist/dedup/analyzer/safety.test.js +39 -0
  69. package/dist/dedup/config.js +18 -0
  70. package/dist/dedup/configload.js +299 -0
  71. package/dist/dedup/configload.test.js +410 -0
  72. package/dist/dedup/dedup.index.test.js +203 -0
  73. package/dist/dedup/dedup.js +143 -0
  74. package/dist/dedup/dedup.test.js +212 -0
  75. package/dist/dedup/dedupcfg/config.js +112 -0
  76. package/dist/dedup/dedupcfg/config.test.js +70 -0
  77. package/dist/dedup/dedupcfg/index.js +1 -0
  78. package/dist/dedup/deduptypes/index.js +1 -0
  79. package/dist/dedup/deduptypes/types.js +9 -0
  80. package/dist/dedup/deduptypes/types.test.js +34 -0
  81. package/dist/dedup/embedder/cache.js +23 -0
  82. package/dist/dedup/embedder/cache.test.js +50 -0
  83. package/dist/dedup/embedder/constants.js +10 -0
  84. package/dist/dedup/embedder/embedder.js +76 -0
  85. package/dist/dedup/embedder/embedder.mock.test.js +128 -0
  86. package/dist/dedup/embedder/embedder.test.js +96 -0
  87. package/dist/dedup/embedder/errors.js +20 -0
  88. package/dist/dedup/embedder/errors.test.js +35 -0
  89. package/dist/dedup/embedder/index.js +4 -0
  90. package/dist/dedup/embedder/session.js +78 -0
  91. package/dist/dedup/embedder/session.test.js +172 -0
  92. package/dist/dedup/gitignore.js +97 -0
  93. package/dist/dedup/gitignore.test.js +98 -0
  94. package/dist/dedup/index.js +11 -0
  95. package/dist/dedup/indexdb/errors.js +48 -0
  96. package/dist/dedup/indexdb/index.js +6 -0
  97. package/dist/dedup/indexdb/indexdb.js +302 -0
  98. package/dist/dedup/indexdb/indexdb.test.js +739 -0
  99. package/dist/dedup/indexdb/load.js +110 -0
  100. package/dist/dedup/indexdb/migrations.js +58 -0
  101. package/dist/dedup/indexdb/schema.js +83 -0
  102. package/dist/dedup/indexer/index.js +9 -0
  103. package/dist/dedup/indexer/indexer.js +501 -0
  104. package/dist/dedup/indexer/indexer.test.js +510 -0
  105. package/dist/dedup/indexer/links.js +89 -0
  106. package/dist/dedup/mdsection/anchor.js +60 -0
  107. package/dist/dedup/mdsection/anchor.test.js +39 -0
  108. package/dist/dedup/mdsection/blocks.js +409 -0
  109. package/dist/dedup/mdsection/blocks.test.js +359 -0
  110. package/dist/dedup/mdsection/index.js +4 -0
  111. package/dist/dedup/mdsection/parse.js +21 -0
  112. package/dist/dedup/mdsection/section.js +234 -0
  113. package/dist/dedup/mdsection/section.test.js +221 -0
  114. package/dist/dedup/report/floatfmt.js +71 -0
  115. package/dist/dedup/report/floatfmt.test.js +42 -0
  116. package/dist/dedup/report/index.js +8 -0
  117. package/dist/dedup/report/quote.js +77 -0
  118. package/dist/dedup/report/quote.test.js +67 -0
  119. package/dist/dedup/report/text.js +251 -0
  120. package/dist/dedup/report/text.test.js +420 -0
  121. package/dist/dedup/report_types.js +8 -0
  122. package/dist/dedup/sectionid/index.js +1 -0
  123. package/dist/dedup/sectionid/sectionid.js +16 -0
  124. package/dist/dedup/sectionid/sectionid.test.js +49 -0
  125. package/dist/guard/api/errors.js +12 -0
  126. package/dist/guard/api/index.js +2 -0
  127. package/dist/guard/api/parser.js +81 -0
  128. package/dist/guard/api/parser.test.js +58 -0
  129. package/dist/guard/api/types.js +1 -0
  130. package/dist/guard/code/errors.js +16 -0
  131. package/dist/guard/code/index.js +2 -0
  132. package/dist/guard/code/parser.js +54 -0
  133. package/dist/guard/code/parser.test.js +111 -0
  134. package/dist/guard/code/types.js +6 -0
  135. package/dist/index.js +1 -0
  136. package/dist/index.test.js +5 -0
  137. package/dist/repo/boundary.js +92 -0
  138. package/dist/repo/boundary.test.js +65 -0
  139. package/dist/repo/errors.js +56 -0
  140. package/dist/repo/errors.test.js +85 -0
  141. package/dist/repo/exists.test.js +72 -0
  142. package/dist/repo/filename.js +46 -0
  143. package/dist/repo/filename.test.js +39 -0
  144. package/dist/repo/fs.js +53 -0
  145. package/dist/repo/index.js +7 -0
  146. package/dist/repo/overlay.js +36 -0
  147. package/dist/repo/overlay.test.js +80 -0
  148. package/dist/repo/repo.js +353 -0
  149. package/dist/repo/repo.test.js +255 -0
  150. package/dist/repo/testutil.js +27 -0
  151. package/dist/repo/write.test.js +125 -0
  152. package/dist/report/color.js +73 -0
  153. package/dist/report/index.js +1 -0
  154. package/dist/report/report.js +112 -0
  155. package/dist/report/report.test.js +368 -0
  156. package/dist/violation/index.js +1 -0
  157. package/dist/violation/types.js +22 -0
  158. package/dist/violation/types.test.js +70 -0
  159. package/package.json +48 -0
@@ -0,0 +1,366 @@
1
+ // Port of internal/codeq/typescript_lang.go: the TypeScript per-language
2
+ // Resolver. It resolves CodeRefs against .ts (typescript grammar) / .tsx (tsx
3
+ // grammar) source via tree-sitter.
4
+ //
5
+ // This module is grammar-agnostic in code but grammar-bound in data: the factory
6
+ // createTSResolver(language) is called ONCE PER GRAMMAR (typescript and tsx are
7
+ // distinct Languages), so each resolver carries its own compiled query set
8
+ // (buildTSQueries) bound to the Language it was built from. The dispatch layer
9
+ // (resolver.ts) routes .ts→typescript resolver and .tsx→tsx resolver.
10
+ //
11
+ // DEVIATION from Go (recorded in resolver.ts): resolve() is SYNC and receives
12
+ // the already-read+decoded `source`; the cache.Read + ErrFileNotFound prelude
13
+ // lives in the dispatch layer. `path` is passed ONLY to build ParseFailedError.
14
+ import { ParseFailedError } from "../errors.js";
15
+ import { signaturesMatch } from "../signature.js";
16
+ import { collectCapture, collectOwned, refKind, suggestFromExtractors, } from "../suggest.js";
17
+ import { Parser, nodeText, parseTree, runQuery, } from "../treesitter.js";
18
+ import { buildTSQueries } from "./typescript_queries.js";
19
+ /**
20
+ * createTSResolver builds a Resolver for one TypeScript dialect grammar,
21
+ * compiling that grammar's queries eagerly. Called once per grammar (typescript
22
+ * for .ts, tsx for .tsx). The analogue of Go's newTSResolver.
23
+ */
24
+ export function createTSResolver(language) {
25
+ const q = buildTSQueries(language);
26
+ return new TSResolver(language, q);
27
+ }
28
+ class TSResolver {
29
+ language;
30
+ q;
31
+ constructor(language, q) {
32
+ this.language = language;
33
+ this.q = q;
34
+ }
35
+ /**
36
+ * resolve parses `source` and delegates based on which facets of `ref` are
37
+ * set. Mirrors tsResolver.Resolve's dispatch precedence exactly.
38
+ *
39
+ * Throws ParseFailedError(path) when the parse tree has ERROR nodes (Go's
40
+ * ErrParseFailed). Returns true=found, false=absent otherwise.
41
+ */
42
+ resolve(source, path, ref) {
43
+ const parser = new Parser();
44
+ parser.setLanguage(this.language);
45
+ const root = parseTree(parser, source);
46
+ if (root.hasError) {
47
+ throw new ParseFailedError(path);
48
+ }
49
+ // Dispatch precedence matches typescript_lang.go's Resolve switch.
50
+ if (ref.Member !== "" && ref.Param !== "") {
51
+ return this.findMemberParam(root, ref.Symbol, ref.Member, ref.Param, ref.Signature);
52
+ }
53
+ if (ref.Member !== "") {
54
+ return this.findMember(root, ref.Symbol, ref.Member, ref.Signature);
55
+ }
56
+ if (ref.Signature !== undefined && ref.Param !== "") {
57
+ return this.findBareSignatureParam(root, ref.Symbol, ref.Param, ref.Signature);
58
+ }
59
+ if (ref.Signature !== undefined) {
60
+ return this.findBareSignature(root, ref.Symbol, ref.Signature);
61
+ }
62
+ if (ref.Param !== "") {
63
+ return this.findParam(root, ref.Symbol, ref.Param);
64
+ }
65
+ return this.findSymbol(root, ref.Symbol);
66
+ }
67
+ /**
68
+ * suggest lists same-kind candidate names for a not-found ref (best-effort; a
69
+ * parse error yields no candidates). Params are gathered from BOTH sources
70
+ * findParam scans — methods named `fn` and top-level functions named `fn`.
71
+ */
72
+ suggest(source, _path, ref) {
73
+ const parser = new Parser();
74
+ parser.setLanguage(this.language);
75
+ const root = parseTree(parser, source);
76
+ if (root.hasError) {
77
+ return { kind: refKind(ref), candidates: [] };
78
+ }
79
+ return suggestFromExtractors(root, ref, {
80
+ symbolNames: (r) => collectCapture(r, this.q.symbol, "name"),
81
+ memberNames: (r, owner) => collectOwned(r, this.q.member, owner, "owner", "name"),
82
+ paramNames: (r, fn) => this.collectParamNames(r, fn),
83
+ });
84
+ }
85
+ /**
86
+ * collectParamNames lists the parameter names of every method or top-level
87
+ * function named `fn` — the union findParam searches. Destructured params are
88
+ * skipped (tsParamNames mirrors tsParamExists's identifier-only resolution).
89
+ */
90
+ collectParamNames(root, fn) {
91
+ const out = [];
92
+ for (const m of runQuery(root, this.q.memberDecl)) {
93
+ const mname = m.captures["mname"];
94
+ const mdecl = m.captures["mdecl"];
95
+ if (mname !== undefined && mdecl !== undefined && nodeText(mname) === fn) {
96
+ out.push(...tsParamNames(mdecl));
97
+ }
98
+ }
99
+ for (const m of runQuery(root, this.q.funcDecl)) {
100
+ const fname = m.captures["fname"];
101
+ const fdecl = m.captures["fdecl"];
102
+ if (fname !== undefined && fdecl !== undefined && nodeText(fname) === fn) {
103
+ out.push(...tsParamNames(fdecl));
104
+ }
105
+ }
106
+ return out;
107
+ }
108
+ // findSymbol searches for a top-level declaration named `name`.
109
+ findSymbol(root, name) {
110
+ for (const m of runQuery(root, this.q.symbol)) {
111
+ const n = m.captures["name"];
112
+ if (n !== undefined && nodeText(n) === name) {
113
+ return true;
114
+ }
115
+ }
116
+ return false;
117
+ }
118
+ // findMember searches for a field or method named `member` on the container
119
+ // named `symbol`. When `sig` is given, only method overloads whose param types
120
+ // satisfy signaturesMatch are considered; fields are matched by name only.
121
+ findMember(root, symbol, member, sig) {
122
+ if (sig === undefined) {
123
+ // Name-only: the flat member query matches fields AND methods.
124
+ for (const m of runQuery(root, this.q.member)) {
125
+ const owner = m.captures["owner"];
126
+ const name = m.captures["name"];
127
+ if (owner !== undefined &&
128
+ name !== undefined &&
129
+ nodeText(owner) === symbol &&
130
+ nodeText(name) === member) {
131
+ return true;
132
+ }
133
+ }
134
+ return false;
135
+ }
136
+ // Signature-filtered: iterate method declarations and check param types.
137
+ for (const m of runQuery(root, this.q.memberDecl)) {
138
+ const owner = m.captures["owner"];
139
+ const mname = m.captures["mname"];
140
+ const mdecl = m.captures["mdecl"];
141
+ if (owner !== undefined &&
142
+ mname !== undefined &&
143
+ mdecl !== undefined &&
144
+ nodeText(owner) === symbol &&
145
+ nodeText(mname) === member &&
146
+ signaturesMatch(sig, collectTSParamTypes(mdecl))) {
147
+ return true;
148
+ }
149
+ }
150
+ return false;
151
+ }
152
+ // findMemberParam searches for a parameter named `paramName` on a method named
153
+ // `member` of the container named `symbol`. When `sig` is given, only the
154
+ // overload whose param types satisfy signaturesMatch is considered.
155
+ findMemberParam(root, symbol, member, paramName, sig) {
156
+ for (const m of runQuery(root, this.q.memberDecl)) {
157
+ const owner = m.captures["owner"];
158
+ const mname = m.captures["mname"];
159
+ const mdecl = m.captures["mdecl"];
160
+ if (owner === undefined ||
161
+ mname === undefined ||
162
+ mdecl === undefined ||
163
+ nodeText(owner) !== symbol ||
164
+ nodeText(mname) !== member) {
165
+ continue;
166
+ }
167
+ if (sig !== undefined && !signaturesMatch(sig, collectTSParamTypes(mdecl))) {
168
+ continue;
169
+ }
170
+ if (tsParamExists(mdecl, paramName)) {
171
+ return true;
172
+ }
173
+ }
174
+ return false;
175
+ }
176
+ // findParam searches for a parameter named `paramName` on any method or
177
+ // top-level function named `symbol`.
178
+ findParam(root, symbol, paramName) {
179
+ // (a) Member decls: any method on a class with mname==symbol.
180
+ for (const m of runQuery(root, this.q.memberDecl)) {
181
+ const mname = m.captures["mname"];
182
+ const mdecl = m.captures["mdecl"];
183
+ if (mname !== undefined &&
184
+ mdecl !== undefined &&
185
+ nodeText(mname) === symbol &&
186
+ tsParamExists(mdecl, paramName)) {
187
+ return true;
188
+ }
189
+ }
190
+ // (b) Top-level function decls: fname==symbol.
191
+ for (const m of runQuery(root, this.q.funcDecl)) {
192
+ const fname = m.captures["fname"];
193
+ const fdecl = m.captures["fdecl"];
194
+ if (fname !== undefined &&
195
+ fdecl !== undefined &&
196
+ nodeText(fname) === symbol &&
197
+ tsParamExists(fdecl, paramName)) {
198
+ return true;
199
+ }
200
+ }
201
+ return false;
202
+ }
203
+ // findBareSignature resolves a bare-symbol + signature ref (no member): either
204
+ // a constructor (method named "constructor" on the class) OR a top-level
205
+ // function named `symbol`. First signature match wins.
206
+ findBareSignature(root, symbol, sig) {
207
+ // (a) Constructor: method decls on `symbol` named "constructor".
208
+ for (const m of runQuery(root, this.q.memberDecl)) {
209
+ const owner = m.captures["owner"];
210
+ const mname = m.captures["mname"];
211
+ const mdecl = m.captures["mdecl"];
212
+ if (owner !== undefined &&
213
+ mname !== undefined &&
214
+ mdecl !== undefined &&
215
+ nodeText(owner) === symbol &&
216
+ nodeText(mname) === "constructor" &&
217
+ signaturesMatch(sig, collectTSParamTypes(mdecl))) {
218
+ return true;
219
+ }
220
+ }
221
+ // (b) Top-level function named `symbol`.
222
+ for (const m of runQuery(root, this.q.funcDecl)) {
223
+ const fname = m.captures["fname"];
224
+ const fdecl = m.captures["fdecl"];
225
+ if (fname !== undefined &&
226
+ fdecl !== undefined &&
227
+ nodeText(fname) === symbol &&
228
+ signaturesMatch(sig, collectTSParamTypes(fdecl))) {
229
+ return true;
230
+ }
231
+ }
232
+ return false;
233
+ }
234
+ // findBareSignatureParam: same two sources as findBareSignature, requires a
235
+ // signature match, then reports whether paramName exists in that declaration.
236
+ findBareSignatureParam(root, symbol, paramName, sig) {
237
+ // (a) Constructor.
238
+ for (const m of runQuery(root, this.q.memberDecl)) {
239
+ const owner = m.captures["owner"];
240
+ const mname = m.captures["mname"];
241
+ const mdecl = m.captures["mdecl"];
242
+ if (owner !== undefined &&
243
+ mname !== undefined &&
244
+ mdecl !== undefined &&
245
+ nodeText(owner) === symbol &&
246
+ nodeText(mname) === "constructor" &&
247
+ signaturesMatch(sig, collectTSParamTypes(mdecl))) {
248
+ return tsParamExists(mdecl, paramName);
249
+ }
250
+ }
251
+ // (b) Top-level function.
252
+ for (const m of runQuery(root, this.q.funcDecl)) {
253
+ const fname = m.captures["fname"];
254
+ const fdecl = m.captures["fdecl"];
255
+ if (fname !== undefined &&
256
+ fdecl !== undefined &&
257
+ nodeText(fname) === symbol &&
258
+ signaturesMatch(sig, collectTSParamTypes(fdecl))) {
259
+ return tsParamExists(fdecl, paramName);
260
+ }
261
+ }
262
+ return false;
263
+ }
264
+ }
265
+ /**
266
+ * collectTSParamTypes returns the declared parameter types of a method/function
267
+ * declaration node in document order. The name is irrelevant; the type is the
268
+ * text of each parameter's type_annotation child with the leading ":" dropped
269
+ * (i.e. the first named child of the type_annotation). required_parameter and
270
+ * optional_parameter are both handled; rest params carry their own "T[]"
271
+ * annotation, so no special varargs handling is needed. An untyped parameter
272
+ * contributes "".
273
+ */
274
+ function collectTSParamTypes(decl) {
275
+ const fp = firstChildOfType(decl, "formal_parameters");
276
+ if (fp === null) {
277
+ return [];
278
+ }
279
+ const types = [];
280
+ for (let i = 0; i < fp.childCount; i++) {
281
+ const c = fp.child(i);
282
+ if (c === null) {
283
+ continue;
284
+ }
285
+ if (c.type !== "required_parameter" && c.type !== "optional_parameter") {
286
+ continue;
287
+ }
288
+ let ta = c.childForFieldName("type"); // type_annotation node, or null if untyped
289
+ if (ta === null) {
290
+ // No "type" field: scan children for a type_annotation.
291
+ ta = firstChildOfType(c, "type_annotation");
292
+ }
293
+ if (ta === null || ta.namedChildCount === 0) {
294
+ types.push("");
295
+ continue;
296
+ }
297
+ const typeNode = ta.namedChild(0); // the type node after ":"
298
+ types.push(typeNode === null ? "" : nodeText(typeNode));
299
+ }
300
+ return types;
301
+ }
302
+ /**
303
+ * tsParamNames lists the resolvable parameter names in the formal_parameters of
304
+ * `decl`, in document order. It mirrors tsParamExists's resolvable set (identifier
305
+ * patterns only; destructured params skipped), so a suggested name is always one
306
+ * tsParamExists would have matched.
307
+ */
308
+ function tsParamNames(decl) {
309
+ const fp = firstChildOfType(decl, "formal_parameters");
310
+ if (fp === null) {
311
+ return [];
312
+ }
313
+ const names = [];
314
+ for (let i = 0; i < fp.childCount; i++) {
315
+ const c = fp.child(i);
316
+ if (c === null) {
317
+ continue;
318
+ }
319
+ if (c.type !== "required_parameter" && c.type !== "optional_parameter") {
320
+ continue;
321
+ }
322
+ const pat = c.childForFieldName("pattern");
323
+ if (pat !== null && pat.type === "identifier") {
324
+ names.push(nodeText(pat));
325
+ }
326
+ }
327
+ return names;
328
+ }
329
+ /**
330
+ * tsParamExists reports whether a parameter named `name` exists in the
331
+ * formal_parameters of `decl`. Only identifier patterns are resolvable by name;
332
+ * destructured parameters are skipped (existence-only non-goal).
333
+ */
334
+ function tsParamExists(decl, name) {
335
+ const fp = firstChildOfType(decl, "formal_parameters");
336
+ if (fp === null) {
337
+ return false;
338
+ }
339
+ for (let i = 0; i < fp.childCount; i++) {
340
+ const c = fp.child(i);
341
+ if (c === null) {
342
+ continue;
343
+ }
344
+ if (c.type !== "required_parameter" && c.type !== "optional_parameter") {
345
+ continue;
346
+ }
347
+ const pat = c.childForFieldName("pattern");
348
+ if (pat === null) {
349
+ continue;
350
+ }
351
+ if (pat.type === "identifier" && nodeText(pat) === name) {
352
+ return true;
353
+ }
354
+ }
355
+ return false;
356
+ }
357
+ /** firstChildOfType returns the first direct child of `n` with type `t`, or null. */
358
+ function firstChildOfType(n, t) {
359
+ for (let i = 0; i < n.childCount; i++) {
360
+ const c = n.child(i);
361
+ if (c !== null && c.type === t) {
362
+ return c;
363
+ }
364
+ }
365
+ return null;
366
+ }
@@ -0,0 +1,180 @@
1
+ import { describe, expect, it } from "vitest";
2
+ import { ParseFailedError } from "../errors.js";
3
+ import { loadGrammar } from "../treesitter.js";
4
+ import { createTSResolver } from "./typescript.js";
5
+ // WHY: TypeScript is the code guard's existence oracle for .ts/.tsx refs. The
6
+ // hand-authored tree-sitter queries (per-grammar compiled) would silently return
7
+ // false on a wrong node/field name, turning every TS ref into a false "symbol
8
+ // absent" violation. These tests lock symbol/member/param resolution for BOTH
9
+ // the typescript and tsx grammars (distinct Languages), overload signature
10
+ // disambiguation, and that a bad parse surfaces ParseFailedError (not false) so
11
+ // callers distinguish "could not check" from "genuinely missing". Ported from
12
+ // internal/codeq/typescript_lang_test.go.
13
+ //
14
+ // Port-specific: Go runs each matrix against r.Resolve(ctx, fsys, ref) on the
15
+ // full DefaultResolver (which routes by extension). Here the dispatch layer is
16
+ // wired separately (WireGate); we test the per-language Resolver directly,
17
+ // building one resolver per grammar and feeding the source string in — exactly
18
+ // what the dispatch layer will do per extension. The `path` arg is only used to
19
+ // build ParseFailedError, so it is cosmetic for the found/absent cases.
20
+ const tsSrc = `export interface AbRequest { id: number; }
21
+ export type Id = string;
22
+ export enum Color { Red, Green }
23
+ export const VERSION = "1";
24
+ export function top(a: string): void {}
25
+ export abstract class Base { abstract run(): void; }
26
+ export class C {
27
+ field: number = 0;
28
+ get(): string { return ""; }
29
+ get(id: Integer): string { return ""; }
30
+ get(names: string[], id: Integer, req: AbRequest): string { return ""; }
31
+ constructor(names: string[], req: AbRequest) {}
32
+ }
33
+ `;
34
+ // Each dialect is a distinct Language and gets its own resolver — mirroring the
35
+ // Go matrix that runs every case against both "a.ts" (typescript grammar) and
36
+ // "a.tsx" (tsx grammar). We label by dialect for clear failure attribution.
37
+ async function resolverFor(dialect) {
38
+ const lang = await loadGrammar(dialect);
39
+ return createTSResolver(lang);
40
+ }
41
+ const dialects = [
42
+ { dialect: "typescript", path: "a.ts" },
43
+ { dialect: "tsx", path: "a.tsx" },
44
+ ];
45
+ describe("TypeScript resolver — symbol/member/param", () => {
46
+ for (const { dialect, path } of dialects) {
47
+ describe(dialect, () => {
48
+ // Build one resolver per dialect; reuse across the matrix.
49
+ let r;
50
+ const check = (ref, want) => expect(r.resolve(tsSrc, path, ref)).toBe(want);
51
+ const ref = (extra) => ({
52
+ Path: path,
53
+ Symbol: "",
54
+ Member: "",
55
+ Param: "",
56
+ ...extra,
57
+ });
58
+ it("resolves the matrix", async () => {
59
+ r = await resolverFor(dialect);
60
+ // Top-level symbols of every declaration kind.
61
+ check(ref({ Symbol: "C" }), true); // class
62
+ check(ref({ Symbol: "Base" }), true); // abstract class
63
+ check(ref({ Symbol: "AbRequest" }), true); // interface
64
+ check(ref({ Symbol: "Id" }), true); // type alias
65
+ check(ref({ Symbol: "Color" }), true); // enum
66
+ check(ref({ Symbol: "top" }), true); // function
67
+ check(ref({ Symbol: "VERSION" }), true); // const
68
+ check(ref({ Symbol: "Nope" }), false); // absent symbol
69
+ // Members.
70
+ check(ref({ Symbol: "C", Member: "get" }), true); // method
71
+ check(ref({ Symbol: "C", Member: "field" }), true); // field
72
+ check(ref({ Symbol: "AbRequest", Member: "id" }), true); // interface member
73
+ check(ref({ Symbol: "C", Member: "nope" }), false); // absent member
74
+ // Params.
75
+ check(ref({ Symbol: "C", Member: "get", Param: "id" }), true); // member param
76
+ check(ref({ Symbol: "C", Member: "get", Param: "zzz" }), false); // absent param
77
+ check(ref({ Symbol: "top", Param: "a" }), true); // top-level fn param
78
+ });
79
+ });
80
+ }
81
+ });
82
+ // WHY: "any declared signature" overload matching must filter method_definition
83
+ // AND method_signature declarations by normalized param-type list, or a signature
84
+ // ref matches the wrong overload (or any overload by name) — a silent false pass.
85
+ describe("TypeScript resolver — overload signature on a member", () => {
86
+ for (const { dialect, path } of dialects) {
87
+ it(`${dialect}: disambiguates overloads by param types`, async () => {
88
+ const r = await resolverFor(dialect);
89
+ const ref = (sig, param) => ({
90
+ Path: path,
91
+ Symbol: "C",
92
+ Member: "get",
93
+ Param: param,
94
+ Signature: sig,
95
+ });
96
+ const must = (rf, want) => expect(r.resolve(tsSrc, path, rf)).toBe(want);
97
+ must(ref(["string[]", "Integer", "AbRequest"], ""), true); // 3-arg overload
98
+ must(ref([], ""), true); // zero-arg overload
99
+ must(ref(["Integer"], ""), true); // 1-arg overload
100
+ must(ref(["string[]", "string[]"], ""), false); // no matching overload
101
+ must(ref(["string[]", "Integer", "AbRequest"], "req"), true); // param of specific overload
102
+ must(ref(["Integer"], "req"), false); // param absent in that overload
103
+ });
104
+ }
105
+ });
106
+ // WHY: a signature on a BARE symbol means a constructor (class) or a function
107
+ // overload (top-level fn) — not the type. Without the dispatch it would fall to
108
+ // findSymbol, which matches the name and ignores the signature.
109
+ describe("TypeScript resolver — bare-symbol signature", () => {
110
+ for (const { dialect, path } of dialects) {
111
+ it(`${dialect}: matches constructor or function overload`, async () => {
112
+ const r = await resolverFor(dialect);
113
+ const base = (extra) => ({
114
+ Path: path,
115
+ Symbol: "",
116
+ Member: "",
117
+ Param: "",
118
+ ...extra,
119
+ });
120
+ const must = (rf, want) => expect(r.resolve(tsSrc, path, rf)).toBe(want);
121
+ // Constructor of class C: (string[], AbRequest).
122
+ must(base({ Symbol: "C", Signature: ["string[]", "AbRequest"] }), true);
123
+ must(base({ Symbol: "C", Signature: ["string[]"] }), false); // no matching constructor
124
+ must(base({ Symbol: "C", Signature: ["string[]", "AbRequest"], Param: "req" }), true); // constructor param
125
+ // Top-level function overload: top(string).
126
+ must(base({ Symbol: "top", Signature: ["string"] }), true);
127
+ must(base({ Symbol: "top", Signature: ["number"] }), false); // wrong sig
128
+ must(base({ Symbol: "top", Signature: ["string"], Param: "a" }), true); // overload param
129
+ // Regression: bare symbol, NO signature, still resolves the TYPE.
130
+ must(base({ Symbol: "C" }), true);
131
+ });
132
+ }
133
+ });
134
+ // WHY: tsMemberDeclQuerySrc captures both method_definition (bodied) and
135
+ // method_signature (declaration-only, no body) nodes. The shared tsSrc fixture
136
+ // only has bodied methods, so the method_signature branch was never exercised.
137
+ // This fixture includes declaration-only overload signatures to verify the "any
138
+ // declared signature" behaviour covers both node shapes.
139
+ describe("TypeScript resolver — overload-only (declaration) signatures", () => {
140
+ const overloadSrc = `
141
+ interface Options {}
142
+ class Svc {
143
+ fetch(id: number): string;
144
+ fetch(id: number, opts: Options): string;
145
+ fetch(id: number, opts?: Options): string { return ""; }
146
+ }
147
+ `;
148
+ for (const { dialect, path } of dialects) {
149
+ it(`${dialect}: matches method_signature (bodyless) overloads`, async () => {
150
+ const r = await resolverFor(dialect);
151
+ const ref = (sig) => ({
152
+ Path: path,
153
+ Symbol: "Svc",
154
+ Member: "fetch",
155
+ Param: "",
156
+ Signature: sig,
157
+ });
158
+ const must = (rf, want) => expect(r.resolve(overloadSrc, path, rf)).toBe(want);
159
+ must(ref(["number", "Options"]), true); // overload-only sig
160
+ must(ref(["number"]), true); // single-param overload-only sig
161
+ must(ref(["string", "string", "string"]), false); // non-existent overload
162
+ });
163
+ }
164
+ });
165
+ // WHY: a parse with ERROR nodes must surface ParseFailedError, NOT false —
166
+ // otherwise "could not check" is indistinguishable from "genuinely missing".
167
+ describe("TypeScript resolver — parse failure", () => {
168
+ for (const { dialect, path } of dialects) {
169
+ it(`${dialect}: throws ParseFailedError on invalid source`, async () => {
170
+ const r = await resolverFor(dialect);
171
+ const badPath = path.replace("a.", "bad.");
172
+ expect(() => r.resolve("class C { (((", badPath, {
173
+ Path: badPath,
174
+ Symbol: "C",
175
+ Member: "",
176
+ Param: "",
177
+ })).toThrow(ParseFailedError);
178
+ });
179
+ }
180
+ });
@@ -0,0 +1,78 @@
1
+ // Port of internal/codeq/typescript_queries.go: the per-grammar compiled query
2
+ // set for one TypeScript dialect. .ts (typescript grammar) and .tsx (tsx
3
+ // grammar) are DISTINCT tree-sitter languages, so a query compiled against one
4
+ // cannot run against a tree parsed with the other — hence per-grammar
5
+ // compilation (a fresh TSQueries per Language), not module-level singletons.
6
+ //
7
+ // The S-expression sources below are copied VERBATIM from the Go *_queries.go;
8
+ // the upstream wasm node-type/field names match, so they compile and match
9
+ // identically (validated by treesitter.test.ts).
10
+ import { compileQuery } from "../treesitter.js";
11
+ // tsSymbolQuerySrc matches top-level declarations. Class/interface/type-alias
12
+ // names are type_identifier; enum/function/const names are identifier.
13
+ const tsSymbolQuerySrc = `
14
+ (class_declaration name: (type_identifier) @name)
15
+ (abstract_class_declaration name: (type_identifier) @name)
16
+ (interface_declaration name: (type_identifier) @name)
17
+ (type_alias_declaration name: (type_identifier) @name)
18
+ (enum_declaration name: (identifier) @name)
19
+ (function_declaration name: (identifier) @name)
20
+ (function_signature name: (identifier) @name)
21
+ (lexical_declaration (variable_declarator name: (identifier) @name))
22
+ (variable_declaration (variable_declarator name: (identifier) @name))
23
+ `;
24
+ // tsMemberQuerySrc matches fields & methods on classes/abstract classes and
25
+ // members on interfaces. @owner = container name, @name = member name. Overload
26
+ // signatures (method_signature, abstract_method_signature) are captured too so
27
+ // name resolution succeeds for declaration-only members.
28
+ const tsMemberQuerySrc = `
29
+ (class_declaration name: (type_identifier) @owner body: (class_body [
30
+ (method_definition name: (property_identifier) @name)
31
+ (method_signature name: (property_identifier) @name)
32
+ (public_field_definition name: (property_identifier) @name)
33
+ ]))
34
+ (abstract_class_declaration name: (type_identifier) @owner body: (class_body [
35
+ (method_definition name: (property_identifier) @name)
36
+ (method_signature name: (property_identifier) @name)
37
+ (abstract_method_signature name: (property_identifier) @name)
38
+ (public_field_definition name: (property_identifier) @name)
39
+ ]))
40
+ (interface_declaration name: (type_identifier) @owner body: (interface_body [
41
+ (method_signature name: (property_identifier) @name)
42
+ (property_signature name: (property_identifier) @name)
43
+ ]))
44
+ `;
45
+ // tsMemberDeclQuerySrc captures the method/signature declaration node (@mdecl)
46
+ // alongside @owner and @mname, so param types can be walked for overload
47
+ // disambiguation and param names for param lookup.
48
+ const tsMemberDeclQuerySrc = `
49
+ (class_declaration name: (type_identifier) @owner body: (class_body [
50
+ (method_definition name: (property_identifier) @mname) @mdecl
51
+ (method_signature name: (property_identifier) @mname) @mdecl
52
+ ]))
53
+ (abstract_class_declaration name: (type_identifier) @owner body: (class_body [
54
+ (method_definition name: (property_identifier) @mname) @mdecl
55
+ (method_signature name: (property_identifier) @mname) @mdecl
56
+ (abstract_method_signature name: (property_identifier) @mname) @mdecl
57
+ ]))
58
+ (interface_declaration name: (type_identifier) @owner body: (interface_body
59
+ (method_signature name: (property_identifier) @mname) @mdecl))
60
+ `;
61
+ // tsFuncDeclQuerySrc captures top-level function declarations (@fname, @fdecl)
62
+ // for top-level-function param lookup and function-overload signature matching.
63
+ const tsFuncDeclQuerySrc = `
64
+ (function_declaration name: (identifier) @fname) @fdecl
65
+ (function_signature name: (identifier) @fname) @fdecl
66
+ `;
67
+ /**
68
+ * buildTSQueries compiles all four queries against `language` (one dialect
69
+ * grammar). compileQuery throws on a bad query (Go's mustLangQuery panic).
70
+ */
71
+ export function buildTSQueries(language) {
72
+ return {
73
+ symbol: compileQuery(language, tsSymbolQuerySrc),
74
+ member: compileQuery(language, tsMemberQuerySrc),
75
+ memberDecl: compileQuery(language, tsMemberDeclQuerySrc),
76
+ funcDecl: compileQuery(language, tsFuncDeclQuerySrc),
77
+ };
78
+ }
@@ -0,0 +1,50 @@
1
+ // signature.ts ports internal/codeq/signature.go: the param-type signature
2
+ // representation used for overload disambiguation. Comparison is existence-only
3
+ // and case-sensitive — no import resolution; the simple text as written is
4
+ // preserved otherwise.
5
+ /**
6
+ * normalizeType reduces a declared type to its overload-identity form: drop all
7
+ * whitespace, erase generic type arguments (List<String> → List), map varargs to
8
+ * an array (T... → T[]).
9
+ */
10
+ export function normalizeType(s) {
11
+ // Strip whitespace: collapse the string to its non-whitespace runs, joined.
12
+ // strings.Join(strings.Fields(s), "") drops ALL whitespace (it splits on any
13
+ // run of whitespace and rejoins with no separator), so we do the same.
14
+ s = s.split(/\s+/).filter((f) => f !== "").join("");
15
+ // Erase generic args: drop everything from the first '<' to the matching end.
16
+ const i = s.indexOf("<");
17
+ if (i >= 0) {
18
+ // A type's generics are a trailing <...>; the base name precedes '<'.
19
+ // Re-attach any array suffix that follows the closing '>'.
20
+ let suffix = "";
21
+ const j = s.lastIndexOf(">");
22
+ if (j >= 0 && j + 1 < s.length) {
23
+ suffix = s.slice(j + 1);
24
+ }
25
+ s = s.slice(0, i) + suffix;
26
+ }
27
+ // Varargs → array.
28
+ if (s.endsWith("...")) {
29
+ s = s.slice(0, -"...".length) + "[]";
30
+ }
31
+ return s;
32
+ }
33
+ /**
34
+ * signaturesMatch reports whether two declared parameter-type lists denote the
35
+ * same overload after normalization: equal length + element-wise normalized
36
+ * equality. Callers handle the nil (name-only) case before calling this.
37
+ */
38
+ export function signaturesMatch(want, got) {
39
+ if (want.length !== got.length) {
40
+ return false;
41
+ }
42
+ for (let i = 0; i < want.length; i++) {
43
+ // want[i]/got[i] are in-bounds (i < length), but noUncheckedIndexedAccess
44
+ // widens them to `| undefined`; the bound makes them defined.
45
+ if (normalizeType(want[i]) !== normalizeType(got[i])) {
46
+ return false;
47
+ }
48
+ }
49
+ return true;
50
+ }