use std::{self, borrow::Cow, iter};
use itertools::{multipeek, MultiPeek};
-use syntax::codemap::Span;
+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])
})
}
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 {
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 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,
+ first_group,
block_style,
style,
shape,
if rest.is_empty() {
Some(first_group_str)
} else {
- identify_comment(&rest, block_style, shape, config, is_doc_comment).map(|rest_str| {
+ identify_comment(rest, block_style, shape, config, is_doc_comment).map(|rest_str| {
format!(
"{}\n{}{}",
first_group_str,
}
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 {
(
}
} else if is_prev_line_multi_line && !line.is_empty() {
result.push(' ')
- } else if is_last && !closer.is_empty() && line.is_empty() {
- 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(' ') {
const RUSTFMT_CUSTOM_COMMENT_PREFIX: &str = "//#### ";
-fn hide_sharp_behind_comment<'a>(s: &'a str) -> Cow<'a, str> {
- if s.trim_left().starts_with('#') {
+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)
} else {
line
}
- })
- .collect::<Vec<_>>()
+ }).collect::<Vec<_>>()
.join("\n")
}
};
// Preserve markdown's double-space line break syntax in doc comment.
trim_right_unless_two_whitespaces(left_trimmed, is_doc_comment)
- })
- .collect();
+ }).collect();
Some(lines.join(&format!("\n{}", offset.to_string(config))))
}
Normal,
LitString,
LitStringEscape,
+ LitRawString(u32),
+ RawStringPrefix(u32),
+ RawStringSuffix(u32),
LitChar,
LitCharEscape,
// The u32 is the nesting deepness of the comment
InComment,
/// Last character of a comment, '\n' for a line comment, '/' for a block comment.
EndComment,
+ /// Start of a mutlitine string
+ StartString,
+ /// End of a mutlitine string
+ EndString,
/// Inside a string.
InString,
}
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 || self == FullCodeCharKind::StartString
}
- fn to_codecharkind(&self) -> CodeCharKind {
+ fn to_codecharkind(self) -> CodeCharKind {
if self.is_comment() {
CodeCharKind::Comment
} else {
}
}
+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::LitString => match chr {
- '"' => CharClassesStatus::Normal,
- '\\' => {
- char_kind = FullCodeCharKind::InString;
- CharClassesStatus::LitStringEscape
+ 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),
}
- _ => {
- char_kind = FullCodeCharKind::InString;
- CharClassesStatus::LitString
+ }
+ 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 => {
+ char_kind = FullCodeCharKind::InString;
+ match chr {
+ '"' => CharClassesStatus::Normal,
+ '\\' => CharClassesStatus::LitStringEscape,
+ _ => CharClassesStatus::LitString,
+ }
+ }
CharClassesStatus::LitStringEscape => {
char_kind = FullCodeCharKind::InString;
CharClassesStatus::LitString
},
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
type Item = (FullCodeCharKind, String);
fn next(&mut self) -> Option<Self::Item> {
- if self.base.peek().is_none() {
- return None;
- }
+ self.base.peek()?;
let mut line = String::new();
+ let start_class = match self.base.peek() {
+ Some((kind, _)) => *kind,
+ None => FullCodeCharKind::Normal,
+ };
+
while let Some((kind, c)) = self.base.next() {
- self.kind = kind;
if c == '\n' {
+ self.kind = match (start_class, kind) {
+ (FullCodeCharKind::Normal, FullCodeCharKind::InString) => {
+ FullCodeCharKind::StartString
+ }
+ (FullCodeCharKind::InString, FullCodeCharKind::Normal) => {
+ FullCodeCharKind::EndString
+ }
+ _ => kind,
+ };
break;
} else {
line.push(c);
) -> 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::StartString
+ | FullCodeCharKind::InString
+ | FullCodeCharKind::EndString => {
+ 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)
.filter_map(|(s, c)| match s {
FullCodeCharKind::Normal | FullCodeCharKind::InString => Some(c),
_ => None,
- })
- .collect()
+ }).collect()
}
#[test]
#[test]
fn test_remove_trailing_white_spaces() {
- let s = format!(" r#\"\n test\n \"#");
+ 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));
+ }
}