X-Git-Url: https://git.lizzy.rs/?a=blobdiff_plain;f=src%2Fcomment.rs;h=8b8112618b4a8c948ae616846cd74cdc5c2a57b0;hb=16184d3e165018fa5b78f5f99cfc844e5af33bd6;hp=9591ffc4427f8010c0153816e4990fac9fd35024;hpb=93c556c6ba197ce136aa50a2d252e77a1421cba5;p=rust.git diff --git a/src/comment.rs b/src/comment.rs index 9591ffc4427..8b8112618b4 100644 --- a/src/comment.rs +++ b/src/comment.rs @@ -14,29 +14,207 @@ use syntax::codemap::Span; -use {Indent, Shape}; use config::Config; use rewrite::RewriteContext; -use string::{StringFormat, rewrite_string}; -use utils::wrap_str; +use shape::{Indent, Shape}; +use string::{rewrite_string, StringFormat}; +use utils::{first_line_width, last_line_width}; fn is_custom_comment(comment: &str) -> bool { if !comment.starts_with("//") { false + } else if let Some(c) = comment.chars().nth(2) { + !c.is_alphanumeric() && !c.is_whitespace() } else { - if let Some(c) = comment.chars().nth(2) { - !c.is_alphanumeric() && !c.is_whitespace() + false + } +} + +#[derive(Copy, Clone, PartialEq, Eq)] +pub enum CommentStyle<'a> { + DoubleSlash, + TripleSlash, + Doc, + SingleBullet, + DoubleBullet, + Exclamation, + Custom(&'a str), +} + +fn custom_opener(s: &str) -> &str { + s.lines().next().map_or("", |first_line| { + first_line + .find(' ') + .map_or(first_line, |space_index| &first_line[0..space_index + 1]) + }) +} + +impl<'a> CommentStyle<'a> { + pub fn opener(&self) -> &'a str { + match *self { + CommentStyle::DoubleSlash => "// ", + CommentStyle::TripleSlash => "/// ", + CommentStyle::Doc => "//! ", + CommentStyle::SingleBullet => "/* ", + CommentStyle::DoubleBullet => "/** ", + CommentStyle::Exclamation => "/*! ", + CommentStyle::Custom(opener) => opener, + } + } + + pub fn closer(&self) -> &'a str { + match *self { + CommentStyle::DoubleSlash + | CommentStyle::TripleSlash + | CommentStyle::Custom(..) + | CommentStyle::Doc => "", + CommentStyle::DoubleBullet => " **/", + CommentStyle::SingleBullet | CommentStyle::Exclamation => " */", + } + } + + pub fn line_start(&self) -> &'a str { + match *self { + CommentStyle::DoubleSlash => "// ", + CommentStyle::TripleSlash => "/// ", + CommentStyle::Doc => "//! ", + CommentStyle::SingleBullet | CommentStyle::Exclamation => " * ", + CommentStyle::DoubleBullet => " ** ", + CommentStyle::Custom(opener) => opener, + } + } + + pub fn to_str_tuplet(&self) -> (&'a str, &'a str, &'a str) { + (self.opener(), self.closer(), self.line_start()) + } + + pub fn line_with_same_comment_style(&self, line: &str, normalize_comments: bool) -> bool { + match *self { + CommentStyle::DoubleSlash | CommentStyle::TripleSlash | CommentStyle::Doc => { + line.trim_left().starts_with(self.line_start().trim_left()) + || comment_style(line, normalize_comments) == *self + } + CommentStyle::DoubleBullet | CommentStyle::SingleBullet | CommentStyle::Exclamation => { + line.trim_left().starts_with(self.closer().trim_left()) + || line.trim_left().starts_with(self.line_start().trim_left()) + || comment_style(line, normalize_comments) == *self + } + CommentStyle::Custom(opener) => line.trim_left().starts_with(opener.trim_right()), + } + } +} + +fn comment_style(orig: &str, normalize_comments: bool) -> CommentStyle { + if !normalize_comments { + if orig.starts_with("/**") && !orig.starts_with("/**/") { + CommentStyle::DoubleBullet + } else if orig.starts_with("/*!") { + CommentStyle::Exclamation + } else if orig.starts_with("/*") { + CommentStyle::SingleBullet + } else if orig.starts_with("///") && orig.chars().nth(3).map_or(true, |c| c != '/') { + CommentStyle::TripleSlash + } else if orig.starts_with("//!") { + CommentStyle::Doc + } else if is_custom_comment(orig) { + CommentStyle::Custom(custom_opener(orig)) } else { - false + CommentStyle::DoubleSlash } + } else if (orig.starts_with("///") && orig.chars().nth(3).map_or(true, |c| c != '/')) + || (orig.starts_with("/**") && !orig.starts_with("/**/")) + { + CommentStyle::TripleSlash + } else if orig.starts_with("//!") || orig.starts_with("/*!") { + CommentStyle::Doc + } else if is_custom_comment(orig) { + CommentStyle::Custom(custom_opener(orig)) + } else { + CommentStyle::DoubleSlash } } -pub fn rewrite_comment(orig: &str, - block_style: bool, - shape: Shape, - config: &Config) - -> Option { +pub fn combine_strs_with_missing_comments( + context: &RewriteContext, + prev_str: &str, + next_str: &str, + span: Span, + shape: Shape, + allow_extend: bool, +) -> Option { + 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 mut one_line_width = + 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 = 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 { + return Some(format!("{}{}{}", prev_str, first_sep, next_str)); + } else { + let sep = if prev_str.is_empty() { + String::new() + } else { + String::from("\n") + &indent_str + }; + return Some(format!("{}{}{}", prev_str, sep, next_str)); + } + } + + // 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 original + // layout whenever possible. + let original_snippet = context.snippet(span); + let prefer_same_line = if let Some(pos) = original_snippet.chars().position(|c| c == '/') { + !original_snippet[..pos].contains('\n') + } else { + !original_snippet.contains('\n') + }; + + one_line_width -= first_sep.len(); + let first_sep = if prev_str.is_empty() || missing_comment.is_empty() { + String::new() + } else { + let one_line_width = last_line_width(prev_str) + first_line_width(&missing_comment) + 1; + if prefer_same_line && one_line_width <= shape.width { + String::from(" ") + } else { + format!("\n{}", indent_str) + } + }; + let second_sep = if missing_comment.is_empty() || next_str.is_empty() { + String::new() + } else if missing_comment.starts_with("//") { + format!("\n{}", indent_str) + } else { + one_line_width += missing_comment.len() + first_sep.len() + 1; + allow_one_line &= !missing_comment.starts_with("//") && !missing_comment.contains('\n'); + if prefer_same_line && allow_one_line && one_line_width <= shape.width { + String::from(" ") + } else { + format!("\n{}", indent_str) + } + }; + Some(format!( + "{}{}{}{}{}", + prev_str, first_sep, missing_comment, second_sep, next_str, + )) +} + +pub fn rewrite_comment( + orig: &str, + block_style: bool, + shape: Shape, + config: &Config, +) -> Option { // If there are lines without a starting sigil, we won't format them correctly // so in that case we won't even re-align (if !config.normalize_comments()) and // we should stop now. @@ -47,39 +225,55 @@ pub fn rewrite_comment(orig: &str, if num_bare_lines > 0 && !config.normalize_comments() { return Some(orig.to_owned()); } - if !config.normalize_comments() && !config.wrap_comments() { return light_rewrite_comment(orig, shape.indent, config); } + identify_comment(orig, block_style, shape, config) +} + +fn identify_comment( + orig: &str, + block_style: bool, + shape: Shape, + config: &Config, +) -> Option { + let style = comment_style(orig, false); + let first_group = orig.lines() + .take_while(|l| style.line_with_same_comment_style(l, false)) + .collect::>() + .join("\n"); + let rest = orig.lines() + .skip(first_group.lines().count()) + .collect::>() + .join("\n"); + + let first_group_str = rewrite_comment_inner(&first_group, block_style, style, shape, config)?; + if rest.is_empty() { + Some(first_group_str) + } else { + identify_comment(&rest, block_style, shape, config).map(|rest_str| { + format!( + "{}\n{}{}", + first_group_str, + shape.indent.to_string(config), + rest_str + ) + }) + } +} + +fn rewrite_comment_inner( + orig: &str, + block_style: bool, + style: CommentStyle, + shape: Shape, + config: &Config, +) -> Option { let (opener, closer, line_start) = if block_style { - ("/* ", " */", " * ") - } else if !config.normalize_comments() { - if orig.starts_with("/**") && !orig.starts_with("/**/") { - ("/** ", " **/", " ** ") - } else if orig.starts_with("/*!") { - ("/*! ", " */", " * ") - } else if orig.starts_with("/*") { - ("/* ", " */", " * ") - } else if orig.starts_with("///") { - ("/// ", "", "/// ") - } else if orig.starts_with("//!") { - ("//! ", "", "//! ") - } else { - ("// ", "", "// ") - } - } else if orig.starts_with("///") || (orig.starts_with("/**") && !orig.starts_with("/**/")) { - ("/// ", "", "/// ") - } else if orig.starts_with("//!") || orig.starts_with("/*!") { - ("//! ", "", "//! ") - } else if is_custom_comment(orig) { - if orig.chars().nth(3) == Some(' ') { - (&orig[0..4], "", &orig[0..4]) - } else { - (&orig[0..3], "", &orig[0..3]) - } + CommentStyle::SingleBullet.to_str_tuplet() } else { - ("// ", "", "// ") + comment_style(orig, config.normalize_comments()).to_str_tuplet() }; let max_chars = shape @@ -87,12 +281,13 @@ pub fn rewrite_comment(orig: &str, .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, }; @@ -104,44 +299,96 @@ pub fn rewrite_comment(orig: &str, line = line.trim(); // Drop old closer. if i == line_breaks && line.ends_with("*/") && !line.starts_with("//") { - line = &line[..(line.len() - 2)].trim_right(); + line = line[..(line.len() - 2)].trim_right(); } line }) - .map(left_trim_comment_line) - .map(|line| if orig.starts_with("/*") && line_breaks == 0 { - line.trim_left() - } else { - line - }); + .map(|s| left_trim_comment_line(s, &style)) + .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 config.wrap_comments() && line.len() > max_chars { - let rewrite = rewrite_string(line, &fmt).unwrap_or(line.to_owned()); - result.push_str(&rewrite); + 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() > 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() { + 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; } } result.push_str(closer); - if result == opener { + if result == opener && result.ends_with(' ') { // Trailing space. result.pop(); } @@ -149,6 +396,56 @@ pub fn rewrite_comment(orig: &str, 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( + span: Span, + shape: Shape, + context: &RewriteContext, +) -> Option { + let missing_snippet = context.snippet(span); + let trimmed_snippet = missing_snippet.trim(); + if !trimmed_snippet.is_empty() { + rewrite_comment(trimmed_snippet, false, shape, context.config) + } else { + Some(String::new()) + } +} + +/// Recover the missing comments in the specified span, if available. +/// The layout of the comments will be preserved as long as it does not break the code +/// and its total width does not exceed the max width. +pub fn recover_missing_comment_in_span( + span: Span, + shape: Shape, + context: &RewriteContext, + used_width: usize, +) -> Option { + let missing_comment = rewrite_missing_comment(span, shape, context)?; + if missing_comment.is_empty() { + Some(String::new()) + } else { + let missing_snippet = context.snippet(span); + let pos = missing_snippet.chars().position(|c| c == '/').unwrap_or(0); + // 1 = ` ` + let total_width = missing_comment.len() + used_width + 1; + let force_new_line_before_comment = + missing_snippet[..pos].contains('\n') || total_width > context.config.max_width(); + let sep = if force_new_line_before_comment { + format!("\n{}", shape.indent.to_string(context.config)) + } else { + String::from(" ") + }; + Some(format!("{}{}", sep, missing_comment)) + } +} + /// Trims whitespace and aligns to indent, but otherwise does not change comments. fn light_rewrite_comment(orig: &str, offset: Indent, config: &Config) -> Option { let lines: Vec<&str> = orig.lines() @@ -158,7 +455,7 @@ fn light_rewrite_comment(orig: &str, offset: Indent, config: &Config) -> Option< // `*` in `/*`. let first_non_whitespace = l.find(|c| !char::is_whitespace(c)); if let Some(fnw) = first_non_whitespace { - if l.as_bytes()[fnw] == '*' as u8 && fnw > 0 { + if l.as_bytes()[fnw] == b'*' && fnw > 0 { &l[fnw - 1..] } else { &l[fnw..] @@ -173,23 +470,26 @@ fn light_rewrite_comment(orig: &str, offset: Indent, config: &Config) -> Option< /// Trims comment characters and possibly a single space from the left of a string. /// Does not trim all whitespace. -fn left_trim_comment_line(line: &str) -> &str { - if line.starts_with("//! ") || line.starts_with("/// ") || line.starts_with("/*! ") || - line.starts_with("/** ") { +fn left_trim_comment_line<'a>(line: &'a str, style: &CommentStyle) -> &'a str { + if line.starts_with("//! ") || line.starts_with("/// ") || line.starts_with("/*! ") + || line.starts_with("/** ") + { &line[4..] - } else if is_custom_comment(line) { - if line.len() > 3 && line.chars().nth(3) == Some(' ') { - &line[4..] + } else if let CommentStyle::Custom(opener) = *style { + if line.starts_with(opener) { + &line[opener.len()..] } else { - &line[3..] + &line[opener.trim_right().len()..] } - } else if line.starts_with("/* ") || line.starts_with("// ") || line.starts_with("//!") || - line.starts_with("///") || - line.starts_with("** ") || line.starts_with("/*!") || - (line.starts_with("/**") && !line.starts_with("/**/")) { + } else if line.starts_with("/* ") || line.starts_with("// ") || line.starts_with("//!") + || line.starts_with("///") || line.starts_with("** ") + || line.starts_with("/*!") + || (line.starts_with("/**") && !line.starts_with("/**/")) + { &line[3..] - } else if line.starts_with("/*") || line.starts_with("* ") || line.starts_with("//") || - line.starts_with("**") { + } else if line.starts_with("/*") || line.starts_with("* ") || line.starts_with("//") + || line.starts_with("**") + { &line[2..] } else if line.starts_with('*') { &line[1..] @@ -210,14 +510,12 @@ fn find_uncommented(&self, pat: &str) -> Option { None => { return Some(i - pat.len()); } - Some(c) => { - match kind { - FullCodeCharKind::Normal if b == c => {} - _ => { - needle_iter = pat.chars(); - } + Some(c) => match kind { + FullCodeCharKind::Normal | FullCodeCharKind::InString if b == c => {} + _ => { + needle_iter = pat.chars(); } - } + }, } } @@ -236,7 +534,7 @@ fn find_uncommented(&self, pat: &str) -> Option { pub fn find_comment_end(s: &str) -> Option { 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); } } @@ -254,9 +552,39 @@ 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 - where T: Iterator, - T::Item: RichChar +where + T: Iterator, + T::Item: RichChar, { base: iter::Peekable, status: CharClassesStatus, @@ -316,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, } } @@ -338,8 +668,9 @@ fn to_codecharkind(&self) -> CodeCharKind { } impl CharClasses - where T: Iterator, - T::Item: RichChar +where + T: Iterator, + T::Item: RichChar, { fn new(base: T) -> CharClasses { CharClasses { @@ -350,53 +681,59 @@ fn new(base: T) -> CharClasses { } impl Iterator for CharClasses - where T: Iterator, - T::Item: RichChar +where + T: Iterator, + T::Item: RichChar, { 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, + CharClassesStatus::LitString => match chr { + '"' => CharClassesStatus::Normal, + '\\' => { + char_kind = FullCodeCharKind::InString; + CharClassesStatus::LitStringEscape } - } - CharClassesStatus::LitStringEscape => CharClassesStatus::LitString, - CharClassesStatus::LitChar => { - match chr { - '\\' => CharClassesStatus::LitCharEscape, - '\'' => CharClassesStatus::Normal, - _ => CharClassesStatus::LitChar, + _ => { + char_kind = FullCodeCharKind::InString; + CharClassesStatus::LitString } + }, + CharClassesStatus::LitStringEscape => { + char_kind = FullCodeCharKind::InString; + CharClassesStatus::LitString } + CharClassesStatus::LitChar => match chr { + '\\' => CharClassesStatus::LitCharEscape, + '\'' => CharClassesStatus::Normal, + _ => CharClassesStatus::LitChar, + }, CharClassesStatus::LitCharEscape => CharClassesStatus::LitChar, - CharClassesStatus::Normal => { - match chr { - '"' => CharClassesStatus::LitString, - '\'' => CharClassesStatus::LitChar, - '/' => { - match self.base.peek() { - Some(next) if next.get_char() == '*' => { - self.status = CharClassesStatus::BlockCommentOpening(1); - return Some((FullCodeCharKind::StartComment, item)); - } - Some(next) if next.get_char() == '/' => { - self.status = CharClassesStatus::LineComment; - return Some((FullCodeCharKind::StartComment, item)); - } - _ => CharClassesStatus::Normal, - } + CharClassesStatus::Normal => match chr { + '"' => { + char_kind = FullCodeCharKind::InString; + CharClassesStatus::LitString + } + '\'' => CharClassesStatus::LitChar, + '/' => match self.base.peek() { + Some(next) if next.get_char() == '*' => { + self.status = CharClassesStatus::BlockCommentOpening(1); + return Some((FullCodeCharKind::StartComment, item)); + } + Some(next) if next.get_char() == '/' => { + self.status = CharClassesStatus::LineComment; + return Some((FullCodeCharKind::StartComment, item)); } _ => CharClassesStatus::Normal, - } - } + }, + _ => CharClassesStatus::Normal, + }, CharClassesStatus::BlockComment(deepness) => { - assert!(deepness != 0); + assert_ne!(deepness, 0); self.status = match self.base.peek() { Some(next) if next.get_char() == '/' && chr == '*' => { CharClassesStatus::BlockCommentClosing(deepness - 1) @@ -423,20 +760,18 @@ fn next(&mut self) -> Option<(FullCodeCharKind, T::Item)> { return Some((FullCodeCharKind::InComment, item)); } } - CharClassesStatus::LineComment => { - match chr { - '\n' => { - self.status = CharClassesStatus::Normal; - return Some((FullCodeCharKind::EndComment, item)); - } - _ => { - self.status = CharClassesStatus::LineComment; - return Some((FullCodeCharKind::InComment, item)); - } + CharClassesStatus::LineComment => match chr { + '\n' => { + self.status = CharClassesStatus::Normal; + return Some((FullCodeCharKind::EndComment, item)); } - } + _ => { + self.status = CharClassesStatus::LineComment; + return Some((FullCodeCharKind::InComment, item)); + } + }, }; - Some((FullCodeCharKind::Normal, item)) + Some((char_kind, item)) } } @@ -461,11 +796,14 @@ impl<'a> Iterator for UngroupedCommentCodeSlices<'a> { type Item = (CodeCharKind, usize, &'a str); fn next(&mut self) -> Option { - 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(); } } @@ -479,13 +817,15 @@ fn next(&mut self) -> Option { Some(&(_, (end_idx, _))) => &self.slice[start_idx..end_idx], None => &self.slice[start_idx..], }; - Some((if kind.is_comment() { - CodeCharKind::Comment - } else { - CodeCharKind::Normal - }, - start_idx, - slice)) + Some(( + if kind.is_comment() { + CodeCharKind::Comment + } else { + CodeCharKind::Normal + }, + start_idx, + slice, + )) } } @@ -525,9 +865,9 @@ fn next(&mut self) -> Option { let mut iter = CharClasses::new(subslice.char_indices()); for (kind, (i, c)) in &mut iter { - let is_comment_connector = self.last_slice_kind == CodeCharKind::Normal && - &subslice[..2] == "//" && - [' ', '\t'].contains(&c); + let is_comment_connector = self.last_slice_kind == CodeCharKind::Normal + && &subslice[..2] == "//" + && [' ', '\t'].contains(&c); if is_comment_connector && first_whitespace.is_none() { first_whitespace = Some(i); @@ -559,7 +899,11 @@ fn next(&mut self) -> Option { CodeCharKind::Comment => CodeCharKind::Normal, CodeCharKind::Normal => CodeCharKind::Comment, }; - let res = (kind, self.last_slice_end, &self.slice[self.last_slice_end..sub_slice_end]); + let res = ( + kind, + self.last_slice_end, + &self.slice[self.last_slice_end..sub_slice_end], + ); self.last_slice_end = sub_slice_end; self.last_slice_kind = kind; @@ -569,16 +913,15 @@ fn next(&mut self) -> Option { /// Checks is `new` didn't miss any comment from `span`, if it removed any, return previous text /// (if it fits in the width/offset, else return None), else return `new` -pub fn recover_comment_removed(new: String, - span: Span, - context: &RewriteContext, - shape: Shape) - -> Option { +pub fn recover_comment_removed( + new: String, + span: Span, + context: &RewriteContext, +) -> Option { 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) } @@ -599,12 +942,14 @@ fn changed_comment_content(orig: &str, new: &str) -> bool { .flat_map(|(_, _, s)| CommentReducer::new(s)) }; let res = code_comment_content(orig).ne(code_comment_content(new)); - debug!("comment::changed_comment_content: {}\norig: '{}'\nnew: '{}'\nraw_old: {}\nraw_new: {}", - res, - orig, - new, - code_comment_content(orig).collect::(), - code_comment_content(new).collect::()); + debug!( + "comment::changed_comment_content: {}\norig: '{}'\nnew: '{}'\nraw_old: {}\nraw_new: {}", + res, + orig, + new, + code_comment_content(orig).collect::(), + code_comment_content(new).collect::() + ); res } @@ -635,19 +980,17 @@ impl<'a> Iterator for CommentReducer<'a> { type Item = char; fn next(&mut self) -> Option { 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()); - } - } else { - if c == '\n' { - self.at_start_line = true; + c = self.iter.next()?; } + } else if c == '\n' { + self.at_start_line = true; } if !c.is_whitespace() { return Some(c); @@ -662,21 +1005,24 @@ fn remove_comment_header(comment: &str) -> &str { &comment[3..] } else if comment.starts_with("//") { &comment[2..] - } else if (comment.starts_with("/**") && !comment.starts_with("/**/")) || - comment.starts_with("/*!") { + } else if (comment.starts_with("/**") && !comment.starts_with("/**/")) + || comment.starts_with("/*!") + { &comment[3..comment.len() - 2] } else { - assert!(comment.starts_with("/*"), - format!("string '{}' is not a comment", comment)); + assert!( + comment.starts_with("/*"), + format!("string '{}' is not a comment", comment) + ); &comment[2..comment.len() - 2] } } #[cfg(test)] mod test { - use super::{CharClasses, CodeCharKind, FullCodeCharKind, contains_comment, rewrite_comment, - FindUncommented, CommentCodeSlices}; - use {Indent, Shape}; + use super::{contains_comment, rewrite_comment, CharClasses, CodeCharKind, CommentCodeSlices, + FindUncommented, FullCodeCharKind}; + use shape::{Indent, Shape}; #[test] fn char_classes() { @@ -695,8 +1041,10 @@ fn comment_code_slices() { let mut iter = CommentCodeSlices::new(input); assert_eq!((CodeCharKind::Normal, 0, "code(); "), iter.next().unwrap()); - assert_eq!((CodeCharKind::Comment, 8, "/* test */"), - iter.next().unwrap()); + assert_eq!( + (CodeCharKind::Comment, 8, "/* test */"), + iter.next().unwrap() + ); assert_eq!((CodeCharKind::Normal, 18, " 1 + 1"), iter.next().unwrap()); assert_eq!(None, iter.next()); } @@ -707,10 +1055,14 @@ fn comment_code_slices_two() { let mut iter = CommentCodeSlices::new(input); assert_eq!((CodeCharKind::Normal, 0, ""), iter.next().unwrap()); - assert_eq!((CodeCharKind::Comment, 0, "// comment\n"), - iter.next().unwrap()); - assert_eq!((CodeCharKind::Normal, 11, " test();"), - iter.next().unwrap()); + assert_eq!( + (CodeCharKind::Comment, 0, "// comment\n"), + iter.next().unwrap() + ); + assert_eq!( + (CodeCharKind::Normal, 11, " test();"), + iter.next().unwrap() + ); assert_eq!(None, iter.next()); } @@ -720,8 +1072,10 @@ fn comment_code_slices_three() { let mut iter = CommentCodeSlices::new(input); assert_eq!((CodeCharKind::Normal, 0, "1 "), iter.next().unwrap()); - assert_eq!((CodeCharKind::Comment, 2, "// comment\n // comment2\n"), - iter.next().unwrap()); + assert_eq!( + (CodeCharKind::Comment, 2, "// comment\n // comment2\n"), + iter.next().unwrap() + ); assert_eq!((CodeCharKind::Normal, 29, "\n"), iter.next().unwrap()); assert_eq!(None, iter.next()); } @@ -772,17 +1126,19 @@ fn format_comments() { fn uncommented(text: &str) -> String { CharClasses::new(text.chars()) .filter_map(|(s, c)| match s { - FullCodeCharKind::Normal => Some(c), - _ => None, - }) + FullCodeCharKind::Normal | FullCodeCharKind::InString => Some(c), + _ => None, + }) .collect() } #[test] fn test_uncommented() { assert_eq!(&uncommented("abc/*...*/"), "abc"); - assert_eq!(&uncommented("// .... /* \n../* /* *** / */ */a/* // */c\n"), - "..ac\n"); + assert_eq!( + &uncommented("// .... /* \n../* /* *** / */ */a/* // */c\n"), + "..ac\n" + ); assert_eq!(&uncommented("abc \" /* */\" qsdf"), "abc \" /* */\" qsdf"); } @@ -803,9 +1159,11 @@ fn check(haystack: &str, needle: &str, expected: Option) { check("/*/ */test", "test", Some(6)); check("//test\ntest", "test", Some(7)); check("/* comment only */", "whatever", None); - check("/* comment */ some text /* more commentary */ result", - "result", - Some(46)); + check( + "/* comment */ some text /* more commentary */ result", + "result", + Some(46), + ); check("sup // sup", "p", Some(2)); check("sup", "x", None); check(r#"π? /**/ π is nice!"#, r#"π is nice"#, Some(9));