diff --git a/src/config/options.rs b/src/config/options.rs index c927cffba29..4de8ef928dd 100644 --- a/src/config/options.rs +++ b/src/config/options.rs @@ -22,62 +22,6 @@ pub enum NewlineStyle { Native, } -impl NewlineStyle { - fn auto_detect(raw_input_text: &str) -> NewlineStyle { - if let Some(pos) = raw_input_text.find('\n') { - let pos = pos.saturating_sub(1); - if let Some('\r') = raw_input_text.chars().nth(pos) { - NewlineStyle::Windows - } else { - NewlineStyle::Unix - } - } else { - NewlineStyle::Native - } - } - - fn native() -> NewlineStyle { - if cfg!(windows) { - NewlineStyle::Windows - } else { - NewlineStyle::Unix - } - } - - /// Apply this newline style to the formatted text. When the style is set - /// to `Auto`, the `raw_input_text` is used to detect the existing line - /// endings. - /// - /// If the style is set to `Auto` and `raw_input_text` contains no - /// newlines, the `Native` style will be used. - pub(crate) fn apply(self, formatted_text: &mut String, raw_input_text: &str) { - use crate::NewlineStyle::*; - let mut style = self; - if style == Auto { - style = Self::auto_detect(raw_input_text); - } - if style == Native { - style = Self::native(); - } - match style { - Windows => { - let mut transformed = String::with_capacity(2 * formatted_text.capacity()); - for c in formatted_text.chars() { - match c { - '\n' => transformed.push_str("\r\n"), - '\r' => continue, - c => transformed.push(c), - } - } - *formatted_text = transformed; - } - Unix => return, - Native => unreachable!("NewlineStyle::Native"), - Auto => unreachable!("NewlineStyle::Auto"), - } - } -} - #[config_type] /// Where to put the opening brace of items (`fn`, `impl`, etc.). pub enum BraceStyle { @@ -412,59 +356,3 @@ impl Edition { } } } - -#[test] -fn test_newline_style_auto_detect() { - let lf = "One\nTwo\nThree"; - let crlf = "One\r\nTwo\r\nThree"; - let none = "One Two Three"; - - assert_eq!(NewlineStyle::Unix, NewlineStyle::auto_detect(lf)); - assert_eq!(NewlineStyle::Windows, NewlineStyle::auto_detect(crlf)); - assert_eq!(NewlineStyle::Native, NewlineStyle::auto_detect(none)); -} - -#[test] -fn test_newline_style_auto_apply() { - let auto = NewlineStyle::Auto; - - let formatted_text = "One\nTwo\nThree"; - let raw_input_text = "One\nTwo\nThree"; - - let mut out = String::from(formatted_text); - auto.apply(&mut out, raw_input_text); - assert_eq!("One\nTwo\nThree", &out, "auto should detect 'lf'"); - - let formatted_text = "One\nTwo\nThree"; - let raw_input_text = "One\r\nTwo\r\nThree"; - - let mut out = String::from(formatted_text); - auto.apply(&mut out, raw_input_text); - assert_eq!("One\r\nTwo\r\nThree", &out, "auto should detect 'crlf'"); - - #[cfg(not(windows))] - { - let formatted_text = "One\nTwo\nThree"; - let raw_input_text = "One Two Three"; - - let mut out = String::from(formatted_text); - auto.apply(&mut out, raw_input_text); - assert_eq!( - "One\nTwo\nThree", &out, - "auto-native-unix should detect 'lf'" - ); - } - - #[cfg(windows)] - { - let formatted_text = "One\nTwo\nThree"; - let raw_input_text = "One Two Three"; - - let mut out = String::from(formatted_text); - auto.apply(&mut out, raw_input_text); - assert_eq!( - "One\r\nTwo\r\nThree", &out, - "auto-native-windows should detect 'crlf'" - ); - } -} diff --git a/src/formatting.rs b/src/formatting.rs index 9776359bef3..3ae73ea1c99 100644 --- a/src/formatting.rs +++ b/src/formatting.rs @@ -12,6 +12,7 @@ use syntax::errors::{DiagnosticBuilder, Handler}; use syntax::parse::{self, ParseSess}; use syntax::source_map::{FilePathMapping, SourceMap, Span, DUMMY_SP}; +use self::newline_style::apply_newline_style; use crate::comment::{CharClasses, FullCodeCharKind}; use crate::config::{Config, FileName, Verbosity}; use crate::ignore_path::IgnorePathSet; @@ -20,6 +21,8 @@ use crate::utils::{count_newlines, get_skip_macro_names}; use crate::visitor::{FmtVisitor, SnippetProvider}; use crate::{modules, source_file, ErrorKind, FormatReport, Input, Session}; +mod newline_style; + // A map of the files of a crate, with their new content pub(crate) type SourceFile = Vec; pub(crate) type FileRecord = (FileName, String); @@ -191,9 +194,12 @@ impl<'a, T: FormatHandler + 'a> FormatContext<'a, T> { &self.config, &self.report, ); - self.config - .newline_style() - .apply(&mut visitor.buffer, &big_snippet); + + apply_newline_style( + self.config.newline_style(), + &mut visitor.buffer, + &big_snippet, + ); if visitor.macro_rewrite_failure { self.report.add_macro_format_failure(); diff --git a/src/formatting/newline_style.rs b/src/formatting/newline_style.rs new file mode 100644 index 00000000000..ac620094900 --- /dev/null +++ b/src/formatting/newline_style.rs @@ -0,0 +1,250 @@ +use crate::NewlineStyle; + +/// Apply this newline style to the formatted text. When the style is set +/// to `Auto`, the `raw_input_text` is used to detect the existing line +/// endings. +/// +/// If the style is set to `Auto` and `raw_input_text` contains no +/// newlines, the `Native` style will be used. +pub(crate) fn apply_newline_style( + newline_style: NewlineStyle, + formatted_text: &mut String, + raw_input_text: &str, +) { + *formatted_text = match effective_newline_style(newline_style, raw_input_text) { + EffectiveNewlineStyle::Windows => convert_to_windows_newlines(formatted_text), + EffectiveNewlineStyle::Unix => convert_to_unix_newlines(formatted_text), + } +} + +#[derive(Debug, Copy, Clone, PartialEq, Eq)] +enum EffectiveNewlineStyle { + Windows, + Unix, +} + +fn effective_newline_style( + newline_style: NewlineStyle, + raw_input_text: &str, +) -> EffectiveNewlineStyle { + match newline_style { + NewlineStyle::Auto => auto_detect_newline_style(raw_input_text), + NewlineStyle::Native => native_newline_style(), + NewlineStyle::Windows => EffectiveNewlineStyle::Windows, + NewlineStyle::Unix => EffectiveNewlineStyle::Unix, + } +} + +const LINE_FEED: char = '\n'; +const CARRIAGE_RETURN: char = '\r'; +const WINDOWS_NEWLINE: &str = "\r\n"; +const UNIX_NEWLINE: &str = "\n"; + +fn auto_detect_newline_style(raw_input_text: &str) -> EffectiveNewlineStyle { + let first_line_feed_pos = raw_input_text.chars().position(|ch| ch == LINE_FEED); + match first_line_feed_pos { + Some(first_line_feed_pos) => { + let char_before_line_feed_pos = first_line_feed_pos.saturating_sub(1); + let char_before_line_feed = raw_input_text.chars().nth(char_before_line_feed_pos); + match char_before_line_feed { + Some(CARRIAGE_RETURN) => EffectiveNewlineStyle::Windows, + _ => EffectiveNewlineStyle::Unix, + } + } + None => native_newline_style(), + } +} + +fn native_newline_style() -> EffectiveNewlineStyle { + if cfg!(windows) { + EffectiveNewlineStyle::Windows + } else { + EffectiveNewlineStyle::Unix + } +} + +fn convert_to_windows_newlines(formatted_text: &String) -> String { + let mut transformed = String::with_capacity(2 * formatted_text.capacity()); + let mut chars = formatted_text.chars().peekable(); + while let Some(current_char) = chars.next() { + let next_char = chars.peek(); + match current_char { + LINE_FEED => transformed.push_str(WINDOWS_NEWLINE), + CARRIAGE_RETURN if next_char == Some(&LINE_FEED) => {} + current_char => transformed.push(current_char), + } + } + transformed +} + +fn convert_to_unix_newlines(formatted_text: &String) -> String { + formatted_text.replace(WINDOWS_NEWLINE, UNIX_NEWLINE) +} + +#[cfg(test)] +mod tests { + use super::*; + + #[test] + fn auto_detects_unix_newlines() { + assert_eq!( + EffectiveNewlineStyle::Unix, + auto_detect_newline_style("One\nTwo\nThree") + ); + } + + #[test] + fn auto_detects_windows_newlines() { + assert_eq!( + EffectiveNewlineStyle::Windows, + auto_detect_newline_style("One\r\nTwo\r\nThree") + ); + } + + #[test] + fn auto_detects_windows_newlines_with_multibyte_char_on_first_line() { + assert_eq!( + EffectiveNewlineStyle::Windows, + auto_detect_newline_style("A 🎢 of a first line\r\nTwo\r\nThree") + ); + } + + #[test] + fn falls_back_to_native_newlines_if_no_newlines_are_found() { + let expected_newline_style = if cfg!(windows) { + EffectiveNewlineStyle::Windows + } else { + EffectiveNewlineStyle::Unix + }; + assert_eq!( + expected_newline_style, + auto_detect_newline_style("One Two Three") + ); + } + + #[test] + fn auto_detects_and_applies_unix_newlines() { + let formatted_text = "One\nTwo\nThree"; + let raw_input_text = "One\nTwo\nThree"; + + let mut out = String::from(formatted_text); + apply_newline_style(NewlineStyle::Auto, &mut out, raw_input_text); + assert_eq!("One\nTwo\nThree", &out, "auto should detect 'lf'"); + } + + #[test] + fn auto_detects_and_applies_windows_newlines() { + let formatted_text = "One\nTwo\nThree"; + let raw_input_text = "One\r\nTwo\r\nThree"; + + let mut out = String::from(formatted_text); + apply_newline_style(NewlineStyle::Auto, &mut out, raw_input_text); + assert_eq!("One\r\nTwo\r\nThree", &out, "auto should detect 'crlf'"); + } + + #[test] + fn auto_detects_and_applies_native_newlines() { + let formatted_text = "One\nTwo\nThree"; + let raw_input_text = "One Two Three"; + + let mut out = String::from(formatted_text); + apply_newline_style(NewlineStyle::Auto, &mut out, raw_input_text); + + if cfg!(windows) { + assert_eq!( + "One\r\nTwo\r\nThree", &out, + "auto-native-windows should detect 'crlf'" + ); + } else { + assert_eq!( + "One\nTwo\nThree", &out, + "auto-native-unix should detect 'lf'" + ); + } + } + + #[test] + fn applies_unix_newlines() { + test_newlines_are_applied_correctly( + "One\r\nTwo\nThree", + "One\nTwo\nThree", + NewlineStyle::Unix, + ); + } + + #[test] + fn applying_unix_newlines_changes_nothing_for_unix_newlines() { + let formatted_text = "One\nTwo\nThree"; + test_newlines_are_applied_correctly(formatted_text, formatted_text, NewlineStyle::Unix); + } + + #[test] + fn applies_unix_newlines_to_string_with_unix_and_windows_newlines() { + test_newlines_are_applied_correctly( + "One\r\nTwo\r\nThree\nFour", + "One\nTwo\nThree\nFour", + NewlineStyle::Unix, + ); + } + + #[test] + fn applies_windows_newlines_to_string_with_unix_and_windows_newlines() { + test_newlines_are_applied_correctly( + "One\nTwo\nThree\r\nFour", + "One\r\nTwo\r\nThree\r\nFour", + NewlineStyle::Windows, + ); + } + + #[test] + fn applying_windows_newlines_changes_nothing_for_windows_newlines() { + let formatted_text = "One\r\nTwo\r\nThree"; + test_newlines_are_applied_correctly(formatted_text, formatted_text, NewlineStyle::Windows); + } + + #[test] + fn keeps_carriage_returns_when_applying_windows_newlines_to_str_with_unix_newlines() { + test_newlines_are_applied_correctly( + "One\nTwo\nThree\rDrei", + "One\r\nTwo\r\nThree\rDrei", + NewlineStyle::Windows, + ); + } + + #[test] + fn keeps_carriage_returns_when_applying_unix_newlines_to_str_with_unix_newlines() { + test_newlines_are_applied_correctly( + "One\nTwo\nThree\rDrei", + "One\nTwo\nThree\rDrei", + NewlineStyle::Unix, + ); + } + + #[test] + fn keeps_carriage_returns_when_applying_windows_newlines_to_str_with_windows_newlines() { + test_newlines_are_applied_correctly( + "One\r\nTwo\r\nThree\rDrei", + "One\r\nTwo\r\nThree\rDrei", + NewlineStyle::Windows, + ); + } + + #[test] + fn keeps_carriage_returns_when_applying_unix_newlines_to_str_with_windows_newlines() { + test_newlines_are_applied_correctly( + "One\r\nTwo\r\nThree\rDrei", + "One\nTwo\nThree\rDrei", + NewlineStyle::Unix, + ); + } + + fn test_newlines_are_applied_correctly( + input: &str, + expected: &str, + newline_style: NewlineStyle, + ) { + let mut out = String::from(input); + apply_newline_style(newline_style, &mut out, input); + assert_eq!(expected, &out); + } +} diff --git a/tests/source/preserves_carriage_return_for_unix.rs b/tests/source/preserves_carriage_return_for_unix.rs new file mode 100644 index 00000000000..e5e0b286598 --- /dev/null +++ b/tests/source/preserves_carriage_return_for_unix.rs @@ -0,0 +1,2 @@ +// rustfmt-newline_style: Unix +// Foo Bar diff --git a/tests/source/preserves_carriage_return_for_windows.rs b/tests/source/preserves_carriage_return_for_windows.rs new file mode 100644 index 00000000000..1085360ee59 --- /dev/null +++ b/tests/source/preserves_carriage_return_for_windows.rs @@ -0,0 +1,2 @@ +// rustfmt-newline_style: Windows +// Foo Bar diff --git a/tests/target/preserves_carriage_return_for_unix.rs b/tests/target/preserves_carriage_return_for_unix.rs new file mode 100644 index 00000000000..e5e0b286598 --- /dev/null +++ b/tests/target/preserves_carriage_return_for_unix.rs @@ -0,0 +1,2 @@ +// rustfmt-newline_style: Unix +// Foo Bar diff --git a/tests/target/preserves_carriage_return_for_windows.rs b/tests/target/preserves_carriage_return_for_windows.rs new file mode 100644 index 00000000000..1085360ee59 --- /dev/null +++ b/tests/target/preserves_carriage_return_for_windows.rs @@ -0,0 +1,2 @@ +// rustfmt-newline_style: Windows +// Foo Bar