From 1c0ab1b130caae726145a3b2a62bc1b6d5ab9f91 Mon Sep 17 00:00:00 2001 From: Stepan Koltsov Date: Thu, 11 Jan 2024 20:56:07 +0000 Subject: [PATCH] Ignore format_code_in_doc_comments if doc is in the middle of comment --- src/attr.rs | 69 ++++++++++++++++++- src/comment.rs | 37 ++++++++-- .../doc-attr-in-the-middle-of-doc-comment.rs | 21 ++++++ .../doc-attr-in-the-middle-of-doc-comment.rs | 21 ++++++ 4 files changed, 140 insertions(+), 8 deletions(-) create mode 100644 tests/source/doc-attr-in-the-middle-of-doc-comment.rs create mode 100644 tests/target/doc-attr-in-the-middle-of-doc-comment.rs diff --git a/src/attr.rs b/src/attr.rs index 4d83547d664..196125e16c8 100644 --- a/src/attr.rs +++ b/src/attr.rs @@ -212,11 +212,60 @@ where &attrs[..len] } +/// Attr is doc attr with doc. +/// +/// ```ignore +/// #[doc = "doc"] +/// ``` +fn is_doc_eq_attr(attr: &ast::Attribute) -> bool { + match &attr.kind { + ast::AttrKind::Normal(normal) if normal.item.path == sym::doc => match normal.item.args { + ast::AttrArgs::Eq(..) => true, + _ => false, + }, + _ => false, + } +} + +/// Is there a `#[doc = "doc"]` attribute in the middle of doc comments. +/// +/// If there's such attribute, we skip formatting, because the doc can point to an external file, +/// and we don't know how that file may break the formatting. +fn has_doc_eq_attr_in_the_middle_of_comments(attrs: &[ast::Attribute]) -> bool { + enum Stage { + Start, + SeenDocComment, + SeenDocAttr, + } + let mut stage = Stage::Start; + for attr in attrs { + match &stage { + Stage::Start => { + if attr.is_doc_comment() { + stage = Stage::SeenDocComment; + } + } + Stage::SeenDocComment => { + if is_doc_eq_attr(attr) { + stage = Stage::SeenDocAttr; + } + } + Stage::SeenDocAttr => { + if attr.is_doc_comment() { + return true; + } + } + } + } + false +} + /// Rewrite the any doc comments which come before any other attributes. fn rewrite_initial_doc_comments( context: &RewriteContext<'_>, attrs: &[ast::Attribute], shape: Shape, + has_doc_attr_in_the_middle_of_comments: bool, ) -> Option<(usize, Option)> { if attrs.is_empty() { return Some((0, None)); @@ -235,6 +284,7 @@ fn rewrite_initial_doc_comments( &snippet, shape.comment(context.config), context.config, + has_doc_attr_in_the_middle_of_comments, )?), )); } @@ -318,7 +368,12 @@ impl Rewrite for ast::Attribute { fn rewrite(&self, context: &RewriteContext<'_>, shape: Shape) -> Option { let snippet = context.snippet(self.span); if self.is_doc_comment() { - rewrite_doc_comment(snippet, shape.comment(context.config), context.config) + rewrite_doc_comment( + snippet, + shape.comment(context.config), + context.config, + false, + ) } else { let should_skip = self .ident() @@ -347,6 +402,7 @@ impl Rewrite for ast::Attribute { &doc_comment, shape.comment(context.config), context.config, + false, ); } } @@ -378,6 +434,9 @@ impl Rewrite for [ast::Attribute] { // or `#![rustfmt::skip::attributes(derive)]` let skip_derives = context.skip_context.attributes.skip("derive"); + let has_doc_attr_in_the_middle_of_comments = + has_doc_eq_attr_in_the_middle_of_comments(attrs); + // This is not just a simple map because we need to handle doc comments // (where we take as many doc comment attributes as possible) and possibly // merging derives into a single attribute. @@ -387,8 +446,12 @@ impl Rewrite for [ast::Attribute] { } // Handle doc comments. - let (doc_comment_len, doc_comment_str) = - rewrite_initial_doc_comments(context, attrs, shape)?; + let (doc_comment_len, doc_comment_str) = rewrite_initial_doc_comments( + context, + attrs, + shape, + has_doc_attr_in_the_middle_of_comments, + )?; if doc_comment_len > 0 { let doc_comment_str = doc_comment_str.expect("doc comments, but no result"); result.push_str(&doc_comment_str); diff --git a/src/comment.rs b/src/comment.rs index da17bc66b44..789e727e58d 100644 --- a/src/comment.rs +++ b/src/comment.rs @@ -248,8 +248,20 @@ pub(crate) fn combine_strs_with_missing_comments( Some(result) } -pub(crate) fn rewrite_doc_comment(orig: &str, shape: Shape, config: &Config) -> Option { - identify_comment(orig, false, shape, config, true) +pub(crate) fn rewrite_doc_comment( + orig: &str, + shape: Shape, + config: &Config, + has_doc_attr_in_the_middle_of_comments: bool, +) -> Option { + identify_comment( + orig, + false, + shape, + config, + true, + has_doc_attr_in_the_middle_of_comments, + ) } pub(crate) fn rewrite_comment( @@ -258,7 +270,7 @@ pub(crate) fn rewrite_comment( shape: Shape, config: &Config, ) -> Option { - identify_comment(orig, block_style, shape, config, false) + identify_comment(orig, block_style, shape, config, false, false) } fn identify_comment( @@ -267,6 +279,7 @@ fn identify_comment( shape: Shape, config: &Config, is_doc_comment: bool, + has_doc_attr_in_the_middle_of_comments: bool, ) -> Option { let style = comment_style(orig, false); @@ -365,7 +378,9 @@ fn identify_comment( && !( // `format_code_in_doc_comments` should only take effect on doc comments, // so we only consider it when this comment block is a doc comment block. - is_doc_comment && config.format_code_in_doc_comments() + is_doc_comment + && config.format_code_in_doc_comments() + && !has_doc_attr_in_the_middle_of_comments ) { light_rewrite_comment(first_group, shape.indent, config, is_doc_comment) @@ -377,6 +392,7 @@ fn identify_comment( shape, config, is_doc_comment || style.is_doc_comment(), + has_doc_attr_in_the_middle_of_comments, )? }; if rest.is_empty() { @@ -388,6 +404,7 @@ fn identify_comment( shape, config, is_doc_comment, + has_doc_attr_in_the_middle_of_comments, ) .map(|rest_str| { format!( @@ -725,6 +742,7 @@ impl<'a> CommentRewrite<'a> { line: &'a str, has_leading_whitespace: bool, is_doc_comment: bool, + has_doc_attr_in_the_middle_of_comments: bool, ) -> bool { let num_newlines = count_newlines(orig); let is_last = i == num_newlines; @@ -767,6 +785,7 @@ impl<'a> CommentRewrite<'a> { let code_block = match self.code_block_attr.as_ref().unwrap() { CodeBlockAttribute::Rust if self.fmt.config.format_code_in_doc_comments() + && !has_doc_attr_in_the_middle_of_comments && !self.code_block_buffer.trim().is_empty() => { let mut config = self.fmt.config.clone(); @@ -912,6 +931,7 @@ fn rewrite_comment_inner( shape: Shape, config: &Config, is_doc_comment: bool, + has_doc_attr_in_the_middle_of_comments: bool, ) -> Option { let mut rewriter = CommentRewrite::new(orig, block_style, shape, config); @@ -941,7 +961,14 @@ fn rewrite_comment_inner( }); for (i, (line, has_leading_whitespace)) in lines.enumerate() { - if rewriter.handle_line(orig, i, line, has_leading_whitespace, is_doc_comment) { + if rewriter.handle_line( + orig, + i, + line, + has_leading_whitespace, + is_doc_comment, + has_doc_attr_in_the_middle_of_comments, + ) { break; } } diff --git a/tests/source/doc-attr-in-the-middle-of-doc-comment.rs b/tests/source/doc-attr-in-the-middle-of-doc-comment.rs new file mode 100644 index 00000000000..1f0baa41dba --- /dev/null +++ b/tests/source/doc-attr-in-the-middle-of-doc-comment.rs @@ -0,0 +1,21 @@ +// rustfmt-format_code_in_doc_comments: true + +//!
+//! Example 1 +//! +//! ``` +#![doc = "test()"] +//! ``` +//! +//!
+//! +//!
+//! Example 2 +//! +//! ``` +//! example(2); +//! ``` +//! +//!
+ +struct FooBar; diff --git a/tests/target/doc-attr-in-the-middle-of-doc-comment.rs b/tests/target/doc-attr-in-the-middle-of-doc-comment.rs new file mode 100644 index 00000000000..1f0baa41dba --- /dev/null +++ b/tests/target/doc-attr-in-the-middle-of-doc-comment.rs @@ -0,0 +1,21 @@ +// rustfmt-format_code_in_doc_comments: true + +//!
+//! Example 1 +//! +//! ``` +#![doc = "test()"] +//! ``` +//! +//!
+//! +//!
+//! Example 2 +//! +//! ``` +//! example(2); +//! ``` +//! +//!
+ +struct FooBar;