@shanepadgett/tau-agent 0.27.0 → 0.28.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/extensions/context/sync.ts +1 -9
- package/extensions/context-pruning/prune.ts +2 -2
- package/extensions/explore/README.md +13 -21
- package/extensions/explore/ast/adapter.ts +97 -0
- package/extensions/explore/ast/engine.ts +470 -0
- package/extensions/explore/ast/format/ast-search.ts +97 -0
- package/extensions/explore/ast/format/composite.ts +26 -0
- package/extensions/explore/ast/format/context.ts +66 -0
- package/extensions/explore/ast/format/deps.ts +86 -0
- package/extensions/explore/ast/format/discover.ts +85 -0
- package/extensions/explore/ast/format/impact.ts +105 -0
- package/extensions/explore/ast/format/outline.ts +66 -0
- package/extensions/explore/ast/format/relationships.ts +81 -0
- package/extensions/explore/ast/format/show.ts +38 -0
- package/extensions/explore/ast/grammars/kotlin.wasm +0 -0
- package/extensions/explore/ast/grammars/manifest.json +41 -0
- package/extensions/explore/ast/grammars/manifest.ts +99 -0
- package/extensions/explore/ast/grammars/odin.wasm +0 -0
- package/extensions/explore/ast/grammars/swift.wasm +0 -0
- package/extensions/explore/ast/graph/file-graph.ts +360 -0
- package/extensions/explore/ast/graph/relationships.ts +597 -0
- package/extensions/explore/ast/identity.ts +119 -0
- package/extensions/explore/ast/ir.ts +130 -0
- package/extensions/explore/ast/languages/csharp-file-deps.ts +189 -0
- package/extensions/explore/ast/languages/csharp.ts +493 -0
- package/extensions/explore/ast/languages/file-dep-util.ts +120 -0
- package/extensions/explore/ast/languages/fixtures/sample.cs +17 -0
- package/extensions/explore/ast/languages/fixtures/sample.java +21 -0
- package/extensions/explore/ast/languages/fixtures/sample.kt +15 -0
- package/extensions/explore/ast/languages/fixtures/sample.odin +28 -0
- package/extensions/explore/ast/languages/fixtures/sample.rs +31 -0
- package/extensions/explore/ast/languages/fixtures/sample.swift +25 -0
- package/extensions/explore/ast/languages/go-file-deps.ts +139 -0
- package/extensions/explore/ast/languages/go.ts +545 -0
- package/extensions/explore/ast/languages/java.ts +412 -0
- package/extensions/explore/ast/languages/jvm-file-deps.ts +217 -0
- package/extensions/explore/ast/languages/kotlin.ts +611 -0
- package/extensions/explore/ast/languages/odin-file-deps.ts +175 -0
- package/extensions/explore/ast/languages/odin.ts +407 -0
- package/extensions/explore/ast/languages/rust-file-deps.ts +159 -0
- package/extensions/explore/ast/languages/rust.ts +506 -0
- package/extensions/explore/ast/languages/swift-file-deps.ts +59 -0
- package/extensions/explore/ast/languages/swift.ts +466 -0
- package/extensions/explore/ast/languages/tree.ts +220 -0
- package/extensions/explore/ast/languages/typescript-file-deps.ts +259 -0
- package/extensions/explore/ast/languages/typescript-package-surface.ts +440 -0
- package/extensions/explore/ast/languages/typescript.ts +716 -0
- package/extensions/explore/ast/markdown.ts +145 -0
- package/extensions/explore/ast/package-surface.ts +42 -0
- package/extensions/explore/ast/queries/ast-search.ts +306 -0
- package/extensions/explore/ast/queries/composite-target.ts +77 -0
- package/extensions/explore/ast/queries/context.ts +406 -0
- package/extensions/explore/ast/queries/discover.ts +427 -0
- package/extensions/explore/ast/queries/impact.ts +250 -0
- package/extensions/explore/ast/queries/outline.ts +203 -0
- package/extensions/explore/ast/queries/show.ts +262 -0
- package/extensions/explore/ast/query.ts +48 -0
- package/extensions/explore/ast/read/hook.ts +125 -0
- package/extensions/explore/ast/read/policy.ts +55 -0
- package/extensions/explore/ast/registry.ts +120 -0
- package/extensions/explore/ast/scan.ts +76 -0
- package/extensions/explore/ast/slice.ts +31 -0
- package/extensions/explore/ast/tools/ast-search.ts +138 -0
- package/extensions/explore/ast/tools/context.ts +126 -0
- package/extensions/explore/ast/tools/deps.ts +72 -0
- package/extensions/explore/ast/tools/discover.ts +208 -0
- package/extensions/explore/ast/tools/impact.ts +135 -0
- package/extensions/explore/ast/tools/outline.ts +200 -0
- package/extensions/explore/ast/tools/relationships.ts +160 -0
- package/extensions/explore/ast/tools/render.ts +121 -0
- package/extensions/explore/ast/tools/reverse-deps.ts +72 -0
- package/extensions/explore/ast/tools/show.ts +116 -0
- package/extensions/explore/guidance.ts +52 -0
- package/extensions/explore/index.ts +82 -92
- package/extensions/explore/read/autoread.ts +118 -0
- package/extensions/explore/settings.ts +49 -31
- package/extensions/explore/traverse.ts +254 -91
- package/extensions/handoff/index.ts +1 -1
- package/extensions/publish/index.ts +18 -85
- package/extensions/publish/publish-ui.ts +0 -4
- package/extensions/soul/README.md +7 -4
- package/extensions/soul/index.ts +11 -5
- package/extensions/soul/prompt.ts +31 -13
- package/extensions/soul/settings.ts +7 -2
- package/extensions/subagent/agents/context-sync.md +6 -10
- package/extensions/subagent/agents/review.md +24 -55
- package/extensions/subagent/agents/scout.md +38 -115
- package/extensions/subagent/run.ts +1 -1
- package/extensions/tau-help/help.md +2 -4
- package/package.json +6 -4
- package/schemas/tau.schema.json +35 -30
- package/{extensions/explore → shared}/autoread.ts +21 -7
- package/shared/events.ts +1 -1
- package/shared/full-file-knowledge.ts +49 -0
- package/extensions/explore/ast-guidance.ts +0 -67
- package/extensions/explore/ast-languages.ts +0 -61
- package/extensions/explore/ast-tools.ts +0 -1994
- package/extensions/explore/ast-worker.ts +0 -1025
- package/extensions/explore/find.ts +0 -147
- package/extensions/explore/full-file-knowledge.ts +0 -234
- package/extensions/explore/grep.ts +0 -749
- package/extensions/explore/limits.ts +0 -22
- package/extensions/explore/ls.ts +0 -113
- package/extensions/explore/orientation-state.ts +0 -280
- package/extensions/explore/path-display.ts +0 -41
- package/extensions/explore/path-tree.ts +0 -150
- package/extensions/explore/read-cache.ts +0 -273
- package/extensions/explore/read-snapshots.ts +0 -57
- package/extensions/explore/read-stats-panel.ts +0 -176
- package/extensions/explore/read-stats.ts +0 -138
- package/extensions/explore/read.ts +0 -372
- package/extensions/explore/result.ts +0 -29
- package/native-bin/darwin-arm64/tau-ast +0 -0
|
@@ -0,0 +1,412 @@
|
|
|
1
|
+
import type { Node, Tree } from "web-tree-sitter";
|
|
2
|
+
import type { ExtractResult, GrammarAdapter, LanguageCapabilities } from "../adapter.ts";
|
|
3
|
+
import { resolveJavaFileDep } from "./jvm-file-deps.ts";
|
|
4
|
+
import type { CallSite, Decl, ImportRef, Visibility } from "../ir.ts";
|
|
5
|
+
import {
|
|
6
|
+
applyDoc,
|
|
7
|
+
callSite,
|
|
8
|
+
docSpanBefore,
|
|
9
|
+
endLine,
|
|
10
|
+
field,
|
|
11
|
+
finishDecl,
|
|
12
|
+
nameText,
|
|
13
|
+
qualify,
|
|
14
|
+
startLine,
|
|
15
|
+
walkBodyNodes,
|
|
16
|
+
type DocSpan,
|
|
17
|
+
} from "./tree.ts";
|
|
18
|
+
|
|
19
|
+
const CAPABILITIES: LanguageCapabilities = {
|
|
20
|
+
shape: true,
|
|
21
|
+
search: true,
|
|
22
|
+
fileDeps: true,
|
|
23
|
+
callEdges: true,
|
|
24
|
+
packageSurface: false,
|
|
25
|
+
};
|
|
26
|
+
|
|
27
|
+
const IMPORT_NOISE = new Set([
|
|
28
|
+
"abstract",
|
|
29
|
+
"assert",
|
|
30
|
+
"boolean",
|
|
31
|
+
"break",
|
|
32
|
+
"byte",
|
|
33
|
+
"case",
|
|
34
|
+
"catch",
|
|
35
|
+
"char",
|
|
36
|
+
"class",
|
|
37
|
+
"const",
|
|
38
|
+
"continue",
|
|
39
|
+
"default",
|
|
40
|
+
"do",
|
|
41
|
+
"double",
|
|
42
|
+
"else",
|
|
43
|
+
"enum",
|
|
44
|
+
"extends",
|
|
45
|
+
"final",
|
|
46
|
+
"finally",
|
|
47
|
+
"float",
|
|
48
|
+
"for",
|
|
49
|
+
"goto",
|
|
50
|
+
"if",
|
|
51
|
+
"implements",
|
|
52
|
+
"import",
|
|
53
|
+
"instanceof",
|
|
54
|
+
"int",
|
|
55
|
+
"interface",
|
|
56
|
+
"long",
|
|
57
|
+
"native",
|
|
58
|
+
"new",
|
|
59
|
+
"package",
|
|
60
|
+
"private",
|
|
61
|
+
"protected",
|
|
62
|
+
"public",
|
|
63
|
+
"return",
|
|
64
|
+
"short",
|
|
65
|
+
"static",
|
|
66
|
+
"strictfp",
|
|
67
|
+
"super",
|
|
68
|
+
"switch",
|
|
69
|
+
"synchronized",
|
|
70
|
+
"this",
|
|
71
|
+
"throw",
|
|
72
|
+
"throws",
|
|
73
|
+
"transient",
|
|
74
|
+
"try",
|
|
75
|
+
"void",
|
|
76
|
+
"volatile",
|
|
77
|
+
"while",
|
|
78
|
+
"true",
|
|
79
|
+
"false",
|
|
80
|
+
"null",
|
|
81
|
+
]);
|
|
82
|
+
|
|
83
|
+
const EXTENSIONS = [".java"] as const;
|
|
84
|
+
|
|
85
|
+
const CLASS_DECLARATION = "class_declaration";
|
|
86
|
+
const INTERFACE_DECLARATION = "interface_declaration";
|
|
87
|
+
const ENUM_DECLARATION = "enum_declaration";
|
|
88
|
+
const RECORD_DECLARATION = "record_declaration";
|
|
89
|
+
const ANNOTATION_TYPE_DECLARATION = "annotation_type_declaration";
|
|
90
|
+
const METHOD_DECLARATION = "method_declaration";
|
|
91
|
+
const CONSTRUCTOR_DECLARATION = "constructor_declaration";
|
|
92
|
+
const FIELD_DECLARATION = "field_declaration";
|
|
93
|
+
const CONSTANT_DECLARATION = "constant_declaration";
|
|
94
|
+
const ENUM_CONSTANT = "enum_constant";
|
|
95
|
+
const ENUM_BODY = "enum_body";
|
|
96
|
+
const ENUM_BODY_DECLARATIONS = "enum_body_declarations";
|
|
97
|
+
const ANNOTATION_TYPE_ELEMENT_DECLARATION = "annotation_type_element_declaration";
|
|
98
|
+
const VARIABLE_DECLARATOR = "variable_declarator";
|
|
99
|
+
const IMPORT_DECLARATION = "import_declaration";
|
|
100
|
+
const MODIFIERS = "modifiers";
|
|
101
|
+
const LINE_COMMENT = "line_comment";
|
|
102
|
+
const BLOCK_COMMENT = "block_comment";
|
|
103
|
+
|
|
104
|
+
const COMMENT_TYPES = [LINE_COMMENT, BLOCK_COMMENT] as const;
|
|
105
|
+
|
|
106
|
+
const CALL_METHOD = "method_invocation";
|
|
107
|
+
const OBJECT_CREATION = "object_creation_expression";
|
|
108
|
+
const ARRAY_CREATION = "array_creation_expression";
|
|
109
|
+
const SUPERCLASS = "superclass";
|
|
110
|
+
const SUPER_INTERFACES = "super_interfaces";
|
|
111
|
+
const TYPE_IDENTIFIER = "type_identifier";
|
|
112
|
+
|
|
113
|
+
const NESTED_SCOPE = new Set([
|
|
114
|
+
"method_declaration",
|
|
115
|
+
"constructor_declaration",
|
|
116
|
+
"class_declaration",
|
|
117
|
+
"interface_declaration",
|
|
118
|
+
"enum_declaration",
|
|
119
|
+
"record_declaration",
|
|
120
|
+
]);
|
|
121
|
+
|
|
122
|
+
function extractCalls(body: Node | null): CallSite[] {
|
|
123
|
+
if (body === null) return [];
|
|
124
|
+
const out: CallSite[] = [];
|
|
125
|
+
walkBodyNodes(
|
|
126
|
+
body,
|
|
127
|
+
(n) => NESTED_SCOPE.has(n.type),
|
|
128
|
+
(node) => {
|
|
129
|
+
if (node.type === CALL_METHOD) {
|
|
130
|
+
const nameNode = field(node, "name");
|
|
131
|
+
const obj = field(node, "object");
|
|
132
|
+
if (nameNode === null) return;
|
|
133
|
+
const site = callSite(
|
|
134
|
+
nameNode.text,
|
|
135
|
+
startLine(nameNode),
|
|
136
|
+
nameNode.startIndex,
|
|
137
|
+
nameNode.endIndex,
|
|
138
|
+
"call",
|
|
139
|
+
obj === null ? "" : obj.text,
|
|
140
|
+
);
|
|
141
|
+
if (site !== undefined) out.push(site);
|
|
142
|
+
return;
|
|
143
|
+
}
|
|
144
|
+
if (node.type === OBJECT_CREATION || node.type === ARRAY_CREATION) {
|
|
145
|
+
const typeNode = field(node, "type");
|
|
146
|
+
if (typeNode === null) return;
|
|
147
|
+
const leaf = typeNode.type === TYPE_IDENTIFIER ? typeNode : typeNode.descendantsOfType(TYPE_IDENTIFIER)[0];
|
|
148
|
+
if (leaf === undefined) return;
|
|
149
|
+
const site = callSite(leaf.text, startLine(leaf), leaf.startIndex, leaf.endIndex, "construct");
|
|
150
|
+
if (site !== undefined) out.push(site);
|
|
151
|
+
}
|
|
152
|
+
},
|
|
153
|
+
);
|
|
154
|
+
return out;
|
|
155
|
+
}
|
|
156
|
+
|
|
157
|
+
/** Simple type name only — drop package path and type arguments. */
|
|
158
|
+
function typeSimpleName(node: Node): string {
|
|
159
|
+
if (node.type === TYPE_IDENTIFIER || node.type === "identifier") return node.text;
|
|
160
|
+
if (node.type === "generic_type" || node.type === "annotated_type") {
|
|
161
|
+
const inner = field(node, "type") ?? node.namedChildren[0];
|
|
162
|
+
return inner === undefined ? "" : typeSimpleName(inner);
|
|
163
|
+
}
|
|
164
|
+
if (node.type === "scoped_type_identifier") {
|
|
165
|
+
const name = field(node, "name");
|
|
166
|
+
if (name !== null) return name.text;
|
|
167
|
+
const last = node.namedChildren[node.namedChildren.length - 1];
|
|
168
|
+
return last === undefined ? "" : typeSimpleName(last);
|
|
169
|
+
}
|
|
170
|
+
for (const child of node.namedChildren) {
|
|
171
|
+
const inner = typeSimpleName(child);
|
|
172
|
+
if (inner.length > 0) return inner;
|
|
173
|
+
}
|
|
174
|
+
return "";
|
|
175
|
+
}
|
|
176
|
+
|
|
177
|
+
function heritageBases(node: Node): string[] {
|
|
178
|
+
const bases: string[] = [];
|
|
179
|
+
const pushTypes = (clause: Node): void => {
|
|
180
|
+
const list = clause.namedChildren.find((c) => c.type === "type_list") ?? clause;
|
|
181
|
+
for (const t of list.namedChildren) {
|
|
182
|
+
const name = typeSimpleName(t);
|
|
183
|
+
if (name.length > 0) bases.push(name);
|
|
184
|
+
}
|
|
185
|
+
};
|
|
186
|
+
for (const child of node.namedChildren) {
|
|
187
|
+
if (
|
|
188
|
+
child.type === SUPERCLASS ||
|
|
189
|
+
child.type === SUPER_INTERFACES ||
|
|
190
|
+
child.type === "extends_interfaces" ||
|
|
191
|
+
child.type === "super_interfaces"
|
|
192
|
+
) {
|
|
193
|
+
pushTypes(child);
|
|
194
|
+
}
|
|
195
|
+
}
|
|
196
|
+
return [...new Set(bases)];
|
|
197
|
+
}
|
|
198
|
+
|
|
199
|
+
function javaDoc(node: Node, source: string): DocSpan | undefined {
|
|
200
|
+
return docSpanBefore(node, source, [], COMMENT_TYPES);
|
|
201
|
+
}
|
|
202
|
+
|
|
203
|
+
function modifiersText(node: Node): string {
|
|
204
|
+
const mods = node.namedChildren.find((child) => child.type === MODIFIERS);
|
|
205
|
+
return mods === undefined ? "" : mods.text;
|
|
206
|
+
}
|
|
207
|
+
|
|
208
|
+
function javaVisibility(node: Node, defaultVisibility: Visibility): Visibility {
|
|
209
|
+
const text = modifiersText(node);
|
|
210
|
+
if (/\bpublic\b/.test(text)) return "public";
|
|
211
|
+
if (/\bprivate\b/.test(text)) return "private";
|
|
212
|
+
if (/\bprotected\b/.test(text)) return "protected";
|
|
213
|
+
// package-private default for types/class members; interface members default public.
|
|
214
|
+
return defaultVisibility;
|
|
215
|
+
}
|
|
216
|
+
|
|
217
|
+
function nameOf(node: Node): string {
|
|
218
|
+
return nameText(field(node, "name"));
|
|
219
|
+
}
|
|
220
|
+
|
|
221
|
+
function leaf(
|
|
222
|
+
node: Node,
|
|
223
|
+
owner: string,
|
|
224
|
+
kind: Decl["kind"],
|
|
225
|
+
name: string,
|
|
226
|
+
visibility: Visibility,
|
|
227
|
+
body: Node | null,
|
|
228
|
+
children: Decl[],
|
|
229
|
+
doc: DocSpan | undefined,
|
|
230
|
+
): Decl | undefined {
|
|
231
|
+
if (name.length === 0) return undefined;
|
|
232
|
+
const callable = kind === "function" || kind === "method" || kind === "constructor";
|
|
233
|
+
const typeLike = kind === "class" || kind === "interface" || kind === "enum" || kind === "struct";
|
|
234
|
+
const decl = finishDecl(
|
|
235
|
+
{
|
|
236
|
+
kind,
|
|
237
|
+
name,
|
|
238
|
+
qualifiedName: qualify(owner, name),
|
|
239
|
+
startLine: startLine(node),
|
|
240
|
+
endLine: endLine(node),
|
|
241
|
+
startOffset: node.startIndex,
|
|
242
|
+
endOffset: node.endIndex,
|
|
243
|
+
visibility,
|
|
244
|
+
exported: visibility === "public",
|
|
245
|
+
children,
|
|
246
|
+
calls: callable ? extractCalls(body) : [],
|
|
247
|
+
bases: typeLike ? heritageBases(node) : [],
|
|
248
|
+
},
|
|
249
|
+
body,
|
|
250
|
+
);
|
|
251
|
+
if (body === null) decl.signatureEndOffset = node.endIndex;
|
|
252
|
+
return applyDoc(decl, doc);
|
|
253
|
+
}
|
|
254
|
+
|
|
255
|
+
function declaratorNames(node: Node): string[] {
|
|
256
|
+
const names: string[] = [];
|
|
257
|
+
for (const child of node.namedChildren) {
|
|
258
|
+
if (child.type !== VARIABLE_DECLARATOR) continue;
|
|
259
|
+
const name = nameText(field(child, "name"));
|
|
260
|
+
if (name.length > 0) names.push(name);
|
|
261
|
+
}
|
|
262
|
+
// constant_declaration uses field "declarator"
|
|
263
|
+
const single = field(node, "declarator");
|
|
264
|
+
if (single !== null && single.type === VARIABLE_DECLARATOR) {
|
|
265
|
+
const name = nameText(field(single, "name"));
|
|
266
|
+
if (name.length > 0 && !names.includes(name)) names.push(name);
|
|
267
|
+
}
|
|
268
|
+
return names;
|
|
269
|
+
}
|
|
270
|
+
|
|
271
|
+
function walkBody(body: Node, owner: string, source: string, defaultMemberVisibility: Visibility): Decl[] {
|
|
272
|
+
const out: Decl[] = [];
|
|
273
|
+
for (const node of body.namedChildren) {
|
|
274
|
+
if (node.type === LINE_COMMENT || node.type === BLOCK_COMMENT) continue;
|
|
275
|
+
if (node.type === ENUM_BODY_DECLARATIONS) {
|
|
276
|
+
out.push(...walkBody(node, owner, source, defaultMemberVisibility));
|
|
277
|
+
continue;
|
|
278
|
+
}
|
|
279
|
+
out.push(...declsFromNode(node, owner, source, defaultMemberVisibility));
|
|
280
|
+
}
|
|
281
|
+
return out;
|
|
282
|
+
}
|
|
283
|
+
|
|
284
|
+
function enumConstants(body: Node, owner: string): Decl[] {
|
|
285
|
+
const out: Decl[] = [];
|
|
286
|
+
for (const node of body.namedChildren) {
|
|
287
|
+
if (node.type !== ENUM_CONSTANT) continue;
|
|
288
|
+
const decl = leaf(node, owner, "enumMember", nameOf(node), "public", null, [], undefined);
|
|
289
|
+
if (decl !== undefined) out.push(decl);
|
|
290
|
+
}
|
|
291
|
+
return out;
|
|
292
|
+
}
|
|
293
|
+
|
|
294
|
+
function declsFromNode(node: Node, owner: string, source: string, defaultMemberVisibility: Visibility): Decl[] {
|
|
295
|
+
if (node.isError || node.type === "ERROR") {
|
|
296
|
+
const out: Decl[] = [];
|
|
297
|
+
for (const child of node.children) {
|
|
298
|
+
if (child.isNamed || child.isError) {
|
|
299
|
+
out.push(...declsFromNode(child, owner, source, defaultMemberVisibility));
|
|
300
|
+
}
|
|
301
|
+
}
|
|
302
|
+
return out;
|
|
303
|
+
}
|
|
304
|
+
|
|
305
|
+
const doc = javaDoc(node, source);
|
|
306
|
+
const visibility = javaVisibility(node, defaultMemberVisibility);
|
|
307
|
+
|
|
308
|
+
switch (node.type) {
|
|
309
|
+
case CLASS_DECLARATION:
|
|
310
|
+
case RECORD_DECLARATION: {
|
|
311
|
+
const name = nameOf(node);
|
|
312
|
+
const body = field(node, "body");
|
|
313
|
+
const qn = qualify(owner, name);
|
|
314
|
+
const children = body !== null ? walkBody(body, qn, source, "internal") : [];
|
|
315
|
+
const decl = leaf(node, owner, "class", name, javaVisibility(node, "internal"), body, children, doc);
|
|
316
|
+
return decl === undefined ? [] : [decl];
|
|
317
|
+
}
|
|
318
|
+
case INTERFACE_DECLARATION:
|
|
319
|
+
case ANNOTATION_TYPE_DECLARATION: {
|
|
320
|
+
// @interface → interface. Members default public.
|
|
321
|
+
const name = nameOf(node);
|
|
322
|
+
const body = field(node, "body");
|
|
323
|
+
const qn = qualify(owner, name);
|
|
324
|
+
const children = body !== null ? walkBody(body, qn, source, "public") : [];
|
|
325
|
+
const decl = leaf(node, owner, "interface", name, javaVisibility(node, "internal"), body, children, doc);
|
|
326
|
+
return decl === undefined ? [] : [decl];
|
|
327
|
+
}
|
|
328
|
+
case ENUM_DECLARATION: {
|
|
329
|
+
const name = nameOf(node);
|
|
330
|
+
const body = field(node, "body");
|
|
331
|
+
const qn = qualify(owner, name);
|
|
332
|
+
const children: Decl[] = [];
|
|
333
|
+
if (body !== null && body.type === ENUM_BODY) {
|
|
334
|
+
children.push(...enumConstants(body, qn));
|
|
335
|
+
children.push(...walkBody(body, qn, source, "internal"));
|
|
336
|
+
}
|
|
337
|
+
const decl = leaf(node, owner, "enum", name, javaVisibility(node, "internal"), body, children, doc);
|
|
338
|
+
return decl === undefined ? [] : [decl];
|
|
339
|
+
}
|
|
340
|
+
case METHOD_DECLARATION: {
|
|
341
|
+
const decl = leaf(node, owner, "method", nameOf(node), visibility, field(node, "body"), [], doc);
|
|
342
|
+
return decl === undefined ? [] : [decl];
|
|
343
|
+
}
|
|
344
|
+
case CONSTRUCTOR_DECLARATION: {
|
|
345
|
+
const body = field(node, "body");
|
|
346
|
+
const decl = leaf(node, owner, "constructor", nameOf(node), visibility, body, [], doc);
|
|
347
|
+
return decl === undefined ? [] : [decl];
|
|
348
|
+
}
|
|
349
|
+
case FIELD_DECLARATION:
|
|
350
|
+
case CONSTANT_DECLARATION: {
|
|
351
|
+
const names = declaratorNames(node);
|
|
352
|
+
const kind =
|
|
353
|
+
node.type === CONSTANT_DECLARATION || /\bfinal\b/.test(modifiersText(node)) ? "constant" : "field";
|
|
354
|
+
const out: Decl[] = [];
|
|
355
|
+
for (let i = 0; i < names.length; i += 1) {
|
|
356
|
+
const name = names[i];
|
|
357
|
+
if (name === undefined) continue;
|
|
358
|
+
const decl = leaf(node, owner, kind, name, visibility, null, [], i === 0 ? doc : undefined);
|
|
359
|
+
if (decl !== undefined) out.push(decl);
|
|
360
|
+
}
|
|
361
|
+
return out;
|
|
362
|
+
}
|
|
363
|
+
case ANNOTATION_TYPE_ELEMENT_DECLARATION: {
|
|
364
|
+
const decl = leaf(node, owner, "method", nameOf(node), visibility, null, [], doc);
|
|
365
|
+
return decl === undefined ? [] : [decl];
|
|
366
|
+
}
|
|
367
|
+
default:
|
|
368
|
+
return [];
|
|
369
|
+
}
|
|
370
|
+
}
|
|
371
|
+
|
|
372
|
+
function collectImports(root: Node): ImportRef[] {
|
|
373
|
+
const imports: ImportRef[] = [];
|
|
374
|
+
for (const node of root.namedChildren) {
|
|
375
|
+
if (node.type !== IMPORT_DECLARATION) continue;
|
|
376
|
+
// Keep full import path text without the keyword.
|
|
377
|
+
const specifier = node.text
|
|
378
|
+
.replace(/^import\s+/, "")
|
|
379
|
+
.replace(/^static\s+/, "")
|
|
380
|
+
.replace(/;$/, "")
|
|
381
|
+
.trim();
|
|
382
|
+
imports.push({
|
|
383
|
+
specifier,
|
|
384
|
+
startLine: startLine(node),
|
|
385
|
+
startOffset: node.startIndex,
|
|
386
|
+
endOffset: node.endIndex,
|
|
387
|
+
bindings: [],
|
|
388
|
+
});
|
|
389
|
+
}
|
|
390
|
+
return imports;
|
|
391
|
+
}
|
|
392
|
+
|
|
393
|
+
function extractJava(tree: Tree, source: string): ExtractResult {
|
|
394
|
+
const root = tree.rootNode;
|
|
395
|
+
const decls: Decl[] = [];
|
|
396
|
+
for (const node of root.children) {
|
|
397
|
+
if (node.isNamed || node.isError) decls.push(...declsFromNode(node, "", source, "internal"));
|
|
398
|
+
}
|
|
399
|
+
return { decls, imports: collectImports(root) };
|
|
400
|
+
}
|
|
401
|
+
|
|
402
|
+
export function javaAdapter(): GrammarAdapter {
|
|
403
|
+
return {
|
|
404
|
+
mode: "grammar",
|
|
405
|
+
id: "java",
|
|
406
|
+
extensions: EXTENSIONS,
|
|
407
|
+
capabilities: CAPABILITIES,
|
|
408
|
+
importNoiseIdentifiers: IMPORT_NOISE,
|
|
409
|
+
extract: extractJava,
|
|
410
|
+
resolveFileDep: resolveJavaFileDep,
|
|
411
|
+
};
|
|
412
|
+
}
|
|
@@ -0,0 +1,217 @@
|
|
|
1
|
+
import { dirname, join, relative, resolve, sep } from "node:path";
|
|
2
|
+
import type { FileDepResolver } from "../adapter.ts";
|
|
3
|
+
import { externalId, firstExistingFile, internalPaths, isWithin } from "./file-dep-util.ts";
|
|
4
|
+
|
|
5
|
+
type JvmFileDepOptions = {
|
|
6
|
+
/** File extensions to match, e.g. .java / .kt */
|
|
7
|
+
extensions: readonly string[];
|
|
8
|
+
/** Leading packages always treated as external. */
|
|
9
|
+
externalPrefixes: readonly string[];
|
|
10
|
+
/** Extra source-root suffixes under scope (relative). */
|
|
11
|
+
sourceRootSuffixes: readonly string[];
|
|
12
|
+
};
|
|
13
|
+
|
|
14
|
+
function stripImportNoise(specifier: string): { path: string; wildcard: boolean } {
|
|
15
|
+
let s = specifier.trim().replace(/;$/u, "");
|
|
16
|
+
s = s.replace(/^static\s+/u, "");
|
|
17
|
+
const wildcard = s.endsWith(".*");
|
|
18
|
+
if (wildcard) {
|
|
19
|
+
// Keep full package path — do not peel segments (all-lowercase packages).
|
|
20
|
+
return { path: s.slice(0, -2).trim(), wildcard: true };
|
|
21
|
+
}
|
|
22
|
+
// Static/member imports: pkg.Type.member or pkg.Type.Companion.member → peel trailing
|
|
23
|
+
// non-type segments (lowercase start, or Companion/INSTANCE).
|
|
24
|
+
const parts = s.split(".").filter((part) => part.length > 0);
|
|
25
|
+
while (parts.length > 1) {
|
|
26
|
+
const last = parts[parts.length - 1];
|
|
27
|
+
if (last === undefined) break;
|
|
28
|
+
if (last === "Companion" || last === "INSTANCE" || last === "Default") {
|
|
29
|
+
parts.pop();
|
|
30
|
+
continue;
|
|
31
|
+
}
|
|
32
|
+
// Java/Kotlin type names are typically UpperCamel; members start lower.
|
|
33
|
+
const start = last.charAt(0);
|
|
34
|
+
if (start !== "" && start === start.toLowerCase() && start !== start.toUpperCase()) {
|
|
35
|
+
parts.pop();
|
|
36
|
+
continue;
|
|
37
|
+
}
|
|
38
|
+
break;
|
|
39
|
+
}
|
|
40
|
+
return { path: parts.join("."), wildcard: false };
|
|
41
|
+
}
|
|
42
|
+
|
|
43
|
+
function isExternalPackage(path: string, prefixes: readonly string[]): boolean {
|
|
44
|
+
for (const prefix of prefixes) {
|
|
45
|
+
if (path === prefix || path.startsWith(`${prefix}.`)) return true;
|
|
46
|
+
}
|
|
47
|
+
return false;
|
|
48
|
+
}
|
|
49
|
+
|
|
50
|
+
function segmentsOf(dotted: string): string[] {
|
|
51
|
+
return dotted.split(".").filter((part) => part.length > 0);
|
|
52
|
+
}
|
|
53
|
+
|
|
54
|
+
/** Infer source root by peeling package segments off dirname(fromPath). */
|
|
55
|
+
function inferredSourceRoot(fromPath: string, packageSegments: readonly string[]): string | undefined {
|
|
56
|
+
if (packageSegments.length === 0) return dirname(fromPath);
|
|
57
|
+
const dir = resolve(dirname(fromPath));
|
|
58
|
+
const parts = dir.split(sep);
|
|
59
|
+
if (parts.length < packageSegments.length) return undefined;
|
|
60
|
+
const tail = parts.slice(parts.length - packageSegments.length);
|
|
61
|
+
if (tail.join("/") !== packageSegments.join("/") && tail.join(sep) !== packageSegments.join(sep)) {
|
|
62
|
+
// compare case-sensitive path segments
|
|
63
|
+
for (let i = 0; i < packageSegments.length; i += 1) {
|
|
64
|
+
if (tail[i] !== packageSegments[i]) return undefined;
|
|
65
|
+
}
|
|
66
|
+
}
|
|
67
|
+
return parts.slice(0, parts.length - packageSegments.length).join(sep) || sep;
|
|
68
|
+
}
|
|
69
|
+
|
|
70
|
+
/** Resolve a type import (`pkg.Type`) to 0–1 files under a source root. No package dumps. */
|
|
71
|
+
async function resolveTypeAgainstRoot(
|
|
72
|
+
host: Parameters<FileDepResolver>[2],
|
|
73
|
+
sourceRoot: string,
|
|
74
|
+
segments: readonly string[],
|
|
75
|
+
extensions: readonly string[],
|
|
76
|
+
): Promise<string | undefined> {
|
|
77
|
+
if (!isWithin(host.scopeRoot, sourceRoot) && resolve(sourceRoot) !== resolve(host.scopeRoot)) {
|
|
78
|
+
return undefined;
|
|
79
|
+
}
|
|
80
|
+
if (segments.length === 0) return undefined;
|
|
81
|
+
const typeName = segments[segments.length - 1];
|
|
82
|
+
const pkg = segments.slice(0, -1);
|
|
83
|
+
if (typeName === undefined) return undefined;
|
|
84
|
+
const dir = pkg.length === 0 ? sourceRoot : join(sourceRoot, ...pkg);
|
|
85
|
+
const candidates = extensions.map((ext) => join(dir, `${typeName}${ext}`));
|
|
86
|
+
const file = await firstExistingFile(host, candidates);
|
|
87
|
+
if (file !== undefined && isWithin(host.scopeRoot, file)) return file;
|
|
88
|
+
return undefined;
|
|
89
|
+
}
|
|
90
|
+
|
|
91
|
+
function sourceRootsFor(
|
|
92
|
+
host: Parameters<FileDepResolver>[2],
|
|
93
|
+
fromPath: string,
|
|
94
|
+
importSegments: readonly string[],
|
|
95
|
+
suffixes: readonly string[],
|
|
96
|
+
): string[] {
|
|
97
|
+
const roots = new Set<string>();
|
|
98
|
+
// Peel one segment as possible type from fromPath package guess via path
|
|
99
|
+
const fromDir = dirname(fromPath);
|
|
100
|
+
const relToScope = relative(host.scopeRoot, fromDir);
|
|
101
|
+
if (!relToScope.startsWith("..") && !isAbsoluteRel(relToScope)) {
|
|
102
|
+
// try every ancestor as potential source root
|
|
103
|
+
const parts = relToScope.length === 0 ? [] : relToScope.split(sep);
|
|
104
|
+
for (let i = 0; i <= parts.length; i += 1) {
|
|
105
|
+
roots.add(i === 0 ? host.scopeRoot : join(host.scopeRoot, ...parts.slice(0, i)));
|
|
106
|
+
}
|
|
107
|
+
}
|
|
108
|
+
const inferred = inferredSourceRoot(fromPath, guessPackageSegments(fromPath, host.scopeRoot));
|
|
109
|
+
if (inferred !== undefined) roots.add(inferred);
|
|
110
|
+
|
|
111
|
+
for (const suffix of suffixes) {
|
|
112
|
+
roots.add(join(host.scopeRoot, ...suffix.split("/").filter((p) => p.length > 0)));
|
|
113
|
+
}
|
|
114
|
+
|
|
115
|
+
// Prefer roots that could contain the import path
|
|
116
|
+
void importSegments;
|
|
117
|
+
return [...roots];
|
|
118
|
+
}
|
|
119
|
+
|
|
120
|
+
function isAbsoluteRel(rel: string): boolean {
|
|
121
|
+
return rel.length > 0 && (rel.startsWith(sep) || /^[A-Za-z]:[\\/]/u.test(rel));
|
|
122
|
+
}
|
|
123
|
+
|
|
124
|
+
function guessPackageSegments(fromPath: string, scopeRoot: string): string[] {
|
|
125
|
+
const rel = relative(scopeRoot, dirname(fromPath));
|
|
126
|
+
if (rel.startsWith("..") || rel.length === 0) return [];
|
|
127
|
+
const parts = rel.split(sep);
|
|
128
|
+
// Drop common source root prefixes
|
|
129
|
+
const dropPrefixes = [
|
|
130
|
+
["src", "main", "java"],
|
|
131
|
+
["src", "main", "kotlin"],
|
|
132
|
+
["src", "commonMain", "kotlin"],
|
|
133
|
+
["src", "commonMain", "java"],
|
|
134
|
+
["src", "jvmMain", "kotlin"],
|
|
135
|
+
["src", "test", "java"],
|
|
136
|
+
["src", "test", "kotlin"],
|
|
137
|
+
["src"],
|
|
138
|
+
];
|
|
139
|
+
for (const prefix of dropPrefixes) {
|
|
140
|
+
if (parts.length >= prefix.length && prefix.every((p, i) => parts[i] === p)) {
|
|
141
|
+
return parts.slice(prefix.length);
|
|
142
|
+
}
|
|
143
|
+
}
|
|
144
|
+
// guava layout: guava/src/com/...
|
|
145
|
+
const comAt = parts.indexOf("com");
|
|
146
|
+
const orgAt = parts.indexOf("org");
|
|
147
|
+
const netAt = parts.indexOf("net");
|
|
148
|
+
const idx = [comAt, orgAt, netAt].filter((v) => v >= 0).sort((a, b) => a - b)[0];
|
|
149
|
+
if (idx !== undefined) return parts.slice(idx);
|
|
150
|
+
return parts;
|
|
151
|
+
}
|
|
152
|
+
|
|
153
|
+
function createJvmFileDepResolver(options: JvmFileDepOptions): FileDepResolver {
|
|
154
|
+
return async (fromPath, specifier, host, signal) => {
|
|
155
|
+
signal.throwIfAborted();
|
|
156
|
+
const raw = specifier
|
|
157
|
+
.trim()
|
|
158
|
+
.replace(/;$/u, "")
|
|
159
|
+
.replace(/^static\s+/u, "");
|
|
160
|
+
const { path, wildcard } = stripImportNoise(specifier);
|
|
161
|
+
if (path.length === 0) return { kind: "unresolved" };
|
|
162
|
+
if (isExternalPackage(path, options.externalPrefixes)) return externalId(path);
|
|
163
|
+
|
|
164
|
+
// Wildcards / bare packages are package ids — never dump every file in the dir.
|
|
165
|
+
if (wildcard) return externalId(path);
|
|
166
|
+
|
|
167
|
+
const segments = segmentsOf(path);
|
|
168
|
+
const packageOnly =
|
|
169
|
+
segments.length > 0 &&
|
|
170
|
+
segments.every((seg) => {
|
|
171
|
+
const c = seg.charAt(0);
|
|
172
|
+
return c === c.toLowerCase() && c !== c.toUpperCase();
|
|
173
|
+
});
|
|
174
|
+
// Top-level function/property import (okio.buffer) peels to package-only segments.
|
|
175
|
+
const rawParts = raw
|
|
176
|
+
.replace(/\.\*$/u, "")
|
|
177
|
+
.split(".")
|
|
178
|
+
.filter((part) => part.length > 0);
|
|
179
|
+
const lastRaw = rawParts[rawParts.length - 1];
|
|
180
|
+
const memberLike =
|
|
181
|
+
lastRaw !== undefined &&
|
|
182
|
+
lastRaw !== "Companion" &&
|
|
183
|
+
lastRaw.charAt(0) === lastRaw.charAt(0).toLowerCase() &&
|
|
184
|
+
lastRaw.charAt(0) !== lastRaw.charAt(0).toUpperCase();
|
|
185
|
+
if (packageOnly) {
|
|
186
|
+
return externalId(memberLike ? raw.replace(/\.\*$/u, "") : path);
|
|
187
|
+
}
|
|
188
|
+
|
|
189
|
+
const roots = sourceRootsFor(host, fromPath, segments, options.sourceRootSuffixes);
|
|
190
|
+
for (const root of roots) {
|
|
191
|
+
signal.throwIfAborted();
|
|
192
|
+
const file = await resolveTypeAgainstRoot(host, root, segments, options.extensions);
|
|
193
|
+
if (file !== undefined) return internalPaths([file]);
|
|
194
|
+
}
|
|
195
|
+
return externalId(path);
|
|
196
|
+
};
|
|
197
|
+
}
|
|
198
|
+
|
|
199
|
+
export const resolveJavaFileDep = createJvmFileDepResolver({
|
|
200
|
+
extensions: [".java"],
|
|
201
|
+
externalPrefixes: ["java", "javax", "jakarta", "jdk", "sun"],
|
|
202
|
+
sourceRootSuffixes: ["src/main/java", "src"],
|
|
203
|
+
});
|
|
204
|
+
|
|
205
|
+
export const resolveKotlinFileDep = createJvmFileDepResolver({
|
|
206
|
+
// .kts is often Gradle — not a package type file.
|
|
207
|
+
extensions: [".kt", ".java"],
|
|
208
|
+
externalPrefixes: ["java", "javax", "jakarta", "kotlin", "kotlinx"],
|
|
209
|
+
sourceRootSuffixes: [
|
|
210
|
+
"src/main/kotlin",
|
|
211
|
+
"src/main/java",
|
|
212
|
+
"src/commonMain/kotlin",
|
|
213
|
+
"src/commonMain/java",
|
|
214
|
+
"src/jvmMain/kotlin",
|
|
215
|
+
"src",
|
|
216
|
+
],
|
|
217
|
+
});
|