Keyboard shortcuts

Press ← or → to navigate between chapters

Press S or / to search in the book

Press ? to show this help

Press Esc to hide this help

Using Dylints

Dylints provides 297 compiler lints in nine general groups and 13 crate-specific groups. Select a group from the workspace root of the project you want to check. The full category descriptions and crate links are in the README.

Compiler and tool requirements

Verified 2026-10-06 with Dylint 6.0.3 and the repository’s nightly-2026-07-15 toolchain. The cargo-dylint and dylint-link command-line tools can be installed with an ordinary Cargo toolchain; those executables do not require nightly. The compiler-based lint libraries, this repository’s Dylint driver, and the target check use Rust’s unstable rustc_private APIs. The Rust Unstable Book requires rustc-dev and llvm-tools for official toolchains. Dylint builds the target with the toolchain used to build the lint library, as described in the versioned Dylint compiler workflow. For this Dylints release, the compiler-facing components were built and tested with nightly-2026-07-15; other compiler versions are not verified. A target project may keep stable for ordinary builds, but its source and dependencies must also compile when Dylint rechecks them with that nightly.

rust-src, Clippy, rustfmt and rust-analyzer are present in this repository’s pinned development toolchain for its driver and contributor workflows. Only rustc-dev and LLVM tools are fundamental to linking rustc_private crates; the other components serve the repository’s configured build or optional development tools.

On Linux, building Dylint’s Git-backed libraries and driver needs a C compiler and linker (cc), pkg-config, OpenSSL development files and zlib. Dylint 6.0.3 enables Git support through git2; its libgit2 build uses OpenSSL for HTTPS and zlib for compression, while pkg-config locates the system headers and libraries. See the upstream Dylint workspace manifest and libgit2-sys manifest. The repository development shell supplies these native tools. On macOS, install the Xcode Command Line Tools and the system libraries needed by the project and Dylint. This workflow was verified on x86_64 Linux; the repository’s Nix development flake also defines aarch64 Linux and aarch64 macOS environments. Windows consumer setup has not been verified here.

Install Dylint

See the upstream Dylint installation guide. For the tested versions:

rustup toolchain install nightly-2026-07-15 --component rustc-dev --component llvm-tools-preview
cargo install --locked --version 6.0.3 cargo-dylint dylint-link

The install command uses your default Cargo toolchain. Use cargo +nightly-2026-07-15 dylint ... for the compiler-facing check so the target, lint libraries and Dylint driver use the tested compiler.

Method 1: persist discovery in the target workspace’s Cargo.toml

In the root Cargo.toml of the workspace being checked, add a Dylint discovery table. Keep existing entries in libraries; this example chooses the recommended general groups:

[[workspace.metadata.dylint.libraries]]
git = "https://github.com/sagan-software/dylints"
branch = "main"
pattern = ["crates/correctness", "crates/perf", "crates/suspicious"]

From that workspace root, ask Dylint to load the configured libraries and check all targets:

cargo +nightly-2026-07-15 dylint --all --workspace -- --all-targets

To select only Bevy lints, use pattern = "crates/bevy" in the metadata entry. This group checks Bevy-specific ECS, systems, schedules and engine API patterns. It does not also load the general groups or the crates parent.

Method 2: run Dylint directly for Bevy

To try the Bevy group without editing Cargo.toml, run this command from the target workspace root:

cargo +nightly-2026-07-15 dylint \
    --git https://github.com/sagan-software/dylints \
    --branch main \
    --pattern crates/bevy \
    --workspace -- \
    --all-targets

--git identifies the lint repository and --pattern selects its Bevy group. For reproducible builds, replace --branch main with --tag TAG or --rev COMMIT. The Bevy group registers only Bevy lints; selecting it does not load unrelated groups. Do not load a parent and any child groups together.

Both methods use ordinary Cargo and Dylint. The repository’s nix --accept-flake-config develop shell is a reproducible environment for contributors; downstream projects can use their own shell or system packages. Downstream Nix environments need the same native build tools and libraries described above. The repository’s Nix linker setup and build-directory configuration are development details; they are not required in a consumer project’s Cargo.toml.

Groups and configuration

