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
576 changes: 288 additions & 288 deletions .bca-baseline.toml

Large diffs are not rendered by default.

13 changes: 7 additions & 6 deletions .claude/rules/formatting.md
Original file line number Diff line number Diff line change
Expand Up @@ -66,7 +66,7 @@ Three things that version gets right and the hand-rolled probe below
did not:

- **It feeds rustfmt on stdin.** Given a *path*, rustfmt resolves and
recurses into `mod` declarations, so `src/getter.rs` and
recurses into `mod` declarations, so `big-code-analysis-ast/src/getter.rs` and
`src/metrics/cognitive.rs` error out with "file not found for module"
— which reads exactly like a bail if stderr is discarded. On stdin
there is nothing to resolve, so those two probe like any other file.
Expand All @@ -81,10 +81,11 @@ did not:
literals**. Do not act on a count from a probe that skips this step.
- **It probes every arm, not the first.** The bail is match-scoped, so a
file whose first `match` formats cleanly still hides a later one that
does not (`src/getter/c.rs`), and a module whose arms are all
does not (`big-code-analysis-ast/src/getter/c.rs`), and a module whose arms are all
expression-bodied (`… => HalsteadType::Operator,` in
`src/getter/go.rs`) has no `=> {` to probe at all. Over `src/getter`
the first-arm-only version gave 11 false verdicts out of 18.
`big-code-analysis-ast/src/getter/go.rs`) has no `=> {` to probe at
all. Over `big-code-analysis-ast/src/getter` the first-arm-only
version gave 11 false verdicts out of 18.

## Two causes, one measurement

Expand Down Expand Up @@ -114,7 +115,7 @@ never go away.
That is a deliberate change from how this section used to read. It
carried a hand-maintained list of files, and that list was wrong twice:
first by naming only `src/metrics/`, which hid the largest cluster
(`src/getter/`, 18 of 25 modules) for two revisions of this file, and
(`big-code-analysis-ast/src/getter/`, 18 of 25 modules) for two revisions of this file, and
then by going stale the moment #1136 hoisted seven comments out of
`src/metrics/cognitive/`. A stale list of bailing files reads exactly
like a clean tree — the failure this whole rule exists to prevent — so
Expand All @@ -132,7 +133,7 @@ reported clean, and every one was found by reading the diff instead.

## How to apply

- After any bulk, scripted, or regex edit under `src/getter/` or
- After any bulk, scripted, or regex edit under `big-code-analysis-ast/src/getter/` or
`src/metrics/`, read the resulting diff rather than trusting the fmt
gate. Check indentation and line length by eye.
- Line length is worth a direct check, since it is mechanical:
Expand Down
18 changes: 10 additions & 8 deletions .claude/rules/grammar-dispatch.md
Original file line number Diff line number Diff line change
Expand Up @@ -25,13 +25,13 @@ Confirm every numeric-suffix variant of every matched rule is either
listed or excluded with a comment:

```bash
rg 'Lang::([A-Za-z]+)\b' src/getter/ src/checker/ src/alterator.rs \
rg 'Lang::([A-Za-z]+)\b' big-code-analysis-ast/src/getter/ big-code-analysis-ast/src/checker/ big-code-analysis-ast/src/alterator.rs \
src/spaces.rs src/metrics/
```

The bug class reaches every match on a grammar rule — `alterator.rs`,
`spaces.rs`, and `src/metrics/*` are as susceptible as `getter.rs` and
`checker.rs`. Centralised alias sets live in `src/macros/kind_sets.rs`;
`checker.rs`. Centralised alias sets live in `big-code-analysis-ast/src/macros/kind_sets.rs`;
prefer extending those over open-coding a list. When a rule has many
aliases, prefer one `node.kind()` string comparison over enumerating
seventeen variants — pay the small runtime cost for forward
Expand All @@ -42,7 +42,8 @@ manifests in lockstep (root `Cargo.toml` and `enums/Cargo.toml` — the
excluded crate cannot inherit the workspace pin), then:

