]> git.lizzy.rs Git - rust.git/blobdiff - src/comment.rs
Cargo fmt and update a test
[rust.git] / src / comment.rs
index 32f46e195c13bc7dcc38ccc4604887414dc2a471..8b8112618b4a8c948ae616846cd74cdc5c2a57b0 100644 (file)
 
 use syntax::codemap::Span;
 
-use {Indent, Shape};
 use config::Config;
 use rewrite::RewriteContext;
+use shape::{Indent, Shape};
 use string::{rewrite_string, StringFormat};
-use utils::{first_line_width, last_line_width, wrap_str};
+use utils::{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 => " */",
         }
@@ -152,7 +152,7 @@ pub fn combine_strs_with_missing_comments(
         last_line_width(prev_str) + first_line_width(next_str) + first_sep.len();
 
     let indent_str = shape.indent.to_string(context.config);
-    let missing_comment = try_opt!(rewrite_missing_comment(span, shape, context));
+    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 {
@@ -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());
@@ -254,13 +248,7 @@ fn identify_comment(
         .collect::<Vec<_>>()
         .join("\n");
 
-    let first_group_str = try_opt!(rewrite_comment_inner(
-        &first_group,
-        block_style,
-        style,
-        shape,
-        config,
-    ));
+    let first_group_str = rewrite_comment_inner(&first_group, block_style, style, shape, config)?;
     if rest.is_empty() {
         Some(first_group_str)
     } else {
@@ -293,12 +281,13 @@ 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,
     };
@@ -316,33 +305,85 @@ 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 {
+            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;
         }
     }
 
@@ -355,6 +396,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(
@@ -380,7 +427,7 @@ pub fn recover_missing_comment_in_span(
     context: &RewriteContext,
     used_width: usize,
 ) -> Option<String> {
-    let missing_comment = try_opt!(rewrite_missing_comment(span, shape, context));
+    let missing_comment = rewrite_missing_comment(span, shape, context)?;
     if missing_comment.is_empty() {
         Some(String::new())
     } else {
@@ -464,7 +511,7 @@ fn find_uncommented(&self, pat: &str) -> Option<usize> {
                     return Some(i - pat.len());
                 }
                 Some(c) => match kind {
-                    FullCodeCharKind::Normal if b == c => {}
+                    FullCodeCharKind::Normal | FullCodeCharKind::InString if b == c => {}
                     _ => {
                         needle_iter = pat.chars();
                     }
@@ -487,7 +534,7 @@ fn find_uncommented(&self, pat: &str) -> Option<usize> {
 pub fn find_comment_end(s: &str) -> Option<usize> {
     let mut iter = CharClasses::new(s.char_indices());
     for (kind, (i, _c)) in &mut iter {
-        if kind == FullCodeCharKind::Normal {
+        if kind == FullCodeCharKind::Normal || kind == FullCodeCharKind::InString {
             return Some(i);
         }
     }
@@ -505,6 +552,35 @@ pub fn contains_comment(text: &str) -> bool {
     CharClasses::new(text.chars()).any(|(kind, _)| kind.is_comment())
 }
 
+/// Remove trailing spaces from the specified snippet. We do not remove spaces
+/// inside strings or comments.
+pub fn remove_trailing_white_spaces(text: &str) -> String {
+    let mut buffer = String::with_capacity(text.len());
+    let mut space_buffer = String::with_capacity(128);
+    for (char_kind, c) in CharClasses::new(text.chars()) {
+        match c {
+            '\n' => {
+                if char_kind == FullCodeCharKind::InString {
+                    buffer.push_str(&space_buffer);
+                }
+                space_buffer.clear();
+                buffer.push('\n');
+            }
+            _ if c.is_whitespace() => {
+                space_buffer.push(c);
+            }
+            _ => {
+                if !space_buffer.is_empty() {
+                    buffer.push_str(&space_buffer);
+                    space_buffer.clear();
+                }
+                buffer.push(c);
+            }
+        }
+    }
+    buffer
+}
+
 struct CharClasses<T>
 where
     T: Iterator,
@@ -568,15 +644,17 @@ enum FullCodeCharKind {
     InComment,
     /// Last character of a comment, '\n' for a line comment, '/' for a block comment.
     EndComment,
+    /// Inside a string.
+    InString,
 }
 
 impl FullCodeCharKind {
     fn is_comment(&self) -> bool {
         match *self {
-            FullCodeCharKind::Normal => false,
-            FullCodeCharKind::StartComment |
-            FullCodeCharKind::InComment |
-            FullCodeCharKind::EndComment => true,
+            FullCodeCharKind::StartComment
+            | FullCodeCharKind::InComment
+            | FullCodeCharKind::EndComment => true,
+            _ => false,
         }
     }
 
@@ -610,15 +688,25 @@ impl<T> Iterator for CharClasses<T>
     type Item = (FullCodeCharKind, T::Item);
 
     fn next(&mut self) -> Option<(FullCodeCharKind, T::Item)> {
-        let item = try_opt!(self.base.next());
+        let item = self.base.next()?;
         let chr = item.get_char();
+        let mut char_kind = FullCodeCharKind::Normal;
         self.status = match self.status {
             CharClassesStatus::LitString => match chr {
                 '"' => CharClassesStatus::Normal,
-                '\\' => CharClassesStatus::LitStringEscape,
-                _ => CharClassesStatus::LitString,
+                '\\' => {
+                    char_kind = FullCodeCharKind::InString;
+                    CharClassesStatus::LitStringEscape
+                }
+                _ => {
+                    char_kind = FullCodeCharKind::InString;
+                    CharClassesStatus::LitString
+                }
             },
-            CharClassesStatus::LitStringEscape => CharClassesStatus::LitString,
+            CharClassesStatus::LitStringEscape => {
+                char_kind = FullCodeCharKind::InString;
+                CharClassesStatus::LitString
+            }
             CharClassesStatus::LitChar => match chr {
                 '\\' => CharClassesStatus::LitCharEscape,
                 '\'' => CharClassesStatus::Normal,
@@ -626,7 +714,10 @@ fn next(&mut self) -> Option<(FullCodeCharKind, T::Item)> {
             },
             CharClassesStatus::LitCharEscape => CharClassesStatus::LitChar,
             CharClassesStatus::Normal => match chr {
-                '"' => CharClassesStatus::LitString,
+                '"' => {
+                    char_kind = FullCodeCharKind::InString;
+                    CharClassesStatus::LitString
+                }
                 '\'' => CharClassesStatus::LitChar,
                 '/' => match self.base.peek() {
                     Some(next) if next.get_char() == '*' => {
@@ -680,7 +771,7 @@ fn next(&mut self) -> Option<(FullCodeCharKind, T::Item)> {
                 }
             },
         };
-        Some((FullCodeCharKind::Normal, item))
+        Some((char_kind, item))
     }
 }
 
@@ -705,11 +796,14 @@ impl<'a> Iterator for UngroupedCommentCodeSlices<'a> {
     type Item = (CodeCharKind, usize, &'a str);
 
     fn next(&mut self) -> Option<Self::Item> {
-        let (kind, (start_idx, _)) = try_opt!(self.iter.next());
+        let (kind, (start_idx, _)) = self.iter.next()?;
         match kind {
-            FullCodeCharKind::Normal => {
+            FullCodeCharKind::Normal | FullCodeCharKind::InString => {
                 // Consume all the Normal code
-                while let Some(&(FullCodeCharKind::Normal, (_, _))) = self.iter.peek() {
+                while let Some(&(char_kind, _)) = self.iter.peek() {
+                    if char_kind.is_comment() {
+                        break;
+                    }
                     let _ = self.iter.next();
                 }
             }
@@ -823,13 +917,11 @@ pub fn recover_comment_removed(
     new: String,
     span: Span,
     context: &RewriteContext,
-    shape: Shape,
 ) -> Option<String> {
     let snippet = context.snippet(span);
-    if changed_comment_content(&snippet, &new) {
-        // We missed some comments
-        // Keep previous formatting if it satisfies the constrains
-        wrap_str(snippet, context.config.max_width(), shape)
+    if snippet != new && changed_comment_content(&snippet, &new) {
+        // We missed some comments. Keep the original text.
+        Some(snippet)
     } else {
         Some(new)
     }
@@ -888,14 +980,14 @@ impl<'a> Iterator for CommentReducer<'a> {
     type Item = char;
     fn next(&mut self) -> Option<Self::Item> {
         loop {
-            let mut c = try_opt!(self.iter.next());
+            let mut c = self.iter.next()?;
             if self.is_block && self.at_start_line {
                 while c.is_whitespace() {
-                    c = try_opt!(self.iter.next());
+                    c = self.iter.next()?;
                 }
                 // Ignore leading '*'
                 if c == '*' {
-                    c = try_opt!(self.iter.next());
+                    c = self.iter.next()?;
                 }
             } else if c == '\n' {
                 self.at_start_line = true;
@@ -930,7 +1022,7 @@ fn remove_comment_header(comment: &str) -> &str {
 mod test {
     use super::{contains_comment, rewrite_comment, CharClasses, CodeCharKind, CommentCodeSlices,
                 FindUncommented, FullCodeCharKind};
-    use {Indent, Shape};
+    use shape::{Indent, Shape};
 
     #[test]
     fn char_classes() {
@@ -1034,7 +1126,7 @@ fn format_comments() {
     fn uncommented(text: &str) -> String {
         CharClasses::new(text.chars())
             .filter_map(|(s, c)| match s {
-                FullCodeCharKind::Normal => Some(c),
+                FullCodeCharKind::Normal | FullCodeCharKind::InString => Some(c),
                 _ => None,
             })
             .collect()