diff --git a/src/source_parser.rs b/src/source_parser.rs index 2fb5df9..a1c1707 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 6087b7a..ab41a2e 100644 --- a/src/tests.rs +++ b/src/tests.rs @@ -115,6 +115,21 @@ 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(...)]` 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_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] fn test_lib() { let shear = CargoShear::new(