Skip to content

Conversation

@phodal
Copy link
Owner

@phodal phodal commented Jan 20, 2026

Summary

Add support for parsing Dart source code using ANTLR4, with support for Dart 3 class modifiers.

Features

  • Library & Imports

    • Parse library declarations (library)
    • Parse imports with show, hide, and as modifiers
  • Type Declarations

    • Classes (regular, abstract)
    • Mixins
    • Extensions
    • Enums
    • Type aliases (typedef)
  • Dart 3 Class Modifiers

    • sealed class
    • base class
    • interface class
    • final class
    • mixin class
    • base mixin
    • sealed mixin
  • Class Members

    • Constructors (regular, named, factory)
    • Methods, getters, setters
    • Operator overloads
    • Fields
  • Top-level Declarations

    • Functions
    • Variables
    • Getters/Setters
  • Analysis Features

    • Method call tracking
    • Constructor invocation tracking
    • Inheritance chain extraction

Grammar

Based on the Dart 2 grammar from antlr/grammars-v4, extended with Dart 3 class modifier keywords (sealed, base, when).

Test plan

  • Parse hello world program
  • Parse library names and imports
  • Parse classes with inheritance
  • Parse abstract classes
  • Parse mixins
  • Parse extensions
  • Parse enums
  • Parse constructors (regular, named, factory)
  • Parse getters and setters
  • Parse top-level functions and variables
  • Parse Dart 3 sealed classes
  • Parse Dart 3 base classes
  • Parse Dart 3 interface classes
  • Parse Dart 3 final classes
  • Parse Dart 3 mixin classes
  • Parse Dart 3 base/sealed mixins

All 22 tests passing.

Summary by CodeRabbit

  • New Features

    • Added full Dart language analysis support including parsing, tokenization, and AST extraction to recognize classes, functions, imports/exports, mixins, enums, extensions, constructors, annotations, and call/field info.
  • Tests

    • Added a comprehensive test suite and example Dart files covering wide Dart language features.
  • Chores

    • Added a new Dart analysis module to the build and included it in coverage aggregation.

✏️ Tip: You can customize this high-level summary in your review settings.

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.
Copilot AI review requested due to automatic review settings January 20, 2026 14:15
@coderabbitai
Copy link

coderabbitai bot commented Jan 20, 2026

Note

Other AI code review bot(s) detected

CodeRabbit 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.

📝 Walkthrough

Walkthrough

Adds a new chapi-ast-dart module that provides ANTLR Dart lexer/parser grammars, a Kotlin-based DartAnalyser and listeners to extract AST information, Gradle/ANTLR build config and tests/resources validating parsing and structural extraction.

Changes

