Skip to content

SaschaOnTour/rustqual

Folders and files

NameName
Last commit message
Last commit date

Latest commit

 

History

25 Commits
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 

Repository files navigation

rustqual

CI crates.io License: MIT

A structural quality guardrail for Rust — with AI coding agents specifically in mind. rustqual scores your code across seven dimensions (IOSP, Complexity, DRY, SRP, Coupling, Test Quality, Architecture) and combines them into one quality number. Equally useful for senior teams enforcing architecture in CI.

What sets it apart from clippy and other Rust linters: rustqual reasons across files and modules, not just within functions. Its architecture rules and call-parity check verify properties that span an entire codebase, which is where most real drift happens.

It catches what AI agents consistently produce and what tired humans consistently miss: god-functions that mix orchestration with logic, copy-paste-with-variation, tests without assertions, architectural drift across adapter layers (CLI, MCP, REST, …), and dead code that piles up after a refactor pivot.

What it looks like

$ cargo install rustqual
$ rustqual .

── src/order.rs
  ✓ INTEGRATION process_order (line 12)
  ✓ OPERATION   calculate_discount (line 28)
  ✗ VIOLATION   process_payment (line 48) [MEDIUM]

═══ Summary ═══
  Functions: 24    Quality Score: 82.3%

  IOSP:           85.7%
  Complexity:     90.0%
  DRY:            95.0%
  SRP:           100.0%
  Test Quality:  100.0%
  Coupling:      100.0%
  Architecture:  100.0%

4 quality findings. Run with --verbose for details.

Exit code 1 on findings — drop it into CI without ceremony.

Why it exists

