Releases: dekobon/big-code-analysis
Release list
v2.0.0
The project's first major version bump since 1.0, and the first
stable release on the 2.x line. It collects every breaking change
staged across the 1.x cycle behind a single major boundary. The
detailed (breaking) entries are listed under the headings below;
the migration notes here summarise the surface and consolidate the
serialized key map and the metric-value re-baseline that the
contract promised the 2.0.0 entry would carry. (The 2.0.0-rc1
release candidate, tagged 2026-06-19, carried the same change set
ahead of this stable cut.)
Migration from 1.x
The breaking changes fall into a few groups, each detailed under its
own (breaking) entry below:
- Library
Statsaccessors and metric naming — Halstead,
NArgs, and MI accessors were renamed to a uniform wire
vocabulary, theexitmetric module was renamed tonexits, and
theMetric::NArgs/Metric::Exitvariants became
Metric::Nargs/Metric::Nexits. - Serialized output shape — metric keys were normalised
(#510, #511), integer-valued metrics now serialize as integers and
their accessors returnu64(#530), non-finite floats serialize as
a uniformnull(#531), and several wire keys were renamed (see the
key map below). - CLI grammar — flags renamed (
--language-type→--language,
--num-jobs→--jobs,--warning→--warnings), exit codes
restructured (argv errors exit 1; 2–5 reserved for metric gates),
and several argument-parsing behaviours tightened. - REST schema — uniform
{error, error_kind, id}error and
{id, language}analysis envelopes, stricter unknown-field
rejection, a nested per-filevcsobject, and removal of the
unprefixed route aliases. - Default grammars —
.js/.jsxnow parse through upstream
tree-sitter-javascript(the Mozilla fork is demoted to the opt-in
mozjs, owning only.jsm),.cpp/.hthrough upstream
tree-sitter-cpp(Mozilla fork demoted to opt-inmozcpp),.c
through a newLANG::C, and.mthrough a newLANG::Objc. - Python bindings — the typed surface tightened and
analyze_batch'sskip_generateddefault flipped fromFalseto
True.
Metric-value re-baseline
2.0.0 is a one-time metric-value re-baseline boundary. Values
shifted across the 1.x cycle from metric-definition fixes
(divide-by-zero guards across the suite, the per-function
cyclomatic average) and, at 2.0, from the default-grammar flips:
.c files now parse through tree-sitter-c, .m through
tree-sitter-objc, and the Mozilla C++ overlay was swapped for
upstream tree-sitter-cpp — each moves the affected files' numbers.
The integration snapshots were re-baselined in lockstep. Consumers
comparing across the 1.x → 2.0 boundary should treat it as a
single re-baseline rather than reconciling the union of every
patch-level drift; pin an exact version and store it alongside your
results if you need bit-for-bit reproducibility.
Serialized key & accessor renames
Library Stats accessor renames — serialized output keys are
unchanged (these are Rust method names only):
| Metric | Old accessor | New accessor |
|---|---|---|
| Halstead | u_operators |
unique_operators |
| Halstead | operators |
total_operators |
| Halstead | u_operands |
unique_operands |
| Halstead | operands |
total_operands |
| NArgs | fn_args (+ _sum/_average/_min/_max) |
function_args (+ _sum/_average/_min/_max) |
| NArgs | nargs_total / nargs_average |
total / average |
| MI | mi_original / mi_sei / mi_visual_studio |
original / sei / visual_studio |
Nexits (was exit) |
exit (+ _sum/_average/_min/_max) |
nexits (+ _sum/_average/_min/_max) |
Module / variant renames: the exit metric module became nexits
(crate::exit → crate::nexits), Metric::Exit → Metric::Nexits,
Metric::NArgs → Metric::Nargs. The retired "exit" metric parse
alias no longer resolves — only "nexits" parses.
Serialized wire-key renames (JSON / YAML / TOML / CBOR, and the
matching CSV columns):
| Block | Old key | New key |
|---|---|---|
npm |
classes / interfaces |
class_npm_sum / interface_npm_sum |
npa |
classes / interfaces |
class_npa_sum / interface_npa_sum |
wmc |
classes / interfaces |
class_wmc_sum / interface_wmc_sum |
tokens |
tokens_average / tokens_min / tokens_max |
average / min / max |
The bare-sum tokens leaf is kept, and the terminal dump's
tokens-sum label changed sum → tokens. The truthful sibling keys
on npm / npa / wmc (class_methods, total, coa, cda, …)
are unchanged.
Type / shape: integer-valued metrics (every count, sum, and min/max,
plus Halstead length / vocabulary and all WMC values) now
serialize as integers and their Stats accessors return u64
instead of f64; ratios, averages, ABC magnitude, the derived
Halstead scores, and MI stay f64. No value changes — only the type.
Added
- Python
Nodeis now a closer py-tree-sitter drop-in: atypeproperty
aliaseskind(the py-tree-sitter spelling —kindstays the canonical
bca name), andtextis now a property rather than a method, matching
py-tree-sitter'snode.text. Together these erase the two most-common
mechanical edits when porting a py-tree-sitter walker. Thetext
method→property shape change is part of the still-unreleased #728 surface,
so it lands free before 2.0. Covered by themake py-stubtestgate (#975). - Public
metric_catalog::MetricScopeenum (File/Function/
Container) withMetricScope::admits(SpaceKind), a
metric_catalog::scope(id)lookup, ascopefield on the
#[non_exhaustive]MetricInfo, andSpaceKind::from_serialized—
the single source of truth for which space kind each threshold metric
gates (#969), shared by the CLI gate and the Pythonto_sarifbinding
so they cannot drift. - Lazy
Nodetraversal handle for Python (big_code_analysis.Node) over
the tree retained byAst, so a caller walks the AST py-tree-sitter-style
—kind(with the py-tree-sitter-compatibletypealias), byte offsets,
points,children,child_by_field_name, thetextproperty, a lazy
pre-orderwalk(),descendants_by_kind()— without
materialising the tree into dicts the waydump()does (#728). Reach one
through the newAst.root_nodeproperty orAst.find(filters); the node
keeps itsAstalive, so it stays valid after every other reference to
the parse is dropped, and is safe to share across aThreadPoolExecutor.
Kinds are the raw grammar kinds (not theAlterator-curated kinds
dump()emits — they intentionally disagree on altered nodes such as
string literals), and each node exposes its location in every vocabulary:
start_byte/end_byte, 0-basedstart_point/end_point(py-tree-sitter
parity), and 1-basedstart_line/end_lineplus aspandict matching
dump(). Covered by themake py-stubtestgate. Node::preorder()(a pre-order iterator) and
Node::descendants_by_kind(kinds)on the RustNodesurface — the
Rust counterparts the Pythonwalk()/descendants_by_kind()mirror,
so Rust callers gain the same ergonomic traversal helpers (#728).- Python
Astparse-once handle (big_code_analysis.Ast) binds the Rust
Astseam, so a Python caller parses a source once and draws both
metrics and the AST from the same parse instead of parsing twice — once
in py-tree-sitter, once inanalyze()(#727).Ast.parse(code, language)andAst.from_path(path)construct the handle;.metrics()
(byte-for-byteanalyze_source),.dump()(thebca dump//astnode
tree),.functions(),.ops(),.count(),.strip_comments(), and
.suppressions()all reuse the one parse.from_pathis no-magic: it
reads through the same text reader asanalyze(so metrics match) but
does not skip generated files and never silently returns nothing. New
AstNodeDict/SpanDict/FunctionSpanDict/OpsDict/
SuppressionMarkerDictTypedDicts; all covered by themake py-stubtest
gate. Ast::from_pathon the Rust surface (the file-backed counterpart to
Ast::parse): reads + language-detects + parses one file, returning a
newFromPathErrorfor each distinct failure (I/O, non-UTF-8 path,
empty/binary/non-text file, unknown language, disabled-language build)
(#727).language_grammar_version(language)(Python) andLANG::grammar_version
(Rust) return the pinned tree-sitter grammar crate version backing a
language (e.g."0.25.1"forbash) — the exact upstream version for
crates.io grammars, the fork crate version for the vendored forks
(#727).- Byte offsets in the AST dump span: every dump node's
spannow carries
start_byte/end_byte(0-based, half-open) alongside the existing
1-based line/column pairs, across the library, CLIdump, web/ast,
and the Pythondump()(#727). Structural consumers can slice the
original source for any node — including internal nodes whosevalue
the dump omits — without re-deriving offsets from lines and columns.
(See ...
v1.1.0
Added
-
bca --exclude-from <FILE>global flag reads--excludeglob
patterns from a file (one per line,.gitignore-style: blank lines
and#-prefixed comments are skipped). Patterns union with any
inline--exclude <GLOB>flags into a single deny-set. The
convention is a.bcaignoreat the repo root so workflow,
recipe, and local baseline-bootstrap can share one source of truth.
Use-for stdin. Fixes
#355. -
Python bindings shipped — close-out of
#103
(umbrella) via phase 9/9 in
#273.
big_code_analysisis now installable from PyPI via
pip install big-code-analysis, exposes the same metric pipeline
as thebcaCLI, and ships abi3 manylinux wheels for Linux
x86_64 and aarch64 on CPython 3.12+. The public surface is:
analyze/analyze_source/analyze_batch(never-raise
per-file errors viaAnalysisError),flatten_spaces(scalar
rows for DataFrames / sqlite),to_sarif(SARIF 2.1.0 ready for
GitHub Code Scanning),language_for_file,
supported_languages,language_extensions, and themetrics=
kwarg threading through every entry point withMETRIC_NAMESas
the validated list. Output dicts matchbca metrics --output-format jsonbyte-for-byte (verified by the
cli_parity.pyexample and the parametrized parity tests in
tests/test_smoke.py). Theexamples/directory ships
cli_parity.py(CLI parity smoke test, wired into
make py-test),pipeline_db.py(directory walk →
analyze_batch→flatten_spaces→ sqlite top-N, with a
deliberately broken file to demonstrate the never-raise
contract),sarif_upload.py(SARIF emission ready for
github/codeql-action/upload-sarif@v3), and
jupyter_quickstart.ipynb(pandas DataFrame + matplotlib
cyclomatic-per-function plot, executed end-to-end in CI via a
newpython-examples-nbconvertjob). Type-checked under
mypy --strictandpyright; PEP 561py.typedships in the
built wheel. The granular per-phase implementation history lives
in the sub-issues
(#265–#272)
and on the commits referenced from them; this umbrella entry is
the single end-user-facing announcement. -
Public
Asttype for parse-once, compute-many-times analysis. Build
one withAst::parse(Source)(re-parses bytes, mirrorsanalyze)
orAst::from_tree_sitter(lang, tree, code, name)(adopts a
caller-builttree_sitter::Tree, theSource-flavored counterpart
ofmetrics_from_treewith no lossy path-to-name conversion). Then
callAst::metrics(options)repeatedly against the same parse —
with differentMetricsOptions::with_onlyselections, interleaved
with a custom tree-sitter walk viaAst::as_tree_sitter, or cached
across configuration changes in an analysis pipeline.analyzeand
metrics_from_treeare now thin wrappers around the same seam, so
the per-language dispatch table lives in exactly one place. See
library/parse-once.md
andlibrary/ast-traversal.md
for working with the heldtree_sitter::Treedirectly
(#264). -
bca check --baseline <path>and--write-baseline <path>flags
for ratcheting thresholds on an existing codebase without raising
limits. The baseline is a sorted TOML file keyed on(path, function, start_line, metric)that records today's offender set;
a baselined function whose value has not worsened is filtered
from threshold checks, but regressions (current > baseline.value)
and new offenders still fail. Composes with in-source suppression
markers —--write-baselineexcludes already-suppressed functions
by default, and--no-suppress --write-baselinerecords every
violation for CI-auditor flows. See
commands/check.md
and the Baselines recipe
for the full adoption flow
(#99). -
Per-language Cargo features (default:
all-languages) so library
consumers can compile only the grammars they need. Each supported
language now has its own feature (rust,typescript,python,
cpp, …) that gates the matchingtree-sitter-*grammar crate
in the dependency graph. The default feature set keeps the
library's historical "every grammar compiled in" behaviour
(bcaandbca-webboth pinfeatures = ["all-languages"]
explicitly); downstream library consumers can opt into a narrower
set withdefault-features = false, features = ["rust", "typescript", …].
TheLANGenum keeps every variant defined regardless of the
active feature set; selecting a variant whose feature is off
producesErr(MetricsError::LanguageDisabled(LANG))from every
dispatch entry point. A newLANG::is_enabledpredicate lets
callers query the compiled-in set without going through a
dispatcher
(#252). -
New
big_code_analysis::preludemodule exposing the recommended
entry points for the 90% case:analyze,metrics_from_tree,
Source,MetricsOptions,MetricsError,Metric,LANG,
FuncSpace,CodeMetrics,SpaceKind. Callers can now write
use big_code_analysis::prelude::*;instead of long
per-import lists; everything outside the prelude is still
reachable by its fully-qualified name from the crate root
(#255). -
MetricsOptions::with_only(&[Metric])for selective metric
computation. Pass a slice of [Metric] values to restrict the
walker to those metrics; everything outside the set is skipped at
the per-node level (noT::Halstead::compute, no
T::Cognitive::compute, etc.) and elided fromCodeMetrics
serialization output. Derived metrics auto-resolve their
dependencies —with_only(&[Metric::Mi])silently adds
Loc + Cyclomatic + Halstead, andwith_only(&[Metric::Wmc])
addsCyclomatic + Nom. TheMetricenum is#[non_exhaustive]
and the backing bitfield (MetricSet) is exposed alongside it so
callers can introspect which metrics were computed via the new
CodeMetrics::selected()accessor. Defaults are unchanged:
MetricsOptions::default()selects every metric, matching the
pre-#257 behaviour byte-for-byte
(#257). -
New library entry point
analyze(Source, MetricsOptions) -> Result<FuncSpace, MetricsError>insrc/spaces.rs.Source<'a>
is#[non_exhaustive]and carries the language, source bytes,
optional caller-supplied display name (Source::name), optional
C++-preprocessor path (Source::preproc_path), and optional
PreprocResults. Construct viaSource::new(lang, code)plus
thewith_name/with_preproc_path/with_preprocsetters.
This is the recommended entry point for in-memory analysis —
callers no longer need to fabricate a&Pathto identify a
buffer
(#254). -
Parse seam for callers who already drive
tree-sitter. New
Parser::from_tree(tree, code)accepts a pre-built
tree_sitter::Treeplus the matching source bytes, skipping the
bundled parse. A non-genericmetrics_from_tree(lang, tree, source, path, pr, options) -> Result<FuncSpace, MetricsError>
dispatches on&LANGfor the common case. Thetree_sitter
crate is re-exported asbig_code_analysis::tree_sitterso
consumers can build trees against the exact version the metric
walker was compiled against without taking a sibling
dependency;LANG::get_tree_sitter_languagereturns the
matching grammar. Both seam entry points accepttree_sitter::Tree
directly, so the internalTreewrapper stays crate-private.
The re-exportedtree_sitterAPI and the
LANG::get_tree_sitter_languagereturn type follow the
underlying grammar pin and are documented as value-not-stable
inSTABILITY.md. Thelibrary/reuse-treebook chapter is
upgraded from a stub to a working example
(#251). -
Top-level
STABILITY.mddocumenting the versioning contract for
the0.xline: which types and entry points are shape-stable,
why no value stability is offered until1.0, the escape hatches
(Node.0, the still-directtree-sitterdependency,
#[doc(hidden)]items), and the MSRV policy
(rust-version = "1.94"workspace-wide). Linked from the README
under a new "Using as a library" section
(#258). -
In-source suppression markers for metric threshold checks. Comments
matchingbca: suppress,bca: suppress(metric, ...),
bca: suppress-file,bca: suppress-file(metric, ...),
#lizard forgives, or#lizard forgive globalsilence offending
bca checkviolations without editing source. A new--no-suppress
flag forces all markers to be ignored for CI auditors.FuncSpace
gains asuppressed: SuppressionScopefield (elided from JSON when
empty so existing snapshots are unchanged). New public types:
MetricKind,SuppressionScope, andSuppressionPolicy. Documented
in the new Suppression markers book chapter
(#98,
#263). -
(breaking)
AstNodeJSON output now carries aFieldName
key holding the tree-sitter grammar field through which each
node was reached (left, `rig...
v1.0.0
Fork-anchor note. Forked from Mozilla's
rust-code-analysis
at commit007ee15on 2026-04-26 and renamed tobig-code-analysis.
This entry consolidates all changes through the first
public release; there were no intermediate tagged releases between
the fork point and1.0.0.
Added
New languages
- Bash — full Checker / Getter / Alterator and metric implementations.
- C# — full implementation with Java-parity test coverage, including
shebang-free detection and aliased-kind_idvariant handling. - Lua — full implementation.
- Perl — full implementation with metrics.
- PHP — full implementation with per-metric test matrix at Java parity
and integration-suite wiring into thebig-code-analysis-outputsubmodule. - Tcl — full implementation.
- Kotlin / Go — promoted from default
implement_metric_trait!stubs
to real per-language metric implementations. Kotlin gained Checker,
Getter, and all seven metric traits; Go gained a real Cognitive
complexity implementation. Both languages parsed pre-fork but emitted
default/no-op metric values.
New metrics and metric variants
- Per-function Tokens metric with markdown-report column wiring.
- Modified cyclomatic complexity exposed alongside the standard count
for languages that distinguish bare-wildcard / fall-through arms.
CLI (bca)
- New
checksubcommand with a threshold engine for CI gates
(per-metric ceilings, exit-code-driven). - CLI restructured into subcommand verbs (breaking) — e.g.
bca metrics,bca check,bca find,bca count. Old top-level
flag invocations no longer work; see the migration notes in
big-code-analysis-book/. --list-metricscommand to enumerate every metric the binary supports.-O markdownaggregated hotspot report with--topand
--strip-prefixflags, padded for plain-text readability.-O htmlaggregated hotspot report (separate from the per-file HTML
output): hover tooltips on aggregate headers, per-language section
tinting with a stable palette.- Gitignore-aware path traversal and
--paths-from <file>for piping
pre-computed file lists. - Mutually exclusive action flags enforced via clap
ArgGroupso
conflicting modes fail at parse time. - Auto-skip files marked as generated (e.g.
@generatedheaders). - Shebang-based language detection for extensionless scripts.
Output formats
- CSV output.
- Checkstyle XML output (with reusable
OffenderRecordstub). - SARIF 2.1.0 output for GitHub Code Scanning ingestion.
- Clang/GCC and MSVC warning-line output formats for editor /
CI integration. - Self-contained HTML per-file report.
REST API (bca-web)
- Synchronous parsing offloaded to the blocking thread pool so the
async runtime stays responsive under load. - Bounded tracking of orphaned blocking tasks; new requests are
rejected with a clear status when the threshold is exceeded. - HTTP 500 responses now sanitise internal error details before
emission.
Tooling and CI
- Makefile-based developer and CI gate (
make pre-commit,
make ci); install targets built withtarget-cpu=native. - Workspace builds the CLI and web crates by default
(no opt-in feature flag required). - Per-PR snapshot-anchor lint
(check-snapshot-anchors.py+.github/workflows/snapshot-anchors.yml)
enforced via baseline file.snapshot-anchor-baseline.txt. - Scheduled
cargo-mutantsjob oversrc/metrics/,
src/checker.rs, andsrc/getter.rs(quarterly cron;
auto-files GitHub issues on escapes). - CI lint blocking new bare insta snapshots in
src/metrics/.
Documentation
- mdBook documentation tree at
big-code-analysis-book/with
Recipes section, file/language detection workflow, per-output-format
chapters, and a developer guide for adding new languages. add-langskill under.claude/skills/codifying the end-to-end
workflow for wiring a new tree-sitter language.- Lessons learned 9–14 added to
docs/development/lessons_learned.md.
Changed
- Project renamed from
rust-code-analysistobig-code-analysis
(fork anchor007ee15). - Binaries renamed (breaking) to
bca(formerly
rust-code-analysis-cli) andbca-web(formerly
rust-code-analysis-web). Distribution package names follow. - Default branch renamed from
mastertomain. - Integration-snapshot submodule renamed from
tests/repositories/rca-output
totests/repositories/big-code-analysis-output
(remote:dekobon/big-code-analysis-output). tree-sitterbumped to0.26.8(withNode::child(u32)signature
adaptation in our wrapper).- CLI
Formatenum replaced with clapValueEnumderivation —
-O/--formataccepts the same values, but error messages and
shell completions are now generated from the type. - Output writers consolidated under a single dispatch path; HTML
per-function metrics format folded into the unified writer set. FindCfg/CountCfgfilter lists now stored asArc<[String]>
(breaking, library-level) for cheaper cloning; downstream
callers constructing these structs by hand must wrap their
Vec<String>accordingly.bca's CLI internals also moved to
Arc<[String]>for find/count filters.FuncSpaceandOpsnow carry aname_was_lossyflag so callers
can detect when a non-UTF-8 path component was lossily converted
for display.- Internal cleanup: numerous
refactor:,chore:, andstyle:
commits across the workspace tightened visibility, removed dead
code, consolidated test helpers, modernised Rust syntax, and
bumped internal-only dependencies (e.g.askama0.15 → 0.16 in
theenums/codegen helper crate, which is excluded from the
default workspace). See
git log 007ee15..HEAD --grep '^refactor\|^chore\|^style\|^build(deps)'
for the full list.
Fixed
Metrics
- Cognitive: handle unary negation in Kotlin and Go boolean
sequences; excludeelsearms of Kotlinwhenexpressions from
cognitive complexity; correct sibling boolean-sequence detection;
generalise depth-stop tracking with correct per-language
boundaries; implementis_else_iffor Java and C# to fix
else ifover-counting. - Cyclomatic: skip bare wildcard
_ =>arms in Rust standard
CCN; remove the spuriousCaseStatementcontainer increment
in Bash standard CCN. - Nargs: count bare-identifier arrow-function parameters in
JS/TS; correct Java and Kotlin argument counting. - Nom: add missing comma separators in the
StatsDisplay
implementation. - Loc: wrap parses with a synthetic
Unitspace when the
grammar's root node is notUnit(e.g. languages whose root is
program/module). - C#: match all aliased
kind_idvariants so that aliased
syntax doesn't silently fall through.
Output
dump_metricsnow usescognitive_sum/cyclomatic_sum
(was previously emitting per-function values where sums were
expected).- Eliminated panic paths in the alterator + output pipeline; added
regression tests. - Flattened the
String2variant in JS / TS / TSX alterators so
template-literal substrings serialise consistently.
Web (bca-web)
- Comment-stripping handler swaps the C++ grammar to
Ccomment
(matches the CLI's behaviour for plain-text comment removal). - Explicit
serdederivefeature flag enabled (was relying on
transitive activation).
Robustness
- Normalise CR and CRLF line endings before parsing
(previously, lone-CR and CRLF inputs could drift line counts). - Walk the parent's children in
Node::has_sibling
(was walking the wrong node and missing siblings). - Spaces: handle non-UTF-8 paths via lossy conversion when
computing the top-level space name. - CLI: trim whitespace from
--paths-fromlines before
PathBufconstruction; warn instead of silently dropping when
non-UTF-8 path components appear inhandle_path. - C macro lookup: switched to
binary_searchwith short-circuit
||for hit-path branch prediction; dropped the static
DOLLARSbuffer to avoid a panic on long identifiers.
Build / scripts / documentation
ops.rs— removed strayprintln!debug output.loc.rs— fixedcloc_min/cloc_maxdoc comments that
saidPlocinstead ofCloc.WebCommentResponse.codedoc comment corrected.enums/build script — regenerate language enums after
grammar version bumps.split-minimal-tests.py— use a raw f-string so regex
metacharacters in metric names aren't misinterpreted; escape
metric_namebefore regex interpolation.- Cargo
repositoryURLs updated to reference themainbranch.
Removed
- HTML per-function metrics output format
(refactor(output): remove HTML metrics output format,
commiteb57500). HTML output remains available via the new
self-contained per-file HTML report (commit7af09d1) and the
aggregated hotspot HTML report (commit5eb41fd); migrate
depending on whether you want per-file or cross-file output.
Security
bca-weberror sanitisation: HTTP 500 responses no longer
leak internal error details (fix(web): sanitize internal error details from HTTP 500 responses, commit99a2691).bca-weborphan-task tracking: bounded tracking of orphaned
blocking tasks rejects new requests when a configurable threshold
is exceeded, mitigating slow-loris-style resource exhaustion of
the blocking thread pool (fix(web): track orphaned blocking tasks and reject when threshold exceeded, commit94c8141).
[1....