]> git.lizzy.rs Git - rust.git/blobdiff - src/comment.rs
fix: do not wrap reference-style doc links
[rust.git] / src / comment.rs
index 5b46001ff7df503c563c563ad98ce7aff8f96e08..830d2b50aad82f9f6958253f24ec7c75cf199e9d 100644 (file)
@@ -3,6 +3,8 @@
 use std::{self, borrow::Cow, iter};
 
 use itertools::{multipeek, MultiPeek};
+use lazy_static::lazy_static;
+use regex::Regex;
 use rustc_span::Span;
 
 use crate::config::Config;
 use crate::shape::{Indent, Shape};
 use crate::string::{rewrite_string, StringFormat};
 use crate::utils::{
-    count_newlines, first_line_width, last_line_width, trim_left_preserve_layout, unicode_str_width,
+    count_newlines, first_line_width, last_line_width, trim_left_preserve_layout,
+    trimmed_last_line_width, unicode_str_width,
 };
 use crate::{ErrorKind, FormattingError};
 
+lazy_static! {
+    /// A regex matching reference doc links.
+    ///
+    /// ```markdown
+    /// /// An [example].
+    /// ///
+    /// /// [example]: this::is::a::link
+    /// ```
+    static ref REFERENCE_LINK_URL: Regex = Regex::new(r"^\[.+\]\s?:").unwrap();
+}
+
 fn is_custom_comment(comment: &str) -> bool {
     if !comment.starts_with("//") {
         false
@@ -67,10 +81,7 @@ pub(crate) fn is_block_comment(&self) -> bool {
 
     /// Returns `true` if the commenting style is for documentation.
     pub(crate) fn is_doc_comment(&self) -> bool {
-        match *self {
-            CommentStyle::TripleSlash | CommentStyle::Doc => true,
-            _ => false,
-        }
+        matches!(*self, CommentStyle::TripleSlash | CommentStyle::Doc)
     }
 
     pub(crate) fn opener(&self) -> &'a str {
@@ -91,8 +102,9 @@ pub(crate) fn closer(&self) -> &'a str {
             | CommentStyle::TripleSlash
             | CommentStyle::Custom(..)
             | CommentStyle::Doc => "",
-            CommentStyle::DoubleBullet => " **/",
-            CommentStyle::SingleBullet | CommentStyle::Exclamation => " */",
+            CommentStyle::SingleBullet | CommentStyle::DoubleBullet | CommentStyle::Exclamation => {
+                " */"
+            }
         }
     }
 
@@ -101,8 +113,9 @@ pub(crate) fn line_start(&self) -> &'a str {
             CommentStyle::DoubleSlash => "// ",
             CommentStyle::TripleSlash => "/// ",
             CommentStyle::Doc => "//! ",
-            CommentStyle::SingleBullet | CommentStyle::Exclamation => " * ",
-            CommentStyle::DoubleBullet => " ** ",
+            CommentStyle::SingleBullet | CommentStyle::DoubleBullet | CommentStyle::Exclamation => {
+                " * "
+            }
             CommentStyle::Custom(opener) => opener,
         }
     }