The nine general groups are cargo, complexity, correctness, crates, maintainability, perf, restriction, style and suspicious. The crates group contains 13 groups for Axum, Bevy, Clap, Insta, reqwest, Schemars, Serde, SQLx, Strum, test-case, thiserror, Tokio and tracing. See the README’s full group hierarchy for each group’s directory and general group scope. The crate-specific checks are:

  • Axum checks router paths, nesting and service configuration for the Axum web framework.
  • Bevy checks ECS queries, systems, schedules and selected engine API usage in Bevy.
  • Clap checks derive attributes and command-line argument configuration for Clap.
  • Insta checks snapshot assertions, filters and snapshot file handling in Insta.
  • reqwest checks HTTP client construction, request loops, retries and TLS settings in reqwest.
  • Schemars checks derives and schema metadata for Schemars, a Rust library for generating JSON Schema.
  • Serde checks serialization and deserialization attributes and round-trip behavior in Serde.
  • SQLx checks query-builder use, row access and connection-pool settings in SQLx.
  • Strum checks enum representation and derive behavior in Strum.
  • test-case checks parameterized test declarations and case matrices in test-case.
  • thiserror checks error derives, source fields and display formatting in thiserror.
  • Tokio checks runtime, task, channel and blocking-call patterns in Tokio.
  • tracing checks spans, fields and instrumentation in tracing.

Configure lint options in a dylint.toml at the target workspace root. Dylint config tables are keyed by the library’s package name, including its canonical kebab-case spelling. The repository’s dylint.toml records every option and its accepted values. The rumdl_doc_comments lint uses rumdl.toml for Markdown rule settings.

Run cargo dylint list --all to inspect the registered lint names. Arguments after -- select Cargo targets, for example:

cargo +nightly-2026-07-15 dylint --all --workspace -- --lib --bins --tests

DYLINT_RUSTFLAGS="-D warnings" rejects lint warnings. Prefer a reasoned #[expect(...)] for a reviewed exception; expectations flag exceptions that no longer trigger. The catalog links each lint to its implementation and tests.

Contributing

