// Formatting and tools for comments.
-use std::{self, iter};
+use std::{self, borrow::Cow, iter};
-use syntax::codemap::Span;
+use itertools::{multipeek, MultiPeek};
+use syntax::source_map::Span;
use config::Config;
use rewrite::RewriteContext;
use shape::{Indent, Shape};
use string::{rewrite_string, StringFormat};
use utils::{count_newlines, first_line_width, last_line_width};
+use {ErrorKind, FormattingError};
fn is_custom_comment(comment: &str) -> bool {
if !comment.starts_with("//") {
s.lines().next().map_or("", |first_line| {
first_line
.find(' ')
- .map_or(first_line, |space_index| &first_line[0..space_index + 1])
+ .map_or(first_line, |space_index| &first_line[0..=space_index])
})
}
impl<'a> CommentStyle<'a> {
+ pub fn is_doc_comment(&self) -> bool {
+ match *self {
+ CommentStyle::TripleSlash | CommentStyle::Doc => true,
+ _ => false,
+ }
+ }
+
pub fn opener(&self) -> &'a str {
match *self {
CommentStyle::DoubleSlash => "// ",
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 {
}
}
+/// Combine `prev_str` and `next_str` into a single `String`. `span` may contain
+/// comments between two strings. If there are such comments, then that will be
+/// recovered. If `allow_extend` is true and there is no comment between the two
+/// strings, then they will be put on a single line as long as doing so does not
+/// exceed max width.
pub fn combine_strs_with_missing_comments(
context: &RewriteContext,
prev_str: &str,
shape: Shape,
allow_extend: bool,
) -> Option<String> {
+ let mut result =
+ 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() {
""
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 config = context.config;
+ let indent = shape.indent;
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));
+ result.push_str(first_sep);
+ } else if !prev_str.is_empty() {
+ result.push_str(&indent.to_string_with_newline(config))
}
+ result.push_str(next_str);
+ return Some(result);
}
// We have a missing comment between the first expression and the second expression.
// 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 == '/') {
+ let prefer_same_line = if let Some(pos) = original_snippet.find('/') {
!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()
+ Cow::from("")
} 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(" ")
+ Cow::from(" ")
} else {
- format!("\n{}", indent_str)
+ indent.to_string_with_newline(config)
}
};
+ result.push_str(&first_sep);
+ result.push_str(&missing_comment);
+
let second_sep = if missing_comment.is_empty() || next_str.is_empty() {
- String::new()
+ Cow::from("")
} else if missing_comment.starts_with("//") {
- format!("\n{}", indent_str)
+ indent.to_string_with_newline(config)
} 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(" ")
+ Cow::from(" ")
} else {
- format!("\n{}", indent_str)
+ indent.to_string_with_newline(config)
}
};
- Some(format!(
- "{}{}{}{}{}",
- prev_str, first_sep, missing_comment, second_sep, next_str,
- ))
+ result.push_str(&second_sep);
+ result.push_str(next_str);
+
+ Some(result)
+}
+
+pub fn rewrite_doc_comment(orig: &str, shape: Shape, config: &Config) -> Option<String> {
+ _rewrite_comment(orig, false, shape, config, true)
}
pub fn rewrite_comment(
block_style: bool,
shape: Shape,
config: &Config,
+) -> Option<String> {
+ _rewrite_comment(orig, block_style, shape, config, false)
+}
+
+fn _rewrite_comment(
+ orig: &str,
+ block_style: bool,
+ shape: Shape,
+ config: &Config,
+ is_doc_comment: bool,
) -> Option<String> {
// 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.
- let num_bare_lines = orig.lines()
+ let num_bare_lines = orig
+ .lines()
.map(|line| line.trim())
.filter(|l| !(l.starts_with('*') || l.starts_with("//") || l.starts_with("/*")))
.count();
return Some(orig.to_owned());
}
if !config.normalize_comments() && !config.wrap_comments() {
- return light_rewrite_comment(orig, shape.indent, config);
+ return light_rewrite_comment(orig, shape.indent, config, is_doc_comment);
}
- identify_comment(orig, block_style, shape, config)
+ identify_comment(orig, block_style, shape, config, is_doc_comment)
}
fn identify_comment(
block_style: bool,
shape: Shape,
config: &Config,
+ is_doc_comment: bool,
) -> Option<String> {
let style = comment_style(orig, false);
- let first_group = orig.lines()
- .take_while(|l| style.line_with_same_comment_style(l, false))
- .collect::<Vec<_>>()
- .join("\n");
- let rest = orig.lines()
- .skip(first_group.lines().count())
- .collect::<Vec<_>>()
- .join("\n");
-
- let first_group_str = rewrite_comment_inner(&first_group, block_style, style, shape, config)?;
+ let mut first_group_ending = 0;
+
+ fn compute_len(orig: &str, line: &str) -> usize {
+ if orig.len() > line.len() {
+ if orig.as_bytes()[line.len()] == b'\r' {
+ line.len() + 2
+ } else {
+ line.len() + 1
+ }
+ } else {
+ line.len()
+ }
+ }
+
+ match style {
+ CommentStyle::DoubleSlash | CommentStyle::TripleSlash | CommentStyle::Doc => {
+ let line_start = style.line_start().trim_left();
+ for line in orig.lines() {
+ if line.trim_left().starts_with(line_start) || comment_style(line, false) == style {
+ first_group_ending += compute_len(&orig[first_group_ending..], line);
+ } else {
+ break;
+ }
+ }
+ }
+ CommentStyle::Custom(opener) => {
+ let trimmed_opener = opener.trim_right();
+ for line in orig.lines() {
+ if line.trim_left().starts_with(trimmed_opener) {
+ first_group_ending += compute_len(&orig[first_group_ending..], line);
+ } else {
+ break;
+ }
+ }
+ }
+ // for a block comment, search for the closing symbol
+ CommentStyle::DoubleBullet | CommentStyle::SingleBullet | CommentStyle::Exclamation => {
+ let closer = style.closer().trim_left();
+ for line in orig.lines() {
+ first_group_ending += compute_len(&orig[first_group_ending..], line);
+ if line.trim_left().ends_with(closer) {
+ break;
+ }
+ }
+ }
+ }
+
+ let (first_group, rest) = orig.split_at(first_group_ending);
+ let first_group_str = rewrite_comment_inner(
+ first_group,
+ block_style,
+ style,
+ shape,
+ config,
+ is_doc_comment || style.is_doc_comment(),
+ )?;
if rest.is_empty() {
Some(first_group_str)
} else {
- identify_comment(&rest, block_style, shape, config).map(|rest_str| {
+ identify_comment(rest, block_style, shape, config, is_doc_comment).map(|rest_str| {
format!(
"{}\n{}{}",
first_group_str,
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()
.width
.checked_sub(closer.len() + opener.len())
.unwrap_or(1);
- let indent_str = shape.indent.to_string(config);
+ 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_start,
+ line_start,
line_end: "",
shape: Shape::legacy(max_chars, fmt_indent),
trim_end: true,
- config: config,
+ config,
};
let line_breaks = count_newlines(orig.trim_right());
- let lines = orig.lines()
+ let lines = orig
+ .lines()
.enumerate()
.map(|(i, mut line)| {
- line = line.trim();
+ 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(|s| left_trim_comment_line(s, &style))
.map(|(line, has_leading_whitespace)| {
if orig.starts_with("/*") && line_breaks == 0 {
(
let mut code_block_buffer = String::with_capacity(128);
let mut is_prev_line_multi_line = false;
let mut inside_code_block = false;
- let comment_line_separator = format!("\n{}{}", indent_str, line_start);
+ let comment_line_separator = format!("{}{}", indent_str, line_start);
let join_code_block_with_comment_line_separator = |s: &str| {
let mut result = String::with_capacity(s.len() + 128);
let mut iter = s.lines().peekable();
if line.starts_with("```") {
inside_code_block = false;
result.push_str(&comment_line_separator);
- let code_block = ::format_code_block(&code_block_buffer, config)
- .unwrap_or_else(|| code_block_buffer.to_owned());
+ let code_block = {
+ let mut config = config.clone();
+ config.set().wrap_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),
+ }
+ };
result.push_str(&join_code_block_with_comment_line_separator(&code_block));
code_block_buffer.clear();
result.push_str(&comment_line_separator);
result.push_str(line);
} else {
- code_block_buffer.push_str(line);
+ code_block_buffer.push_str(&hide_sharp_behind_comment(line));
code_block_buffer.push('\n');
+
+ if is_last {
+ // There is an code block that is not properly enclosed by backticks.
+ // We will leave them untouched.
+ result.push_str(&comment_line_separator);
+ result.push_str(&join_code_block_with_comment_line_separator(
+ &trim_custom_comment_prefix(&code_block_buffer),
+ ));
+ }
}
continue;
}
} else if is_prev_line_multi_line && !line.is_empty() {
result.push(' ')
- } else if is_last && !closer.is_empty() && line.is_empty() {
- result.push('\n');
- result.push_str(&indent_str);
+ } else if is_last && line.is_empty() {
+ // trailing blank lines are unwanted
+ if !closer.is_empty() {
+ result.push_str(&indent_str);
+ }
+ break;
} else {
result.push_str(&comment_line_separator);
if !has_leading_whitespace && result.ends_with(' ') {
}
if config.wrap_comments() && line.len() > fmt.shape.width && !has_url(line) {
- match rewrite_string(line, &fmt, Some(max_chars)) {
+ match rewrite_string(line, &fmt) {
Some(ref s) => {
is_prev_line_multi_line = s.contains('\n');
result.push_str(s);
result.pop();
result.push_str(&comment_line_separator);
fmt.shape = Shape::legacy(max_chars, fmt_indent);
- match rewrite_string(line, &fmt, Some(max_chars)) {
+ match rewrite_string(line, &fmt) {
Some(ref s) => {
is_prev_line_multi_line = s.contains('\n');
result.push_str(s);
// 1 = " "
let offset = 1 + last_line_width(&result) - line_start.len();
Shape {
- width: max_chars.checked_sub(offset).unwrap_or(0),
+ width: max_chars.saturating_sub(offset),
indent: fmt_indent,
offset: fmt.shape.offset + offset,
}
}
result.push_str(closer);
- if result == opener && result.ends_with(' ') {
+ if result.ends_with(opener) && opener.ends_with(' ') {
// Trailing space.
result.pop();
}
Some(result)
}
+const RUSTFMT_CUSTOM_COMMENT_PREFIX: &str = "//#### ";
+
+fn hide_sharp_behind_comment(s: &str) -> Cow<str> {
+ if s.trim_left().starts_with("# ") {
+ Cow::from(format!("{}{}", RUSTFMT_CUSTOM_COMMENT_PREFIX, s))
+ } else {
+ Cow::from(s)
+ }
+}
+
+fn trim_custom_comment_prefix(s: &str) -> String {
+ s.lines()
+ .map(|line| {
+ let left_trimmed = line.trim_left();
+ if left_trimmed.starts_with(RUSTFMT_CUSTOM_COMMENT_PREFIX) {
+ left_trimmed.trim_left_matches(RUSTFMT_CUSTOM_COMMENT_PREFIX)
+ } else {
+ line
+ }
+ }).collect::<Vec<_>>()
+ .join("\n")
+}
+
/// 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.
Some(String::new())
} else {
let missing_snippet = context.snippet(span);
- let pos = missing_snippet.chars().position(|c| c == '/').unwrap_or(0);
+ let pos = missing_snippet.find('/').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))
+ shape.indent.to_string_with_newline(context.config)
} else {
- String::from(" ")
+ Cow::from(" ")
};
Some(format!("{}{}", sep, missing_comment))
}
}
+/// Trim trailing whitespaces unless they consist of two or more whitespaces.
+fn trim_right_unless_two_whitespaces(s: &str, is_doc_comment: bool) -> &str {
+ if is_doc_comment && s.ends_with(" ") {
+ s
+ } else {
+ s.trim_right()
+ }
+}
+
/// Trims whitespace and aligns to indent, but otherwise does not change comments.
-fn light_rewrite_comment(orig: &str, offset: Indent, config: &Config) -> Option<String> {
- let lines: Vec<&str> = orig.lines()
+fn light_rewrite_comment(
+ orig: &str,
+ offset: Indent,
+ config: &Config,
+ is_doc_comment: bool,
+) -> Option<String> {
+ let lines: Vec<&str> = orig
+ .lines()
.map(|l| {
// This is basically just l.trim(), but in the case that a line starts
// with `*` we want to leave one space before it, so it aligns with the
// `*` in `/*`.
let first_non_whitespace = l.find(|c| !char::is_whitespace(c));
- if let Some(fnw) = first_non_whitespace {
+ let left_trimmed = if let Some(fnw) = first_non_whitespace {
if l.as_bytes()[fnw] == b'*' && fnw > 0 {
&l[fnw - 1..]
} else {
}
} else {
""
- }.trim_right()
- })
- .collect();
+ };
+ // Preserve markdown's double-space line break syntax in doc comment.
+ trim_right_unless_two_whitespaces(left_trimmed, is_doc_comment)
+ }).collect();
Some(lines.join(&format!("\n{}", offset.to_string(config))))
}
/// Does not trim all whitespace. If a single space is trimmed from the left of the string,
/// this function returns true.
fn left_trim_comment_line<'a>(line: &'a str, style: &CommentStyle) -> (&'a str, bool) {
- if line.starts_with("//! ") || line.starts_with("/// ") || line.starts_with("/*! ")
+ if line.starts_with("//! ")
+ || line.starts_with("/// ")
+ || line.starts_with("/*! ")
|| line.starts_with("/** ")
{
(&line[4..], true)
} else {
(&line[opener.trim_right().len()..], false)
}
- } else if 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..], line.chars().nth(2).unwrap() == ' ')
- } else if 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..], line.chars().nth(1).unwrap() == ' ')
T: Iterator,
T::Item: RichChar,
{
- base: iter::Peekable<T>,
+ base: MultiPeek<T>,
status: CharClassesStatus,
}
}
}
-impl RichChar for (char, usize) {
- fn get_char(&self) -> char {
- self.0
- }
-}
-
#[derive(PartialEq, Eq, Debug, Clone, Copy)]
enum CharClassesStatus {
Normal,
LitString,
LitStringEscape,
+ LitRawString(u32),
+ RawStringPrefix(u32),
+ RawStringSuffix(u32),
LitChar,
LitCharEscape,
// The u32 is the nesting deepness of the comment
}
impl FullCodeCharKind {
- pub fn is_comment(&self) -> bool {
- match *self {
+ pub fn is_comment(self) -> bool {
+ match self {
FullCodeCharKind::StartComment
| FullCodeCharKind::InComment
| FullCodeCharKind::EndComment => true,
}
}
- pub fn is_string(&self) -> bool {
- *self == FullCodeCharKind::InString
+ pub fn is_string(self) -> bool {
+ self == FullCodeCharKind::InString
}
- fn to_codecharkind(&self) -> CodeCharKind {
+ fn to_codecharkind(self) -> CodeCharKind {
if self.is_comment() {
CodeCharKind::Comment
} else {
{
pub fn new(base: T) -> CharClasses<T> {
CharClasses {
- base: base.peekable(),
+ base: multipeek(base),
status: CharClassesStatus::Normal,
}
}
}
+fn is_raw_string_suffix<T>(iter: &mut MultiPeek<T>, count: u32) -> bool
+where
+ T: Iterator,
+ T::Item: RichChar,
+{
+ for _ in 0..count {
+ match iter.peek() {
+ Some(c) if c.get_char() == '#' => continue,
+ _ => return false,
+ }
+ }
+ true
+}
+
impl<T> Iterator for CharClasses<T>
where
T: Iterator,
let chr = item.get_char();
let mut char_kind = FullCodeCharKind::Normal;
self.status = match self.status {
+ CharClassesStatus::LitRawString(sharps) => {
+ char_kind = FullCodeCharKind::InString;
+ match chr {
+ '"' => {
+ if sharps == 0 {
+ char_kind = FullCodeCharKind::Normal;
+ CharClassesStatus::Normal
+ } else if is_raw_string_suffix(&mut self.base, sharps) {
+ CharClassesStatus::RawStringSuffix(sharps)
+ } else {
+ CharClassesStatus::LitRawString(sharps)
+ }
+ }
+ _ => CharClassesStatus::LitRawString(sharps),
+ }
+ }
+ CharClassesStatus::RawStringPrefix(sharps) => {
+ char_kind = FullCodeCharKind::InString;
+ match chr {
+ '#' => CharClassesStatus::RawStringPrefix(sharps + 1),
+ '"' => CharClassesStatus::LitRawString(sharps),
+ _ => CharClassesStatus::Normal, // Unreachable.
+ }
+ }
+ CharClassesStatus::RawStringSuffix(sharps) => {
+ match chr {
+ '#' => {
+ if sharps == 1 {
+ CharClassesStatus::Normal
+ } else {
+ char_kind = FullCodeCharKind::InString;
+ CharClassesStatus::RawStringSuffix(sharps - 1)
+ }
+ }
+ _ => CharClassesStatus::Normal, // Unreachable
+ }
+ }
CharClassesStatus::LitString => match chr {
'"' => CharClassesStatus::Normal,
'\\' => {
},
CharClassesStatus::LitCharEscape => CharClassesStatus::LitChar,
CharClassesStatus::Normal => match chr {
+ 'r' => match self.base.peek().map(|c| c.get_char()) {
+ Some('#') | Some('"') => {
+ char_kind = FullCodeCharKind::InString;
+ CharClassesStatus::RawStringPrefix(0)
+ }
+ _ => CharClassesStatus::Normal,
+ },
'"' => {
char_kind = FullCodeCharKind::InString;
CharClassesStatus::LitString
}
- '\'' => CharClassesStatus::LitChar,
+ '\'' => {
+ // HACK: Work around mut borrow.
+ match self.base.peek() {
+ Some(next) if next.get_char() == '\\' => {
+ self.status = CharClassesStatus::LitChar;
+ return Some((char_kind, item));
+ }
+ _ => (),
+ }
+
+ match self.base.peek() {
+ Some(next) if next.get_char() == '\'' => CharClassesStatus::LitChar,
+ _ => CharClassesStatus::Normal,
+ }
+ }
'/' => match self.base.peek() {
Some(next) if next.get_char() == '*' => {
self.status = CharClassesStatus::BlockCommentOpening(1);
}
}
+/// An iterator over the lines of a string, paired with the char kind at the
+/// end of the line.
+pub struct LineClasses<'a> {
+ base: iter::Peekable<CharClasses<std::str::Chars<'a>>>,
+ kind: FullCodeCharKind,
+}
+
+impl<'a> LineClasses<'a> {
+ pub fn new(s: &'a str) -> Self {
+ LineClasses {
+ base: CharClasses::new(s.chars()).peekable(),
+ kind: FullCodeCharKind::Normal,
+ }
+ }
+}
+
+impl<'a> Iterator for LineClasses<'a> {
+ type Item = (FullCodeCharKind, String);
+
+ fn next(&mut self) -> Option<Self::Item> {
+ self.base.peek()?;
+
+ let mut line = String::new();
+
+ while let Some((kind, c)) = self.base.next() {
+ self.kind = kind;
+ if c == '\n' {
+ break;
+ } else {
+ line.push(c);
+ }
+ }
+
+ Some((self.kind, line))
+ }
+}
+
/// Iterator over functional and commented parts of a string. Any part of a string is either
/// functional code, either *one* block comment, either *one* line comment. Whitespace between
/// comments is functional code. Line comments contain their ending newlines.
impl<'a> CommentCodeSlices<'a> {
pub fn new(slice: &'a str) -> CommentCodeSlices<'a> {
CommentCodeSlices {
- slice: slice,
+ slice,
last_slice_kind: CodeCharKind::Comment,
last_slice_end: 0,
}
) -> Option<String> {
let snippet = context.snippet(span);
if snippet != new && changed_comment_content(snippet, &new) {
- // We missed some comments. Keep the original text.
+ // 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(),
+ vec![FormattingError::from_span(
+ span,
+ &context.source_map,
+ ErrorKind::LostComment,
+ )],
+ );
+ }
Some(snippet.to_owned())
} else {
Some(new)
}
}
+pub fn filter_normal_code(code: &str) -> String {
+ let mut buffer = String::with_capacity(code.len());
+ LineClasses::new(code).for_each(|(kind, line)| match kind {
+ FullCodeCharKind::Normal | FullCodeCharKind::InString => {
+ buffer.push_str(&line);
+ buffer.push('\n');
+ }
+ _ => (),
+ });
+ if !code.ends_with('\n') && buffer.ends_with('\n') {
+ buffer.pop();
+ }
+ buffer
+}
+
/// Return true if the two strings of code have the same payload of comments.
/// The payload of comments is everything in the string except:
/// - actual code (not comments)
let is_block = comment.starts_with("/*");
let comment = remove_comment_header(comment);
CommentReducer {
- is_block: is_block,
+ is_block,
at_start_line: false, // There are no supplementary '*' on the first line
iter: comment.chars(),
}
impl<'a> Iterator for CommentReducer<'a> {
type Item = char;
+
fn next(&mut self) -> Option<Self::Item> {
loop {
let mut c = self.iter.next()?;
#[cfg(test)]
mod test {
- use super::{contains_comment, rewrite_comment, CharClasses, CodeCharKind, CommentCodeSlices,
- FindUncommented, FullCodeCharKind};
+ use super::*;
use shape::{Indent, Shape};
#[test]
}
#[test]
- #[cfg_attr(rustfmt, rustfmt_skip)]
+ #[rustfmt::skip]
fn format_comments() {
let mut config: ::config::Config = Default::default();
config.set().wrap_comments(true);
.filter_map(|(s, c)| match s {
FullCodeCharKind::Normal | FullCodeCharKind::InString => Some(c),
_ => None,
- })
- .collect()
+ }).collect()
}
#[test]
check("\"/* abc */\"", "abc", Some(4));
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#"
+fn main() {
+ println!("hello, world");
+}
+"#;
+ assert_eq!(s, filter_normal_code(s));
+ let s_with_comment = r#"
+fn main() {
+ // hello, world
+ println!("hello, world");
+}
+"#;
+ assert_eq!(s, filter_normal_code(s_with_comment));
+ }
}