Skip to content

Dart: function_signature/method_signature never contains its own body — breaks per-function complexity/dataflow analysis #2182

Description

@carlos-alm

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:

  1. 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).
  2. 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;).
  3. 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.

Metadata

Metadata

Assignees

No one assigned

    Labels

    No labels
    No labels

    Type

    No type

    Projects

    No projects

    Milestone

    No milestone

    Relationships

    None yet

    Development

    No branches or pull requests

    Issue actions