diff --git a/README.md b/README.md index 79a32d6..ef24e17 100644 --- a/README.md +++ b/README.md @@ -75,8 +75,8 @@ assert!("oh no, an error: muahaha i am an error" == &format!("{}", error)); - Two optional attributes can be added to your types next to the derive: - `#[ignore_extra_doc_attributes]` makes the macro ignore any doc - comment attributes (or `///` lines) after the first. Multi-line - comments using `///` are otherwise treated as an error, so use this + comment attributes (or `///` lines) after the last non-empty line. Multi-line + comments with line breaks using `///` are otherwise treated as an error, so use this attribute or consider switching to block doc comments (`/** */`). - `#[prefix_enum_doc_attributes]` combines the doc comment message on diff --git a/examples/simple.rs b/examples/simple.rs index f4aad21..2f03d74 100644 --- a/examples/simple.rs +++ b/examples/simple.rs @@ -8,6 +8,9 @@ pub enum DataStoreError { Redaction(String), /// invalid header (expected {expected:?}, found {found:?}) InvalidHeader { expected: String, found: String }, + /// connection refused: + /// please try again later + ConnectionRefused, /// unknown data store error Unknown, } @@ -33,4 +36,14 @@ fn main() { "Enum value `InvalidHeader` should be printed as:\n\t{}", invalid_header ); + + println!( + "Enum value `ConnectionRefused` should be printed as:\n\t{}", + DataStoreError::ConnectionRefused + ); + + println!( + "Enum value `Unknown` should be printed as:\n\t{}", + DataStoreError::Unknown + ); } diff --git a/src/attr.rs b/src/attr.rs index afda4b9..89701b9 100644 --- a/src/attr.rs +++ b/src/attr.rs @@ -69,50 +69,54 @@ impl AttrsHelper { return Ok(Some(display)); } - let num_doc_attrs = attrs + let literals = attrs .iter() .filter(|attr| attr.path().is_ident("doc")) - .count(); + .map(|attr| match &attr.meta { + Meta::NameValue(syn::MetaNameValue { + value: + syn::Expr::Lit(syn::ExprLit { + lit: syn::Lit::Str(lit), + .. + }), + .. + }) => lit, + _ => unimplemented!(), + }); + + let span = match literals.clone().next() { + Some(lit) => lit.span(), + None => return Ok(None), + }; - if !self.ignore_extra_doc_attributes && num_doc_attrs > 1 { - panic!("Multi-line comments are disabled by default by displaydoc. Please consider using block doc comments (/** */) or adding the #[ignore_extra_doc_attributes] attribute to your type next to the derive."); - } + let strs = literals.map(|lit| { + // Make an attempt at cleaning up multiline doc comments. + let doc_str = lit + .value() + .lines() + .map(|line| line.trim().trim_start_matches('*').trim()) + .collect::>() + .join("\n") + .trim() + .to_string(); + (!doc_str.is_empty()).then(|| doc_str) + }); - for attr in attrs { - if attr.path().is_ident("doc") { - let lit = match &attr.meta { - Meta::NameValue(syn::MetaNameValue { - value: - syn::Expr::Lit(syn::ExprLit { - lit: syn::Lit::Str(lit), - .. - }), - .. - }) => lit, - _ => unimplemented!(), - }; - - // Make an attempt at cleaning up multiline doc comments. - let doc_str = lit - .value() - .lines() - .map(|line| line.trim().trim_start_matches('*').trim()) - .collect::>() - .join("\n"); - - let lit = LitStr::new(doc_str.trim(), lit.span()); - - let mut display = Display { - fmt: lit, - args: TokenStream::new(), - }; - - display.expand_shorthand(); - return Ok(Some(display)); - } - } + let joined = if self.ignore_extra_doc_attributes { + strs.take_while(|x| x.is_some()).collect::>>() + } else { + strs.collect::>>() + }.unwrap_or_else(|| { + panic!("Line breaks in multi-line doc comments are disabled by default by displaydoc. Please consider using block doc comments (/** */) or adding the #[ignore_extra_doc_attributes] attribute to your type next to the derive"); + }).join(" "); + + let mut display = Display { + fmt: LitStr::new(&joined, span), + args: TokenStream::new(), + }; - Ok(None) + display.expand_shorthand(); + Ok(Some(display)) } pub(crate) fn display_with_input( diff --git a/src/lib.rs b/src/lib.rs index 1a32400..b7545de 100644 --- a/src/lib.rs +++ b/src/lib.rs @@ -70,8 +70,8 @@ //! - Two optional attributes can be added to your types next to the derive: //! //! - `#[ignore_extra_doc_attributes]` makes the macro ignore any doc -//! comment attributes (or `///` lines) after the first. Multi-line -//! comments using `///` are otherwise treated as an error, so use this +//! comment attributes (or `///` lines) after the last non-empty line. Multi-line +//! comments with line breaks using `///` are otherwise treated as an error, so use this //! attribute or consider switching to block doc comments (`/** */`). //! //! - `#[prefix_enum_doc_attributes]` combines the doc comment message on diff --git a/tests/compile_tests.rs b/tests/compile_tests.rs index a806321..6513eb7 100644 --- a/tests/compile_tests.rs +++ b/tests/compile_tests.rs @@ -4,7 +4,9 @@ fn no_std() { let t = trybuild::TestCases::new(); t.compile_fail("tests/ui/without.rs"); - t.compile_fail("tests/ui/multi_line.rs"); + t.compile_fail("tests/ui/multi_line_line_break.rs"); + t.compile_fail("tests/ui/multi_line_line_break_block.rs"); + t.pass("tests/ui/multi_line.rs"); t.pass("tests/ui/multi_line_allow.rs"); t.compile_fail("tests/ui/enum_prefix_missing.rs"); t.pass("tests/ui/enum_prefix.rs"); diff --git a/tests/happy.rs b/tests/happy.rs index 85aa78f..7d94e16 100644 --- a/tests/happy.rs +++ b/tests/happy.rs @@ -12,11 +12,53 @@ struct HappyStruct { #[derive(Display)] #[ignore_extra_doc_attributes] /// Just a basic struct {thing} -/// and this line should get ignored +/// and this line should not get ignored struct HappyStruct2 { thing: &'static str, } +#[derive(Display)] +/// Really fancy first line with thing: {thing} +/// Really cool second line +struct HappyMultiLine { + thing: &'static str, +} + +#[derive(Display)] +#[ignore_extra_doc_attributes] +/// multi +/// line +/// +/// new paragraph should be ignored +struct HappyMultilineWithIgnore; + +#[derive(Display)] +enum MixedBlockAndLineComments { + /** + * hello + * block + * comment + */ + /// line comment + BlockFirst, + /// line comment + /** + * hello + * block + * comment + */ + LineFirst, + /** + * block + * comment + */ + /** + * block + * comment2 + */ + DoubleBlock, +} + #[derive(Display)] enum Happy { /// I really like Variant1 @@ -109,7 +151,30 @@ fn does_it_print() { ); assert_display(HappyStruct { thing: "hi" }, "Just a basic struct hi"); - assert_display(HappyStruct2 { thing: "hi2" }, "Just a basic struct hi2"); + assert_display( + HappyStruct2 { thing: "hi2" }, + "Just a basic struct hi2 and this line should not get ignored", + ); + + assert_display( + HappyMultiLine { thing: "rust" }, + "Really fancy first line with thing: rust Really cool second line", + ); + + assert_display(HappyMultilineWithIgnore, "multi line"); + + assert_display( + MixedBlockAndLineComments::BlockFirst, + "hello\nblock\ncomment line comment", + ); + assert_display( + MixedBlockAndLineComments::LineFirst, + "line comment hello\nblock\ncomment", + ); + assert_display( + MixedBlockAndLineComments::DoubleBlock, + "block\ncomment block\ncomment2", + ); assert_display(inner_mod::InnerHappy::Variant1, "I really like Variant1"); assert_display( diff --git a/tests/ui/multi_line.rs b/tests/ui/multi_line.rs index 4ca908b..dcb5ba5 100644 --- a/tests/ui/multi_line.rs +++ b/tests/ui/multi_line.rs @@ -8,7 +8,10 @@ enum TestType { /// Multi /// line - /// doc. + /// doc + /// is + /// pretty + /// swell Variant2, } diff --git a/tests/ui/multi_line_allow.rs b/tests/ui/multi_line_allow.rs index c2ea9d3..fb544cf 100644 --- a/tests/ui/multi_line_allow.rs +++ b/tests/ui/multi_line_allow.rs @@ -11,8 +11,17 @@ enum TestType { /// line /// doc. Variant2, + + /// Multi + /// line + /// doc + /// + /// with + /// line + /// breaks + Variant3, } static_assertions::assert_impl_all!(TestType: core::fmt::Display); -fn main() {} \ No newline at end of file +fn main() {} diff --git a/tests/ui/multi_line_line_break.rs b/tests/ui/multi_line_line_break.rs new file mode 100644 index 0000000..503a2db --- /dev/null +++ b/tests/ui/multi_line_line_break.rs @@ -0,0 +1,20 @@ +use displaydoc::Display; + +#[derive(Display)] +/// Multi +/// line +/// doc +/// with +/// line +/// break +/// +/// is +/// pretty +/// not +/// swell +/// 😞👊 +struct TestType; + +static_assertions::assert_impl_all!(TestType: core::fmt::Display); + +fn main() {} diff --git a/tests/ui/multi_line.stderr b/tests/ui/multi_line_line_break.stderr similarity index 50% rename from tests/ui/multi_line.stderr rename to tests/ui/multi_line_line_break.stderr index b37e1b4..9f7b26f 100644 --- a/tests/ui/multi_line.stderr +++ b/tests/ui/multi_line_line_break.stderr @@ -1,25 +1,25 @@ error: proc-macro derive panicked - --> tests/ui/multi_line.rs:4:10 + --> tests/ui/multi_line_line_break.rs:3:10 | -4 | #[derive(Display)] +3 | #[derive(Display)] | ^^^^^^^ | - = help: message: Multi-line comments are disabled by default by displaydoc. Please consider using block doc comments (/** */) or adding the #[ignore_extra_doc_attributes] attribute to your type next to the derive. + = help: message: Line breaks in multi-line doc comments are disabled by default by displaydoc. Please consider using block doc comments (/** */) or adding the #[ignore_extra_doc_attributes] attribute to your type next to the derive error[E0277]: `TestType` doesn't implement `std::fmt::Display` - --> tests/ui/multi_line.rs:15:37 + --> tests/ui/multi_line_line_break.rs:18:37 | -15 | static_assertions::assert_impl_all!(TestType: core::fmt::Display); +18 | static_assertions::assert_impl_all!(TestType: core::fmt::Display); | ^^^^^^^^ unsatisfied trait bound | help: the trait `std::fmt::Display` is not implemented for `TestType` - --> tests/ui/multi_line.rs:5:1 + --> tests/ui/multi_line_line_break.rs:16:1 | - 5 | enum TestType { - | ^^^^^^^^^^^^^ +16 | struct TestType; + | ^^^^^^^^^^^^^^^ note: required by a bound in `assert_impl_all` - --> tests/ui/multi_line.rs:15:1 + --> tests/ui/multi_line_line_break.rs:18:1 | -15 | static_assertions::assert_impl_all!(TestType: core::fmt::Display); +18 | static_assertions::assert_impl_all!(TestType: core::fmt::Display); | ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ required by this bound in `assert_impl_all` = note: this error originates in the macro `static_assertions::assert_impl_all` (in Nightly builds, run with -Z macro-backtrace for more info) diff --git a/tests/ui/multi_line_line_break_block.rs b/tests/ui/multi_line_line_break_block.rs new file mode 100644 index 0000000..a79d96b --- /dev/null +++ b/tests/ui/multi_line_line_break_block.rs @@ -0,0 +1,14 @@ +use displaydoc::Display; + +#[derive(Display)] +/** + * block + */ +/** + * + */ +struct TestType; + +static_assertions::assert_impl_all!(TestType: core::fmt::Display); + +fn main() {} diff --git a/tests/ui/multi_line_line_break_block.stderr b/tests/ui/multi_line_line_break_block.stderr new file mode 100644 index 0000000..a83864b --- /dev/null +++ b/tests/ui/multi_line_line_break_block.stderr @@ -0,0 +1,25 @@ +error: proc-macro derive panicked + --> tests/ui/multi_line_line_break_block.rs:3:10 + | +3 | #[derive(Display)] + | ^^^^^^^ + | + = help: message: Line breaks in multi-line doc comments are disabled by default by displaydoc. Please consider using block doc comments (/** */) or adding the #[ignore_extra_doc_attributes] attribute to your type next to the derive + +error[E0277]: `TestType` doesn't implement `std::fmt::Display` + --> tests/ui/multi_line_line_break_block.rs:12:37 + | +12 | static_assertions::assert_impl_all!(TestType: core::fmt::Display); + | ^^^^^^^^ unsatisfied trait bound + | +help: the trait `std::fmt::Display` is not implemented for `TestType` + --> tests/ui/multi_line_line_break_block.rs:10:1 + | +10 | struct TestType; + | ^^^^^^^^^^^^^^^ +note: required by a bound in `assert_impl_all` + --> tests/ui/multi_line_line_break_block.rs:12:1 + | +12 | static_assertions::assert_impl_all!(TestType: core::fmt::Display); + | ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ required by this bound in `assert_impl_all` + = note: this error originates in the macro `static_assertions::assert_impl_all` (in Nightly builds, run with -Z macro-backtrace for more info)