@descryy/adapter-go 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/dist/adapter.d.ts +26 -0
- package/dist/adapter.d.ts.map +1 -0
- package/dist/adapter.js +247 -0
- package/dist/adapter.js.map +1 -0
- package/dist/client.d.ts +114 -0
- package/dist/client.d.ts.map +1 -0
- package/dist/client.js +342 -0
- package/dist/client.js.map +1 -0
- package/dist/extract.d.ts +54 -0
- package/dist/extract.d.ts.map +1 -0
- package/dist/extract.js +871 -0
- package/dist/extract.js.map +1 -0
- package/dist/grpc.d.ts +62 -0
- package/dist/grpc.d.ts.map +1 -0
- package/dist/grpc.js +112 -0
- package/dist/grpc.js.map +1 -0
- package/dist/index.d.ts +5 -0
- package/dist/index.d.ts.map +1 -0
- package/dist/index.js +5 -0
- package/dist/index.js.map +1 -0
- package/dist/modules.d.ts +37 -0
- package/dist/modules.d.ts.map +1 -0
- package/dist/modules.js +83 -0
- package/dist/modules.js.map +1 -0
- package/dist/parse.d.ts +215 -0
- package/dist/parse.d.ts.map +1 -0
- package/dist/parse.js +890 -0
- package/dist/parse.js.map +1 -0
- package/dist/routes.d.ts +92 -0
- package/dist/routes.d.ts.map +1 -0
- package/dist/routes.js +661 -0
- package/dist/routes.js.map +1 -0
- package/dist/tree-sitter-go.wasm +0 -0
- package/package.json +35 -0
package/dist/parse.js
ADDED
|
@@ -0,0 +1,890 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* One Go file, read into the shape the resolver wants.
|
|
3
|
+
*
|
|
4
|
+
* Hand-written walk, as in Java and for the same reason: containment is what a
|
|
5
|
+
* resolver needs and a query's match set is exactly what discards it.
|
|
6
|
+
*
|
|
7
|
+
* ## What Go changes, and what it does not
|
|
8
|
+
*
|
|
9
|
+
* The harness is untouched — the same `parserFor` / `parseText` / node cursor.
|
|
10
|
+
* What is new is a rule set, and Go's rules are genuinely different from Java's
|
|
11
|
+
* rather than a translation of them:
|
|
12
|
+
*
|
|
13
|
+
* - The importable unit is the **package**, not the file. Files in one package
|
|
14
|
+
* share a namespace with no qualification at all, so an unqualified name
|
|
15
|
+
* resolves across files that never mention each other.
|
|
16
|
+
* - A method is attached by a *receiver*, not by nesting: `func (o *Order)
|
|
17
|
+
* Describe()` declares a method on `Order` from anywhere in the package.
|
|
18
|
+
* - There is no inheritance. Embedding is the nearest thing and it is
|
|
19
|
+
* explicit; interface satisfaction is structural and invisible without a
|
|
20
|
+
* type checker, so it is not claimed at all.
|
|
21
|
+
*/
|
|
22
|
+
import { readGoRoutes } from "./routes.js";
|
|
23
|
+
import { readGoClientCalls } from "./client.js";
|
|
24
|
+
import { readGrpcMethodConsts, readGrpcRegistrations } from "./grpc.js";
|
|
25
|
+
/** The `//go:build` line, which must appear before the package clause. */
|
|
26
|
+
export function buildConstraintOf(text) {
|
|
27
|
+
for (const raw of text.split("\n", 40)) {
|
|
28
|
+
const line = raw.trim();
|
|
29
|
+
if (line.startsWith("//go:build"))
|
|
30
|
+
return line.slice("//go:build".length).trim();
|
|
31
|
+
if (line.startsWith("package "))
|
|
32
|
+
return undefined;
|
|
33
|
+
}
|
|
34
|
+
return undefined;
|
|
35
|
+
}
|
|
36
|
+
/**
|
|
37
|
+
* Go's universe block: predeclared types, constants and functions.
|
|
38
|
+
*
|
|
39
|
+
* These are not declarations any package makes, so a reference to one is not a
|
|
40
|
+
* candidate edge and filing it is not disclosure but noise. On `prometheus` they
|
|
41
|
+
* were roughly 28,000 ledger entries claiming "no declaration for this name in
|
|
42
|
+
* the package" about `string`, `int64` and `len` — a reason that is not merely
|
|
43
|
+
* unhelpful but false.
|
|
44
|
+
*
|
|
45
|
+
* The grammar cannot help here: unlike Java, where `int` is an `integral_type`,
|
|
46
|
+
* Go spells every one of these as an ordinary `type_identifier`.
|
|
47
|
+
*/
|
|
48
|
+
const UNIVERSE = new Set([
|
|
49
|
+
"bool", "byte", "complex64", "complex128", "error", "float32", "float64",
|
|
50
|
+
"int", "int8", "int16", "int32", "int64", "rune", "string",
|
|
51
|
+
"uint", "uint8", "uint16", "uint32", "uint64", "uintptr", "any", "comparable",
|
|
52
|
+
"true", "false", "iota", "nil",
|
|
53
|
+
"append", "cap", "clear", "close", "complex", "copy", "delete", "imag", "len",
|
|
54
|
+
"make", "max", "min", "new", "panic", "print", "println", "real", "recover",
|
|
55
|
+
]);
|
|
56
|
+
export function isUniverse(name) {
|
|
57
|
+
return UNIVERSE.has(name);
|
|
58
|
+
}
|
|
59
|
+
/** `*T`, `[]T`, `map[K]T`, `chan T` — the name underneath. */
|
|
60
|
+
export function baseTypeName(node) {
|
|
61
|
+
if (node === null)
|
|
62
|
+
return undefined;
|
|
63
|
+
switch (node.type) {
|
|
64
|
+
case "type_identifier":
|
|
65
|
+
return UNIVERSE.has(node.text) ? undefined : node.text;
|
|
66
|
+
case "qualified_type": {
|
|
67
|
+
// `pkg.Type` — the package qualifier matters, so both parts are kept and
|
|
68
|
+
// the resolver splits them.
|
|
69
|
+
return node.text;
|
|
70
|
+
}
|
|
71
|
+
case "pointer_type":
|
|
72
|
+
case "slice_type":
|
|
73
|
+
case "array_type":
|
|
74
|
+
case "channel_type":
|
|
75
|
+
case "parenthesized_type":
|
|
76
|
+
case "variadic_parameter_declaration":
|
|
77
|
+
return baseTypeName(node.namedChild(0));
|
|
78
|
+
case "map_type":
|
|
79
|
+
// The value type is the dependency worth recording; the key is usually a
|
|
80
|
+
// primitive and recording both would need two refs for one written type.
|
|
81
|
+
return baseTypeName(node.childForFieldName("value") ?? node.namedChild(1));
|
|
82
|
+
case "generic_type":
|
|
83
|
+
return baseTypeName(node.childForFieldName("type") ?? node.namedChild(0));
|
|
84
|
+
default:
|
|
85
|
+
return undefined;
|
|
86
|
+
}
|
|
87
|
+
}
|
|
88
|
+
/** Every type name inside a type node, generic arguments and map keys included. */
|
|
89
|
+
function allTypeNames(node, out, skip) {
|
|
90
|
+
if (node === null)
|
|
91
|
+
return;
|
|
92
|
+
if (node.type === "qualified_type") {
|
|
93
|
+
// The qualified name and *nothing underneath it*. Recursing into it also
|
|
94
|
+
// yielded the bare `type_identifier`, so `http.ResponseWriter` asked about
|
|
95
|
+
// `ResponseWriter` as well — and gin declares one, so the parameter type of
|
|
96
|
+
// a `http.Handler` implementation resolved to gin's own interface. A false
|
|
97
|
+
// claim, and the kind that corrupts every layer above the graph.
|
|
98
|
+
out.push(node.text);
|
|
99
|
+
return;
|
|
100
|
+
}
|
|
101
|
+
if (node.type === "type_identifier" && !UNIVERSE.has(node.text) && skip?.has(node.text) !== true) {
|
|
102
|
+
out.push(node.text);
|
|
103
|
+
}
|
|
104
|
+
for (let i = 0; i < node.namedChildCount; i += 1)
|
|
105
|
+
allTypeNames(node.namedChild(i), out, skip);
|
|
106
|
+
}
|
|
107
|
+
/**
|
|
108
|
+
* Type parameter names declared by a function or type.
|
|
109
|
+
*
|
|
110
|
+
* `T` was the ninth most common ledger target on `prometheus` with 2,215
|
|
111
|
+
* entries. A type parameter is a binding, not a reference to a declaration, and
|
|
112
|
+
* demanding an edge for it is the same mistake TypeScript made with `infer`.
|
|
113
|
+
*/
|
|
114
|
+
function typeParametersOf(node) {
|
|
115
|
+
const names = new Set();
|
|
116
|
+
const list = node.childForFieldName("type_parameters");
|
|
117
|
+
if (list === null)
|
|
118
|
+
return names;
|
|
119
|
+
for (let i = 0; i < list.namedChildCount; i += 1) {
|
|
120
|
+
const declaration = list.namedChild(i);
|
|
121
|
+
if (declaration === null)
|
|
122
|
+
continue;
|
|
123
|
+
for (let n = 0; n < declaration.namedChildCount; n += 1) {
|
|
124
|
+
const child = declaration.namedChild(n);
|
|
125
|
+
if (child !== null && child.type === "identifier")
|
|
126
|
+
names.add(child.text);
|
|
127
|
+
}
|
|
128
|
+
}
|
|
129
|
+
return names;
|
|
130
|
+
}
|
|
131
|
+
function declare(table, name, type) {
|
|
132
|
+
const value = type ?? null;
|
|
133
|
+
if (!table.has(name)) {
|
|
134
|
+
table.set(name, value);
|
|
135
|
+
return;
|
|
136
|
+
}
|
|
137
|
+
if (table.get(name) !== value)
|
|
138
|
+
table.set(name, null);
|
|
139
|
+
}
|
|
140
|
+
/** The name a receiver is written as, or `(computed)` when it is not a name. */
|
|
141
|
+
function receiverName(node) {
|
|
142
|
+
if (node.type === "identifier" || node.type === "package_identifier" || node.type === "field_identifier") {
|
|
143
|
+
return node.text;
|
|
144
|
+
}
|
|
145
|
+
if (node.type === "selector_expression") {
|
|
146
|
+
const operand = node.childForFieldName("operand");
|
|
147
|
+
const field = node.childForFieldName("field");
|
|
148
|
+
if (operand !== null && field !== null) {
|
|
149
|
+
const base = receiverName(operand);
|
|
150
|
+
return base === "(computed)" ? "(computed)" : `${base}.${field.text}`;
|
|
151
|
+
}
|
|
152
|
+
}
|
|
153
|
+
// `(*x).f`, `x[0].f`, `f().g` — nothing wrote a name here.
|
|
154
|
+
return "(computed)";
|
|
155
|
+
}
|
|
156
|
+
/**
|
|
157
|
+
* The receiver's type where the site writes it down.
|
|
158
|
+
*
|
|
159
|
+
* Two forms, both unambiguous: a type assertion states the type outright, and a
|
|
160
|
+
* parenthesised composite literal is a value of exactly the type it names. Both
|
|
161
|
+
* previously became `(computed)`, and both are common — `p.(*listPostings).Len()`
|
|
162
|
+
* on prometheus, `(IndentedJSON{data}).Render(w)` on gin.
|
|
163
|
+
*/
|
|
164
|
+
function receiverTypeAtSite(node) {
|
|
165
|
+
if (node.type === "parenthesized_expression") {
|
|
166
|
+
const inner = node.namedChild(0);
|
|
167
|
+
return inner === null ? undefined : receiverTypeAtSite(inner);
|
|
168
|
+
}
|
|
169
|
+
if (node.type === "type_assertion_expression") {
|
|
170
|
+
return baseTypeName(node.childForFieldName("type"));
|
|
171
|
+
}
|
|
172
|
+
if (node.type === "composite_literal") {
|
|
173
|
+
return baseTypeName(node.childForFieldName("type"));
|
|
174
|
+
}
|
|
175
|
+
if (node.type === "unary_expression" && node.text.startsWith("&")) {
|
|
176
|
+
const inner = node.namedChild(0);
|
|
177
|
+
return inner === null ? undefined : receiverTypeAtSite(inner);
|
|
178
|
+
}
|
|
179
|
+
return undefined;
|
|
180
|
+
}
|
|
181
|
+
/** `xs[i]` -> `xs`. Only a plain name is followed; `f()[i]` writes no type. */
|
|
182
|
+
function indexedReceiver(node) {
|
|
183
|
+
const current = node.type === "parenthesized_expression" ? node.namedChild(0) : node;
|
|
184
|
+
if (current === null || current.type !== "index_expression")
|
|
185
|
+
return undefined;
|
|
186
|
+
const operand = current.childForFieldName("operand");
|
|
187
|
+
return operand !== null && operand.type === "identifier" ? operand.text : undefined;
|
|
188
|
+
}
|
|
189
|
+
/** The call whose result is the receiver: `engine().RunUnix(file)`. */
|
|
190
|
+
function receiverCallOf(node) {
|
|
191
|
+
const current = node.type === "parenthesized_expression" ? node.namedChild(0) : node;
|
|
192
|
+
if (current === null || current.type !== "call_expression")
|
|
193
|
+
return undefined;
|
|
194
|
+
const callee = current.childForFieldName("function");
|
|
195
|
+
if (callee === null)
|
|
196
|
+
return undefined;
|
|
197
|
+
if (callee.type === "identifier")
|
|
198
|
+
return { receiver: undefined, name: callee.text };
|
|
199
|
+
if (callee.type === "selector_expression") {
|
|
200
|
+
const operand = callee.childForFieldName("operand");
|
|
201
|
+
const field = callee.childForFieldName("field");
|
|
202
|
+
if (operand === null || field === null || operand.type !== "identifier")
|
|
203
|
+
return undefined;
|
|
204
|
+
return { receiver: operand.text, name: field.text };
|
|
205
|
+
}
|
|
206
|
+
return undefined;
|
|
207
|
+
}
|
|
208
|
+
export function readFile(root, file, hasError) {
|
|
209
|
+
const cut = file.lastIndexOf("/");
|
|
210
|
+
const unit = {
|
|
211
|
+
file,
|
|
212
|
+
packageName: "",
|
|
213
|
+
directory: cut === -1 ? "" : file.slice(0, cut),
|
|
214
|
+
imports: new Map(),
|
|
215
|
+
importPaths: [],
|
|
216
|
+
types: [],
|
|
217
|
+
funcs: [],
|
|
218
|
+
aliases: [],
|
|
219
|
+
packageVars: new Map(),
|
|
220
|
+
routes: [],
|
|
221
|
+
routeRefusals: [],
|
|
222
|
+
grpcMethodConsts: [],
|
|
223
|
+
grpcRegistrations: [],
|
|
224
|
+
hasError,
|
|
225
|
+
importPath: "",
|
|
226
|
+
buildConstraint: undefined,
|
|
227
|
+
};
|
|
228
|
+
let packageName = "";
|
|
229
|
+
for (let i = 0; i < root.namedChildCount; i += 1) {
|
|
230
|
+
const statement = root.namedChild(i);
|
|
231
|
+
if (statement === null)
|
|
232
|
+
continue;
|
|
233
|
+
if (statement.type === "package_clause") {
|
|
234
|
+
packageName = statement.namedChild(0)?.text ?? "";
|
|
235
|
+
continue;
|
|
236
|
+
}
|
|
237
|
+
if (statement.type === "import_declaration") {
|
|
238
|
+
readImports(statement, unit);
|
|
239
|
+
continue;
|
|
240
|
+
}
|
|
241
|
+
if (statement.type === "type_declaration") {
|
|
242
|
+
for (let s = 0; s < statement.namedChildCount; s += 1) {
|
|
243
|
+
const spec = statement.namedChild(s);
|
|
244
|
+
if (spec === null || (spec.type !== "type_spec" && spec.type !== "type_alias"))
|
|
245
|
+
continue;
|
|
246
|
+
readType(spec, unit);
|
|
247
|
+
}
|
|
248
|
+
continue;
|
|
249
|
+
}
|
|
250
|
+
if (statement.type === "var_declaration" || statement.type === "const_declaration") {
|
|
251
|
+
readAliases(statement, unit);
|
|
252
|
+
readPackageVars(statement, unit);
|
|
253
|
+
continue;
|
|
254
|
+
}
|
|
255
|
+
if (statement.type === "function_declaration" || statement.type === "method_declaration") {
|
|
256
|
+
readFunc(statement, unit, file);
|
|
257
|
+
}
|
|
258
|
+
}
|
|
259
|
+
// After the loop, because provenance is decided by the import table and the
|
|
260
|
+
// import declaration may sit anywhere above the call that uses it.
|
|
261
|
+
const read = readGoRoutes(root, unit);
|
|
262
|
+
unit.routes.push(...read.declarations);
|
|
263
|
+
unit.routeRefusals.push(...read.refusals);
|
|
264
|
+
unit.grpcMethodConsts.push(...readGrpcMethodConsts(root));
|
|
265
|
+
unit.grpcRegistrations.push(...readGrpcRegistrations(root));
|
|
266
|
+
return { ...unit, packageName };
|
|
267
|
+
}
|
|
268
|
+
/**
|
|
269
|
+
* An unaliased import's likely binding name: the last path segment, unless
|
|
270
|
+
* that segment is a bare major-version marker (`v2`, `v3`, …), in which case
|
|
271
|
+
* the segment before it — Go's semantic-import-versioning convention keeps the
|
|
272
|
+
* package's own name there, not in the version segment.
|
|
273
|
+
*/
|
|
274
|
+
function defaultImportAlias(path) {
|
|
275
|
+
const segments = path.split("/");
|
|
276
|
+
const last = segments[segments.length - 1] ?? path;
|
|
277
|
+
if (segments.length > 1 && /^v[0-9]+$/.test(last)) {
|
|
278
|
+
return segments[segments.length - 2] ?? last;
|
|
279
|
+
}
|
|
280
|
+
return last;
|
|
281
|
+
}
|
|
282
|
+
function readImports(node, unit) {
|
|
283
|
+
const specs = [];
|
|
284
|
+
const collect = (current) => {
|
|
285
|
+
if (current.type === "import_spec") {
|
|
286
|
+
specs.push(current);
|
|
287
|
+
return;
|
|
288
|
+
}
|
|
289
|
+
for (let i = 0; i < current.namedChildCount; i += 1) {
|
|
290
|
+
const child = current.namedChild(i);
|
|
291
|
+
if (child !== null)
|
|
292
|
+
collect(child);
|
|
293
|
+
}
|
|
294
|
+
};
|
|
295
|
+
collect(node);
|
|
296
|
+
for (const spec of specs) {
|
|
297
|
+
const pathNode = spec.childForFieldName("path");
|
|
298
|
+
if (pathNode === null)
|
|
299
|
+
continue;
|
|
300
|
+
const path = pathNode.text.replace(/^["`]|["`]$/g, "");
|
|
301
|
+
unit.importPaths.push({ path, line: spec.startPosition.row + 1 });
|
|
302
|
+
const alias = spec.childForFieldName("name")?.text;
|
|
303
|
+
// `_ "…"` is a blank import for side effects and binds no name; `. "…"` dumps
|
|
304
|
+
// the package into this file's scope, which cannot be told apart from a
|
|
305
|
+
// package-local name without reading the imported package, so neither binds
|
|
306
|
+
// an alias here.
|
|
307
|
+
if (alias === "_" || alias === ".")
|
|
308
|
+
continue;
|
|
309
|
+
// No alias: the binding is the package's declared name, which is *usually*
|
|
310
|
+
// but not always the last path segment. The one named, common exception is
|
|
311
|
+
// Go's own semantic-import-versioning convention (go.dev/blog/v2-go-modules):
|
|
312
|
+
// a `/v2`+ suffix is a major-version marker, not part of the package's own
|
|
313
|
+
// name — `go-chi/chi/v5` still declares `package chi`, not `package v5` — so
|
|
314
|
+
// that segment is skipped in favour of the one before it. Anything else the
|
|
315
|
+
// last segment gets wrong is disclosed by the reference failing to resolve
|
|
316
|
+
// rather than by a further guess.
|
|
317
|
+
unit.imports.set(alias ?? defaultImportAlias(path), path);
|
|
318
|
+
}
|
|
319
|
+
}
|
|
320
|
+
/**
|
|
321
|
+
* Package-level `var X = Y` / `var X = pkg.Y` bindings.
|
|
322
|
+
*
|
|
323
|
+
* Deliberately narrow. One name, one value, and that value a bare name or a
|
|
324
|
+
* qualified name — nothing else. A call, a literal, a composite or a
|
|
325
|
+
* multi-assignment is a value being computed, not a name being bound, and
|
|
326
|
+
* treating it as a binding would put a guessed target in the graph.
|
|
327
|
+
*/
|
|
328
|
+
/**
|
|
329
|
+
* Package-level values whose type is written down.
|
|
330
|
+
*
|
|
331
|
+
* Two forms, both unambiguous: an explicit type, and a composite literal, which
|
|
332
|
+
* is a value of exactly the type it names. Anything else — a call result, an
|
|
333
|
+
* expression — is left out, because guessing there is the thing the precision
|
|
334
|
+
* rule forbids and the ledger already says so.
|
|
335
|
+
*/
|
|
336
|
+
function readPackageVars(node, unit) {
|
|
337
|
+
for (let i = 0; i < node.namedChildCount; i += 1) {
|
|
338
|
+
const spec = node.namedChild(i);
|
|
339
|
+
if (spec === null)
|
|
340
|
+
continue;
|
|
341
|
+
if (spec.type !== "var_spec" && spec.type !== "const_spec")
|
|
342
|
+
continue;
|
|
343
|
+
const names = [];
|
|
344
|
+
for (let n = 0; n < spec.namedChildCount; n += 1) {
|
|
345
|
+
const child = spec.namedChild(n);
|
|
346
|
+
if (child !== null && child.type === "identifier")
|
|
347
|
+
names.push(child);
|
|
348
|
+
}
|
|
349
|
+
if (names.length !== 1)
|
|
350
|
+
continue;
|
|
351
|
+
const name = names[0];
|
|
352
|
+
if (name === undefined)
|
|
353
|
+
continue;
|
|
354
|
+
const declared = baseTypeName(spec.childForFieldName("type"));
|
|
355
|
+
if (declared !== undefined) {
|
|
356
|
+
unit.packageVars.set(name.text, declared);
|
|
357
|
+
continue;
|
|
358
|
+
}
|
|
359
|
+
const values = spec.childForFieldName("value");
|
|
360
|
+
if (values === null || values.namedChildCount !== 1)
|
|
361
|
+
continue;
|
|
362
|
+
const value = values.namedChild(0);
|
|
363
|
+
const written = value === null ? undefined : receiverTypeAtSite(value);
|
|
364
|
+
if (written !== undefined)
|
|
365
|
+
unit.packageVars.set(name.text, written);
|
|
366
|
+
}
|
|
367
|
+
}
|
|
368
|
+
function readAliases(node, unit) {
|
|
369
|
+
for (let i = 0; i < node.namedChildCount; i += 1) {
|
|
370
|
+
const spec = node.namedChild(i);
|
|
371
|
+
if (spec === null)
|
|
372
|
+
continue;
|
|
373
|
+
if (spec.type !== "var_spec" && spec.type !== "const_spec")
|
|
374
|
+
continue;
|
|
375
|
+
const names = [];
|
|
376
|
+
for (let n = 0; n < spec.namedChildCount; n += 1) {
|
|
377
|
+
const child = spec.namedChild(n);
|
|
378
|
+
if (child !== null && child.type === "identifier")
|
|
379
|
+
names.push(child);
|
|
380
|
+
}
|
|
381
|
+
const name = names[0];
|
|
382
|
+
if (names.length !== 1 || name === undefined)
|
|
383
|
+
continue;
|
|
384
|
+
const values = spec.childForFieldName("value");
|
|
385
|
+
if (values === null || values.namedChildCount !== 1)
|
|
386
|
+
continue;
|
|
387
|
+
const value = values.namedChild(0);
|
|
388
|
+
if (value === null)
|
|
389
|
+
continue;
|
|
390
|
+
if (value.type === "identifier") {
|
|
391
|
+
unit.aliases.push({ name: name.text, target: value.text, line: spec.startPosition.row + 1 });
|
|
392
|
+
continue;
|
|
393
|
+
}
|
|
394
|
+
if (value.type === "selector_expression") {
|
|
395
|
+
const operand = value.childForFieldName("operand");
|
|
396
|
+
const field = value.childForFieldName("field");
|
|
397
|
+
if (operand === null || field === null || operand.type !== "identifier")
|
|
398
|
+
continue;
|
|
399
|
+
unit.aliases.push({
|
|
400
|
+
name: name.text,
|
|
401
|
+
target: `${operand.text}.${field.text}`,
|
|
402
|
+
line: spec.startPosition.row + 1,
|
|
403
|
+
});
|
|
404
|
+
}
|
|
405
|
+
}
|
|
406
|
+
}
|
|
407
|
+
function readType(spec, unit) {
|
|
408
|
+
const nameNode = spec.childForFieldName("name");
|
|
409
|
+
if (nameNode === null)
|
|
410
|
+
return;
|
|
411
|
+
const typeNode = spec.childForFieldName("type");
|
|
412
|
+
const form = typeNode?.type === "struct_type" ? "struct" : typeNode?.type === "interface_type" ? "interface" : "alias";
|
|
413
|
+
const params = typeParametersOf(spec);
|
|
414
|
+
const type = {
|
|
415
|
+
name: nameNode.text,
|
|
416
|
+
form,
|
|
417
|
+
startLine: spec.startPosition.row + 1,
|
|
418
|
+
endLine: spec.endPosition.row + 1,
|
|
419
|
+
embeds: [],
|
|
420
|
+
fields: new Map(),
|
|
421
|
+
typeRefs: [],
|
|
422
|
+
};
|
|
423
|
+
if (typeNode?.type === "struct_type") {
|
|
424
|
+
const fields = typeNode.namedChild(0);
|
|
425
|
+
for (let i = 0; fields !== null && i < fields.namedChildCount; i += 1) {
|
|
426
|
+
const field = fields.namedChild(i);
|
|
427
|
+
if (field === null || field.type !== "field_declaration")
|
|
428
|
+
continue;
|
|
429
|
+
const fieldType = field.childForFieldName("type");
|
|
430
|
+
const declared = baseTypeName(fieldType);
|
|
431
|
+
const names = [];
|
|
432
|
+
for (let n = 0; n < field.namedChildCount; n += 1) {
|
|
433
|
+
const child = field.namedChild(n);
|
|
434
|
+
if (child !== null && child.type === "field_identifier")
|
|
435
|
+
names.push(child);
|
|
436
|
+
}
|
|
437
|
+
if (names.length === 0) {
|
|
438
|
+
// An embedded field: `Base` with no name of its own. Go's only explicit
|
|
439
|
+
// type-to-type relationship, and the closest thing it has to inheritance.
|
|
440
|
+
const embedded = baseTypeName(fieldType) ?? field.text.replace(/^\*/, "").trim();
|
|
441
|
+
if (embedded !== "")
|
|
442
|
+
type.embeds.push(embedded);
|
|
443
|
+
}
|
|
444
|
+
for (const name of names)
|
|
445
|
+
declare(type.fields, name.text, declared);
|
|
446
|
+
const written = [];
|
|
447
|
+
allTypeNames(fieldType, written, params);
|
|
448
|
+
for (const each of written) {
|
|
449
|
+
type.typeRefs.push({
|
|
450
|
+
kind: "type",
|
|
451
|
+
name: each,
|
|
452
|
+
receiver: undefined,
|
|
453
|
+
line: field.startPosition.row + 1,
|
|
454
|
+
raw: fieldType?.text.slice(0, 120) ?? each,
|
|
455
|
+
});
|
|
456
|
+
}
|
|
457
|
+
}
|
|
458
|
+
}
|
|
459
|
+
if (typeNode?.type === "interface_type") {
|
|
460
|
+
// An embedded interface is written exactly like an embedded struct field.
|
|
461
|
+
for (let i = 0; i < typeNode.namedChildCount; i += 1) {
|
|
462
|
+
const member = typeNode.namedChild(i);
|
|
463
|
+
if (member === null)
|
|
464
|
+
continue;
|
|
465
|
+
if (member.type === "type_identifier" || member.type === "qualified_type") {
|
|
466
|
+
type.embeds.push(member.text);
|
|
467
|
+
}
|
|
468
|
+
}
|
|
469
|
+
}
|
|
470
|
+
if (form === "alias" && typeNode !== null) {
|
|
471
|
+
const written = [];
|
|
472
|
+
allTypeNames(typeNode, written, params);
|
|
473
|
+
for (const each of written) {
|
|
474
|
+
type.typeRefs.push({
|
|
475
|
+
kind: "type",
|
|
476
|
+
name: each,
|
|
477
|
+
receiver: undefined,
|
|
478
|
+
line: spec.startPosition.row + 1,
|
|
479
|
+
raw: typeNode.text.slice(0, 120),
|
|
480
|
+
});
|
|
481
|
+
}
|
|
482
|
+
}
|
|
483
|
+
unit.types.push(type);
|
|
484
|
+
}
|
|
485
|
+
/** `func TestXxx(t *testing.T)` in a `_test.go` file — the whole convention. */
|
|
486
|
+
function isTestFunc(name, file) {
|
|
487
|
+
return file.endsWith("_test.go") && /^(Test|Benchmark|Fuzz|Example)[^a-z]?/.test(name);
|
|
488
|
+
}
|
|
489
|
+
/** The types whose `Run` method declares a subtest. */
|
|
490
|
+
const TEST_RECEIVERS = new Set(["testing.T", "testing.B", "testing.F"]);
|
|
491
|
+
function readFunc(node, unit, file) {
|
|
492
|
+
const nameNode = node.childForFieldName("name");
|
|
493
|
+
if (nameNode === null)
|
|
494
|
+
return;
|
|
495
|
+
let receiver;
|
|
496
|
+
const receiverList = node.childForFieldName("receiver");
|
|
497
|
+
if (receiverList !== null) {
|
|
498
|
+
const declaration = receiverList.namedChild(0);
|
|
499
|
+
receiver = baseTypeName(declaration?.childForFieldName("type") ?? null);
|
|
500
|
+
}
|
|
501
|
+
const params = typeParametersOf(node);
|
|
502
|
+
const name = nameNode.text;
|
|
503
|
+
const fn = {
|
|
504
|
+
name,
|
|
505
|
+
key: receiver === undefined ? name : `${receiver}.${name}`,
|
|
506
|
+
receiver,
|
|
507
|
+
startLine: node.startPosition.row + 1,
|
|
508
|
+
endLine: node.endPosition.row + 1,
|
|
509
|
+
isTest: receiver === undefined && isTestFunc(name, file),
|
|
510
|
+
refs: [],
|
|
511
|
+
locals: new Map(),
|
|
512
|
+
pending: new Map(),
|
|
513
|
+
parameterNames: new Set(),
|
|
514
|
+
subtests: [],
|
|
515
|
+
results: resultTypes(node.childForFieldName("result")),
|
|
516
|
+
};
|
|
517
|
+
if (receiverList !== null) {
|
|
518
|
+
const declaration = receiverList.namedChild(0);
|
|
519
|
+
const receiverName_ = declaration?.childForFieldName("name");
|
|
520
|
+
if (receiverName_ !== null && receiverName_ !== undefined && receiver !== undefined) {
|
|
521
|
+
declare(fn.locals, receiverName_.text, receiver);
|
|
522
|
+
}
|
|
523
|
+
}
|
|
524
|
+
const parameters = node.childForFieldName("parameters");
|
|
525
|
+
if (parameters !== null)
|
|
526
|
+
collectParameters(parameters, fn, params, true);
|
|
527
|
+
const result = node.childForFieldName("result");
|
|
528
|
+
if (result !== null) {
|
|
529
|
+
if (result.type === "parameter_list")
|
|
530
|
+
collectParameters(result, fn, params);
|
|
531
|
+
const written = [];
|
|
532
|
+
allTypeNames(result, written, params);
|
|
533
|
+
for (const each of written) {
|
|
534
|
+
fn.refs.push({ kind: "type", name: each, receiver: undefined, line: fn.startLine, raw: result.text.slice(0, 120) });
|
|
535
|
+
}
|
|
536
|
+
}
|
|
537
|
+
let clientCalls = [];
|
|
538
|
+
let clientRefusals = [];
|
|
539
|
+
const body = node.childForFieldName("body");
|
|
540
|
+
if (body !== null) {
|
|
541
|
+
walkBody(body, fn, {
|
|
542
|
+
params,
|
|
543
|
+
subtests: fn.subtests,
|
|
544
|
+
suite: fn.isTest ? [name] : [],
|
|
545
|
+
inTest: fn.isTest,
|
|
546
|
+
});
|
|
547
|
+
// After `walkBody`, so `fn.locals` already has every parameter and every
|
|
548
|
+
// `:=`/`var` declaration this function makes — including a `*http.Client`
|
|
549
|
+
// constructed partway through the body, not just one declared as a parameter.
|
|
550
|
+
const client = readGoClientCalls(body, unit, fn.locals, fn.isTest, fn.parameterNames);
|
|
551
|
+
clientCalls = client.calls;
|
|
552
|
+
clientRefusals = client.refusals;
|
|
553
|
+
}
|
|
554
|
+
unit.funcs.push({ ...fn, clientCalls, clientRefusals });
|
|
555
|
+
}
|
|
556
|
+
/**
|
|
557
|
+
* The declared result types of a function, in order.
|
|
558
|
+
*
|
|
559
|
+
* Go allows `(a, b Foo)` — one type, two results — so a declaration contributes
|
|
560
|
+
* as many entries as it names, and position is what `x, err := f()` binds by.
|
|
561
|
+
*/
|
|
562
|
+
function resultTypes(result) {
|
|
563
|
+
if (result === null)
|
|
564
|
+
return [];
|
|
565
|
+
if (result.type !== "parameter_list")
|
|
566
|
+
return [baseTypeName(result)];
|
|
567
|
+
const out = [];
|
|
568
|
+
for (let i = 0; i < result.namedChildCount; i += 1) {
|
|
569
|
+
const parameter = result.namedChild(i);
|
|
570
|
+
if (parameter === null)
|
|
571
|
+
continue;
|
|
572
|
+
if (parameter.type !== "parameter_declaration" && parameter.type !== "variadic_parameter_declaration")
|
|
573
|
+
continue;
|
|
574
|
+
const written = baseTypeName(parameter.childForFieldName("type"));
|
|
575
|
+
let named = 0;
|
|
576
|
+
for (let n = 0; n < parameter.namedChildCount; n += 1) {
|
|
577
|
+
if (parameter.namedChild(n)?.type === "identifier")
|
|
578
|
+
named += 1;
|
|
579
|
+
}
|
|
580
|
+
for (let k = 0; k < Math.max(1, named); k += 1)
|
|
581
|
+
out.push(written);
|
|
582
|
+
}
|
|
583
|
+
return out;
|
|
584
|
+
}
|
|
585
|
+
function collectParameters(list, fn, params, isInputParameterList = false) {
|
|
586
|
+
for (let i = 0; i < list.namedChildCount; i += 1) {
|
|
587
|
+
const parameter = list.namedChild(i);
|
|
588
|
+
if (parameter === null)
|
|
589
|
+
continue;
|
|
590
|
+
if (parameter.type !== "parameter_declaration" && parameter.type !== "variadic_parameter_declaration")
|
|
591
|
+
continue;
|
|
592
|
+
const typeNode = parameter.childForFieldName("type");
|
|
593
|
+
const declared = baseTypeName(typeNode);
|
|
594
|
+
const nameNode = parameter.childForFieldName("name");
|
|
595
|
+
if (nameNode !== null) {
|
|
596
|
+
declare(fn.locals, nameNode.text, declared);
|
|
597
|
+
// Only the true input parameter list, never a named-return parameter
|
|
598
|
+
// list (Go's grammar uses the same node shape for both) — a named
|
|
599
|
+
// return starts at its zero value and may be reassigned, so it is not
|
|
600
|
+
// the caller-supplied, never-fixed value DEC-242's varies-per-call means.
|
|
601
|
+
if (isInputParameterList)
|
|
602
|
+
fn.parameterNames.add(nameNode.text);
|
|
603
|
+
}
|
|
604
|
+
const written = [];
|
|
605
|
+
allTypeNames(typeNode, written, params);
|
|
606
|
+
for (const each of written) {
|
|
607
|
+
fn.refs.push({
|
|
608
|
+
kind: "type",
|
|
609
|
+
name: each,
|
|
610
|
+
receiver: undefined,
|
|
611
|
+
line: parameter.startPosition.row + 1,
|
|
612
|
+
raw: typeNode?.text.slice(0, 120) ?? each,
|
|
613
|
+
});
|
|
614
|
+
}
|
|
615
|
+
}
|
|
616
|
+
}
|
|
617
|
+
/** The text of a string literal, or `undefined` when it is not one. */
|
|
618
|
+
function literalText(node) {
|
|
619
|
+
if (node === null)
|
|
620
|
+
return undefined;
|
|
621
|
+
if (node.type !== "interpreted_string_literal" && node.type !== "raw_string_literal")
|
|
622
|
+
return undefined;
|
|
623
|
+
return node.text.slice(1, -1);
|
|
624
|
+
}
|
|
625
|
+
/**
|
|
626
|
+
* `t.Run("name", func(t *testing.T) { … })`, when that is what this call is.
|
|
627
|
+
*
|
|
628
|
+
* Every condition is evidence written in the source: the receiver's type is a
|
|
629
|
+
* declared parameter type, the name is a literal, and the body is a closure
|
|
630
|
+
* right there. A call named `Run` on anything else, or with a computed name, is
|
|
631
|
+
* not a subtest declaration and produces nothing.
|
|
632
|
+
*/
|
|
633
|
+
function subtestOf(node, scope, ctx) {
|
|
634
|
+
if (!ctx.inTest)
|
|
635
|
+
return undefined;
|
|
636
|
+
const callee = node.childForFieldName("function");
|
|
637
|
+
if (callee === null || callee.type !== "selector_expression")
|
|
638
|
+
return undefined;
|
|
639
|
+
if (callee.childForFieldName("field")?.text !== "Run")
|
|
640
|
+
return undefined;
|
|
641
|
+
const operand = callee.childForFieldName("operand");
|
|
642
|
+
if (operand === null || operand.type !== "identifier")
|
|
643
|
+
return undefined;
|
|
644
|
+
const receiverType = scope.locals.get(operand.text);
|
|
645
|
+
if (receiverType === undefined || receiverType === null || !TEST_RECEIVERS.has(receiverType))
|
|
646
|
+
return undefined;
|
|
647
|
+
const args = node.childForFieldName("arguments");
|
|
648
|
+
if (args === null || args.namedChildCount !== 2)
|
|
649
|
+
return undefined;
|
|
650
|
+
const name = literalText(args.namedChild(0));
|
|
651
|
+
const closure = args.namedChild(1);
|
|
652
|
+
if (name === undefined || closure === null || closure.type !== "func_literal")
|
|
653
|
+
return undefined;
|
|
654
|
+
const body = closure.childForFieldName("body");
|
|
655
|
+
if (body === null)
|
|
656
|
+
return undefined;
|
|
657
|
+
const subtest = {
|
|
658
|
+
name,
|
|
659
|
+
suite: [...ctx.suite],
|
|
660
|
+
startLine: node.startPosition.row + 1,
|
|
661
|
+
endLine: node.endPosition.row + 1,
|
|
662
|
+
refs: [],
|
|
663
|
+
// A closure sees the names around it, so the enclosing scope's types carry
|
|
664
|
+
// in. Its own parameters are collected below and shadow them.
|
|
665
|
+
locals: new Map(scope.locals),
|
|
666
|
+
pending: new Map(scope.pending),
|
|
667
|
+
parameterNames: new Set(scope.parameterNames),
|
|
668
|
+
};
|
|
669
|
+
const parameters = closure.childForFieldName("parameters");
|
|
670
|
+
if (parameters !== null)
|
|
671
|
+
collectParameters(parameters, subtest, ctx.params, true);
|
|
672
|
+
return { subtest, body };
|
|
673
|
+
}
|
|
674
|
+
function walkBody(node, fn, ctx) {
|
|
675
|
+
const params = ctx.params;
|
|
676
|
+
// A subtest declaration, not a call. Taken before the generic call handling so
|
|
677
|
+
// that `t.Run` is neither emitted as an edge nor filed as an unresolved one —
|
|
678
|
+
// it was never a candidate edge, it was a declaration.
|
|
679
|
+
if (node.type === "call_expression") {
|
|
680
|
+
const found = subtestOf(node, fn, ctx);
|
|
681
|
+
if (found !== undefined) {
|
|
682
|
+
ctx.subtests.push(found.subtest);
|
|
683
|
+
walkBody(found.body, found.subtest, {
|
|
684
|
+
...ctx,
|
|
685
|
+
suite: [...ctx.suite, found.subtest.name],
|
|
686
|
+
});
|
|
687
|
+
return;
|
|
688
|
+
}
|
|
689
|
+
}
|
|
690
|
+
/**
|
|
691
|
+
* A closure's parameters are written down exactly like a function's.
|
|
692
|
+
*
|
|
693
|
+
* `router.Use(func(c *Context) { c.Next() })` is the commonest shape in Go —
|
|
694
|
+
* every HTTP handler, every `sort.Slice`, every goroutine — and leaving these
|
|
695
|
+
* out of `locals` made the receiver untypeable at each of them. Five of gin's
|
|
696
|
+
* nine measured recall misses were this one omission.
|
|
697
|
+
*
|
|
698
|
+
* The closure's names go into the enclosing function's table rather than a
|
|
699
|
+
* scope of their own, because a closure has no node to attribute references
|
|
700
|
+
* to. That flattening is safe in the direction that matters: `declare()`
|
|
701
|
+
* degrades a name declared twice with different types to `null`, so a genuine
|
|
702
|
+
* shadow produces a *dropped* edge and never a wrong one.
|
|
703
|
+
*/
|
|
704
|
+
if (node.type === "func_literal") {
|
|
705
|
+
const parameters = node.childForFieldName("parameters");
|
|
706
|
+
if (parameters !== null)
|
|
707
|
+
collectParameters(parameters, fn, params, true);
|
|
708
|
+
}
|
|
709
|
+
// `var x T` and `x := expr` — only the first writes a type down.
|
|
710
|
+
if (node.type === "var_declaration" || node.type === "const_declaration") {
|
|
711
|
+
for (let i = 0; i < node.namedChildCount; i += 1) {
|
|
712
|
+
const spec = node.namedChild(i);
|
|
713
|
+
if (spec === null)
|
|
714
|
+
continue;
|
|
715
|
+
const typeNode = spec.childForFieldName("type");
|
|
716
|
+
const declared = baseTypeName(typeNode);
|
|
717
|
+
for (let n = 0; n < spec.namedChildCount; n += 1) {
|
|
718
|
+
const child = spec.namedChild(n);
|
|
719
|
+
if (child !== null && child.type === "identifier")
|
|
720
|
+
declare(fn.locals, child.text, declared);
|
|
721
|
+
}
|
|
722
|
+
const written = [];
|
|
723
|
+
allTypeNames(typeNode, written, params);
|
|
724
|
+
for (const each of written) {
|
|
725
|
+
fn.refs.push({
|
|
726
|
+
kind: "type",
|
|
727
|
+
name: each,
|
|
728
|
+
receiver: undefined,
|
|
729
|
+
line: spec.startPosition.row + 1,
|
|
730
|
+
raw: typeNode?.text.slice(0, 120) ?? each,
|
|
731
|
+
});
|
|
732
|
+
}
|
|
733
|
+
}
|
|
734
|
+
}
|
|
735
|
+
// `o := &Order{…}` — a short declaration whose right-hand side is a composite
|
|
736
|
+
// literal is the one inferred form whose type *is* written down.
|
|
737
|
+
if (node.type === "short_var_declaration") {
|
|
738
|
+
const left = node.childForFieldName("left");
|
|
739
|
+
const right = node.childForFieldName("right");
|
|
740
|
+
const literal = findCompositeType(right);
|
|
741
|
+
if (left !== null && left.namedChildCount === 1 && literal !== undefined) {
|
|
742
|
+
const name = left.namedChild(0);
|
|
743
|
+
if (name !== null && name.type === "identifier")
|
|
744
|
+
declare(fn.locals, name.text, literal);
|
|
745
|
+
}
|
|
746
|
+
else if (left !== null) {
|
|
747
|
+
// `x, err := f(…)` — the type is written down, in the callee's signature
|
|
748
|
+
// rather than here. Recorded as a pending binding and resolved once every
|
|
749
|
+
// package is known; parsing is per file and the callee may be elsewhere.
|
|
750
|
+
const call = calledOnce(right);
|
|
751
|
+
for (let i = 0; i < left.namedChildCount; i += 1) {
|
|
752
|
+
const name = left.namedChild(i);
|
|
753
|
+
if (name === null || name.type !== "identifier")
|
|
754
|
+
continue;
|
|
755
|
+
declare(fn.locals, name.text, undefined);
|
|
756
|
+
if (call === undefined || name.text === "_")
|
|
757
|
+
continue;
|
|
758
|
+
const existing = fn.pending.get(name.text);
|
|
759
|
+
const binding = { ...call, index: i };
|
|
760
|
+
if (existing === undefined)
|
|
761
|
+
fn.pending.set(name.text, binding);
|
|
762
|
+
else if (existing.name !== binding.name || existing.receiver !== binding.receiver || existing.index !== i) {
|
|
763
|
+
// Declared twice from different calls: ambiguous, so neither.
|
|
764
|
+
fn.pending.delete(name.text);
|
|
765
|
+
}
|
|
766
|
+
}
|
|
767
|
+
}
|
|
768
|
+
}
|
|
769
|
+
// `for _, tc := range cases` binds names with no written type. Left out of
|
|
770
|
+
// `locals` they looked like package-level identifiers, and `tc` alone became
|
|
771
|
+
// 1,184 ledger entries on prometheus.
|
|
772
|
+
if (node.type === "range_clause") {
|
|
773
|
+
const left = node.childForFieldName("left");
|
|
774
|
+
for (let i = 0; left !== null && i < left.namedChildCount; i += 1) {
|
|
775
|
+
const name = left.namedChild(i);
|
|
776
|
+
if (name !== null && name.type === "identifier")
|
|
777
|
+
declare(fn.locals, name.text, undefined);
|
|
778
|
+
}
|
|
779
|
+
}
|
|
780
|
+
if (node.type === "composite_literal") {
|
|
781
|
+
const written = baseTypeName(node.childForFieldName("type"));
|
|
782
|
+
if (written !== undefined) {
|
|
783
|
+
fn.refs.push({
|
|
784
|
+
kind: "type",
|
|
785
|
+
name: written,
|
|
786
|
+
receiver: undefined,
|
|
787
|
+
line: node.startPosition.row + 1,
|
|
788
|
+
raw: written,
|
|
789
|
+
});
|
|
790
|
+
}
|
|
791
|
+
}
|
|
792
|
+
if (node.type === "call_expression") {
|
|
793
|
+
const callee = node.childForFieldName("function");
|
|
794
|
+
if (callee !== null) {
|
|
795
|
+
if (callee.type === "selector_expression") {
|
|
796
|
+
const operand = callee.childForFieldName("operand");
|
|
797
|
+
const field = callee.childForFieldName("field");
|
|
798
|
+
if (operand !== null && field !== null) {
|
|
799
|
+
const receiver = receiverName(operand);
|
|
800
|
+
const written = receiverTypeAtSite(operand);
|
|
801
|
+
const call = written === undefined ? receiverCallOf(operand) : undefined;
|
|
802
|
+
const indexed = written === undefined && call === undefined ? indexedReceiver(operand) : undefined;
|
|
803
|
+
fn.refs.push({
|
|
804
|
+
kind: "call",
|
|
805
|
+
name: field.text,
|
|
806
|
+
receiver,
|
|
807
|
+
...(written === undefined ? {} : { receiverType: written }),
|
|
808
|
+
...(call === undefined ? {} : { receiverCall: call }),
|
|
809
|
+
...(indexed === undefined ? {} : { receiverIndex: indexed }),
|
|
810
|
+
line: node.startPosition.row + 1,
|
|
811
|
+
raw: `${receiver}.${field.text}`,
|
|
812
|
+
});
|
|
813
|
+
}
|
|
814
|
+
}
|
|
815
|
+
else if (callee.type === "identifier") {
|
|
816
|
+
fn.refs.push({
|
|
817
|
+
kind: "call",
|
|
818
|
+
name: callee.text,
|
|
819
|
+
receiver: undefined,
|
|
820
|
+
line: node.startPosition.row + 1,
|
|
821
|
+
raw: callee.text,
|
|
822
|
+
});
|
|
823
|
+
}
|
|
824
|
+
}
|
|
825
|
+
}
|
|
826
|
+
// `pkg.Const` / `Type.Member` outside a call — a dependency on the qualifier.
|
|
827
|
+
if (node.type === "selector_expression" && node.parent?.type !== "call_expression") {
|
|
828
|
+
const operand = node.childForFieldName("operand");
|
|
829
|
+
const field = node.childForFieldName("field");
|
|
830
|
+
if (operand !== null && field !== null && operand.type === "identifier") {
|
|
831
|
+
// The *qualified* name, so `model.LabelValue` can resolve to the type in
|
|
832
|
+
// the imported package rather than being asked about as the bare word
|
|
833
|
+
// `model`, which named nothing and produced 1,694 entries on prometheus.
|
|
834
|
+
fn.refs.push({
|
|
835
|
+
kind: "member",
|
|
836
|
+
name: `${operand.text}.${field.text}`,
|
|
837
|
+
receiver: operand.text,
|
|
838
|
+
line: node.startPosition.row + 1,
|
|
839
|
+
raw: `${operand.text}.${field.text}`,
|
|
840
|
+
});
|
|
841
|
+
}
|
|
842
|
+
}
|
|
843
|
+
for (let i = 0; i < node.namedChildCount; i += 1) {
|
|
844
|
+
const child = node.namedChild(i);
|
|
845
|
+
if (child !== null)
|
|
846
|
+
walkBody(child, fn, ctx);
|
|
847
|
+
}
|
|
848
|
+
}
|
|
849
|
+
/**
|
|
850
|
+
* The single call on the right of `:=`, as a name the extractor can resolve.
|
|
851
|
+
*
|
|
852
|
+
* Only a lone call counts. `a, b := f(), g()` binds two different results and
|
|
853
|
+
* `a := f() + 1` binds an expression, so neither is a result binding.
|
|
854
|
+
*/
|
|
855
|
+
function calledOnce(right) {
|
|
856
|
+
if (right === null || right.namedChildCount !== 1)
|
|
857
|
+
return undefined;
|
|
858
|
+
const call = right.namedChild(0);
|
|
859
|
+
if (call === null || call.type !== "call_expression")
|
|
860
|
+
return undefined;
|
|
861
|
+
const callee = call.childForFieldName("function");
|
|
862
|
+
if (callee === null)
|
|
863
|
+
return undefined;
|
|
864
|
+
if (callee.type === "identifier")
|
|
865
|
+
return { receiver: undefined, name: callee.text };
|
|
866
|
+
if (callee.type === "selector_expression") {
|
|
867
|
+
const operand = callee.childForFieldName("operand");
|
|
868
|
+
const field = callee.childForFieldName("field");
|
|
869
|
+
if (operand === null || field === null || operand.type !== "identifier")
|
|
870
|
+
return undefined;
|
|
871
|
+
return { receiver: operand.text, name: field.text };
|
|
872
|
+
}
|
|
873
|
+
return undefined;
|
|
874
|
+
}
|
|
875
|
+
/** The type of a composite literal on the right of `:=`, when there is one. */
|
|
876
|
+
function findCompositeType(node) {
|
|
877
|
+
if (node === null)
|
|
878
|
+
return undefined;
|
|
879
|
+
if (node.type === "composite_literal")
|
|
880
|
+
return baseTypeName(node.childForFieldName("type"));
|
|
881
|
+
if (node.type === "unary_expression" || node.type === "expression_list") {
|
|
882
|
+
for (let i = 0; i < node.namedChildCount; i += 1) {
|
|
883
|
+
const found = findCompositeType(node.namedChild(i));
|
|
884
|
+
if (found !== undefined)
|
|
885
|
+
return found;
|
|
886
|
+
}
|
|
887
|
+
}
|
|
888
|
+
return undefined;
|
|
889
|
+
}
|
|
890
|
+
//# sourceMappingURL=parse.js.map
|