From 4b7130dfa191e94043fa84673c9cb57e7ff46f63 Mon Sep 17 00:00:00 2001 From: lqd Date: Fri, 7 Sep 2018 19:51:21 +0200 Subject: [PATCH 1/8] normalize_doc_attributes option: convert doc attributes to comments Convert `#![doc]` and `#[doc]` attributes to `//!` and `///` doc comments. --- Configurations.md | 26 ++++++++++++++++++++++++++ src/attr.rs | 29 ++++++++++++++++++++++++++--- src/config/mod.rs | 1 + tests/source/doc-attrib.rs | 11 +++++++++++ tests/target/doc-attrib.rs | 17 +++++++++++++++++ 5 files changed, 81 insertions(+), 3 deletions(-) create mode 100644 tests/source/doc-attrib.rs create mode 100644 tests/target/doc-attrib.rs diff --git a/Configurations.md b/Configurations.md index ae8ea2b3c6d2..7f44fa5d3e87 100644 --- a/Configurations.md +++ b/Configurations.md @@ -2188,6 +2188,32 @@ If you want to format code that requires edition 2018, add the following to your edition = "2018" ``` +## `normalize_doc_attributes` + +Convert `#![doc]` and `#[doc]` attributes to `//!` and `///` doc comments. + +- **Default value**: `false` +- **Possible values**: `true`, `false` +- **Stable**: No + +#### `false` (default): + +```rust +#![doc = "Example documentation"] + +#[doc = "Example item documentation"] +pub enum Foo {} +``` + +#### `true`: + +```rust +//! Example documentation + +/// Example item documentation +pub enum Foo {} +``` + ## `emit_mode` Internal option diff --git a/src/attr.rs b/src/attr.rs index fc29c69b498c..c7ee633ec4ed 100644 --- a/src/attr.rs +++ b/src/attr.rs @@ -10,7 +10,7 @@ //! Format attributes and meta items. -use comment::{contains_comment, rewrite_doc_comment}; +use comment::{contains_comment, rewrite_doc_comment, CommentStyle}; use config::lists::*; use config::IndentStyle; use expr::rewrite_literal; @@ -350,11 +350,34 @@ impl Rewrite for ast::Attribute { if contains_comment(snippet) { return Some(snippet.to_owned()); } + + let meta = self.meta(); + + // This attribute is possibly a doc attribute needing normalization to a doc comment + if context.config.normalize_doc_attributes() { + if let Some(ref meta) = meta { + if meta.check_name("doc") { + if let Some(ref literal) = meta.value_str() { + let comment_style = match self.style { + ast::AttrStyle::Inner => CommentStyle::Doc, + ast::AttrStyle::Outer => CommentStyle::TripleSlash, + }; + + let doc_comment = format!("{}{}", comment_style.opener(), literal); + return rewrite_doc_comment( + &doc_comment, + shape.comment(context.config), + context.config, + ); + } + } + } + } + // 1 = `[` let shape = shape.offset_left(prefix.len() + 1)?; Some( - self.meta() - .and_then(|meta| meta.rewrite(context, shape)) + meta.and_then(|meta| meta.rewrite(context, shape)) .map_or_else(|| snippet.to_owned(), |rw| format!("{}[{}]", prefix, rw)), ) } diff --git a/src/config/mod.rs b/src/config/mod.rs index f240c7b13c68..fca4ca98ddda 100644 --- a/src/config/mod.rs +++ b/src/config/mod.rs @@ -107,6 +107,7 @@ create_config! { blank_lines_lower_bound: usize, 0, false, "Minimum number of blank lines which must be put between items"; edition: Edition, Edition::Edition2015, false, "The edition of the parser (RFC 2052)"; + normalize_doc_attributes: bool, false, false, "Normalize doc attributes as doc comments"; // Options that can change the source code beyond whitespace/blocks (somewhat linty things) merge_derives: bool, true, true, "Merge multiple `#[derive(...)]` into a single one"; diff --git a/tests/source/doc-attrib.rs b/tests/source/doc-attrib.rs new file mode 100644 index 000000000000..f8f2b9566dc1 --- /dev/null +++ b/tests/source/doc-attrib.rs @@ -0,0 +1,11 @@ +// rustfmt-wrap_comments: true +// rustfmt-normalize_doc_attributes: true + +#![doc = "Example doc attribute comment"] + +// Long `#[doc = "..."]` +struct A { #[doc = "xxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxx"] b: i32 } + + +#[doc = "The `nodes` and `edges` method each return instantiations of `Cow<[T]>` to leave implementers the freedom to create entirely new vectors or to pass back slices into internally owned vectors."] +struct B { b: i32 } \ No newline at end of file diff --git a/tests/target/doc-attrib.rs b/tests/target/doc-attrib.rs new file mode 100644 index 000000000000..5f77de07de32 --- /dev/null +++ b/tests/target/doc-attrib.rs @@ -0,0 +1,17 @@ +// rustfmt-wrap_comments: true +// rustfmt-normalize_doc_attributes: true + +//! Example doc attribute comment + +// Long `#[doc = "..."]` +struct A { + /// xxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxx + b: i32, +} + +/// The `nodes` and `edges` method each return instantiations of `Cow<[T]>` to +/// leave implementers the freedom to create entirely new vectors or to pass +/// back slices into internally owned vectors. +struct B { + b: i32, +} From 9f6cf7b72c0b34f66349ad03a801827f6b203e0e Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Re=CC=81my=20Rakic?= Date: Thu, 13 Sep 2018 00:37:31 +0200 Subject: [PATCH 2/8] move config attribute --- src/config/mod.rs | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/config/mod.rs b/src/config/mod.rs index fca4ca98ddda..52401a18d92e 100644 --- a/src/config/mod.rs +++ b/src/config/mod.rs @@ -49,6 +49,7 @@ create_config! { comment_width: usize, 80, false, "Maximum length of comments. No effect unless wrap_comments = true"; normalize_comments: bool, false, false, "Convert /* */ comments to // comments where possible"; + normalize_doc_attributes: bool, false, false, "Normalize doc attributes as doc comments"; license_template_path: String, String::default(), false, "Beginning of file must match license template"; format_strings: bool, false, false, "Format string literals where necessary"; @@ -107,7 +108,6 @@ create_config! { blank_lines_lower_bound: usize, 0, false, "Minimum number of blank lines which must be put between items"; edition: Edition, Edition::Edition2015, false, "The edition of the parser (RFC 2052)"; - normalize_doc_attributes: bool, false, false, "Normalize doc attributes as doc comments"; // Options that can change the source code beyond whitespace/blocks (somewhat linty things) merge_derives: bool, true, true, "Merge multiple `#[derive(...)]` into a single one"; From 43f813867c5f9ffaab01e78cc8a0b887ca1311d5 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Re=CC=81my=20Rakic?= Date: Thu, 13 Sep 2018 01:02:11 +0200 Subject: [PATCH 3/8] add test with multiple levels of indents --- tests/source/doc-attrib.rs | 16 +++++++++++++++- tests/target/doc-attrib.rs | 12 ++++++++++++ 2 files changed, 27 insertions(+), 1 deletion(-) diff --git a/tests/source/doc-attrib.rs b/tests/source/doc-attrib.rs index f8f2b9566dc1..533e7f1c3acf 100644 --- a/tests/source/doc-attrib.rs +++ b/tests/source/doc-attrib.rs @@ -8,4 +8,18 @@ struct A { #[doc = "xxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxx #[doc = "The `nodes` and `edges` method each return instantiations of `Cow<[T]>` to leave implementers the freedom to create entirely new vectors or to pass back slices into internally owned vectors."] -struct B { b: i32 } \ No newline at end of file +struct B { b: i32 } + + +#[doc = "Level 1 comment"] +mod tests { + #[doc = "Level 2 comment"] + impl A { + #[doc = "Level 3 comment"] + fn f() { + #[doc = "Level 4 comment"] + fn g() { + } + } + } +} diff --git a/tests/target/doc-attrib.rs b/tests/target/doc-attrib.rs index 5f77de07de32..475cc9c748ce 100644 --- a/tests/target/doc-attrib.rs +++ b/tests/target/doc-attrib.rs @@ -15,3 +15,15 @@ struct A { struct B { b: i32, } + +/// Level 1 comment +mod tests { + /// Level 2 comment + impl A { + /// Level 3 comment + fn f() { + /// Level 4 comment + fn g() {} + } + } +} From 4af0888648d58a0641d8f928fdf757bdd16f23c2 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Re=CC=81my=20Rakic?= Date: Thu, 13 Sep 2018 01:06:02 +0200 Subject: [PATCH 4/8] add non-regression test to existing doc attributes when `normalize_doc_attributes` is off, they shouldn't be normalized to a doc comment --- tests/source/attrib.rs | 1 + tests/target/attrib.rs | 1 + 2 files changed, 2 insertions(+) diff --git a/tests/source/attrib.rs b/tests/source/attrib.rs index 113464112695..d607b7e6f2f3 100644 --- a/tests/source/attrib.rs +++ b/tests/source/attrib.rs @@ -30,6 +30,7 @@ impl Bar { /// Blah blah blooo. /// Blah blah blooo. #[an_attribute] + #[doc = "an attribute that shouldn't be normalized to a doc comment"] fn foo(&mut self) -> isize { } diff --git a/tests/target/attrib.rs b/tests/target/attrib.rs index 84841f33d8ef..942f669d4956 100644 --- a/tests/target/attrib.rs +++ b/tests/target/attrib.rs @@ -33,6 +33,7 @@ impl Bar { /// Blah blah blooo. /// Blah blah blooo. #[an_attribute] + #[doc = "an attribute that shouldn't be normalized to a doc comment"] fn foo(&mut self) -> isize {} /// Blah blah bing. From 171e656f64daf2384b97e4db1d2bcefd3ebe9950 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Re=CC=81my=20Rakic?= Date: Thu, 13 Sep 2018 01:10:57 +0200 Subject: [PATCH 5/8] add non-regression test to existing attributes when `normalize_doc_attributes` is on, they shouldn't be affected --- tests/source/doc-attrib.rs | 50 ++++++++++++++++++++++++++++++++ tests/target/doc-attrib.rs | 58 ++++++++++++++++++++++++++++++++++++++ 2 files changed, 108 insertions(+) diff --git a/tests/source/doc-attrib.rs b/tests/source/doc-attrib.rs index 533e7f1c3acf..5f78acdbcf54 100644 --- a/tests/source/doc-attrib.rs +++ b/tests/source/doc-attrib.rs @@ -23,3 +23,53 @@ mod tests { } } } + +// non-regression test for regular attributes, from #2647 +#[cfg(feature = "this_line_is_101_characters_long_xxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxx")] +pub fn foo() {} + +// path attrs +#[clippy::bar] +#[clippy::bar=foo] +#[clippy::bar(a, b, c)] +pub fn foo() {} + +mod issue_2620 { + #[derive(Debug, StructOpt)] +#[structopt(about = "Display information about the character on FF Logs")] +pub struct Params { + #[structopt(help = "The server the character is on")] + server: String, + #[structopt(help = "The character's first name")] + first_name: String, + #[structopt(help = "The character's last name")] + last_name: String, + #[structopt( + short = "j", + long = "job", + help = "The job to look at", + parse(try_from_str) + )] + job: Option +} +} + +// non-regression test for regular attributes, from #2969 +#[cfg(not(all(feature="std", + any(target_os = "linux", target_os = "android", + target_os = "netbsd", + target_os = "dragonfly", + target_os = "haiku", + target_os = "emscripten", + target_os = "solaris", + target_os = "cloudabi", + target_os = "macos", target_os = "ios", + target_os = "freebsd", + target_os = "openbsd", target_os = "bitrig", + target_os = "redox", + target_os = "fuchsia", + windows, + all(target_arch = "wasm32", feature = "stdweb"), + all(target_arch = "wasm32", feature = "wasm-bindgen"), + ))))] +type Os = NoSource; diff --git a/tests/target/doc-attrib.rs b/tests/target/doc-attrib.rs index 475cc9c748ce..5b565223eff4 100644 --- a/tests/target/doc-attrib.rs +++ b/tests/target/doc-attrib.rs @@ -27,3 +27,61 @@ mod tests { } } } + +// non-regression test for regular attributes, from #2647 +#[cfg( + feature = "this_line_is_101_characters_long_xxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxx" +)] +pub fn foo() {} + +// path attrs +#[clippy::bar] +#[clippy::bar=foo] +#[clippy::bar(a, b, c)] +pub fn foo() {} + +mod issue_2620 { + #[derive(Debug, StructOpt)] + #[structopt(about = "Display information about the character on FF Logs")] + pub struct Params { + #[structopt(help = "The server the character is on")] + server: String, + #[structopt(help = "The character's first name")] + first_name: String, + #[structopt(help = "The character's last name")] + last_name: String, + #[structopt( + short = "j", + long = "job", + help = "The job to look at", + parse(try_from_str) + )] + job: Option, + } +} + +// non-regression test for regular attributes, from #2969 +#[cfg(not(all( + feature = "std", + any( + target_os = "linux", + target_os = "android", + target_os = "netbsd", + target_os = "dragonfly", + target_os = "haiku", + target_os = "emscripten", + target_os = "solaris", + target_os = "cloudabi", + target_os = "macos", + target_os = "ios", + target_os = "freebsd", + target_os = "openbsd", + target_os = "bitrig", + target_os = "redox", + target_os = "fuchsia", + windows, + all(target_arch = "wasm32", feature = "stdweb"), + all(target_arch = "wasm32", feature = "wasm-bindgen"), + ) +)))] +type Os = NoSource; From 6c96ab7c9a6d2d1824af121022e6e70c639a3b3b Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Re=CC=81my=20Rakic?= Date: Thu, 13 Sep 2018 01:13:07 +0200 Subject: [PATCH 6/8] address review comment in Attribute rewrite fn --- src/attr.rs | 48 +++++++++++++++++++++++------------------------- 1 file changed, 23 insertions(+), 25 deletions(-) diff --git a/src/attr.rs b/src/attr.rs index c7ee633ec4ed..f8f6cd1c3760 100644 --- a/src/attr.rs +++ b/src/attr.rs @@ -351,35 +351,33 @@ impl Rewrite for ast::Attribute { return Some(snippet.to_owned()); } - let meta = self.meta(); + if let Some(ref meta) = self.meta() { + // This attribute is possibly a doc attribute needing normalization to a doc comment + if context.config.normalize_doc_attributes() && meta.check_name("doc") { + if let Some(ref literal) = meta.value_str() { + let comment_style = match self.style { + ast::AttrStyle::Inner => CommentStyle::Doc, + ast::AttrStyle::Outer => CommentStyle::TripleSlash, + }; - // This attribute is possibly a doc attribute needing normalization to a doc comment - if context.config.normalize_doc_attributes() { - if let Some(ref meta) = meta { - if meta.check_name("doc") { - if let Some(ref literal) = meta.value_str() { - let comment_style = match self.style { - ast::AttrStyle::Inner => CommentStyle::Doc, - ast::AttrStyle::Outer => CommentStyle::TripleSlash, - }; - - let doc_comment = format!("{}{}", comment_style.opener(), literal); - return rewrite_doc_comment( - &doc_comment, - shape.comment(context.config), - context.config, - ); - } + let doc_comment = format!("{}{}", comment_style.opener(), literal); + return rewrite_doc_comment( + &doc_comment, + shape.comment(context.config), + context.config, + ); } } - } - // 1 = `[` - let shape = shape.offset_left(prefix.len() + 1)?; - Some( - meta.and_then(|meta| meta.rewrite(context, shape)) - .map_or_else(|| snippet.to_owned(), |rw| format!("{}[{}]", prefix, rw)), - ) + // 1 = `[` + let shape = shape.offset_left(prefix.len() + 1)?; + Some( + meta.rewrite(context, shape) + .map_or_else(|| snippet.to_owned(), |rw| format!("{}[{}]", prefix, rw)), + ) + } else { + Some(snippet.to_owned()) + } } } } From cbc58410d9577a49533a08b14a7a7e9227e37d5a Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Re=CC=81my=20Rakic?= Date: Thu, 13 Sep 2018 02:27:09 +0200 Subject: [PATCH 7/8] add tests interleaving doc attrib comments and regular comments --- tests/source/doc-attrib.rs | 12 +++++++++++- tests/target/doc-attrib.rs | 10 ++++++++++ 2 files changed, 21 insertions(+), 1 deletion(-) diff --git a/tests/source/doc-attrib.rs b/tests/source/doc-attrib.rs index 5f78acdbcf54..67caeff1d540 100644 --- a/tests/source/doc-attrib.rs +++ b/tests/source/doc-attrib.rs @@ -18,12 +18,22 @@ mod tests { #[doc = "Level 3 comment"] fn f() { #[doc = "Level 4 comment"] - fn g() { + fn g() { } } } } +struct C { + #[doc = "item doc attrib comment"] + // regular item comment + b: i32, + + // regular item comment + #[doc = "item doc attrib comment"] + c: i32, +} + // non-regression test for regular attributes, from #2647 #[cfg(feature = "this_line_is_101_characters_long_xxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxx")] pub fn foo() {} diff --git a/tests/target/doc-attrib.rs b/tests/target/doc-attrib.rs index 5b565223eff4..c672bc39c05f 100644 --- a/tests/target/doc-attrib.rs +++ b/tests/target/doc-attrib.rs @@ -28,6 +28,16 @@ mod tests { } } +struct C { + /// item doc attrib comment + // regular item comment + b: i32, + + // regular item comment + /// item doc attrib comment + c: i32, +} + // non-regression test for regular attributes, from #2647 #[cfg( feature = "this_line_is_101_characters_long_xxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxx" From 0c73b9414bdfdc1f8d4d910bf2276e5ea9d2e9de Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Re=CC=81my=20Rakic?= Date: Thu, 13 Sep 2018 02:40:24 +0200 Subject: [PATCH 8/8] add test ensuring only doc = "" attributes are normalized to comments The other shapes of doc attributes shouldn't be normalized or modified. --- tests/source/doc-attrib.rs | 6 ++++++ tests/target/doc-attrib.rs | 8 ++++++++ 2 files changed, 14 insertions(+) diff --git a/tests/source/doc-attrib.rs b/tests/source/doc-attrib.rs index 67caeff1d540..363e1441da3e 100644 --- a/tests/source/doc-attrib.rs +++ b/tests/source/doc-attrib.rs @@ -1,7 +1,13 @@ // rustfmt-wrap_comments: true // rustfmt-normalize_doc_attributes: true +// Only doc = "" attributes should be normalized #![doc = "Example doc attribute comment"] +#![doc(html_logo_url = "https://www.rust-lang.org/logos/rust-logo-128x128-blk-v2.png", + html_favicon_url = "https://doc.rust-lang.org/favicon.ico", + html_root_url = "https://doc.rust-lang.org/nightly/", + html_playground_url = "https://play.rust-lang.org/", test(attr(deny(warnings))))] + // Long `#[doc = "..."]` struct A { #[doc = "xxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxx"] b: i32 } diff --git a/tests/target/doc-attrib.rs b/tests/target/doc-attrib.rs index c672bc39c05f..044e004decc2 100644 --- a/tests/target/doc-attrib.rs +++ b/tests/target/doc-attrib.rs @@ -1,7 +1,15 @@ // rustfmt-wrap_comments: true // rustfmt-normalize_doc_attributes: true +// Only doc = "" attributes should be normalized //! Example doc attribute comment +#![doc( + html_logo_url = "https://www.rust-lang.org/logos/rust-logo-128x128-blk-v2.png", + html_favicon_url = "https://doc.rust-lang.org/favicon.ico", + html_root_url = "https://doc.rust-lang.org/nightly/", + html_playground_url = "https://play.rust-lang.org/", + test(attr(deny(warnings))) +)] // Long `#[doc = "..."]` struct A {