Skip to content

Commit 0b1b51c

Browse files
committed
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
1 parent 7f0f2c8 commit 0b1b51c

File tree

2 files changed

+164
-26
lines changed

2 files changed

+164
-26
lines changed
Lines changed: 133 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,133 @@
1+
import { describe, it, expect } from "vitest"
2+
import { testParseSourceCodeDefinitions } from "./helpers"
3+
import { javaQuery } from "../queries"
4+
5+
describe("Java interface methods and annotations", () => {
6+
it("should correctly parse interface methods", async () => {
7+
const interfaceContent = `interface TestInterface {
8+
/**
9+
* This is a test method
10+
*/
11+
void testMethod();
12+
13+
String getName();
14+
15+
int calculate(int a, int b);
16+
}`
17+
18+
const testOptions = {
19+
language: "java",
20+
wasmFile: "tree-sitter-java.wasm",
21+
queryString: javaQuery,
22+
extKey: "java",
23+
}
24+
25+
const parseResult = await testParseSourceCodeDefinitions(
26+
"/test/TestInterface.java",
27+
interfaceContent,
28+
testOptions,
29+
)
30+
31+
console.log("\n=== INTERFACE PARSE RESULT ===")
32+
console.log(parseResult)
33+
console.log("==============================\n")
34+
35+
// Interface methods should be detected
36+
expect(parseResult).toBeTruthy()
37+
38+
// Force test to fail to see output
39+
if (!parseResult) {
40+
throw new Error("No parse result for interface")
41+
}
42+
if (!parseResult.includes("testMethod")) {
43+
throw new Error(`Interface methods not detected. Result:\n${parseResult}`)
44+
}
45+
expect(parseResult).toContain("testMethod")
46+
expect(parseResult).toContain("getName")
47+
expect(parseResult).toContain("calculate")
48+
})
49+
50+
it("should correctly handle multiple annotations on methods", async () => {
51+
const classContent = `class TestClass implements TestInterface {
52+
53+
@Override
54+
@Test
55+
public void testMethod() {
56+
// Implementation goes here
57+
}
58+
59+
@Override
60+
public String getName() {
61+
return "TestClass";
62+
}
63+
64+
@Override
65+
@Deprecated
66+
public int calculate(int a, int b) {
67+
return a + b;
68+
}
69+
70+
@SuppressWarnings("unchecked")
71+
private void helperMethod() {
72+
// Helper implementation
73+
}
74+
}`
75+
76+
const testOptions = {
77+
language: "java",
78+
wasmFile: "tree-sitter-java.wasm",
79+
queryString: javaQuery,
80+
extKey: "java",
81+
}
82+
83+
const parseResult = await testParseSourceCodeDefinitions("/test/TestClass.java", classContent, testOptions)
84+
85+
console.log("\n=== CLASS PARSE RESULT ===")
86+
console.log(parseResult)
87+
console.log("==========================\n")
88+
89+
if (parseResult) {
90+
const lines = parseResult.split("\n").filter((line) => line.trim())
91+
92+
// Check that method names are shown, not annotations
93+
const hasMethodNames = lines.some((line) => line.includes("testMethod"))
94+
const hasGetName = lines.some((line) => line.includes("getName"))
95+
const hasCalculate = lines.some((line) => line.includes("calculate"))
96+
const hasHelper = lines.some((line) => line.includes("helperMethod"))
97+
98+
// Check for the bug: annotations shown as method names
99+
const hasStandaloneOverride = lines.some(
100+
(line) =>
101+
line.includes("@Override") &&
102+
!line.includes("testMethod") &&
103+
!line.includes("getName") &&
104+
!line.includes("calculate"),
105+
)
106+
const hasStandaloneTest = lines.some((line) => line.includes("@Test") && !line.includes("testMethod"))
107+
const hasStandaloneDeprecated = lines.some(
108+
(line) => line.includes("@Deprecated") && !line.includes("calculate"),
109+
)
110+
111+
console.log("Method detection:")
112+
console.log(" testMethod:", hasMethodNames)
113+
console.log(" getName:", hasGetName)
114+
console.log(" calculate:", hasCalculate)
115+
console.log(" helperMethod:", hasHelper)
116+
console.log("\nAnnotation issues:")
117+
console.log(" Standalone @Override:", hasStandaloneOverride)
118+
console.log(" Standalone @Test:", hasStandaloneTest)
119+
console.log(" Standalone @Deprecated:", hasStandaloneDeprecated)
120+
121+
// All methods should be detected
122+
expect(hasMethodNames).toBe(true)
123+
expect(hasGetName).toBe(true)
124+
expect(hasCalculate).toBe(true)
125+
expect(hasHelper).toBe(true)
126+
127+
// Annotations should not appear as standalone method names
128+
expect(hasStandaloneOverride).toBe(false)
129+
expect(hasStandaloneTest).toBe(false)
130+
expect(hasStandaloneDeprecated).toBe(false)
131+
}
132+
})
133+
})

