Skip to content

C# WASM complexity visitor computes trivial complexity for bodyless interface method signatures; native correctly omits them #2055

Description

@carlos-alm

Summary

For C#, native and WASM disagree on whether an interface method signature (no body) should get a complexity entry at all:

  • crates/codegraph-core/src/extractors/csharp.rs's handle_interface_decl explicitly passes complexity: None, cfg: None for interface methods, with a comment: "Interface method declarations have no body — skip CFG and complexity to mirror the WASM extractor and avoid producing meaningless metrics for body-less declarations."
  • But the WASM/TS side's COMPLEXITY_RULES for C# (src/ast-analysis/rules/csharp.ts) puts method_declaration in functionNodes unconditionally (matching both bodied class methods and bodyless interface signatures, since C# uses the same node type for both):
    functionNodes: new Set(['method_declaration', 'constructor_declaration', 'lambda_expression', 'local_function_statement']),
    The complexity visitor walks any node matching functionNodes without checking whether it actually has a body — so once the file-level gate lets the visitor run, it computes a trivial-but-non-null { cyclomatic: 1, cognitive: 0 } for the interface stub too.

So the "mirror the WASM extractor" comment in csharp.rs describes intent, not actual current WASM behavor — WASM does not skip bodyless method_declaration nodes; it just walks them and gets a trivial result.

Reproduction

interface IRepo {
    bool Save(string id, int value);
}

class Repo : IRepo {
    public bool Save(string id, int value) {
        if (value < 0) { return false; }
        for (int i = 0; i < 3; i++) { if (i == value) { return true; } }
        return true;
    }
}
$ codegraph complexity --file Repo.cs -T --json --engine native
functions: 1   [ Repo.Save ]

$ codegraph complexity --file Repo.cs -T --json --engine wasm
functions: 2   [ Repo.Save, IRepo.Save ]   ← IRepo.Save has cyclomatic: 1, cognitive: 0

Per CLAUDE.md: "Both engines must produce identical results. If they diverge, the less-accurate engine has a bug." Here WASM is the less-accurate engine — it fabricates a complexity entry for a method with no executable body.

Suggested fix

The WASM complexity visitor (src/ast-analysis/visitors/complexity-visitor.ts / the shared walker) needs a real "does this function-node have a body" check before computing metrics — not just node-type membership in functionNodes — so it agrees with native's explicit exclusion for bodyless method_declaration nodes. This likely affects any language where the same node type is used for both bodied and bodyless declarations (C# interfaces; Java interfaces/abstract methods happen to already agree between engines only because native's java.rs also computes unconditionally for these — a related but separate inconsistency worth auditing at the same time).

Discovery context

Found while fixing #1922 (WASM complexity engine skips entire file when every function has a dotted name) — live-testing native vs WASM parity for C# interface + implementing-class fixtures as part of verifying the mirrored Definition.bodyless signal added in that fix. Independent, pre-existing bug in how each engine handles signature-only declarations that happen to share a node type with real methods — not fixed there to keep that PR scoped to the file-level complexity gate. Related to #2053 (same class of "signature-only node computed as if bodied" bug, opposite direction, for Rust trait methods).

Metadata

Metadata

Assignees

No one assigned

    Labels

    bugSomething isn't working

    Type

    No type

    Fields

    No fields configured for issues without a type.

    Projects

    No projects

    Milestone

    No milestone

    Relationships

    None yet

    Development

    No branches or pull requests

    Issue actions