Skip to content

Commit d61af93

Browse files
committed
fix(dart): address code review feedback and improve test coverage
Fixes based on CodeRabbit review: - Fix build.gradle.kts: use srcDir() instead of srcDirs() to append generated sources without replacing src/main/java - Fix grammar typo: "gramamr" -> "grammar" - Remove duplicate FUNCTION_ and DYNAMIC_ in identifier rule - Remove unused WHEN_ keyword from lexer - Add BASE_ and SEALED_ to identifier and typeIdentifier rules - Implement addFieldToCurrentClass in DartFullIdentListener - Fix enterLibraryExport to actually add exports to TopLevel - Make currentNode and hasEnterClass protected for subclass access - Fix function body scope tracking (isInFunction in enterFunctionBody) Test improvements (36 tests now): - Add test for exports parsing - Add test for class fields - Add test for method parameters - Add test for typedef - Add test for operator overloading - Add test for mixin constraints - Add test for class with mixins - Add test for import hide - Add test for static methods - Add test for async methods - Add test for generic class - Add test for anonymous extension - Add test for top-level getter/setter - Fix shouldParseComplexFile to use assertNotNull instead of silent return
1 parent ab29872 commit d61af93

6 files changed

Lines changed: 368 additions & 81 deletions

File tree

chapi-ast-dart/build.gradle.kts

Lines changed: 2 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -35,7 +35,8 @@ dependencies {
3535
}
3636

3737
sourceSets.main {
38-
java.srcDirs("${project.layout.buildDirectory.get().asFile}/generated-src")
38+
// Append generated-src to existing source directories (don't replace src/main/java)
39+
java.srcDir("${project.layout.buildDirectory.get().asFile}/generated-src")
3940
}
4041

4142
tasks.generateGrammarSource {

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

Lines changed: 1 addition & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -8,7 +8,7 @@
88
* This grammar is generated from the CFG contained in:
99
* https://github.com/dart-lang/language/blob/70eb85cf9a6606a9da0de824a5d55fd06de1287f/specification/dartLangSpec.tex
1010
*
11-
* The bash script used to scrape and the refactor the gramamr is here:
11+
* The bash script used to scrape and the refactor the grammar is here:
1212
* https://github.com/kaby76/ScrapeDartSpec/blob/master/refactor.sh
1313
*
1414
* Note: the CFG in the Specification is in development, and is for approximately
@@ -143,7 +143,6 @@ RETURN_ : 'return';
143143
// Dart 3 class modifiers
144144
SEALED_ : 'sealed';
145145
BASE_ : 'base';
146-
WHEN_ : 'when';
147146
SET_ : 'set';
148147
SHOW_ : 'show';
149148
STATIC_ : 'static';

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

Lines changed: 5 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -8,7 +8,7 @@
88
* This grammar is generated from the CFG contained in:
99
* https://github.com/dart-lang/language/blob/70eb85cf9a6606a9da0de824a5d55fd06de1287f/specification/dartLangSpec.tex
1010
*
11-
* The bash script used to scrape and the refactor the gramamr is here:
11+
* The bash script used to scrape and the refactor the grammar is here:
1212
* https://github.com/kaby76/ScrapeDartSpec/blob/master/refactor.sh
1313
*
1414
* Note: the CFG in the Specification is in development, and is for approximately
@@ -484,7 +484,6 @@ identifier
484484
| SET_
485485
| STATIC_
486486
| TYPEDEF_
487-
| FUNCTION_
488487
| ASYNC_
489488
| HIDE_
490489
| OF_
@@ -493,8 +492,9 @@ identifier
493492
| SYNC_
494493
| AWAIT_
495494
| YIELD_
496-
| DYNAMIC_
497495
| NATIVE_
496+
| BASE_
497+
| SEALED_
498498
;
499499

500500
identifierList
@@ -1052,6 +1052,8 @@ typeIdentifier
10521052
| DYNAMIC_
10531053
| NATIVE_
10541054
| FUNCTION_
1055+
| BASE_
1056+
| SEALED_
10551057
;
10561058

10571059
typeList

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

Lines changed: 23 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -16,9 +16,9 @@ open class DartBasicIdentListener(private val filePath: String) : DartAstListene
1616
)
1717

1818
private var classNodes: MutableList<CodeDataStruct> = mutableListOf()
19-
private var currentNode: CodeDataStruct = CodeDataStruct()
19+
protected var currentNode: CodeDataStruct = CodeDataStruct()
2020
private var currentFunction: CodeFunction = CodeFunction()
21-
private var hasEnterClass: Boolean = false
21+
protected var hasEnterClass: Boolean = false
2222