Run Cargo commands inside nix --accept-flake-config develop. Keep dependency sources in [workspace.dependencies]; consumers inherit sources and specify their own features. The virtual workspace contains category libraries, leaf lints, shared support, web and xtask. All workspace crates except xtask are direct children of crates/; the crates/* glob leaves nested UI fixtures outside workspace discovery. There is no portable runner.

The locked Nix development shell and CI use cargo-nextest 0.9.143 for compatible test binaries. Nextest does not execute rustdoc tests, so the exact Cargo doctest command below remains separate. The repository also keeps its exact Cargo unit, binary and integration-test gate.

Build and test concurrency is capped for workstation-sized machines. Cargo uses one compiler process, and Cargo’s test harness and nextest use one test thread. The flake requests one Nix derivation and one build core where the Nix client controls scheduling. Separate clients can still submit work concurrently.

On a multi-user installation, the Nix daemon schedules builds for every client and reads the system-wide configuration. A flake or command-line limit caps that invocation; a per-user setting supplies defaults to that user’s commands. Neither caps combined Nix work submitted by other clients. This machine’s daemon currently has max-jobs = auto and no memory limit. Avoid overlapping heavyweight builds until the daemon limit is changed.

To cap all local Nix builds on a multi-user installation, an administrator must set max-jobs = 1 and cores = 1 in the Nix daemon’s global configuration and restart the daemon. On this machine, Determinate Nix marks /etc/nix/nix.conf as managed and includes /etc/nix/nix.custom.conf; put the settings in that included file instead of editing the managed file. The daemon-wide cap affects every project on the machine. After existing builds finish, an administrator can add these lines to /etc/nix/nix.custom.conf:

max-jobs = 1
cores = 1

Then restart the daemon:

sudo systemctl restart nix-daemon.service

The repository does not change system configuration. Per-user and per-flake settings cap the invocations that use them, but do not replace the daemon-wide cap across independent Nix clients. Standalone Cargo commands use this workspace’s one-job configuration; other projects need their own job cap before compiling in parallel with Dylints.

Nix asks before applying this flake’s settings; pass --accept-flake-config to each Nix command that evaluates this flake. Raise limits only when memory is available. These settings were verified with Nix 2.35.2 on 2026-10-06. Cargo’s build.jobs can be overridden with CARGO_BUILD_JOBS; Nix’s build limits are controlled by the daemon on a multi-user installation; nextest’s test-threads can be overridden with --test-threads.

Nix environment

nix --accept-flake-config develop reads the checked-in toolchain file through rust-overlay and uses Crane for cached Cargo builds. It sets RUSTUP_AUTO_INSTALL=0 because the pinned compiler lives read-only in /nix/store; Dylint invokes rustup show active-toolchain while discovering libraries, and rustup must not try to install the file’s components into that Nix store path. Nix command wrappers include the C compiler wrapper because dylint-link delegates compiler links to cc, and GNU Make because the Rumdl lint’s jemalloc dependency builds native code. Dylint 6.0.3’s driver package exposes a library, so nix/dylint-driver supplies a small executable wrapper that Nix builds with the upstream lockfile. The development shell keeps Dylint’s linker and target-directory setup for UI tests; downstream projects do not copy it. Native OpenSSL and zlib support Dylint’s Git transport, as described in the usage guide.

The review baseline was 315 lines in flake.nix; the current file is 245 lines, a 22.2% reduction. The 25% limit would be 236 lines. The remaining dylintTools, driver-wrapper, shell-setup, treefmt and workspaceChecks definitions provide the Dylint tools and executable, pinned compiler/linker, formatter, and required CI gates. The nine-line gap is retained for build limits: the flake’s one-job/one-core defaults and explicit one-job Cargo caps. Removing those settings weakens the workstation memory limits. The single formatter and check outputs replace the duplicate local check wrappers; cargo xtask site remains the site-generation path.

Validation

cargo fmt --all
cargo fmt --all -- --check
cargo clippy --workspace --lib --bins --tests -- -D warnings -A unknown-lints
cargo nextest run --workspace --lib --bins --tests
cargo test --workspace --lib --bins --tests
cargo test --workspace --doc
cargo xtask lint
cargo xtask coverage --min-lines 97 --min-file-lines 90 -- --workspace --lib --bins --tests
cargo xtask bench
cargo xtask site
nix --accept-flake-config fmt
nix --accept-flake-config --max-jobs 1 --cores 1 flake check

Before changing a lint, add a failing UI regression at its compiler boundary. Run cargo test -p LINT_NAME --lib after each coherent change. When diagnostics change, copy the test’s reported actual stderr into the expected fixture. When fixture imports or suggested fixes change, copy the test’s reported actual fixed output into the rustfix fixture. When a fixture deliberately violates a lint, preserve that input instead of applying its suggested fix. Verify triggering and non-triggering cases.

cargo xtask lint checks ordinary library, binary and integration-test targets. Deliberate UI and example fixtures are excluded. New production paths require direct tests; line hits alone do not prove correctness.

The Clippy gate allows only unknown-lints because ordinary Cargo does not load this workspace’s Dylint libraries, while source crates use #[expect] for those custom lint names. The separate cargo dylint gate runs with -D warnings, so custom lint expectations and diagnostics remain strict there.

Coverage tests retain artifacts after test failure and reject unsafe output targets. They propagate LLVM export, summary-write and terminal-write failures. Site tests propagate catalog and book failures before report assembly.

The process fixture is a Cargo example with clap derive parsing. Tests build it in a private target directory. cargo fmt --all includes its source; nix --accept-flake-config fmt checks it with the rest of the repository.

Adding a lint

Place each lint as a direct child of crates/, beside its category and crate-specific group crates. Its manifest inherits dependencies, package metadata and lint configuration. Declare its default level as Warn. Register it through its parent group’s rlib constituent dependency. Parent groups register nested groups; discovery lists only parents.

The optional aggregate at crates/sagan-lints owns all nine general categories and must be discovered independently. Its UI fixture also records Cargo-policy diagnostics from the isolated compiler input.

Each lint owns Cargo.toml, README.md, src/lib.rs, ui/main.rs and ui/main.stderr. Keep the five required README sections in order: What it does, Why is this bad?, Known problems, Example and Use instead. Add interpretation and primary sources after those sections when needed.

Prefer resolved compiler types, methods and definitions for semantic checks. Source-layout lints can use AST structure when they explain that boundary. Shared compiler helpers live in support; category helpers remain with their category.

The compiler-sysroot bootstrap tests compile and execute the unchanged Cargo build script. They verify compiler selection, symlinks, PATH order, missing configuration, invalid layouts and protocol-write failure. Instrumented runs retain this executable in the coverage object archive, because Cargo build-script binaries are excluded from ordinary object discovery.

Reports

cargo xtask coverage validates percentages and scope directories before replacing generated outputs. It instruments test binaries and lint libraries, merges profiles and writes canonical HTML, JSON, text, LCOV, TSV and gap reports. Each compiler object is exported separately, then source aliases merge by canonical path and line, retaining a hit when any compiled variant reaches that line. The HTML, JSON, LCOV data and threshold all show one entry per canonical source file; raw LLVM alias rows are not published. The aggregate floor is 97%; each source file shown in the report must also reach 90%. Inspect canonical_gaps.txt for exact zero-hit source lines; inspect assertions before treating a covered line as correct. Examples, UI files, fixture directories, and support crates whose directory name ends in -fixture are excluded; cfg(test) and ordinary integration tests are included.

cargo nextest run --workspace --lib --bins --tests consumes .config/nextest.toml for ordinary test binaries, including Dylint UI test harnesses. Nextest does not run Rust documentation tests; run cargo test --workspace --doc separately. The exact Cargo unit, binary and integration-test command remains a separate repository gate.

Constituent builds can replace toolchain-named lint libraries with another feature variant. A Unix linker wrapper retains each successfully linked shared library before replacement. It delegates unchanged arguments to dylint-link; compiler commands remain visible to Dylint’s UI tests. Canonical coverage exports each retained object separately and merges line hits because combined LLVM mappings can discard another variant’s counters. Retaining libraries increases coverage disk use; coverage replaces only its owned build, object, profile and report directories.

The catalog follows rustdoc hidden-line rules: Rust setup lines beginning with a hash followed by a space, or a lone #, are hidden, and an escaped leading ## displays one fewer hash. Other code languages retain their comments.

Runnable README examples compile as documentation tests. Deliberately invalid examples use compile_fail. Abbreviated file-layout examples and SQLx fixture sketches use ignore and explain their omitted context; documentation tests do not verify those sketches against PostgreSQL.

Use repeated --path DIRECTORY options for a focused report. Relative paths resolve from the workspace. COVERAGE_TARGET_DIR overrides the dedicated output directory. The workspace root, its ancestors and target itself are rejected. Coverage tests reject malformed input before cleanup and prove that owned generated outputs are replaced.

Failed tests remain failures even when coverage reaches the threshold. LLVM warnings remain in llvm-cov.stderr.

cargo xtask site assembles the catalog and mdBook with existing coverage and benchmark reports under public. CI creates those reports before uploading one Pages artifact.

Releases

Increment [workspace.package].version for every push. Patch versions repair compatible behavior, minor versions add compatible lint capabilities, and major versions remove or change public workflows. For pre-1.0 development, a minor increment marks breaking changes. This removal of the portable runner uses 0.2.0.

CI tags and publishes a release only after tests, Clippy, self-lint, coverage, benchmarks, documentation and Nix checks pass. cargo xtask release validates and prints the shared version. cargo xtask release --publish creates the annotated tag and GitHub release. Existing tags cannot move to another commit. Major and minor maintenance branches use release/MAJOR and release/MAJOR.MINOR; existing branches are never retargeted automatically.

Creation uses an empty expected-value Git lease, so a branch created after discovery cannot move during publication. A rejected creation stops publication; retry after inspecting the remote branch.

Publication requires a stable version without prerelease or build identifiers. Version parsing preserves semver errors. Increment checks run before publication side effects.

Use the configured user identity for commits and tags. Keep release fixes on their maintenance branch and increment that branch’s patch version. Git-loaded compiler libraries remain unpublished on crates.io.

Performance

Measure each independently loadable leaf lint against compiler workloads. Category and aggregate libraries register their constituents and are excluded from the per-lint inventory. The harness verifies discovery against Cargo metadata and uses a shared workload generator.

Benchmarking

cargo xtask bench
cargo xtask bench --full

The default command performs a bounded inventory sweep: it samples every leaf lint with a prebuilt driver and small workloads using shortened warm-up and measurement windows. It retains Criterion’s normal sample collection and plots. Fast and full file-size results use separate Criterion groups, so their sampling modes are not compared as code changes. Use --full for longer statistical measurements before comparing a performance change. Reports include compiler startup and workload checking; they do not isolate only callback time.

Use the same pinned compiler, build profile, machine and workload sizes when comparing results. Keep external workload and toolchain changes separate from the measured implementation. Open target/criterion/report/index.html; the site publishes this index under benches/report/. Keep the generated directory structure so the index can resolve its chart and result links.

Build timings are omitted. Reliable comparisons need explicitly controlled warm and cold caches, dependency state, profile and host; this report measures compiler analysis with a prebuilt driver. Timings from an uncontrolled runner would mostly describe cache state.

Build costs

The Dylint libraries use compiler-private dependencies. Link them through dylint-link. Cargo’s build and target directories must agree because UI tests load the linked dynamic libraries from the target directory. The Nix shell clears an inherited shared build directory so each Dylint child can select its own target directory. A forced shared build directory places linker-named libraries outside Dylint’s lookup directory.

Development and CI builds disable incremental compilation and debug information to limit artifacts. For source-level backtraces, opt in with CARGO_PROFILE_DEV_DEBUG=line-tables-only; use CARGO_PROFILE_DEV_DEBUG=2 for full debugger information. These overrides rebuild affected artifacts, so use them for focused lint development. Existing artifacts from previous profiles remain on disk until explicitly cleaned.

Lint crates retain both cdylib and rlib outputs: Dylint loads the shared library, while category and aggregate crates link their constituents through Rust libraries. The rlib feature controls Dylint registration symbols, not Cargo’s output types. Helper crates already produce only rlib. Removing either output from a constituent breaks standalone loading or group composition.

Nix scopes the lint dependency cache to the aggregate lint library instead of the entire workspace. Workspace verification still covers all of its original targets. The catalog is generated through the existing xtask command.

Incremental optimization measurements

Local x86_64 Linux measurements on the 0.2.0 workspace layout with nightly-2026-07-15 (before the 0.3 layout refactor):

WorkloadBeforeAfter
Fresh cargo build -p perf --lib target directory336.2 MiB145.3 MiB
Loadable libraries in that build51.4 MiB5.9 MiB
bevy-asset-source-after-asset-plugin test executable71.1 MiB34.0 MiB
Compiler check of 500 locally allowed Markdown doc blocks492 ms55 ms

The build comparison changes only development debug information from line-tables-only to 0, using separate empty target/build directories and already downloaded dependencies. Allocated disk usage counts each inode once. One build of each profile took 17.0 and 15.7 seconds respectively; these are observations, not statistically established build-time improvements. Test executable sizes compare the same package and dependencies under both profiles.

The Markdown comparison uses identical no-debug profiles with and without the per-item lint-level guard. Each of 500 public functions has a locally allowed doc comment containing spaced emphasis. The lint remains enabled at crate level, and rumdl.toml enables MD037. The figures are medians of five alternating driver runs after warmup, including compiler startup and metadata emission. This measures skipped work on suppressed documentation, not a speedup for enabled Markdown checks or the entire lint suite. The UI regression checks local re-enabling, local suppression, and fulfilled expectations.

These measurements exclude toolchain installation, Nix store closures, and downstream project dependencies. Existing caches are not automatically removed.

Parser dependencies in focused builds

Workspace lints disable dylint-support’s default features. Only large-rust-file and unnecessary-module-directory enable its rust-file-size feature, which supplies the shared source-size parser. Direct consumers of the support crate retain that feature by default. This keeps unrelated lint builds from compiling the support crate’s full syn parser and span-location support. Other dependencies can still require syn, including compiler-side proc macros.

On the 0.3.2 workspace with the pinned compiler and debug information disabled, a fresh cargo build --locked --offline -p perf --lib used 145.3 MiB before this feature split and 126.4 MiB afterward, a 13% reduction. Both measurements used empty temporary target/build directories, warm downloaded dependencies, and one Cargo build job; allocated disk usage counted each inode once. The single builds took 29.1 and 25.2 seconds, respectively, which is insufficient to establish a repeatable build-time improvement. Temporary artifacts were removed.

These savings apply to focused builds that do not enable the parser elsewhere. The full aggregate and workspace test builds still include the parser-dependent lints, so they are not expected to see the same reduction.

Fixture dependencies

Bevy 0.19 fixture dependencies use umbrella re-exports and only required features. Bevy 0.18 remains for deliberate cross-version diagnostics. Bevy derive macros require the current umbrella dependency’s canonical name bevy; aliases are not discovered by its macro manifest helper.

Dylint’s upstream CI caches Cargo tools, registry and Git sources, compiler toolchains and Dylint drivers. The workflow also caches workspace build artifacts. Compiler and tool caches use their own versions rather than the workspace lockfile, so workspace release bumps do not invalidate them. Restore the compiler before invoking Rustup, and skip tool installation on an exact tool-cache hit. Compiler and tool caches are saved immediately after successful setup, even if a later gate fails. Registry and Git-source caching belongs to the workspace Cargo cache to avoid uploading the same data twice. Failed main-branch validations also retain dependency artifacts, so fixing a late gate does not force another cold dependency build. Keep coverage instrumentation outputs separate from ordinary build artifacts. After uploading coverage reports, CI discards the instrumented build, raw profiles and retained objects; the next measurement deliberately rebuilds those, so caching them would waste transfer time and disk space.

The Cargo CI job uses two compiler jobs on the public Ubuntu runner (four CPUs and 16 GB RAM), leaving headroom for compiler and native-dependency memory. Tests remain serialized, and local and Nix build limits remain unchanged. See GitHub runner specifications.

CI runs ordinary tests once with nextest, with doctests and instrumented coverage as separate gates. The small xtask integration suite also runs before workspace Clippy and UI tests so broken development commands fail early. Nix verification retains its independent sandboxed test run. The previous failing run spent 3½ minutes reinstalling tools despite restoring a cache; cache-hit savings and total CI times must be measured on successful runs of the updated workflow.

Source: Dylint 6.0.3 CI and Bevy 0.19 macro manifest lookup.

Attribute-free source-size checks

Source-size checks skip test-region parsing when a file contains no # character: every supported test-only marker requires an attribute. Sources containing # still use the full parser, including comments and strings that merely resemble attributes. Both the shared file-size helper and crate-size lint use this filter.

A development-profile microbenchmark of the shared file-size helper on 1,600 const _: () = (); lines took 22.65 ms before and 0.16 ms afterward per call. These are medians of seven batches of 30 calls using black_box, on the pinned compiler with debug information disabled. This isolates the helper and does not measure compiler startup, file I/O, or full-suite runtime.

Repeated-cfg diagnostic locations

repeated-cfg-gate indexes source line starts once per file that contains cfg gates. Each diagnostic endpoint then uses an indexed lookup instead of scanning all preceding lines. UTF-8 boundary checks, empty trailing lines, and invalid location rejection are preserved. The index uses one usize per source line.

A development-profile microbenchmark resolving column zero on each of 2,000 lines took 65.49 ms before and 0.18 ms afterward, including index construction. The figures are medians of seven batches, with inputs and results passed through black_box. This measures location conversion alone, not syntax parsing or a complete lint run; files with few gates have less opportunity to benefit.

Clap source snapshots

Clap derive and documentation recovery now borrows rustc’s retained source text instead of reopening and reading the complete file for each lookup. The shared source-map handle is cloned without cloning its text, and only the selected source segment is copied. This also keeps byte offsets aligned with the compiler’s source snapshot. Files without retained text still use the existing disk fallback.

Regression tests verify that retained text, including an empty snapshot, works without an accessible file, and that fallback reads and missing-file handling remain available. This removes file I/O and full-file allocations on the retained source path; no whole-suite runtime percentage has been measured for this change.

Candidate-only public-type scanning

unnecessary-public-type skips workspace scanning when the crate has no candidate types. Otherwise it stores only candidate-name counters, capped at two occurrences, and stops reading additional files when all candidates have a second occurrence. Identifier slices are borrowed instead of allocated per token. The existing lexical matching policy is unchanged.

A development-profile microbenchmark scanning 50,000 unrelated unique identifiers and two occurrences of one candidate retained 1 counter instead of 50,001. Median scanning time over seven batches was 52.9 ms before and 20.1 ms afterward. This excludes workspace discovery and file I/O; memory growth now follows the number of candidate names instead of all distinct workspace identifiers.

Shared cfg source text

repeated-cfg-gate now retains shared handles to rustc’s source strings instead of copying all loaded source text into a second collection. Files without # are omitted before parsing because they cannot contain cfg attributes. Files with attribute-like text still use the existing syntax-aware parser. The change removes full-source copies and parser work for attribute-free files; it does not change cfg classification or diagnostic locations. No whole-suite speedup is claimed for this change.

Coverage source identity caching

The coverage-based complexity lint resolves each reported source path once per compilation instead of canonicalizing it for every function. Successful and failed resolutions are cached in the report’s state; caches do not persist across compilations. Coverage still comes only from the measured executable-line records.

A development-profile microbenchmark of 5,000 coverage queries for one source file had median batch times of 49.1 ms before and 2.1 ms after caching, over seven batches. The report was parsed before timing. This measures repeated source lookup and fraction calculation, not report loading, complexity analysis, or compiler startup. It does not imply the same benefit for one-function-per-file workloads.

Bounded catalog discovery

Catalog discovery visits only crates/<crate>/ui and crates/<crate>/src, rather than recursively traversing fixture and build-output subtrees first. Source files inside a selected crate’s src are still read recursively. On the measured checkout, one discovery walk visited 1,647 entries instead of 3,169. Seven-run median times were 9.27 ms and 8.21 ms; the small timing difference is not evidence of a substantial catalog-generation speedup. The structural benefit is bounded traversal even when nested fixture/build directories grow.

Reqwest fixture features

Reqwest fixtures explicitly enable TLS, blocking requests, cookies, and multipart instead of enabling all defaults. Charset decoding, HTTP/2, and system-proxy integration are not used by these compile-time fixtures. The Linux Reqwest subtree drops from 131 to 127 distinct package/version entries: encoding_rs, fnv, h2, and tokio-util leave that subtree. The lockfile also drops unused platform-specific proxy dependencies. Packages required elsewhere can remain in the workspace graph; these counts are not a measurement of total disk savings. TLS diagnostics still compile against the existing default TLS implementation.

Router-only Axum fixtures

Axum fixture dependencies disable default features because the fixtures exercise routing and middleware APIs rather than server startup, JSON/form/query extractors, or tracing integration. The Linux Axum dependency subtree drops from 64 to 35 distinct package/version entries under the measured workspace resolution. Other workspace packages may still need some of those dependencies. All Axum UI fixtures retain their expected diagnostics with the smaller feature configuration.

Sparse field-cohesion graphs

The field-cohesion lint now connects each field’s users to one representative method and indexes direct callees, rather than comparing all method pairs and materializing shared-field cliques. Connected components—and therefore the existing diagnostic thresholds and summaries—remain the same. Regression tests compare components against the original pairwise algorithm across 4,096 field and call combinations, including self-calls and unknown callees.

For 300 methods sharing four fields, a development-profile microbenchmark of graph construction and component traversal stored 598 adjacency entries instead of 89,700. Seven-run median times were 74.64 ms before and 1.67 ms afterward. This isolates graph work; it excludes rustc traversal and does not represent a whole-suite speedup. Sparse graphs avoid quadratic edge storage for shared fields.

Interpreting metrics

The maintainability lints document their counting profiles and sources in the catalog. Cyclomatic complexity counts independent decisions. NPath estimates combinations of acyclic routes. Cognitive complexity weights nesting and control flow. ABC separates assignments, calls and conditions.

CRAP adds measured coverage to cyclomatic complexity. Compare values from the same analyzer version and source profile.

Additional measurements

Halstead counts distinct and total operators and operands. Volume is N × log₂(n), where N is the total count and n is the distinct vocabulary count. Difficulty is (n₁ / 2) × (N₂ / n₂). These dimensionless counts require a Rust-specific classification of macros, patterns, method calls and ?.

Volume and difficulty can rank candidates after corpus calibration. Estimated time and defects are heuristic projections and must not become defect or duration claims. Halstead metric definitions.

The CK suite’s inheritance depth and child count do not map directly to Rust traits. The existing type-method complexity, module fan-out and field-usage cohesion lints use explicit local Rust units. Package abstractness and main-sequence distance also need a validated Rust unit before implementation. CK definitions and Martin’s experimental package metrics.

Hotspots combine complexity with a bounded history window. Change coupling records files changed together. Choose rename, generated-file and bulk-formatting policies before comparing rankings. These history measurements belong in reports, because a compiler pass has no complete change history. Code Maat.

Mutation tests measure whether assertions reject altered behavior. Line coverage records execution. Keep those results separate. Duplication detectors need a minimum clone size and Rust grammar fixtures. Unsafe and public API counts measure review surface, not correctness. cargo-mutants, cargo-llvm-cov, and cargo-geiger.

Adoption policy

Use the implemented lint profiles for deterministic compiler feedback. Before adding Halstead, package metrics, duplication or history gates, define their Rust semantics and test them against accepted and rejected code. Use reports for calibration. Require direct behavioral tests even when coverage reaches every changed line.