Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
4 changes: 2 additions & 2 deletions .bca-baseline.toml
Original file line number Diff line number Diff line change
Expand Up @@ -915,7 +915,7 @@ value = 15.0
path = "src/metrics/abc/kotlin.rs"
qualified = "KotlinCode::compute"
metric = "cyclomatic"
value = 18.0
value = 20.0

[[entry]]
path = "src/metrics/abc/objc.rs"
Expand Down Expand Up @@ -1203,7 +1203,7 @@ value = 7.0
path = "src/spaces/compute.rs"
qualified = "metrics_inner"
metric = "halstead.effort"
value = 119147.7514530567
value = 116715.6053686016

[[entry]]
path = "src/spaces/compute.rs"
Expand Down
144 changes: 144 additions & 0 deletions CHANGELOG.md
Original file line number Diff line number Diff line change
Expand Up @@ -135,6 +135,150 @@ for historical reference.

### Fixed

- **ABC now counts a boolean test that the grammar gives its own
production** (#1449). A construct spelled as a dedicated node rather
than a `binary_expression` reaches no comparison-token arm, so Groovy's
`in` / `!in` / `===` / `!==` / `=~` / `==~`, Perl's bare `//` and `m{}`
matches, and Ruby's `a in Integer` each scored zero conditions in an
`if` predicate or a `&&` operand where a bare identifier scores one.
Groovy's `===` / `!==` / `=~` / `==~` are counted as comparison
operator tokens rather than terminal kinds, so they now also score
outside a boolean slot, as `==` already did; `in` / `!in` cannot take
that route, because `in` is shared with `for_in_statement` and `!in`
emits no operator token at all. **Metric drift:** `abc.conditions`
rises by one per occurrence of these constructs in a boolean slot, and
by one per `===` / `!==` / `=~` / `==~` anywhere in Groovy.

- **Kotlin ABC counts a null-asserted or cast condition, and C# ABC
counts `??`** (#1459). Both languages model a condition slot that
delegates to a per-language helper, and both helpers contributed
nothing for a valid boolean expression outside their allowlist. In
Kotlin, `when { a!! -> … }` and `when { a as Boolean -> … }` scored
zero against a decision count of one: `unary_expression` is a single
kind for both the prefix `!x` and the postfix `x!!`, so the slot routed
the null assertion in as handled while the peel looked for the operand
on the wrong side of the token, and an `as` cast was not recognised at
all. The peel now reads its operand through the grammar's fields and
unwraps parentheses, `!`, `!!` and `as` in any combination; the safe
cast `as?` is deliberately left to the `AsQMARK` token that already
counts it, so the two cast spellings come out level rather than the
safe one scoring twice. In C#, `??` was a cyclomatic decision that no
ABC arm counted, so `when b ?? false` scored one fewer condition than
`when x > 2`, `when E(x)` or `when x is int` — the spelling-dependence
#1422 exists to remove. It joins the condition tokens, matching the
JS/TS family, which has always counted it. `??=` is unchanged and
remains an assignment, as in the JS family. **Metric drift:** Kotlin
`abc.conditions` rises by one per `!!` or `as` cast standing as a
predicate or a `&&` / `||` operand; C# `abc.conditions` rises by one
per `??` anywhere.

- **C# comparison-operator overloads no longer score a spurious
condition** (#1420). C# overloads six comparison operators and #1297
gated only `<` and `>`; the other four are distinct tokens that
reached a different arm, so `operator <=`, `operator >=`,
`operator ==` and `operator !=` each scored one ABC condition on their
*declaration*, where the token names the operator being defined rather
than applying it. All six now share one arm gated on a
`binary_expression` parent, which also subsumes #1383's
`relational_pattern` denial and fails closed on a grammar bump.
**Metric drift:** C# `abc.conditions` falls by one for each
`operator <=` / `>=` / `==` / `!=` declaration. `operator <` and
`operator >` already scored zero under #1297's allowlist, so a type
overloading all six — which C# requires to be declared in pairs —
falls by four, not six.

- **C# ABC counts a primary-constructor base call** (#1406). C# 12 lets a
class, struct or record declare its constructor in the header and pass
arguments to its base there — `class Sub(int x) : Base(x)` — which
invokes the base constructor exactly as the `: base(x)` initializer
added in #1279 does, but scored nothing. It is now one branch, in both
spellings tree-sitter-c-sharp uses: the record form nests the arguments
under a `primary_constructor_base_type`, the class form hangs them
straight off the `base_list`, so one node would have covered only half
the declarations. The arm is gated on that `base_list` parent, since an
`argument_list` is otherwise the argument list of every call in the
file. A base type passing no arguments (`struct S(int x) : IBase`) is
unchanged at zero. This is the C# sibling of Kotlin's #1384.
**Metric drift:** C# `abc.branches` rises by one per type passing
arguments to its base from a primary constructor.

- **A C# `when` guard now counts as a decision in both cyclomatic
complexity and ABC** (#1422). Cyclomatic had no arm for either guard
spelling — `when_clause` on a switch arm or `case` section, and
`catch_filter_clause` on `catch (E e) when (…)` — so a guarded arm
scored one decision where it has two ways to fail: the pattern does not
match, or it matches and the guard is false. ABC counted whatever
operator happened to sit inside the guard, so `when x > 2` scored one
condition while the equivalent `when IsEven(x)` scored none. The guard
is now a condition slot like an `if` condition, so every spelling
scores exactly one and a compound guard keeps its sub-structure. The
same change adds the bare type test `x is int` to C#'s terminal-bool
operand set: it is `is_expression`, a distinct kind from the
`is_pattern_expression` of `x is int y`, and only the latter was
listed — so the two spellings of one test disagreed, `if (x is int)`
scoring zero conditions against a cyclomatic decision of one.
**Metric drift:** C# `cyclomatic` (standard and modified) gains one per
guard; `abc.conditions` gains one for any guard not already
operator-shaped, and one for every bare `is` type test in a boolean
slot. `wmc` and `mi` are derived from cyclomatic and move with it — the
C# corpus snapshot records `class_wmc_sum` 27 → 29 and a matching fall
in all three `mi` variants — so a `wmc` or `mi` threshold can newly
fire on an unedited C# file carrying guarded arms.

- **Kotlin no longer double-counts a subject-less `when` arm's comparison
operator** (#1421). `when { x > 5 -> 1; x < 0 -> 2; else -> 0 }`
reported 4 conditions against a cyclomatic decision count of 2, because
the arm added a blanket one on top of the comparison the token arms
already scored; all six comparison spellings were affected, not only
`<` and `>`. A subject-less arm now scores its condition through the
same slot an `if` predicate uses, while a subject-ful arm keeps its
per-entry count — its pattern is not an independent boolean expression,
so the implicit `subject == pattern` is a decision nothing in the
source spells. As part of the same fix, Kotlin's `is` and `in` tests
count as conditions wherever a boolean is evaluated: `if (a is String)`
and `if (a in 1..2)` previously scored zero against a decision count of
one, as did `a and b` — Kotlin spells boolean `and` / `or` / `xor` as
infix *functions*, so they parse as `infix_expression` rather than as
a binary expression and no token arm ever saw them either.
**Metric drift:** Kotlin `abc.conditions` falls for subject-less
`when` arms carrying a comparison, and rises for any `is` / `in` test
and any infix boolean call in a boolean slot.

- **An enum constant carrying constructor arguments is counted as a
branch** (#1407). `A(1)` in `enum E { A(1), B, C(2); }` invokes the
enum's constructor, so it is an object construction under
Fitzpatrick's rule — the same as the `super(…)` / `this(…)` and
`class Sub : Base(1, 2)` forms counted since #1279 and #1384 — and
scored zero in Kotlin, Java and Groovy alike. Each arm is gated on the
entry's argument-list child, so a constant with no arguments (`B`), and
every constant of an enum with no constructor, stays at zero; Java's
gate names `argument_list` specifically, so an annotated constant
cannot satisfy it through the distinct `annotation_argument_list`
production. **Metric drift:** `abc.branches` rises by one per
argument-carrying enum constant in Kotlin, Java and Groovy.

- **A truthy numeric literal in a boolean operand slot now scores a unary
condition in PHP, Groovy and the C family** (#1410). `abc.conditions`
keys on a per-language terminal-bool kind set, and six of them named no
numeric kind — the last deferrals from #1379. `$a && 1` scored one
condition against `$a && $b`'s two; `def f(a) { a && 1 }` the same in
Groovy; and in C, C++, Mozcpp and Objective-C the omission showed
*within* one language, `if (true)` scoring one condition and `if (1)`
none, because `"true"` was in the set and `number_literal` was not.
Each set now names every numeric kind its grammar emits — `integer` /
`float` for PHP, one consolidated `number_literal` for Groovy and for
each C-family grammar — verified by `bca dump` per language rather than
by an alias sweep, which is the check #1379's first cut skipped. The
C-family set also gains `char_literal`: `'c'` has integral type and is
truthy exactly as a number is. PHP's `float` *type* keyword, C++'s
`user_defined_literal` and Objective-C's `version_number` are
deliberately excluded — none is a truthy value literal. **Metric
drift:** `abc.conditions` and `abc.magnitude` rise for PHP, Groovy, C,
C++, Mozcpp and Objective-C code using a numeric (or, in the C family,
character) literal as a bare `&&` / `||` operand, an `if` / `while` /
`do`-`while` / `for` condition, or a ternary condition — `while (1)`
and `do { … } while (0)` being the common idioms.

- **Single-language feature subsets build their tests again, and CI
gates them** (#1426). Five of the twenty-two single-language subsets
of `-p big-code-analysis --all-targets` failed, all in
Expand Down
Loading