Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
Show all changes
25 commits
Select commit Hold shift + click to select a range
7f2a1fe
fix(getter/groovy): gate the Super arm on Wildcard
dekobon Sep 14, 2026
aa64989
feat(check): warn when a baseline entry goes stale
dekobon Sep 14, 2026
bf6252b
fix(abc/objc): count `@available` as a condition
dekobon Sep 14, 2026
41e107f
fix(abc/groovy): count indexing and navigation predicates
dekobon Sep 14, 2026
17ee482
fix(abc/csharp): count the null-forgiving predicate
dekobon Sep 14, 2026
b6c8747
fix(abc+cyclomatic/csharp): gate `case` on the switch arm
dekobon Sep 14, 2026
e82fc38
fix(abc): count Perl statement-modifier conditions
dekobon Sep 15, 2026
04cefe5
fix(metrics): count match guards in five more languages
dekobon Sep 15, 2026
61a2871
fix(abc): count non-numeric literal bool operands
dekobon Sep 15, 2026
747b0b3
fix(abc): score relational operators by use, not by slot
dekobon Sep 15, 2026
6121667
fix(abc/elixir): make the match guard a condition slot
dekobon Sep 15, 2026
a9100f3
fix(abc): extend the literal sweep to Perl, Ruby and Elixir
dekobon Sep 15, 2026
8a2d7bd
docs(lessons): record three merges from the batch
dekobon Sep 15, 2026
6820736
docs(rules): add perturbation and gate guidance
dekobon Sep 15, 2026
0e15c49
test(abc): gate hidden_literal_supertypes on its features
dekobon Sep 15, 2026
821f7cc
fix(abc): score an Elixir repeated guard as an or-chain
dekobon Sep 15, 2026
f94a447
fix(cyclomatic): count Groovy's safe-indexing operator
dekobon Sep 15, 2026
4e4703f
test(abc): reject an emptied operand row in both sweeps
dekobon Sep 15, 2026
69f4ad0
docs(changelog): describe ABC drift by the axis that moves
dekobon Sep 15, 2026
b6d9bde
test(abc): cover the defguard anchor in the Elixir guard gate
dekobon Sep 15, 2026
e485ecf
docs(abc): mark the infallible-lookup branches unreachable
dekobon Sep 15, 2026
a958d39
ci(codecov): set a reachable patch-coverage floor
dekobon Sep 15, 2026
7de897d
docs(ci): say why feature-matrix runs no tests
dekobon Sep 15, 2026
b8de5cc
ci(feature-matrix): gate union-gated test reachability
dekobon Sep 15, 2026
38a2e47
ci(feature-matrix): pass --locked to the feature-gate step
dekobon Sep 15, 2026
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
20 changes: 7 additions & 13 deletions .bca-baseline.toml
Original file line number Diff line number Diff line change
Expand Up @@ -263,6 +263,12 @@ qualified = "RubyCode::get_op_type"
metric = "halstead.effort"
value = 120805.48245794396

[[entry]]
path = "big-code-analysis-ast/src/macros/kind_sets.rs"
qualified = "<file>"
metric = "loc.sloc"
value = 1357.0

[[entry]]
path = "big-code-analysis-ast/src/node.rs"
qualified = "Node<'a>"
Expand Down Expand Up @@ -881,12 +887,6 @@ qualified = "options_from"
metric = "nexits"
value = 8.0

[[entry]]
path = "src/metrics/abc/csharp.rs"
qualified = "csharp_inspect_container"
metric = "cognitive"
value = 15.0

[[entry]]
path = "src/metrics/abc/go.rs"
qualified = "GoCode::compute"
Expand All @@ -899,12 +899,6 @@ qualified = "GoCode::compute"
metric = "halstead.effort"
value = 49999.43426005393

[[entry]]
path = "src/metrics/abc/groovy.rs"
qualified = "groovy_inspect_container"
metric = "cognitive"
value = 15.0

[[entry]]
path = "src/metrics/abc/java.rs"
qualified = "java_inspect_container"
Expand All @@ -927,7 +921,7 @@ value = 50110.01872952996
path = "src/metrics/abc/rust.rs"
qualified = "RustCode::compute"
metric = "cyclomatic"
value = 16.0
value = 15.0