```bash
cargo run --manifest-path ./enums/Cargo.toml -- -lrust -o ./src/languages
cargo run --manifest-path ./enums/Cargo.toml -- \
-lrust -o ./big-code-analysis-ast/src/languages
```

Stable *named*-node ids are not evidence the ids held. Inserting one
Expand All @@ -55,7 +56,7 @@ anonymous terminal renumbers the whole anonymous block after it, so
A rule whose name begins with `_` (`_string`, `_multiline_string_literal`)
is hidden: the variant exists in the enum and the parser never emits it.
Check the `Lang::Variant => "name"` arm in
`src/languages/language_<lang>.rs` before listing a "looks like an alias"
`big-code-analysis-ast/src/languages/language_<lang>.rs` before listing a "looks like an alias"
variant. Keep the defensive arm *and* pin its hidden status with a
`!ast_has_kind_id(&parser, Lang::HiddenVariant as u16)` assertion naming
the hidden rule — otherwise a future grammar that promotes the rule
Expand Down Expand Up @@ -92,8 +93,8 @@ new one. **Re-derive it rather than trusting the list below** — it moves
whenever a language is added:

```bash
rg -o 'impl_is_else_if_(\w+)!\(\s*(\w+)' -r '$1 $2' src/checker/ --no-filename | sort
rg -l 'fn is_else_if' src/checker/ # hand-written impls
rg -o 'impl_is_else_if_(\w+)!\(\s*(\w+)' -r '$1 $2' big-code-analysis-ast/src/checker/ --no-filename | sort
rg -l 'fn is_else_if' big-code-analysis-ast/src/checker/ # hand-written impls
```

| Strategy | Macro | Languages |
Expand Down Expand Up @@ -183,7 +184,8 @@ Minimum cross-walk when you edit one:
into a **wrong count**, which a snapshot diff shows you. This one
disagrees into an **absent key**: a node the walker promoted but the
getter left `Unknown` is not a member scope
(`SpaceKind::is_member_scope`), so its space serializes no `npm` /
(`MemberScopeExt::is_member_scope`, `src/metrics/mod.rs`), so its
space serializes no `npm` /
`npa` block at all — which looks exactly like a language that
legitimately has no containers. Nothing diffs. §6's "gate all three
on the same predicate" is this bullet's fix.
Expand Down Expand Up @@ -280,7 +282,7 @@ that descends — and test-via-revert that arm alone per
## When you fix one language, sweep the rest

Every item above is a per-language failure that almost always exists in
siblings. `src/languages/` modules are deliberate clones, so the fix for
siblings. `big-code-analysis-ast/src/languages/` modules are deliberate clones, so the fix for
one is the audit table for the other twenty. Build that table in the
issue, land the sibling fixes in one commit so the symmetry is visible
to a reviewer, and anchor any known-wrong-but-unfixed case with an
Expand Down
2 changes: 1 addition & 1 deletion .claude/rules/testing.md
Original file line number Diff line number Diff line change
Expand Up @@ -220,7 +220,7 @@ whenever many tests reach a line while all supplying the same value to
the part that matters, and the tool cannot see the difference because
which-inputs-varied is not what it measures.

