Skip to content

Commit 1692103

Browse files
committed
test(parser): add tests for fragmented Kotlin class parsing
Add JVM tests to verify correct parsing of fragmented Kotlin class declarations and improve coverage for DocQLExecutor and markdown document scenarios.
1 parent 255c845 commit 1692103

File tree

2 files changed

+497
-0
lines changed

2 files changed

+497
-0
lines changed
Lines changed: 159 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,159 @@
1+
package cc.unitmesh.codegraph.parser.jvm
2+
3+
import cc.unitmesh.codegraph.model.CodeElementType
4+
import cc.unitmesh.codegraph.parser.Language
5+
import kotlinx.coroutines.runBlocking
6+
import org.junit.Test
7+
import kotlin.test.assertEquals
8+
import kotlin.test.assertTrue
9+
10+
/**
11+
* Tests for handling fragmented class declarations in Kotlin.
12+
*
13+
* When Tree-sitter fails to parse large Kotlin files correctly, it may output
14+
* class declarations as separate nodes (class keyword + identifier + constructor)
15+
* instead of a proper class_declaration node. These tests verify that our parser
16+
* correctly reconstructs such fragmented class declarations.
17+
*/
18+
class FragmentedClassParsingTest {
19+
20+
private val parser = JvmCodeParser()
21+
22+
@Test
23+
fun `should parse simple data class`() = runBlocking {
24+
val sourceCode = """
25+
package cc.unitmesh.test
26+
27+
data class FileInfo(
28+
val path: String,
29+
val name: String = ""
30+
)
31+
""".trimIndent()
32+
33+
val nodes = parser.parseNodes(sourceCode, "test.kt", Language.KOTLIN)
34+
35+
val classes = nodes.filter { it.type == CodeElementType.CLASS }
36+
assertEquals(1, classes.size, "Should find exactly 1 class")
37+
assertEquals("FileInfo", classes[0].name)
38+
}
39+
40+
@Test
41+
fun `should parse normal class with constructor`() = runBlocking {
42+
val sourceCode = """
43+
package cc.unitmesh.test
44+
45+
class DocQLExecutor(
46+
private val documentFile: String?,
47+
private val parserService: String?
48+
) {
49+
fun execute(): String {
50+
return "hello"
51+
}
52+
}
53+
""".trimIndent()
54+
55+
val nodes = parser.parseNodes(sourceCode, "test.kt", Language.KOTLIN)
56+
57+
val classes = nodes.filter { it.type == CodeElementType.CLASS }
58+
assertEquals(1, classes.size, "Should find exactly 1 class")
59+
assertEquals("DocQLExecutor", classes[0].name)
60+
61+
val methods = nodes.filter { it.type == CodeElementType.METHOD }
62+
assertEquals(1, methods.size, "Should find exactly 1 method")
63+
assertEquals("execute", methods[0].name)
64+
}
65+
66+
@Test
67+
fun `should parse multiple classes in same file`() = runBlocking {
68+
val sourceCode = """
69+
package cc.unitmesh.test
70+
71+
data class FileInfo(val path: String)
72+
73+
data class CodeBlock(val language: String?)
74+
75+
class DocQLExecutor(private val file: String?) {
76+
fun execute(): String = "hello"
77+
}
78+
""".trimIndent()
79+
80+
val nodes = parser.parseNodes(sourceCode, "test.kt", Language.KOTLIN)
81+
82+
val classes = nodes.filter { it.type == CodeElementType.CLASS }
83+
assertEquals(3, classes.size, "Should find 3 classes")
84+
85+
val classNames = classes.map { it.name }.toSet()
86+
assertTrue(classNames.contains("FileInfo"), "Should find FileInfo class")
87+
assertTrue(classNames.contains("CodeBlock"), "Should find CodeBlock class")
88+
assertTrue(classNames.contains("DocQLExecutor"), "Should find DocQLExecutor class")
89+
}
90+
91+
@Test
92+
fun `should parse actual DocQLExecutor file from project`() = runBlocking {
93+
// Read actual file from project to test real-world scenario
94+
val file = java.io.File("/Volumes/source/ai/autocrud/mpp-core/src/commonMain/kotlin/cc/unitmesh/devins/document/docql/DocQLExecutor.kt")
95+
if (!file.exists()) {
96+
println("Skipping test: DocQLExecutor.kt not found at expected path")
97+
return@runBlocking
98+
}
99+
100+
val sourceCode = file.readText()
101+
val nodes = parser.parseNodes(sourceCode, "DocQLExecutor.kt", Language.KOTLIN)
102+
103+
val classes = nodes.filter { it.type == CodeElementType.CLASS }
104+
105+
// Should find at least these classes
106+
val expectedClasses = setOf("DocQLExecutor", "FileInfo", "CodeBlock", "TableBlock")
107+
val actualClassNames = classes.map { it.name }.toSet()
108+
109+
println("Found classes: $actualClassNames")
110+
111+
for (expected in expectedClasses) {
112+
assertTrue(
113+
actualClassNames.contains(expected),
114+
"Should find $expected class, but found: $actualClassNames"
115+
)
116+
}
117+
}
118+
119+
@Test
120+
fun `should not create duplicate class nodes`() = runBlocking {
121+
val sourceCode = """
122+
package cc.unitmesh.test
123+
124+
class DocQLExecutor(private val file: String?) {
125+
fun execute(): String = "hello"
126+
}
127+
""".trimIndent()
128+
129+
val nodes = parser.parseNodes(sourceCode, "test.kt", Language.KOTLIN)
130+
131+
val classes = nodes.filter { it.type == CodeElementType.CLASS }
132+
assertEquals(1, classes.size, "Should find exactly 1 class, not duplicates")
133+
assertEquals("DocQLExecutor", classes[0].name)
134+
135+
// Ensure no 'unknown' class nodes
136+
val unknownClasses = classes.filter { it.name == "unknown" }
137+
assertEquals(0, unknownClasses.size, "Should not have any 'unknown' class nodes")
138+
}
139+
140+
@Test
141+
fun `should correctly identify class line numbers`() = runBlocking {
142+
val sourceCode = """
143+
package cc.unitmesh.test
144+
145+
// Line 3: comment
146+
// Line 4: another comment
147+
class MyClass(val name: String) { // Line 5
148+
fun method() {} // Line 6
149+
} // Line 7
150+
""".trimIndent()
151+
152+
val nodes = parser.parseNodes(sourceCode, "test.kt", Language.KOTLIN)
153+
154+
val myClass = nodes.find { it.type == CodeElementType.CLASS && it.name == "MyClass" }
155+
assertTrue(myClass != null, "Should find MyClass")
156+
assertEquals(5, myClass!!.startLine, "MyClass should start at line 5")
157+
}
158+
}
159+

0 commit comments

Comments
 (0)