context-slice 1.8.2
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/LICENSE +21 -0
- package/README.md +349 -0
- package/dist/src/cli.js +199 -0
- package/dist/src/indexer/index.js +325 -0
- package/dist/src/languages/adapter.js +23 -0
- package/dist/src/languages/go/index.js +15 -0
- package/dist/src/languages/go/parse.js +443 -0
- package/dist/src/languages/go/resolve.js +313 -0
- package/dist/src/languages/java/enterprise/dependency-injection.js +175 -0
- package/dist/src/languages/java/enterprise/jpa-entity.js +89 -0
- package/dist/src/languages/java/enterprise/registry.js +22 -0
- package/dist/src/languages/java/enterprise/spring-data.js +239 -0
- package/dist/src/languages/java/enterprise/spring-mvc.js +183 -0
- package/dist/src/languages/java/enterprise/transactions.js +110 -0
- package/dist/src/languages/java.js +84 -0
- package/dist/src/languages/javascript/index.js +25 -0
- package/dist/src/languages/python/index.js +29 -0
- package/dist/src/languages/python/parse.js +415 -0
- package/dist/src/languages/python/resolve.js +413 -0
- package/dist/src/languages/rust/calls-resolve.js +1405 -0
- package/dist/src/languages/rust/index.js +12 -0
- package/dist/src/languages/rust/parse.js +545 -0
- package/dist/src/languages/rust/resolve.js +284 -0
- package/dist/src/languages/typescript/index.js +25 -0
- package/dist/src/languages/typescript/parse.js +793 -0
- package/dist/src/languages/typescript/resolve.js +463 -0
- package/dist/src/package-info.js +16 -0
- package/dist/src/parser/java-parser.js +339 -0
- package/dist/src/planner/budget.js +1 -0
- package/dist/src/planner/composition.js +372 -0
- package/dist/src/planner/rank.js +10 -0
- package/dist/src/render/compact-context.js +11 -0
- package/dist/src/server/mcp-server.js +154 -0
- package/dist/src/storage/sqlite.js +105 -0
- package/dist/src/types/enterprise.js +1 -0
- package/dist/src/types/model.js +1 -0
- package/dist/src/workflow/errors.js +10 -0
- package/dist/src/workflow/preview.js +220 -0
- package/dist/src/workflow/repository.js +56 -0
- package/mcp.json +10 -0
- package/package.json +76 -0
- package/plugin.json +15 -0
- package/queries/java/annotations.scm +1 -0
- package/queries/java/calls.scm +1 -0
- package/queries/java/imports.scm +1 -0
- package/queries/java/symbols.scm +2 -0
- package/skills/context-slice/SKILL.md +60 -0
|
@@ -0,0 +1,443 @@
|
|
|
1
|
+
import Parser from "tree-sitter";
|
|
2
|
+
import Go from "tree-sitter-go";
|
|
3
|
+
export const LANGUAGE_ID = "go";
|
|
4
|
+
let parser;
|
|
5
|
+
function goParser() {
|
|
6
|
+
if (!parser) {
|
|
7
|
+
parser = new Parser();
|
|
8
|
+
parser.setLanguage(Go);
|
|
9
|
+
}
|
|
10
|
+
return parser;
|
|
11
|
+
}
|
|
12
|
+
const range = (node) => ({
|
|
13
|
+
startLine: node.startPosition.row + 1,
|
|
14
|
+
startColumn: node.startPosition.column,
|
|
15
|
+
endLine: node.endPosition.row + 1,
|
|
16
|
+
endColumn: node.endPosition.column,
|
|
17
|
+
});
|
|
18
|
+
const field = (node, name) => node.childForFieldName(name);
|
|
19
|
+
const text = (node) => node?.text ?? "";
|
|
20
|
+
/**
|
|
21
|
+
* A method's receiver base type name, e.g. "User" for "*User"/"User", or
|
|
22
|
+
* "Stack" for a generic receiver "*Stack[T]"/"Stack[T]" (verified: the
|
|
23
|
+
* receiver's own parameter_declaration has a "type" field that is either a
|
|
24
|
+
* pointer_type node (whose own namedChild(0) is the base type_identifier or
|
|
25
|
+
* a generic_type), a generic_type node directly, or a type_identifier node
|
|
26
|
+
* directly for a plain value receiver. generic_type's base identifier is
|
|
27
|
+
* reachable via its own "type" field, verified empirically — not a "name"
|
|
28
|
+
* field as one might guess).
|
|
29
|
+
*/
|
|
30
|
+
function receiverBaseTypeName(methodNode) {
|
|
31
|
+
const receiverList = field(methodNode, "receiver");
|
|
32
|
+
const paramDecl = receiverList?.namedChildren.find((c) => c.type === "parameter_declaration");
|
|
33
|
+
let typeNode = paramDecl ? field(paramDecl, "type") : undefined;
|
|
34
|
+
if (!typeNode)
|
|
35
|
+
return undefined;
|
|
36
|
+
if (typeNode.type === "pointer_type") {
|
|
37
|
+
typeNode = typeNode.namedChild(0) ?? undefined;
|
|
38
|
+
}
|
|
39
|
+
if (!typeNode)
|
|
40
|
+
return undefined;
|
|
41
|
+
if (typeNode.type === "generic_type") {
|
|
42
|
+
const base = field(typeNode, "type");
|
|
43
|
+
return base?.type === "type_identifier" ? base.text : undefined;
|
|
44
|
+
}
|
|
45
|
+
return typeNode.type === "type_identifier" ? typeNode.text : undefined;
|
|
46
|
+
}
|
|
47
|
+
function canonicalId(filePath, kind, name, parameters) {
|
|
48
|
+
return [filePath, kind, name, parameters].filter(Boolean).join("::");
|
|
49
|
+
}
|
|
50
|
+
function modifiersFor(name) {
|
|
51
|
+
return /^[A-Z]/.test(name) ? ["exported"] : [];
|
|
52
|
+
}
|
|
53
|
+
/** Ordered, name-stripped parameter TYPES only, e.g. "(a, b string, c int)" -> "string,string,int"
|
|
54
|
+
* — used only to compare two signatures for interface satisfaction, never displayed. A grouped
|
|
55
|
+
* declaration ("a, b string") is one parameter_declaration node whose namedChildren are every name
|
|
56
|
+
* identifier followed by the shared type; the type is duplicated once per name. A declaration with
|
|
57
|
+
* no name (just a type, legal in both interface methods and func types) has exactly one named
|
|
58
|
+
* child, which is the type. Anything else (e.g. a variadic_parameter_declaration) falls back to its
|
|
59
|
+
* own full text as one opaque "type" — consistent as long as it's compared against itself. */
|
|
60
|
+
function paramTypesOf(parameters) {
|
|
61
|
+
if (!parameters)
|
|
62
|
+
return "";
|
|
63
|
+
const types = [];
|
|
64
|
+
for (const decl of parameters.namedChildren) {
|
|
65
|
+
if (decl.type === "parameter_declaration") {
|
|
66
|
+
const children = decl.namedChildren;
|
|
67
|
+
const type = children.at(-1)?.text ?? "";
|
|
68
|
+
const count = Math.max(1, children.length - 1);
|
|
69
|
+
for (let i = 0; i < count; i++)
|
|
70
|
+
types.push(type);
|
|
71
|
+
}
|
|
72
|
+
else {
|
|
73
|
+
types.push(decl.text);
|
|
74
|
+
}
|
|
75
|
+
}
|
|
76
|
+
return types.join(",");
|
|
77
|
+
}
|
|
78
|
+
function resultTypesOf(result) {
|
|
79
|
+
if (!result)
|
|
80
|
+
return "";
|
|
81
|
+
return result.type === "parameter_list" ? paramTypesOf(result) : result.text;
|
|
82
|
+
}
|
|
83
|
+
/** A normalized "(types)result" signature for a function/method/interface-method node — comparable
|
|
84
|
+
* by exact string equality regardless of the declared parameter NAMES, which Go's own rules never
|
|
85
|
+
* require to match for interface satisfaction. */
|
|
86
|
+
function methodSignatureOf(node) {
|
|
87
|
+
return `(${paramTypesOf(field(node, "parameters"))})${resultTypesOf(field(node, "result"))}`;
|
|
88
|
+
}
|
|
89
|
+
/** A constructor-style function's own declared return type, bare pointer-stripped (e.g. "*Bar" ->
|
|
90
|
+
* "Bar"), used to look up what a "x := NewFoo()" binding actually constructs instead of assuming
|
|
91
|
+
* the type from the function's own name. A multi-value return ("(*Bar, error)") takes the first
|
|
92
|
+
* value, matching Go's constructor convention of returning the created value first. */
|
|
93
|
+
function primaryReturnType(result) {
|
|
94
|
+
if (!result)
|
|
95
|
+
return undefined;
|
|
96
|
+
const first = result.type === "parameter_list" ? result.namedChildren[0] : result;
|
|
97
|
+
if (!first)
|
|
98
|
+
return undefined;
|
|
99
|
+
const typeNode = first.type === "parameter_declaration" ? (first.namedChildren.at(-1) ?? first) : first;
|
|
100
|
+
return typeNode.text.replace(/^\*/, "");
|
|
101
|
+
}
|
|
102
|
+
/**
|
|
103
|
+
* A call_expression's callee name and (when the operand is a plain identifier) its
|
|
104
|
+
* receiver text. selector_expression's "operand"/"field" fields and call_expression's
|
|
105
|
+
* "function"/"arguments" fields verified empirically against tree-sitter-go@0.23.4.
|
|
106
|
+
*/
|
|
107
|
+
function buildCallEdge(callNode, filePath, callerId, wrapKind) {
|
|
108
|
+
const fnNode = field(callNode, "function");
|
|
109
|
+
const argsNode = field(callNode, "arguments");
|
|
110
|
+
const argumentCount = argsNode?.namedChildCount ?? 0;
|
|
111
|
+
const evidence = ["syntactic call"];
|
|
112
|
+
if (wrapKind === "go")
|
|
113
|
+
evidence.push("goroutine launch");
|
|
114
|
+
else if (wrapKind === "defer")
|
|
115
|
+
evidence.push("deferred call");
|
|
116
|
+
let calleeName = "";
|
|
117
|
+
let receiverText;
|
|
118
|
+
if (fnNode?.type === "identifier") {
|
|
119
|
+
calleeName = fnNode.text;
|
|
120
|
+
}
|
|
121
|
+
else if (fnNode?.type === "selector_expression") {
|
|
122
|
+
calleeName = text(field(fnNode, "field"));
|
|
123
|
+
const operand = field(fnNode, "operand");
|
|
124
|
+
if (operand?.type === "identifier")
|
|
125
|
+
receiverText = operand.text;
|
|
126
|
+
}
|
|
127
|
+
return {
|
|
128
|
+
callerId,
|
|
129
|
+
calleeName,
|
|
130
|
+
receiverText,
|
|
131
|
+
argumentCount,
|
|
132
|
+
filePath,
|
|
133
|
+
language: LANGUAGE_ID,
|
|
134
|
+
range: range(callNode),
|
|
135
|
+
confidence: "unresolved",
|
|
136
|
+
resolutionKind: "unresolved",
|
|
137
|
+
evidence,
|
|
138
|
+
};
|
|
139
|
+
}
|
|
140
|
+
/**
|
|
141
|
+
* Walks every descendant of `node` for call_expressions, attributing each to `ownerId`
|
|
142
|
+
* regardless of nesting depth (blocks, closures — Go closures have no symbol of their
|
|
143
|
+
* own in this adapter's model, so their calls attribute to the innermost enclosing
|
|
144
|
+
* NAMED function/method). Whether a call is the direct target of a go/defer statement
|
|
145
|
+
* is read off the immediate parent's type during the walk — verified empirically that
|
|
146
|
+
* this correctly tags only the direct target, never a call nested inside its arguments.
|
|
147
|
+
*/
|
|
148
|
+
function collectCalls(node, ownerId, filePath, calls) {
|
|
149
|
+
for (const child of node.namedChildren) {
|
|
150
|
+
if (child.type === "call_expression") {
|
|
151
|
+
const wrapKind = node.type === "go_statement" ? "go" : node.type === "defer_statement" ? "defer" : undefined;
|
|
152
|
+
const edge = buildCallEdge(child, filePath, ownerId, wrapKind);
|
|
153
|
+
// Skip edges whose callee couldn't be named (e.g. `go func(){ ... }()`,
|
|
154
|
+
// `getHandler()()`, `(g)()`) rather than emitting a garbage calleeName: "" edge.
|
|
155
|
+
// Still recurse below so calls nested inside the callee/args are found.
|
|
156
|
+
if (edge.calleeName !== "")
|
|
157
|
+
calls.push(edge);
|
|
158
|
+
}
|
|
159
|
+
collectCalls(child, ownerId, filePath, calls);
|
|
160
|
+
}
|
|
161
|
+
}
|
|
162
|
+
export function parseGo(filePath, source) {
|
|
163
|
+
const symbols = [];
|
|
164
|
+
const calls = [];
|
|
165
|
+
const imports = [];
|
|
166
|
+
const exports = [];
|
|
167
|
+
let parseError = false;
|
|
168
|
+
let tree;
|
|
169
|
+
try {
|
|
170
|
+
// tree-sitter's node binding rejects large single-string inputs; feed it in chunks,
|
|
171
|
+
// matching every other adapter in this project (rust, typescript).
|
|
172
|
+
tree = goParser().parse((index) => source.slice(index, index + 4_096));
|
|
173
|
+
}
|
|
174
|
+
catch {
|
|
175
|
+
return { symbols, calls, imports, exports, parseError: true };
|
|
176
|
+
}
|
|
177
|
+
if (tree.rootNode.hasError)
|
|
178
|
+
parseError = true;
|
|
179
|
+
const seenIds = new Set();
|
|
180
|
+
const uniqueId = (id) => {
|
|
181
|
+
if (!seenIds.has(id)) {
|
|
182
|
+
seenIds.add(id);
|
|
183
|
+
return id;
|
|
184
|
+
}
|
|
185
|
+
let n = 2;
|
|
186
|
+
while (seenIds.has(`${id}#${n}`))
|
|
187
|
+
n++;
|
|
188
|
+
const deduped = `${id}#${n}`;
|
|
189
|
+
seenIds.add(deduped);
|
|
190
|
+
return deduped;
|
|
191
|
+
};
|
|
192
|
+
const structByName = new Map();
|
|
193
|
+
const pendingMethods = [];
|
|
194
|
+
const callables = [];
|
|
195
|
+
for (const child of tree.rootNode.namedChildren) {
|
|
196
|
+
if (child.type === "function_declaration") {
|
|
197
|
+
const name = text(field(child, "name"));
|
|
198
|
+
if (!name)
|
|
199
|
+
continue;
|
|
200
|
+
const parameters = text(field(child, "parameters"));
|
|
201
|
+
const id = uniqueId(canonicalId(filePath, "function", name, parameters));
|
|
202
|
+
const returnType = primaryReturnType(field(child, "result"));
|
|
203
|
+
const symbol = {
|
|
204
|
+
id,
|
|
205
|
+
language: LANGUAGE_ID,
|
|
206
|
+
kind: "function",
|
|
207
|
+
name,
|
|
208
|
+
qualifiedName: name,
|
|
209
|
+
canonicalIdentity: id,
|
|
210
|
+
signature: `func ${name}${parameters}`,
|
|
211
|
+
filePath,
|
|
212
|
+
range: range(child),
|
|
213
|
+
bodyRange: field(child, "body") ? range(field(child, "body")) : undefined,
|
|
214
|
+
annotations: [],
|
|
215
|
+
modifiers: modifiersFor(name),
|
|
216
|
+
metadata: returnType ? { returnType } : undefined,
|
|
217
|
+
source: child.text,
|
|
218
|
+
body: field(child, "body")?.text,
|
|
219
|
+
};
|
|
220
|
+
symbols.push(symbol);
|
|
221
|
+
callables.push({ symbol, body: field(child, "body") });
|
|
222
|
+
}
|
|
223
|
+
else if (child.type === "method_declaration") {
|
|
224
|
+
// Deferred: struct symbols may appear later in this same file's top-level
|
|
225
|
+
// children (Go has no forward-declaration ordering requirement), so method
|
|
226
|
+
// linkage runs in a second pass below once every struct in this file is known.
|
|
227
|
+
pendingMethods.push({ node: child, receiverName: receiverBaseTypeName(child) });
|
|
228
|
+
}
|
|
229
|
+
else if (child.type === "type_declaration") {
|
|
230
|
+
for (const spec of child.namedChildren.filter((c) => c.type === "type_spec" || c.type === "type_alias")) {
|
|
231
|
+
const name = text(field(spec, "name"));
|
|
232
|
+
const typeNode = field(spec, "type");
|
|
233
|
+
if (!name || !typeNode)
|
|
234
|
+
continue;
|
|
235
|
+
if (typeNode.type === "struct_type") {
|
|
236
|
+
const id = uniqueId(canonicalId(filePath, "class", name));
|
|
237
|
+
const symbol = {
|
|
238
|
+
id,
|
|
239
|
+
language: LANGUAGE_ID,
|
|
240
|
+
kind: "class",
|
|
241
|
+
name,
|
|
242
|
+
qualifiedName: name,
|
|
243
|
+
canonicalIdentity: id,
|
|
244
|
+
signature: `type ${name} struct`,
|
|
245
|
+
filePath,
|
|
246
|
+
range: range(spec),
|
|
247
|
+
annotations: [],
|
|
248
|
+
modifiers: modifiersFor(name),
|
|
249
|
+
source: spec.text,
|
|
250
|
+
};
|
|
251
|
+
symbols.push(symbol);
|
|
252
|
+
structByName.set(name, symbol);
|
|
253
|
+
// struct_type's field list is a NAMED CHILD (field_declaration_list), not
|
|
254
|
+
// reachable via childForFieldName("body") — verified empirically: field()
|
|
255
|
+
// returns undefined here, so the fallback lookup is required, not optional.
|
|
256
|
+
const fieldList = typeNode.namedChildren.find((c) => c.type === "field_declaration_list");
|
|
257
|
+
for (const fieldDecl of fieldList?.namedChildren.filter((c) => c.type === "field_declaration") ?? []) {
|
|
258
|
+
// An embedded field (e.g. plain "Base") has no "name" field, only "type" —
|
|
259
|
+
// verified empirically: field_declaration's name field is absent for embeds.
|
|
260
|
+
const explicitName = text(field(fieldDecl, "name"));
|
|
261
|
+
const fieldName = explicitName || text(field(fieldDecl, "type"));
|
|
262
|
+
if (!fieldName)
|
|
263
|
+
continue;
|
|
264
|
+
const fieldId = uniqueId(canonicalId(filePath, "field", `${name}.${fieldName}`));
|
|
265
|
+
symbols.push({
|
|
266
|
+
id: fieldId,
|
|
267
|
+
language: LANGUAGE_ID,
|
|
268
|
+
kind: "field",
|
|
269
|
+
name: fieldName,
|
|
270
|
+
qualifiedName: `${name}.${fieldName}`,
|
|
271
|
+
canonicalIdentity: fieldId,
|
|
272
|
+
signature: fieldDecl.text,
|
|
273
|
+
filePath,
|
|
274
|
+
range: range(fieldDecl),
|
|
275
|
+
parentId: id,
|
|
276
|
+
annotations: [],
|
|
277
|
+
modifiers: modifiersFor(fieldName),
|
|
278
|
+
metadata: explicitName ? undefined : { embedded: true },
|
|
279
|
+
source: fieldDecl.text,
|
|
280
|
+
});
|
|
281
|
+
}
|
|
282
|
+
}
|
|
283
|
+
else if (typeNode.type === "interface_type") {
|
|
284
|
+
const id = uniqueId(canonicalId(filePath, "interface", name));
|
|
285
|
+
const methodElems = typeNode.namedChildren.filter((c) => c.type === "method_elem");
|
|
286
|
+
const interfaceMethods = methodElems.map((m) => text(m.namedChild(0))).filter(Boolean);
|
|
287
|
+
const interfaceMethodSignatures = {};
|
|
288
|
+
for (const m of methodElems) {
|
|
289
|
+
const methodName = text(m.namedChild(0));
|
|
290
|
+
if (methodName)
|
|
291
|
+
interfaceMethodSignatures[methodName] = methodSignatureOf(m);
|
|
292
|
+
}
|
|
293
|
+
symbols.push({
|
|
294
|
+
id,
|
|
295
|
+
language: LANGUAGE_ID,
|
|
296
|
+
kind: "interface",
|
|
297
|
+
name,
|
|
298
|
+
qualifiedName: name,
|
|
299
|
+
canonicalIdentity: id,
|
|
300
|
+
signature: `type ${name} interface`,
|
|
301
|
+
filePath,
|
|
302
|
+
range: range(spec),
|
|
303
|
+
annotations: [],
|
|
304
|
+
modifiers: modifiersFor(name),
|
|
305
|
+
metadata: { interfaceMethods, interfaceMethodSignatures },
|
|
306
|
+
source: spec.text,
|
|
307
|
+
});
|
|
308
|
+
}
|
|
309
|
+
else {
|
|
310
|
+
const id = uniqueId(canonicalId(filePath, "type", name));
|
|
311
|
+
symbols.push({
|
|
312
|
+
id,
|
|
313
|
+
language: LANGUAGE_ID,
|
|
314
|
+
kind: "type",
|
|
315
|
+
name,
|
|
316
|
+
qualifiedName: name,
|
|
317
|
+
canonicalIdentity: id,
|
|
318
|
+
signature: `type ${name} ${text(typeNode)}`,
|
|
319
|
+
filePath,
|
|
320
|
+
range: range(spec),
|
|
321
|
+
annotations: [],
|
|
322
|
+
modifiers: modifiersFor(name),
|
|
323
|
+
source: spec.text,
|
|
324
|
+
});
|
|
325
|
+
}
|
|
326
|
+
}
|
|
327
|
+
}
|
|
328
|
+
else if (child.type === "const_declaration" || child.type === "var_declaration") {
|
|
329
|
+
const kind = child.type === "const_declaration" ? "const" : "var";
|
|
330
|
+
const specType = kind === "const" ? "const_spec" : "var_spec";
|
|
331
|
+
for (const spec of child.namedChildren.filter((c) => c.type === specType)) {
|
|
332
|
+
// const_spec/var_spec's "name" field only yields the FIRST identifier in a
|
|
333
|
+
// multi-name spec (e.g. "var a, b int"); Phase 1 scope is single-name specs,
|
|
334
|
+
// the common case — multi-name specs are a known, accepted gap, not a bug to
|
|
335
|
+
// chase here (mirrors this project's established "accepted divergence" pattern
|
|
336
|
+
// for narrow multi-declarator gaps elsewhere, e.g. Java field extraction).
|
|
337
|
+
const name = text(field(spec, "name"));
|
|
338
|
+
if (!name)
|
|
339
|
+
continue;
|
|
340
|
+
const id = uniqueId(canonicalId(filePath, "variable", name));
|
|
341
|
+
symbols.push({
|
|
342
|
+
id,
|
|
343
|
+
language: LANGUAGE_ID,
|
|
344
|
+
kind: "variable",
|
|
345
|
+
name,
|
|
346
|
+
qualifiedName: name,
|
|
347
|
+
canonicalIdentity: id,
|
|
348
|
+
signature: spec.text,
|
|
349
|
+
filePath,
|
|
350
|
+
range: range(spec),
|
|
351
|
+
annotations: [],
|
|
352
|
+
modifiers: modifiersFor(name),
|
|
353
|
+
source: spec.text,
|
|
354
|
+
});
|
|
355
|
+
}
|
|
356
|
+
}
|
|
357
|
+
else if (child.type === "import_declaration") {
|
|
358
|
+
const specs = child.namedChildren.flatMap((c) => c.type === "import_spec_list"
|
|
359
|
+
? c.namedChildren.filter((s) => s.type === "import_spec")
|
|
360
|
+
: c.type === "import_spec"
|
|
361
|
+
? [c]
|
|
362
|
+
: []);
|
|
363
|
+
for (const spec of specs) {
|
|
364
|
+
const pathNode = field(spec, "path");
|
|
365
|
+
const module = text(pathNode?.namedChild(0) ?? pathNode).replace(/^["']|["']$/g, "");
|
|
366
|
+
if (!module)
|
|
367
|
+
continue;
|
|
368
|
+
const nameNode = field(spec, "name");
|
|
369
|
+
if (nameNode?.type === "blank_identifier") {
|
|
370
|
+
imports.push({
|
|
371
|
+
filePath,
|
|
372
|
+
language: LANGUAGE_ID,
|
|
373
|
+
module,
|
|
374
|
+
kind: "side-effect",
|
|
375
|
+
typeOnly: false,
|
|
376
|
+
range: range(spec),
|
|
377
|
+
});
|
|
378
|
+
}
|
|
379
|
+
else if (nameNode?.type === "dot") {
|
|
380
|
+
imports.push({
|
|
381
|
+
filePath,
|
|
382
|
+
language: LANGUAGE_ID,
|
|
383
|
+
module,
|
|
384
|
+
kind: "namespace",
|
|
385
|
+
typeOnly: false,
|
|
386
|
+
wildcard: true,
|
|
387
|
+
range: range(spec),
|
|
388
|
+
});
|
|
389
|
+
}
|
|
390
|
+
else {
|
|
391
|
+
imports.push({
|
|
392
|
+
filePath,
|
|
393
|
+
language: LANGUAGE_ID,
|
|
394
|
+
module,
|
|
395
|
+
kind: "namespace",
|
|
396
|
+
typeOnly: false,
|
|
397
|
+
localName: nameNode?.type === "package_identifier" ? nameNode.text : undefined,
|
|
398
|
+
range: range(spec),
|
|
399
|
+
});
|
|
400
|
+
}
|
|
401
|
+
}
|
|
402
|
+
}
|
|
403
|
+
}
|
|
404
|
+
// Second pass: by now structByName holds every struct declared in THIS file (built
|
|
405
|
+
// in the first pass above), so same-file receiver linkage is real here. A method
|
|
406
|
+
// whose receiver type isn't in this file's own structByName correctly gets
|
|
407
|
+
// parentId: undefined with supertypes still set — never dropped, never misattributed.
|
|
408
|
+
for (const { node: methodNode, receiverName } of pendingMethods) {
|
|
409
|
+
const name = text(field(methodNode, "name"));
|
|
410
|
+
if (!name)
|
|
411
|
+
continue;
|
|
412
|
+
const parameters = text(field(methodNode, "parameters"));
|
|
413
|
+
const qualifiedName = receiverName ? `${receiverName}.${name}` : name;
|
|
414
|
+
const id = uniqueId(canonicalId(filePath, "method", qualifiedName, parameters));
|
|
415
|
+
const receiverStruct = receiverName ? structByName.get(receiverName) : undefined;
|
|
416
|
+
const symbol = {
|
|
417
|
+
id,
|
|
418
|
+
language: LANGUAGE_ID,
|
|
419
|
+
kind: "method",
|
|
420
|
+
name,
|
|
421
|
+
qualifiedName,
|
|
422
|
+
canonicalIdentity: id,
|
|
423
|
+
signature: `func (${receiverName ?? ""}) ${name}${parameters}`,
|
|
424
|
+
filePath,
|
|
425
|
+
range: range(methodNode),
|
|
426
|
+
bodyRange: field(methodNode, "body") ? range(field(methodNode, "body")) : undefined,
|
|
427
|
+
parentId: receiverStruct?.id,
|
|
428
|
+
supertypes: receiverName ? [receiverName] : undefined,
|
|
429
|
+
annotations: [],
|
|
430
|
+
modifiers: modifiersFor(name),
|
|
431
|
+
metadata: { methodSignature: methodSignatureOf(methodNode) },
|
|
432
|
+
source: methodNode.text,
|
|
433
|
+
body: field(methodNode, "body")?.text,
|
|
434
|
+
};
|
|
435
|
+
symbols.push(symbol);
|
|
436
|
+
callables.push({ symbol, body: field(methodNode, "body") });
|
|
437
|
+
}
|
|
438
|
+
for (const { symbol, body } of callables) {
|
|
439
|
+
if (body)
|
|
440
|
+
collectCalls(body, symbol.id, filePath, calls);
|
|
441
|
+
}
|
|
442
|
+
return { symbols, calls, imports, exports, parseError };
|
|
443
|
+
}
|