Skip to content

Commit a2b0f66

Browse files
committed
feat(kotlin): 添加现代 Kotlin 语法测试
添加针对 Kotlin 1.7+ 新特性的测试用例,包括范围直到运算符(..<)、明确非空类型(T & Any)、数据对象、挂起匿名函数、值类和挂起函数类型。确保解析器能正确处理这些现代语法。
1 parent 2663d55 commit a2b0f66

File tree

3 files changed

+207
-6
lines changed

3 files changed

+207
-6
lines changed

chapi-ast-kotlin/src/main/antlr/KotlinLexer.g4

Lines changed: 4 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -68,6 +68,7 @@ MOD_ASSIGNMENT: '%=';
6868
ARROW: '->';
6969
DOUBLE_ARROW: '=>';
7070
RANGE: '..';
71+
RANGE_UNTIL: '..<';
7172
COLONCOLON: '::';
7273
DOUBLE_SEMICOLON: ';;';
7374
HASH: '#';
@@ -87,6 +88,7 @@ AS_SAFE: 'as?';
8788
EQEQ: '==';
8889
EQEQEQ: '===';
8990
SINGLE_QUOTE: '\'';
91+
AMP: '&';
9092

9193
// SECTION: keywords
9294

@@ -416,6 +418,7 @@ Inside_MOD_ASSIGNMENT: MOD_ASSIGNMENT -> type(MOD_ASSIGNMENT);
416418
Inside_ARROW: ARROW -> type(ARROW);
417419
Inside_DOUBLE_ARROW: DOUBLE_ARROW -> type(DOUBLE_ARROW);
418420
Inside_RANGE: RANGE -> type(RANGE);
421+
Inside_RANGE_UNTIL: RANGE_UNTIL -> type(RANGE_UNTIL);
419422
Inside_RESERVED: RESERVED -> type(RESERVED);
420423
Inside_COLONCOLON: COLONCOLON -> type(COLONCOLON);
421424
Inside_DOUBLE_SEMICOLON: DOUBLE_SEMICOLON -> type(DOUBLE_SEMICOLON);
@@ -440,6 +443,7 @@ Inside_AS_SAFE: AS_SAFE -> type(AS_SAFE);
440443
Inside_EQEQ: EQEQ -> type(EQEQ);
441444
Inside_EQEQEQ: EQEQEQ -> type(EQEQEQ);
442445
Inside_SINGLE_QUOTE: SINGLE_QUOTE -> type(SINGLE_QUOTE);
446+
Inside_AMP: AMP -> type(AMP);
443447
Inside_QUOTE_OPEN: QUOTE_OPEN -> pushMode(LineString), type(QUOTE_OPEN);
444448
Inside_TRIPLE_QUOTE_OPEN: TRIPLE_QUOTE_OPEN -> pushMode(MultiLineString), type(TRIPLE_QUOTE_OPEN);
445449

chapi-ast-kotlin/src/main/antlr/KotlinParser.g4

Lines changed: 12 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -91,10 +91,11 @@ delegationSpecifier
9191
| explicitDelegation
9292
| userType
9393
| functionType
94+
| SUSPEND NL* functionType
9495
;
9596

9697
constructorInvocation
97-
: userType valueArguments
98+
: userType NL* valueArguments
9899
;
99100

100101
annotatedDelegationSpecifier
@@ -139,7 +140,7 @@ anonymousInitializer
139140
;
140141

141142
companionObject
142-
: modifiers? COMPANION NL* OBJECT
143+
: modifiers? COMPANION NL* DATA? NL* OBJECT
143144
(NL* simpleIdentifier)?
144145
(NL* COLON NL* delegationSpecifiers)?
145146
(NL* classBody)?
@@ -247,7 +248,7 @@ enumEntry
247248
// SECTION: types
248249

249250
type
250-
: typeModifiers? (parenthesizedType | nullableType | typeReference | functionType)
251+
: typeModifiers? (functionType | parenthesizedType | nullableType | typeReference | definitelyNonNullableType)
251252
;
252253

253254
typeReference
@@ -306,6 +307,10 @@ parenthesizedUserType
306307
: LPAREN NL* (userType | parenthesizedUserType) NL* RPAREN
307308
;
308309

310+
definitelyNonNullableType
311+
: typeModifiers? (userType | parenthesizedUserType) NL* AMP NL* typeModifiers? (userType | parenthesizedUserType)
312+
;
313+
309314
// SECTION: statements
310315

311316
statements
@@ -412,7 +417,7 @@ infixFunctionCall
412417

413418
// range
414419
rangeExpression
415-
: additiveExpression (RANGE NL* additiveExpression)*
420+
: additiveExpression ((RANGE | RANGE_UNTIL) NL* additiveExpression)*
416421
;
417422

418423
// +
@@ -589,7 +594,8 @@ lambdaParameter
589594
;
590595

591596
anonymousFunction
592-
: FUN
597+
: SUSPEND? NL*
598+
FUN
593599
(NL* type NL* DOT)?
594600
NL* parametersWithOptionalType
595601
(NL* COLON NL* type)?
@@ -603,7 +609,7 @@ functionLiteral
603609
;
604610

605611
objectLiteral
606-
: OBJECT (NL* COLON NL* delegationSpecifiers NL*)? (NL* classBody)?
612+
: DATA? NL* OBJECT (NL* COLON NL* delegationSpecifiers NL*)? (NL* classBody)?
607613
;
608614