If you've used Claude Code, Cursor, GitHub Copilot, or Codex on Rust projects, you've seen the same patterns:

  • Functions that mix orchestration ("call helper, if/else, call another helper") with logic — hard to test, hard to refactor.
  • Copy-paste with minor variation when asked to "do the same for X" instead of extracting an abstraction.
  • Tests that exercise code without checking it (#[test] fn it_works() { run_thing(); }) — coverage looks good, real coverage is zero.
  • Architectural drift: new functionality lands in one adapter (CLI, MCP, REST) and silently misses the others.

rustqual catches all of these mechanically. Wired into the agent's feedback loop (CI, hooks, instruction file), the agent self-corrects. Reviewer time goes to the actual logic, not to spotting fake tests and inlined god-functions.

The same checks help senior teams enforce architecture decisions in CI — the layer rules and forbidden-edge rules don't care whether the code came from a human or an LLM. They keep the codebase coherent over time.

rustqual addresses each of these patterns through a separate quality dimension. Each is independently tunable; together they produce one aggregated quality score.

Seven quality dimensions

Dimension What it checks
IOSP Function separation: every function is either Integration (orchestrates) or Operation (logic), never both. From Ralf Westphal's Flow Design.
Complexity Cognitive/cyclomatic complexity, magic numbers, nesting depth, function length, unsafe, error-handling style.
DRY Duplicate functions, fragments, dead code, boilerplate (10 BP-* rules), repeated match patterns.
SRP Struct cohesion (LCOM4), module length, function clusters, structural method-checks (BTC, SLM, NMS).
Coupling Module instability, circular deps, Stable Dependencies Principle, structural checks (OI, SIT, DEH, IET).
Test Quality Assertion density, no-SUT tests, untested functions, optional LCOV-based coverage gaps.
Architecture Layer rules, forbidden edges, symbol patterns, trait contracts, call parity across adapters.

Each dimension contributes to the aggregated quality score with a configurable weight (defaults to a balanced split summing to 1.0). Each dimension can also be tuned or disabled in rustqual.toml — full reference: book/reference-configuration.md.

What's unusual: call parity

Most architecture linters prove what can't be called (containment: "domain doesn't import adapters"). rustqual's call_parity rule additionally proves what must be called — that several adapter modules collectively cover every public capability of a target module.

[architecture.call_parity]
adapters = ["cli", "mcp"]
target   = "application"

Two checks under one rule:

  • Check A — every adapter must delegate. A CLI command that doesn't reach into the application layer is logic in the wrong place.
  • Check B — every application capability must reach every adapter. Add app::ingest::run, forget to wire it into CLI, and Check B reports it by name in CI before review.

The hard part is making the call graph honest across method chains, field access, trait dispatch, type aliases, framework extractors, and Self substitution. rustqual ships a shallow type-inference engine that resolves these cases without fabricating edges. Full write-up: book/adapter-parity.md.

Use cases

What is IOSP?

The Integration Operation Segregation Principle (Ralf Westphal's Flow Design) says every function should be:

  • Integration — orchestrates other functions. No own logic.
  • Operation — contains logic. No calls to your own project's functions.

A function that does both is a Violation — that's the smell to fix.

┌─────────────┐     ┌─────────────┐     ┌────────────────────┐
│ Integration │     │  Operation  │     │    ✗ Violation     │
│             │     │             │     │                    │
│ calls A()   │     │ if x > 0    │     │ if x > 0           │
│ calls B()   │     │   y = x*2   │     │   r = calc()       │ ← mixes both
│ calls C()   │     │ return y    │     │ return r + 1       │
└─────────────┘     └─────────────┘     └────────────────────┘

Out of the box rustqual is forgiving where it matters — closures, iterator chains, match-as-dispatch, and trivial self-getters are all leniency cases. Tighten with --strict-closures / --strict-iterators if you want them counted as logic. Full breakdown: book/function-quality.md.

Install & first run

cargo install rustqual
cd your-rust-project
rustqual

Walkthrough with --init, --no-fail, --findings, the common flags, and the first-run output: book/getting-started.md. Full flag reference: book/reference-cli.md.

CI integration

Minimal GitHub Actions step:

- run: cargo install rustqual
- run: rustqual --format github --min-quality-score 90

With coverage and PR annotations:

- run: cargo install rustqual cargo-llvm-cov
- run: cargo llvm-cov --lcov --output-path lcov.info
- run: rustqual --diff origin/main --coverage lcov.info --format github

For codebases that aren't yet at 100% but want to prevent regression:

rustqual --save-baseline baseline.json
git add baseline.json && git commit -m "Add quality baseline"
- run: rustqual --compare baseline.json --fail-on-regression

Full patterns: book/ci-integration.md.

AI coding agent integration

Drop this into CLAUDE.md, .cursorrules, .github/copilot-instructions.md, or whichever instruction file your tool reads:

## Code Quality Rules

- Run `rustqual` after making changes. All findings must be resolved before marking a task complete.
- Follow IOSP: every function is either an Integration or an Operation, never both.
- Keep functions under 60 lines and cognitive complexity under 15.
- Don't duplicate logic — extract shared patterns into reusable Operations.
- Don't introduce functions with more than 5 parameters.
- Every test function must contain at least one assertion.
- For public-API functions intentionally untested in this crate, mark with `// qual:api`.

The agent gets actionable feedback: rustqual tells it which function violated which principle, so it can self-correct without you having to point each issue out. Full patterns: book/ai-coding-workflow.md.

Suppression annotations

For genuine exceptions:

// qual:allow(iosp) — match dispatcher; arms intentionally inlined
fn dispatch(cmd: Command) -> Result<()> { /* … */ }

// qual:api — public re-export, callers live outside this crate
pub fn parse(input: &str) -> Result<Ast> { /* … */ }

// qual:test_helper — used only from integration tests
pub fn build_test_session() -> Session { /* … */ }

max_suppression_ratio (default 5%) caps how much code can be under qual:allow. Stale suppressions (no matching finding in their window) are flagged as ORPHAN-001. Full reference: book/reference-suppression.md.

Output formats

--format <FMT>text (default), json, github, sarif, dot, html, ai, ai-json. Same analysis, different serialisation. Full reference: book/reference-output-formats.md.

Self-compliance

rustqual analyses itself — the full source tree (~2.5k functions across all seven dimensions) reports Quality Score: 100.0% with zero findings and zero warnings:

$ cargo run -- . --fail-on-warnings --coverage coverage.lcov

═══ Summary ═══
  Quality Score: 100.0%

  IOSP:        100.0%
  Complexity:  100.0%
  DRY:         100.0%
  SRP:         100.0%
  Coupling:    100.0%
  Test Quality:100.0%
  Architecture:100.0%

All quality checks passed!

Verified by the integration test suite and CI on every push.

Build & test

cargo nextest run                                  # full test suite
cargo run -- . --fail-on-warnings --coverage coverage.lcov   # self-analysis
RUSTFLAGS="-Dwarnings" cargo clippy --all-targets  # lints (0 warnings)

In use at

  • rlm — Rust local memory manager. The reference adopter codebase that prompted the call-parity rule.
  • turboquant — Rust quantitative finance toolkit (in active development).

Known limitations

  1. Syntactic analysis only. Uses syn for AST parsing. The receiver-type-inference engine (v1.2+) resolves most method-call receivers; what it can't resolve stays unresolved rather than being fabricated.
  2. Macros. Macro invocations are not expanded. println! etc. are special-cased; custom macros producing logic or calls may be misclassified. Configurable via [architecture.call_parity].transparent_macros.
  3. External file modules. mod foo; declarations pointing to separate files are not followed. Only inline modules (mod foo { ... }) are analysed recursively.
  4. Sequential analysis pass. proc_macro2::Span (with span-locations enabled for line numbers) is not Sync. File I/O is parallelised via rayon.

License

MIT. See LICENSE.

Contributing

Bug reports and feature requests: open an issue at github.com/SaschaOnTour/rustqual/issues. For PRs:

  1. cargo nextest run — all tests must stay green.
  2. cargo run -- . --fail-on-warnings --coverage coverage.lcov — the source tree must keep its 100% self-compliance score.
  3. RUSTFLAGS="-Dwarnings" cargo clippy --all-targets — clippy must stay clean.
  4. Update CHANGELOG.md for any user-visible change; bump Cargo.toml version on release-worthy contributions.

The codebase is its own best reference for IOSP self-compliance and the architecture rules. The CLAUDE.md file documents internal conventions and common pitfalls.

About

Structural quality analyzer for Rust. Catches drift across files and modules — IOSP violations, duplication, weak tests, layer breaks, and adapter call-parity (a check unique to rustqual). Built for codebases mixing human and AI-generated code, where reviewers can't catch every structural slip. Seven dimensions, self-compliant at 100%.

Topics

Resources

License

Stars

Watchers

Forks

Packages

 
 
 

Contributors

Languages