Skip to content

Commit 64864d3

Browse files
committed
feat(dart): add Dart 3 class modifiers support
Add support for Dart 3 class modifiers: - sealed class - base class - interface class - final class - mixin class - base mixin - sealed mixin Update grammar to recognize new keywords (sealed, base, when). Store class modifiers as annotations for downstream processing. Add comprehensive tests for all Dart 3 class modifier combinations.
1 parent 8e66ed3 commit 64864d3

4 files changed

Lines changed: 171 additions & 4 deletions

File tree

chapi-ast-dart/src/main/antlr/Dart2Lexer.g4

Lines changed: 4 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -140,6 +140,10 @@ PART_ : 'part';
140140
REQUIRED_ : 'required';
141141
RETHROW_ : 'rethrow';
142142
RETURN_ : 'return';
143+
// Dart 3 class modifiers
144+
SEALED_ : 'sealed';
145+
BASE_ : 'base';
146+
WHEN_ : 'when';
143147
SET_ : 'set';
144148
SHOW_ : 'show';
145149
STATIC_ : 'static';

chapi-ast-dart/src/main/antlr/Dart2Parser.g4

Lines changed: 14 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -163,11 +163,21 @@ catchPart
163163
: CATCH_ OP identifier (C identifier)? CP
164164
;
165165