`CommaIndex::splits` (`src/cfg_predicate.rs`) measured 11 of 11 regions
`CommaIndex::splits` (`big-code-analysis-ast/src/cfg_predicate.rs`) measured 11 of 11 regions
covered and was entered 150,200 times in one run. Replacing its
`region.start` lower bound with `0` panics on ordinary input — and
before #1105 that perturbation failed **none** of the 3,969 tests then
Expand Down
2 changes: 1 addition & 1 deletion .claude/rules/tool-output.md
Original file line number Diff line number Diff line change
Expand Up @@ -16,7 +16,7 @@ it:
- `sort | uniq -c` — the counts you are hunting are the largest, and
they sort **last** unless you passed `-rn`.
- `rg` over a tree — hits arrive in path order, so a sweep across
`src/languages/` or `src/getter/` shows the alphabetically early
`big-code-analysis-ast/src/languages/` or `big-code-analysis-ast/src/getter/` shows the alphabetically early
languages and hides every one after them.
- `cargo test` / `make pre-commit` — the failure summary is at the end,
behind all the passing output.
Expand Down
101 changes: 67 additions & 34 deletions .claude/skills/add-lang/SKILL.md
Original file line number Diff line number Diff line change
Expand Up @@ -25,7 +25,7 @@ Parse `$ARGUMENTS` as: `<lang-name> <grammar-crate>=<version> [<file-ext>...]`

- `<lang-name>` (required): PascalCase enum variant name, e.g. `Go`,
`Ruby`, `Swift`. Must not collide with existing variants in
`src/langs.rs` or `enums/src/languages.rs`.
`big-code-analysis-ast/src/langs.rs` or `enums/src/languages.rs`.
- `<grammar-crate>=<version>` (required): the tree-sitter crate name
and pinned version, e.g. `tree-sitter-ruby=0.23.1`. The version MUST
be pinned with `=X.Y.Z` (project convention — see `AGENTS.md`).
Expand All @@ -45,8 +45,10 @@ continuing.
- **No public-API breaks** in unrelated crates. The new language
variant is itself a public-API addition (acceptable; minor bump);
do not change other variants or trait signatures.
- **Pin the grammar version** with `=X.Y.Z` in both `Cargo.toml` and
`enums/Cargo.toml`. Never use a range without explicit user
- **Pin the grammar version** with `=X.Y.Z` in the root `Cargo.toml`
(`[workspace.dependencies]`) and in `enums/Cargo.toml`; the
`big-code-analysis-ast` manifest inherits the pin with
`workspace = true`. Never use a range without explicit user
approval.
- **Cross-language parity.** All 12 metric trait impls
(`Abc`, `Cognitive`, `Cyclomatic`, `Exit`, `Halstead`, `Loc`, `Mi`,
Expand Down Expand Up @@ -77,7 +79,7 @@ the user's behalf.
### 0b: Validate name and version pin

- Confirm `<lang-name>` is not already a variant in
`src/langs.rs` (search for `Lang::<lang-name>`).
`big-code-analysis-ast/src/langs.rs` (search for `Lang::<lang-name>`).
- Confirm `<grammar-crate>=<version>` parses cleanly and that
`crates.io/crates/<grammar-crate>` actually publishes
`<version>` — fetch the crate page or `cargo search` to verify.
Expand All @@ -99,7 +101,7 @@ symbol-level navigation/editing is the default for all `.rs` edits.
## Step 1: Wire up the `enums` codegen helper

The `enums` crate is excluded from the default workspace and exists
solely to regenerate `src/languages/language_<lang>.rs` from a tree-sitter
solely to regenerate `big-code-analysis-ast/src/languages/language_<lang>.rs` from a tree-sitter
grammar's node-kind table. Wire it up first so we can produce the enum
file before touching the main crate.

Expand Down Expand Up @@ -182,7 +184,8 @@ empty kind name at one position. If it is missing, add it.
From the repo root, mirroring `recreate-grammars.sh`:

```bash
cargo run --manifest-path ./enums/Cargo.toml -- -l rust -o ./src/languages
cargo run --manifest-path ./enums/Cargo.toml -- \
-l rust -o ./big-code-analysis-ast/src/languages
cargo fmt --all
```

Expand All @@ -197,8 +200,8 @@ one. The `enums` binary iterates `Lang::into_enum_iter()` and writes
one file per registered variant. After running, inspect the diff:

```bash
git status -- src/languages/
git diff src/languages/
git status -- big-code-analysis-ast/src/languages/
git diff big-code-analysis-ast/src/languages/
```

The new `language_<lang>.rs` should appear as a new file. Existing
Expand All @@ -212,14 +215,15 @@ no longer matching what the workspace clippy gate expects); fix the
template — not the emitted output — and re-run codegen. See lesson
17 in `lessons_learned.md`.