src/services/tree-sitter/index.ts

Lines changed: 31 additions & 26 deletions
Original file line numberDiff line numberDiff line change
@@ -339,40 +339,45 @@ function processCaptures(captures: QueryCapture[], lines: string[], language: st
339339
}
340340
// For other component definitions
341341
else if (isNotHtmlElement(startLineContent)) {
342-
// For Java, special handling to avoid showing @Override as a separate line
343-
// when it's part of a method declaration
342+
// For Java, special handling for methods with annotations
344343
if (language === "java" && name === "definition.method") {
345-
// Check if the method has an annotation like @Override
346-
const methodText = definitionNode.text
347-
if (methodText.includes("@Override")) {
348-
// Find the actual method declaration line (not the annotation line)
349-
let methodDeclarationLine = startLine
350-
for (let i = startLine; i <= endLine; i++) {
351-
if (
352-
lines[i].includes("public") ||
353-
lines[i].includes("private") ||
354-
lines[i].includes("protected") ||
355-
lines[i].includes("void") ||
356-
lines[i].includes("static")
357-
) {
358-
methodDeclarationLine = i
359-
break
360-
}
344+
// Find the actual method declaration line (skip annotation lines)
345+
let methodDeclarationLine = startLine
346+
for (let i = startLine; i <= endLine; i++) {
347+
const line = lines[i].trim()
348+
// Skip empty lines and annotation lines (lines starting with @)
349+
if (line && !line.startsWith("@") && !line.startsWith("//") && !line.startsWith("/*")) {
350+
methodDeclarationLine = i
351+
break
361352
}
362-
// Output the method with its proper line range, but show the method declaration line
363-
formattedOutput += `${startLine + 1}--${endLine + 1} | ${lines[methodDeclarationLine]}\n`
364-
processedLines.add(lineKey)
365-
} else {
366-
// Normal method without annotations
367-
formattedOutput += `${startLine + 1}--${endLine + 1} | ${lines[startLine]}\n`
368-
processedLines.add(lineKey)
369353
}
354+
// Output the method with its proper line range, showing the method declaration line
355+
formattedOutput += `${startLine + 1}--${endLine + 1} | ${lines[methodDeclarationLine]}\n`
356+
processedLines.add(lineKey)
370357
} else if (language === "java" && name === "definition.class") {
371358
// For Java classes, skip the entire class definition to avoid duplication
372359
// The class name will be handled by name.definition.class
373360
return
361+
} else if (language === "java" && name.includes("definition.annotation")) {
362+
// Skip standalone annotation definitions - they're not useful for code structure overview
363+
// Annotations will be shown as part of the methods/fields they annotate
364+
return
374365
} else {
375-
formattedOutput += `${startLine + 1}--${endLine + 1} | ${lines[startLine]}\n`
366+
// For Java, check if this line is just an annotation
367+
if (language === "java" && lines[startLine].trim().startsWith("@")) {
368+
// Find the next non-annotation line
369+
let actualDefinitionLine = startLine
370+
for (let i = startLine + 1; i <= endLine; i++) {
371+
const line = lines[i].trim()
372+
if (line && !line.startsWith("@")) {
373+
actualDefinitionLine = i
374+
break
375+
}
376+
}
377+
formattedOutput += `${startLine + 1}--${endLine + 1} | ${lines[actualDefinitionLine]}\n`
378+
} else {
379+
formattedOutput += `${startLine + 1}--${endLine + 1} | ${lines[startLine]}\n`
380+
}
376381
processedLines.add(lineKey)
377382
}
378383

0 commit comments

Comments
 (0)