Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
Show all changes
23 commits
Select commit Hold shift + click to select a range
06dc7d6
docs(stability): correct the output-writer contract table
dekobon Aug 9, 2026
2cf816e
docs(cli/diff): name materialize_tree, not git archive
dekobon Aug 9, 2026
e020f01
docs(py): correct the SARIF sentinel and the vcs CLI spelling
dekobon Aug 9, 2026
9da9188
docs(enums): state the post-#720 Cpp binding
dekobon Aug 9, 2026
6355dbf
docs: correct stale claims and apply the prose rules
dekobon Aug 9, 2026
f3dc659
fix(getter/php): count each $variable once in Halstead operands
dekobon Aug 9, 2026
1ee17ff
test(halstead): make the #1259 PHP fixtures discriminate N2 from n2
dekobon Aug 9, 2026
09915de
fix(getter): count a boolean or nil literal once in N2
dekobon Aug 9, 2026
e4742e6
fix(abc): exclude Java/Groovy generic type syntax
dekobon Aug 9, 2026
4e94ee2
fix(npm/cpp): count templated members with bodies
dekobon Aug 9, 2026
6d23b4d
fix(checker): match the aliased C-family call kind
dekobon Aug 9, 2026
1637288
fix(web): give InvalidAuthorHashKey its own token
dekobon Aug 9, 2026
07a06bb
test(vcs): make the vcs error guards falsifiable
dekobon Aug 9, 2026
8bb990b
fix(cli): sort the aggregate --output document by path
dekobon Aug 9, 2026
da45c8f
fix(cli/check): emit a refresh command that parses
dekobon Aug 9, 2026
014226a
test(cli/check): close three holes in the refresh-command guards
dekobon Aug 9, 2026
35bc053
fix(ci): catch a newly generated man page in the drift gate
dekobon Aug 9, 2026
4a7e0b3
test(ci): close six gaps in the man-drift self-tests
dekobon Aug 9, 2026
1cf64c8
fix(xtask): reject a case-only man-page rename
dekobon Aug 9, 2026
1af590e
test(xtask): pin multi-conflict reporting in the sweep
dekobon Aug 9, 2026
4c9dfff
docs(changelog): consolidate entries from the quick-fix batch
dekobon Aug 9, 2026
00755b4
chore(baseline): regenerate after rebase onto main
dekobon Aug 9, 2026
e0ed245
test(xtask): build the two-file rename fixture only where it exists
dekobon Aug 9, 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
8 changes: 4 additions & 4 deletions .bca-baseline.toml
Original file line number Diff line number Diff line change
Expand Up @@ -45,7 +45,7 @@ value = 69608.24451083591
path = "big-code-analysis-cli/src/commands/analyze.rs"
qualified = "write_aggregate"
metric = "halstead.effort"
value = 48544.21441032752
value = 54210.895759662584

[[entry]]
path = "big-code-analysis-cli/src/commands/check.rs"
Expand All @@ -69,7 +69,7 @@ value = 6.0
path = "big-code-analysis-cli/src/commands/check.rs"
qualified = "run_check"
metric = "halstead.effort"
value = 62885.05529367418
value = 63636.29428662941

[[entry]]
path = "big-code-analysis-cli/src/commands/check/effective_config.rs"
Expand Down Expand Up @@ -813,7 +813,7 @@ value = 64170.027458982564
path = "src/getter/elixir.rs"
qualified = "ElixirCode::get_op_type"
metric = "halstead.effort"
value = 56464.822908007496
value = 54685.446169787065

[[entry]]
path = "src/getter/irules.rs"
Expand All @@ -831,7 +831,7 @@ value = 76538.42828561828
path = "src/getter/php.rs"
qualified = "PhpCode::get_op_type"
metric = "halstead.effort"
value = 54691.60133906107
value = 71190.04074846079