Confirm the new file exists at `src/languages/language_<lang>.rs` and
Confirm the new file exists at `big-code-analysis-ast/src/languages/language_<lang>.rs` and
that it begins with `// Code generated; DO NOT EDIT.`.

If the project also depends on C-macro tables for the new language
(only relevant for C/C++ family preprocessor work), also run:

```bash
cargo run --manifest-path ./enums/Cargo.toml -- -l c_macros -o ./src/c_langs_macros
cargo run --manifest-path ./enums/Cargo.toml -- \
-l c_macros -o ./big-code-analysis-ast/src/c_langs_macros
```

Most languages do not need this step.
Expand All @@ -228,16 +232,44 @@ Most languages do not need this step.

## Step 2: Wire the grammar into the main crate

### 2a: Add the grammar to root `Cargo.toml`
### 2a: Wire the grammar into two manifests

Same pinned-version line as 1a, inserted alphabetically among the
other `tree-sitter-*` deps:
Since #1376 the grammar crates are dependencies of
`big-code-analysis-ast`, not of the root crate, so a language needs
three edits across two manifests. Missing any of them leaves
`LANG::<New>` returning `LanguageDisabled` in every build, or leaves an
unused dependency that `cargo +nightly udeps` fails on.

```toml
tree-sitter-<lang> = "=<version>"
```
1. Root `Cargo.toml`, `[workspace.dependencies]` — the version pin,
inserted alphabetically among the other `tree-sitter-*` entries
(same pinned-version line as 1a):

```toml
tree-sitter-<lang> = "=<version>"
```

2. `big-code-analysis-ast/Cargo.toml` — the optional dependency and the
feature that enables it, plus an entry in `all-languages`:

```toml
[dependencies]
tree-sitter-<lang> = { workspace = true, optional = true }

[features]
all-languages = [..., "<lang>", ...]
<lang> = ["dep:tree-sitter-<lang>"]
```

3. Root `Cargo.toml`, `[features]` — the forwarding feature and the
matching `all-languages` entry, so the root's feature set stays a
superset of the sub-crate's:

```toml
all-languages = [..., "<lang>", ...]
<lang> = ["big-code-analysis-ast/<lang>"]
```

### 2b: Export the generated module from `src/languages/mod.rs`
### 2b: Export the generated module from `big-code-analysis-ast/src/languages/mod.rs`

