-
Notifications
You must be signed in to change notification settings - Fork 47
feat(dart): add Dart language support with Dart 3 class modifiers #52
New issue
Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.
By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.
Already on GitHub? Sign in to your account
Conversation
Add new chapi-ast-dart module for parsing Dart source code using ANTLR4. Features: - Parse library declarations and imports (with show/hide/as) - Parse class, abstract class, mixin, extension, enum, typedef - Parse inheritance (extends), mixins (with), interfaces (implements) - Parse constructors (regular, named, factory) - Parse methods, getters, setters, operator overloads - Parse top-level functions and variables - Track method calls and constructor invocations Grammar based on Dart 2.15 specification from antlr/grammars-v4.
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.
|
Note Other AI code review bot(s) detectedCodeRabbit has detected other AI code review bot(s) in this pull request and will avoid duplicating their findings in the review comments. This may lead to a less comprehensive review. 📝 WalkthroughWalkthroughAdds a new Changes
Sequence DiagramsequenceDiagram
participant Client as Client
participant Analyser as DartAnalyser
participant Stream as CharStream
participant Lexer as Dart2Lexer
participant Parser as Dart2Parser
participant Listener as DartFullIdentListener
participant Container as CodeContainer
Client->>Analyser: analysis(code, filePath)
Analyser->>Stream: CharStreams.fromString(code)
Stream-->>Analyser: input stream
Analyser->>Lexer: new(input stream)
Lexer-->>Analyser: token stream
Analyser->>Parser: new(token stream)
Parser-->>Analyser: parseTree (compilationUnit)
Analyser->>Listener: new(filePath)
Analyser->>Parser: walk(parseTree) with Listener
Listener-->>Listener: enter/exit rules (classes, funcs, expressions)
Listener-->>Container: aggregate CodeContainer
Analyser-->>Client: return CodeContainer
Estimated Code Review Effort🎯 4 (Complex) | ⏱️ ~45 minutes Poem
🚥 Pre-merge checks | ✅ 2 | ❌ 1❌ Failed checks (1 warning)
✅ Passed checks (2 passed)
✏️ Tip: You can configure your own custom pre-merge checks in the settings. ✨ Finishing touches
Thanks for using CodeRabbit! It's free for OSS, and your support helps us grow. If you like it, consider giving us a shout-out. Comment |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Pull request overview
This pull request adds comprehensive Dart language support to the CHAPI project using ANTLR4 for parsing. The implementation includes support for Dart 3 class modifiers (sealed, base, interface, final, mixin) and can extract structural information such as classes, mixins, extensions, enums, functions, and their relationships.
Changes:
- Added ANTLR4 grammar files for Dart 2/3 lexer and parser with Dart 3 class modifier support
- Implemented analyzer and listener classes to extract code structure information
- Created comprehensive test suite with 22 passing tests covering various Dart language features
Reviewed changes
Copilot reviewed 12 out of 13 changed files in this pull request and generated 9 comments.
Show a summary per file
| File | Description |
|---|---|
| settings.gradle.kts | Added chapi-ast-dart module to the build |
| build.gradle.kts | Added chapi-ast-dart to jacoco aggregation |
| chapi-ast-dart/build.gradle.kts | Build configuration for the Dart module with ANTLR4 plugin |
| chapi-ast-dart/src/main/antlr/Dart2Lexer.g4 | Lexer grammar defining Dart tokens and keywords |
| chapi-ast-dart/src/main/antlr/Dart2Parser.g4 | Parser grammar defining Dart syntax rules |
| chapi-ast-dart/src/main/java/chapi/ast/antlr/Dart2LexerBase.java | Base class for generated lexer |
| chapi-ast-dart/src/main/kotlin/chapi/ast/dartast/DartAnalyser.kt | Main analyzer entry point |
| chapi-ast-dart/src/main/kotlin/chapi/ast/dartast/DartAstListener.kt | Base listener with utility methods |
| chapi-ast-dart/src/main/kotlin/chapi/ast/dartast/DartBasicIdentListener.kt | Listener for basic structure extraction |
| chapi-ast-dart/src/main/kotlin/chapi/ast/dartast/DartFullIdentListener.kt | Listener for full analysis including method calls |
| chapi-ast-dart/src/test/kotlin/chapi/ast/dartast/DartAnalyserTest.kt | Comprehensive test suite with 22 tests |
| chapi-ast-dart/src/test/resources/grammar/hello_world.dart | Test resource file |
| chapi-ast-dart/src/test/resources/grammar/class_example.dart | Test resource file with complex examples |
💡 Add Copilot custom instructions for smarter, more guided reviews. Learn how to get started.
|
|
||
| override fun enterMethodSignature(ctx: Dart2Parser.MethodSignatureContext?) { | ||
| super.enterMethodSignature(ctx) | ||
| isInFunction = true | ||
| currentFunctionCalls.clear() | ||
| localVariables.clear() | ||
| } | ||
|
|
||
| override fun exitMethodSignature(ctx: Dart2Parser.MethodSignatureContext?) { | ||
| super.exitMethodSignature(ctx) | ||
| isInFunction = false | ||
| } | ||
|
|
||
| override fun enterFunctionBody(ctx: Dart2Parser.FunctionBodyContext?) { | ||
| // Reset for function body processing | ||
| } | ||
|
|
||
| override fun exitFunctionBody(ctx: Dart2Parser.FunctionBodyContext?) { | ||
| // Attach collected function calls |
Copilot
AI
Jan 20, 2026
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
The enterFunctionBody and exitFunctionBody methods have empty implementations with only comments. The exitFunctionBody comment mentions "Attach collected function calls" but currentFunctionCalls is never actually attached to the current function. This means that method call tracking won't work properly - the calls collected during function body processing won't be associated with their containing function.
| override fun enterMethodSignature(ctx: Dart2Parser.MethodSignatureContext?) { | |
| super.enterMethodSignature(ctx) | |
| isInFunction = true | |
| currentFunctionCalls.clear() | |
| localVariables.clear() | |
| } | |
| override fun exitMethodSignature(ctx: Dart2Parser.MethodSignatureContext?) { | |
| super.exitMethodSignature(ctx) | |
| isInFunction = false | |
| } | |
| override fun enterFunctionBody(ctx: Dart2Parser.FunctionBodyContext?) { | |
| // Reset for function body processing | |
| } | |
| override fun exitFunctionBody(ctx: Dart2Parser.FunctionBodyContext?) { | |
| // Attach collected function calls | |
| private var currentFunctionIdentifier: String? = null | |
| private val functionCallsByFunction: MutableMap<String, MutableList<CodeCall>> = mutableMapOf() | |
| override fun enterMethodSignature(ctx: Dart2Parser.MethodSignatureContext?) { | |
| super.enterMethodSignature(ctx) | |
| isInFunction = true | |
| currentFunctionCalls.clear() | |
| localVariables.clear() | |
| currentFunctionIdentifier = ctx?.text | |
| } | |
| override fun exitMethodSignature(ctx: Dart2Parser.MethodSignatureContext?) { | |
| super.exitMethodSignature(ctx) | |
| isInFunction = false | |
| currentFunctionIdentifier = null | |
| } | |
| override fun enterFunctionBody(ctx: Dart2Parser.FunctionBodyContext?) { | |
| // Reset for function body processing if needed; currentFunctionCalls | |
| // is already cleared in enterMethodSignature. | |
| } | |
| override fun exitFunctionBody(ctx: Dart2Parser.FunctionBodyContext?) { | |
| // Attach collected function calls to the current function identifier | |
| val functionId = currentFunctionIdentifier | |
| if (isInFunction && functionId != null && currentFunctionCalls.isNotEmpty()) { | |
| val callsForFunction = functionCallsByFunction.getOrPut(functionId) { mutableListOf() } | |
| callsForFunction.addAll(currentFunctionCalls) | |
| } |
| // Handle await expressions | ||
| override fun enterAwaitExpression(ctx: Dart2Parser.AwaitExpressionContext?) { | ||
| // Mark that we're in an async context | ||
| } | ||
|
|
Copilot
AI
Jan 20, 2026
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
The enterAwaitExpression method has only a comment "Mark that we're in an async context" but no implementation. If tracking async context is needed for the analysis, this should be implemented; otherwise, the empty method and comment should be removed.
| // Handle await expressions | |
| override fun enterAwaitExpression(ctx: Dart2Parser.AwaitExpressionContext?) { | |
| // Mark that we're in an async context | |
| } |
| // Track identifiers for variable resolution | ||
| ctx?.identifier()?.let { id -> | ||
| // Could be a variable reference or function call |
Copilot
AI
Jan 20, 2026
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
The enterPrimary method has only a comment "Could be a variable reference or function call" but no implementation. If identifier tracking is needed, this should be implemented; otherwise, the empty method and comment should be removed.
| // Track identifiers for variable resolution | |
| ctx?.identifier()?.let { id -> | |
| // Could be a variable reference or function call | |
| // Identifier handling is not required in this listener; this method is intentionally a no-op. | |
| if (ctx == null) { | |
| return |
| * This grammar is generated from the CFG contained in: | ||
| * https://github.com/dart-lang/language/blob/70eb85cf9a6606a9da0de824a5d55fd06de1287f/specification/dartLangSpec.tex | ||
| * | ||
| * The bash script used to scrape and the refactor the gramamr is here: |
Copilot
AI
Jan 20, 2026
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
The word "gramamr" is misspelled. It should be "grammar".
| * The bash script used to scrape and the refactor the gramamr is here: | |
| * The bash script used to scrape and the refactor the grammar is here: |
| * This grammar is generated from the CFG contained in: | ||
| * https://github.com/dart-lang/language/blob/70eb85cf9a6606a9da0de824a5d55fd06de1287f/specification/dartLangSpec.tex | ||
| * | ||
| * The bash script used to scrape and the refactor the gramamr is here: |
Copilot
AI
Jan 20, 2026
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
The word "gramamr" is misspelled. It should be "grammar".
| * The bash script used to scrape and the refactor the gramamr is here: | |
| * The bash script used to scrape and the refactor the grammar is here: |
| | SET_ | ||
| | STATIC_ | ||
| | TYPEDEF_ | ||
| | FUNCTION_ |
Copilot
AI
Jan 20, 2026
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
The identifier rule includes FUNCTION_ twice (on lines 487 and 119/224). This duplication should be removed to avoid ambiguity in the grammar.
| | FUNCTION_ |
| | SYNC_ | ||
| | AWAIT_ | ||
| | YIELD_ | ||
| | DYNAMIC_ |
Copilot
AI
Jan 20, 2026
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
The identifier rule includes DYNAMIC_ twice (on lines 468 and 496). This duplication should be removed to avoid ambiguity in the grammar.
| | DYNAMIC_ |
| // Dart 3 class modifiers | ||
| SEALED_ : 'sealed'; | ||
| BASE_ : 'base'; | ||
| WHEN_ : 'when'; |
Copilot
AI
Jan 20, 2026
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
The WHEN_ keyword is defined in the lexer but is not used anywhere in the parser grammar. According to Dart 3 documentation, 'when' is used in switch expressions/pattern matching, not as a class modifier. Consider removing this unused keyword or adding its proper usage in the grammar.
| WHEN_ : 'when'; |
| isFinal: Boolean = false, | ||
| isLate: Boolean = false | ||
| ) { | ||
| // This will be handled by the parent class's current node |
Copilot
AI
Jan 20, 2026
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
The function extractFieldsFromDeclaration extracts field information but the helper function addFieldToCurrentClass doesn't actually add the fields to the current node. The function body only contains a comment stating "This will be handled by the parent class's current node" but no implementation. This means fields declared in classes won't be properly captured in the data structure.
| // This will be handled by the parent class's current node | |
| val field = CodeField().apply { | |
| Name = name | |
| TypeValue = type | |
| val modifiers = mutableListOf<String>() | |
| if (isStatic) { | |
| modifiers.add("static") | |
| } | |
| if (isFinal) { | |
| modifiers.add("final") | |
| } | |
| if (isLate) { | |
| modifiers.add("late") | |
| } | |
| Modifiers = modifiers | |
| } | |
| // Attach this field to the current class/node maintained by the parent listener. | |
| // Assumes the parent exposes a current node with a Fields collection. | |
| currentNode.Fields.add(field) |
Codecov Report❌ Patch coverage is Additional details and impacted files@@ Coverage Diff @@
## master #52 +/- ##
============================================
- Coverage 67.79% 67.01% -0.79%
- Complexity 1590 1712 +122
============================================
Files 83 88 +5
Lines 7422 7977 +555
Branches 1456 1624 +168
============================================
+ Hits 5032 5346 +314
- Misses 1496 1611 +115
- Partials 894 1020 +126 ☔ View full report in Codecov by Sentry. 🚀 New features to boost your workflow:
|
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Actionable comments posted: 8
🤖 Fix all issues with AI agents
In `@chapi-ast-dart/build.gradle.kts`:
- Around line 37-39: The Gradle sourceSets.main block uses java.srcDirs(...)
which replaces default dirs and thus excludes src/main/java (holding
Dart2LexerBase.java) causing the generated Dart2Lexer (from Dart2Lexer.g4) to
fail resolving its superclass; change the configuration to append the
generated-src directory instead of replacing defaults—use
java.srcDir("${project.layout.buildDirectory.get().asFile}/generated-src") or
explicitly set java.srcDirs(listOf("src/main/java",
"${project.layout.buildDirectory.get().asFile}/generated-src")) so both
src/main/java (Dart2LexerBase.java) and the generated-src are compiled.
In `@chapi-ast-dart/src/main/antlr/Dart2Parser.g4`:
- Around line 462-498: Update the parser grammar: add the tokens BASE_, SEALED_,
and WHEN_ to both the identifier and typeIdentifier parser rules so they are
recognized as contextual keywords (same treatment as AS_, STATIC_, etc.), and
remove the duplicate FUNCTION_ and DYNAMIC_ entries from the identifier rule to
avoid redundancy; modify the identifier and typeIdentifier rule definitions
(search for the identifier and typeIdentifier rule names in the diff) to include
BASE_, SEALED_, WHEN_ and deduplicate FUNCTION_ and DYNAMIC_.
In `@chapi-ast-dart/src/main/kotlin/chapi/ast/dartast/DartBasicIdentListener.kt`:
- Around line 66-74: enterLibraryExport currently constructs a CodeExport
(codeExport) but never adds it to the container; either push the created export
into the default data-structure's Exports list or explicitly mark the method as
a no-op. Update the block that looks up codeContainer.DataStructures.find {
it.NodeName == "default" }?.Exports to append codeExport (e.g., add to the
collection) so exports are tracked, or if export tracking isn't required,
replace the empty block with a clear TODO comment or remove the method entirely;
reference symbols: enterLibraryExport, CodeExport, codeExport,
codeContainer.DataStructures, NodeName, Exports.
In `@chapi-ast-dart/src/main/kotlin/chapi/ast/dartast/DartFullIdentListener.kt`:
- Around line 36-83: The addFieldToCurrentClass function is a no-op so fields
extracted in enterClassMemberDeclaration / extractFieldsFromDeclaration are
discarded; implement addFieldToCurrentClass to record fields into the current
class AST node (or delegate to the parent listener/storage) by locating the
current class node (e.g., the listener's currentClass/currentNode field or
parent listener method) and appending a Field model containing name, type,
isStatic, isFinal, isLate; ensure thread-safety/context checks (return early if
no current class) and preserve existing visibility/ordering semantics used
elsewhere in this listener.
- Around line 16-34: The current code flips isInFunction to false in
exitMethodSignature (before the body is traversed) and never attaches
currentFunctionCalls; to fix, remove the isInFunction toggle from
exitMethodSignature and instead set isInFunction = true in enterFunctionBody and
set isInFunction = false in exitFunctionBody, initialize/clear
currentFunctionCalls and localVariables in enterFunctionBody, and in
exitFunctionBody attach the collected currentFunctionCalls to the function’s
node/context (or whatever collection you use for method bodies) before clearing
them so body-level call collection is preserved.
- Around line 192-233: The extractMethodCallChain is building CodeCall.NodeName
from currentTarget before appending the selector identifier, so calls record the
receiver rather than receiver.method; change the logic in extractMethodCallChain
(used by enterPostfixExpression) to compute the full target including the
selector identifier before constructing the CodeCall: when
selector.argumentPart() is present, first derive the method name by checking
selector.assignableSelector()?.identifier()?.text and building newTarget =
"$currentTarget.${idText}" (or just idText if appropriate), then set
call.NodeName = newTarget and only after handling the call update currentTarget
= newTarget for subsequent chained selectors; ensure CodeCall creation,
call.Position, and call.Parameters still occur but use the computed full target.
- Around line 86-126: enterArgumentPart is creating CodeCall objects via
extractFunctionCall without setting NodeName or Type and duplicates entries also
handled by enterPostfixExpression; fix by either removing enterArgumentPart
entirely or by updating extractFunctionCall to set the callee and call type
similarly to extractMethodCallChain: derive the callee name from ctx.parent when
it is a SelectorContext/PostfixExpression (matching how extractMethodCallChain
builds currentTarget) and assign call.NodeName = <callee> and call.Type =
CallType.FUNCTION before adding to currentFunctionCalls, ensuring you only add
calls in one listener to avoid duplicates.
In `@chapi-ast-dart/src/test/kotlin/chapi/ast/dartast/DartAnalyserTest.kt`:
- Around line 284-288: Replace the silent early return when loading the resource
in DartAnalyserTest by asserting or assuming the resource exists: instead of
checking "val code = this::class.java.getResource(...)? .readText()" and
returning when null, use JUnit's assumeTrue or assertNotNull on the getResource
result (or on code) with a clear message so the test fails/ is skipped
explicitly; locate the resource load in DartAnalyserTest (the "code" variable)
and change the null-path to call assumeTrue(getResource != null, "Resource
/grammar/class_example.dart not found") or assertNotNull(code, "Resource
/grammar/class_example.dart not found") rather than returning silently.
🧹 Nitpick comments (1)
chapi-ast-dart/src/main/kotlin/chapi/ast/dartast/DartAstListener.kt (1)
12-101: LGTM!Clean base class providing common utility methods for AST processing. The
buildPosition,buildAnnotation, and identifier extraction methods provide a good foundation for derived listeners.Note: The
isAbstractandisStaticmethods are unused in the codebase. Derived listeners (DartBasicIdentListener,DartFullIdentListener) use grammar-specific methods likemodifiers?.any { it.ABSTRACT_() != null }andctx.STATIC_() != nullinstead. These utility methods can be removed if not intended as future API, or kept as fallback utilities for consistency.
| identifier | ||
| : IDENTIFIER | ||
| | ABSTRACT_ | ||
| | AS_ | ||
| | COVARIANT_ | ||
| | DEFERRED_ | ||
| | DYNAMIC_ | ||
| | EXPORT_ | ||
| | EXTERNAL_ | ||
| | EXTENSION_ | ||
| | FACTORY_ | ||
| | FUNCTION_ | ||
| | GET_ | ||
| | IMPLEMENTS_ | ||
| | IMPORT_ | ||
| | INTERFACE_ | ||
| | LATE_ | ||
| | LIBRARY_ | ||
| | MIXIN_ | ||
| | OPERATOR_ | ||
| | PART_ | ||
| | REQUIRED_ | ||
| | SET_ | ||
| | STATIC_ | ||
| | TYPEDEF_ | ||
| | FUNCTION_ | ||
| | ASYNC_ | ||
| | HIDE_ | ||
| | OF_ | ||
| | ON_ | ||
| | SHOW_ | ||
| | SYNC_ | ||
| | AWAIT_ | ||
| | YIELD_ | ||
| | DYNAMIC_ | ||
| | NATIVE_ | ||
| ; |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
🧩 Analysis chain
🏁 Script executed:
# First, let's locate the file
find . -name "Dart2Parser.g4" -type fRepository: phodal/chapi
Length of output: 102
🏁 Script executed:
# Read the identifier rule around lines 462-498
sed -n '460,500p' chapi-ast-dart/src/main/antlr/Dart2Parser.g4Repository: phodal/chapi
Length of output: 588
🏁 Script executed:
# Read the typeIdentifier rule around lines 1042-1055
sed -n '1040,1060p' chapi-ast-dart/src/main/antlr/Dart2Parser.g4Repository: phodal/chapi
Length of output: 278
🏁 Script executed:
# Search for BASE_, SEALED_, WHEN_ tokens in the file
rg "BASE_|SEALED_|WHEN_" chapi-ast-dart/src/main/antlr/Dart2Parser.g4 -nRepository: phodal/chapi
Length of output: 197
🏁 Script executed:
# Search for classModifier to understand context of these tokens
rg "classModifier" chapi-ast-dart/src/main/antlr/Dart2Parser.g4 -n -A 10Repository: phodal/chapi
Length of output: 622
🏁 Script executed:
# Check if WHEN_ token is defined anywhere in the file
rg "WHEN_" chapi-ast-dart/src/main/antlr/Dart2Parser.g4Repository: phodal/chapi
Length of output: 38
🏁 Script executed:
# Get total line count and check if WHEN_ might be in lexer file instead
wc -l chapi-ast-dart/src/main/antlr/Dart2Parser.g4Repository: phodal/chapi
Length of output: 105
🏁 Script executed:
# Search for WHEN_ in lexer files
find chapi-ast-dart -name "*Lexer*" -type f | head -5Repository: phodal/chapi
Length of output: 164
🏁 Script executed:
# Check for WHEN_ in potential lexer files
find chapi-ast-dart -name "*Lexer.g4" -o -name "*Tokens.g4" | xargs grep -l "WHEN_" 2>/dev/nullRepository: phodal/chapi
Length of output: 99
🏁 Script executed:
# More broadly search for WHEN anywhere in antlr directory
rg "WHEN" chapi-ast-dart/src/main/antlr/ -iRepository: phodal/chapi
Length of output: 121
🏁 Script executed:
# Check for WHEN_ usage in the parser file
rg "WHEN_" chapi-ast-dart/src/main/antlr/Dart2Parser.g4Repository: phodal/chapi
Length of output: 38
🏁 Script executed:
# Search for where BASE_ and SEALED_ appear in classModifier vs identifier/typeIdentifier
rg -n "(BASE_|SEALED_)" chapi-ast-dart/src/main/antlr/Dart2Parser.g4Repository: phodal/chapi
Length of output: 197
🏁 Script executed:
# Check if these tokens appear anywhere else in grammar rules
rg "BASE_|SEALED_|WHEN_" chapi-ast-dart/src/main/antlr/Dart2Lexer.g4Repository: phodal/chapi
Length of output: 123
🏁 Script executed:
# Search the entire chapi-ast-dart directory for WHEN_ usage patterns
rg "WHEN_" chapi-ast-dart/src/main/antlr/Repository: phodal/chapi
Length of output: 121
🏁 Script executed:
# Also check for pattern-related rules that might use when
rg "pattern|when" chapi-ast-dart/src/main/antlr/Dart2Parser.g4 -iRepository: phodal/chapi
Length of output: 38
Add BASE_, SEALED_, and WHEN_ to identifier and typeIdentifier rules.
BASE_, SEALED_, and WHEN_ are tokenized in the lexer but excluded from identifier and typeIdentifier rules. While BASE_ and SEALED_ have context-specific uses in classModifier and mixinDeclaration, they remain unavailable for general identifier use, which prevents valid Dart 3 code like var base = 1 or class sealed {} from parsing. WHEN_ is currently defined in the lexer but unused in any parser rule. Include these tokens in both identifier and typeIdentifier to treat them as contextual keywords consistent with other keywords already in those rules.
🛠️ Suggested adjustment
identifier
: IDENTIFIER
| ABSTRACT_
| AS_
| COVARIANT_
| DEFERRED_
| DYNAMIC_
| EXPORT_
| EXTERNAL_
| EXTENSION_
| FACTORY_
| FUNCTION_
| GET_
| IMPLEMENTS_
| IMPORT_
| INTERFACE_
| LATE_
| LIBRARY_
| MIXIN_
| OPERATOR_
| PART_
| REQUIRED_
| SET_
| STATIC_
| TYPEDEF_
| FUNCTION_
| ASYNC_
| HIDE_
| OF_
| ON_
| SHOW_
| SYNC_
| AWAIT_
| YIELD_
| DYNAMIC_
| NATIVE_
+ | BASE_
+ | SEALED_
+ | WHEN_
;
typeIdentifier
: IDENTIFIER
| ASYNC_
| HIDE_
| OF_
| ON_
| SHOW_
| SYNC_
| AWAIT_
| YIELD_
| DYNAMIC_
| NATIVE_
| FUNCTION_
+ | BASE_
+ | SEALED_
+ | WHEN_
;Also note: The identifier rule contains duplicate entries for FUNCTION_ and DYNAMIC_.
🤖 Prompt for AI Agents
In `@chapi-ast-dart/src/main/antlr/Dart2Parser.g4` around lines 462 - 498, Update
the parser grammar: add the tokens BASE_, SEALED_, and WHEN_ to both the
identifier and typeIdentifier parser rules so they are recognized as contextual
keywords (same treatment as AS_, STATIC_, etc.), and remove the duplicate
FUNCTION_ and DYNAMIC_ entries from the identifier rule to avoid redundancy;
modify the identifier and typeIdentifier rule definitions (search for the
identifier and typeIdentifier rule names in the diff) to include BASE_, SEALED_,
WHEN_ and deduplicate FUNCTION_ and DYNAMIC_.
chapi-ast-dart/src/main/kotlin/chapi/ast/dartast/DartBasicIdentListener.kt
Show resolved
Hide resolved
| override fun enterMethodSignature(ctx: Dart2Parser.MethodSignatureContext?) { | ||
| super.enterMethodSignature(ctx) | ||
| isInFunction = true | ||
| currentFunctionCalls.clear() | ||
| localVariables.clear() | ||
| } | ||
|
|
||
| override fun exitMethodSignature(ctx: Dart2Parser.MethodSignatureContext?) { | ||
| super.exitMethodSignature(ctx) | ||
| isInFunction = false | ||
| } | ||
|
|
||
| override fun enterFunctionBody(ctx: Dart2Parser.FunctionBodyContext?) { | ||
| // Reset for function body processing | ||
| } | ||
|
|
||
| override fun exitFunctionBody(ctx: Dart2Parser.FunctionBodyContext?) { | ||
| // Attach collected function calls | ||
| } |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Function-scope flag resets before body traversal (calls never collected).
Line 24 sets isInFunction = false on exitMethodSignature, which runs before the function body is visited, so body-level collectors won’t run. Also, currentFunctionCalls is never attached in exitFunctionBody (Line 32).
Please move the flag handling to the body and persist the collected calls there.
✅ Proposed fix (scope flag)
override fun enterMethodSignature(ctx: Dart2Parser.MethodSignatureContext?) {
super.enterMethodSignature(ctx)
- isInFunction = true
currentFunctionCalls.clear()
localVariables.clear()
}
override fun exitMethodSignature(ctx: Dart2Parser.MethodSignatureContext?) {
super.exitMethodSignature(ctx)
- isInFunction = false
}
override fun enterFunctionBody(ctx: Dart2Parser.FunctionBodyContext?) {
- // Reset for function body processing
+ isInFunction = true
}
override fun exitFunctionBody(ctx: Dart2Parser.FunctionBodyContext?) {
- // Attach collected function calls
+ // Attach collected function calls to the current function/method here
+ isInFunction = false
}🤖 Prompt for AI Agents
In `@chapi-ast-dart/src/main/kotlin/chapi/ast/dartast/DartFullIdentListener.kt`
around lines 16 - 34, The current code flips isInFunction to false in
exitMethodSignature (before the body is traversed) and never attaches
currentFunctionCalls; to fix, remove the isInFunction toggle from
exitMethodSignature and instead set isInFunction = true in enterFunctionBody and
set isInFunction = false in exitFunctionBody, initialize/clear
currentFunctionCalls and localVariables in enterFunctionBody, and in
exitFunctionBody attach the collected currentFunctionCalls to the function’s
node/context (or whatever collection you use for method bodies) before clearing
them so body-level call collection is preserved.
chapi-ast-dart/src/main/kotlin/chapi/ast/dartast/DartFullIdentListener.kt
Outdated
Show resolved
Hide resolved
chapi-ast-dart/src/main/kotlin/chapi/ast/dartast/DartFullIdentListener.kt
Outdated
Show resolved
Hide resolved
chapi-ast-dart/src/main/kotlin/chapi/ast/dartast/DartFullIdentListener.kt
Show resolved
Hide resolved
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
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Actionable comments posted: 1
🤖 Fix all issues with AI agents
In `@chapi-ast-dart/src/main/kotlin/chapi/ast/dartast/DartFullIdentListener.kt`:
- Around line 232-255: The enterCascade method currently only retrieves a single
cascadeSection from the Dart2Parser.CascadeContext so chained cascade calls are
missed; update enterCascade(Dart2Parser.CascadeContext?) to iterate over all
cascadeSection children (e.g., using ctx.cascadeSection(index) in a loop or the
list accessor if available) and call extractCascadeCall for each section so
every method in a cascade chain is captured; keep the null/isInFunction guards
and ensure extractCascadeCall and buildPosition usage remain unchanged.
♻️ Duplicate comments (2)
chapi-ast-dart/src/test/kotlin/chapi/ast/dartast/DartAnalyserTest.kt (1)
282-295: Consider removing this test in favor ofshouldParseComplexFileWithAssert.This test silently returns when the resource is missing, which can mask configuration issues. The test at lines 687-697 (
shouldParseComplexFileWithAssert) properly addresses this withassertNotNull. Having both tests is redundant—consider removing this one.Suggested removal
- `@Test` - fun shouldParseComplexFile() { - val code = this::class.java.getResource("/grammar/class_example.dart")?.readText() - if (code == null) { - // Skip if resource not found - return - } - - val codeContainer = DartAnalyser().analysis(code, "class_example.dart") - - assertEquals("example", codeContainer.PackageName) - assertTrue(codeContainer.Imports.isNotEmpty()) - assertTrue(codeContainer.DataStructures.isNotEmpty()) - }chapi-ast-dart/src/main/kotlin/chapi/ast/dartast/DartFullIdentListener.kt (1)
25-35: Collected function calls are never attached to the owning function.
currentFunctionCallsis cleared onenterFunctionBodyand reset onexitFunctionBody, but the collected calls are never persisted to the function'sCodeFunctionobject. The comment on line 32-33 acknowledges this ("can be attached to the current function if needed") but without implementation, method call tracking data is lost.If call tracking is an intended feature, wire the calls into
currentNode.Functions(the current function being built) before clearing.Proposed fix
override fun exitFunctionBody(ctx: Dart2Parser.FunctionBodyContext?) { - // Function calls collected during body processing are stored in currentFunctionCalls - // They can be attached to the current function if needed + // Attach collected calls to the current function + if (currentNode.Functions.isNotEmpty()) { + val currentFunction = currentNode.Functions.last() + currentFunction.FunctionCalls += currentFunctionCalls.toList() + } isInFunction = false }
🧹 Nitpick comments (2)
chapi-ast-dart/src/main/kotlin/chapi/ast/dartast/DartFullIdentListener.kt (2)
203-219: Named argument properties appear semantically inverted.For named arguments,
TypeTypeis set to the expression value andTypeValueis set to the parameter name. This seems backwards—typicallyTypeValuewould hold the value and the name would be stored elsewhere.Consider using
TypeKeyfor the parameter name instead:Suggested fix
// Named arguments ctx.namedArgument()?.forEach { named -> val name = named.label()?.identifier()?.text ?: "" val value = named.expr()?.text ?: "" - params.add(CodeProperty(TypeType = value, TypeValue = name)) + params.add(CodeProperty(TypeKey = name, TypeType = "dynamic", TypeValue = value)) }
257-268: Consider whether recordingthrowas a function call is appropriate.Recording
throwas aCodeCallwithCallType.FUNCTIONmay pollute the call graph sincethrowis a language keyword/statement, not a function invocation. This could affect downstream analysis that relies on function call data.If throw tracking is intentional for exception flow analysis, consider using a distinct
CallTypeor annotation to differentiate it from actual function calls.
| // Handle cascade expressions | ||
| override fun enterCascade(ctx: Dart2Parser.CascadeContext?) { | ||
| if (!isInFunction) return | ||
| if (ctx == null) return | ||
|
|
||
| // Cascade sections represent method calls on the same target | ||
| val cascadeSection = ctx.cascadeSection() | ||
| if (cascadeSection != null) { | ||
| extractCascadeCall(cascadeSection) | ||
| } | ||
| } | ||
|
|
||
| private fun extractCascadeCall(ctx: Dart2Parser.CascadeSectionContext) { | ||
| val selector = ctx.cascadeSelector() ?: return | ||
|
|
||
| selector.identifier()?.let { id -> | ||
| val call = CodeCall( | ||
| NodeName = id.text, | ||
| Type = CallType.FUNCTION | ||
| ) | ||
| call.Position = buildPosition(ctx) | ||
| currentFunctionCalls.add(call) | ||
| } | ||
| } |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
🧩 Analysis chain
🏁 Script executed:
# Find Dart grammar files
find . -name "*.g4" | head -20Repository: phodal/chapi
Length of output: 999
🏁 Script executed:
# Search for cascadeSection in the codebase
rg -n "cascadeSection" --type-add 'grammar:*.g4' -A 3 -B 1Repository: phodal/chapi
Length of output: 1855
🏁 Script executed:
# Check the Dart2Parser class to understand what methods are available
find . -name "Dart2Parser.java" -o -name "Dart2Parser.kt" | head -5Repository: phodal/chapi
Length of output: 38
🏁 Script executed:
# Search for Dart2Parser generated files
find . -path "*chapi-ast-dart*" -name "*Parser*.java" -o -path "*chapi-ast-dart*" -name "*Parser*.kt" | head -10Repository: phodal/chapi
Length of output: 38
🏁 Script executed:
# Look at the full grammar for cascade rules
rg -n "^cascade" ./chapi-ast-dart/src/main/antlr/Dart2Parser.g4 -A 10Repository: phodal/chapi
Length of output: 718
🏁 Script executed:
# Check if there are cascadeSection() and cascadeSections() methods in the listener
rg -n "cascadeSection" ./chapi-ast-dart/src/main/kotlin/chapi/ast/dartast/DartFullIdentListener.kt -B 2 -A 2Repository: phodal/chapi
Length of output: 305
🏁 Script executed:
# Search for how other list-like contexts are handled in the listener
rg -n "List|list()" ./chapi-ast-dart/src/main/kotlin/chapi/ast/dartast/DartFullIdentListener.kt | head -20Repository: phodal/chapi
Length of output: 1420
🏁 Script executed:
# Look at the full cascade-related code in the listener
rg -n "cascade" ./chapi-ast-dart/src/main/kotlin/chapi/ast/dartast/DartFullIdentListener.kt -B 5 -A 10Repository: phodal/chapi
Length of output: 1030
🏁 Script executed:
# Check if there's a cascadeSectionList method by looking at ANTLR generated patterns
rg "cascadeSection\(\)" ./chapi-ast-dart/src/main/kotlin/chapi/ast/dartast/ -B 2 -A 2Repository: phodal/chapi
Length of output: 641
🏁 Script executed:
# Search for ANTLR rule handling in the build or generated files
find ./chapi-ast-dart -name "*Dart2Parser*" -type f | head -20Repository: phodal/chapi
Length of output: 102
🏁 Script executed:
# Check if there's documentation or generated code that shows cascadeSection methods
find ./chapi-ast-dart -type f -name "*.g4" -o -name "*.java" -o -name "*.kt" | xargs grep -l "CascadeContext" | head -5Repository: phodal/chapi
Length of output: 131
🏁 Script executed:
# Look for how recursive rules with multiple items are typically handled
rg "cascadeSection\(" ./chapi-ast-dart/src/main/kotlin/ -B 3 -A 3Repository: phodal/chapi
Length of output: 835
🏁 Script executed:
# Check if there's generated Java files with parser info
find ./chapi-ast-dart -type f -name "*.java" | head -20Repository: phodal/chapi
Length of output: 122
🏁 Script executed:
# Look at the grammar more carefully - check the cascade rule structure
cat ./chapi-ast-dart/src/main/antlr/Dart2Parser.g4 | sed -n '139,165p'Repository: phodal/chapi
Length of output: 523
🏁 Script executed:
# Check if there's a way to access multiple cascadeSections or if recursion handling is needed
# by looking at ANTLR documentation patterns - search for other recursive rules in the grammar
rg "^\s*:\s*\w+\s+\w+\s+\w+\s*\|" ./chapi-ast-dart/src/main/antlr/Dart2Parser.g4 | head -10Repository: phodal/chapi
Length of output: 38
🏁 Script executed:
# Let's understand the recursive structure better by creating a test to see how ANTLR handles this
# First, search for any tests or examples of cascade handling
rg -n "cascade" ./chapi-ast-dart/src/test --type kotlin --type java | head -20Repository: phodal/chapi
Length of output: 38
🏁 Script executed:
# Check the Dart language spec to confirm cascade semantics
# For now, let's look at how the listener typically handles recursion
rg "override fun enter" ./chapi-ast-dart/src/main/kotlin/chapi/ast/dartast/DartFullIdentListener.kt | head -10Repository: phodal/chapi
Length of output: 909
🏁 Script executed:
# Look for other methods that might show how nested/recursive contexts are handled
rg -n "ctx\.[a-zA-Z]+\(\)" ./chapi-ast-dart/src/main/kotlin/chapi/ast/dartast/DartFullIdentListener.kt | grep -E "List|forEach|for\s" | head -15Repository: phodal/chapi
Length of output: 531
🏁 Script executed:
# Let's understand how ANTLR generates methods for the cascade rule
# Search for any Java generated code that might show us the structure
find ./chapi-ast-dart -name "*Cascade*" -type fRepository: phodal/chapi
Length of output: 38
🏁 Script executed:
# Look at the Dart2Parser.g4 more carefully to understand cascadeSection occurrences
rg -n "cascadeSection" ./chapi-ast-dart/src/main/antlr/Dart2Parser.g4Repository: phodal/chapi
Length of output: 238
🏁 Script executed:
# Create a test to understand the parse tree structure
# Let's check if there are any example Dart code files in tests
find ./chapi-ast-dart -name "*.dart" -o -name "*test*" -type f | grep -i "test\|example" | head -10Repository: phodal/chapi
Length of output: 179
🏁 Script executed:
# Let's check the test file to see if there are cascade examples
cat ./chapi-ast-dart/src/test/resources/grammar/class_example.dartRepository: phodal/chapi
Length of output: 1723
🏁 Script executed:
# Let's create a simple Python script to understand ANTLR's typical method generation
# For a rule like: cascade : cascade DD cascadeSection | conditionalExpression DD cascadeSection
# ANTLR generates methods to access children
python3 << 'EOF'
# In ANTLR, for a rule with multiple occurrences of the same element:
# rule : rule ELEMENT child | base ELEMENT child
# The generated context class has:
# - element() - returns first occurrence
# - element(int i) - returns occurrence at index i
# - elementList() or element() via list pattern - returns all
# For our cascade rule:
# cascade : cascade DD cascadeSection | conditionalExpression QUDD? DD cascadeSection
# This means a CascadeContext would have multiple cascadeSection children in the parse tree
# The typical ANTLR generation would be:
print("Typical ANTLR methods for 'cascadeSection' in CascadeContext:")
print("1. cascadeSection() - returns first CascadeSectionContext")
print("2. cascadeSection(int i) - returns CascadeSectionContext at index i")
print("3. cascadeSectionList() OR direct list access - returns List<CascadeSectionContext>")
print("\nFor left-recursive rules, all sections are available as child nodes")
EOFRepository: phodal/chapi
Length of output: 142
🏁 Script executed:
# Let's check if there's generated code or source that shows CascadeContext methods
# by looking at how to build this project
cat ./chapi-ast-dart/pom.xml | grep -A 20 "antlr4-maven-plugin" | head -30Repository: phodal/chapi
Length of output: 112
🏁 Script executed:
# Let's look at the build configuration for dart AST
ls -la ./chapi-ast-dart/ | grep -E "pom|build|gradle"Repository: phodal/chapi
Length of output: 122
🏁 Script executed:
# Check how ANTLR-generated code typically works by examining if there's any documentation
# or other language implementations that handle similar recursive rules
rg "getChild\|get\(int\)" ./chapi-ast-dart/src/main/kotlin/chapi/ast/dartast/ -B 2 -A 2 | head -20Repository: phodal/chapi
Length of output: 38
🏁 Script executed:
# Let's check if there's a way to understand the CascadeContext by looking at imports
head -50 ./chapi-ast-dart/src/main/kotlin/chapi/ast/dartast/DartFullIdentListener.ktRepository: phodal/chapi
Length of output: 1975
🏁 Script executed:
# Let's check how the grammar actually structures the parse tree by looking at ANTLR documentation patterns
# and check if there's a way to iterate through cascadeSections
# First, let's see if there are any List methods available
rg -n "\.getChild\|\.get\(|List|getChildCount" ./chapi-ast-dart/src/main/kotlin/chapi/ast/dartast/DartFullIdentListener.kt | head -20Repository: phodal/chapi
Length of output: 1361
🏁 Script executed:
# Check ANTLR version being used
cat ./chapi-ast-dart/build.gradle.ktsRepository: phodal/chapi
Length of output: 1600
🏁 Script executed:
# Let's look for any documentation or examples of how multi-child contexts are accessed
rg "override fun enter" ./chapi-ast-dart/src/main/kotlin/chapi/ast/dartast/DartFullIdentListener.kt -A 15 | grep -E "forEach|for\s|while|getChild|index" | head -10Repository: phodal/chapi
Length of output: 326
🏁 Script executed:
# Let's search for documentation on how cascadeSectionList might be accessed
# by checking if there's a pattern in ANTLR-generated code
rg "List<.*Context>" ./chapi-ast-dart/src/main/kotlin/chapi/ast/dartast/DartFullIdentListener.kt | head -10Repository: phodal/chapi
Length of output: 38
🏁 Script executed:
# Let's verify the correct way to access multiple cascadeSections by checking
# how the Dart2Parser might handle this - look for getRuleIndex or other ANTLR patterns
rg "getRuleIndex\|override" ./chapi-ast-dart/src/main/kotlin/chapi/ast/dartast/DartFullIdentListener.kt | head -20Repository: phodal/chapi
Length of output: 38
🏁 Script executed:
# Create a test to understand the structure - let's look at what a Dart cascade looks like
# and verify it should handle multiple sections
python3 << 'EOF'
# Dart cascade example: obj..method1()..method2()..method3()
# This creates a chain where methods are called on the same object
# Grammar shows: cascade : cascade DD cascadeSection | ...
# This is left-recursive, so the parse tree for obj..m1()..m2() would be:
# Cascade(
# Cascade(
# obj DD cascadeSection(m1)
# ) DD cascadeSection(m2)
# )
# The current code only calls ctx.cascadeSection() once, which gets ONE section
# But in ANTLR, recursive rules have multiple children with the same rule type
print("Analysis of cascade rule structure:")
print("1. Left-recursive rule allows chaining")
print("2. Each section after .. is a separate cascadeSection")
print("3. Code must either:")
print(" - Call cascadeSection(0), cascadeSection(1), etc.")
print(" - Or use cascadeSectionList() if available")
print(" - Or recursively call cascade() to traverse up")
print("\nCurrent code only uses ctx.cascadeSection() - INCOMPLETE")
EOFRepository: phodal/chapi
Length of output: 412
Cascade expressions only process the first method call in a chain.
The current implementation calls ctx.cascadeSection() which returns only a single cascadeSection, but the grammar allows multiple chained sections via left-recursion (e.g., obj..method1()..method2()). This means subsequent method calls in cascade chains are silently ignored.
To fix this, iterate through all cascadeSection children. In ANTLR 4.13.2, you can access multiple children via cascadeSection(int index) or similar enumeration method:
override fun enterCascade(ctx: Dart2Parser.CascadeContext?) {
if (!isInFunction) return
if (ctx == null) return
var index = 0
while (true) {
val cascadeSection = ctx.cascadeSection(index) ?: break
extractCascadeCall(cascadeSection)
index++
}
}Or use a list accessor if available (e.g., ctx.cascadeSectionList()).
🤖 Prompt for AI Agents
In `@chapi-ast-dart/src/main/kotlin/chapi/ast/dartast/DartFullIdentListener.kt`
around lines 232 - 255, The enterCascade method currently only retrieves a
single cascadeSection from the Dart2Parser.CascadeContext so chained cascade
calls are missed; update enterCascade(Dart2Parser.CascadeContext?) to iterate
over all cascadeSection children (e.g., using ctx.cascadeSection(index) in a
loop or the list accessor if available) and call extractCascadeCall for each
section so every method in a cascade chain is captured; keep the
null/isInFunction guards and ensure extractCascadeCall and buildPosition usage
remain unchanged.
Summary
Add support for parsing Dart source code using ANTLR4, with support for Dart 3 class modifiers.
Features
Library & Imports
library)show,hide, andasmodifiersType Declarations
typedef)Dart 3 Class Modifiers
sealed classbase classinterface classfinal classmixin classbase mixinsealed mixinClass Members
Top-level Declarations
Analysis Features
Grammar
Based on the Dart 2 grammar from antlr/grammars-v4, extended with Dart 3 class modifier keywords (
sealed,base,when).Test plan
All 22 tests passing.
Summary by CodeRabbit
New Features
Tests
Chores
✏️ Tip: You can customize this high-level summary in your review settings.