Summary
While attempting to wire Dart into COMPLEXITY_RULES/HALSTEAD_RULES (part of #1923's tier-2 language rollout), I found that tree-sitter-dart's grammar puts a function's body in a node sibling to its signature, not a child of it:
program
function_signature [line 1] <- name, params, return type
function_body [line 1-3] <- SIBLING, not a child of function_signature
block
...actual statements...
Confirmed identically for methods (method_signature + sibling function_body under class_body), and for both block bodies ({ ... }) and arrow bodies (=> expr;).
Why this breaks analysis
functionNodes: new Set(['function_signature', 'method_signature']) is already used by the existing dataflowDart config in src/ast-analysis/rules/b2.ts (added for P5 Batch B2), and is exactly what I was about to add for complexity/Halstead. But every consumer of functionNodes in this codebase assumes the function's entire relevant content lives in the matched node's own subtree:
- Standalone DFS reference (
computeFunctionComplexity/computeHalsteadMetrics in src/features/complexity.ts): walks only functionNode's own children. Since function_signature/method_signature's only children are the parameter list, walking it can never see any if/for/while/etc. in the body — cyclomatic/cognitive complexity for every Dart function would silently compute as the trivial base case (verified directly: a function with an if/else-if/else chain still reports cyclomatic: 1, not 3).
- Visitor-based batch engine (
src/ast-analysis/visitor.ts + src/ast-analysis/visitors/complexity-visitor.ts, the actual path used during codegraph build): dispatches enterFunction/exitFunction at the boundaries of nodes matching functionNodeTypes. exitFunction fires as soon as the walker finishes function_signature's own subtree — i.e. before the walker ever reaches the sibling function_body — so the accumulator for that function is finalized (and activeFuncNode reset to null) before a single statement in the real body is visited. enterNode then no-ops for the entire body (if (fileLevelWalk && !activeFuncNode) return;).
- This almost certainly means the existing
dataflowDart config has the same problem — return-value tracking and any body-scoped dataflow analysis for Dart may currently be silently producing empty/no-op results. I have not verified this directly (out of scope for what I was doing), but the mechanism is identical.
Both the JS/TS engine and (presumably, unverified) the mirrored Rust native engine would need this fix — crates/codegraph-core/src/ast_analysis/complexity.rs's LangRules/function-boundary handling likely has the same assumption, though Dart isn't wired there yet either (this bug is why I didn't finish wiring it).
Why this was never caught
Dart currently has zero entries in COMPLEXITY_RULES/HALSTEAD_RULES/CFG_RULES (tracked by #1923) — the only existing consumer, dataflowDart, is used for parameter/return extraction that's mostly satisfied by data already present within function_signature's own children (the parameter list itself), so a silently-empty body walk may not have produced an obviously-wrong result anyone noticed.
Suggested fix direction
Not a config-only port. Needs a generic mechanism — since this "signature separate from body" shape could recur in other not-yet-supported languages — for a language's rules to declare a body-sibling node type, and for:
src/ast-analysis/shared.ts's findFunctionNode (used by the on-demand complexity lookup path) to substitute the sibling when present.
src/ast-analysis/visitor.ts's enterFunction/exitFunction dispatch to keep the function scope active through the sibling body, not just the matched node's own subtree.
- The equivalent Rust mirror in
crates/codegraph-core/src/ast_analysis/.
- Verification (and likely a fix) that
dataflowDart in src/ast-analysis/rules/b2.ts produces correct results once this lands — it may have been silently broken since P5 Batch B2.
Evidence
Verified via direct AST dumps (tree-sitter-dart, both createParsers()-based WASM parsing and node-types.json) for:
- Top-level functions and class methods — same shape.
- Single-line (
int bar(int x) {) and multi-line ({ on its own line) signature/body splits — body's start line is not reliably adjacent to the signature's start line, so a naive "same start line" substitution in findFunctionNode is not sufficient on its own.
- Arrow-bodied functions (
int bar(int x) => x + 1;) — also use function_body as the sibling wrapper (containing => + expression), so the fix should handle both body shapes uniformly.
Filed per this repo's scope-discipline convention — discovered while working on #1923, but this is a distinct, foundational bug that blocks correctly wiring complexity (and possibly means dataflow is already broken) for Dart specifically, not something #1923's per-language rule-porting scope should absorb.
Summary
While attempting to wire Dart into
COMPLEXITY_RULES/HALSTEAD_RULES(part of #1923's tier-2 language rollout), I found that tree-sitter-dart's grammar puts a function's body in a node sibling to its signature, not a child of it:Confirmed identically for methods (
method_signature+ siblingfunction_bodyunderclass_body), and for both block bodies ({ ... }) and arrow bodies (=> expr;).Why this breaks analysis
functionNodes: new Set(['function_signature', 'method_signature'])is already used by the existingdataflowDartconfig insrc/ast-analysis/rules/b2.ts(added for P5 Batch B2), and is exactly what I was about to add for complexity/Halstead. But every consumer offunctionNodesin this codebase assumes the function's entire relevant content lives in the matched node's own subtree:computeFunctionComplexity/computeHalsteadMetricsinsrc/features/complexity.ts): walks onlyfunctionNode's own children. Sincefunction_signature/method_signature's only children are the parameter list, walking it can never see anyif/for/while/etc. in the body — cyclomatic/cognitive complexity for every Dart function would silently compute as the trivial base case (verified directly: a function with an if/else-if/else chain still reportscyclomatic: 1, not3).src/ast-analysis/visitor.ts+src/ast-analysis/visitors/complexity-visitor.ts, the actual path used duringcodegraph build): dispatchesenterFunction/exitFunctionat the boundaries of nodes matchingfunctionNodeTypes.exitFunctionfires as soon as the walker finishesfunction_signature's own subtree — i.e. before the walker ever reaches the siblingfunction_body— so the accumulator for that function is finalized (andactiveFuncNodereset tonull) before a single statement in the real body is visited.enterNodethen no-ops for the entire body (if (fileLevelWalk && !activeFuncNode) return;).dataflowDartconfig has the same problem — return-value tracking and any body-scoped dataflow analysis for Dart may currently be silently producing empty/no-op results. I have not verified this directly (out of scope for what I was doing), but the mechanism is identical.Both the JS/TS engine and (presumably, unverified) the mirrored Rust native engine would need this fix —
crates/codegraph-core/src/ast_analysis/complexity.rs'sLangRules/function-boundary handling likely has the same assumption, though Dart isn't wired there yet either (this bug is why I didn't finish wiring it).Why this was never caught
Dart currently has zero entries in
COMPLEXITY_RULES/HALSTEAD_RULES/CFG_RULES(tracked by #1923) — the only existing consumer,dataflowDart, is used for parameter/return extraction that's mostly satisfied by data already present withinfunction_signature's own children (the parameter list itself), so a silently-empty body walk may not have produced an obviously-wrong result anyone noticed.Suggested fix direction
Not a config-only port. Needs a generic mechanism — since this "signature separate from body" shape could recur in other not-yet-supported languages — for a language's rules to declare a body-sibling node type, and for:
src/ast-analysis/shared.ts'sfindFunctionNode(used by the on-demand complexity lookup path) to substitute the sibling when present.src/ast-analysis/visitor.ts'senterFunction/exitFunctiondispatch to keep the function scope active through the sibling body, not just the matched node's own subtree.crates/codegraph-core/src/ast_analysis/.dataflowDartinsrc/ast-analysis/rules/b2.tsproduces correct results once this lands — it may have been silently broken since P5 Batch B2.Evidence
Verified via direct AST dumps (tree-sitter-dart, both
createParsers()-based WASM parsing andnode-types.json) for:int bar(int x) {) and multi-line ({on its own line) signature/body splits — body's start line is not reliably adjacent to the signature's start line, so a naive "same start line" substitution infindFunctionNodeis not sufficient on its own.int bar(int x) => x + 1;) — also usefunction_bodyas the sibling wrapper (containing=>+ expression), so the fix should handle both body shapes uniformly.Filed per this repo's scope-discipline convention — discovered while working on #1923, but this is a distinct, foundational bug that blocks correctly wiring complexity (and possibly means dataflow is already broken) for Dart specifically, not something #1923's per-language rule-porting scope should absorb.