From 6fecf775a4d13700e55bec06cc1d3a611b5591ee Mon Sep 17 00:00:00 2001 From: Boshen Date: Wed, 15 Jul 2026 20:02:45 +0800 Subject: [PATCH 1/6] test: cover ignore that suppresses a misplaced dependency Adds a fixture for the scenario in #542: a dependency in `[dependencies]` that is only detected in a dev target, with an `ignored` entry covering it. The ignore suppresses `misplaced_dependency`, so it must not be reported as a redundant ignore. This was fixed in #367 but only covered incidentally, through `slab` in the `complex` fixture. Reverting the suppression branch from #367 makes the new test fail with the `misplaced_dependency` error from the report. --- tests/fixtures/misplaced_ignored/Cargo.lock | 16 ++++++++++++++++ tests/fixtures/misplaced_ignored/Cargo.toml | 12 ++++++++++++ tests/fixtures/misplaced_ignored/src/lib.rs | 0 tests/fixtures/misplaced_ignored/tests/test.rs | 4 ++++ tests/integration_tests.rs | 18 ++++++++++++++++++ 5 files changed, 50 insertions(+) create mode 100644 tests/fixtures/misplaced_ignored/Cargo.lock create mode 100644 tests/fixtures/misplaced_ignored/Cargo.toml create mode 100644 tests/fixtures/misplaced_ignored/src/lib.rs create mode 100644 tests/fixtures/misplaced_ignored/tests/test.rs diff --git a/tests/fixtures/misplaced_ignored/Cargo.lock b/tests/fixtures/misplaced_ignored/Cargo.lock new file mode 100644 index 00000000..53e65f7f --- /dev/null +++ b/tests/fixtures/misplaced_ignored/Cargo.lock @@ -0,0 +1,16 @@ +# This file is automatically @generated by Cargo. +# It is not intended for manual editing. +version = 4 + +[[package]] +name = "anyhow" +version = "1.0.100" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "a23eb6b1614318a8071c9b2521f36b424b2c83db5eb3a0fead4a6c0809af6e61" + +[[package]] +name = "misplaced_ignored" +version = "0.1.0" +dependencies = [ + "anyhow", +] diff --git a/tests/fixtures/misplaced_ignored/Cargo.toml b/tests/fixtures/misplaced_ignored/Cargo.toml new file mode 100644 index 00000000..75a9612b --- /dev/null +++ b/tests/fixtures/misplaced_ignored/Cargo.toml @@ -0,0 +1,12 @@ +[package] +name = "misplaced_ignored" +version = "0.1.0" +edition = "2024" + +[dependencies] +# Only detected in the dev target below, so the ignore suppresses +# `misplaced_dependency` and is therefore not redundant. +anyhow = "1.0" + +[package.metadata.cargo-shear] +ignored = ["anyhow"] diff --git a/tests/fixtures/misplaced_ignored/src/lib.rs b/tests/fixtures/misplaced_ignored/src/lib.rs new file mode 100644 index 00000000..e69de29b diff --git a/tests/fixtures/misplaced_ignored/tests/test.rs b/tests/fixtures/misplaced_ignored/tests/test.rs new file mode 100644 index 00000000..282bd7c7 --- /dev/null +++ b/tests/fixtures/misplaced_ignored/tests/test.rs @@ -0,0 +1,4 @@ +#[test] +fn test() -> anyhow::Result<()> { + Ok(()) +} diff --git a/tests/integration_tests.rs b/tests/integration_tests.rs index eaf49793..299c5c01 100644 --- a/tests/integration_tests.rs +++ b/tests/integration_tests.rs @@ -857,6 +857,24 @@ fn misplaced_fix() -> Result<(), Box> { Ok(()) } +// `anyhow` is only detected in a dev target, so the ignore suppresses the +// `misplaced_dependency` error and must not be reported as a redundant ignore. +// Otherwise removing the ignore, as the redundant-ignore help suggests, would +// surface an error instead. See https://github.com/Boshen/cargo-shear/issues/542. +#[test] +fn misplaced_ignored() -> Result<(), Box> { + let (exit_code, output, _temp_dir) = CargoShearRunner::new("misplaced_ignored").run()?; + assert_eq!(exit_code, ExitCode::SUCCESS); + + insta::assert_snapshot!(output, @r" + shear/summary + + ✓ no issues found + "); + + Ok(()) +} + // Optional `anyhow` is only used in tests but declared in `dependencies`. #[test] fn misplaced_optional_detection() -> Result<(), Box> { From 862975902d468ef416cdf2f80082315c2010fe33 Mon Sep 17 00:00:00 2001 From: Boshen Date: Wed, 15 Jul 2026 20:14:23 +0800 Subject: [PATCH 2/6] test: cover dependency used only through a serde attribute `humantime-serde` is never imported; the `serde(with = "...")` attribute is its only usage. This is the position from #542, where being flagged unused is what pushes users toward adding an ignore they don't need. Covered at the parser level in `src/tests.rs`, but not end-to-end against a real dependency. Dropping `with` from `is_serde_attribute_key` makes the new test fail with `unused dependency humantime-serde`. --- tests/fixtures/serde_attribute/Cargo.lock | 92 +++++++++++++++++++++++ tests/fixtures/serde_attribute/Cargo.toml | 9 +++ tests/fixtures/serde_attribute/src/lib.rs | 9 +++ tests/integration_tests.rs | 17 +++++ 4 files changed, 127 insertions(+) create mode 100644 tests/fixtures/serde_attribute/Cargo.lock create mode 100644 tests/fixtures/serde_attribute/Cargo.toml create mode 100644 tests/fixtures/serde_attribute/src/lib.rs diff --git a/tests/fixtures/serde_attribute/Cargo.lock b/tests/fixtures/serde_attribute/Cargo.lock new file mode 100644 index 00000000..233244f1 --- /dev/null +++ b/tests/fixtures/serde_attribute/Cargo.lock @@ -0,0 +1,92 @@ +# This file is automatically @generated by Cargo. +# It is not intended for manual editing. +version = 4 + +[[package]] +name = "humantime" +version = "2.4.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "15cdd26707701c53297e2fa6afb323d55fbc1d0810c3aec078ae3ef0424c3c15" + +[[package]] +name = "humantime-serde" +version = "1.1.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "57a3db5ea5923d99402c94e9feb261dc5ee9b4efa158b0315f788cf549cc200c" +dependencies = [ + "humantime", + "serde", +] + +[[package]] +name = "proc-macro2" +version = "1.0.106" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "8fd00f0bb2e90d81d1044c2b32617f68fcb9fa3bb7640c23e9c748e53fb30934" +dependencies = [ + "unicode-ident", +] + +[[package]] +name = "quote" +version = "1.0.46" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "dfbc457d0c7a0759a614551b11a6409e5951f6c7537be1f1b7682b9ae9230368" +dependencies = [ + "proc-macro2", +] + +[[package]] +name = "serde" +version = "1.0.228" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "9a8e94ea7f378bd32cbbd37198a4a91436180c5bb472411e48b5ec2e2124ae9e" +dependencies = [ + "serde_core", + "serde_derive", +] + +[[package]] +name = "serde_attribute" +version = "0.1.0" +dependencies = [ + "humantime-serde", + "serde", +] + +[[package]] +name = "serde_core" +version = "1.0.228" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "41d385c7d4ca58e59fc732af25c3983b67ac852c1a25000afe1175de458b67ad" +dependencies = [ + "serde_derive", +] + +[[package]] +name = "serde_derive" +version = "1.0.228" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "d540f220d3187173da220f885ab66608367b6574e925011a9353e4badda91d79" +dependencies = [ + "proc-macro2", + "quote", + "syn", +] + +[[package]] +name = "syn" +version = "2.0.119" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "872831b642d1a07999a962a351ed35b955ea2cfc8f3862091e2a240a84f17297" +dependencies = [ + "proc-macro2", + "quote", + "unicode-ident", +] + +[[package]] +name = "unicode-ident" +version = "1.0.24" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "e6e4313cd5fcd3dad5cafa179702e2b244f760991f45397d14d4ebf38247da75" diff --git a/tests/fixtures/serde_attribute/Cargo.toml b/tests/fixtures/serde_attribute/Cargo.toml new file mode 100644 index 00000000..7a3442ac --- /dev/null +++ b/tests/fixtures/serde_attribute/Cargo.toml @@ -0,0 +1,9 @@ +[package] +name = "serde_attribute" +version = "0.1.0" +edition = "2024" + +[dependencies] +serde = { version = "1.0", features = ["derive"] } +# Never imported; the `serde(with = "...")` attribute is its only usage. +humantime-serde = "1.1" diff --git a/tests/fixtures/serde_attribute/src/lib.rs b/tests/fixtures/serde_attribute/src/lib.rs new file mode 100644 index 00000000..bb1c71df --- /dev/null +++ b/tests/fixtures/serde_attribute/src/lib.rs @@ -0,0 +1,9 @@ +use std::time::Duration; + +use serde::{Deserialize, Serialize}; + +#[derive(Serialize, Deserialize)] +pub struct Config { + #[serde(with = "humantime_serde")] + pub timeout: Duration, +} diff --git a/tests/integration_tests.rs b/tests/integration_tests.rs index 299c5c01..e977e593 100644 --- a/tests/integration_tests.rs +++ b/tests/integration_tests.rs @@ -875,6 +875,23 @@ fn misplaced_ignored() -> Result<(), Box> { Ok(()) } +// `humantime-serde` is never imported; the `serde(with = "...")` attribute is its +// only usage. Flagging it as unused pushes users toward an ignore they don't need, +// which is the start of the cycle in https://github.com/Boshen/cargo-shear/issues/542. +#[test] +fn serde_attribute() -> Result<(), Box> { + let (exit_code, output, _temp_dir) = CargoShearRunner::new("serde_attribute").run()?; + assert_eq!(exit_code, ExitCode::SUCCESS); + + insta::assert_snapshot!(output, @r" + shear/summary + + ✓ no issues found + "); + + Ok(()) +} + // Optional `anyhow` is only used in tests but declared in `dependencies`. #[test] fn misplaced_optional_detection() -> Result<(), Box> { From 47a5c3756be989ffc32c9f04a6482805d49b3d04 Mon Sep 17 00:00:00 2001 From: Boshen Date: Wed, 15 Jul 2026 21:16:06 +0800 Subject: [PATCH 3/6] fix: detect serde attributes inside macro bodies A macro body keeps its attributes as plain tokens, so a `#[serde(with = "humantime_serde")]` inside one never reaches `collect_meta_imports` and the dependency looks unused. Worse, if the crate also uses it in a dev target, the only *visible* usage is the dev one, so it is reported as a misplaced dependency at error level. Following that advice, or running `--fix`, moves it to `[dev-dependencies]` and breaks the lib. That matches the escalation reported in #542. `collect_token_tree` already recovers `::` paths from macro bodies for this reason; run the serde scan there too, gated on a `serde` ident so unrelated attribute macros are unaffected. --- src/source_parser.rs | 7 ++ src/tests.rs | 11 +++ .../fixtures/serde_attribute_macro/Cargo.lock | 92 +++++++++++++++++++ .../fixtures/serde_attribute_macro/Cargo.toml | 10 ++ .../fixtures/serde_attribute_macro/src/lib.rs | 11 +++ .../serde_attribute_macro/tests/test.rs | 4 + tests/integration_tests.rs | 18 ++++ 7 files changed, 153 insertions(+) create mode 100644 tests/fixtures/serde_attribute_macro/Cargo.lock create mode 100644 tests/fixtures/serde_attribute_macro/Cargo.toml create mode 100644 tests/fixtures/serde_attribute_macro/src/lib.rs create mode 100644 tests/fixtures/serde_attribute_macro/tests/test.rs diff --git a/src/source_parser.rs b/src/source_parser.rs index 2fb5df94..a1c17070 100644 --- a/src/source_parser.rs +++ b/src/source_parser.rs @@ -403,6 +403,13 @@ impl SourceParser { } } + // A macro body keeps its attributes as plain tokens, so a + // `#[serde(with = "...")]` inside one never reaches `collect_meta_imports`. + // Here the `serde` ident is part of the stream, unlike an attribute's own path. + if tokens.iter().any(|token| token.kind() == SyntaxKind::IDENT && token.text() == "serde") { + self.collect_serde_attribute(token_tree); + } + self.collect_path_imports(&tokens); } diff --git a/src/tests.rs b/src/tests.rs index 6087b7a1..222c2060 100644 --- a/src/tests.rs +++ b/src/tests.rs @@ -115,6 +115,17 @@ fn serde_root_scoped_path() { test("#[serde(crate = \"::foo\")] struct Foo { bar: () }"); } +// A macro body keeps its attributes as tokens, so these never reach the `Attr` node +// that `#[serde(with = "...")]` is normally read from. +#[test] +fn serde_with_inside_macro() { + // `macro_rules!` definition + test("macro_rules! m { ($n:ident) => { struct $n { #[serde(with = \"foo\")] f: () } }; }"); + // macro invocation + test("m! { struct Bar { #[serde(with = \"foo\")] f: () } }"); + test("m! { struct Bar { #[serde(deserialize_with = \"foo::deserialize\")] f: () } }"); +} + #[test] fn test_lib() { let shear = CargoShear::new( diff --git a/tests/fixtures/serde_attribute_macro/Cargo.lock b/tests/fixtures/serde_attribute_macro/Cargo.lock new file mode 100644 index 00000000..b4571cbe --- /dev/null +++ b/tests/fixtures/serde_attribute_macro/Cargo.lock @@ -0,0 +1,92 @@ +# This file is automatically @generated by Cargo. +# It is not intended for manual editing. +version = 4 + +[[package]] +name = "humantime" +version = "2.4.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "15cdd26707701c53297e2fa6afb323d55fbc1d0810c3aec078ae3ef0424c3c15" + +[[package]] +name = "humantime-serde" +version = "1.1.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "57a3db5ea5923d99402c94e9feb261dc5ee9b4efa158b0315f788cf549cc200c" +dependencies = [ + "humantime", + "serde", +] + +[[package]] +name = "proc-macro2" +version = "1.0.106" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "8fd00f0bb2e90d81d1044c2b32617f68fcb9fa3bb7640c23e9c748e53fb30934" +dependencies = [ + "unicode-ident", +] + +[[package]] +name = "quote" +version = "1.0.46" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "dfbc457d0c7a0759a614551b11a6409e5951f6c7537be1f1b7682b9ae9230368" +dependencies = [ + "proc-macro2", +] + +[[package]] +name = "serde" +version = "1.0.228" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "9a8e94ea7f378bd32cbbd37198a4a91436180c5bb472411e48b5ec2e2124ae9e" +dependencies = [ + "serde_core", + "serde_derive", +] + +[[package]] +name = "serde_attribute_macro" +version = "0.1.0" +dependencies = [ + "humantime-serde", + "serde", +] + +[[package]] +name = "serde_core" +version = "1.0.228" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "41d385c7d4ca58e59fc732af25c3983b67ac852c1a25000afe1175de458b67ad" +dependencies = [ + "serde_derive", +] + +[[package]] +name = "serde_derive" +version = "1.0.228" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "d540f220d3187173da220f885ab66608367b6574e925011a9353e4badda91d79" +dependencies = [ + "proc-macro2", + "quote", + "syn", +] + +[[package]] +name = "syn" +version = "2.0.119" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "872831b642d1a07999a962a351ed35b955ea2cfc8f3862091e2a240a84f17297" +dependencies = [ + "proc-macro2", + "quote", + "unicode-ident", +] + +[[package]] +name = "unicode-ident" +version = "1.0.24" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "e6e4313cd5fcd3dad5cafa179702e2b244f760991f45397d14d4ebf38247da75" diff --git a/tests/fixtures/serde_attribute_macro/Cargo.toml b/tests/fixtures/serde_attribute_macro/Cargo.toml new file mode 100644 index 00000000..fdf56a52 --- /dev/null +++ b/tests/fixtures/serde_attribute_macro/Cargo.toml @@ -0,0 +1,10 @@ +[package] +name = "serde_attribute_macro" +version = "0.1.0" +edition = "2024" + +[dependencies] +serde = { version = "1.0", features = ["derive"] } +# Used only by the macro-generated `serde(with = "...")` in `src/lib.rs`, and in +# `tests/test.rs`. Missing the macro-generated usage makes this look dev-only. +humantime-serde = "1.1" diff --git a/tests/fixtures/serde_attribute_macro/src/lib.rs b/tests/fixtures/serde_attribute_macro/src/lib.rs new file mode 100644 index 00000000..eb489290 --- /dev/null +++ b/tests/fixtures/serde_attribute_macro/src/lib.rs @@ -0,0 +1,11 @@ +macro_rules! config { + ($name:ident) => { + #[derive(serde::Serialize, serde::Deserialize)] + pub struct $name { + #[serde(with = "humantime_serde")] + pub timeout: std::time::Duration, + } + }; +} + +config!(Config); diff --git a/tests/fixtures/serde_attribute_macro/tests/test.rs b/tests/fixtures/serde_attribute_macro/tests/test.rs new file mode 100644 index 00000000..60e277a7 --- /dev/null +++ b/tests/fixtures/serde_attribute_macro/tests/test.rs @@ -0,0 +1,4 @@ +#[test] +fn test() { + let _ = humantime_serde::re::humantime::parse_duration("1s"); +} diff --git a/tests/integration_tests.rs b/tests/integration_tests.rs index e977e593..2317b909 100644 --- a/tests/integration_tests.rs +++ b/tests/integration_tests.rs @@ -892,6 +892,24 @@ fn serde_attribute() -> Result<(), Box> { Ok(()) } +// A macro body keeps its attributes as tokens, so the `serde(with = "...")` inside +// one is the only usage of `humantime-serde` in the lib. Missing it makes the +// `tests/` usage look like the only one, and the dependency gets reported as +// misplaced — moving it to `[dev-dependencies]` would break the lib. +#[test] +fn serde_attribute_macro() -> Result<(), Box> { + let (exit_code, output, _temp_dir) = CargoShearRunner::new("serde_attribute_macro").run()?; + assert_eq!(exit_code, ExitCode::SUCCESS); + + insta::assert_snapshot!(output, @r" + shear/summary + + ✓ no issues found + "); + + Ok(()) +} + // Optional `anyhow` is only used in tests but declared in `dependencies`. #[test] fn misplaced_optional_detection() -> Result<(), Box> { From 080179adc388e8a35439bfe385f9532dc3e8446b Mon Sep 17 00:00:00 2001 From: Boshen Date: Wed, 15 Jul 2026 21:24:47 +0800 Subject: [PATCH 4/6] test: cover every serde attribute key, minimize serde fixtures MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit `serde_attributes_inside_macro` covered only `with` and `deserialize_with`; extend it to every key in `is_serde_attribute_key`. `crate` is a keyword rather than an ident, so it takes a separate branch in the key check. Drop `serde` and the derive from both serde fixtures. Nothing compiles them — cargo-shear only runs `cargo metadata` and parses — so the attribute alone exercises the parser. `humantime-serde` still pulls `serde` in transitively, so the lockfiles are unchanged in size. --- src/tests.rs | 8 ++++++-- tests/fixtures/serde_attribute/Cargo.lock | 1 - tests/fixtures/serde_attribute/Cargo.toml | 1 - tests/fixtures/serde_attribute/src/lib.rs | 7 +------ tests/fixtures/serde_attribute_macro/Cargo.lock | 1 - tests/fixtures/serde_attribute_macro/Cargo.toml | 3 +-- tests/fixtures/serde_attribute_macro/src/lib.rs | 1 - 7 files changed, 8 insertions(+), 14 deletions(-) diff --git a/src/tests.rs b/src/tests.rs index 222c2060..ab41a2e1 100644 --- a/src/tests.rs +++ b/src/tests.rs @@ -116,14 +116,18 @@ fn serde_root_scoped_path() { } // A macro body keeps its attributes as tokens, so these never reach the `Attr` node -// that `#[serde(with = "...")]` is normally read from. +// that `#[serde(...)]` is normally read from. Covers every key in +// `is_serde_attribute_key`, including `crate`, which is a keyword rather than an ident. #[test] -fn serde_with_inside_macro() { +fn serde_attributes_inside_macro() { // `macro_rules!` definition test("macro_rules! m { ($n:ident) => { struct $n { #[serde(with = \"foo\")] f: () } }; }"); // macro invocation test("m! { struct Bar { #[serde(with = \"foo\")] f: () } }"); test("m! { struct Bar { #[serde(deserialize_with = \"foo::deserialize\")] f: () } }"); + test("m! { struct Bar { #[serde(serialize_with = \"foo::serialize\")] f: () } }"); + test("m! { #[serde(crate = \"foo\")] struct Bar; }"); + test("m! { #[serde(remote = \"foo\")] struct Bar; }"); } #[test] diff --git a/tests/fixtures/serde_attribute/Cargo.lock b/tests/fixtures/serde_attribute/Cargo.lock index 233244f1..151b2bc2 100644 --- a/tests/fixtures/serde_attribute/Cargo.lock +++ b/tests/fixtures/serde_attribute/Cargo.lock @@ -51,7 +51,6 @@ name = "serde_attribute" version = "0.1.0" dependencies = [ "humantime-serde", - "serde", ] [[package]] diff --git a/tests/fixtures/serde_attribute/Cargo.toml b/tests/fixtures/serde_attribute/Cargo.toml index 7a3442ac..d6f47b54 100644 --- a/tests/fixtures/serde_attribute/Cargo.toml +++ b/tests/fixtures/serde_attribute/Cargo.toml @@ -4,6 +4,5 @@ version = "0.1.0" edition = "2024" [dependencies] -serde = { version = "1.0", features = ["derive"] } # Never imported; the `serde(with = "...")` attribute is its only usage. humantime-serde = "1.1" diff --git a/tests/fixtures/serde_attribute/src/lib.rs b/tests/fixtures/serde_attribute/src/lib.rs index bb1c71df..338be8d4 100644 --- a/tests/fixtures/serde_attribute/src/lib.rs +++ b/tests/fixtures/serde_attribute/src/lib.rs @@ -1,9 +1,4 @@ -use std::time::Duration; - -use serde::{Deserialize, Serialize}; - -#[derive(Serialize, Deserialize)] pub struct Config { #[serde(with = "humantime_serde")] - pub timeout: Duration, + pub timeout: std::time::Duration, } diff --git a/tests/fixtures/serde_attribute_macro/Cargo.lock b/tests/fixtures/serde_attribute_macro/Cargo.lock index b4571cbe..5abad805 100644 --- a/tests/fixtures/serde_attribute_macro/Cargo.lock +++ b/tests/fixtures/serde_attribute_macro/Cargo.lock @@ -51,7 +51,6 @@ name = "serde_attribute_macro" version = "0.1.0" dependencies = [ "humantime-serde", - "serde", ] [[package]] diff --git a/tests/fixtures/serde_attribute_macro/Cargo.toml b/tests/fixtures/serde_attribute_macro/Cargo.toml index fdf56a52..f720be13 100644 --- a/tests/fixtures/serde_attribute_macro/Cargo.toml +++ b/tests/fixtures/serde_attribute_macro/Cargo.toml @@ -4,7 +4,6 @@ version = "0.1.0" edition = "2024" [dependencies] -serde = { version = "1.0", features = ["derive"] } -# Used only by the macro-generated `serde(with = "...")` in `src/lib.rs`, and in +# Used by the macro-generated `serde(with = "...")` in `src/lib.rs`, and in # `tests/test.rs`. Missing the macro-generated usage makes this look dev-only. humantime-serde = "1.1" diff --git a/tests/fixtures/serde_attribute_macro/src/lib.rs b/tests/fixtures/serde_attribute_macro/src/lib.rs index eb489290..17c99d55 100644 --- a/tests/fixtures/serde_attribute_macro/src/lib.rs +++ b/tests/fixtures/serde_attribute_macro/src/lib.rs @@ -1,6 +1,5 @@ macro_rules! config { ($name:ident) => { - #[derive(serde::Serialize, serde::Deserialize)] pub struct $name { #[serde(with = "humantime_serde")] pub timeout: std::time::Duration, From c08626fd9ccb8658c5c1a3bb07711a72f3c11198 Mon Sep 17 00:00:00 2001 From: Boshen Date: Wed, 15 Jul 2026 21:43:57 +0800 Subject: [PATCH 5/6] test: drop misplaced_ignored fixture The behaviour it guards is already covered: reverting #367's suppression branch fails complex_detection and complex_fix, so a dedicated fixture adds no protection, and it is unrelated to the serde fix in this branch. --- tests/fixtures/misplaced_ignored/Cargo.lock | 16 ---------------- tests/fixtures/misplaced_ignored/Cargo.toml | 12 ------------ tests/fixtures/misplaced_ignored/src/lib.rs | 0 tests/fixtures/misplaced_ignored/tests/test.rs | 4 ---- tests/integration_tests.rs | 18 ------------------ 5 files changed, 50 deletions(-) delete mode 100644 tests/fixtures/misplaced_ignored/Cargo.lock delete mode 100644 tests/fixtures/misplaced_ignored/Cargo.toml delete mode 100644 tests/fixtures/misplaced_ignored/src/lib.rs delete mode 100644 tests/fixtures/misplaced_ignored/tests/test.rs diff --git a/tests/fixtures/misplaced_ignored/Cargo.lock b/tests/fixtures/misplaced_ignored/Cargo.lock deleted file mode 100644 index 53e65f7f..00000000 --- a/tests/fixtures/misplaced_ignored/Cargo.lock +++ /dev/null @@ -1,16 +0,0 @@ -# This file is automatically @generated by Cargo. -# It is not intended for manual editing. -version = 4 - -[[package]] -name = "anyhow" -version = "1.0.100" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "a23eb6b1614318a8071c9b2521f36b424b2c83db5eb3a0fead4a6c0809af6e61" - -[[package]] -name = "misplaced_ignored" -version = "0.1.0" -dependencies = [ - "anyhow", -] diff --git a/tests/fixtures/misplaced_ignored/Cargo.toml b/tests/fixtures/misplaced_ignored/Cargo.toml deleted file mode 100644 index 75a9612b..00000000 --- a/tests/fixtures/misplaced_ignored/Cargo.toml +++ /dev/null @@ -1,12 +0,0 @@ -[package] -name = "misplaced_ignored" -version = "0.1.0" -edition = "2024" - -[dependencies] -# Only detected in the dev target below, so the ignore suppresses -# `misplaced_dependency` and is therefore not redundant. -anyhow = "1.0" - -[package.metadata.cargo-shear] -ignored = ["anyhow"] diff --git a/tests/fixtures/misplaced_ignored/src/lib.rs b/tests/fixtures/misplaced_ignored/src/lib.rs deleted file mode 100644 index e69de29b..00000000 diff --git a/tests/fixtures/misplaced_ignored/tests/test.rs b/tests/fixtures/misplaced_ignored/tests/test.rs deleted file mode 100644 index 282bd7c7..00000000 --- a/tests/fixtures/misplaced_ignored/tests/test.rs +++ /dev/null @@ -1,4 +0,0 @@ -#[test] -fn test() -> anyhow::Result<()> { - Ok(()) -} diff --git a/tests/integration_tests.rs b/tests/integration_tests.rs index 2317b909..57e19a2b 100644 --- a/tests/integration_tests.rs +++ b/tests/integration_tests.rs @@ -857,24 +857,6 @@ fn misplaced_fix() -> Result<(), Box> { Ok(()) } -// `anyhow` is only detected in a dev target, so the ignore suppresses the -// `misplaced_dependency` error and must not be reported as a redundant ignore. -// Otherwise removing the ignore, as the redundant-ignore help suggests, would -// surface an error instead. See https://github.com/Boshen/cargo-shear/issues/542. -#[test] -fn misplaced_ignored() -> Result<(), Box> { - let (exit_code, output, _temp_dir) = CargoShearRunner::new("misplaced_ignored").run()?; - assert_eq!(exit_code, ExitCode::SUCCESS); - - insta::assert_snapshot!(output, @r" - shear/summary - - ✓ no issues found - "); - - Ok(()) -} - // `humantime-serde` is never imported; the `serde(with = "...")` attribute is its // only usage. Flagging it as unused pushes users toward an ignore they don't need, // which is the start of the cycle in https://github.com/Boshen/cargo-shear/issues/542. From 859ba653afb952869c5430aca08840e52e0bef85 Mon Sep 17 00:00:00 2001 From: Boshen Date: Wed, 15 Jul 2026 21:49:28 +0800 Subject: [PATCH 6/6] test: drop serde integration fixtures The defect is in the parser, which this repo covers with unit tests in src/tests.rs. serde_attributes_inside_macro pins it directly and fails without the fix. The fixtures could not fail unless that unit test also failed: everything downstream of the parser is generic and already covered by misplaced, clean, and unused_naming_hyphen. They cost two 91-line lockfiles and fetching humantime-serde in CI for no added protection. --- tests/fixtures/serde_attribute/Cargo.lock | 91 ------------------- tests/fixtures/serde_attribute/Cargo.toml | 8 -- tests/fixtures/serde_attribute/src/lib.rs | 4 - .../fixtures/serde_attribute_macro/Cargo.lock | 91 ------------------- .../fixtures/serde_attribute_macro/Cargo.toml | 9 -- .../fixtures/serde_attribute_macro/src/lib.rs | 10 -- .../serde_attribute_macro/tests/test.rs | 4 - tests/integration_tests.rs | 35 ------- 8 files changed, 252 deletions(-) delete mode 100644 tests/fixtures/serde_attribute/Cargo.lock delete mode 100644 tests/fixtures/serde_attribute/Cargo.toml delete mode 100644 tests/fixtures/serde_attribute/src/lib.rs delete mode 100644 tests/fixtures/serde_attribute_macro/Cargo.lock delete mode 100644 tests/fixtures/serde_attribute_macro/Cargo.toml delete mode 100644 tests/fixtures/serde_attribute_macro/src/lib.rs delete mode 100644 tests/fixtures/serde_attribute_macro/tests/test.rs diff --git a/tests/fixtures/serde_attribute/Cargo.lock b/tests/fixtures/serde_attribute/Cargo.lock deleted file mode 100644 index 151b2bc2..00000000 --- a/tests/fixtures/serde_attribute/Cargo.lock +++ /dev/null @@ -1,91 +0,0 @@ -# This file is automatically @generated by Cargo. -# It is not intended for manual editing. -version = 4 - -[[package]] -name = "humantime" -version = "2.4.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "15cdd26707701c53297e2fa6afb323d55fbc1d0810c3aec078ae3ef0424c3c15" - -[[package]] -name = "humantime-serde" -version = "1.1.1" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "57a3db5ea5923d99402c94e9feb261dc5ee9b4efa158b0315f788cf549cc200c" -dependencies = [ - "humantime", - "serde", -] - -[[package]] -name = "proc-macro2" -version = "1.0.106" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "8fd00f0bb2e90d81d1044c2b32617f68fcb9fa3bb7640c23e9c748e53fb30934" -dependencies = [ - "unicode-ident", -] - -[[package]] -name = "quote" -version = "1.0.46" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "dfbc457d0c7a0759a614551b11a6409e5951f6c7537be1f1b7682b9ae9230368" -dependencies = [ - "proc-macro2", -] - -[[package]] -name = "serde" -version = "1.0.228" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "9a8e94ea7f378bd32cbbd37198a4a91436180c5bb472411e48b5ec2e2124ae9e" -dependencies = [ - "serde_core", - "serde_derive", -] - -[[package]] -name = "serde_attribute" -version = "0.1.0" -dependencies = [ - "humantime-serde", -] - -[[package]] -name = "serde_core" -version = "1.0.228" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "41d385c7d4ca58e59fc732af25c3983b67ac852c1a25000afe1175de458b67ad" -dependencies = [ - "serde_derive", -] - -[[package]] -name = "serde_derive" -version = "1.0.228" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "d540f220d3187173da220f885ab66608367b6574e925011a9353e4badda91d79" -dependencies = [ - "proc-macro2", - "quote", - "syn", -] - -[[package]] -name = "syn" -version = "2.0.119" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "872831b642d1a07999a962a351ed35b955ea2cfc8f3862091e2a240a84f17297" -dependencies = [ - "proc-macro2", - "quote", - "unicode-ident", -] - -[[package]] -name = "unicode-ident" -version = "1.0.24" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "e6e4313cd5fcd3dad5cafa179702e2b244f760991f45397d14d4ebf38247da75" diff --git a/tests/fixtures/serde_attribute/Cargo.toml b/tests/fixtures/serde_attribute/Cargo.toml deleted file mode 100644 index d6f47b54..00000000 --- a/tests/fixtures/serde_attribute/Cargo.toml +++ /dev/null @@ -1,8 +0,0 @@ -[package] -name = "serde_attribute" -version = "0.1.0" -edition = "2024" - -[dependencies] -# Never imported; the `serde(with = "...")` attribute is its only usage. -humantime-serde = "1.1" diff --git a/tests/fixtures/serde_attribute/src/lib.rs b/tests/fixtures/serde_attribute/src/lib.rs deleted file mode 100644 index 338be8d4..00000000 --- a/tests/fixtures/serde_attribute/src/lib.rs +++ /dev/null @@ -1,4 +0,0 @@ -pub struct Config { - #[serde(with = "humantime_serde")] - pub timeout: std::time::Duration, -} diff --git a/tests/fixtures/serde_attribute_macro/Cargo.lock b/tests/fixtures/serde_attribute_macro/Cargo.lock deleted file mode 100644 index 5abad805..00000000 --- a/tests/fixtures/serde_attribute_macro/Cargo.lock +++ /dev/null @@ -1,91 +0,0 @@ -# This file is automatically @generated by Cargo. -# It is not intended for manual editing. -version = 4 - -[[package]] -name = "humantime" -version = "2.4.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "15cdd26707701c53297e2fa6afb323d55fbc1d0810c3aec078ae3ef0424c3c15" - -[[package]] -name = "humantime-serde" -version = "1.1.1" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "57a3db5ea5923d99402c94e9feb261dc5ee9b4efa158b0315f788cf549cc200c" -dependencies = [ - "humantime", - "serde", -] - -[[package]] -name = "proc-macro2" -version = "1.0.106" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "8fd00f0bb2e90d81d1044c2b32617f68fcb9fa3bb7640c23e9c748e53fb30934" -dependencies = [ - "unicode-ident", -] - -[[package]] -name = "quote" -version = "1.0.46" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "dfbc457d0c7a0759a614551b11a6409e5951f6c7537be1f1b7682b9ae9230368" -dependencies = [ - "proc-macro2", -] - -[[package]] -name = "serde" -version = "1.0.228" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "9a8e94ea7f378bd32cbbd37198a4a91436180c5bb472411e48b5ec2e2124ae9e" -dependencies = [ - "serde_core", - "serde_derive", -] - -[[package]] -name = "serde_attribute_macro" -version = "0.1.0" -dependencies = [ - "humantime-serde", -] - -[[package]] -name = "serde_core" -version = "1.0.228" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "41d385c7d4ca58e59fc732af25c3983b67ac852c1a25000afe1175de458b67ad" -dependencies = [ - "serde_derive", -] - -[[package]] -name = "serde_derive" -version = "1.0.228" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "d540f220d3187173da220f885ab66608367b6574e925011a9353e4badda91d79" -dependencies = [ - "proc-macro2", - "quote", - "syn", -] - -[[package]] -name = "syn" -version = "2.0.119" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "872831b642d1a07999a962a351ed35b955ea2cfc8f3862091e2a240a84f17297" -dependencies = [ - "proc-macro2", - "quote", - "unicode-ident", -] - -[[package]] -name = "unicode-ident" -version = "1.0.24" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "e6e4313cd5fcd3dad5cafa179702e2b244f760991f45397d14d4ebf38247da75" diff --git a/tests/fixtures/serde_attribute_macro/Cargo.toml b/tests/fixtures/serde_attribute_macro/Cargo.toml deleted file mode 100644 index f720be13..00000000 --- a/tests/fixtures/serde_attribute_macro/Cargo.toml +++ /dev/null @@ -1,9 +0,0 @@ -[package] -name = "serde_attribute_macro" -version = "0.1.0" -edition = "2024" - -[dependencies] -# Used by the macro-generated `serde(with = "...")` in `src/lib.rs`, and in -# `tests/test.rs`. Missing the macro-generated usage makes this look dev-only. -humantime-serde = "1.1" diff --git a/tests/fixtures/serde_attribute_macro/src/lib.rs b/tests/fixtures/serde_attribute_macro/src/lib.rs deleted file mode 100644 index 17c99d55..00000000 --- a/tests/fixtures/serde_attribute_macro/src/lib.rs +++ /dev/null @@ -1,10 +0,0 @@ -macro_rules! config { - ($name:ident) => { - pub struct $name { - #[serde(with = "humantime_serde")] - pub timeout: std::time::Duration, - } - }; -} - -config!(Config); diff --git a/tests/fixtures/serde_attribute_macro/tests/test.rs b/tests/fixtures/serde_attribute_macro/tests/test.rs deleted file mode 100644 index 60e277a7..00000000 --- a/tests/fixtures/serde_attribute_macro/tests/test.rs +++ /dev/null @@ -1,4 +0,0 @@ -#[test] -fn test() { - let _ = humantime_serde::re::humantime::parse_duration("1s"); -} diff --git a/tests/integration_tests.rs b/tests/integration_tests.rs index 57e19a2b..eaf49793 100644 --- a/tests/integration_tests.rs +++ b/tests/integration_tests.rs @@ -857,41 +857,6 @@ fn misplaced_fix() -> Result<(), Box> { Ok(()) } -// `humantime-serde` is never imported; the `serde(with = "...")` attribute is its -// only usage. Flagging it as unused pushes users toward an ignore they don't need, -// which is the start of the cycle in https://github.com/Boshen/cargo-shear/issues/542. -#[test] -fn serde_attribute() -> Result<(), Box> { - let (exit_code, output, _temp_dir) = CargoShearRunner::new("serde_attribute").run()?; - assert_eq!(exit_code, ExitCode::SUCCESS); - - insta::assert_snapshot!(output, @r" - shear/summary - - ✓ no issues found - "); - - Ok(()) -} - -// A macro body keeps its attributes as tokens, so the `serde(with = "...")` inside -// one is the only usage of `humantime-serde` in the lib. Missing it makes the -// `tests/` usage look like the only one, and the dependency gets reported as -// misplaced — moving it to `[dev-dependencies]` would break the lib. -#[test] -fn serde_attribute_macro() -> Result<(), Box> { - let (exit_code, output, _temp_dir) = CargoShearRunner::new("serde_attribute_macro").run()?; - assert_eq!(exit_code, ExitCode::SUCCESS); - - insta::assert_snapshot!(output, @r" - shear/summary - - ✓ no issues found - "); - - Ok(()) -} - // Optional `anyhow` is only used in tests but declared in `dependencies`. #[test] fn misplaced_optional_detection() -> Result<(), Box> {