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.
- package/README.md +242 -0
- package/dist/apispec/apispec.js +401 -0
- package/dist/apispec/apispec.test.js +444 -0
- package/dist/apispec/errors.js +17 -0
- package/dist/apispec/index.js +2 -0
- package/dist/check/doclinks.js +167 -0
- package/dist/check/index.js +8 -0
- package/dist/check/run.js +391 -0
- package/dist/check/run.test.js +513 -0
- package/dist/check/suggest.js +134 -0
- package/dist/check/suggest.test.js +92 -0
- package/dist/check/tokens.js +125 -0
- package/dist/cmd/main.js +330 -0
- package/dist/cmd/main.test.js +422 -0
- package/dist/codeq/cache.js +71 -0
- package/dist/codeq/cache.test.js +67 -0
- package/dist/codeq/errors.js +52 -0
- package/dist/codeq/grammars/tree-sitter-go.wasm +0 -0
- package/dist/codeq/grammars/tree-sitter-java.wasm +0 -0
- package/dist/codeq/grammars/tree-sitter-javascript.wasm +0 -0
- package/dist/codeq/grammars/tree-sitter-tsx.wasm +0 -0
- package/dist/codeq/grammars/tree-sitter-typescript.wasm +0 -0
- package/dist/codeq/index.js +11 -0
- package/dist/codeq/resolve.test.js +109 -0
- package/dist/codeq/resolver.js +128 -0
- package/dist/codeq/resolver.test.js +124 -0
- package/dist/codeq/resolvers/go.js +242 -0
- package/dist/codeq/resolvers/go.test.js +143 -0
- package/dist/codeq/resolvers/java.js +349 -0
- package/dist/codeq/resolvers/java.test.js +138 -0
- package/dist/codeq/resolvers/java_queries.js +63 -0
- package/dist/codeq/resolvers/javascript.js +412 -0
- package/dist/codeq/resolvers/javascript.test.js +125 -0
- package/dist/codeq/resolvers/javascript_queries.js +46 -0
- package/dist/codeq/resolvers/typescript.js +366 -0
- package/dist/codeq/resolvers/typescript.test.js +180 -0
- package/dist/codeq/resolvers/typescript_queries.js +78 -0
- package/dist/codeq/signature.js +50 -0
- package/dist/codeq/signature.test.js +50 -0
- package/dist/codeq/suggest.js +96 -0
- package/dist/codeq/treesitter.js +122 -0
- package/dist/codeq/treesitter.test.js +118 -0
- package/dist/config/config.js +74 -0
- package/dist/config/config.test.js +98 -0
- package/dist/config/fs.js +116 -0
- package/dist/config/glob.js +82 -0
- package/dist/config/glob.test.js +61 -0
- package/dist/config/index.js +4 -0
- package/dist/dedup/analyzer/analyzer.js +533 -0
- package/dist/dedup/analyzer/analyzer.test.js +530 -0
- package/dist/dedup/analyzer/canonical.js +74 -0
- package/dist/dedup/analyzer/canonical.test.js +70 -0
- package/dist/dedup/analyzer/cosine_clusters.js +169 -0
- package/dist/dedup/analyzer/cosine_clusters.test.js +131 -0
- package/dist/dedup/analyzer/distinctive.js +85 -0
- package/dist/dedup/analyzer/distinctive.test.js +49 -0
- package/dist/dedup/analyzer/exact_clusters.js +63 -0
- package/dist/dedup/analyzer/exact_clusters.test.js +81 -0
- package/dist/dedup/analyzer/index.js +14 -0
- package/dist/dedup/analyzer/multiplicity.js +110 -0
- package/dist/dedup/analyzer/multiplicity.test.js +123 -0
- package/dist/dedup/analyzer/order.js +22 -0
- package/dist/dedup/analyzer/partial_overlaps.js +65 -0
- package/dist/dedup/analyzer/partial_overlaps.test.js +161 -0
- package/dist/dedup/analyzer/preview.js +84 -0
- package/dist/dedup/analyzer/preview.test.js +46 -0
- package/dist/dedup/analyzer/safety.js +27 -0
- package/dist/dedup/analyzer/safety.test.js +39 -0
- package/dist/dedup/config.js +18 -0
- package/dist/dedup/configload.js +299 -0
- package/dist/dedup/configload.test.js +410 -0
- package/dist/dedup/dedup.index.test.js +203 -0
- package/dist/dedup/dedup.js +143 -0
- package/dist/dedup/dedup.test.js +212 -0
- package/dist/dedup/dedupcfg/config.js +112 -0
- package/dist/dedup/dedupcfg/config.test.js +70 -0
- package/dist/dedup/dedupcfg/index.js +1 -0
- package/dist/dedup/deduptypes/index.js +1 -0
- package/dist/dedup/deduptypes/types.js +9 -0
- package/dist/dedup/deduptypes/types.test.js +34 -0
- package/dist/dedup/embedder/cache.js +23 -0
- package/dist/dedup/embedder/cache.test.js +50 -0
- package/dist/dedup/embedder/constants.js +10 -0
- package/dist/dedup/embedder/embedder.js +76 -0
- package/dist/dedup/embedder/embedder.mock.test.js +128 -0
- package/dist/dedup/embedder/embedder.test.js +96 -0
- package/dist/dedup/embedder/errors.js +20 -0
- package/dist/dedup/embedder/errors.test.js +35 -0
- package/dist/dedup/embedder/index.js +4 -0
- package/dist/dedup/embedder/session.js +78 -0
- package/dist/dedup/embedder/session.test.js +172 -0
- package/dist/dedup/gitignore.js +97 -0
- package/dist/dedup/gitignore.test.js +98 -0
- package/dist/dedup/index.js +11 -0
- package/dist/dedup/indexdb/errors.js +48 -0
- package/dist/dedup/indexdb/index.js +6 -0
- package/dist/dedup/indexdb/indexdb.js +302 -0
- package/dist/dedup/indexdb/indexdb.test.js +739 -0
- package/dist/dedup/indexdb/load.js +110 -0
- package/dist/dedup/indexdb/migrations.js +58 -0
- package/dist/dedup/indexdb/schema.js +83 -0
- package/dist/dedup/indexer/index.js +9 -0
- package/dist/dedup/indexer/indexer.js +501 -0
- package/dist/dedup/indexer/indexer.test.js +510 -0
- package/dist/dedup/indexer/links.js +89 -0
- package/dist/dedup/mdsection/anchor.js +60 -0
- package/dist/dedup/mdsection/anchor.test.js +39 -0
- package/dist/dedup/mdsection/blocks.js +409 -0
- package/dist/dedup/mdsection/blocks.test.js +359 -0
- package/dist/dedup/mdsection/index.js +4 -0
- package/dist/dedup/mdsection/parse.js +21 -0
- package/dist/dedup/mdsection/section.js +234 -0
- package/dist/dedup/mdsection/section.test.js +221 -0
- package/dist/dedup/report/floatfmt.js +71 -0
- package/dist/dedup/report/floatfmt.test.js +42 -0
- package/dist/dedup/report/index.js +8 -0
- package/dist/dedup/report/quote.js +77 -0
- package/dist/dedup/report/quote.test.js +67 -0
- package/dist/dedup/report/text.js +251 -0
- package/dist/dedup/report/text.test.js +420 -0
- package/dist/dedup/report_types.js +8 -0
- package/dist/dedup/sectionid/index.js +1 -0
- package/dist/dedup/sectionid/sectionid.js +16 -0
- package/dist/dedup/sectionid/sectionid.test.js +49 -0
- package/dist/guard/api/errors.js +12 -0
- package/dist/guard/api/index.js +2 -0
- package/dist/guard/api/parser.js +81 -0
- package/dist/guard/api/parser.test.js +58 -0
- package/dist/guard/api/types.js +1 -0
- package/dist/guard/code/errors.js +16 -0
- package/dist/guard/code/index.js +2 -0
- package/dist/guard/code/parser.js +54 -0
- package/dist/guard/code/parser.test.js +111 -0
- package/dist/guard/code/types.js +6 -0
- package/dist/index.js +1 -0
- package/dist/index.test.js +5 -0
- package/dist/repo/boundary.js +92 -0
- package/dist/repo/boundary.test.js +65 -0
- package/dist/repo/errors.js +56 -0
- package/dist/repo/errors.test.js +85 -0
- package/dist/repo/exists.test.js +72 -0
- package/dist/repo/filename.js +46 -0
- package/dist/repo/filename.test.js +39 -0
- package/dist/repo/fs.js +53 -0
- package/dist/repo/index.js +7 -0
- package/dist/repo/overlay.js +36 -0
- package/dist/repo/overlay.test.js +80 -0
- package/dist/repo/repo.js +353 -0
- package/dist/repo/repo.test.js +255 -0
- package/dist/repo/testutil.js +27 -0
- package/dist/repo/write.test.js +125 -0
- package/dist/report/color.js +73 -0
- package/dist/report/index.js +1 -0
- package/dist/report/report.js +112 -0
- package/dist/report/report.test.js +368 -0
- package/dist/violation/index.js +1 -0
- package/dist/violation/types.js +22 -0
- package/dist/violation/types.test.js +70 -0
- 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
|
+
}
|