Skip to content

Native complexity rules misclassify switch/case for c/cpp/kotlin/scala (and drop the switch container entirely for swift); comment-line undercounting for c/cpp/kotlin/swift/scala #2058

Description

@carlos-alm

Summary

While porting the native LangRules/HalsteadRules for c/cpp/kotlin/swift/scala/bash to the TS/WASM side (#1923), several latent correctness issues were found in the existing native rules (crates/codegraph-core/src/ast_analysis/complexity.rs). All are pre-existing — unrelated to the TS-side port — and were mirrored verbatim into the new TS rules for exact engine parity, since fixing rule semantics is a separate, larger workstream from fixing rule presence (which is #1923's scope). Filing here so they don't get silently perpetuated.

Finding 1: case-node shadowing in C_RULES, CPP_RULES, KOTLIN_RULES, SCALA_RULES

In walk(), node classification order is: logical-op check → optional-chain check → is_branch(kind) (returns unconditionally on match) → pattern-C-else check → is_case(kind). So any node type listed in both branch_nodes and case_nodes is always handled as a generic branch — the case_nodes arm for that type is dead code.

Four of the five affected languages put the actual per-case node type in branch_nodes alongside (or instead of) the switch container:

  • C_RULES / CPP_RULES: branch_nodes includes "case_statement" (also listed in case_nodes); the switch container "switch_statement" is not in branch_nodes at all (only in switch_like_nodes, which is only consulted from inside the branch handler).
  • KOTLIN_RULES: branch_nodes includes both the container "when_expression" and the case "when_entry" (also in case_nodes).
  • SCALA_RULES: branch_nodes includes both the container "match_expression" and the case "case_clause" (also in case_nodes).

Every unaffected switch-having language in the same file (JS/TS, Java, C#, PHP, Ruby, Bash) puts only the container in branch_nodes + switch_like_nodes (contributing nesting once, cyclomatic 0) and puts the case node in case_nodes only (flat cyclomatic += 1, no per-case cognitive/nesting weight). For C/C++/Kotlin/Scala, each case arm instead gets the full "regular branch" treatment (cognitive += 1 + nesting_level, cyclomatic += 1), which very likely inflates cognitive-complexity scores for every switch/when/match statement in these four languages relative to the metric's intended semantics (and relative to every other supported language).

Finding 2: SWIFT_RULES drops the switch container from complexity entirely

SWIFT_RULES.branch_nodes and .nesting_nodes never include "switch_statement" — only the per-case "switch_entry" appears (in both branch_nodes and case_nodes, hitting the same shadowing bug as Finding 1). Net effect: a Swift switch statement contributes zero nesting for its cases and each case arm is scored as an unnested "regular branch" rather than as a switch case.

Finding 3: comment_prefixes() omits */*/ continuation-line prefixes for c/cpp/kotlin/swift/scala

"javascript" | "typescript" | "tsx" | "go" | "rust" | "java" | "csharp" => {
    &["//", "/*", "*", "*/"]
}
...
"c" | "cpp" => &["//", "/*"],
"kotlin" => &["//", "/*"],
"swift" => &["//", "/*"],
"scala" => &["//", "/*"],

All of these are C-family/JVM languages with the same /** ... */ block-comment style as JS/Java/C#, but only get the 2-entry ["//", "/*"] list instead of the 4-entry list that also matches bare * continuation lines and closing */ lines. For a function with a multi-line Javadoc-style comment, only the first line (/**) is counted as a comment line for these 5 languages — undercounting commentLines and skewing the Maintainability Index input.

Scope note

Not fixed as part of #1923, which is scoped to adding missing complexity/Halstead configs for languages that have none — not auditing the correctness of configs that already exist and are already used in production by the native engine today (real native users are seeing these numbers right now; changing the methodology needs its own careful before/after validation). Both TS-side mirrors intentionally match current native behavior byte-for-byte in #1923's PR to keep native/WASM parity. Fixing the underlying branch_nodes/case_nodes/switch_like_nodes design for c/cpp/kotlin/swift/scala correctly — likely moving the container into branch_nodes+switch_like_nodes+nesting_nodes and dropping the per-case node from branch_nodes on both engines, matching the pattern already used by JS/Java/C#/PHP/Ruby/Bash — is tracked here as follow-up work.

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