use rewrite::RewriteContext;
use shape::{Indent, Shape};
use string::{rewrite_string, StringFormat};
-use utils::{count_newlines, first_line_width, last_line_width};
+use utils::{count_newlines, first_line_width, last_line_width, trim_left_preserve_layout};
use {ErrorKind, FormattingError};
fn is_custom_comment(comment: &str) -> bool {
let (first_group, rest) = orig.split_at(first_group_ending);
let rewritten_first_group =
if !config.normalize_comments() && has_bare_lines && style.is_block_comment() {
- light_rewrite_block_comment_with_bare_lines(first_group, shape, config)?
+ trim_left_preserve_layout(first_group, shape.indent, config)?
} else if !config.normalize_comments()
&& !config.wrap_comments()
&& !config.format_doc_comments()
{
- light_rewrite_comment(first_group, shape.indent, config, is_doc_comment)?
+ light_rewrite_comment(first_group, shape.indent, config, is_doc_comment)
} else {
rewrite_comment_inner(
first_group,
}
}
-/// Trims a minimum of leading whitespaces so that the content layout is kept and aligns to indent.
-fn light_rewrite_block_comment_with_bare_lines(
- orig: &str,
- shape: Shape,
- config: &Config,
-) -> Option<String> {
- let prefix_whitespace_min = orig
- .lines()
- // skip the line with the starting sigil since the leading whitespace is removed
- // otherwise, the minimum would always be zero
- .skip(1)
- .filter(|line| !line.is_empty())
- .map(|line| {
- let mut width = 0;
- for c in line.chars() {
- match c {
- ' ' => width += 1,
- '\t' => width += config.tab_spaces(),
- _ => break,
- }
- }
- width
- })
- .min()?;
-
- let indent_str = shape.indent.to_string(config);
- let mut lines = orig.lines();
- let first_line = lines.next()?;
- let rest = lines
- .map(|line| {
- if line.is_empty() {
- line
- } else {
- &line[prefix_whitespace_min..]
- }
- })
- .collect::<Vec<&str>>()
- .join(&format!("\n{}", indent_str));
- Some(format!("{}\n{}{}", first_line, indent_str, rest))
-}
-
/// Attributes for code blocks in rustdoc.
/// See https://doc.rust-lang.org/rustdoc/print.html#attributes
enum CodeBlockAttribute {
/// Block that is formatted as an item.
///
/// An item starts with either a star `*` or a dash `-`. Different level of indentation are
-/// handled.
+/// handled by shrinking the shape accordingly.
struct ItemizedBlock {
/// the number of whitespaces up to the item sigil
indent: usize,
}
}
-fn rewrite_comment_inner(
- orig: &str,
- block_style: bool,
- style: CommentStyle,
- shape: Shape,
- config: &Config,
- is_doc_comment: bool,
-) -> Option<String> {
- let (opener, closer, line_start) = if block_style {
- CommentStyle::SingleBullet.to_str_tuplet()
- } else {
- comment_style(orig, config.normalize_comments()).to_str_tuplet()
- };
+struct CommentRewrite<'a> {
+ result: String,
+ code_block_buffer: String,
+ is_prev_line_multi_line: bool,
+ code_block_attr: Option<CodeBlockAttribute>,
+ item_block_buffer: String,
+ item_block: Option<ItemizedBlock>,
+ comment_line_separator: String,
+ indent_str: String,
+ max_chars: usize,
+ fmt_indent: Indent,
+ fmt: StringFormat<'a>,
- let max_chars = shape
- .width
- .checked_sub(closer.len() + opener.len())
- .unwrap_or(1);
- let indent_str = shape.indent.to_string_with_newline(config);
- let fmt_indent = shape.indent + (opener.len() - line_start.len());
- let mut fmt = StringFormat {
- opener: "",
- closer: "",
- line_start,
- line_end: "",
- shape: Shape::legacy(max_chars, fmt_indent),
- trim_end: true,
- config,
- };
+ opener: String,
+ closer: String,
+ line_start: String,
+}
- let line_breaks = count_newlines(orig.trim_right());
- let lines = orig
- .lines()
- .enumerate()
- .map(|(i, mut line)| {
- line = trim_right_unless_two_whitespaces(line.trim_left(), is_doc_comment);
- // Drop old closer.
- if i == line_breaks && line.ends_with("*/") && !line.starts_with("//") {
- line = line[..(line.len() - 2)].trim_right();
- }
+impl<'a> CommentRewrite<'a> {
+ fn new(
+ orig: &'a str,
+ block_style: bool,
+ shape: Shape,
+ config: &'a Config,
+ ) -> CommentRewrite<'a> {
+ let (opener, closer, line_start) = if block_style {
+ CommentStyle::SingleBullet.to_str_tuplet()
+ } else {
+ comment_style(orig, config.normalize_comments()).to_str_tuplet()
+ };
- line
- })
- .map(|s| left_trim_comment_line(s, &style))
- .map(|(line, has_leading_whitespace)| {
- if orig.starts_with("/*") && line_breaks == 0 {
- (
- line.trim_left(),
- has_leading_whitespace || config.normalize_comments(),
- )
- } else {
- (line, has_leading_whitespace || config.normalize_comments())
- }
- });
+ let max_chars = shape
+ .width
+ .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),
+ code_block_buffer: String::with_capacity(128),
+ is_prev_line_multi_line: false,
+ code_block_attr: None,
+ item_block_buffer: String::with_capacity(128),
+ item_block: None,
+ comment_line_separator: format!("{}{}", indent_str, line_start),
+ max_chars,
+ indent_str,
+ fmt_indent,
+
+ fmt: StringFormat {
+ opener: "",
+ closer: "",
+ line_start,
+ line_end: "",
+ shape: Shape::legacy(max_chars, fmt_indent),
+ trim_end: true,
+ config,
+ },
- let mut result = String::with_capacity(orig.len() * 2);
- result.push_str(opener);
- let mut code_block_buffer = String::with_capacity(128);
- let mut is_prev_line_multi_line = false;
- let mut code_block_attr = None;
- let mut item_block_buffer = String::with_capacity(128);
- let mut item_block: Option<ItemizedBlock> = None;
- let comment_line_separator = format!("{}{}", indent_str, line_start);
- let join_block = |s: &str, sep: &str| {
+ opener: opener.to_owned(),
+ closer: closer.to_owned(),
+ line_start: line_start.to_owned(),
+ };
+ cr.result.push_str(opener);
+ cr
+ }
+
+ fn join_block(s: &str, sep: &str) -> String {
let mut result = String::with_capacity(s.len() + 128);
let mut iter = s.lines().peekable();
while let Some(line) = iter.next() {
});
}
result
- };
+ }
- for (i, (line, has_leading_whitespace)) in lines.enumerate() {
+ fn finish(mut self) -> String {
+ if !self.code_block_buffer.is_empty() {
+ // There is a code block that is not properly enclosed by backticks.
+ // We will leave them untouched.
+ self.result.push_str(&self.comment_line_separator);
+ self.result.push_str(&Self::join_block(
+ &trim_custom_comment_prefix(&self.code_block_buffer),
+ &self.comment_line_separator,
+ ));
+ }
+
+ if !self.item_block_buffer.is_empty() {
+ // the last few lines are part of an itemized block
+ self.fmt.shape = Shape::legacy(self.max_chars, self.fmt_indent);
+ let mut ib = None;
+ ::std::mem::swap(&mut ib, &mut self.item_block);
+ let ib = ib.unwrap();
+ let item_fmt = ib.create_string_format(&self.fmt);
+ self.result.push_str(&self.comment_line_separator);
+ self.result.push_str(&ib.opener);
+ match rewrite_string(
+ &self.item_block_buffer.replace("\n", " "),
+ &item_fmt,
+ self.max_chars.saturating_sub(ib.indent),
+ ) {
+ Some(s) => self.result.push_str(&Self::join_block(
+ &s,
+ &format!("{}{}", &self.comment_line_separator, ib.line_start),
+ )),
+ None => self.result.push_str(&Self::join_block(
+ &self.item_block_buffer,
+ &self.comment_line_separator,
+ )),
+ };
+ }
+
+ self.result.push_str(&self.closer);
+ if self.result.ends_with(&self.opener) && self.opener.ends_with(' ') {
+ // Trailing space.
+ self.result.pop();
+ }
+
+ self.result
+ }
+
+ fn handle_line(
+ &mut self,
+ orig: &'a str,
+ i: usize,
+ line: &'a str,
+ has_leading_whitespace: bool,
+ ) -> bool {
let is_last = i == count_newlines(orig);
- if let Some(ref ib) = item_block {
+ if let Some(ref ib) = self.item_block {
if ib.in_block(&line) {
- item_block_buffer.push_str(&line);
- item_block_buffer.push('\n');
- continue;
+ self.item_block_buffer.push_str(line.trim_start());
+ self.item_block_buffer.push('\n');
+ return false;
}
- is_prev_line_multi_line = false;
- fmt.shape = Shape::legacy(max_chars, fmt_indent);
- let item_fmt = ib.create_string_format(&fmt);
- result.push_str(&comment_line_separator);
- result.push_str(&ib.opener);
+ self.is_prev_line_multi_line = false;
+ self.fmt.shape = Shape::legacy(self.max_chars, self.fmt_indent);
+ let item_fmt = ib.create_string_format(&self.fmt);
+ self.result.push_str(&self.comment_line_separator);
+ self.result.push_str(&ib.opener);
match rewrite_string(
- &item_block_buffer.replace("\n", " "),
+ &self.item_block_buffer.replace("\n", " "),
&item_fmt,
- max_chars.saturating_sub(ib.indent),
+ self.max_chars.saturating_sub(ib.indent),
) {
- Some(s) => result.push_str(&join_block(
+ Some(s) => self.result.push_str(&Self::join_block(
&s,
- &format!("{}{}", &comment_line_separator, ib.line_start),
+ &format!("{}{}", &self.comment_line_separator, ib.line_start),
+ )),
+ None => self.result.push_str(&Self::join_block(
+ &self.item_block_buffer,
+ &self.comment_line_separator,
)),
- None => result.push_str(&join_block(&item_block_buffer, &comment_line_separator)),
};
- item_block_buffer.clear();
- } else if let Some(ref attr) = code_block_attr {
+ self.item_block_buffer.clear();
+ } else if self.code_block_attr.is_some() {
if line.starts_with("```") {
- let code_block = match attr {
+ let code_block = match self.code_block_attr.as_ref().unwrap() {
CodeBlockAttribute::Ignore | CodeBlockAttribute::Text => {
- trim_custom_comment_prefix(&code_block_buffer)
+ trim_custom_comment_prefix(&self.code_block_buffer)
}
- _ if code_block_buffer.is_empty() => String::new(),
+ _ if self.code_block_buffer.is_empty() => String::new(),
_ => {
- let mut config = config.clone();
+ let mut config = self.fmt.config.clone();
config.set().format_doc_comments(false);
- match ::format_code_block(&code_block_buffer, &config) {
- Some(ref s) => trim_custom_comment_prefix(s),
- None => trim_custom_comment_prefix(&code_block_buffer),
+ match ::format_code_block(&self.code_block_buffer, &config) {
+ Some(ref s) => trim_custom_comment_prefix(&s.snippet),
+ None => trim_custom_comment_prefix(&self.code_block_buffer),
}
}
};
if !code_block.is_empty() {
- result.push_str(&comment_line_separator);
- result.push_str(&join_block(&code_block, &comment_line_separator));
+ self.result.push_str(&self.comment_line_separator);
+ self.result
+ .push_str(&Self::join_block(&code_block, &self.comment_line_separator));
}
- code_block_buffer.clear();
- result.push_str(&comment_line_separator);
- result.push_str(line);
- code_block_attr = None;
+ self.code_block_buffer.clear();
+ self.result.push_str(&self.comment_line_separator);
+ self.result.push_str(line);
+ self.code_block_attr = None;
} else {
- code_block_buffer.push_str(&hide_sharp_behind_comment(line));
- code_block_buffer.push('\n');
+ self.code_block_buffer
+ .push_str(&hide_sharp_behind_comment(line));
+ self.code_block_buffer.push('\n');
}
- continue;
+ return false;
}
- code_block_attr = None;
- item_block = None;
+ self.code_block_attr = None;
+ self.item_block = None;
if line.starts_with("```") {
- code_block_attr = Some(CodeBlockAttribute::new(&line[3..]))
- } else if config.wrap_comments() && ItemizedBlock::is_itemized_line(&line) {
+ 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);
- item_block_buffer.push_str(&line[ib.indent..]);
- item_block_buffer.push('\n');
- item_block = Some(ib);
- continue;
+ self.item_block_buffer.push_str(&line[ib.indent..]);
+ self.item_block_buffer.push('\n');
+ self.item_block = Some(ib);
+ return false;
}
- if result == opener {
- let force_leading_whitespace = opener == "/* " && count_newlines(orig) == 0;
- if !has_leading_whitespace && !force_leading_whitespace && result.ends_with(' ') {
- result.pop();
+ if self.result == self.opener {
+ let force_leading_whitespace = &self.opener == "/* " && count_newlines(orig) == 0;
+ if !has_leading_whitespace && !force_leading_whitespace && self.result.ends_with(' ') {
+ self.result.pop();
}
if line.is_empty() {
- continue;
+ return false;
}
- } else if is_prev_line_multi_line && !line.is_empty() {
- result.push(' ')
+ } else if self.is_prev_line_multi_line && !line.is_empty() {
+ self.result.push(' ')
} else if is_last && line.is_empty() {
// trailing blank lines are unwanted
- if !closer.is_empty() {
- result.push_str(&indent_str);
+ if !self.closer.is_empty() {
+ self.result.push_str(&self.indent_str);
}
- break;
+ return true;
} else {
- result.push_str(&comment_line_separator);
- if !has_leading_whitespace && result.ends_with(' ') {
- result.pop();
+ self.result.push_str(&self.comment_line_separator);
+ if !has_leading_whitespace && self.result.ends_with(' ') {
+ self.result.pop();
}
}
- if config.wrap_comments() && line.len() > fmt.shape.width && !has_url(line) {
- match rewrite_string(line, &fmt, max_chars) {
+ if self.fmt.config.wrap_comments() && line.len() > self.fmt.shape.width && !has_url(line) {
+ match rewrite_string(line, &self.fmt, self.max_chars) {
Some(ref s) => {
- is_prev_line_multi_line = s.contains('\n');
- result.push_str(s);
+ self.is_prev_line_multi_line = s.contains('\n');
+ self.result.push_str(s);
}
- None if is_prev_line_multi_line => {
+ None if self.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, max_chars) {
+ self.result.pop();
+ self.result.push_str(&self.comment_line_separator);
+ self.fmt.shape = Shape::legacy(self.max_chars, self.fmt_indent);
+ match rewrite_string(line, &self.fmt, self.max_chars) {
Some(ref s) => {
- is_prev_line_multi_line = s.contains('\n');
- result.push_str(s);
+ self.is_prev_line_multi_line = s.contains('\n');
+ self.result.push_str(s);
}
None => {
- is_prev_line_multi_line = false;
- result.push_str(line);
+ self.is_prev_line_multi_line = false;
+ self.result.push_str(line);
}
}
}
None => {
- is_prev_line_multi_line = false;
- result.push_str(line);
+ self.is_prev_line_multi_line = false;
+ self.result.push_str(line);
}
}
- fmt.shape = if is_prev_line_multi_line {
+ self.fmt.shape = if self.is_prev_line_multi_line {
// 1 = " "
- let offset = 1 + last_line_width(&result) - line_start.len();
+ let offset = 1 + last_line_width(&self.result) - self.line_start.len();
Shape {
- width: max_chars.saturating_sub(offset),
- indent: fmt_indent,
- offset: fmt.shape.offset + offset,
+ width: self.max_chars.saturating_sub(offset),
+ indent: self.fmt_indent,
+ offset: self.fmt.shape.offset + offset,
}
} else {
- Shape::legacy(max_chars, fmt_indent)
+ Shape::legacy(self.max_chars, self.fmt_indent)
};
} else {
- if line.is_empty() && result.ends_with(' ') && !is_last {
+ if line.is_empty() && self.result.ends_with(' ') && !is_last {
// Remove space if this is an empty comment or a doc comment.
- result.pop();
+ self.result.pop();
}
- result.push_str(line);
- fmt.shape = Shape::legacy(max_chars, fmt_indent);
- is_prev_line_multi_line = false;
+ self.result.push_str(line);
+ self.fmt.shape = Shape::legacy(self.max_chars, self.fmt_indent);
+ self.is_prev_line_multi_line = false;
}
+
+ false
}
- if !code_block_buffer.is_empty() {
- // There is a code block that is not properly enclosed by backticks.
- // We will leave them untouched.
- result.push_str(&comment_line_separator);
- result.push_str(&join_block(
- &trim_custom_comment_prefix(&code_block_buffer),
- &comment_line_separator,
- ));
- }
- if !item_block_buffer.is_empty() {
- // the last few lines are part of an itemized block
- let ib = item_block.unwrap();
- fmt.shape = Shape::legacy(max_chars, fmt_indent);
- let item_fmt = ib.create_string_format(&fmt);
- result.push_str(&comment_line_separator);
- result.push_str(&ib.opener);
- match rewrite_string(
- &item_block_buffer.replace("\n", " "),
- &item_fmt,
- max_chars.saturating_sub(ib.indent),
- ) {
- Some(s) => result.push_str(&join_block(
- &s,
- &format!("{}{}", &comment_line_separator, ib.line_start),
- )),
- None => result.push_str(&join_block(&item_block_buffer, &comment_line_separator)),
- };
- }
+}
+
+fn rewrite_comment_inner(
+ orig: &str,
+ block_style: bool,
+ style: CommentStyle,
+ shape: Shape,
+ config: &Config,
+ is_doc_comment: bool,
+) -> Option<String> {
+ let mut rewriter = CommentRewrite::new(orig, block_style, shape, config);
+
+ let line_breaks = count_newlines(orig.trim_right());
+ let lines = orig
+ .lines()
+ .enumerate()
+ .map(|(i, mut line)| {
+ line = trim_right_unless_two_whitespaces(line.trim_left(), is_doc_comment);
+ // Drop old closer.
+ if i == line_breaks && line.ends_with("*/") && !line.starts_with("//") {
+ line = line[..(line.len() - 2)].trim_right();
+ }
+
+ line
+ })
+ .map(|s| left_trim_comment_line(s, &style))
+ .map(|(line, has_leading_whitespace)| {
+ if orig.starts_with("/*") && line_breaks == 0 {
+ (
+ line.trim_left(),
+ has_leading_whitespace || config.normalize_comments(),
+ )
+ } else {
+ (line, has_leading_whitespace || config.normalize_comments())
+ }
+ });
- result.push_str(closer);
- if result.ends_with(opener) && opener.ends_with(' ') {
- // Trailing space.
- result.pop();
+ for (i, (line, has_leading_whitespace)) in lines.enumerate() {
+ if rewriter.handle_line(orig, i, line, has_leading_whitespace) {
+ break;
+ }
}
- Some(result)
+ Some(rewriter.finish())
}
const RUSTFMT_CUSTOM_COMMENT_PREFIX: &str = "//#### ";
let left_trimmed = line.trim_left();
if left_trimmed.starts_with(RUSTFMT_CUSTOM_COMMENT_PREFIX) {
let orig = left_trimmed.trim_left_matches(RUSTFMT_CUSTOM_COMMENT_PREFIX);
- // due to comment wrapping, a line that was originaly behind `#` is split over
+ // due to comment wrapping, a line that was originally behind `#` is split over
// multiple lines, which needs then to be prefixed with a `#`
if !orig.trim_left().starts_with("# ") {
Cow::from(format!("# {}", orig))
offset: Indent,
config: &Config,
is_doc_comment: bool,
-) -> Option<String> {
+) -> String {
let lines: Vec<&str> = orig
.lines()
.map(|l| {
trim_right_unless_two_whitespaces(left_trimmed, is_doc_comment)
})
.collect();
- Some(lines.join(&format!("\n{}", offset.to_string(config))))
+ lines.join(&format!("\n{}", offset.to_string(config)))
}
/// Trims comment characters and possibly a single space from the left of a string.
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
-}
-
pub struct CharClasses<T>
where
T: Iterator,
check("\"/* abc", "abc", Some(4));
}
- #[test]
- fn test_remove_trailing_white_spaces() {
- let s = " r#\"\n test\n \"#";
- assert_eq!(remove_trailing_white_spaces(&s), s);
- }
-
#[test]
fn test_filter_normal_code() {
let s = r#"