fix: improve Java annotation handling in listCodeDefinitionNames

- Made annotation detection more generic (not hard-coded for specific annotations)
- Skip lines starting with @ when looking for method signatures
- Filter out standalone annotation definitions
- Handles any number of annotations on methods/fields dynamically

Fixes #7330
This commit is contained in:
Roo Code 2025-08-22 18:37:24 +00:00
parent 7f0f2c83b2
commit 0b1b51c226
2 changed files with 164 additions and 26 deletions

View file

@ -0,0 +1,133 @@
import { describe, it, expect } from "vitest"
import { testParseSourceCodeDefinitions } from "./helpers"
import { javaQuery } from "../queries"
describe("Java interface methods and annotations", () => {
it("should correctly parse interface methods", async () => {
const interfaceContent = `interface TestInterface {
/**
* This is a test method
*/
void testMethod();
String getName();
int calculate(int a, int b);
}`
const testOptions = {
language: "java",
wasmFile: "tree-sitter-java.wasm",
queryString: javaQuery,
extKey: "java",
}
const parseResult = await testParseSourceCodeDefinitions(
"/test/TestInterface.java",
interfaceContent,
testOptions,
)
console.log("\n=== INTERFACE PARSE RESULT ===")
console.log(parseResult)
console.log("==============================\n")
// Interface methods should be detected
expect(parseResult).toBeTruthy()
// Force test to fail to see output
if (!parseResult) {
throw new Error("No parse result for interface")
}
if (!parseResult.includes("testMethod")) {
throw new Error(`Interface methods not detected. Result:\n${parseResult}`)
}
expect(parseResult).toContain("testMethod")
expect(parseResult).toContain("getName")
expect(parseResult).toContain("calculate")
})
it("should correctly handle multiple annotations on methods", async () => {
const classContent = `class TestClass implements TestInterface {
@Override
@Test
public void testMethod() {
// Implementation goes here
}
@Override
public String getName() {
return "TestClass";
}
@Override
@Deprecated
public int calculate(int a, int b) {
return a + b;
}
@SuppressWarnings("unchecked")
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", classContent, testOptions)
console.log("\n=== CLASS PARSE RESULT ===")
console.log(parseResult)
console.log("==========================\n")
if (parseResult) {
const lines = parseResult.split("\n").filter((line) => line.trim())
// Check that method names are shown, not annotations
const hasMethodNames = lines.some((line) => line.includes("testMethod"))
const hasGetName = lines.some((line) => line.includes("getName"))
const hasCalculate = lines.some((line) => line.includes("calculate"))
const hasHelper = lines.some((line) => line.includes("helperMethod"))
// Check for the bug: annotations shown as method names
const hasStandaloneOverride = lines.some(
(line) =>
line.includes("@Override") &&
!line.includes("testMethod") &&
!line.includes("getName") &&
!line.includes("calculate"),
)
const hasStandaloneTest = lines.some((line) => line.includes("@Test") && !line.includes("testMethod"))
const hasStandaloneDeprecated = lines.some(
(line) => line.includes("@Deprecated") && !line.includes("calculate"),
)
console.log("Method detection:")
console.log(" testMethod:", hasMethodNames)
console.log(" getName:", hasGetName)
console.log(" calculate:", hasCalculate)
console.log(" helperMethod:", hasHelper)
console.log("\nAnnotation issues:")
console.log(" Standalone @Override:", hasStandaloneOverride)
console.log(" Standalone @Test:", hasStandaloneTest)
console.log(" Standalone @Deprecated:", hasStandaloneDeprecated)
// All methods should be detected
expect(hasMethodNames).toBe(true)
expect(hasGetName).toBe(true)
expect(hasCalculate).toBe(true)
expect(hasHelper).toBe(true)
// Annotations should not appear as standalone method names
expect(hasStandaloneOverride).toBe(false)
expect(hasStandaloneTest).toBe(false)
expect(hasStandaloneDeprecated).toBe(false)
}
})
})

View file

@ -339,40 +339,45 @@ function processCaptures(captures: QueryCapture[], lines: string[], language: st
}
// For other component definitions
else if (isNotHtmlElement(startLineContent)) {
// For Java, special handling to avoid showing @Override as a separate line
// when it's part of a method declaration
// For Java, special handling for methods with annotations
if (language === "java" && name === "definition.method") {
// Check if the method has an annotation like @Override
const methodText = definitionNode.text
if (methodText.includes("@Override")) {
// Find the actual method declaration line (not the annotation line)
let methodDeclarationLine = startLine
for (let i = startLine; i <= endLine; i++) {
if (
lines[i].includes("public") ||
lines[i].includes("private") ||
lines[i].includes("protected") ||
lines[i].includes("void") ||
lines[i].includes("static")
) {
methodDeclarationLine = i
break
}
// Find the actual method declaration line (skip annotation lines)
let methodDeclarationLine = startLine
for (let i = startLine; i <= endLine; i++) {
const line = lines[i].trim()
// Skip empty lines and annotation lines (lines starting with @)
if (line && !line.startsWith("@") && !line.startsWith("//") && !line.startsWith("/*")) {
methodDeclarationLine = i
break
}
// Output the method with its proper line range, but show the method declaration line
formattedOutput += `${startLine + 1}--${endLine + 1} | ${lines[methodDeclarationLine]}\n`
processedLines.add(lineKey)
} else {
// Normal method without annotations
formattedOutput += `${startLine + 1}--${endLine + 1} | ${lines[startLine]}\n`
processedLines.add(lineKey)
}
// Output the method with its proper line range, showing the method declaration line
formattedOutput += `${startLine + 1}--${endLine + 1} | ${lines[methodDeclarationLine]}\n`
processedLines.add(lineKey)
} else if (language === "java" && name === "definition.class") {
// For Java classes, skip the entire class definition to avoid duplication
// The class name will be handled by name.definition.class
return
} else if (language === "java" && name.includes("definition.annotation")) {
// Skip standalone annotation definitions - they're not useful for code structure overview
// Annotations will be shown as part of the methods/fields they annotate
return
} else {
formattedOutput += `${startLine + 1}--${endLine + 1} | ${lines[startLine]}\n`
// For Java, check if this line is just an annotation
if (language === "java" && lines[startLine].trim().startsWith("@")) {
// Find the next non-annotation line
let actualDefinitionLine = startLine
for (let i = startLine + 1; i <= endLine; i++) {
const line = lines[i].trim()
if (line && !line.startsWith("@")) {
actualDefinitionLine = i
break
}
}
formattedOutput += `${startLine + 1}--${endLine + 1} | ${lines[actualDefinitionLine]}\n`
} else {
formattedOutput += `${startLine + 1}--${endLine + 1} | ${lines[startLine]}\n`
}
processedLines.add(lineKey)
}