```rust
pub mod language_<lang>;
Expand All @@ -246,7 +278,7 @@ pub use language_<lang>::*;

Insert alphabetically.

### 2c: Add the language definition to `src/langs.rs`
### 2c: Add the language definition to `big-code-analysis-ast/src/langs.rs`

Append a `mk_langs!` tuple alphabetically:

Expand Down Expand Up @@ -306,7 +338,7 @@ test that parses a deliberately malformed fixture and asserts
`blank ≥ 0` and `kind == Unit` at the file level. See lesson 9 in
`lessons_learned.md` (issue #80, `dc09eb3`).

### 3a: `Checker` impl in `src/checker.rs`
### 3a: `Checker` impl in `big-code-analysis-ast/src/checker.rs`

Append an `impl Checker for <LangName>Code` block. Required methods:

Expand Down Expand Up @@ -346,7 +378,7 @@ Append an `impl Checker for <LangName>Code` block. Required methods:
- `is_primitive` — usually `false` unless the grammar emits a
primitive-type kind (most don't).

### 3b: `Getter` impl in `src/getter.rs`
### 3b: `Getter` impl in `big-code-analysis-ast/src/getter.rs`

Append an `impl Getter for <LangName>Code` block. Required methods:

Expand Down Expand Up @@ -374,7 +406,7 @@ clashed with `use Go::*` in pattern position; the fix was
`use Go as G;`. Detect the collision proactively after Step 1e:

```bash
rg "^\s*<LangName>\s*=" src/languages/language_<lang>.rs
rg "^\s*<LangName>\s*=" big-code-analysis-ast/src/languages/language_<lang>.rs
```

If the search returns a hit, alias the import at the top of the
Expand All @@ -392,7 +424,7 @@ assert the load-bearing invariants from lesson 4: run both
assert that `len(dedupe(ops.operators)) == n1` and
`len(dedupe(ops.operands)) == n2`.

### 3c: `Alterator` impl in `src/alterator.rs`
### 3c: `Alterator` impl in `big-code-analysis-ast/src/alterator.rs`

If the language has string/raw-string/char-literal node kinds whose
default text representation should be preserved verbatim (no whitespace
Expand Down Expand Up @@ -451,7 +483,7 @@ rule-name root for which both an unsuffixed variant and one or more
numbered siblings exist in the generated enum:

```bash
LANG_FILE="src/languages/language_<lang>.rs"
LANG_FILE="big-code-analysis-ast/src/languages/language_<lang>.rs"
comm -12 \
<(rg -o '^\s+([A-Z][A-Za-z]*)\d+\s*=' -r '$1' "$LANG_FILE" | sort -u) \
<(rg -o '^\s+([A-Z][A-Za-z]*)\s*=' -r '$1' "$LANG_FILE" | sort -u)
Expand All @@ -465,8 +497,8 @@ names whose every numbered variant is a potential aliasing risk.

For each printed base, confirm that EVERY numbered variant in its
group holds one of the following in EVERY file that does a `match`
on the underlying rule (`src/checker.rs`, `src/getter.rs`,
`src/alterator.rs`, `src/metrics/*.rs`, `src/spaces.rs`):
on the underlying rule (`big-code-analysis-ast/src/checker.rs`, `big-code-analysis-ast/src/getter.rs`,
`big-code-analysis-ast/src/alterator.rs`, `src/metrics/*.rs`, `src/spaces.rs`):

1. The variant is explicitly listed in the relevant arm (typically
alongside its unsuffixed sibling:
Expand Down Expand Up @@ -561,7 +593,7 @@ whose name suggests the construct:

```bash
rg 'For[A-Z]|While[A-Z]|If[A-Z]|Switch[A-Z]|Conditional|Ternary|Try[A-Z]|Catch[A-Z]|Match[A-Z]|Case[A-Z]' \
src/languages/language_<lang>.rs
big-code-analysis-ast/src/languages/language_<lang>.rs
```

Confirm each hit is either explicitly matched, or explicitly excluded
Expand Down Expand Up @@ -1059,16 +1091,17 @@ Before exiting, print a one-screen summary:
Added <LangName> language support.

Files changed:
Cargo.toml
Cargo.toml ([workspace.dependencies] pin + forwarding feature)
big-code-analysis-ast/Cargo.toml (optional dep + feature)
enums/Cargo.toml
enums/src/languages.rs
enums/src/macros.rs
src/langs.rs
src/languages/mod.rs
src/languages/language_<lang>.rs (generated)
src/checker.rs
src/getter.rs
src/alterator.rs (if applicable)
big-code-analysis-ast/src/langs.rs
big-code-analysis-ast/src/languages/mod.rs
big-code-analysis-ast/src/languages/language_<lang>.rs (generated)
big-code-analysis-ast/src/checker.rs
big-code-analysis-ast/src/getter.rs
big-code-analysis-ast/src/alterator.rs (if applicable)
src/metrics/{abc,cognitive,cyclomatic,nexits,halstead,loc,mi,nargs,nom,npa,npm,wmc}.rs
big-code-analysis-book/src/languages.md
*.snap (new insta snapshots)
Expand All @@ -1083,7 +1116,7 @@ manually.
**Heads up: mutation testing.** Quarterly mutation testing
(`.github/workflows/mutation-test.yml`, see
`docs/development/mutation_testing.md`) runs against
`src/metrics/`, `src/checker.rs`, and `src/getter.rs`. Within one
`src/metrics/`, `big-code-analysis-ast/src/checker.rs`, and `big-code-analysis-ast/src/getter.rs`. Within one
cycle, expect auto-filed issues labelled `mutation-testing` against
the new language's impls; treat them as standard fix-issue work.
Escapes mean the per-language test set under-specified the new
Expand Down
8 changes: 4 additions & 4 deletions .claude/skills/audit-crate/SKILL.md
Original file line number Diff line number Diff line change
Expand Up @@ -19,7 +19,7 @@ titles). Never let an unresolved or empty `$ARGUMENTS` reach a template like
`audit-state-$ARGUMENTS` — that would write to a malformed memory key.