@@ -172,11 +185,12 @@ pub(crate) fn combine_strs_with_missing_comments(
         String::with_capacity(prev_str.len() + next_str.len() + shape.indent.width() + 128);
     result.push_str(prev_str);
     let mut allow_one_line = !prev_str.contains('\n') && !next_str.contains('\n');
-    let first_sep = if prev_str.is_empty() || next_str.is_empty() {
-        ""
-    } else {
-        " "
-    };
+    let first_sep =
+        if prev_str.is_empty() || next_str.is_empty() || trimmed_last_line_width(prev_str) == 0 {
+            ""
+        } else {
+            " "
+        };
     let mut one_line_width =
         last_line_width(prev_str) + first_line_width(next_str) + first_sep.len();
 
@@ -185,7 +199,7 @@ pub(crate) fn combine_strs_with_missing_comments(
     let missing_comment = rewrite_missing_comment(span, shape, context)?;
 
     if missing_comment.is_empty() {
-        if allow_extend && prev_str.len() + first_sep.len() + next_str.len() <= shape.width {
+        if allow_extend && one_line_width <= shape.width {
             result.push_str(first_sep);
         } else if !prev_str.is_empty() {
             result.push_str(&indent.to_string_with_newline(config))
@@ -393,28 +407,26 @@ fn consume_same_line_comments(
     }
 }
 
-/// Attributes for code blocks in rustdoc.
-/// See https://doc.rust-lang.org/rustdoc/print.html#attributes
+/// Enum indicating if the code block contains rust based on attributes
 enum CodeBlockAttribute {
     Rust,
-    Ignore,
-    Text,
-    ShouldPanic,
-    NoRun,
-    CompileFail,
+    NotRust,
 }
 
 impl CodeBlockAttribute {
-    fn new(attribute: &str) -> CodeBlockAttribute {
-        match attribute {
-            "rust" | "" => CodeBlockAttribute::Rust,
-            "ignore" => CodeBlockAttribute::Ignore,
-            "text" => CodeBlockAttribute::Text,
-            "should_panic" => CodeBlockAttribute::ShouldPanic,
-            "no_run" => CodeBlockAttribute::NoRun,
-            "compile_fail" => CodeBlockAttribute::CompileFail,
-            _ => CodeBlockAttribute::Text,
+    /// Parse comma separated attributes list. Return rust only if all
+    /// attributes are valid rust attributes
+    /// See <https://doc.rust-lang.org/rustdoc/print.html#attributes>
+    fn new(attributes: &str) -> CodeBlockAttribute {
+        for attribute in attributes.split(',') {
+            match attribute.trim() {
+                "" | "rust" | "should_panic" | "no_run" | "edition2015" | "edition2018"
+                | "edition2021" => (),
+                "ignore" | "compile_fail" | "text" => return CodeBlockAttribute::NotRust,
+                _ => return CodeBlockAttribute::NotRust,
+            }
         }
+        CodeBlockAttribute::Rust
     }
 }
 
@@ -527,7 +539,6 @@ fn new(
             .checked_sub(closer.len() + opener.len())
             .unwrap_or(1);
         let indent_str = shape.indent.to_string_with_newline(config).to_string();
-        let fmt_indent = shape.indent + (opener.len() - line_start.len());
 
         let mut cr = CommentRewrite {
             result: String::with_capacity(orig.len() * 2),
@@ -538,14 +549,14 @@ fn new(
             comment_line_separator: format!("{}{}", indent_str, line_start),
             max_width,
             indent_str,
-            fmt_indent,
+            fmt_indent: shape.indent,
 
             fmt: StringFormat {
                 opener: "",
                 closer: "",
                 line_start,
                 line_end: "",
-                shape: Shape::legacy(max_width, fmt_indent),
+                shape: Shape::legacy(max_width, shape.indent),
                 trim_end: true,
                 config,
             },
@@ -565,7 +576,7 @@ fn join_block(s: &str, sep: &str) -> String {
             result.push_str(line);
             result.push_str(match iter.peek() {
                 Some(next_line) if next_line.is_empty() => sep.trim_end(),
-                Some(..) => &sep,
+                Some(..) => sep,
                 None => "",
             });
         }
@@ -624,7 +635,7 @@ fn handle_line(
         let is_last = i == count_newlines(orig);
 
         if let Some(ref mut ib) = self.item_block {
-            if ib.add_line(&line) {
+            if ib.add_line(line) {
                 return false;
             }
             self.is_prev_line_multi_line = false;
@@ -649,25 +660,21 @@ fn handle_line(
         } else if self.code_block_attr.is_some() {
             if line.starts_with("```") {
                 let code_block = match self.code_block_attr.as_ref().unwrap() {
-                    CodeBlockAttribute::Ignore | CodeBlockAttribute::Text => {
-                        trim_custom_comment_prefix(&self.code_block_buffer)
-                    }
-                    _ if self.code_block_buffer.is_empty() => String::new(),
-                    _ => {
+                    CodeBlockAttribute::Rust
+                        if self.fmt.config.format_code_in_doc_comments()
+                            && !self.code_block_buffer.is_empty() =>
+                    {
                         let mut config = self.fmt.config.clone();
                         config.set().wrap_comments(false);
-                        if config.format_code_in_doc_comments() {
-                            if let Some(s) =
-                                crate::format_code_block(&self.code_block_buffer, &config)
-                            {
-                                trim_custom_comment_prefix(&s.snippet)
-                            } else {
-                                trim_custom_comment_prefix(&self.code_block_buffer)
-                            }
+                        if let Some(s) =
+                            crate::format_code_block(&self.code_block_buffer, &config, false)
+                        {
+                            trim_custom_comment_prefix(&s.snippet)
                         } else {
                             trim_custom_comment_prefix(&self.code_block_buffer)
                         }
                     }
+                    _ => trim_custom_comment_prefix(&self.code_block_buffer),
                 };
                 if !code_block.is_empty() {
                     self.result.push_str(&self.comment_line_separator);
@@ -688,10 +695,10 @@ fn handle_line(
 
         self.code_block_attr = None;
         self.item_block = None;
-        if line.starts_with("```") {
-            self.code_block_attr = Some(CodeBlockAttribute::new(&line[3..]))
-        } else if self.fmt.config.wrap_comments() && ItemizedBlock::is_itemized_line(&line) {
-            let ib = ItemizedBlock::new(&line);
+        if let Some(stripped) = line.strip_prefix("```") {
+            self.code_block_attr = Some(CodeBlockAttribute::new(stripped))
+        } else if self.fmt.config.wrap_comments() && ItemizedBlock::is_itemized_line(line) {
+            let ib = ItemizedBlock::new(line);
             self.item_block = Some(ib);
             return false;
         }
@@ -848,7 +855,11 @@ fn trim_custom_comment_prefix(s: &str) -> String {
 /// Returns `true` if the given string MAY include URLs or alike.
 fn has_url(s: &str) -> bool {
     // This function may return false positive, but should get its job done in most cases.
-    s.contains("https://") || s.contains("http://") || s.contains("ftp://") || s.contains("file://")
+    s.contains("https://")
+        || s.contains("http://")
+        || s.contains("ftp://")
+        || s.contains("file://")
+        || REFERENCE_LINK_URL.is_match(s)
 }
 
 /// Given the span, rewrite the missing comment inside it if available.
@@ -947,8 +958,8 @@ fn left_trim_comment_line<'a>(line: &'a str, style: &CommentStyle<'_>) -> (&'a s
     {
         (&line[4..], true)
     } else if let CommentStyle::Custom(opener) = *style {
-        if line.starts_with(opener) {
-            (&line[opener.len()..], true)
+        if let Some(stripped) = line.strip_prefix(opener) {
+            (stripped, true)
         } else {
             (&line[opener.trim_end().len()..], false)
         }
@@ -967,8 +978,8 @@ fn left_trim_comment_line<'a>(line: &'a str, style: &CommentStyle<'_>) -> (&'a s
         || line.starts_with("**")
     {
         (&line[2..], line.chars().nth(1).unwrap() == ' ')
-    } else if line.starts_with('*') {
-        (&line[1..], false)
+    } else if let Some(stripped) = line.strip_prefix('*') {
+        (stripped, false)
     } else {
         (line, line.starts_with(' '))
     }
@@ -976,6 +987,7 @@ fn left_trim_comment_line<'a>(line: &'a str, style: &CommentStyle<'_>) -> (&'a s
 
 pub(crate) trait FindUncommented {
     fn find_uncommented(&self, pat: &str) -> Option<usize>;
+    fn find_last_uncommented(&self, pat: &str) -> Option<usize>;
 }
 
 impl FindUncommented for str {
@@ -1001,6 +1013,19 @@ fn find_uncommented(&self, pat: &str) -> Option<usize> {
             None => Some(self.len() - pat.len()),
         }
     }
+
+    fn find_last_uncommented(&self, pat: &str) -> Option<usize> {
+        if let Some(left) = self.find_uncommented(pat) {
+            let mut result = left;
+            // add 1 to use find_last_uncommented for &str after pat
+            while let Some(next) = self[(result + 1)..].find_last_uncommented(pat) {
+                result += next + 1;
+            }
+            Some(result)
+        } else {
+            None
+        }
+    }
 }
 
 // Returns the first byte position after the first comment. The given string
@@ -1296,6 +1321,9 @@ fn next(&mut self) -> Option<(FullCodeCharKind, T::Item)> {
                 char_kind = FullCodeCharKind::InStringCommented;
                 if chr == '"' {
                     CharClassesStatus::BlockComment(deepness)
+                } else if chr == '*' && self.base.peek().map(RichChar::get_char) == Some('/') {
+                    char_kind = FullCodeCharKind::InComment;
+                    CharClassesStatus::BlockCommentClosing(deepness - 1)
                 } else {
                     CharClassesStatus::StringInBlockComment(deepness)
                 }
@@ -1373,7 +1401,7 @@ fn next(&mut self) -> Option<Self::Item> {
             None => unreachable!(),
         };
 
-        while let Some((kind, c)) = self.base.next() {
+        for (kind, c) in self.base.by_ref() {
             // needed to set the kind of the ending character on the last line
             self.kind = kind;
             if c == '\n' {
@@ -1556,10 +1584,10 @@ pub(crate) fn recover_comment_removed(
         // We missed some comments. Warn and keep the original text.
         if context.config.error_on_unformatted() {
             context.report.append(
-                context.source_map.span_to_filename(span).into(),
+                context.parse_sess.span_to_filename(span),
                 vec![FormattingError::from_span(
                     span,
-                    &context.source_map,
+                    context.parse_sess,
                     ErrorKind::LostComment,
                 )],
             );
@@ -1664,8 +1692,8 @@ fn next(&mut self) -> Option<Self::Item> {
 fn remove_comment_header(comment: &str) -> &str {
     if comment.starts_with("///") || comment.starts_with("//!") {
         &comment[3..]
-    } else if comment.starts_with("//") {
-        &comment[2..]
+    } else if let Some(stripped) = comment.strip_prefix("//") {
+        stripped
     } else if (comment.starts_with("/**") && !comment.starts_with("/**/"))
         || comment.starts_with("/*!")
     {
@@ -1673,7 +1701,8 @@ fn remove_comment_header(comment: &str) -> &str {
     } else {
         assert!(
             comment.starts_with("/*"),
-            format!("string '{}' is not a comment", comment)
+            "string '{}' is not a comment",
+            comment
         );
         &comment[2..comment.len() - 2]
     }