From 163d7bb43083fa204c303c485735667884518bef Mon Sep 17 00:00:00 2001 From: Claude Date: Sun, 10 May 2026 15:38:14 +0000 Subject: [PATCH 1/3] ci: add dylint check using `perfectionist` Wire up the `perfectionist` dylint library so its lints run alongside the existing fmt and clippy checks. The library is referenced via git in `dylint.toml`, pinned to the commit that matches the published `0.0.0-rc.0` release on crates.io; `cargo-dylint` does not currently support registry sources. `test.sh` gains a `DYLINT` toggle (default `false`) that runs `cargo dylint --all -- --all-features --all-targets` once, outside the per-feature `unit` loop, since the lints inspect source style and a single `--all-features` pass already covers conditional code. CI runs this through a dedicated `dylint.yaml` workflow that installs `cargo-dylint` and `dylint-link`, matching the project's convention of one workflow per concern. --- .github/copilot-instructions.md | 6 ++-- .github/workflows/dylint.yaml | 57 ++++++++++++++++++++++++++++++ AGENTS.md | 6 ++-- CLAUDE.md | 6 ++-- CONTRIBUTING.md | 13 ++++++- dylint.toml | 4 +++ template/ai-instructions/shared.md | 6 ++-- test.sh | 3 ++ 8 files changed, 88 insertions(+), 13 deletions(-) create mode 100644 .github/workflows/dylint.yaml create mode 100644 dylint.toml diff --git a/.github/copilot-instructions.md b/.github/copilot-instructions.md index 04f9ef45..ef492d48 100644 --- a/.github/copilot-instructions.md +++ b/.github/copilot-instructions.md @@ -17,8 +17,8 @@ Read and follow the CONTRIBUTING.md file in this repository for all code style c - Minimize `unwrap()` in non-test code. Use proper error handling instead. - Prefer `#[cfg_attr(..., ignore = "reason")]` over `#[cfg(...)]` when skipping tests. Use `#[cfg]` on tests only when the code cannot compile under the condition, such as when it references types or functions that do not exist on other platforms. - A unit-test module may sit inline as `mod tests { ... }` when it is short. Once it grows long enough to noticeably extend the parent, move it to an external file and declare the module with `#[cfg(test)] mod tests;` at the end of the parent. The external file lives at `src/foo/tests.rs` for a parent `src/foo.rs`, and at `src/foo/bar/tests.rs` for a parent `src/foo/bar.rs`. Use this layout even when the parent has no other submodules. -- Install the toolchain before running tests: `rustup toolchain install "$(< rust-toolchain)" && rustup component add --toolchain "$(< rust-toolchain)" rustfmt clippy`. +- Install the toolchain before running tests: `rustup toolchain install "$(< rust-toolchain)" && rustup component add --toolchain "$(< rust-toolchain)" rustfmt clippy`. To run the dylint checks (`DYLINT=true`), additionally install `cargo-dylint` and `dylint-link` with `cargo install cargo-dylint dylint-link`. - When you change CLI arguments, help text, or anything that affects command-line output, run `./generate-completions.sh` to regenerate the shell completion files, the help text files, `USAGE.md`, and the man page. **Do not attempt to regenerate these files manually.** Always use the script. -- Validate changes with `FMT=true LINT=true BUILD=true TEST=true DOC=true ./test.sh`. When a test fails with a hint about `TEST_SKIP`, follow the hint and rerun with the suggested variable. When a sync test fails, read its error message and run the exact command it reports. -- **Always run the full test suite** (`FMT=true LINT=true BUILD=true TEST=true DOC=true ./test.sh`) before every commit. This rule applies to all changes, including documentation changes, comment edits, config changes, and refactors. The test suite checks formatting, linting, building, tests, and docs across multiple feature combinations, and any kind of change can break any of these checks. +- Validate changes with `FMT=true LINT=true BUILD=true TEST=true DOC=true DYLINT=true ./test.sh`. When a test fails with a hint about `TEST_SKIP`, follow the hint and rerun with the suggested variable. When a sync test fails, read its error message and run the exact command it reports. +- **Always run the full test suite** (`FMT=true LINT=true BUILD=true TEST=true DOC=true DYLINT=true ./test.sh`) before every commit. This rule applies to all changes, including documentation changes, comment edits, config changes, and refactors. The test suite checks formatting, linting, building, tests, and docs across multiple feature combinations, and any kind of change can break any of these checks. - When the user provides a diff to apply, run `git apply` rather than interpreting each hunk manually. When a diff is provided for context or discussion, respond accordingly. diff --git a/.github/workflows/dylint.yaml b/.github/workflows/dylint.yaml new file mode 100644 index 00000000..c9edfb13 --- /dev/null +++ b/.github/workflows/dylint.yaml @@ -0,0 +1,57 @@ +name: Dylint + +on: + - push + - pull_request + +jobs: + dylint_check: + name: Dylint + + runs-on: ubuntu-latest + + steps: + - uses: actions/checkout@v6 + + - name: Cache + uses: actions/cache@v5 + timeout-minutes: 2 + continue-on-error: true + with: + path: | + ~/.cargo + ~/.dylint_drivers + target + key: ${{ github.job }}-${{ runner.os }}-${{ hashFiles('rust-toolchain') }}-${{ hashFiles('dylint.toml') }}-${{ hashFiles('**/Cargo.lock') }} + restore-keys: | + ${{ github.job }}-${{ runner.os }}-${{ hashFiles('rust-toolchain') }}-${{ hashFiles('dylint.toml') }}-${{ hashFiles('**/Cargo.lock') }} + ${{ github.job }}-${{ runner.os }}-${{ hashFiles('rust-toolchain') }}-${{ hashFiles('dylint.toml') }}- + ${{ github.job }}-${{ runner.os }}-${{ hashFiles('rust-toolchain') }}- + + - name: Install Rust + shell: bash + run: | + installer=$(mktemp -d)/install-rustup + curl --proto '=https' --tlsv1.2 -sSf https://sh.rustup.rs > $installer + bash $installer --default-toolchain $(cat rust-toolchain) -y + + - name: Install dylint tooling + shell: bash + run: cargo install cargo-dylint dylint-link + + - name: Run dylint + shell: bash + env: + FMT: 'false' + LINT: 'false' + DOC: 'false' + BUILD: 'false' + TEST: 'false' + DYLINT: 'true' + run: ./test.sh + + - name: Clean workspace artifacts before cache + if: always() + continue-on-error: true + shell: bash + run: cargo clean --workspace diff --git a/AGENTS.md b/AGENTS.md index 04f9ef45..ef492d48 100644 --- a/AGENTS.md +++ b/AGENTS.md @@ -17,8 +17,8 @@ Read and follow the CONTRIBUTING.md file in this repository for all code style c - Minimize `unwrap()` in non-test code. Use proper error handling instead. - Prefer `#[cfg_attr(..., ignore = "reason")]` over `#[cfg(...)]` when skipping tests. Use `#[cfg]` on tests only when the code cannot compile under the condition, such as when it references types or functions that do not exist on other platforms. - A unit-test module may sit inline as `mod tests { ... }` when it is short. Once it grows long enough to noticeably extend the parent, move it to an external file and declare the module with `#[cfg(test)] mod tests;` at the end of the parent. The external file lives at `src/foo/tests.rs` for a parent `src/foo.rs`, and at `src/foo/bar/tests.rs` for a parent `src/foo/bar.rs`. Use this layout even when the parent has no other submodules. -- Install the toolchain before running tests: `rustup toolchain install "$(< rust-toolchain)" && rustup component add --toolchain "$(< rust-toolchain)" rustfmt clippy`. +- Install the toolchain before running tests: `rustup toolchain install "$(< rust-toolchain)" && rustup component add --toolchain "$(< rust-toolchain)" rustfmt clippy`. To run the dylint checks (`DYLINT=true`), additionally install `cargo-dylint` and `dylint-link` with `cargo install cargo-dylint dylint-link`. - When you change CLI arguments, help text, or anything that affects command-line output, run `./generate-completions.sh` to regenerate the shell completion files, the help text files, `USAGE.md`, and the man page. **Do not attempt to regenerate these files manually.** Always use the script. -- Validate changes with `FMT=true LINT=true BUILD=true TEST=true DOC=true ./test.sh`. When a test fails with a hint about `TEST_SKIP`, follow the hint and rerun with the suggested variable. When a sync test fails, read its error message and run the exact command it reports. -- **Always run the full test suite** (`FMT=true LINT=true BUILD=true TEST=true DOC=true ./test.sh`) before every commit. This rule applies to all changes, including documentation changes, comment edits, config changes, and refactors. The test suite checks formatting, linting, building, tests, and docs across multiple feature combinations, and any kind of change can break any of these checks. +- Validate changes with `FMT=true LINT=true BUILD=true TEST=true DOC=true DYLINT=true ./test.sh`. When a test fails with a hint about `TEST_SKIP`, follow the hint and rerun with the suggested variable. When a sync test fails, read its error message and run the exact command it reports. +- **Always run the full test suite** (`FMT=true LINT=true BUILD=true TEST=true DOC=true DYLINT=true ./test.sh`) before every commit. This rule applies to all changes, including documentation changes, comment edits, config changes, and refactors. The test suite checks formatting, linting, building, tests, and docs across multiple feature combinations, and any kind of change can break any of these checks. - When the user provides a diff to apply, run `git apply` rather than interpreting each hunk manually. When a diff is provided for context or discussion, respond accordingly. diff --git a/CLAUDE.md b/CLAUDE.md index cbc9cd8a..a6ba4eee 100644 --- a/CLAUDE.md +++ b/CLAUDE.md @@ -17,9 +17,9 @@ Read and follow the CONTRIBUTING.md file in this repository for all code style c - Minimize `unwrap()` in non-test code. Use proper error handling instead. - Prefer `#[cfg_attr(..., ignore = "reason")]` over `#[cfg(...)]` when skipping tests. Use `#[cfg]` on tests only when the code cannot compile under the condition, such as when it references types or functions that do not exist on other platforms. - A unit-test module may sit inline as `mod tests { ... }` when it is short. Once it grows long enough to noticeably extend the parent, move it to an external file and declare the module with `#[cfg(test)] mod tests;` at the end of the parent. The external file lives at `src/foo/tests.rs` for a parent `src/foo.rs`, and at `src/foo/bar/tests.rs` for a parent `src/foo/bar.rs`. Use this layout even when the parent has no other submodules. -- Install the toolchain before running tests: `rustup toolchain install "$(< rust-toolchain)" && rustup component add --toolchain "$(< rust-toolchain)" rustfmt clippy`. +- Install the toolchain before running tests: `rustup toolchain install "$(< rust-toolchain)" && rustup component add --toolchain "$(< rust-toolchain)" rustfmt clippy`. To run the dylint checks (`DYLINT=true`), additionally install `cargo-dylint` and `dylint-link` with `cargo install cargo-dylint dylint-link`. - When you change CLI arguments, help text, or anything that affects command-line output, run `./generate-completions.sh` to regenerate the shell completion files, the help text files, `USAGE.md`, and the man page. **Do not attempt to regenerate these files manually.** Always use the script. -- Validate changes with `FMT=true LINT=true BUILD=true TEST=true DOC=true ./test.sh`. When a test fails with a hint about `TEST_SKIP`, follow the hint and rerun with the suggested variable. When a sync test fails, read its error message and run the exact command it reports. -- **Always run the full test suite** (`FMT=true LINT=true BUILD=true TEST=true DOC=true ./test.sh`) before every commit. This rule applies to all changes, including documentation changes, comment edits, config changes, and refactors. The test suite checks formatting, linting, building, tests, and docs across multiple feature combinations, and any kind of change can break any of these checks. +- Validate changes with `FMT=true LINT=true BUILD=true TEST=true DOC=true DYLINT=true ./test.sh`. When a test fails with a hint about `TEST_SKIP`, follow the hint and rerun with the suggested variable. When a sync test fails, read its error message and run the exact command it reports. +- **Always run the full test suite** (`FMT=true LINT=true BUILD=true TEST=true DOC=true DYLINT=true ./test.sh`) before every commit. This rule applies to all changes, including documentation changes, comment edits, config changes, and refactors. The test suite checks formatting, linting, building, tests, and docs across multiple feature combinations, and any kind of change can break any of these checks. - When the user provides a diff to apply, run `git apply` rather than interpreting each hunk manually. When a diff is provided for context or discussion, respond accordingly. - The `gh` (GitHub CLI) is not installed. Do not attempt to use it. diff --git a/CONTRIBUTING.md b/CONTRIBUTING.md index fcfe06e6..94c4ab83 100644 --- a/CONTRIBUTING.md +++ b/CONTRIBUTING.md @@ -379,6 +379,14 @@ rustup toolchain install "$(< rust-toolchain)" rustup component add --toolchain "$(< rust-toolchain)" rustfmt clippy ``` +To run the dylint checks locally, also install `cargo-dylint` and `dylint-link`: + +```sh +cargo install cargo-dylint dylint-link +``` + +These are only required when running with `DYLINT=true`. The dylint libraries declared in `dylint.toml` are built against their own pinned nightly toolchain, which `cargo-dylint` fetches automatically on first run. + ## Optional External Dependencies Some integration tests require external tools that are not managed by `Cargo.toml`. These tests panic when the tools are absent. CI installs them to get full coverage. @@ -397,13 +405,16 @@ Before submitting, ensure: - `cargo clippy` passes on all feature combinations. - `cargo test` passes. - The project builds with no default features, with default features, and with all features. +- `cargo dylint --all` passes (requires `cargo-dylint` and `dylint-link`). The CI script `test.sh` runs all of these across every supported feature combination. You can run it locally with: ```sh -FMT=true LINT=true BUILD=true TEST=true DOC=true ./test.sh +FMT=true LINT=true BUILD=true TEST=true DOC=true DYLINT=true ./test.sh ``` +`DYLINT` defaults to `false` because it requires extra tooling and a separate nightly toolchain. Enable it once `cargo-dylint` and `dylint-link` are installed. + > [!IMPORTANT] > Always run the full test suite before every commit. This rule applies to all changes, including documentation edits, comment changes, and config updates. Any change can break formatting, linting, building, tests, or doc generation across the different feature combinations. diff --git a/dylint.toml b/dylint.toml new file mode 100644 index 00000000..f844833e --- /dev/null +++ b/dylint.toml @@ -0,0 +1,4 @@ +[workspace.metadata.dylint] +libraries = [ + { git = "https://github.com/KSXGitHub/perfectionist", rev = "561ed190f4b99bacfbf6b2ca6c6a86174d0d1fc5" }, +] diff --git a/template/ai-instructions/shared.md b/template/ai-instructions/shared.md index 04f9ef45..ef492d48 100644 --- a/template/ai-instructions/shared.md +++ b/template/ai-instructions/shared.md @@ -17,8 +17,8 @@ Read and follow the CONTRIBUTING.md file in this repository for all code style c - Minimize `unwrap()` in non-test code. Use proper error handling instead. - Prefer `#[cfg_attr(..., ignore = "reason")]` over `#[cfg(...)]` when skipping tests. Use `#[cfg]` on tests only when the code cannot compile under the condition, such as when it references types or functions that do not exist on other platforms. - A unit-test module may sit inline as `mod tests { ... }` when it is short. Once it grows long enough to noticeably extend the parent, move it to an external file and declare the module with `#[cfg(test)] mod tests;` at the end of the parent. The external file lives at `src/foo/tests.rs` for a parent `src/foo.rs`, and at `src/foo/bar/tests.rs` for a parent `src/foo/bar.rs`. Use this layout even when the parent has no other submodules. -- Install the toolchain before running tests: `rustup toolchain install "$(< rust-toolchain)" && rustup component add --toolchain "$(< rust-toolchain)" rustfmt clippy`. +- Install the toolchain before running tests: `rustup toolchain install "$(< rust-toolchain)" && rustup component add --toolchain "$(< rust-toolchain)" rustfmt clippy`. To run the dylint checks (`DYLINT=true`), additionally install `cargo-dylint` and `dylint-link` with `cargo install cargo-dylint dylint-link`. - When you change CLI arguments, help text, or anything that affects command-line output, run `./generate-completions.sh` to regenerate the shell completion files, the help text files, `USAGE.md`, and the man page. **Do not attempt to regenerate these files manually.** Always use the script. -- Validate changes with `FMT=true LINT=true BUILD=true TEST=true DOC=true ./test.sh`. When a test fails with a hint about `TEST_SKIP`, follow the hint and rerun with the suggested variable. When a sync test fails, read its error message and run the exact command it reports. -- **Always run the full test suite** (`FMT=true LINT=true BUILD=true TEST=true DOC=true ./test.sh`) before every commit. This rule applies to all changes, including documentation changes, comment edits, config changes, and refactors. The test suite checks formatting, linting, building, tests, and docs across multiple feature combinations, and any kind of change can break any of these checks. +- Validate changes with `FMT=true LINT=true BUILD=true TEST=true DOC=true DYLINT=true ./test.sh`. When a test fails with a hint about `TEST_SKIP`, follow the hint and rerun with the suggested variable. When a sync test fails, read its error message and run the exact command it reports. +- **Always run the full test suite** (`FMT=true LINT=true BUILD=true TEST=true DOC=true DYLINT=true ./test.sh`) before every commit. This rule applies to all changes, including documentation changes, comment edits, config changes, and refactors. The test suite checks formatting, linting, building, tests, and docs across multiple feature combinations, and any kind of change can break any of these checks. - When the user provides a diff to apply, run `git apply` rather than interpreting each hunk manually. When a diff is provided for context or discussion, respond accordingly. diff --git a/test.sh b/test.sh index ebcd43dc..6d5ca49b 100755 --- a/test.sh +++ b/test.sh @@ -73,6 +73,9 @@ unit() ( ) run_if "${FMT:-true}" cargo fmt -- --check +# Dylint inspects the source under all feature gates in a single pass, so it is +# run once with `--all-features` rather than across every feature combination. +run_if "${DYLINT:-false}" cargo dylint --all -- --all-features --all-targets unit "$@" unit --no-default-features "$@" unit --all-features "$@" From 693cab6a882b7fcca77c3a29f6c8b8c6d932dc29 Mon Sep 17 00:00:00 2001 From: Claude Date: Sun, 10 May 2026 16:25:55 +0000 Subject: [PATCH 2/3] ci(dylint): pass `--locked` to `cargo install` --- .github/workflows/dylint.yaml | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/.github/workflows/dylint.yaml b/.github/workflows/dylint.yaml index c9edfb13..81cb92f4 100644 --- a/.github/workflows/dylint.yaml +++ b/.github/workflows/dylint.yaml @@ -37,7 +37,7 @@ jobs: - name: Install dylint tooling shell: bash - run: cargo install cargo-dylint dylint-link + run: cargo install --locked cargo-dylint dylint-link - name: Run dylint shell: bash From 8ae7fded54f2def1934a8a88cf2c4d5c45d0f1e1 Mon Sep 17 00:00:00 2001 From: Claude Date: Sun, 10 May 2026 17:21:59 +0000 Subject: [PATCH 3/3] ci(dylint): pin `perfectionist` to tag `0.0.0-rc.0` --- dylint.toml | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/dylint.toml b/dylint.toml index f844833e..770c0632 100644 --- a/dylint.toml +++ b/dylint.toml @@ -1,4 +1,4 @@ [workspace.metadata.dylint] libraries = [ - { git = "https://github.com/KSXGitHub/perfectionist", rev = "561ed190f4b99bacfbf6b2ca6c6a86174d0d1fc5" }, + { git = "https://github.com/KSXGitHub/perfectionist", tag = "0.0.0-rc.0" }, ]