Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
Binary file added public/wasm/tree-sitter-java.wasm
Binary file not shown.
1 change: 1 addition & 0 deletions src/app/api/analyze/route.ts
Original file line number Diff line number Diff line change
Expand Up @@ -22,6 +22,7 @@ const EXT: Record<string, string> = {
go: "go",
rust: "rs",
sql: "sql",
java: "java",
};

function normalizePath(p: string): string {
Expand Down
27 changes: 26 additions & 1 deletion src/components/ui/CodeWorkspace.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -6,7 +6,7 @@ import { saveGraph } from "@/lib/graphs";
import type { Graph } from "@/lib/analysis/types";
import { graphSchema } from "@/lib/validation";

const LANGUAGES = ["python", "javascript", "typescript", "go", "rust", "sql"];
const LANGUAGES = ["python", "javascript", "typescript", "go", "rust", "sql", "java"];

const EXT: Record<string, string> = {
python: "py",
Expand All @@ -15,6 +15,7 @@ const EXT: Record<string, string> = {
go: "go",
rust: "rs",
sql: "sql",
java: "java",
};

const PROJECT_EXTS: Record<string, string[]> = {
Expand All @@ -24,6 +25,7 @@ const PROJECT_EXTS: Record<string, string[]> = {
go: [".go"],
rust: [".rs"],
sql: [".sql"],
java: [".java"],
};

const IGNORE_DIR =
Expand Down Expand Up @@ -163,6 +165,29 @@ CREATE TABLE post_tags (
FOREIGN KEY (post_id) REFERENCES posts (id),
FOREIGN KEY (tag_id) REFERENCES tags (id)
);
`,
java: `public class App {
public static void main(String[] args) {
int[] data = load();
save(transform(data));
}

public static int[] load() {
return read();
}

public static int[] transform(int[] data) {
return clean(data);
}

public static int[] clean(int[] data) {
return data;
}

public static void save(int[] x) {
write(x);
}
}
`,
};

Expand Down
45 changes: 45 additions & 0 deletions src/lib/analysis/analyzers/analyzers.test.ts
Original file line number Diff line number Diff line change
Expand Up @@ -5,6 +5,7 @@ import { typescriptAnalyzer } from "./typescript";
import { goAnalyzer } from "./go";
import { rustAnalyzer } from "./rust";
import { sqlAnalyzer } from "./sql";
import { javaAnalyzer } from "./java";
import type { Graph, LanguageAnalyzer, SourceFile } from "../types";

function run(
Expand Down Expand Up @@ -493,3 +494,47 @@ CREATE TABLE user_roles (
]);
});
});

describe("java", () => {
test("call graph and inheritance inside java files", async () => {
const graph = await run(javaAnalyzer, [
[
"com/example/App.java",
`package com.example;
import com.example.Service;

public class App extends BaseApp {
public static void main(String[] args) {
Service service = new Service();
service.execute();
}
}
class BaseApp {}`
],
[
"com/example/Service.java",
`package com.example;

public class Service {
public void execute() {
log();
}
private void log() {}
}`
]
]);

// Check class nodes
expect(hasNode(graph, "class::com/example/App.java::App")).toBe(true);
expect(hasNode(graph, "class::com/example/Service.java::Service")).toBe(true);

// Check class inheritance (App extends BaseApp)
expect(hasEdge(graph, "class::com/example/App.java::App", "class::com/example/App.java::BaseApp", "extends")).toBe(true);

// Check call graph edge (execute calls log)
expect(hasEdge(graph, "com/example/Service.java::execute", "com/example/Service.java::log", "calls")).toBe(true);

// Check call graph edge between files (main calls execute)
expect(hasEdge(graph, "com/example/App.java::main", "com/example/Service.java::execute", "calls")).toBe(true);
});
});
63 changes: 63 additions & 0 deletions src/lib/analysis/analyzers/java.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,63 @@
import type { LanguageAnalyzer } from "../types";
import { analyzeProjectWith, type LangSpec } from "./shared";
import type Parser from "web-tree-sitter";

function classBases(node: Parser.SyntaxNode): string[] {
const superclass = node.namedChildren.find((c) => c.type === "superclass");
if (!superclass) return [];
const typeNode = superclass.namedChildren[0];
if (typeNode) {
if (typeNode.type === "generic_type") {
const baseNode = typeNode.childForFieldName("type");
if (baseNode) return [baseNode.text];
}
return [typeNode.text];
}
return [];
}

function resolveModule(
fromFile: string,
specifier: string,
paths: Set<string>,
): string | null {
if (specifier.endsWith(".*")) {
return null;
}
const relPath = specifier.replace(/\./g, "/") + ".java";
for (const p of paths) {
if (p.endsWith(relPath)) {
return p;
}
}
return null;
Comment on lines +19 to +33

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

🎯 Functional Correctness | 🟡 Minor | ⚡ Quick win

🧩 Analysis chain

🏁 Script executed:

#!/bin/bash
set -euo pipefail

printf '\n== files ==\n'
git ls-files src/lib/analysis/analyzers/java.ts src/lib/analysis/analyzers/shared.ts

printf '\n== outline: java.ts ==\n'
ast-grep outline src/lib/analysis/analyzers/java.ts --view expanded || true

printf '\n== outline: shared.ts ==\n'
ast-grep outline src/lib/analysis/analyzers/shared.ts --view expanded || true

printf '\n== relevant snippets ==\n'
sed -n '1,220p' src/lib/analysis/analyzers/java.ts
printf '\n---- shared.ts ----\n'
sed -n '1,260p' src/lib/analysis/analyzers/shared.ts

printf '\n== search for java tree-sitter queries / constructor handling ==\n'
rg -n "constructor_declaration|object_creation|method_invocation|import_static|static_import|superclass|resolveModule|callQuery|funcDefQuery" src/lib/analysis/analyzers src/lib -g '!**/dist/**' -g '!**/build/**'

Repository: DataDave-Dev/weftmap

Length of output: 16095


🏁 Script executed:

#!/bin/bash
set -euo pipefail

sed -n '260,420p' src/lib/analysis/analyzers/shared.ts

Repository: DataDave-Dev/weftmap

Length of output: 2136


Strip the member segment for static imports. In src/lib/analysis/analyzers/java.ts:19-33, import static com.example.Util.log; is resolved as com/example/Util/log.java, so the graph misses the actual Util.java import and log() only resolves when it is otherwise unique.

🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

In `@src/lib/analysis/analyzers/java.ts` around lines 19 - 33, The static import
handling in resolveModule is treating the member name as part of the module
path, so imports like com.example.Util.log are being resolved as a non-existent
Util/log.java instead of Util.java. Update the resolution logic in resolveModule
to detect static imports and strip the final member segment before building
relPath, while keeping the existing wildcard import behavior unchanged; use the
resolveModule helper in java.ts as the fix location.

}

const spec: LangSpec = {
language: "java",
wasm: "tree-sitter-java.wasm",
funcDefQuery: `
(method_declaration) @def
(constructor_declaration) @def
`,
callQuery: `
(method_invocation name: (identifier) @callee)
`,
importQuery: `
(import_declaration (scoped_identifier) @mod)
(import_declaration (identifier) @mod)
`,
classQuery: `
(class_declaration) @class
(interface_declaration) @class
`,
funcDefTypes: new Set(["method_declaration", "constructor_declaration"]),
classNodeTypes: new Set(["class_declaration", "interface_declaration"]),
classBases,
resolveModule,
};

export const javaAnalyzer: LanguageAnalyzer = {
language: spec.language,
analyzeProject: (files) => analyzeProjectWith(spec, files),
};
2 changes: 2 additions & 0 deletions src/lib/analysis/registry.ts
Original file line number Diff line number Diff line change
Expand Up @@ -5,6 +5,7 @@ import { typescriptAnalyzer } from "./analyzers/typescript";
import { goAnalyzer } from "./analyzers/go";
import { rustAnalyzer } from "./analyzers/rust";
import { sqlAnalyzer } from "./analyzers/sql";
import { javaAnalyzer } from "./analyzers/java";

const analyzers: LanguageAnalyzer[] = [
pythonAnalyzer,
Expand All @@ -13,6 +14,7 @@ const analyzers: LanguageAnalyzer[] = [
goAnalyzer,
rustAnalyzer,
sqlAnalyzer,
javaAnalyzer,
];

const registry = new Map(analyzers.map((a) => [a.language, a]));
Expand Down
Loading