166+
// Dart 3 class modifiers: sealed, base, interface, final, mixin
167+
classModifier
168+
: ABSTRACT_
169+
| SEALED_
170+
| BASE_
171+
| INTERFACE_
172+
| FINAL_
173+
| MIXIN_
174+
;
175+
166176
classDeclaration
167-
: ABSTRACT_? CLASS_ typeIdentifier typeParameters? superclass? interfaces? OBC (
177+
: classModifier* CLASS_ typeIdentifier typeParameters? superclass? interfaces? OBC (
168178
metadata classMemberDeclaration
169179
)* CBC
170-
| ABSTRACT_? CLASS_ mixinApplicationClass
180+
| classModifier* CLASS_ mixinApplicationClass
171181
;
172182

173183
classMemberDeclaration
@@ -642,8 +652,9 @@ mixinApplicationClass
642652
: identifier typeParameters? EQ mixinApplication SC
643653
;
644654

655+
// Dart 3: base mixin, sealed mixin
645656
mixinDeclaration
646-
: MIXIN_ typeIdentifier typeParameters? (ON_ typeNotVoidList)? interfaces? OBC (
657+
: (BASE_ | SEALED_)? MIXIN_ typeIdentifier typeParameters? (ON_ typeNotVoidList)? interfaces? OBC (
647658
metadata classMemberDeclaration
648659
)* CBC
649660
;

chapi-ast-dart/src/main/kotlin/chapi/ast/dartast/DartBasicIdentListener.kt

Lines changed: 47 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -78,12 +78,46 @@ open class DartBasicIdentListener(private val filePath: String) : DartAstListene
7878
hasEnterClass = true
7979

8080
val typeId = ctx?.typeIdentifier() ?: return
81+
82+
// Determine class type based on modifiers (Dart 3 support)
83+
val modifiers = ctx.classModifier()
84+
val isAbstract = modifiers?.any { it.ABSTRACT_() != null } == true
85+
val isSealed = modifiers?.any { it.SEALED_() != null } == true
86+
val isBase = modifiers?.any { it.BASE_() != null } == true
87+
val isInterface = modifiers?.any { it.INTERFACE_() != null } == true
88+
val isFinal = modifiers?.any { it.FINAL_() != null } == true
89+
val isMixinClass = modifiers?.any { it.MIXIN_() != null } == true
90+
91+
val classType = when {
92+
isSealed -> DataStructType.CLASS // sealed classes are still classes
93+
isAbstract -> DataStructType.ABSTRACT_CLASS
94+
isInterface -> DataStructType.INTERFACE
95+
isMixinClass -> DataStructType.TRAIT // mixin class is similar to trait
96+
else -> DataStructType.CLASS
97+
}
98+
8199
currentNode = CodeDataStruct(
82100
NodeName = getTypeIdentifierText(typeId),
83-
Type = if (ctx.ABSTRACT_() != null) DataStructType.ABSTRACT_CLASS else DataStructType.CLASS,
101+
Type = classType,
84102
Package = codeContainer.PackageName,
85103
FilePath = filePath
86104
)
105+
106+
// Store class modifiers as annotations for additional context
107+
val modifierList = mutableListOf<String>()
108+
if (isSealed) modifierList.add("sealed")
109+
if (isBase) modifierList.add("base")
110+
if (isInterface) modifierList.add("interface")
111+
if (isFinal) modifierList.add("final")
112+
if (isAbstract) modifierList.add("abstract")
113+
if (isMixinClass) modifierList.add("mixin")
114+
115+
if (modifierList.isNotEmpty()) {
116+
currentNode.Annotations = modifierList.map {
117+
CodeAnnotation(Name = it)
118+
}
119+
}
120+
87121
currentNode.Position = buildPosition(ctx)
88122

89123
// Handle extends
@@ -116,12 +150,24 @@ open class DartBasicIdentListener(private val filePath: String) : DartAstListene
116150
hasEnterClass = true
117151

118152
val typeId = ctx?.typeIdentifier() ?: return
153+
154+
// Check for Dart 3 mixin modifiers (base, sealed)
155+
val isBase = ctx.BASE_() != null
156+
val isSealed = ctx.SEALED_() != null
157+
119158
currentNode = CodeDataStruct(
120159
NodeName = getTypeIdentifierText(typeId),
121160
Type = DataStructType.TRAIT, // Mixin is similar to trait
122161
Package = codeContainer.PackageName,
123162
FilePath = filePath
124163
)
164+
165+
// Store mixin modifiers
166+
val modifierList = mutableListOf<String>("mixin")
167+
if (isBase) modifierList.add("base")
168+
if (isSealed) modifierList.add("sealed")
169+
currentNode.Annotations = modifierList.map { CodeAnnotation(Name = it) }
170+
125171
currentNode.Position = buildPosition(ctx)
126172

127173
// Handle 'on' constraints (superclass constraints)

chapi-ast-dart/src/test/kotlin/chapi/ast/dartast/DartAnalyserTest.kt

Lines changed: 106 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -307,4 +307,110 @@ internal class DartAnalyserTest {
307307
assertEquals(1, codeContainer.DataStructures.size)
308308
assertEquals("Simple", codeContainer.DataStructures[0].NodeName)
309309
}
310+
311+
// Dart 3 tests
312+
@Test
313+
fun shouldParseSealedClass() {
314+
val code = """
315+
sealed class Shape {}
316+
317+
class Circle extends Shape {
318+
double radius;
319+
}
320+
321+
class Square extends Shape {
322+
double side;
323+
}
324+
""".trimIndent()
325+
326+
val codeContainer = DartAnalyser().analysis(code, "sealed.dart")
327+
328+
assertEquals(3, codeContainer.DataStructures.size)
329+
val shapeClass = codeContainer.DataStructures[0]
330+
assertEquals("Shape", shapeClass.NodeName)
331+
assertTrue(shapeClass.Annotations.any { it.Name == "sealed" })
332+
}
333+
334+
@Test
335+
fun shouldParseBaseClass() {
336+
val code = """
337+
base class Vehicle {
338+
void move() {}
339+
}
340+
""".trimIndent()
341+
342+
val codeContainer = DartAnalyser().analysis(code, "base.dart")
343+
344+
assertEquals(1, codeContainer.DataStructures.size)
345+
val vehicleClass = codeContainer.DataStructures[0]
346+
assertEquals("Vehicle", vehicleClass.NodeName)
347+
assertTrue(vehicleClass.Annotations.any { it.Name == "base" })
348+
}
349+
350+
@Test
351+
fun shouldParseInterfaceClass() {
352+
val code = """
353+
interface class Flyable {
354+
void fly();
355+
}
356+
""".trimIndent()
357+
358+
val codeContainer = DartAnalyser().analysis(code, "interface.dart")
359+
360+
assertEquals(1, codeContainer.DataStructures.size)
361+
val flyableClass = codeContainer.DataStructures[0]
362+
assertEquals("Flyable", flyableClass.NodeName)
363+
assertEquals(DataStructType.INTERFACE, flyableClass.Type)
364+
}
365+
366+
@Test
367+
fun shouldParseFinalClass() {
368+
val code = """
369+
final class Config {
370+
final String name;
371+
}
372+
""".trimIndent()
373+
374+
val codeContainer = DartAnalyser().analysis(code, "final.dart")
375+
376+
assertEquals(1, codeContainer.DataStructures.size)
377+
val configClass = codeContainer.DataStructures[0]
378+
assertEquals("Config", configClass.NodeName)
379+
assertTrue(configClass.Annotations.any { it.Name == "final" })
380+
}
381+
382+
@Test
383+
fun shouldParseMixinClass() {
384+
val code = """
385+
mixin class Both {
386+
void doSomething() {}
387+
}
388+
""".trimIndent()
389+
390+
val codeContainer = DartAnalyser().analysis(code, "mixinclass.dart")
391+
392+
assertEquals(1, codeContainer.DataStructures.size)
393+
val bothClass = codeContainer.DataStructures[0]
394+
assertEquals("Both", bothClass.NodeName)
395+
assertEquals(DataStructType.TRAIT, bothClass.Type)
396+
}
397+
398+
@Test
399+
fun shouldParseBaseMixin() {
400+
val code = """
401+
base mixin Logger {
402+
void log(String message) {
403+
print(message);
404+
}
405+
}
406+
""".trimIndent()
407+
408+
val codeContainer = DartAnalyser().analysis(code, "basemixin.dart")
409+
410+
assertEquals(1, codeContainer.DataStructures.size)
411+
val loggerMixin = codeContainer.DataStructures[0]
412+
assertEquals("Logger", loggerMixin.NodeName)
413+
assertEquals(DataStructType.TRAIT, loggerMixin.Type)
414+
assertTrue(loggerMixin.Annotations.any { it.Name == "base" })
415+
}
310416
}

0 commit comments

Comments
 (0)