diff --git a/src/attr.rs b/src/attr.rs index e40b16314fd2..1fb4ef407eb0 100644 --- a/src/attr.rs +++ b/src/attr.rs @@ -3,6 +3,7 @@ use syntax::ast; use syntax::source_map::{BytePos, Span, DUMMY_SP}; +use self::doc_comment::DocCommentFormatter; use crate::comment::{contains_comment, rewrite_doc_comment, CommentStyle}; use crate::config::lists::*; use crate::config::IndentStyle; @@ -14,6 +15,8 @@ use crate::shape::Shape; use crate::types::{rewrite_path, PathContext}; use crate::utils::{count_newlines, mk_sp}; +mod doc_comment; + /// Returns attributes on the given statement. pub(crate) fn get_attrs_from_stmt(stmt: &ast::Stmt) -> &[ast::Attribute] { match stmt.node { @@ -330,11 +333,9 @@ impl Rewrite for ast::Attribute { ast::AttrStyle::Outer => CommentStyle::TripleSlash, }; - // Remove possible whitespace from the `CommentStyle::opener()` so that - // the literal itself has control over the comment's leading spaces. - let opener = comment_style.opener().trim_end(); - - let doc_comment = format!("{}{}", opener, literal); + let doc_comment_formatter = + DocCommentFormatter::new(literal.as_str().get(), comment_style); + let doc_comment = format!("{}", doc_comment_formatter); return rewrite_doc_comment( &doc_comment, shape.comment(context.config), diff --git a/src/attr/doc_comment.rs b/src/attr/doc_comment.rs new file mode 100644 index 000000000000..00185425d6a5 --- /dev/null +++ b/src/attr/doc_comment.rs @@ -0,0 +1,77 @@ +use crate::comment::CommentStyle; +use std::fmt::{self, Display}; + +/// Formats a string as a doc comment using the given [`CommentStyle`]. +#[derive(new)] +pub(super) struct DocCommentFormatter<'a> { + literal: &'a str, + style: CommentStyle<'a>, +} + +impl Display for DocCommentFormatter<'_> { + fn fmt(&self, formatter: &mut fmt::Formatter<'_>) -> fmt::Result { + let opener = self.style.opener().trim_end(); + let mut lines = self.literal.lines().peekable(); + while let Some(line) = lines.next() { + let is_last_line = lines.peek().is_none(); + if is_last_line { + write!(formatter, "{}{}", opener, line)?; + } else { + writeln!(formatter, "{}{}", opener, line)?; + } + } + Ok(()) + } +} + +#[cfg(test)] +mod tests { + use super::*; + + #[test] + fn literal_controls_leading_spaces() { + test_doc_comment_is_formatted_correctly( + " Lorem ipsum", + "/// Lorem ipsum", + CommentStyle::TripleSlash, + ); + } + + #[test] + fn single_line_doc_comment_is_formatted_correctly() { + test_doc_comment_is_formatted_correctly( + "Lorem ipsum", + "///Lorem ipsum", + CommentStyle::TripleSlash, + ); + } + + #[test] + fn multi_line_doc_comment_is_formatted_correctly() { + test_doc_comment_is_formatted_correctly( + "Lorem ipsum\nDolor sit amet", + "///Lorem ipsum\n///Dolor sit amet", + CommentStyle::TripleSlash, + ); + } + + #[test] + fn whitespace_within_lines_is_preserved() { + test_doc_comment_is_formatted_correctly( + " Lorem ipsum \n Dolor sit amet ", + "/// Lorem ipsum \n/// Dolor sit amet ", + CommentStyle::TripleSlash, + ); + } + + fn test_doc_comment_is_formatted_correctly( + literal: &str, + expected_comment: &str, + style: CommentStyle<'_>, + ) { + assert_eq!( + expected_comment, + format!("{}", DocCommentFormatter::new(&literal, style)) + ); + } +} diff --git a/tests/source/normalize_multiline_doc_attribute.rs b/tests/source/normalize_multiline_doc_attribute.rs new file mode 100644 index 000000000000..91d930912444 --- /dev/null +++ b/tests/source/normalize_multiline_doc_attribute.rs @@ -0,0 +1,7 @@ +// rustfmt-unstable: true +// rustfmt-normalize_doc_attributes: true + +#[doc = "This comment +is split +on multiple lines"] +fn foo() {} diff --git a/tests/target/normalize_multiline_doc_attribute.rs b/tests/target/normalize_multiline_doc_attribute.rs new file mode 100644 index 000000000000..3b30525cacef --- /dev/null +++ b/tests/target/normalize_multiline_doc_attribute.rs @@ -0,0 +1,7 @@ +// rustfmt-unstable: true +// rustfmt-normalize_doc_attributes: true + +///This comment +///is split +///on multiple lines +fn foo() {}