@nanonets/graft 0.10.0 → 0.12.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/README.md +34 -13
- package/dist/ai/crux.d.ts.map +1 -1
- package/dist/ai/crux.js +7 -6
- package/dist/ai/crux.js.map +1 -1
- package/dist/ai/failure.d.ts +49 -0
- package/dist/ai/failure.d.ts.map +1 -0
- package/dist/ai/failure.js +74 -0
- package/dist/ai/failure.js.map +1 -0
- package/dist/ai/llm/anthropic.d.ts.map +1 -1
- package/dist/ai/llm/anthropic.js +4 -1
- package/dist/ai/llm/anthropic.js.map +1 -1
- package/dist/ai/llm/claude-cli.d.ts +60 -0
- package/dist/ai/llm/claude-cli.d.ts.map +1 -0
- package/dist/ai/llm/claude-cli.js +417 -0
- package/dist/ai/llm/claude-cli.js.map +1 -0
- package/dist/ai/llm/openai.d.ts.map +1 -1
- package/dist/ai/llm/openai.js +2 -0
- package/dist/ai/llm/openai.js.map +1 -1
- package/dist/ai/llm/types.d.ts +9 -0
- package/dist/ai/llm/types.d.ts.map +1 -1
- package/dist/ai/llm/types.js +12 -1
- package/dist/ai/llm/types.js.map +1 -1
- package/dist/ask/ask.d.ts.map +1 -1
- package/dist/ask/ask.js +39 -45
- package/dist/ask/ask.js.map +1 -1
- package/dist/ask/fuse.d.ts +83 -46
- package/dist/ask/fuse.d.ts.map +1 -1
- package/dist/ask/fuse.js +165 -67
- package/dist/ask/fuse.js.map +1 -1
- package/dist/blast/blast-cli.d.ts +30 -0
- package/dist/blast/blast-cli.d.ts.map +1 -0
- package/dist/blast/blast-cli.js +163 -0
- package/dist/blast/blast-cli.js.map +1 -0
- package/dist/blast/blast.d.ts +119 -0
- package/dist/blast/blast.d.ts.map +1 -0
- package/dist/blast/blast.js +383 -0
- package/dist/blast/blast.js.map +1 -0
- package/dist/blast/diff.d.ts +53 -0
- package/dist/blast/diff.d.ts.map +1 -0
- package/dist/blast/diff.js +193 -0
- package/dist/blast/diff.js.map +1 -0
- package/dist/blast/modules.d.ts +33 -0
- package/dist/blast/modules.d.ts.map +1 -0
- package/dist/blast/modules.js +83 -0
- package/dist/blast/modules.js.map +1 -0
- package/dist/blast/name.d.ts +61 -0
- package/dist/blast/name.d.ts.map +1 -0
- package/dist/blast/name.js +233 -0
- package/dist/blast/name.js.map +1 -0
- package/dist/blast/render.d.ts +37 -0
- package/dist/blast/render.d.ts.map +1 -0
- package/dist/blast/render.js +300 -0
- package/dist/blast/render.js.map +1 -0
- package/dist/blast/viz.d.ts +17 -0
- package/dist/blast/viz.d.ts.map +1 -0
- package/dist/blast/viz.js +324 -0
- package/dist/blast/viz.js.map +1 -0
- package/dist/claude/hooks.d.ts +13 -0
- package/dist/claude/hooks.d.ts.map +1 -1
- package/dist/claude/hooks.js +44 -4
- package/dist/claude/hooks.js.map +1 -1
- package/dist/claude/shim-template.d.ts.map +1 -1
- package/dist/claude/shim-template.js +45 -15
- package/dist/claude/shim-template.js.map +1 -1
- package/dist/claude/state.d.ts +5 -0
- package/dist/claude/state.d.ts.map +1 -1
- package/dist/claude/state.js.map +1 -1
- package/dist/cli-picker.d.ts +44 -5
- package/dist/cli-picker.d.ts.map +1 -1
- package/dist/cli-picker.js +73 -26
- package/dist/cli-picker.js.map +1 -1
- package/dist/cli.js +270 -14
- package/dist/cli.js.map +1 -1
- package/dist/context/build.d.ts +8 -0
- package/dist/context/build.d.ts.map +1 -1
- package/dist/context/build.js +57 -9
- package/dist/context/build.js.map +1 -1
- package/dist/context/check.d.ts.map +1 -1
- package/dist/context/check.js +7 -6
- package/dist/context/check.js.map +1 -1
- package/dist/context/node-file.d.ts.map +1 -1
- package/dist/context/node-file.js +9 -2
- package/dist/context/node-file.js.map +1 -1
- package/dist/graph/bindings.d.ts.map +1 -1
- package/dist/graph/bindings.js +245 -2
- package/dist/graph/bindings.js.map +1 -1
- package/dist/graph/build.d.ts.map +1 -1
- package/dist/graph/build.js +36 -16
- package/dist/graph/build.js.map +1 -1
- package/dist/graph/check.d.ts +4 -0
- package/dist/graph/check.d.ts.map +1 -1
- package/dist/graph/check.js +8 -1
- package/dist/graph/check.js.map +1 -1
- package/dist/graph/container.d.ts +63 -0
- package/dist/graph/container.d.ts.map +1 -0
- package/dist/graph/container.js +185 -0
- package/dist/graph/container.js.map +1 -0
- package/dist/graph/enrich.d.ts +18 -0
- package/dist/graph/enrich.d.ts.map +1 -1
- package/dist/graph/enrich.js +55 -2
- package/dist/graph/enrich.js.map +1 -1
- package/dist/graph/extract.d.ts +8 -1
- package/dist/graph/extract.d.ts.map +1 -1
- package/dist/graph/extract.js +401 -12
- package/dist/graph/extract.js.map +1 -1
- package/dist/graph/generic.d.ts +34 -1
- package/dist/graph/generic.d.ts.map +1 -1
- package/dist/graph/generic.js +176 -6
- package/dist/graph/generic.js.map +1 -1
- package/dist/graph/invariants.d.ts +32 -0
- package/dist/graph/invariants.d.ts.map +1 -0
- package/dist/graph/invariants.js +49 -0
- package/dist/graph/invariants.js.map +1 -0
- package/dist/graph/queries/clojure.scm +58 -0
- package/dist/graph/resolve.d.ts.map +1 -1
- package/dist/graph/resolve.js +249 -23
- package/dist/graph/resolve.js.map +1 -1
- package/dist/graph/scopes.d.ts +2 -2
- package/dist/graph/scopes.d.ts.map +1 -1
- package/dist/graph/scopes.js +8 -6
- package/dist/graph/scopes.js.map +1 -1
- package/dist/graph/source-files.d.ts +13 -4
- package/dist/graph/source-files.d.ts.map +1 -1
- package/dist/graph/source-files.js +35 -12
- package/dist/graph/source-files.js.map +1 -1
- package/dist/graph/types.d.ts +3 -1
- package/dist/graph/types.d.ts.map +1 -1
- package/dist/graph/workspace-cli.d.ts +2 -0
- package/dist/graph/workspace-cli.d.ts.map +1 -1
- package/dist/graph/workspace-cli.js +10 -4
- package/dist/graph/workspace-cli.js.map +1 -1
- package/dist/graph/workspace.d.ts +11 -6
- package/dist/graph/workspace.d.ts.map +1 -1
- package/dist/graph/workspace.js +24 -13
- package/dist/graph/workspace.js.map +1 -1
- package/dist/graph/write.d.ts.map +1 -1
- package/dist/graph/write.js +25 -2
- package/dist/graph/write.js.map +1 -1
- package/dist/hosts/antigravity.d.ts +7 -0
- package/dist/hosts/antigravity.d.ts.map +1 -0
- package/dist/hosts/antigravity.js +42 -0
- package/dist/hosts/antigravity.js.map +1 -0
- package/dist/hosts/codex-hooks.d.ts +3 -3
- package/dist/hosts/codex-hooks.d.ts.map +1 -1
- package/dist/hosts/codex-hooks.js +27 -21
- package/dist/hosts/codex-hooks.js.map +1 -1
- package/dist/hosts/init.d.ts.map +1 -1
- package/dist/hosts/init.js +6 -1
- package/dist/hosts/init.js.map +1 -1
- package/dist/hosts/mcp-config.d.ts.map +1 -1
- package/dist/hosts/mcp-config.js +7 -0
- package/dist/hosts/mcp-config.js.map +1 -1
- package/dist/hosts/plan.d.ts +1 -1
- package/dist/hosts/plan.d.ts.map +1 -1
- package/dist/hosts/plan.js +2 -0
- package/dist/hosts/plan.js.map +1 -1
- package/dist/hosts/registry.d.ts.map +1 -1
- package/dist/hosts/registry.js +13 -0
- package/dist/hosts/registry.js.map +1 -1
- package/dist/ingest/fs.d.ts +9 -2
- package/dist/ingest/fs.d.ts.map +1 -1
- package/dist/ingest/fs.js +100 -16
- package/dist/ingest/fs.js.map +1 -1
- package/dist/mcp/server.d.ts.map +1 -1
- package/dist/mcp/server.js +45 -2
- package/dist/mcp/server.js.map +1 -1
- package/dist/telemetry/contract.d.ts +90 -0
- package/dist/telemetry/contract.d.ts.map +1 -0
- package/dist/telemetry/contract.js +203 -0
- package/dist/telemetry/contract.js.map +1 -0
- package/dist/telemetry/flush.d.ts +21 -0
- package/dist/telemetry/flush.d.ts.map +1 -0
- package/dist/telemetry/flush.js +84 -0
- package/dist/telemetry/flush.js.map +1 -0
- package/dist/telemetry/gate.d.ts +16 -0
- package/dist/telemetry/gate.d.ts.map +1 -0
- package/dist/telemetry/gate.js +64 -0
- package/dist/telemetry/gate.js.map +1 -0
- package/dist/telemetry/identity.d.ts +33 -0
- package/dist/telemetry/identity.d.ts.map +1 -0
- package/dist/telemetry/identity.js +80 -0
- package/dist/telemetry/identity.js.map +1 -0
- package/dist/telemetry/index.d.ts +18 -0
- package/dist/telemetry/index.d.ts.map +1 -0
- package/dist/telemetry/index.js +16 -0
- package/dist/telemetry/index.js.map +1 -0
- package/dist/telemetry/key.d.ts +21 -0
- package/dist/telemetry/key.d.ts.map +1 -0
- package/dist/telemetry/key.js +49 -0
- package/dist/telemetry/key.js.map +1 -0
- package/dist/telemetry/notice.d.ts +19 -0
- package/dist/telemetry/notice.d.ts.map +1 -0
- package/dist/telemetry/notice.js +90 -0
- package/dist/telemetry/notice.js.map +1 -0
- package/dist/telemetry/queue.d.ts +35 -0
- package/dist/telemetry/queue.d.ts.map +1 -0
- package/dist/telemetry/queue.js +162 -0
- package/dist/telemetry/queue.js.map +1 -0
- package/dist/telemetry/send.d.ts +19 -0
- package/dist/telemetry/send.d.ts.map +1 -0
- package/dist/telemetry/send.js +84 -0
- package/dist/telemetry/send.js.map +1 -0
- package/dist/telemetry/sessions.d.ts +8 -0
- package/dist/telemetry/sessions.d.ts.map +1 -0
- package/dist/telemetry/sessions.js +81 -0
- package/dist/telemetry/sessions.js.map +1 -0
- package/dist/telemetry/track.d.ts +43 -0
- package/dist/telemetry/track.d.ts.map +1 -0
- package/dist/telemetry/track.js +108 -0
- package/dist/telemetry/track.js.map +1 -0
- package/dist/upkeep-run.d.ts +15 -0
- package/dist/upkeep-run.d.ts.map +1 -0
- package/dist/upkeep-run.js +60 -0
- package/dist/upkeep-run.js.map +1 -0
- package/dist/upkeep.d.ts +114 -0
- package/dist/upkeep.d.ts.map +1 -0
- package/dist/upkeep.js +232 -0
- package/dist/upkeep.js.map +1 -0
- package/dist/util/num.d.ts +3 -0
- package/dist/util/num.d.ts.map +1 -0
- package/dist/util/num.js +20 -0
- package/dist/util/num.js.map +1 -0
- package/dist/util/state.d.ts +11 -4
- package/dist/util/state.d.ts.map +1 -1
- package/dist/util/state.js +9 -0
- package/dist/util/state.js.map +1 -1
- package/dist/viewer/app.js +1 -1
- package/dist/viewer/index.html +1 -0
- package/dist/viewer/style.css +23 -2
- package/dist/viz/assemble.d.ts +33 -0
- package/dist/viz/assemble.d.ts.map +1 -1
- package/dist/viz/assemble.js.map +1 -1
- package/dist/viz/export.d.ts +39 -0
- package/dist/viz/export.d.ts.map +1 -0
- package/dist/viz/export.js +98 -0
- package/dist/viz/export.js.map +1 -0
- package/package.json +15 -5
- package/scripts/stamp-telemetry-key.mjs +53 -0
package/dist/graph/extract.js
CHANGED
|
@@ -10,6 +10,8 @@ import Parser from "tree-sitter";
|
|
|
10
10
|
import TypeScript from "tree-sitter-typescript";
|
|
11
11
|
import Python from "tree-sitter-python";
|
|
12
12
|
import Go from "tree-sitter-go";
|
|
13
|
+
import Java from "tree-sitter-java";
|
|
14
|
+
import PHP from "tree-sitter-php";
|
|
13
15
|
import { basename } from "node:path";
|
|
14
16
|
import { contentHash } from "../util/id.js";
|
|
15
17
|
import { collectBindings, goReceiverVarOf, resolveRecvType } from "./bindings.js";
|
|
@@ -40,11 +42,17 @@ const EXTENSIONS = [
|
|
|
40
42
|
{ ext: ".pyi", grammar: "python", label: "python" },
|
|
41
43
|
{ ext: ".py", grammar: "python", label: "python" },
|
|
42
44
|
{ ext: ".go", grammar: "go", label: "go" },
|
|
45
|
+
{ ext: ".java", grammar: "java", label: "java" },
|
|
46
|
+
{ ext: ".php", grammar: "php", label: "php" },
|
|
43
47
|
];
|
|
44
48
|
function entryFor(path) {
|
|
45
49
|
const p = path.toLowerCase();
|
|
46
50
|
return EXTENSIONS.find((e) => p.endsWith(e.ext));
|
|
47
51
|
}
|
|
52
|
+
/** Every file extension a depth-tier (hand-written) extractor claims. */
|
|
53
|
+
export function depthExtensions() {
|
|
54
|
+
return EXTENSIONS.map((e) => e.ext);
|
|
55
|
+
}
|
|
48
56
|
/** Map a file path to a supported language, or null if unsupported. */
|
|
49
57
|
export function languageOf(path) {
|
|
50
58
|
return entryFor(path)?.grammar ?? null;
|
|
@@ -116,11 +124,62 @@ const GO_KINDS = {
|
|
|
116
124
|
function_declaration: "function",
|
|
117
125
|
method_declaration: "method",
|
|
118
126
|
};
|
|
127
|
+
// Java: a record is a nominal data carrier, so it takes "struct" — the same role
|
|
128
|
+
// Go's struct plays — rather than "class", which would make a service and a DTO
|
|
129
|
+
// indistinguishable in a repo where DTOs are most of the type surface.
|
|
130
|
+
const JAVA_KINDS = {
|
|
131
|
+
class_declaration: "class",
|
|
132
|
+
interface_declaration: "interface",
|
|
133
|
+
enum_declaration: "enum",
|
|
134
|
+
record_declaration: "struct",
|
|
135
|
+
annotation_type_declaration: "interface",
|
|
136
|
+
annotation_type_element_declaration: "method",
|
|
137
|
+
method_declaration: "method",
|
|
138
|
+
constructor_declaration: "method",
|
|
139
|
+
};
|
|
140
|
+
/** Java type declarations: they set `enclosingClass` for the methods nested in them,
|
|
141
|
+
* which "class"-only logic would miss for a record's or interface's members. */
|
|
142
|
+
const JAVA_TYPE_KINDS = new Set(["class", "interface", "enum", "struct"]);
|
|
143
|
+
// PHP: definition node types are all distinct (no py-style function→method
|
|
144
|
+
// promotion needed — a class body uses `method_declaration`, not
|
|
145
|
+
// `function_definition`). `trait_declaration` maps to the PHP-only `trait` kind.
|
|
146
|
+
const PHP_KINDS = {
|
|
147
|
+
function_definition: "function",
|
|
148
|
+
method_declaration: "method",
|
|
149
|
+
class_declaration: "class",
|
|
150
|
+
interface_declaration: "interface",
|
|
151
|
+
trait_declaration: "trait",
|
|
152
|
+
enum_declaration: "enum",
|
|
153
|
+
};
|
|
119
154
|
const KINDS_BY_LANG = {
|
|
120
155
|
typescript: TS_KINDS,
|
|
121
156
|
tsx: TS_KINDS,
|
|
122
157
|
python: PY_KINDS,
|
|
123
158
|
go: GO_KINDS,
|
|
159
|
+
java: JAVA_KINDS,
|
|
160
|
+
php: PHP_KINDS,
|
|
161
|
+
};
|
|
162
|
+
/**
|
|
163
|
+
* The node type(s) that constitute a call site, per language.
|
|
164
|
+
*
|
|
165
|
+
* Java is the reason this is a set rather than a string: `method_invocation` and
|
|
166
|
+
* `object_creation_expression` (`new Foo()`) are separate node types, and a Java
|
|
167
|
+
* codebase's constructor calls are a large share of its real edges. PHP is
|
|
168
|
+
* likewise multi-shape: a call is a function / member / nullsafe-member / scoped
|
|
169
|
+
* call, never a single `call_expression`.
|
|
170
|
+
*/
|
|
171
|
+
const CALL_TYPES = {
|
|
172
|
+
typescript: new Set(["call_expression"]),
|
|
173
|
+
tsx: new Set(["call_expression"]),
|
|
174
|
+
python: new Set(["call"]),
|
|
175
|
+
go: new Set(["call_expression"]),
|
|
176
|
+
java: new Set(["method_invocation", "object_creation_expression"]),
|
|
177
|
+
php: new Set([
|
|
178
|
+
"function_call_expression",
|
|
179
|
+
"member_call_expression",
|
|
180
|
+
"nullsafe_member_call_expression",
|
|
181
|
+
"scoped_call_expression",
|
|
182
|
+
]),
|
|
124
183
|
};
|
|
125
184
|
const FUNCTION_VALUE_TYPES = new Set([
|
|
126
185
|
"arrow_function",
|
|
@@ -134,6 +193,8 @@ const GRAMMARS = {
|
|
|
134
193
|
tsx: TypeScript.tsx,
|
|
135
194
|
python: Python,
|
|
136
195
|
go: Go,
|
|
196
|
+
java: Java,
|
|
197
|
+
php: PHP.php,
|
|
137
198
|
};
|
|
138
199
|
/** tree-sitter's string `parse()` fails with "Invalid argument" on any input
|
|
139
200
|
* ≥ 32 KB, which silently drops large files — often the most important ones (a
|
|
@@ -233,7 +294,11 @@ function walk(node, ctx, out, edges, minted) {
|
|
|
233
294
|
? !desc.name.startsWith("_")
|
|
234
295
|
: ctx.lang === "go"
|
|
235
296
|
? goExported(desc.name)
|
|
236
|
-
:
|
|
297
|
+
: ctx.lang === "java"
|
|
298
|
+
? javaExported(node)
|
|
299
|
+
: ctx.lang === "php"
|
|
300
|
+
? phpExported(node)
|
|
301
|
+
: tsExported(node),
|
|
237
302
|
origin: "ast",
|
|
238
303
|
body_hash: contentHash(desc.hashNode.text),
|
|
239
304
|
body_text: searchBody(desc.hashNode.text),
|
|
@@ -241,13 +306,21 @@ function walk(node, ctx, out, edges, minted) {
|
|
|
241
306
|
summary: null,
|
|
242
307
|
crux: null,
|
|
243
308
|
...(owner !== undefined ? { owner } : {}),
|
|
309
|
+
...(desc.arity !== undefined ? { arity: desc.arity } : {}),
|
|
310
|
+
...(desc.variadic ? { variadic: true } : {}),
|
|
244
311
|
});
|
|
245
312
|
// structural containment
|
|
246
313
|
edges.push({ source: ctx.parentId, relation: "contains", targetId: id, file: ctx.rel });
|
|
247
|
-
// class heritage
|
|
248
|
-
|
|
314
|
+
// class heritage — in Java an interface may also `extends`, and a record/enum
|
|
315
|
+
// may `implements`, so every type declaration is a heritage site, not just a class.
|
|
316
|
+
const javaTypeDecl = ctx.lang === "java" && JAVA_TYPE_KINDS.has(desc.kind);
|
|
317
|
+
if (desc.kind === "class" || javaTypeDecl)
|
|
249
318
|
edges.push(...heritageEdges(node, id, ctx));
|
|
250
|
-
const enclosingClass = desc.kind === "class"
|
|
319
|
+
const enclosingClass = desc.kind === "class" || javaTypeDecl
|
|
320
|
+
? desc.name
|
|
321
|
+
: isGoMethod
|
|
322
|
+
? goReceiverType(node)
|
|
323
|
+
: ctx.enclosingClass;
|
|
251
324
|
const childCtx = {
|
|
252
325
|
...ctx,
|
|
253
326
|
scope: [...ctx.scope, idPart],
|
|
@@ -264,8 +337,8 @@ function walk(node, ctx, out, edges, minted) {
|
|
|
264
337
|
return;
|
|
265
338
|
}
|
|
266
339
|
// not a definition — capture calls/imports/references, then descend with the same context
|
|
267
|
-
const
|
|
268
|
-
if (node.type
|
|
340
|
+
const callTypes = CALL_TYPES[ctx.lang];
|
|
341
|
+
if (callTypes.has(node.type)) {
|
|
269
342
|
const callee = calleeName(node, ctx.lang);
|
|
270
343
|
if (callee) {
|
|
271
344
|
const callEdge = {
|
|
@@ -275,6 +348,10 @@ function walk(node, ctx, out, edges, minted) {
|
|
|
275
348
|
viaMember: callee.viaMember,
|
|
276
349
|
file: ctx.rel,
|
|
277
350
|
};
|
|
351
|
+
// Java only: the call site's argument count, to pick the right overload.
|
|
352
|
+
const argCount = ctx.lang === "java" ? javaArgCount(node) : undefined;
|
|
353
|
+
if (argCount !== undefined)
|
|
354
|
+
callEdge.argCount = argCount;
|
|
278
355
|
const recvType = resolveRecvType(callee.receiver, ctx);
|
|
279
356
|
edges.push(recvType ? { ...callEdge, recvType } : callEdge);
|
|
280
357
|
}
|
|
@@ -287,8 +364,19 @@ function walk(node, ctx, out, edges, minted) {
|
|
|
287
364
|
// above already recorded them, so do not descend and emit false references.
|
|
288
365
|
return;
|
|
289
366
|
}
|
|
367
|
+
else if (ctx.lang === "php" && node.type === "use_declaration") {
|
|
368
|
+
// Trait composition inside a class body (`use HasFactory, Notifiable;`).
|
|
369
|
+
// Modelled as `implements`: like an interface, a trait is a contract of
|
|
370
|
+
// behaviour the class mixes in (Graft's Relation set has no `uses`).
|
|
371
|
+
for (const t of node.namedChildren) {
|
|
372
|
+
if (t.type === "name" || t.type === "qualified_name") {
|
|
373
|
+
edges.push({ source: ctx.parentId, relation: "implements", name: t.text.replace(/^.*\\/, ""), file: ctx.rel });
|
|
374
|
+
}
|
|
375
|
+
}
|
|
376
|
+
return;
|
|
377
|
+
}
|
|
290
378
|
else if (node.type === "identifier" &&
|
|
291
|
-
!isDirectCallee(node,
|
|
379
|
+
!isDirectCallee(node, callTypes) &&
|
|
292
380
|
!isDeclarationName(node)) {
|
|
293
381
|
const imported = ctx.importedSymbols.get(node.text);
|
|
294
382
|
if (imported) {
|
|
@@ -338,6 +426,19 @@ function collectTsImportBindings(node, specifier, out) {
|
|
|
338
426
|
for (const child of node.namedChildren)
|
|
339
427
|
collectTsImportBindings(child, specifier, out);
|
|
340
428
|
}
|
|
429
|
+
/**
|
|
430
|
+
* Do these two wrappers stand for the same syntax node? `===` does not answer that:
|
|
431
|
+
* node-tree-sitter materializes `SyntaxNode` objects on demand and caches them
|
|
432
|
+
* weakly, so reaching one node twice can return two different JS objects. Comparing
|
|
433
|
+
* wrappers makes a purely syntactic question depend on collector timing — two cold
|
|
434
|
+
* builds of unchanged source then disagree on `references` edges (#116).
|
|
435
|
+
*
|
|
436
|
+
* `id` is the stable identity, unique within one tree, so the tree is compared too.
|
|
437
|
+
* A `Tree` is one object per parse (unlike its nodes), so `===` is right for it.
|
|
438
|
+
*/
|
|
439
|
+
function sameSyntaxNode(a, b) {
|
|
440
|
+
return !!a && !!b && a.tree === b.tree && a.id === b.id;
|
|
441
|
+
}
|
|
341
442
|
/**
|
|
342
443
|
* A parameter or local declaration wins over an import inside that function.
|
|
343
444
|
* Drop that imported binding for the whole function rather than create a false
|
|
@@ -349,7 +450,7 @@ function withoutShadowedImports(imports, definition) {
|
|
|
349
450
|
const shadowed = new Set();
|
|
350
451
|
const definitionValue = definition.childForFieldName("value");
|
|
351
452
|
const visit = (node) => {
|
|
352
|
-
if (node
|
|
453
|
+
if (!sameSyntaxNode(node, definition) && !sameSyntaxNode(node, definitionValue) && isFunctionBoundary(node)) {
|
|
353
454
|
const name = node.childForFieldName("name");
|
|
354
455
|
if (name?.type === "identifier")
|
|
355
456
|
shadowed.add(name.text);
|
|
@@ -384,21 +485,41 @@ function isFunctionBoundary(node) {
|
|
|
384
485
|
node.type === "function_expression" ||
|
|
385
486
|
node.type === "function");
|
|
386
487
|
}
|
|
387
|
-
/** A direct invocation already emits a stronger `calls` edge.
|
|
388
|
-
|
|
488
|
+
/** A direct invocation already emits a stronger `calls` edge. Java names the callee
|
|
489
|
+
* in a `name` field (there is no `function` field on `method_invocation`), so both
|
|
490
|
+
* spellings count. */
|
|
491
|
+
function isDirectCallee(node, callTypes) {
|
|
389
492
|
const parent = node.parent;
|
|
390
|
-
|
|
493
|
+
if (!parent || !callTypes.has(parent.type))
|
|
494
|
+
return false;
|
|
495
|
+
return (sameSyntaxNode(parent.childForFieldName("function"), node) ||
|
|
496
|
+
sameSyntaxNode(parent.childForFieldName("name"), node));
|
|
391
497
|
}
|
|
392
498
|
/** Definition/declaration identifiers name a new binding; they do not use one. */
|
|
393
499
|
function isDeclarationName(node) {
|
|
394
500
|
const parent = node.parent;
|
|
395
|
-
return parent?.childForFieldName("name")
|
|
501
|
+
return sameSyntaxNode(parent?.childForFieldName("name"), node);
|
|
396
502
|
}
|
|
397
503
|
/** Recognize the definition shapes: mapped node types, Go's type/method forms, and
|
|
398
504
|
* TS arrow-consts. */
|
|
399
505
|
function describe(node, ctx) {
|
|
400
506
|
if (ctx.lang === "go")
|
|
401
507
|
return describeGo(node, ctx);
|
|
508
|
+
if (ctx.lang === "java")
|
|
509
|
+
return describeJava(node, ctx);
|
|
510
|
+
// PHP closures: `$h = function () {…}` / `fn() => …`, and bare callbacks
|
|
511
|
+
// (`$routes->get('/x', function () {…})`). Captured as function nodes so a
|
|
512
|
+
// closure-only file (a routing table, a DI container) keeps its structure
|
|
513
|
+
// and the calls inside attribute to the closure, not the file.
|
|
514
|
+
if (ctx.lang === "php" && (node.type === "anonymous_function" || node.type === "arrow_function")) {
|
|
515
|
+
const body = node.childForFieldName("body");
|
|
516
|
+
return {
|
|
517
|
+
name: phpClosureName(node),
|
|
518
|
+
kind: "function",
|
|
519
|
+
headerEnd: body ? body.startIndex : node.endIndex,
|
|
520
|
+
hashNode: node,
|
|
521
|
+
};
|
|
522
|
+
}
|
|
402
523
|
const mapped = ctx.kinds[node.type];
|
|
403
524
|
if (mapped) {
|
|
404
525
|
const name = node.childForFieldName("name")?.text;
|
|
@@ -470,6 +591,46 @@ function describeGo(node, _ctx) {
|
|
|
470
591
|
}
|
|
471
592
|
return null;
|
|
472
593
|
}
|
|
594
|
+
/** Java definition shapes. Uniform in a way Go's are not: every declaration carries
|
|
595
|
+
* a `name` field and (for types and most members) a `body`, so one mapped lookup
|
|
596
|
+
* covers classes, interfaces, enums, records, methods, and constructors. Methods are
|
|
597
|
+
* lexically nested in their type, so — unlike Go — they need no receiver qualification. */
|
|
598
|
+
function describeJava(node, ctx) {
|
|
599
|
+
const mapped = ctx.kinds[node.type];
|
|
600
|
+
if (!mapped)
|
|
601
|
+
return null;
|
|
602
|
+
const name = node.childForFieldName("name")?.text;
|
|
603
|
+
if (!name)
|
|
604
|
+
return null;
|
|
605
|
+
const body = node.childForFieldName("body");
|
|
606
|
+
const desc = {
|
|
607
|
+
name,
|
|
608
|
+
kind: mapped,
|
|
609
|
+
headerEnd: body ? body.startIndex : node.endIndex,
|
|
610
|
+
hashNode: node,
|
|
611
|
+
};
|
|
612
|
+
// Only callables carry arity. A record declaration also has a `parameters` node,
|
|
613
|
+
// but its components are not an overload set and must never be filtered against.
|
|
614
|
+
if (node.type === "method_declaration" || node.type === "constructor_declaration") {
|
|
615
|
+
const params = node.childForFieldName("parameters");
|
|
616
|
+
if (params) {
|
|
617
|
+
const declared = params.namedChildren.filter((c) => c.type === "formal_parameter" || c.type === "spread_parameter");
|
|
618
|
+
desc.arity = declared.length;
|
|
619
|
+
if (declared.some((c) => c.type === "spread_parameter"))
|
|
620
|
+
desc.variadic = true;
|
|
621
|
+
}
|
|
622
|
+
}
|
|
623
|
+
return desc;
|
|
624
|
+
}
|
|
625
|
+
/** Java visibility: `public` (or `protected`) on the declaration's own modifier list.
|
|
626
|
+
* A package-private or private member is not part of the API surface. Read off the
|
|
627
|
+
* `modifiers` child's tokens, ignoring annotations, which live in the same node. */
|
|
628
|
+
function javaExported(node) {
|
|
629
|
+
const mods = node.namedChildren.find((c) => c.type === "modifiers");
|
|
630
|
+
if (!mods)
|
|
631
|
+
return false;
|
|
632
|
+
return mods.children.some((c) => c.type === "public" || c.type === "protected");
|
|
633
|
+
}
|
|
473
634
|
/** The receiver's base type name for a Go method, unwrapping a pointer receiver
|
|
474
635
|
* (`func (u *User) …` → `User`). Null if it can't be read. */
|
|
475
636
|
function goReceiverType(node) {
|
|
@@ -487,8 +648,51 @@ function goExported(name) {
|
|
|
487
648
|
const first = own[0] ?? "";
|
|
488
649
|
return first !== first.toLowerCase() && first === first.toUpperCase();
|
|
489
650
|
}
|
|
651
|
+
/** PHP visibility: a class member is "exported" unless it is `private`/`protected`.
|
|
652
|
+
* Top-level functions/classes carry no visibility modifier and are always visible. */
|
|
653
|
+
function phpExported(node) {
|
|
654
|
+
const vis = node.namedChildren.find((c) => c.type === "visibility_modifier");
|
|
655
|
+
return vis ? vis.text === "public" : true;
|
|
656
|
+
}
|
|
657
|
+
/** Name for a PHP closure / arrow-fn: the variable it's assigned to
|
|
658
|
+
* (`$handler = fn(...)` -> `handler`, mirroring how TS names arrow-consts),
|
|
659
|
+
* else the anonymous `{closure}` (deduplicated per file by mintId).
|
|
660
|
+
*
|
|
661
|
+
* The "is this the assignment's right-hand side" check compares tree-sitter node
|
|
662
|
+
* `.id` (a stable per-tree node identity) rather than `===` on the wrapper
|
|
663
|
+
* objects: the binding does not guarantee that two traversals to the same
|
|
664
|
+
* underlying node hand back the same JS wrapper, so `right === node` can be false
|
|
665
|
+
* even when they are the same node — producing a stray `{closure}` name that
|
|
666
|
+
* makes `graft check` report the graph STALE against its own stored output. */
|
|
667
|
+
function phpClosureName(node) {
|
|
668
|
+
const parent = node.parent;
|
|
669
|
+
if (parent?.type === "assignment_expression" && parent.childForFieldName("right")?.id === node.id) {
|
|
670
|
+
const left = parent.childForFieldName("left");
|
|
671
|
+
if (left?.type === "variable_name")
|
|
672
|
+
return left.text.replace(/^\$/, "");
|
|
673
|
+
}
|
|
674
|
+
return "{closure}";
|
|
675
|
+
}
|
|
490
676
|
function heritageEdges(node, classId, ctx) {
|
|
491
677
|
const edges = [];
|
|
678
|
+
if (ctx.lang === "java") {
|
|
679
|
+
// `superclass` holds `extends X`; `super_interfaces` holds `implements A, B`
|
|
680
|
+
// (and, on an interface declaration, `extends A, B` — which tree-sitter-java
|
|
681
|
+
// still spells `extends_interfaces`).
|
|
682
|
+
for (const child of node.namedChildren) {
|
|
683
|
+
const relation = child.type === "superclass"
|
|
684
|
+
? "extends"
|
|
685
|
+
: child.type === "super_interfaces" || child.type === "extends_interfaces"
|
|
686
|
+
? "implements"
|
|
687
|
+
: null;
|
|
688
|
+
if (!relation)
|
|
689
|
+
continue;
|
|
690
|
+
for (const t of typeIdentifiersIn(child)) {
|
|
691
|
+
edges.push({ source: classId, relation, name: t, file: ctx.rel });
|
|
692
|
+
}
|
|
693
|
+
}
|
|
694
|
+
return edges;
|
|
695
|
+
}
|
|
492
696
|
if (ctx.lang === "python") {
|
|
493
697
|
const supers = node.childForFieldName("superclasses"); // argument_list
|
|
494
698
|
for (const c of supers?.namedChildren ?? []) {
|
|
@@ -498,6 +702,21 @@ function heritageEdges(node, classId, ctx) {
|
|
|
498
702
|
}
|
|
499
703
|
return edges;
|
|
500
704
|
}
|
|
705
|
+
if (ctx.lang === "php") {
|
|
706
|
+
// `class C extends B implements I, J` → base_clause (extends) +
|
|
707
|
+
// class_interface_clause (implements); names may be namespace-qualified.
|
|
708
|
+
for (const clause of node.namedChildren) {
|
|
709
|
+
const relation = clause.type === "base_clause" ? "extends" : clause.type === "class_interface_clause" ? "implements" : null;
|
|
710
|
+
if (!relation)
|
|
711
|
+
continue;
|
|
712
|
+
for (const t of clause.namedChildren) {
|
|
713
|
+
if (t.type === "name" || t.type === "qualified_name") {
|
|
714
|
+
edges.push({ source: classId, relation, name: t.text.replace(/^.*\\/, ""), file: ctx.rel });
|
|
715
|
+
}
|
|
716
|
+
}
|
|
717
|
+
}
|
|
718
|
+
return edges;
|
|
719
|
+
}
|
|
501
720
|
const heritage = node.namedChildren.find((c) => c.type === "class_heritage");
|
|
502
721
|
for (const clause of heritage?.namedChildren ?? []) {
|
|
503
722
|
const relation = clause.type === "implements_clause"
|
|
@@ -515,7 +734,46 @@ function heritageEdges(node, classId, ctx) {
|
|
|
515
734
|
}
|
|
516
735
|
return edges;
|
|
517
736
|
}
|
|
737
|
+
/** Every `type_identifier` under a heritage clause, so `implements A, B<C>` yields
|
|
738
|
+
* each named type rather than the clause's raw text. */
|
|
739
|
+
function typeIdentifiersIn(node) {
|
|
740
|
+
const out = [];
|
|
741
|
+
const visit = (n) => {
|
|
742
|
+
if (n.type === "type_identifier")
|
|
743
|
+
out.push(n.text);
|
|
744
|
+
for (const c of n.namedChildren)
|
|
745
|
+
visit(c);
|
|
746
|
+
};
|
|
747
|
+
visit(node);
|
|
748
|
+
return out;
|
|
749
|
+
}
|
|
518
750
|
function calleeName(node, lang) {
|
|
751
|
+
// Java first: `method_invocation` has NO `function` field (it splits the callee
|
|
752
|
+
// into `object` + `name`), so the shared lookup below would return null for every
|
|
753
|
+
// Java call site and the language would extract nodes with no call edges at all.
|
|
754
|
+
if (lang === "java") {
|
|
755
|
+
if (node.type === "object_creation_expression") {
|
|
756
|
+
// `new Foo()` — the constructed type is the call target, named as the graph
|
|
757
|
+
// names it.
|
|
758
|
+
const name = javaConstructedTypeName(node.childForFieldName("type"));
|
|
759
|
+
return name ? { name, viaMember: false } : null;
|
|
760
|
+
}
|
|
761
|
+
const nameNode = node.childForFieldName("name");
|
|
762
|
+
if (!nameNode)
|
|
763
|
+
return null;
|
|
764
|
+
const obj = node.childForFieldName("object");
|
|
765
|
+
// No `object` means an implicit-`this` call (`decorate(name)`), which in Java is a
|
|
766
|
+
// method call, not a free function — Java has none. Reporting it as a plain call
|
|
767
|
+
// would send it to the function-only resolver and drop it, losing the most common
|
|
768
|
+
// intra-class edge there is. Spelling it as a `this` member call routes it through
|
|
769
|
+
// owner-qualified resolution, which also walks the superclass chain and stays
|
|
770
|
+
// conservative: an unmatched name (e.g. a static import) resolves to nothing.
|
|
771
|
+
if (!obj)
|
|
772
|
+
return { name: nameNode.text, viaMember: true, receiver: "this" };
|
|
773
|
+
return { name: nameNode.text, viaMember: true, receiver: javaReceiver(obj) };
|
|
774
|
+
}
|
|
775
|
+
if (lang === "php")
|
|
776
|
+
return phpCallee(node);
|
|
519
777
|
const fn = node.childForFieldName("function");
|
|
520
778
|
if (!fn)
|
|
521
779
|
return null;
|
|
@@ -538,6 +796,67 @@ function calleeName(node, lang) {
|
|
|
538
796
|
}
|
|
539
797
|
return null;
|
|
540
798
|
}
|
|
799
|
+
/** The number of arguments at a Java call site (`method_invocation` or
|
|
800
|
+
* `object_creation_expression`), read off the `arguments` list. Undefined when the
|
|
801
|
+
* list is absent, which keeps resolution at its previous name-only behavior rather
|
|
802
|
+
* than filtering on a count we never established. */
|
|
803
|
+
function javaArgCount(node) {
|
|
804
|
+
const args = node.childForFieldName("arguments");
|
|
805
|
+
return args ? args.namedChildren.length : undefined;
|
|
806
|
+
}
|
|
807
|
+
/**
|
|
808
|
+
* The name a `new` CONSTRUCTS, as the graph names it — or null when this pass cannot
|
|
809
|
+
* say, in which case the construction resolves to nothing.
|
|
810
|
+
*
|
|
811
|
+
* Erasing type arguments is the only transformation here, because it is the only one
|
|
812
|
+
* that provably does not change which type is being named:
|
|
813
|
+
*
|
|
814
|
+
* Box -> Box
|
|
815
|
+
* Box<String> -> Box (the node is `Box`; the arguments are not part of it)
|
|
816
|
+
* Box<> -> Box
|
|
817
|
+
*
|
|
818
|
+
* A QUALIFIED name is deliberately dropped rather than reduced to its final segment:
|
|
819
|
+
*
|
|
820
|
+
* java.io.File -> null (not the repo's own `File`)
|
|
821
|
+
* Beta.Builder -> null (not `Alpha.Builder` in the same file)
|
|
822
|
+
*
|
|
823
|
+
* Collapsing those was the first attempt at this fix, and it traded lost edges for
|
|
824
|
+
* WRONG ones — `new java.io.File(…)` resolved to an unrelated in-repo `File`, and a
|
|
825
|
+
* nested `Beta.Builder` bound to a sibling `Alpha.Builder` at `extracted` confidence,
|
|
826
|
+
* because the same-file tiebreak takes the first candidate. Dropping keeps this pass
|
|
827
|
+
* on the resolver's own rule: resolve precisely, or not at all.
|
|
828
|
+
*
|
|
829
|
+
* Deliberately NOT shared with bindings.ts's `javaTypeName`. That one answers "what
|
|
830
|
+
* type does this variable HOLD", where reducing `java.util.List` to `List` is a local
|
|
831
|
+
* heuristic with different stakes; this one answers "what type is being constructed",
|
|
832
|
+
* and the two questions do not have the same safe answer. Supporting qualified
|
|
833
|
+
* construction properly needs an import-aware type index, not a longer helper.
|
|
834
|
+
*/
|
|
835
|
+
function javaConstructedTypeName(node) {
|
|
836
|
+
if (!node)
|
|
837
|
+
return null;
|
|
838
|
+
if (node.type === "generic_type")
|
|
839
|
+
return javaConstructedTypeName(node.namedChildren[0]);
|
|
840
|
+
return node.type === "type_identifier" ? node.text : null;
|
|
841
|
+
}
|
|
842
|
+
/** A Java call's receiver text: a bare identifier (`repo.save()`), `this`, or
|
|
843
|
+
* `this.x` for a field access (`this.repo.save()`). A chained call or a qualified
|
|
844
|
+
* static reference yields none — there is no confident local clue to bind. */
|
|
845
|
+
function javaReceiver(obj) {
|
|
846
|
+
if (!obj)
|
|
847
|
+
return undefined;
|
|
848
|
+
if (obj.type === "identifier")
|
|
849
|
+
return obj.text;
|
|
850
|
+
if (obj.type === "this")
|
|
851
|
+
return "this";
|
|
852
|
+
if (obj.type === "field_access") {
|
|
853
|
+
const inner = obj.childForFieldName("object");
|
|
854
|
+
const field = obj.childForFieldName("field");
|
|
855
|
+
if (inner?.type === "this" && field)
|
|
856
|
+
return `this.${field.text}`;
|
|
857
|
+
}
|
|
858
|
+
return undefined;
|
|
859
|
+
}
|
|
541
860
|
/** py `attribute` node's receiver text: bare identifier, or `self.x` for a
|
|
542
861
|
* chained `self.x.y()`. Anything else (e.g. a chained call `f().g()`) → none. */
|
|
543
862
|
function pyReceiver(fn) {
|
|
@@ -552,6 +871,58 @@ function pyReceiver(fn) {
|
|
|
552
871
|
}
|
|
553
872
|
return undefined;
|
|
554
873
|
}
|
|
874
|
+
/**
|
|
875
|
+
* PHP call shapes: `foo()` (function_call_expression), `$obj->m()` /
|
|
876
|
+
* `$obj?->m()` (member/nullsafe_member_call_expression), and `Cls::m()`
|
|
877
|
+
* (scoped_call_expression). The called name is the trailing `name`; the
|
|
878
|
+
* receiver, when locally knowable (`$this`, `self`/`static`/`parent`), feeds
|
|
879
|
+
* receiver-typed resolution the same way Python's `self` and Go's receiver do.
|
|
880
|
+
*/
|
|
881
|
+
function phpCallee(node) {
|
|
882
|
+
if (node.type === "function_call_expression") {
|
|
883
|
+
const fn = node.childForFieldName("function");
|
|
884
|
+
const name = fn ? phpName(fn) : null;
|
|
885
|
+
return name ? { name, viaMember: false } : null;
|
|
886
|
+
}
|
|
887
|
+
const nameNode = node.childForFieldName("name");
|
|
888
|
+
if (!nameNode)
|
|
889
|
+
return null;
|
|
890
|
+
if (node.type === "scoped_call_expression") {
|
|
891
|
+
return { name: nameNode.text, viaMember: true, receiver: phpScopeReceiver(node.childForFieldName("scope")) };
|
|
892
|
+
}
|
|
893
|
+
// member_call_expression / nullsafe_member_call_expression
|
|
894
|
+
return { name: nameNode.text, viaMember: true, receiver: phpObjReceiver(node.childForFieldName("object")) };
|
|
895
|
+
}
|
|
896
|
+
/** A PHP callee identifier: bare `name`, or the trailing segment of a
|
|
897
|
+
* `qualified_name` (`\App\helpers\slug` → `slug`). Dynamic calls (`$fn()`) → null. */
|
|
898
|
+
function phpName(node) {
|
|
899
|
+
if (node.type === "name")
|
|
900
|
+
return node.text;
|
|
901
|
+
if (node.type === "qualified_name")
|
|
902
|
+
return node.text.replace(/^.*\\/, "") || null;
|
|
903
|
+
return null;
|
|
904
|
+
}
|
|
905
|
+
/** `$obj->m()` receiver: `$this` normalizes to `this` (→ enclosing class); any
|
|
906
|
+
* other variable is returned verbatim for a bindings lookup. */
|
|
907
|
+
function phpObjReceiver(obj) {
|
|
908
|
+
if (obj?.type !== "variable_name")
|
|
909
|
+
return undefined;
|
|
910
|
+
return obj.text === "$this" ? "this" : obj.text;
|
|
911
|
+
}
|
|
912
|
+
/** `Cls::m()` receiver: `self`/`static`/`parent` normalize to `self` (→ enclosing
|
|
913
|
+
* class); an explicit class name is the trailing segment of its qualified path. */
|
|
914
|
+
function phpScopeReceiver(scope) {
|
|
915
|
+
if (!scope)
|
|
916
|
+
return undefined;
|
|
917
|
+
const text = scope.text;
|
|
918
|
+
if (scope.type === "relative_scope" || text === "self" || text === "static" || text === "parent")
|
|
919
|
+
return "self";
|
|
920
|
+
if (scope.type === "name")
|
|
921
|
+
return text;
|
|
922
|
+
if (scope.type === "qualified_name")
|
|
923
|
+
return text.replace(/^.*\\/, "");
|
|
924
|
+
return undefined;
|
|
925
|
+
}
|
|
555
926
|
/** ts `member_expression` node's receiver text: `this`, `this.x`, or a bare identifier. */
|
|
556
927
|
function tsReceiver(fn) {
|
|
557
928
|
const obj = fn.childForFieldName("object");
|
|
@@ -572,9 +943,20 @@ function isImport(node, lang) {
|
|
|
572
943
|
// (`import ( … )`) forms each yield one edge as the walk recurses into the list.
|
|
573
944
|
if (lang === "go")
|
|
574
945
|
return node.type === "import_spec";
|
|
946
|
+
if (lang === "java")
|
|
947
|
+
return node.type === "import_declaration";
|
|
948
|
+
// PHP: one edge per imported symbol — the clause leaf inside a (possibly
|
|
949
|
+
// grouped) `use A\B, C\D;` / `use A\{B, C};` declaration.
|
|
950
|
+
if (lang === "php")
|
|
951
|
+
return node.type === "namespace_use_clause";
|
|
575
952
|
return node.type === "import_statement" || node.type === "import_from_statement";
|
|
576
953
|
}
|
|
577
954
|
function importSpecifier(node, lang) {
|
|
955
|
+
if (lang === "php") {
|
|
956
|
+
// namespace_use_clause → its `qualified_name`/`name`, e.g. `App\Models\Animal`.
|
|
957
|
+
const q = node.namedChildren.find((c) => c.type === "qualified_name" || c.type === "name");
|
|
958
|
+
return q ? q.text.replace(/^\\/, "") : null;
|
|
959
|
+
}
|
|
578
960
|
if (lang === "python") {
|
|
579
961
|
const m = node.childForFieldName("module_name") ??
|
|
580
962
|
node.namedChildren.find((c) => c.type === "dotted_name" || c.type === "relative_import");
|
|
@@ -585,6 +967,13 @@ function importSpecifier(node, lang) {
|
|
|
585
967
|
const path = node.childForFieldName("path") ?? node.namedChildren.at(-1);
|
|
586
968
|
return path ? path.text.replace(/^["`]|["`]$/g, "") : null;
|
|
587
969
|
}
|
|
970
|
+
if (lang === "java") {
|
|
971
|
+
// `import a.b.C;` / `import static a.b.C.d;` / `import a.b.*;` — the fully
|
|
972
|
+
// qualified name is the scoped_identifier; a wildcard `*` is a separate token
|
|
973
|
+
// and is dropped, leaving the package as the import target.
|
|
974
|
+
const id = node.namedChildren.find((c) => c.type === "scoped_identifier" || c.type === "identifier");
|
|
975
|
+
return id?.text ?? null;
|
|
976
|
+
}
|
|
588
977
|
const str = node.namedChildren.find((c) => c.type === "string");
|
|
589
978
|
if (!str)
|
|
590
979
|
return null;
|