**Memory-key sanitization**: when `$ARGUMENTS` is a directory path (e.g.,
`src/languages`), replace `/` with `-` before composing memory keys so the
`big-code-analysis-ast/src/languages`), replace `/` with `-` before composing memory keys so the
key is a single flat token (e.g., `audit-state-src-languages`, not
`audit-state-src/languages`). This avoids backend interpretation of slashes
as path separators. Apply the same rule to every memory-key reference
Expand Down Expand Up @@ -249,7 +249,7 @@ Group every file into one of these categories before auditing:

| Group | Contents |
|-------|----------|
| A — Library core | `src/lib.rs` and the modules it exports (`src/languages/`, `src/metrics/`, `src/output/`, `src/spaces.rs`, `src/parser.rs`, `src/checker.rs`, `src/getter.rs`, `src/alterator.rs`, `src/node.rs`, `src/traits.rs`, etc.) |
| A — Library core | `src/lib.rs` and the modules it exports (`big-code-analysis-ast/src/languages/`, `src/metrics/`, `src/output/`, `src/spaces.rs`, `big-code-analysis-ast/src/parser.rs`, `big-code-analysis-ast/src/checker.rs`, `big-code-analysis-ast/src/getter.rs`, `big-code-analysis-ast/src/alterator.rs`, `big-code-analysis-ast/src/node.rs`, `big-code-analysis-ast/src/traits.rs`, etc.) |
| B — Binaries | `src/bin/` entries plus the workspace crates `big-code-analysis-cli` and `big-code-analysis-web` when those are the audit target |
| C — Tests | `tests/` directory |
| D — Supporting files | `README.md`, examples, `Cargo.toml`, `big-code-analysis-book/`, helper scripts, `.claude/rules/` if present |
Expand Down Expand Up @@ -353,7 +353,7 @@ will apply, so the vocabulary must match end-to-end. Mapping rules:

### Project-Specific (big-code-analysis)

27. Per-language modules under `src/languages/` deliberately mirror each
27. Per-language modules under `big-code-analysis-ast/src/languages/` deliberately mirror each
other. Does any change introduce a discrepancy that one language exhibits
and another does not (different metric formula, different node-type
handling, different operator/operand classification) without justification?
Expand Down Expand Up @@ -554,7 +554,7 @@ last_model: <model-id>

## File Coverage
src/lib.rs | full | 2026-04-25 | 3 findings | claude-opus-4-7
src/languages/language_rust.rs | partial | 2026-04-25 | 1 finding | claude-opus-4-7
big-code-analysis-ast/src/languages/language_rust.rs | partial | 2026-04-25 | 1 finding | claude-opus-4-7
src/metrics/halstead.rs | none | - | - | -
tests/parser.rs | full | 2026-04-25 | 0 findings | claude-sonnet-4-6
```
Expand Down
Loading