[[entry]]
path = "src/metrics/cognitive/bash.rs"
Expand Down
35 changes: 34 additions & 1 deletion .claude/rules/testing.md
Original file line number Diff line number Diff line change
Expand Up @@ -96,6 +96,28 @@ perturbation then reports the integration guard as passing, which reads
as a dead guard rather than a harness artifact (#1270). Pass
`--no-fail-fast` in any multi-target perturbation run.

**Classify a build failure by `error[` or `could not compile`, never by
`error`.** `cargo test` prints `error: test failed` to stderr on an
ordinary red run, so a driver matching bare `error` reads every
discriminating perturbation as a build break. One sweep returned
BUILD-ERROR for six of seven cases that were in fact all working (#1466)
— a uniform non-answer of exactly the shape the uniform-34 and uniform-0
tells above describe.

**A perturbation that *passes* is a finding, not a null result.** It says
the assertion does not depend on the line you neutralised, and the usual
cause is that the test's stated claim is wrong rather than that the test
is weak. Check whether the behaviour is *enforced by the mechanism* or
merely *absent from a list* before strengthening anything: Perl's
statement-modifier walker keys on the grammar's `condition` field, so
adding `ForSimpleStatement` to its arm changes nothing — the `for`
modifier exposes no such field — and a comment claiming the test would
catch a widened arm was false until the sweep proved it (#1464). Correct
the comment; do not invent a fixture to justify it. The inverse error is
equally common: two candidate rankings in #1465 were merely *different
valid orderings* rather than defects, and the honest fix was to trim the
test comment's claim.

After restoring, `git status` / `git diff --stat` must show exactly
the edits you intend — nothing extra, nothing missing.

Expand Down Expand Up @@ -233,7 +255,7 @@ fixture rather than like the fix working.
`loc/wide-cfg-test-mod` (`big-code-analysis-bench/src/shapes.rs`) read
`sloc` under `exclude_tests` on a file of nothing but `#[cfg(test)]
mod m {}` repeated. Its only non-zero row was the phantom attribute row
#1431 then removed, so the probe scored zero and tripped
that #1431 then removed, so the probe scored zero and tripped
`probe_workload_is_exercised`. Left unnoticed it would have timed the
walk's fixed overhead and reported an excellent exponent forever. The
repair was to render a retained `fn p() {}` per item, so the reading
Expand Down Expand Up @@ -428,6 +450,17 @@ single non-listed language (`--features go`) is the reproducer.
Note the union is over *features*, not languages: `LANG::Tsx` rides
`feature = "typescript"`, so a seven-row table can need only six.

**That verification needs `-p big-code-analysis`.** A workspace-wide
`cargo test --no-default-features --features X` does not isolate features
at all: `big-code-analysis-cli` and `big-code-analysis-web` depend on the
library with default features, and cargo unifies them, so everything is
silently re-enabled. Three different feature sets reported an identical
`4 passed; 3428 filtered out` before this was noticed (#1457) — the
uniform-number tell from the harness sections above. Only `-p` narrowed
it, and then the counts varied (0 / 2 / 3). A feature-gate check run
without `-p` proves nothing, and proves it while looking green; confirm
real isolation by watching the *filtered-out* count differ across sets.

## Assert a whole-run invariant in the run, not in a fixture list

When a change establishes an invariant that holds at the end of *every*
Expand Down
27 changes: 27 additions & 0 deletions .github/workflows/ci.yml
Original file line number Diff line number Diff line change
Expand Up @@ -497,7 +497,34 @@ jobs:
# The `-- -D warnings` is belt-and-braces against that env var
# being narrowed later. All ten pre-existing legs were verified
# against the stricter command before it landed.
#
# These legs verify that the workspace *compiles* across feature
# combinations. They deliberately do not run the suite — that runs
# under `default` and `--all-features` in the `test` job — because
# running it here would fail in the thousands rather than report
# anything. Per-language tests are not feature-gated (30 of 3,482
# `#[test]` fns in `src/`), and `Node` panics by contract on a
# grammar that is not compiled in, so `--no-default-features
# --features rust` fails 2,642 of 3,238 tests. Gating them is
# tracked in #1472.
- run: cargo clippy --all-targets ${{ matrix.flags }} --locked -- -D warnings
- uses: taiki-e/install-action@fa23953489c080190314742a9b907f8e97c6767c # v2.87.10
with:
tool: cargo-nextest
# The one thing a partial feature set breaks that the clippy run
# above cannot see (#1472): a test gated on the union of its rows
# must be *absent* when none of those features is enabled, not
# present and tripping its own `checked > 0` non-vacuity guard.
# That is a valid build, so nothing warns — it surfaces as a test
# failure on whichever leg enables none of the rows, which is how
# #1220 and PR #1221 shipped. `nextest list` answers it without
# running a test.
#
# `--locked` for the same reason the clippy run above carries it:
# the script forwards every unrecognised argument to `cargo nextest
# list`, which would otherwise be the one cargo invocation in this
# job allowed to rewrite `Cargo.lock` instead of failing on it.
- run: ./utils/check-feature-gates.py ${{ matrix.flags }} --locked

deny:
name: cargo-deny
Expand Down
12 changes: 12 additions & 0 deletions .pre-commit-config.yaml
Original file line number Diff line number Diff line change
Expand Up @@ -417,6 +417,18 @@ repos:
entry: python3 -m unittest -q utils/check-diagnostic-prefix-test.py
pass_filenames: false

# Self-tests for the feature-gate reachability gate (#1472). Only
# the self-tests run here: the gate itself needs a test-binary
# build per feature set and lives in the `feature-matrix` CI job.
# Its scanner is the same hazard as the one above — a scan that
# stops matching reports a clean tree.
- id: check-feature-gates-test
name: check-feature-gates-test
language: system
files: '^utils/check-feature-gates(-test)?\.py$'
entry: python3 -m unittest -q utils/check-feature-gates-test.py
pass_filenames: false

# Safety-doc pin gate — the module doc of
# big-code-analysis-py/src/node.rs is the canonical soundness
# argument for this workspace's only sanctioned `unsafe` block,
Expand Down
2 changes: 1 addition & 1 deletion .rustfmt-bail-baseline.txt
Original file line number Diff line number Diff line change
Expand Up @@ -129,7 +129,7 @@ big-code-analysis-ast/src/getter/cpp.rs 5
big-code-analysis-ast/src/getter/csharp.rs 8
big-code-analysis-ast/src/getter/elixir.rs 3
big-code-analysis-ast/src/getter/go.rs 1
big-code-analysis-ast/src/getter/groovy.rs 5
big-code-analysis-ast/src/getter/groovy.rs 7
big-code-analysis-ast/src/getter/irules.rs 6
big-code-analysis-ast/src/getter/java.rs 5
big-code-analysis-ast/src/getter/kotlin.rs 4
Expand Down
12 changes: 12 additions & 0 deletions AGENTS.md
Original file line number Diff line number Diff line change
Expand Up @@ -421,6 +421,18 @@ purely procedural: do not bypass pre-commit, and refresh the baseline
with `make self-scan-write-baseline-headroom` in the commit that moved
the metric. A red gate on `main` traces directly to skipping this step.

**The same duty runs the other way**, and nothing can make it red. A
change that *lowers* a baselined metric leaves the recorded value
describing a tree that no longer exists, and the filter keeps
suppressing the offender all the way up to it — headroom nobody chose,
in which a later regression hides. Since #1465 `bca check --baseline`
warns on stderr when a covered offender has measured past its record
(below it, or above it for the lower-is-worse `mi.*` family), naming
the worst entry and the count. Refresh in the same commit, exactly as
for an increase. The warning covers only offenders still above their
limits: one that stopped breaching altogether produces no violation and
so leaves an entry that only a full regeneration finds.

The same rule governs **merges**. `.bca-baseline.toml` is marked
`-merge` in `.gitattributes`, so git leaves it wholly conflicted rather
than splicing two branches' entries together. That is deliberate:
Expand Down
Loading