@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/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