diff --git a/src/comment.rs b/src/comment.rs index 05b423d1898..9d5543258f6 100644 --- a/src/comment.rs +++ b/src/comment.rs @@ -901,6 +901,45 @@ where } } +/// 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>>, + 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 { + if self.base.peek().is_none() { + return None; + } + + 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. @@ -1141,8 +1180,7 @@ fn remove_comment_header(comment: &str) -> &str { #[cfg(test)] mod test { - use super::{contains_comment, rewrite_comment, CharClasses, CodeCharKind, CommentCodeSlices, - FindUncommented, FullCodeCharKind}; + use super::*; use shape::{Indent, Shape}; #[test] @@ -1298,4 +1336,10 @@ mod test { check("\"/* abc */\"", "abc", Some(4)); check("\"/* abc", "abc", Some(4)); } + + #[test] + fn test_remove_trailing_white_spaces() { + let s = format!(" r#\"\n test\n \"#"); + assert_eq!(remove_trailing_white_spaces(&s), s); + } } diff --git a/src/lib.rs b/src/lib.rs index 151b3acada2..6aa9f7f3107 100644 --- a/src/lib.rs +++ b/src/lib.rs @@ -47,7 +47,7 @@ use syntax::errors::{DiagnosticBuilder, Handler}; use syntax::parse::{self, ParseSess}; use checkstyle::{output_footer, output_header}; -use comment::{CharClasses, FullCodeCharKind}; +use comment::{CharClasses, FullCodeCharKind, LineClasses}; use issues::{BadIssueSeeker, Issue}; use shape::Indent; use utils::use_colored_tty; @@ -605,10 +605,19 @@ const FN_MAIN_PREFIX: &str = "fn main() {\n"; fn enclose_in_main_block(s: &str, config: &Config) -> String { let indent = Indent::from_width(config, config.tab_spaces()); - FN_MAIN_PREFIX.to_owned() + &indent.to_string(config) - + &s.lines() - .collect::>() - .join(&indent.to_string_with_newline(config)) + "\n}" + let mut result = String::with_capacity(s.len() * 2); + result.push_str(FN_MAIN_PREFIX); + let mut need_indent = true; + for (kind, line) in LineClasses::new(s) { + if need_indent { + result.push_str(&indent.to_string(config)); + } + result.push_str(&line); + result.push('\n'); + need_indent = !(kind.is_string() && !line.ends_with('\\')); + } + result.push('}'); + result } /// Format the given code block. Mainly targeted for code block in comment. @@ -626,13 +635,16 @@ pub fn format_code_block(code_snippet: &str, config: &Config) -> Option let formatted = format_snippet(&snippet, config)?; // 2 = "}\n" let block_len = formatted.len().checked_sub(2).unwrap_or(0); - for line in formatted[FN_MAIN_PREFIX.len()..block_len].lines() { + let mut is_indented = true; + for (kind, ref line) in LineClasses::new(&formatted[FN_MAIN_PREFIX.len()..block_len]) { if !is_first { result.push('\n'); } else { is_first = false; } - let trimmed_line = if line.len() > config.max_width() { + let trimmed_line = if !is_indented { + line + } else if line.len() > config.max_width() { // If there are lines that are larger than max width, we cannot tell // whether we have succeeded but have some comments or strings that // are too long, or we have failed to format code block. We will be @@ -655,6 +667,7 @@ pub fn format_code_block(code_snippet: &str, config: &Config) -> Option line }; result.push_str(trimmed_line); + is_indented = !(kind.is_string() && !line.ends_with('\\')); } Some(result) } diff --git a/src/macros.rs b/src/macros.rs index aded2508aaa..3769c97845b 100644 --- a/src/macros.rs +++ b/src/macros.rs @@ -34,7 +34,7 @@ use syntax::{ast, ptr}; use codemap::SpanUtils; use comment::{contains_comment, remove_trailing_white_spaces, CharClasses, FindUncommented, - FullCodeCharKind}; + FullCodeCharKind, LineClasses}; use expr::rewrite_array; use lists::{itemize_list, write_list, ListFormatting}; use overflow; @@ -1054,18 +1054,27 @@ fn indent_macro_snippet( macro_str: &str, indent: Indent, ) -> Option { - let mut lines = macro_str.lines(); - let first_line = lines.next().map(|s| s.trim_right())?; + let mut lines = LineClasses::new(macro_str); + let first_line = lines.next().map(|(_, s)| s.trim_right().to_owned())?; let mut trimmed_lines = Vec::with_capacity(16); + let mut veto_trim = false; let min_prefix_space_width = lines - .filter_map(|line| { - let prefix_space_width = if is_empty_line(line) { + .filter_map(|(kind, line)| { + let mut trimmed = true; + let prefix_space_width = if is_empty_line(&line) { None } else { - Some(get_prefix_space_width(context, line)) + Some(get_prefix_space_width(context, &line)) }; - trimmed_lines.push((line.trim(), prefix_space_width)); + let line = if veto_trim || (kind.is_string() && !line.ends_with('\\')) { + veto_trim = kind.is_string() && !line.ends_with('\\'); + trimmed = false; + line + } else { + line.trim().to_owned() + }; + trimmed_lines.push((trimmed, line, prefix_space_width)); prefix_space_width }) .min()?; @@ -1074,17 +1083,20 @@ fn indent_macro_snippet( String::from(first_line) + "\n" + &trimmed_lines .iter() - .map(|&(line, prefix_space_width)| match prefix_space_width { - Some(original_indent_width) => { - let new_indent_width = indent.width() - + original_indent_width - .checked_sub(min_prefix_space_width) - .unwrap_or(0); - let new_indent = Indent::from_width(context.config, new_indent_width); - format!("{}{}", new_indent.to_string(context.config), line.trim()) - } - None => String::new(), - }) + .map( + |&(trimmed, ref line, prefix_space_width)| match prefix_space_width { + _ if !trimmed => line.to_owned(), + Some(original_indent_width) => { + let new_indent_width = indent.width() + + original_indent_width + .checked_sub(min_prefix_space_width) + .unwrap_or(0); + let new_indent = Indent::from_width(context.config, new_indent_width); + format!("{}{}", new_indent.to_string(context.config), line.trim()) + } + None => String::new(), + }, + ) .collect::>() .join("\n"), ) @@ -1231,15 +1243,17 @@ impl MacroBranch { // Indent the body since it is in a block. let indent_str = body_indent.to_string(&config); - let mut new_body = new_body - .trim_right() - .lines() - .fold(String::new(), |mut s, l| { - if !l.is_empty() { - s += &indent_str; - } - s + l + "\n" - }); + let mut new_body = LineClasses::new(new_body.trim_right()) + .fold( + (String::new(), true), + |(mut s, need_indent), (kind, ref l)| { + if !l.is_empty() && need_indent { + s += &indent_str; + } + (s + l + "\n", !(kind.is_string() && !l.ends_with('\\'))) + }, + ) + .0; // Undo our replacement of macro variables. // FIXME: this could be *much* more efficient. diff --git a/tests/source/macros.rs b/tests/source/macros.rs index 7ed3199d1ce..6e730fffbf6 100644 --- a/tests/source/macros.rs +++ b/tests/source/macros.rs @@ -348,6 +348,20 @@ macro lex_err($kind: ident $(, $body: expr)*) { methods![ get, post, delete, ]; methods!( get, post, delete, ); +// #2588 +macro_rules! m { + () => { + r#" + test + "# + }; +} +fn foo() { + f!{r#" + test + "#}; +} + // #2591 fn foo() { match 0u32 { diff --git a/tests/target/macros.rs b/tests/target/macros.rs index 482423958da..d430f5711dd 100644 --- a/tests/target/macros.rs +++ b/tests/target/macros.rs @@ -923,6 +923,20 @@ macro lex_err($kind: ident $(, $body: expr)*) { methods![get, post, delete,]; methods!(get, post, delete,); +// #2588 +macro_rules! m { + () => { + r#" + test + "# + }; +} +fn foo() { + f!{r#" + test + "#}; +} + // #2591 fn foo() { match 0u32 {