[[entry]]
path = "src/getter/python.rs"
Expand Down
23 changes: 18 additions & 5 deletions .github/workflows/ci.yml
Original file line number Diff line number Diff line change
Expand Up @@ -594,6 +594,15 @@ jobs:
# it from the aggregate recipe.
- name: check-manpage-assets (explicit)
run: python3 utils/check-manpage-assets.py
# Defensive twin for the man-page drift gate's self-tests
# (#1249). The gate itself runs in the `manpage` job, which
# invokes the script directly and so cannot be dropped by a
# refactor of the `make lint` aggregate — but the tests that
# prove the gate still catches an untracked page can be, and a
# gate that has silently stopped gating is indistinguishable
# from a clean tree. That is the failure this pair exists for.
- name: check-manpage-drift-test (explicit)
run: python3 -m unittest -q utils/check-manpage-drift-test.py
# Defensive twin for the diagnostic-prefix gate (#1199): a
# capitalised `Warning:` / `Error:` / `Note:` baked into a string
# literal bypasses the one-helper-per-crate severity ladder and
Expand Down Expand Up @@ -971,12 +980,16 @@ jobs:
- uses: Swatinem/rust-cache@6323deb102c322ba6fcbdcafc7e3dddab59af2b6 # v2.9.2
- name: Regenerate man pages
run: cargo xtask
# Fails on every direction the regenerated tree can differ from
# the committed one — modified, deleted, *and* added pages. The
# added direction needs `git ls-files --others`: the page xtask
# writes for a brand-new subcommand is untracked, and `git diff`
# alone passed green over it until #1249. The check lives in the
# script rather than inline here so this step and
# `make manpages-check` cannot drift apart; the `::error::`
# annotation is emitted by the script when GITHUB_ACTIONS is set.
- name: Verify no drift
run: |
if ! git diff --exit-code -- man/; then
echo "::error::Committed man pages drift from the clap schema. Run 'cargo xtask' locally and commit the result."
exit 1
fi
run: python3 utils/check-manpage-drift.py

# Single required status check for branch protection — depends on
# every gating job. Passing this one check guarantees the full matrix
Expand Down
18 changes: 17 additions & 1 deletion .pre-commit-config.yaml
Original file line number Diff line number Diff line change
Expand Up @@ -309,13 +309,17 @@ repos:

# Catch clap-vs-man-page drift before push. The
# `.github/workflows/ci.yml` `manpage` job (Regenerate, then
# `git diff --exit-code -- man/`) is the upstream backstop;
# assert no drift) is the upstream backstop;
# this hook makes the same check available locally and via
# `pre-commit run --all-files`. The hook fails on drift and
# leaves the regenerated files in the working tree — same
# side effect CI runs into, accepted because man pages are
# generated artifacts.
#
# Since #1249 the assertion half lives in
# `utils/check-manpage-drift.py`, shared with the CI job, and
# covers added pages as well as modified and deleted ones.
#
# The `files:` filter covers every input that can change the
# generated `.1` output:
# - clap definitions in the two CLI crates and xtask itself
Expand All @@ -333,6 +337,18 @@ repos:
entry: make manpages-check
pass_filenames: false

# Self-tests for the man-page drift gate. Separate hook so the
# gate above stays a regenerate-then-assert pair and the tests
# get their own pass/fail in `pre-commit run` output. They run
# against scratch repositories only — never the real `man/`,
# which the `manpages` hook rewrites.
- id: check-manpage-drift-test
name: check-manpage-drift-test
language: system
files: '^utils/check-manpage-drift(-test)?\.py$'
entry: python3 -m unittest -q utils/check-manpage-drift-test.py
pass_filenames: false

