feat: add complexity metrics for Zig (#1923) - #2281
Conversation
Progress on #1923 — adds zig complexity/Halstead support (tier 2). tree-sitter-zig's if_statement wraps its else branch in an else_clause node (Pattern A, same as JS/C#/Rust), confirmed by parsing if/else-if/else and inspecting the S-expression. and/or/orelse are keyword operators sharing the generic binary_expression node type, same shared-type pattern as Lua's and/or. catch_expression (expr catch fallback, expr catch |err| {...}) is treated as a branch/nesting node like C/C++/ObjC/C#'s catch_clause, since its fallback can be an arbitrary block with its own control flow. try_expression is Halstead-only (not a branch), mirroring Rust's ? operator. Wires COMPLEXITY_RULES/HALSTEAD_RULES (TS) and lang_rules()/halstead_rules() (native) for zig, plus a zig entry in both engines' comment-prefix tables (zig has no block comments, only //). Verified native and WASM produce byte-identical codegraph complexity --health --json output on the existing zig resolution fixture and a hand-built fixture covering if/else-if/else, while, for-range, switch with multi-value cases, orelse, catch (both expression and block-with- payload forms), and try. docs check acknowledged — README's language table tracks imports/exports/ call-sites/heritage/dataflow, not complexity; no per-language complexity matrix exists in any of the three docs to update.
Greptile SummaryAdds Zig complexity and Halstead analysis to the TypeScript/WASM and native Rust engines.
Confidence Score: 5/5The PR appears safe to merge, with only non-blocking regression-test completeness remaining. The prior coverage thread remains partially outstanding despite the reply claiming it was fixed: the added branch cases are concrete, but the Zig Halstead assertion only checks for positive volume and the native cases never calculate Halstead, so the listed literal, call, field-access, and indexing classifications remain unverified; no blocking failure remains. Files Needing Attention: tests/unit/complexity.test.ts and crates/codegraph-core/src/ast_analysis/complexity.rs Important Files Changed
Reviews (2): Last reviewed commit: "test: add regression coverage for Zig co..." | Re-trigger Greptile |
| // is NOT a branch — it propagates the error up rather than branching | ||
| // locally, mirroring how Rust's `?` operator is Halstead-only (rust.ts). | ||
|
|
||
| export const complexity: ComplexityRules = { |
There was a problem hiding this comment.
Zig metric rules lack regression coverage
The new Zig branch and token classifications are not covered by committed automated cases for else-if, switch arms, catch, logical operators, literals, calls, and indexing. This leaves future grammar or rule changes able to silently alter Zig complexity, Halstead, or maintainability metrics in either engine.
Knowledge Base Used:
Note: If this suggestion doesn't match your team's coding style, reply to this and let me know. I'll remember it for next time!
There was a problem hiding this comment.
Added regression coverage in the follow-up commit: 6 TS cases in tests/unit/complexity.test.ts (else-if, while+orelse, for-range+multi-value switch, catch with error-payload block, try-is-not-a-branch, Halstead volume) and 5 mirrored Rust cases in complexity.rs's test module. Expected values were computed by running the actual engine against each snippet (not hand-derived), then baked into the assertions.
Addresses Greptile feedback on #2281: the new Zig branch/token classifications (else-if, switch arms with multi-value cases, catch with error-payload block, orelse, try) had no committed automated cases, leaving future grammar or rule changes able to silently alter Zig metrics in either engine. Mirrors the existing ObjC test blocks in both tests/unit/complexity.test.ts and complexity.rs's test module. Expected values computed by running the actual engine against each snippet, not hand-derived.
Summary
Progress on #1923 — adds
zigcomplexity/Halstead support (tier 2).tree-sitter-zig's
if_statementwraps its else branch in anelse_clausenode whose single named child is either a nestedif_statement(else-if) or the terminal else body — confirmed by parsingif (..) {..} else if (..) {..} else {..}and inspecting the S-expression. This is Pattern A (JS/C#/Rust-style wrapper), even though the grammar internally tags that child with analternativefield name — the wrapper-node detection only checks the parent node's type, not field names, so that's immaterial.and/or/orelseare keyword operators sharing the single genericbinary_expressionnode type (confirmed by parsinga and b or canda orelse b) — same shared-type pattern as Lua'sand/or.catch_expression(expr catch fallback,expr catch |err| { .. }) is treated as a branch/nesting node, the same treatment C/C++/ObjC/C# givecatch_clause— its fallback can be an arbitrary block with its own control flow, not just a coalescing value.try_expression(try expr) is not a branch — it propagates the error up rather than branching locally, mirroring how Rust's?operator is Halstead-only.Wires
COMPLEXITY_RULES/HALSTEAD_RULES(TS) andlang_rules()/halstead_rules()(native) forzig, plus azigentry in both engines' comment-prefix tables (zig has no block comments, only//).Verification
npm run lint— cleannpm test— 273/273 test files, 4430 passed, 30 skipped, 2 todocargo test --lib(crates/codegraph-core) — 753/753 passedcodegraph complexity --health --jsonoutput on:tests/benchmarks/resolution/fixtures/zig/fixtureorelse,catch(both expression and block-with-payload forms), andtryRemaining scope on #1923
Tier 1 (c, cpp, kotlin, swift, scala, bash) and CUDA were already done. Objective-C (tier 2) done in #2233. Dart is blocked on #2182 (grammar structural issue) — do not attempt until that's fixed. Remaining unattempted: haskell, ocaml, ocaml-interface, fsharp, fsharp-signature, gleam, clojure, julia, r, erlang, solidity, groovy, verilog.
Test plan
npm run lintnpm testcargo test --libincrates/codegraph-core