609615
thisExpression
Lines changed: 191 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,191 @@
1+
package chapi.ast.kotlinast
2+
3+
import org.junit.jupiter.api.Nested
4+
import org.junit.jupiter.api.Test
5+
import kotlin.test.assertEquals
6+
import kotlin.test.assertTrue
7+
8+
/**
9+
* Tests for modern Kotlin syntax features:
10+
* - Range until operator (..<) - Kotlin 1.7.20+
11+
* - Definitely non-nullable types (T & Any) - Kotlin 1.7+
12+
* - Data objects - Kotlin 1.9+
13+
* - Suspend anonymous functions
14+
*/
15+
internal class KotlinModernSyntaxTest {
16+
private fun analyse(code: String, fileName: String = "modern.kt") =
17+
KotlinAnalyser().analysis(code, fileName, AnalysisMode.Basic)
18+
19+
@Nested
20+
inner class RangeUntilOperator {
21+
@Test
22+
fun `should parse range until operator`() {
23+
val code = """
24+
package test
25+
26+
fun rangeUntilExample() {
27+
for (i in 0..<10) {
28+
println(i)
29+
}
30+
}
31+
"""
32+
val codeContainer = analyse(code)
33+
assertEquals(1, codeContainer.DataStructures.size)
34+
assertEquals("rangeUntilExample", codeContainer.DataStructures[0].Functions[0].Name)
35+
}
36+
37+
@Test
38+
fun `should parse range until in variable assignment`() {
39+
val code = """
40+
package test
41+
42+
class RangeTest {
43+
fun getRange(): IntRange {
44+
return 0..<100
45+
}
46+
}
47+
"""
48+
val codeContainer = analyse(code)
49+
assertEquals("RangeTest", codeContainer.DataStructures[0].NodeName)
50+
assertEquals("getRange", codeContainer.DataStructures[0].Functions[0].Name)
51+
}
52+
}
53+
54+
@Nested
55+
inner class DefinitelyNonNullableTypes {
56+
@Test
57+
fun `should parse definitely non-nullable type`() {
58+
val code = """
59+
package test
60+
61+
fun <T> elvisLike(x: T, y: T & Any): T & Any = x ?: y
62+
"""
63+
val codeContainer = analyse(code)
64+
assertEquals(1, codeContainer.DataStructures.size)
65+
assertEquals("elvisLike", codeContainer.DataStructures[0].Functions[0].Name)
66+
}
67+
68+
@Test
69+
fun `should parse definitely non-nullable type in class`() {
70+
val code = """
71+
package test
72+
73+
class NonNullableProcessor<T> {
74+
fun process(value: T & Any): T & Any {
75+
return value
76+
}
77+
}
78+
"""
79+
val codeContainer = analyse(code)
80+
assertEquals("NonNullableProcessor", codeContainer.DataStructures[0].NodeName)
81+
}
82+
}
83+
84+
@Nested
85+
inner class DataObjects {
86+
@Test
87+
fun `should parse data object declaration`() {
88+
val code = """
89+
package test
90+
91+
data object MySingleton {
92+
val name = "Singleton"
93+
}
94+
"""
95+
val codeContainer = analyse(code)
96+
assertEquals(1, codeContainer.DataStructures.size)
97+
assertEquals("MySingleton", codeContainer.DataStructures[0].NodeName)
98+
}
99+
100+
@Test
101+
fun `should parse data object in sealed hierarchy`() {
102+
val code = """
103+
package test
104+
105+
sealed interface Status
106+
107+
data object Loading : Status
108+
data class Success(val data: String) : Status
109+
data object Error : Status
110+
"""
111+
val codeContainer = analyse(code)
112+
assertTrue(codeContainer.DataStructures.size >= 1)
113+
// Should contain Status, Loading, Success, and Error
114+
val nodeNames = codeContainer.DataStructures.map { it.NodeName }
115+
assertTrue(nodeNames.contains("Status"))
116+
}
117+
}
118+
119+
@Nested
120+
inner class SuspendAnonymousFunctions {
121+
@Test
122+
fun `should parse suspend anonymous function`() {
123+
val code = """
124+
package test
125+
126+
import kotlinx.coroutines.delay
127+
128+
class CoroutineExample {
129+
val suspendFunc = suspend fun() {
130+
delay(1000)
131+
}
132+
}
133+
"""
134+
val codeContainer = analyse(code)
135+
assertEquals("CoroutineExample", codeContainer.DataStructures[0].NodeName)
136+
}
137+
138+
@Test
139+
fun `should parse suspend anonymous function with receiver`() {
140+
val code = """
141+
package test
142+
143+
class SuspendBuilder {
144+
val block = suspend fun String.(): Int {
145+
return this.length
146+
}
147+
}
148+
"""
149+
val codeContainer = analyse(code)
150+
assertEquals("SuspendBuilder", codeContainer.DataStructures[0].NodeName)
151+
}
152+
}
153+
154+
@Nested
155+
inner class ValueClasses {
156+
@Test
157+
fun `should parse value class`() {
158+
val code = """
159+
package test
160+
161+
@JvmInline
162+
value class Password(private val s: String)
163+
"""
164+
val codeContainer = analyse(code)
165+
assertEquals(1, codeContainer.DataStructures.size)
166+
assertEquals("Password", codeContainer.DataStructures[0].NodeName)
167+
}
168+
}
169+
170+
@Nested
171+
inner class SuspendFunctionTypes {
172+
@Test
173+
fun `should parse suspend function type in delegation`() {
174+
val code = """
175+
package test
176+
177+
interface SuspendAction {
178+
suspend fun execute()
179+
}
180+
181+
class SuspendActionImpl : SuspendAction {
182+
override suspend fun execute() {
183+
println("Executing")
184+
}
185+
}
186+
"""
187+
val codeContainer = analyse(code)
188+
assertTrue(codeContainer.DataStructures.size >= 1)
189+
}
190+
}
191+
}

0 commit comments

Comments
 (0)