2323
// Track library name if specified
2424
override fun enterLibraryName(ctx: Dart2Parser.LibraryNameContext?) {
@@ -66,11 +66,28 @@ open class DartBasicIdentListener(private val filePath: String) : DartAstListene
6666
override fun enterLibraryExport(ctx: Dart2Parser.LibraryExportContext?) {
6767
val uri = ctx?.configurableUri()?.uri()?.stringLiteral()?.text?.trim('"', '\'') ?: return
6868

69-
// Create an export entry
70-
val codeExport = CodeExport(Name = uri)
71-
codeContainer.DataStructures.find { it.NodeName == "default" }?.Exports?.let {
72-
// Add to default struct if exists
69+
// Create an export entry and store in TopLevel scope
70+
val codeExport = CodeExport(
71+
Name = uri,
72+
FromSource = uri,
73+
Kind = ExportKind.RE_EXPORT_ALL
74+
)
75+
76+
// Handle show/hide combinators for exports
77+
ctx.combinator()?.forEach { combinator ->
78+
val identifiers = combinator.identifierList()?.identifier()?.map { it.text } ?: return@forEach
79+
if (combinator.SHOW_() != null) {
80+
codeExport.Kind = ExportKind.RE_EXPORT_NAMED
81+
codeExport.Specifiers = identifiers.map { name ->
82+
ExportSpecifier(LocalName = name, ExportedName = name)
83+
}
84+
}
7385
}
86+
87+
ensureTopLevelScope()
88+
codeContainer.TopLevel = codeContainer.TopLevel?.copy(
89+
Exports = (codeContainer.TopLevel?.Exports ?: emptyList()) + codeExport
90+
)
7491
}
7592

7693
// Handle class declaration

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

Lines changed: 55 additions & 69 deletions
Original file line numberDiff line numberDiff line change
@@ -15,22 +15,23 @@ open class DartFullIdentListener(filePath: String) : DartBasicIdentListener(file
1515

1616
override fun enterMethodSignature(ctx: Dart2Parser.MethodSignatureContext?) {
1717
super.enterMethodSignature(ctx)
18-
isInFunction = true
19-
currentFunctionCalls.clear()
20-
localVariables.clear()
18+
// Don't set isInFunction here - wait for function body
2119
}
2220

2321
override fun exitMethodSignature(ctx: Dart2Parser.MethodSignatureContext?) {
2422
super.exitMethodSignature(ctx)
25-
isInFunction = false
2623
}
2724

2825
override fun enterFunctionBody(ctx: Dart2Parser.FunctionBodyContext?) {
29-
// Reset for function body processing
26+
isInFunction = true
27+
currentFunctionCalls.clear()
28+
localVariables.clear()
3029
}
3130

3231
override fun exitFunctionBody(ctx: Dart2Parser.FunctionBodyContext?) {
33-
// Attach collected function calls
32+
// Function calls collected during body processing are stored in currentFunctionCalls
33+
// They can be attached to the current function if needed
34+
isInFunction = false
3435
}
3536

3637
// Handle class member declarations for fields
@@ -72,65 +73,32 @@ open class DartFullIdentListener(filePath: String) : DartBasicIdentListener(file
7273
?: "var"
7374
}
7475

75-
private fun addFieldToCurrentClass(
76+
/**
77+
* Add a field to the current class being parsed.
78+
* Creates a CodeField and adds it to the current node's fields.
79+
*/
80+
protected fun addFieldToCurrentClass(
7681
name: String,
7782
type: String,
7883
isStatic: Boolean = false,
7984
isFinal: Boolean = false,
8085
isLate: Boolean = false
8186
) {
82-
// This will be handled by the parent class's current node
83-
}
84-
85-
// Handle function calls in expressions
86-
override fun enterArgumentPart(ctx: Dart2Parser.ArgumentPartContext?) {
87-
if (!isInFunction) return
88-
89-
// Extract function call information from the parent context
90-
val parent = ctx?.parent
91-
if (parent is Dart2Parser.SelectorContext || parent is Dart2Parser.PostfixExpressionContext) {
92-
// Function call detected
93-
extractFunctionCall(ctx)
94-
}
95-
}
96-
97-
private fun extractFunctionCall(ctx: Dart2Parser.ArgumentPartContext) {
98-
// Build function call from context
99-
val call = CodeCall()
100-
call.Position = buildPosition(ctx)
101-
102-
// Parse arguments
103-
ctx.arguments()?.argumentList()?.let { argList ->
104-
call.Parameters = parseCallArguments(argList)
105-
}
106-
107-
currentFunctionCalls.add(call)
108-
}
109-
110-
private fun parseCallArguments(ctx: Dart2Parser.ArgumentListContext): List<CodeProperty> {
111-
val params = mutableListOf<CodeProperty>()
112-
113-
// Expression list
114-
ctx.expressionList()?.expr()?.forEach { expr ->
115-
params.add(CodeProperty(TypeType = "dynamic", TypeValue = expr.text))
116-
}
117-
118-
// Named arguments
119-
ctx.namedArgument()?.forEach { named ->
120-
val name = named.label()?.identifier()?.text ?: ""
121-
val value = named.expr()?.text ?: ""
122-
params.add(CodeProperty(TypeType = value, TypeValue = name))
123-
}
124-
125-
return params
126-
}
87+
if (!hasEnterClass) return
88+
89+
val modifiers = mutableListOf<String>()
90+
if (isStatic) modifiers.add("static")
91+
if (isFinal) modifiers.add("final")
92+
if (isLate) modifiers.add("late")
93+
94+
val field = CodeField(
95+
TypeKey = name,
96+
TypeType = type,
97+
TypeValue = "",
98+
Modifiers = modifiers
99+
)
127100

128-
// Handle primary expressions (identifiers, literals, etc.)
129-
override fun enterPrimary(ctx: Dart2Parser.PrimaryContext?) {
130-
// Track identifiers for variable resolution
131-
ctx?.identifier()?.let { id ->
132-
// Could be a variable reference or function call
133-
}
101+
currentNode.Fields += field
134102
}
135103

136104
// Handle new expressions
@@ -208,6 +176,13 @@ open class DartFullIdentListener(filePath: String) : DartBasicIdentListener(file
208176
var currentTarget = primary.text
209177

210178
for (selector in selectors) {
179+
// First update target if there's an identifier selector
180+
selector.assignableSelector()?.let { assignable ->
181+
assignable.identifier()?.let { id ->
182+
currentTarget = "$currentTarget.${id.text}"
183+
}
184+
}
185+
211186
// Check if this selector includes arguments (function call)
212187
selector.argumentPart()?.let { argPart ->
213188
val call = CodeCall(
@@ -222,14 +197,25 @@ open class DartFullIdentListener(filePath: String) : DartBasicIdentListener(file
222197

223198
currentFunctionCalls.add(call)
224199
}
200+
}
201+
}
225202

226-
// Update target for chained calls
227-
selector.assignableSelector()?.let { assignable ->
228-
assignable.identifier()?.let { id ->
229-
currentTarget = "$currentTarget.${id.text}"
230-
}
231-
}
203+
private fun parseCallArguments(ctx: Dart2Parser.ArgumentListContext): List<CodeProperty> {
204+
val params = mutableListOf<CodeProperty>()
205+
206+
// Expression list
207+
ctx.expressionList()?.expr()?.forEach { expr ->
208+
params.add(CodeProperty(TypeType = "dynamic", TypeValue = expr.text))
209+
}
210+
211+
// Named arguments
212+
ctx.namedArgument()?.forEach { named ->
213+
val name = named.label()?.identifier()?.text ?: ""
214+
val value = named.expr()?.text ?: ""
215+
params.add(CodeProperty(TypeType = value, TypeValue = name))
232216
}
217+
218+
return params
233219
}
234220

235221
// Handle local variable declarations
@@ -268,11 +254,6 @@ open class DartFullIdentListener(filePath: String) : DartBasicIdentListener(file
268254
}
269255
}
270256

271-
// Handle await expressions
272-
override fun enterAwaitExpression(ctx: Dart2Parser.AwaitExpressionContext?) {
273-
// Mark that we're in an async context
274-
}
275-
276257
// Handle throw expressions
277258
override fun enterThrowExpression(ctx: Dart2Parser.ThrowExpressionContext?) {
278259
if (!isInFunction) return
@@ -299,4 +280,9 @@ open class DartFullIdentListener(filePath: String) : DartBasicIdentListener(file
299280
currentFunctionCalls.add(call)
300281
}
301282
}
283+
284+
/**
285+
* Get the function calls collected during parsing.
286+
*/
287+
fun getFunctionCalls(): List<CodeCall> = currentFunctionCalls.toList()
302288
}

0 commit comments

Comments
 (0)