Cohort / File(s) Summary
Build Configuration
build.gradle.kts, chapi-ast-dart/build.gradle.kts, settings.gradle.kts
Root build adds jacocoAggregation(project(":chapi-ast-dart")); new module Gradle config enables ANTLR 4.13.2, Kotlin/JVM, serialization, test setup, and generated grammar sources; module is added to include(...).
ANTLR Grammars
chapi-ast-dart/src/main/antlr/Dart2Lexer.g4, .../Dart2Parser.g4
New comprehensive lexer (Dart2Lexer.g4) and parser (Dart2Parser.g4) grammars covering Dart lexical tokens, literals, expressions, declarations, and many Dart 2/3 language constructs.
ANTLR Base Class
chapi-ast-dart/src/main/java/chapi/ast/antlr/Dart2LexerBase.java
Added abstract Dart2LexerBase extending ANTLR Lexer with a CheckNotOpenBrace() helper used by lexer grammar.
Kotlin Analyser & Utilities
chapi-ast-dart/src/main/kotlin/chapi/ast/dartast/DartAnalyser.kt, .../DartAstListener.kt
DartAnalyser provides analysis and identBasicInfo entry points, wiring lexer/parser and tree walk; DartAstListener supplies utilities for positions, annotations, type and modifier extraction.
Basic Identifier Listener
chapi-ast-dart/src/main/kotlin/chapi/ast/dartast/DartBasicIdentListener.kt
New listener that collects package/library, imports/exports, classes, mixins, enums, typedefs, top-level functions/fields and builds a CodeContainer representation.
Full Identifier Listener
chapi-ast-dart/src/main/kotlin/chapi/ast/dartast/DartFullIdentListener.kt
Extends basic listener with per-function state, call extraction (new/constructor/call/cascade), local variable tracking, and field initialization extraction.
Tests & Resources
chapi-ast-dart/src/test/kotlin/.../DartAnalyserTest.kt, .../resources/grammar/*.dart
Added extensive test suite exercising parsing of declarations, imports, classes, constructors, modifiers, generics, annotations and multiple test Dart files (hello world, class examples).

Sequence Diagram

sequenceDiagram
    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
Loading

Estimated Code Review Effort

🎯 4 (Complex) | ⏱️ ~45 minutes

Poem

🐰 A tiny hop, a grammar sprung,
I nibble tokens, lines are sung,
Parsers dance and listeners cheer,
CodeContainer gathers near,
Hooray — Dart lives in Chapi here! 🥕

🚥 Pre-merge checks | ✅ 2 | ❌ 1
❌ Failed checks (1 warning)
Check name Status Explanation Resolution
Docstring Coverage ⚠️ Warning Docstring coverage is 33.33% which is insufficient. The required threshold is 80.00%. Write docstrings for the functions missing them to satisfy the coverage threshold.
✅ Passed checks (2 passed)
Check name Status Explanation
Description Check ✅ Passed Check skipped - CodeRabbit’s high-level summary is enabled.
Title check ✅ Passed The title accurately describes the main change: adding Dart language support with Dart 3 class modifiers, which matches the substantial grammar additions, parser implementation, and test coverage in the changeset.

✏️ Tip: You can configure your own custom pre-merge checks in the settings.

✨ Finishing touches
  • 📝 Generate docstrings

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.

❤️ Share

Comment @coderabbitai help to get the list of available commands and usage tips.

Copy link

Copilot AI left a 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.

Comment on lines 15 to 33

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
Copy link

Copilot AI Jan 20, 2026

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.

Suggested change
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)
}

Copilot uses AI. Check for mistakes.
Comment on lines 271 to 275
// Handle await expressions
override fun enterAwaitExpression(ctx: Dart2Parser.AwaitExpressionContext?) {
// Mark that we're in an async context
}

Copy link

Copilot AI Jan 20, 2026

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.

Suggested change
// Handle await expressions
override fun enterAwaitExpression(ctx: Dart2Parser.AwaitExpressionContext?) {
// Mark that we're in an async context
}

Copilot uses AI. Check for mistakes.
Comment on lines 130 to 132
// Track identifiers for variable resolution
ctx?.identifier()?.let { id ->
// Could be a variable reference or function call
Copy link

Copilot AI Jan 20, 2026

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.

Suggested change
// 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

Copilot uses AI. Check for mistakes.
* 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:
Copy link

Copilot AI Jan 20, 2026

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".

Suggested change
* 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:

Copilot uses AI. Check for mistakes.
* 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:
Copy link

Copilot AI Jan 20, 2026

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".

Suggested change
* 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:

Copilot uses AI. Check for mistakes.
| SET_
| STATIC_
| TYPEDEF_
| FUNCTION_
Copy link

Copilot AI Jan 20, 2026

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.

Suggested change
| FUNCTION_

Copilot uses AI. Check for mistakes.
| SYNC_
| AWAIT_
| YIELD_
| DYNAMIC_
Copy link

Copilot AI Jan 20, 2026

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.

Suggested change
| DYNAMIC_

Copilot uses AI. Check for mistakes.
// Dart 3 class modifiers
SEALED_ : 'sealed';
BASE_ : 'base';
WHEN_ : 'when';
Copy link

Copilot AI Jan 20, 2026

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.

Suggested change
WHEN_ : 'when';

Copilot uses AI. Check for mistakes.
isFinal: Boolean = false,
isLate: Boolean = false
) {
// This will be handled by the parent class's current node
Copy link

Copilot AI Jan 20, 2026

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.

Suggested change
// 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)

Copilot uses AI. Check for mistakes.
@codecov
Copy link

codecov bot commented Jan 20, 2026

Codecov Report

❌ Patch coverage is 56.57658% with 241 lines in your changes missing coverage. Please review.
✅ Project coverage is 67.01%. Comparing base (b1ba8e7) to head (a04c00f).
⚠️ Report is 3 commits behind head on master.

Files with missing lines Patch % Lines
...kotlin/chapi/ast/dartast/DartBasicIdentListener.kt 64.12% 27 Missing and 100 partials ⚠️
.../kotlin/chapi/ast/dartast/DartFullIdentListener.kt 42.58% 66 Missing and 23 partials ⚠️
...c/main/kotlin/chapi/ast/dartast/DartAstListener.kt 16.66% 22 Missing and 3 partials ⚠️
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.
📢 Have feedback on the report? Share it here.

🚀 New features to boost your workflow:
  • ❄️ Test Analytics: Detect flaky tests, report on failures, and find test suite problems.
  • 📦 JS Bundle Analysis: Save yourself from yourself by tracking and limiting bundle sizes in JS merges.

Copy link

@coderabbitai coderabbitai bot left a 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 isAbstract and isStatic methods are unused in the codebase. Derived listeners (DartBasicIdentListener, DartFullIdentListener) use grammar-specific methods like modifiers?.any { it.ABSTRACT_() != null } and ctx.STATIC_() != null instead. These utility methods can be removed if not intended as future API, or kept as fallback utilities for consistency.

Comment on lines 462 to 498
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_
;
Copy link

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

⚠️ Potential issue | 🟠 Major

🧩 Analysis chain

🏁 Script executed:

# First, let's locate the file
find . -name "Dart2Parser.g4" -type f

Repository: 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.g4

Repository: 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.g4

Repository: 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 -n

Repository: 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 10

Repository: 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.g4

Repository: 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.g4

Repository: phodal/chapi

Length of output: 105


🏁 Script executed:

# Search for WHEN_ in lexer files
find chapi-ast-dart -name "*Lexer*" -type f | head -5

Repository: 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/null

Repository: 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/ -i

Repository: 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.g4

Repository: 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.g4

Repository: 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.g4

Repository: 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 -i

Repository: 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_.

Comment on lines 16 to 34
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
}
Copy link

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

⚠️ Potential issue | 🟠 Major

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.

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
Copy link

@coderabbitai coderabbitai bot left a 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 of shouldParseComplexFileWithAssert.

This test silently returns when the resource is missing, which can mask configuration issues. The test at lines 687-697 (shouldParseComplexFileWithAssert) properly addresses this with assertNotNull. 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.

currentFunctionCalls is cleared on enterFunctionBody and reset on exitFunctionBody, but the collected calls are never persisted to the function's CodeFunction object. 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, TypeType is set to the expression value and TypeValue is set to the parameter name. This seems backwards—typically TypeValue would hold the value and the name would be stored elsewhere.

Consider using TypeKey for 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 recording throw as a function call is appropriate.

Recording throw as a CodeCall with CallType.FUNCTION may pollute the call graph since throw is 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 CallType or annotation to differentiate it from actual function calls.

Comment on lines +232 to +255
// 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)
}
}
Copy link

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

⚠️ Potential issue | 🟠 Major

🧩 Analysis chain

🏁 Script executed:

# Find Dart grammar files
find . -name "*.g4" | head -20

Repository: phodal/chapi

Length of output: 999


🏁 Script executed:

# Search for cascadeSection in the codebase
rg -n "cascadeSection" --type-add 'grammar:*.g4' -A 3 -B 1

Repository: 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 -5

Repository: 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 -10

Repository: 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 10

Repository: 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 2

Repository: 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 -20

Repository: 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 10

Repository: 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 2

Repository: 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 -20

Repository: 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 -5

Repository: 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 3

Repository: 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 -20

Repository: 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 -10

Repository: 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 -20

Repository: 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 -10

Repository: 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 -15

Repository: 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 f

Repository: 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.g4

Repository: 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 -10

Repository: 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.dart

Repository: 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")
EOF

Repository: 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 -30

Repository: 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 -20

Repository: 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.kt

Repository: 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 -20

Repository: phodal/chapi

Length of output: 1361


🏁 Script executed:

# Check ANTLR version being used
cat ./chapi-ast-dart/build.gradle.kts

Repository: 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 -10

Repository: 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 -10

Repository: 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 -20

Repository: 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")
EOF

Repository: 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.

@phodal phodal merged commit d61af93 into master Jan 20, 2026
4 of 6 checks passed
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

2 participants