# Lockstep version invariant — every owned crate and every
# `=<v>` internal-dep pin must match the workspace version.
# See RELEASING.md "Lockstep version policy".
Expand Down
16 changes: 14 additions & 2 deletions .rustfmt-bail-baseline.txt
Original file line number Diff line number Diff line change
Expand Up @@ -38,6 +38,17 @@
# tests/common/mod.rs an `insta::with_settings!` invocation,
# whose `key => value` token tree is not a
# match at all
# src/vcs/error.rs `classify_error_variants!` (#1245): its
# matcher, plus the 11 `$pat => $sample`
# entries at the invocation, which are
# `pattern => expression` pairs and so
# parse as neither. Deliberate: generating
# `is_client_input` and
# `client_input_samples` from one list is
# what makes a new client-input variant a
# compile error. Those 11 lines sit outside
# `cargo fmt` — keep them one per line and
# under 100 columns by hand.
#
# Every remaining entry is cause 1, and all of them share one shape:
# a section comment sitting between `|` alternatives of a single long
Expand Down Expand Up @@ -99,7 +110,7 @@ src/getter.rs 2
src/getter/bash.rs 3
src/getter/c.rs 3
src/getter/cpp.rs 6
src/getter/csharp.rs 2
src/getter/csharp.rs 5
src/getter/elixir.rs 2
src/getter/go.rs 1
src/getter/groovy.rs 4
Expand All @@ -110,7 +121,7 @@ src/getter/lua.rs 3
src/getter/mozcpp.rs 6
src/getter/objc.rs 3
src/getter/perl.rs 2
src/getter/php.rs 2
src/getter/php.rs 8
src/getter/python.rs 7
src/getter/ruby.rs 2
src/getter/tcl.rs 4
Expand All @@ -126,4 +137,5 @@ src/metrics/cyclomatic/php.rs 4
src/metrics/cyclomatic/ruby.rs 5
src/metrics/loc/perl.rs 6
src/metrics/loc/python.rs 6
src/vcs/error.rs 12
tests/common/mod.rs 2
9 changes: 6 additions & 3 deletions AGENTS.md
Original file line number Diff line number Diff line change
Expand Up @@ -61,7 +61,8 @@ and `cargo run -p big-code-analysis-web --`.
- `utils/` — repo-maintenance helper scripts, including the gates run
by `make pre-commit` / `make ci`: `check-versions.py`,
`check-snapshot-anchors.py`, `check-rustfmt-bail.py`,
`check-manpage-assets.py`, `check-diagnostic-prefix.py`,
`check-manpage-assets.py`, `check-manpage-drift.py`,
`check-diagnostic-prefix.py`,
`check-grammar-marker-sync.py`, `check-enums-codegen-drift.sh`,
`check-grammar-crate.py`, `check-grammars-crates.sh`,
`check-excluded-manifests.py`, `check-ruff-lockstep.py`,
Expand Down Expand Up @@ -267,8 +268,10 @@ cannot run), `cargo doc --no-deps --workspace
--all-features` with `RUSTDOCFLAGS="-D warnings"`,
`cargo +nightly udeps`, the markdown /
TOML / shell / Makefile / GitHub Actions lint families, the man-page
drift gate (`cargo xtask` + `git diff --exit-code -- man/`, mirroring
the `manpage` CI job), the diagnostic-prefix gate
drift gate (`cargo xtask` + `utils/check-manpage-drift.py`, mirroring
the `manpage` CI job, which calls the same script — it fails on
modified, deleted, **and** newly added pages, the last of which
`git diff` alone cannot see, #1249), the diagnostic-prefix gate
(`make check-diagnostic-prefix`, which blocks a capitalised
`Warning:` / `Error:` / `Note:` string literal — see "Rust
conventions"), the bca self-scan threshold gate at both
Expand Down
91 changes: 91 additions & 0 deletions CHANGELOG.md
Original file line number Diff line number Diff line change
Expand Up @@ -90,6 +90,10 @@ for historical reference.
against a lockfile resolving 0.15.22), which stays invisible until the two
versions disagree and then presents as "works locally, red in CI" (#1230).

- **web:** `error_kind` token `vcs_invalid_author_hash_key`. The
`STABILITY.md` vocabulary list also gains `not_acceptable` and
`serialize_failed`, added by #657 and never documented (#1245).

### Fixed

- `fix_includes` returned its diagnostics in a different order on every
Expand Down Expand Up @@ -117,6 +121,93 @@ for historical reference.
`cpp_ast_source_reflects_preproc_expansion`; the `C` arm had nothing.
Found while verifying that the new `preproc_macro` fuzz target is not
vacuous (#1154).

- **Halstead, PHP:** variable references no longer count twice. `$x` parses
as a `variable_name` wrapping a `name` leaf and both were classified as
operands, so `N2` roughly doubled on variable-dense PHP and `n2` carried a
sigil-less twin per variable; variable-variable syntax compounded it (`$$a`
scored 3, `$$$b` scored 4). `Name` is now suppressed under a
`variable_name` or `dynamic_variable_name` parent, and those wrappers under
a `dynamic_variable_name` parent, so every reference counts once at any
nesting depth. Volume, difficulty, effort and MI change for every PHP file
(#1259).

- **Halstead, Elixir and C#:** `true` / `false` / `nil` / `null` literals no
longer count twice in `N2`. The grammar wraps the keyword leaf in a named
literal node and `get_op_type` classified both as operands; because
operands are keyed by source text, `n2` hid the duplication while `N2` —
and the length, volume, difficulty and effort derived from it — inflated by
one per literal occurrence. Elixir drops the leaves outright; C# parent-
guards them, because `operator true` emits a bare leaf with no
`boolean_literal` wrapper (#1253).

- **ABC, Java and Groovy:** generic type syntax is no longer counted as
conditions. A generic declaration (`class Gen<T>`, `<T> void m()`, Groovy's
`def <U> U m(U x)`) scored two conditions per bracket pair, and a wildcard
bound (`List<? extends T>`) scored one as a phantom ternary. Both tokens
are now gated on their parent node kind, matching the polarity C, C++,
Objective-C, mozcpp, Rust and Go already use (#1274).

- **npm, C++ and Mozcpp:** a templated member function with an inline body
was not counted. The `template_declaration` guard resolves through a shared
helper that hunted a `function_declarator`, but a templated member *with a
body* parses as `template_declaration > function_definition` and has no
such node at that level — so the method scored zero while `nom` opened a
function space for it and `wmc` weighted its cyclomatic, leaving three
metrics disagreeing about one class. The helper now accepts a
`function_definition` child outright rather than recursing into it, which
also fixes a templated conversion operator (whose declarator is an
`operator_cast`, so no `function_declarator` exists at any depth). C++
`npm` values rise on classes with templated inline-bodied members (#1258).

- `bca count -t call` and `bca find call` reported 0 on all C, C++ and Mozcpp
source. `Checker::is_call` matched only the unsuffixed `CallExpression`
kind_id, which is the grammar's always-aliased `preproc_call_expression`
and never reaches `kind_id()`; every real call carries the aliased variant.
No metric values are affected (#1254).

- **web:** a misused `author_hash_key` on `POST /v1/vcs` and `/v1/vcs/trend`
reported `error_kind: "vcs_internal_error"`, the token reserved for backend
faults, so a client branching on the token saw its own mistake as a server
failure. It now carries `vcs_invalid_author_hash_key`. The status was
already correct (`400`) (#1245).

- `bca metrics --output <FILE>` and `bca ops --output <FILE>` wrote the
aggregate document's per-file elements in worker-completion order, so two
runs over an unchanged tree produced differently-ordered files at
`--jobs > 1`. The elements are now sorted by emitted path in every format
(JSON, YAML, TOML, CBOR, CSV), making the artifact diffable and usable as a
cache key. Multi-seed runs change too: the walk sorts per seed and
concatenates, so `bca metrics -p b -p a --output x.json` previously emitted
`b`'s files first and now emits one globally sorted document (#1244).

- `bca check`'s remediation block printed a baseline-refresh command in the
pre-#597 flag order (`bca --paths … check …`), which exits 1 with a clap
usage error. The command now names the `check` subcommand first and mirrors
every flag that decides what `--write-baseline` records — including
`--threshold`, `--no-config`, `--check-exclude` and `--check-exclude-from`,
without which the suggested refresh wrote a different baseline than the
gate measured, or failed outright (#1243).

- The man-page drift gate now fails on a *newly generated* page.
`git diff --exit-code -- man/` reports tracked content only, so the page
`cargo xtask` writes for a brand-new subcommand passed both the CI
`manpage` job and `make manpages-check` green and silently never shipped.
The check moved out of the two hand-mirrored shell blocks into
`utils/check-manpage-drift.py`, which both sites now call, and covers
modified, deleted, and added pages (#1249).

- `cargo xtask` deleted the man page it had just written when a command was
renamed case-only, on macOS/APFS and Windows/NTFS, while still exiting 0.
`render_man_page`'s collision guard folded ASCII case; `sweep_orphans`
compared byte-for-byte. Because case-insensitive filesystems are also
case-preserving, the write for `BCA.1` landed in the existing `bca.1`
directory entry and the case-sensitive sweep then unlinked it as an orphan.
The relation now has a single definition that both sites call, and the
sweep classifies three ways rather than two: byte-equal keeps, no match
removes, and a case-only match is refused with an error naming every
conflicting spelling (#1250).

- The pre-tag `cargo publish --dry-run` for `big-code-analysis` was skipped
on every release, not only the first. It was gated on a crates.io sparse
index probe for a leaf version that the Lockstep policy guarantees is the
Expand Down
Loading