fix: improve Java parsing to handle complete grammar and fix duplication issues

- Created comprehensive Java test fixture with complete grammar coverage
- Fixed duplication issues in processCaptures function
- Improved deduplication logic with priority-based capture selection
- Added tests for all Java constructs including interfaces, enums, records, sealed classes
- Enhanced query patterns to avoid overlapping captures
This commit is contained in:
Roo Code 2025-08-23 18:07:57 +00:00
parent 24ab961c61
commit 8c50fa9f0e
11 changed files with 708 additions and 409 deletions

View file

@ -1,49 +0,0 @@
# file.java
2--193 | module test.module.definition {
3--7 | module test.module.definition {
8--8 | package test.package.definition;
10--10 | import java.util.List;
11--11 | import java.util.Map;
12--12 | import java.util.function.Function;
13--13 | import java.time.LocalDateTime;
30--44 | public interface TestInterfaceDefinition<T extends Comparable<T>> {
30--30 | public interface TestInterfaceDefinition<T extends Comparable<T>> {
47--63 | public enum TestEnumDefinition {
53--53 | private final int level;
53--53 | private final int level;
54--54 | private final String description;
54--54 | private final String description;
56--62 | TestEnumDefinition(
66--119 | value = "test",
71--71 | public class TestClassDefinition<T extends Comparable<T>>
75--79 | value = "field",
79--79 | private final String prefix;
80--80 | private static int instanceCount = 0;
80--80 | private static int instanceCount = 0;
83--90 | public TestClassDefinition(
93--99 | public void testInterfaceMethod(
102--108 | public <R extends Comparable<R>> R testGenericMethodDefinition(
102--102 | public <R extends Comparable<R>> R testGenericMethodDefinition(
111--118 | private final Function<String, Integer> testLambdaDefinition = (
111--118 | private final Function<String, Integer> testLambdaDefinition = (
111--118 | private final Function<String, Integer> testLambdaDefinition = (
122--143 | public record TestRecordDefinition(
135--142 | public String formatMessage() {
146--160 | public abstract class TestAbstractClassDefinition<T> {
146--146 | public abstract class TestAbstractClassDefinition<T> {
147--147 | protected final T data;
147--147 | protected final T data;
149--153 | protected TestAbstractClassDefinition(
156--159 | public abstract String testAbstractMethod(
163--192 | public class TestOuterClassDefinition {
164--164 | private int value;
164--164 | private int value;
166--180 | public class TestInnerClassDefinition {
167--167 | private String innerField;
167--167 | private String innerField;
169--173 | public TestInnerClassDefinition(
175--179 | public void testInnerMethod() {
183--191 | public static class TestStaticNestedClassDefinition {
184--184 | private final String nestedField;
184--184 | private final String nestedField;
186--190 | public TestStaticNestedClassDefinition(

View file

@ -1,29 +0,0 @@
=== FULL PARSE RESULT ===
# TestFile.java
2--6 | interface TestInterface {
9--29 | class TestClass implements TestInterface {
11--14 | public void testMethod() {
16--19 | public String getName() {
21--24 | public int calculate(int a, int b) {
26--28 | private void helperMethod() {
========================
=== INDIVIDUAL LINES ===
Line 0: # TestFile.java
Line 1: 2--6 | interface TestInterface {
Line 2: 9--29 | class TestClass implements TestInterface {
Line 3: 11--14 | public void testMethod() {
Line 4: 16--19 | public String getName() {
Line 5: 21--24 | public int calculate(int a, int b) {
Line 6: 26--28 | private void helperMethod() {
========================
=== INTERFACE LINES ===
2--6 | interface TestInterface {
========================
=== testMethod LINES ===
11--14 | public void testMethod() {
========================

View file

@ -1,24 +0,0 @@
=== FULL PARSE RESULT ===
# TestClass.java
1--16 | class TestClass implements TestInterface {
3--6 | public void testMethod() {
8--11 | public String getName() {
13--15 | private void helperMethod() {
========================
=== INDIVIDUAL LINES ===
Line 0: # TestClass.java
Line 1: 1--16 | class TestClass implements TestInterface {
Line 2: 3--6 | public void testMethod() {
Line 3: 8--11 | public String getName() {
Line 4: 13--15 | private void helperMethod() {
========================
=== testMethod LINES ===
3--6 | public void testMethod() {
========================
=== @Override ONLY LINES ===
============================

View file

@ -1,49 +0,0 @@
import { describe, it, expect } from "vitest"
import { testParseSourceCodeDefinitions } from "./helpers"
import { javaQuery } from "../queries"
import sampleJavaContent from "./fixtures/sample-java"
import * as fs from "fs"
describe("Debug full Java parsing", () => {
it("should show what's being captured for full sample", async () => {
const testOptions = {
language: "java",
wasmFile: "tree-sitter-java.wasm",
queryString: javaQuery,
extKey: "java",
}
const parseResult = await testParseSourceCodeDefinitions("/test/file.java", sampleJavaContent, testOptions)
if (parseResult) {
// Write to file
fs.writeFileSync("debug-full-java-output.txt", parseResult)
console.log("Debug output written to debug-full-java-output.txt")
// Check for specific patterns
const lines = parseResult.split("\n").filter((line) => line.trim())
// Check for class with annotations
const classLines = lines.filter((line) => line.includes("TestClassDefinition"))
console.log("\n=== CLASS LINES ===")
classLines.forEach((line) => console.log(line))
// Check for annotation declarations
const annotationLines = lines.filter((line) => line.includes("@Target") || line.includes("@TestAnnotation"))
console.log("\n=== ANNOTATION LINES ===")
annotationLines.forEach((line) => console.log(line))
// Check for interface methods
const interfaceMethodLines = lines.filter(
(line) =>
line.includes("void testInterfaceMethod") ||
line.includes("default String testInterfaceDefaultMethod"),
)
console.log("\n=== INTERFACE METHOD LINES ===")
interfaceMethodLines.forEach((line) => console.log(line))
}
// This test is just for debugging, always pass
expect(true).toBe(true)
})
})

View file

@ -1,81 +0,0 @@
import { describe, it, expect } from "vitest"
import { testParseSourceCodeDefinitions } from "./helpers"
import { javaQuery } from "../queries"
import * as fs from "fs"
describe("Debug Java interface parsing", () => {
it("should show what's being captured for interface", async () => {
const javaContent = `// Test interface with methods
interface TestInterface {
void testMethod();
String getName();
int calculate(int a, int b);
}
// Test class implementing interface with annotations
class TestClass implements TestInterface {
@Override
public void testMethod() {
// Implementation goes here
}
@Override
public String getName() {
return "TestClass";
}
@Override
public int calculate(int a, int b) {
return a + b;
}
private void helperMethod() {
// Helper implementation
}
}`
const testOptions = {
language: "java",
wasmFile: "tree-sitter-java.wasm",
queryString: javaQuery,
extKey: "java",
}
const parseResult = await testParseSourceCodeDefinitions("/test/TestFile.java", javaContent, testOptions)
let debugOutput = ""
if (parseResult) {
debugOutput += "\n=== FULL PARSE RESULT ===\n"
debugOutput += parseResult + "\n"
debugOutput += "========================\n"
const lines = parseResult.split("\n").filter((line) => line.trim())
debugOutput += "\n=== INDIVIDUAL LINES ===\n"
lines.forEach((line, i) => {
debugOutput += `Line ${i}: ${line}\n`
})
debugOutput += "========================\n"
// Check for interface
const interfaceLines = lines.filter((line) => line.includes("interface TestInterface"))
debugOutput += "\n=== INTERFACE LINES ===\n"
interfaceLines.forEach((line) => (debugOutput += line + "\n"))
debugOutput += "========================\n"
// Check for testMethod
const methodLines = lines.filter((line) => line.includes("testMethod"))
debugOutput += "\n=== testMethod LINES ===\n"
methodLines.forEach((line) => (debugOutput += line + "\n"))
debugOutput += "========================\n"
// Write to file
fs.writeFileSync("debug-interface-output.txt", debugOutput)
console.log("Debug output written to debug-interface-output.txt")
}
// This test is just for debugging, always pass
expect(true).toBe(true)
})
})

View file

@ -1,71 +0,0 @@
import { describe, it, expect } from "vitest"
import { testParseSourceCodeDefinitions } from "./helpers"
import { javaQuery } from "../queries"
import * as fs from "fs"
describe("Debug Java parsing", () => {
it("should show what's being captured", async () => {
const javaContent = `class TestClass implements TestInterface {
@Override
public void testMethod() {
// Implementation goes here
}
@Override
public String getName() {
return "TestClass";
}
private void helperMethod() {
// Helper implementation
}
}`
const testOptions = {
language: "java",
wasmFile: "tree-sitter-java.wasm",
queryString: javaQuery,
extKey: "java",
}
const parseResult = await testParseSourceCodeDefinitions("/test/TestClass.java", javaContent, testOptions)
let debugOutput = ""
if (parseResult) {
debugOutput += "\n=== FULL PARSE RESULT ===\n"
debugOutput += parseResult + "\n"
debugOutput += "========================\n"
const lines = parseResult.split("\n").filter((line) => line.trim())
debugOutput += "\n=== INDIVIDUAL LINES ===\n"
lines.forEach((line, i) => {
debugOutput += `Line ${i}: ${line}\n`
})
debugOutput += "========================\n"
// Check for duplicates
const methodLines = lines.filter((line) => line.includes("testMethod"))
debugOutput += "\n=== testMethod LINES ===\n"
methodLines.forEach((line) => (debugOutput += line + "\n"))
debugOutput += "========================\n"
// Check for @Override lines
const overrideLines = lines.filter((line) => {
const content = line.split("|")[1]?.trim() || ""
return content === "@Override"
})
debugOutput += "\n=== @Override ONLY LINES ===\n"
overrideLines.forEach((line) => (debugOutput += line + "\n"))
debugOutput += "============================\n"
// Write to file
fs.writeFileSync("debug-output.txt", debugOutput)
console.log("Debug output written to debug-output.txt")
}
// This test is just for debugging, always pass
expect(true).toBe(true)
})
})

View file

@ -0,0 +1,351 @@
export default String.raw`
// Package declaration
package com.example.comprehensive;
// Import statements
import java.util.*;
import java.util.concurrent.CompletableFuture;
import java.util.function.Function;
import java.io.IOException;
import java.lang.annotation.*;
import static java.lang.Math.PI;
import static java.util.Collections.*;
// Single-line comment
/* Multi-line comment
spanning multiple lines */
/**
* JavaDoc comment for annotation
* @since 1.0
*/
@Target({ElementType.TYPE, ElementType.METHOD, ElementType.FIELD})
@Retention(RetentionPolicy.RUNTIME)
@Documented
@Inherited
public @interface CustomAnnotation {
String value() default "";
int priority() default 0;
Class<?>[] types() default {};
String[] tags() default {};
}
/**
* Interface with various method types
*/
public interface GenericInterface<T extends Comparable<T>, U> {
// Abstract method
void abstractMethod(T param);
// Default method with implementation
default U defaultMethod(T input) {
return processInput(input);
}
// Static method in interface
static <V> V staticInterfaceMethod(V value) {
return value;
}
// Private method in interface (Java 9+)
private U processInput(T input) {
return null;
}
}
/**
* Abstract class with various members
*/
public abstract class AbstractBase<T> implements GenericInterface<T, String> {
// Protected field
protected T data;
// Static field
private static final String CONSTANT = "CONST_VALUE";
// Constructor
protected AbstractBase(T data) {
this.data = data;
}
// Abstract method
public abstract T process(T input);
// Concrete method
public final String getName() {
return this.getClass().getSimpleName();
}
}
/**
* Enum with constructor and methods
*/
public enum Status {
PENDING(0, "Pending"),
ACTIVE(1, "Active") {
@Override
public String getDescription() {
return "Currently active: " + description;
}
},
COMPLETED(2, "Completed"),
FAILED(-1, "Failed");
private final int code;
protected final String description;
Status(int code, String description) {
this.code = code;
this.description = description;
}
public String getDescription() {
return description;
}
}
/**
* Main class with comprehensive Java features
*/
@CustomAnnotation(value = "MainClass", priority = 1, types = {String.class, Integer.class})
@SuppressWarnings("unchecked")
public class ComprehensiveExample<T extends Comparable<T>>
extends AbstractBase<T>
implements Serializable, Cloneable {
// Serial version UID
private static final long serialVersionUID = 1L;
// Various field types
private volatile int counter;
private transient String tempData;
public static final double PI_VALUE = 3.14159;
private final List<T> items = new ArrayList<>();
// Static initializer block
static {
System.out.println("Static initializer");
}
// Instance initializer block
{
counter = 0;
tempData = "temp";
}
// Constructor with annotations
@SuppressWarnings("deprecation")
public ComprehensiveExample(@NonNull T initialData) {
super(initialData);
}
// Overloaded constructor
public ComprehensiveExample(T data, int counter) {
this(data);
this.counter = counter;
}
// Method with generic return type and throws clause
@Override
public T process(T input) throws IllegalArgumentException {
if (input == null) {
throw new IllegalArgumentException("Input cannot be null");
}
return input;
}
// Synchronized method
public synchronized void incrementCounter() {
counter++;
}
// Method with varargs
public void processMultiple(T... items) {
for (T item : items) {
this.items.add(item);
}
}
// Generic method with bounds
public <U extends Number & Comparable<U>> U genericMethod(U value) {
return value;
}
// Method with array parameter
public static void arrayMethod(String[] args, int[][] matrix) {
System.out.println(Arrays.toString(args));
}
// Inner class
public class InnerClass {
private String innerField;
public InnerClass(String field) {
this.innerField = field;
}
public void accessOuter() {
System.out.println(ComprehensiveExample.this.counter);
}
}
// Static nested class
public static class StaticNestedClass {
private static int nestedCounter;
public StaticNestedClass() {
nestedCounter++;
}
public static int getCounter() {
return nestedCounter;
}
}
// Local class inside method
public void methodWithLocalClass() {
class LocalClass {
private String localField;
public LocalClass(String field) {
this.localField = field;
}
public void printLocal() {
System.out.println(localField);
}
}
LocalClass local = new LocalClass("local");
local.printLocal();
}
// Anonymous class
public Runnable createRunnable() {
return new Runnable() {
@Override
public void run() {
System.out.println("Anonymous class");
}
};
}
// Lambda expressions
public void lambdaExamples() {
// Simple lambda
Runnable r1 = () -> System.out.println("Lambda");
// Lambda with parameters
Function<String, Integer> f1 = s -> s.length();
// Lambda with block
Function<Integer, String> f2 = (Integer i) -> {
String result = "Number: " + i;
return result;
};
// Method reference
Function<String, Integer> f3 = String::length;
}
// Try-with-resources
public void tryWithResources() throws IOException {
try (var resource = new AutoCloseable() {
@Override
public void close() throws Exception {
System.out.println("Closing");
}
}) {
// Use resource
} catch (Exception e) {
e.printStackTrace();
} finally {
System.out.println("Finally");
}
}
// Switch expression (Java 14+)
public String switchExpression(Status status) {
return switch (status) {
case PENDING -> "Waiting";
case ACTIVE -> "Running";
case COMPLETED -> "Done";
case FAILED -> {
System.out.println("Failed status");
yield "Error";
}
};
}
}
/**
* Record class (Java 14+)
*/
public record PersonRecord(
String name,
int age,
List<String> hobbies
) {
// Compact constructor
public PersonRecord {
Objects.requireNonNull(name);
if (age < 0) {
throw new IllegalArgumentException("Age cannot be negative");
}
}
// Additional method
public String getInfo() {
return String.format("%s (%d years)", name, age);
}
// Static factory method
public static PersonRecord of(String name, int age) {
return new PersonRecord(name, age, new ArrayList<>());
}
}
/**
* Sealed class (Java 17+)
*/
public sealed class Shape
permits Circle, Rectangle, Triangle {
protected final double area;
protected Shape(double area) {
this.area = area;
}
public double getArea() {
return area;
}
}
// Permitted subclasses
final class Circle extends Shape {
private final double radius;
public Circle(double radius) {
super(Math.PI * radius * radius);
this.radius = radius;
}
}
final class Rectangle extends Shape {
private final double width;
private final double height;
public Rectangle(double width, double height) {
super(width * height);
this.width = width;
this.height = height;
}
}
non-sealed class Triangle extends Shape {
public Triangle(double base, double height) {
super(0.5 * base * height);
}
}
`

View file

@ -0,0 +1,254 @@
import { describe, it, expect, beforeAll } from "vitest"
import { testParseSourceCodeDefinitions } from "./helpers"
import { javaQuery } from "../queries"
import sampleJavaComprehensiveContent from "./fixtures/sample-java-comprehensive"
describe("Java parsing - comprehensive grammar test", () => {
let parseResult: string = ""
let lines: string[] = []
beforeAll(async () => {
const testOptions = {
language: "java",
wasmFile: "tree-sitter-java.wasm",
queryString: javaQuery,
extKey: "java",
}
const result = await testParseSourceCodeDefinitions(
"/test/ComprehensiveExample.java",
sampleJavaComprehensiveContent,
testOptions,
)
if (!result) {
throw new Error("Failed to parse Java source code")
}
parseResult = result
lines = parseResult.split("\n").filter((line) => line.trim())
// Debug output
console.log("\n=== COMPREHENSIVE PARSE RESULT ===")
console.log(parseResult)
console.log("==================================\n")
})
describe("No duplications", () => {
it("should not have duplicate class declarations", () => {
const classDeclarations = lines.filter(
(line) =>
line.includes("class ComprehensiveExample") ||
line.includes("class AbstractBase") ||
line.includes("class Shape"),
)
// Check each class appears only once
const comprehensiveLines = classDeclarations.filter((line) => line.includes("ComprehensiveExample"))
const abstractLines = classDeclarations.filter((line) => line.includes("AbstractBase"))
const shapeLines = classDeclarations.filter((line) => line.includes("Shape"))
expect(comprehensiveLines.length).toBeLessThanOrEqual(1)
expect(abstractLines.length).toBeLessThanOrEqual(1)
expect(shapeLines.length).toBeLessThanOrEqual(1)
})
it("should not have duplicate interface declarations", () => {
const interfaceLines = lines.filter((line) => line.includes("interface GenericInterface"))
expect(interfaceLines.length).toBeLessThanOrEqual(1)
})
it("should not have duplicate method declarations", () => {
// Check specific methods don't appear multiple times
const processLines = lines.filter((line) => line.includes("process("))
const incrementLines = lines.filter((line) => line.includes("incrementCounter"))
const genericMethodLines = lines.filter((line) => line.includes("genericMethod"))
// Each method should appear at most once
expect(processLines.length).toBeLessThanOrEqual(1)
expect(incrementLines.length).toBeLessThanOrEqual(1)
expect(genericMethodLines.length).toBeLessThanOrEqual(1)
})
it("should not show @Override as standalone definition", () => {
const overrideOnlyLines = lines.filter((line) => {
const content = line.split("|")[1]?.trim() || ""
return content === "@Override"
})
expect(overrideOnlyLines.length).toBe(0)
})
})
describe("Package and imports", () => {
it("should parse package declaration", () => {
const packageLine = lines.find((line) => line.includes("package com.example.comprehensive"))
expect(packageLine).toBeDefined()
})
})
describe("Annotations", () => {
it("should parse annotation declarations", () => {
const annotationLine = lines.find((line) => line.includes("@interface CustomAnnotation"))
expect(annotationLine).toBeDefined()
})
it("should show annotated class with class declaration, not annotation", () => {
const classLine = lines.find((line) => line.includes("class ComprehensiveExample"))
expect(classLine).toBeDefined()
expect(classLine).toContain("class ComprehensiveExample")
expect(classLine).not.toContain("@CustomAnnotation")
})
})
describe("Interfaces", () => {
it("should parse interface declaration", () => {
const interfaceLine = lines.find((line) => line.includes("interface GenericInterface"))
expect(interfaceLine).toBeDefined()
})
it("should not duplicate interface methods", () => {
// Interface methods should be part of interface declaration, not separate
const abstractMethodLines = lines.filter((line) => line.includes("void abstractMethod"))
const defaultMethodLines = lines.filter((line) => line.includes("defaultMethod"))
expect(abstractMethodLines.length).toBeLessThanOrEqual(1)
expect(defaultMethodLines.length).toBeLessThanOrEqual(1)
})
})
describe("Classes", () => {
it("should parse abstract class", () => {
const abstractLine = lines.find((line) => line.includes("abstract class AbstractBase"))
expect(abstractLine).toBeDefined()
})
it("should parse main class", () => {
const mainClassLine = lines.find((line) => line.includes("class ComprehensiveExample"))
expect(mainClassLine).toBeDefined()
})
it("should parse sealed class", () => {
const sealedLine = lines.find((line) => line.includes("sealed class Shape"))
expect(sealedLine).toBeDefined()
})
it("should parse final classes", () => {
const circleLine = lines.find((line) => line.includes("class Circle"))
const rectangleLine = lines.find((line) => line.includes("class Rectangle"))
expect(circleLine).toBeDefined()
expect(rectangleLine).toBeDefined()
})
})
describe("Enums", () => {
it("should parse enum declaration", () => {
const enumLine = lines.find((line) => line.includes("enum Status"))
expect(enumLine).toBeDefined()
})
})
describe("Records", () => {
it("should parse record declaration", () => {
const recordLine = lines.find((line) => line.includes("record PersonRecord"))
expect(recordLine).toBeDefined()
})
})
describe("Inner classes", () => {
it("should parse inner class", () => {
const innerLine = lines.find((line) => line.includes("class InnerClass"))
expect(innerLine).toBeDefined()
})
it("should parse static nested class", () => {
const nestedLine = lines.find((line) => line.includes("class StaticNestedClass"))
expect(nestedLine).toBeDefined()
})
})
describe("Methods", () => {
it("should parse overridden methods with correct signature", () => {
const processMethod = lines.find((line) => line.includes("process(T input)"))
expect(processMethod).toBeDefined()
if (processMethod) {
expect(processMethod).toContain("process")
expect(processMethod).not.toContain("@Override")
}
})
it("should parse synchronized methods", () => {
const syncMethod = lines.find((line) => line.includes("incrementCounter"))
expect(syncMethod).toBeDefined()
})
it("should parse generic methods", () => {
const genericMethod = lines.find((line) => line.includes("genericMethod"))
expect(genericMethod).toBeDefined()
})
it("should parse varargs methods", () => {
const varargMethod = lines.find((line) => line.includes("processMultiple"))
expect(varargMethod).toBeDefined()
})
it("should parse static methods", () => {
const staticMethod = lines.find((line) => line.includes("arrayMethod"))
expect(staticMethod).toBeDefined()
})
})
describe("Constructors", () => {
it("should parse constructors", () => {
const constructorLines = lines.filter(
(line) =>
line.includes("ComprehensiveExample(") ||
line.includes("AbstractBase(") ||
line.includes("Circle(") ||
line.includes("Rectangle("),
)
expect(constructorLines.length).toBeGreaterThan(0)
})
})
describe("Lambda expressions", () => {
it("should parse lambda expressions", () => {
const lambdaLines = lines.filter((line) => line.includes("->"))
// Should find at least some lambda expressions
expect(lambdaLines.length).toBeGreaterThan(0)
})
})
describe("Line ranges", () => {
it("should have correct line ranges for multi-line definitions", () => {
lines.forEach((line) => {
const match = line.match(/(\d+)--(\d+)/)
if (match) {
const startLine = parseInt(match[1])
const endLine = parseInt(match[2])
// Multi-line definitions should have different start and end
if (endLine - startLine >= 3) {
// This is a multi-line definition (4+ lines)
expect(endLine).toBeGreaterThan(startLine)
}
}
})
})
})
describe("Output format", () => {
it("should format output correctly", () => {
lines.forEach((line) => {
// Each line should have the format: "startLine--endLine | content"
expect(line).toMatch(/^\d+--\d+ \| .+/)
})
})
it("should not include comment-only lines as definitions", () => {
const commentOnlyLines = lines.filter((line) => {
const content = line.split("|")[1]?.trim() || ""
return content.startsWith("//") || content.startsWith("/*") || content.startsWith("*")
})
// Comments should not be standalone definitions
expect(commentOnlyLines.length).toBe(0)
})
})
})

View file

@ -289,7 +289,11 @@ function processCaptures(captures: QueryCapture[], lines: string[], language: st
captures.sort((a, b) => a.node.startPosition.row - b.node.startPosition.row)
// Track already processed definitions to avoid duplicates
const processedDefinitions = new Map<string, { startLine: number; endLine: number; displayLine: number }>()
// Use a more comprehensive key that includes the actual content to better detect duplicates
const processedDefinitions = new Map<
string,
{ startLine: number; endLine: number; displayLine: number; priority: number }
>()
// Process captures and group by definition type and location
captures.forEach((capture) => {
@ -302,40 +306,72 @@ function processCaptures(captures: QueryCapture[], lines: string[], language: st
// For Java, skip certain captures to avoid duplication
if (language === "java") {
// Skip class body definitions as they duplicate the class declaration
if (name === "definition.class" && node.parent?.type === "class_body") {
return
}
// Skip standalone annotation type declarations
if (name.includes("definition.annotation")) {
return
}
// Skip comment definitions
if (name === "definition.comment") {
return
}
// Skip individual interface method definitions to avoid duplication
// The interface declaration already shows the interface with its methods
// Check various parent levels as the structure might vary
const parent = node.parent
const grandParent = parent?.parent
const greatGrandParent = grandParent?.parent
if (name === "definition.method" || name === "name.definition.method") {
// Check if this method is inside an interface
// Check if this is a method inside an interface body
if (name.includes("method")) {
if (
parent?.type === "interface_body" ||
grandParent?.type === "interface_body" ||
greatGrandParent?.type === "interface_body" ||
(parent?.type === "method_declaration" && grandParent?.type === "interface_body")
greatGrandParent?.type === "interface_body"
) {
// Skip interface methods as they're part of the interface declaration
return
}
}
// Handle overlapping class captures
// Skip general class definitions if we have more specific inner/nested class captures
if (name === "definition.class" || name === "name.definition.class") {
const nodeStartRow = node.startPosition.row
// Check if this class is inside another class body (making it an inner/nested class)
let currentParent = parent
while (currentParent) {
if (currentParent.type === "class_body") {
// This is a nested class, check if we have a more specific capture
const hasSpecificCapture = captures.some(
(c) =>
(c.name === "definition.inner_class" ||
c.name === "name.definition.inner_class" ||
c.name === "definition.static_nested_class" ||
c.name === "name.definition.static_nested_class") &&
Math.abs(c.node.startPosition.row - nodeStartRow) <= 1,
)
if (hasSpecificCapture) {
return // Skip this general capture in favor of the specific one
}
break
}
currentParent = currentParent.parent
}
}
// Skip duplicate inner/static nested class captures
// Keep only the most specific one
if (name === "definition.inner_class" || name === "definition.static_nested_class") {
const nodeStartRow = node.startPosition.row
// Check if we already have a class definition at this location
const hasGeneralClass = captures.some(
(c) =>
(c.name === "definition.class" || c.name === "name.definition.class") &&
Math.abs(c.node.startPosition.row - nodeStartRow) <= 1,
)
// If we have both, we'll keep this specific one and the general one will be skipped above
}
}
// Get the parent node that contains the full definition
const definitionNode = name.includes("name") ? node.parent : node
const definitionNode = name.includes("name") && node.parent ? node.parent : node
if (!definitionNode) return
// Get the start and end lines of the full definition
@ -352,46 +388,59 @@ function processCaptures(captures: QueryCapture[], lines: string[], language: st
let displayLine = startLine
if (language === "java") {
// For methods, classes, interfaces, etc. with annotations, find the actual declaration line
if (name.includes("definition")) {
for (let i = startLine; i <= endLine; i++) {
const line = lines[i].trim()
// Skip empty lines, annotations, and comments
if (
line &&
!line.startsWith("@") &&
!line.startsWith("//") &&
!line.startsWith("/*") &&
!line.startsWith("*")
) {
displayLine = i
break
}
for (let i = startLine; i <= endLine; i++) {
const line = lines[i]?.trim() || ""
// Skip empty lines, annotations, and comments
if (
line &&
!line.startsWith("@") &&
!line.startsWith("//") &&
!line.startsWith("/*") &&
!line.startsWith("*")
) {
displayLine = i
break
}
}
}
// Create a unique key for this definition
const defKey = `${definitionNode.type}-${startLine}-${endLine}`
// Check if we've already processed a definition at this location
const existing = processedDefinitions.get(defKey)
if (existing) {
// For Java, prefer method definitions over other types at the same location
if (language === "java" && name === "definition.method" && !existing.displayLine) {
// Update with the method definition which has the correct display line
processedDefinitions.set(defKey, { startLine, endLine, displayLine })
}
return
}
// Check if this is a valid component definition (not an HTML element)
const displayLineContent = lines[displayLine].trim()
const displayLineContent = lines[displayLine]?.trim() || ""
if (!isNotHtmlElement(displayLineContent)) {
return
}
// Create a unique key for this definition based on location and content
// This helps prevent duplicates when the same definition is captured multiple times
const defKey = `${startLine}-${endLine}-${displayLineContent.substring(0, 50)}`
// Assign priority based on capture type (more specific captures have higher priority)
let priority = 0
if (name.includes("inner_class") || name.includes("static_nested_class")) {
priority = 3
} else if (name.includes("method") || name.includes("constructor")) {
priority = 2
} else if (
name.includes("class") ||
name.includes("interface") ||
name.includes("enum") ||
name.includes("record")
) {
priority = 1
}
// Check if we've already processed a definition at this location
const existing = processedDefinitions.get(defKey)
if (existing) {
// Keep the capture with higher priority (more specific)
if (priority > existing.priority) {
processedDefinitions.set(defKey, { startLine, endLine, displayLine, priority })
}
return
}
// Store this definition
processedDefinitions.set(defKey, { startLine, endLine, displayLine })
processedDefinitions.set(defKey, { startLine, endLine, displayLine, priority })
})
// Generate output from processed definitions

View file

@ -42,17 +42,16 @@ export default `
(method_declaration
name: (identifier) @name.definition.method) @definition.method
; Inner class declarations
(class_declaration
(class_body
(class_declaration
name: (identifier) @name.definition.inner_class))) @definition.inner_class
; Inner class declarations (inside class body)
(class_body
(class_declaration
name: (identifier) @name.definition.inner_class)) @definition.inner_class
; Static nested class declarations
(class_declaration
(class_body
(class_declaration
name: (identifier) @name.definition.static_nested_class))) @definition.static_nested_class
; Static nested class declarations (with static modifier)
(class_body
(class_declaration
(modifiers "static")
name: (identifier) @name.definition.static_nested_class)) @definition.static_nested_class
; Lambda expressions
(lambda_expression) @definition.lambda

View file

@ -1,51 +0,0 @@
RUN v3.2.4 /data/repos/Roo-Code/src
··xx·
⎯⎯⎯⎯⎯⎯⎯ Failed Tests 2 ⎯⎯⎯⎯⎯⎯⎯
FAIL services/tree-sitter/__tests__/parseSourceCodeDefinitions.java-simple.spec.ts > Java parsing - duplication issue > should parse each method without duplication
AssertionError: expected 2 to be 1 // Object.is equality
- Expected
+ Received
- 1
+ 2
services/tree-sitter/__tests__/parseSourceCodeDefinitions.java-simple.spec.ts:63:34
61| const testMethodLines = lines.filter(line => line.includes("testMeth…
62| console.log("testMethod lines found:", testMethodLines)
63| expect(testMethodLines.length).toBe(1)
| ^
64|
65| // Check getName
⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯[1/2]⎯
FAIL services/tree-sitter/__tests__/parseSourceCodeDefinitions.java-simple.spec.ts > Java parsing - duplication issue > should show method signatures, not annotations
AssertionError: expected 3 to be +0 // Object.is equality
- Expected
+ Received
- 0
+ 3
services/tree-sitter/__tests__/parseSourceCodeDefinitions.java-simple.spec.ts:92:36
90|
91| // Should not have any lines with just @Override
92| expect(overrideOnlyLines.length).toBe(0)
| ^
93| })
94|
⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯[2/2]⎯
Test Files 1 failed (1)
Tests 2 failed | 3 passed (5)
Start at 17:39:51
Duration 870ms (transform 285ms, setup 98ms, collect 333ms, tests 83ms, environment 0ms, prepare 92ms)