]> git.lizzy.rs Git - rust.git/blobdiff - src/comment.rs
Cargo clippy
[rust.git] / src / comment.rs
index 5112c333dee25ac9f36e460de47e6979a7241a59..439a7f6a05f32c1cfea3d19c4f117f89fbb8c575 100644 (file)
@@ -18,7 +18,7 @@
 use rewrite::RewriteContext;
 use shape::{Indent, Shape};
 use string::{rewrite_string, StringFormat};
-use utils::{first_line_width, last_line_width};
+use utils::{count_newlines, first_line_width, last_line_width};
 
 fn is_custom_comment(comment: &str) -> bool {
     if !comment.starts_with("//") {
@@ -64,10 +64,10 @@ pub fn opener(&self) -> &'a str {
 
     pub fn closer(&self) -> &'a str {
         match *self {
-            CommentStyle::DoubleSlash |
-            CommentStyle::TripleSlash |
-            CommentStyle::Custom(..) |
-            CommentStyle::Doc => "",
+            CommentStyle::DoubleSlash
+            | CommentStyle::TripleSlash
+            | CommentStyle::Custom(..)
+            CommentStyle::Doc => "",
             CommentStyle::DoubleBullet => " **/",
             CommentStyle::SingleBullet | CommentStyle::Exclamation => " */",
         }
@@ -170,7 +170,7 @@ pub fn combine_strs_with_missing_comments(
     // We have a missing comment between the first expression and the second expression.
 
     // Peek the the original source code and find out whether there is a newline between the first
-    // expression and the second expression or the missing comment. We will preserve the orginal
+    // expression and the second expression or the missing comment. We will preserve the original
     // layout whenever possible.
     let original_snippet = context.snippet(span);
     let prefer_same_line = if let Some(pos) = original_snippet.chars().position(|c| c == '/') {
@@ -205,11 +205,7 @@ pub fn combine_strs_with_missing_comments(
     };
     Some(format!(
         "{}{}{}{}{}",
-        prev_str,
-        first_sep,
-        missing_comment,
-        second_sep,
-        next_str,
+        prev_str, first_sep, missing_comment, second_sep, next_str,
     ))
 }
 
@@ -224,9 +220,7 @@ pub fn rewrite_comment(
     // we should stop now.
     let num_bare_lines = orig.lines()
         .map(|line| line.trim())
-        .filter(|l| {
-            !(l.starts_with('*') || l.starts_with("//") || l.starts_with("/*"))
-        })
+        .filter(|l| !(l.starts_with('*') || l.starts_with("//") || l.starts_with("/*")))
         .count();
     if num_bare_lines > 0 && !config.normalize_comments() {
         return Some(orig.to_owned());
@@ -287,17 +281,18 @@ fn rewrite_comment_inner(
         .checked_sub(closer.len() + opener.len())
         .unwrap_or(1);
     let indent_str = shape.indent.to_string(config);
-    let fmt = StringFormat {
+    let fmt_indent = shape.indent + (opener.len() - line_start.len());
+    let mut fmt = StringFormat {
         opener: "",
         closer: "",
         line_start: line_start,
         line_end: "",
-        shape: Shape::legacy(max_chars, shape.indent + (opener.len() - line_start.len())),
+        shape: Shape::legacy(max_chars, fmt_indent),
         trim_end: true,
         config: config,
     };
 
-    let line_breaks = orig.trim_right().chars().filter(|&c| c == '\n').count();
+    let line_breaks = count_newlines(orig.trim_right());
     let lines = orig.lines()
         .enumerate()
         .map(|(i, mut line)| {
@@ -310,33 +305,89 @@ fn rewrite_comment_inner(
             line
         })
         .map(|s| left_trim_comment_line(s, &style))
-        .map(|line| if orig.starts_with("/*") && line_breaks == 0 {
-            line.trim_left()
-        } else {
-            line
+        .map(|line| {
+            if orig.starts_with("/*") && line_breaks == 0 {
+                line.trim_left()
+            } else {
+                line
+            }
         });
 
     let mut result = opener.to_owned();
+    let mut is_prev_line_multi_line = false;
+    let mut inside_code_block = false;
+    let comment_line_separator = format!("\n{}{}", indent_str, line_start);
     for line in lines {
         if result == opener {
             if line.is_empty() {
                 continue;
             }
+        } else if is_prev_line_multi_line && !line.is_empty() {
+            result.push(' ')
         } else {
-            result.push('\n');
-            result.push_str(&indent_str);
-            result.push_str(line_start);
+            result.push_str(&comment_line_separator);
+        }
+
+        if line.starts_with("```") {
+            inside_code_block = !inside_code_block;
+        }
+        if inside_code_block {
+            if line.is_empty() && result.ends_with(' ') {
+                result.pop();
+            } else {
+                result.push_str(line);
+            }
+            continue;
         }
 
-        if config.wrap_comments() && line.len() > max_chars {
-            let rewrite = rewrite_string(line, &fmt).unwrap_or_else(|| line.to_owned());
-            result.push_str(&rewrite);
+        if config.wrap_comments() && line.len() > fmt.shape.width && !has_url(line) {
+            match rewrite_string(line, &fmt, Some(max_chars)) {
+                Some(ref s) => {
+                    is_prev_line_multi_line = s.contains('\n');
+                    result.push_str(s);
+                }
+                None if is_prev_line_multi_line => {
+                    // We failed to put the current `line` next to the previous `line`.
+                    // Remove the trailing space, then start rewrite on the next line.
+                    result.pop();
+                    result.push_str(&comment_line_separator);
+                    fmt.shape = Shape::legacy(max_chars, fmt_indent);
+                    match rewrite_string(line, &fmt, Some(max_chars)) {
+                        Some(ref s) => {
+                            is_prev_line_multi_line = s.contains('\n');
+                            result.push_str(s);
+                        }
+                        None => {
+                            is_prev_line_multi_line = false;
+                            result.push_str(line);
+                        }
+                    }
+                }
+                None => {
+                    is_prev_line_multi_line = false;
+                    result.push_str(line);
+                }
+            }
+
+            fmt.shape = if is_prev_line_multi_line {
+                // 1 = " "
+                let offset = 1 + last_line_width(&result) - line_start.len();
+                Shape {
+                    width: max_chars.checked_sub(offset).unwrap_or(0),
+                    indent: fmt_indent,
+                    offset: fmt.shape.offset + offset,
+                }
+            } else {
+                Shape::legacy(max_chars, fmt_indent)
+            };
         } else {
             if line.is_empty() && result.ends_with(' ') {
                 // Remove space if this is an empty comment or a doc comment.
                 result.pop();
             }
             result.push_str(line);
+            fmt.shape = Shape::legacy(max_chars, fmt_indent);
+            is_prev_line_multi_line = false;
         }
     }
 
@@ -349,6 +400,12 @@ fn rewrite_comment_inner(
     Some(result)
 }
 
+/// 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://")
+}
+
 /// Given the span, rewrite the missing comment inside it if available.
 /// Note that the given span must only include comments (or leading/trailing whitespaces).
 pub fn rewrite_missing_comment(
@@ -528,7 +585,7 @@ pub fn remove_trailing_white_spaces(text: &str) -> String {
     buffer
 }
 
-struct CharClasses<T>
+pub struct CharClasses<T>
 where
     T: Iterator,
     T::Item: RichChar,
@@ -537,7 +594,7 @@ struct CharClasses<T>
     status: CharClassesStatus,
 }
 
-trait RichChar {
+pub trait RichChar {
     fn get_char(&self) -> char;
 }
 
@@ -553,6 +610,12 @@ fn get_char(&self) -> char {
     }
 }
 
+impl RichChar for (char, usize) {
+    fn get_char(&self) -> char {
+        self.0
+    }
+}
+
 #[derive(PartialEq, Eq, Debug, Clone, Copy)]
 enum CharClassesStatus {
     Normal,
@@ -582,7 +645,7 @@ pub enum CodeCharKind {
 /// describing opening and closing of comments for ease when chunking
 /// code from tagged characters
 #[derive(PartialEq, Eq, Debug, Clone, Copy)]
-enum FullCodeCharKind {
+pub enum FullCodeCharKind {
     Normal,
     /// The first character of a comment, there is only one for a comment (always '/')
     StartComment,
@@ -596,15 +659,19 @@ enum FullCodeCharKind {
 }
 
 impl FullCodeCharKind {
-    fn is_comment(&self) -> bool {
+    pub fn is_comment(&self) -> bool {
         match *self {
-            FullCodeCharKind::StartComment |
-            FullCodeCharKind::InComment |
-            FullCodeCharKind::EndComment => true,
+            FullCodeCharKind::StartComment
+            | FullCodeCharKind::InComment
+            FullCodeCharKind::EndComment => true,
             _ => false,
         }
     }
 
+    pub fn is_string(&self) -> bool {
+        *self == FullCodeCharKind::InString
+    }
+
     fn to_codecharkind(&self) -> CodeCharKind {
         if self.is_comment() {
             CodeCharKind::Comment
@@ -619,7 +686,7 @@ impl<T> CharClasses<T>
     T: Iterator,
     T::Item: RichChar,
 {
-    fn new(base: T) -> CharClasses<T> {
+    pub fn new(base: T) -> CharClasses<T> {
         CharClasses {
             base: base.peekable(),
             status: CharClassesStatus::Normal,
@@ -776,9 +843,6 @@ fn next(&mut self) -> Option<Self::Item> {
     }
 }
 
-
-
-
 /// Iterator over an alternating sequence of functional and commented parts of
 /// a string. The first item is always a, possibly zero length, subslice of
 /// functional text. Line style comments contain their ending newlines.
@@ -866,9 +930,9 @@ pub fn recover_comment_removed(
     context: &RewriteContext,
 ) -> Option<String> {
     let snippet = context.snippet(span);
-    if snippet != new && changed_comment_content(&snippet, &new) {
+    if snippet != new && changed_comment_content(snippet, &new) {
         // We missed some comments. Keep the original text.
-        Some(snippet)
+        Some(snippet.to_owned())
     } else {
         Some(new)
     }
@@ -900,7 +964,6 @@ fn changed_comment_content(orig: &str, new: &str) -> bool {
     res
 }
 
-
 /// Iterator over the 'payload' characters of a comment.
 /// It skips whitespace, comment start/end marks, and '*' at the beginning of lines.
 /// The comment must be one comment, ie not more than one start mark (no multiple line comments,
@@ -946,7 +1009,6 @@ fn next(&mut self) -> Option<Self::Item> {
     }
 }
 
-
 fn remove_comment_header(comment: &str) -> &str {
     if comment.starts_with("///") || comment.starts_with("//!") {
         &comment[3..]