diff --git a/src/emitter.rs b/src/emitter.rs index 75d402abfa3..89d0dc1f058 100644 --- a/src/emitter.rs +++ b/src/emitter.rs @@ -9,7 +9,7 @@ use std::io::{self, Write}; use thiserror::Error; -use crate::{config::FileName, FormatReport, FormatResult}; +use crate::{config::options::NewlineStyle, config::FileName, FormatReport, FormatResult}; pub mod checkstyle; pub mod diff; @@ -45,6 +45,7 @@ pub trait Emitter { &mut self, output: &mut dyn Write, formatted_file: FormattedFile<'_>, + newline_style: NewlineStyle, ) -> Result; fn emit_header(&self, _output: &mut dyn Write) -> Result<(), EmitterError> { @@ -136,6 +137,7 @@ pub struct EmitterConfig { pub color: Color, pub verbosity: Verbosity, pub print_filename: bool, + pub newline_style: NewlineStyle, } impl Default for EmitterConfig { @@ -145,6 +147,7 @@ impl Default for EmitterConfig { color: Color::Auto, verbosity: Verbosity::Normal, print_filename: false, + newline_style: NewlineStyle::default(), } } } @@ -162,7 +165,14 @@ where emitter.emit_header(out)?; for (filename, format_result) in format_report.format_result_as_rc().borrow().iter() { - has_diff |= write_file(filename, &format_result, out, &mut *emitter)?.has_diff; + has_diff |= write_file( + filename, + &format_result, + out, + &mut *emitter, + config.newline_style, + )? + .has_diff; } emitter.emit_footer(out)?; @@ -174,6 +184,7 @@ pub(crate) fn write_file( formatted_result: &FormatResult, out: &mut T, emitter: &mut dyn Emitter, + newline_style: NewlineStyle, ) -> Result where T: Write, @@ -183,8 +194,7 @@ where original_text: formatted_result.original_text(), formatted_text: formatted_result.formatted_text(), }; - - emitter.emit_formatted_file(out, formatted_file) + emitter.emit_formatted_file(out, formatted_file, newline_style) } fn create_emitter(emitter_config: EmitterConfig) -> Box { diff --git a/src/emitter/checkstyle.rs b/src/emitter/checkstyle.rs index c686580ed0a..cb6671de8c5 100644 --- a/src/emitter/checkstyle.rs +++ b/src/emitter/checkstyle.rs @@ -28,6 +28,7 @@ impl Emitter for CheckstyleEmitter { original_text, formatted_text, }: FormattedFile<'_>, + _newline_style: NewlineStyle, ) -> Result { const CONTEXT_SIZE: usize = 0; let diff = make_diff(original_text, formatted_text, CONTEXT_SIZE); @@ -107,6 +108,7 @@ mod tests { original_text: &bin_original.join("\n"), formatted_text: &bin_formatted.join("\n"), }, + NewlineStyle::default(), ) .unwrap(); let _ = emitter @@ -117,6 +119,7 @@ mod tests { original_text: &lib_original.join("\n"), formatted_text: &lib_formatted.join("\n"), }, + NewlineStyle::default(), ) .unwrap(); let _ = emitter.emit_footer(&mut writer); diff --git a/src/emitter/diff.rs b/src/emitter/diff.rs index 995e18b9c22..792316c2aea 100644 --- a/src/emitter/diff.rs +++ b/src/emitter/diff.rs @@ -34,6 +34,7 @@ impl Emitter for DiffEmitter { original_text, formatted_text, }: FormattedFile<'_>, + _newline_style: NewlineStyle, ) -> Result { const CONTEXT_SIZE: usize = 3; let mismatch = make_diff(&original_text, formatted_text, CONTEXT_SIZE); @@ -80,6 +81,7 @@ mod tests { original_text: "fn empty() {}\n", formatted_text: "fn empty() {}\n", }, + NewlineStyle::default(), ) .unwrap(); assert_eq!(result.has_diff, false); @@ -108,6 +110,7 @@ mod tests { original_text: bin_original, formatted_text: bin_formatted, }, + NewlineStyle::default(), ) .unwrap(); let _ = emitter @@ -118,6 +121,7 @@ mod tests { original_text: lib_original, formatted_text: lib_formatted, }, + NewlineStyle::default(), ) .unwrap(); @@ -139,6 +143,7 @@ mod tests { original_text: "fn empty() {}\n", formatted_text: "fn empty() {}\r\n", }, + NewlineStyle::default(), ) .unwrap(); assert_eq!( diff --git a/src/emitter/files.rs b/src/emitter/files.rs index 660059ae0a1..a86fa3d4b4e 100644 --- a/src/emitter/files.rs +++ b/src/emitter/files.rs @@ -24,6 +24,7 @@ impl Emitter for FilesEmitter { original_text, formatted_text, }: FormattedFile<'_>, + _newline_style: NewlineStyle, ) -> Result { // Write text directly over original file if there is a diff. let filename = match filename { diff --git a/src/emitter/json.rs b/src/emitter/json.rs index b2ba30b2589..6b40893067a 100644 --- a/src/emitter/json.rs +++ b/src/emitter/json.rs @@ -5,6 +5,10 @@ use rustfmt_diff::{make_diff, DiffLine, Mismatch}; use serde::Serialize; use serde_json::to_string as to_json_string; +use crate::formatting::newline_style::{ + apply_newline_style, get_newline_string, get_newline_string_of_text, +}; + #[derive(Debug, Default)] pub struct JsonEmitter { mismatched_files: Vec, @@ -40,13 +44,17 @@ impl Emitter for JsonEmitter { original_text, formatted_text, }: FormattedFile<'_>, + newline_style: NewlineStyle, ) -> Result { const CONTEXT_SIZE: usize = 0; let diff = make_diff(original_text, formatted_text, CONTEXT_SIZE); let has_diff = !diff.is_empty(); + let mut formatted_text_string = String::from(formatted_text); + apply_newline_style(newline_style, &mut formatted_text_string, &original_text); + if has_diff { - self.add_misformatted_file(filename, diff)?; + self.add_misformatted_file(filename, diff, newline_style, &original_text)?; } Ok(EmitterResult { has_diff }) @@ -58,8 +66,13 @@ impl JsonEmitter { &mut self, filename: &FileName, diff: Vec, + newline_style: NewlineStyle, + raw_input_text: &str, ) -> Result<(), EmitterError> { let mut mismatches = vec![]; + let expected_newline = get_newline_string(newline_style, raw_input_text); + let origin_newline = get_newline_string_of_text(raw_input_text); + for mismatch in diff { let original_begin_line = mismatch.line_number_orig; let expected_begin_line = mismatch.line_number; @@ -76,13 +89,13 @@ impl JsonEmitter { expected_end_line = expected_begin_line + expected_line_counter; expected_line_counter += 1; expected.push_str(&msg); - expected.push('\n'); + expected.push_str(&expected_newline); } DiffLine::Resulting(msg) => { original_end_line = original_begin_line + original_line_counter; original_line_counter += 1; original.push_str(&msg); - original.push('\n'); + original.push_str(&origin_newline); } DiffLine::Context(_) => continue, } @@ -141,7 +154,12 @@ mod tests { }; emitter - .add_misformatted_file(&FileName::Real(PathBuf::from(file)), vec![mismatch]) + .add_misformatted_file( + &FileName::Real(PathBuf::from(file)), + vec![mismatch], + NewlineStyle::default(), + &mismatched_file.mismatches[0].original, + ) .unwrap(); assert_eq!(emitter.mismatched_files.len(), 1); @@ -186,7 +204,12 @@ mod tests { }; emitter - .add_misformatted_file(&FileName::Real(PathBuf::from(file)), vec![mismatch]) + .add_misformatted_file( + &FileName::Real(PathBuf::from(file)), + vec![mismatch], + NewlineStyle::default(), + &mismatched_file.mismatches[0].original, + ) .unwrap(); assert_eq!(emitter.mismatched_files.len(), 1); @@ -206,6 +229,7 @@ mod tests { original_text: "fn empty() {}\n", formatted_text: "fn empty() {}\n", }, + NewlineStyle::default(), ) .unwrap(); let _ = emitter.emit_footer(&mut writer); @@ -253,6 +277,7 @@ mod tests { original_text: &original.join("\n"), formatted_text: &formatted.join("\n"), }, + NewlineStyle::default(), ) .unwrap(); let _ = emitter.emit_footer(&mut writer); @@ -305,6 +330,7 @@ mod tests { original_text: &bin_original.join("\n"), formatted_text: &bin_formatted.join("\n"), }, + NewlineStyle::default(), ) .unwrap(); let _ = emitter @@ -315,6 +341,7 @@ mod tests { original_text: &lib_original.join("\n"), formatted_text: &lib_formatted.join("\n"), }, + NewlineStyle::default(), ) .unwrap(); let _ = emitter.emit_footer(&mut writer); @@ -345,4 +372,169 @@ mod tests { let exp_json = to_json_string(&vec![exp_bin, exp_lib]).unwrap(); assert_eq!(&writer[..], format!("{}\n", exp_json).as_bytes()); } + + #[test] + fn emits_empty_array_on_no_diffs_newline_windows_to_windows() { + let mut writer = Vec::new(); + let mut emitter = JsonEmitter::default(); + let _ = emitter.emit_header(&mut writer); + let result = emitter + .emit_formatted_file( + &mut writer, + FormattedFile { + filename: &FileName::Real(PathBuf::from("src/lib.rs")), + original_text: "fn main() {\r\n x = y;\r\n}\r\n", + formatted_text: "fn main() {\r\n x = y;\r\n}\r\n", + }, + NewlineStyle::Windows, + ) + .unwrap(); + let _ = emitter.emit_footer(&mut writer); + assert_eq!(result.has_diff, false); + assert_eq!(&writer[..], b"[]\n"); + } + + #[test] + fn emits_empty_array_on_no_diffs_newline_unix_to_windows() { + let mut writer = Vec::new(); + let mut emitter = JsonEmitter::default(); + let _ = emitter.emit_header(&mut writer); + let result = emitter + .emit_formatted_file( + &mut writer, + FormattedFile { + filename: &FileName::Real(PathBuf::from("src/lib.rs")), + original_text: "fn main() {\n x = y;\n}\n", + formatted_text: "fn main() {\r\n x = y;\r\n}\r\n", + }, + NewlineStyle::Windows, + ) + .unwrap(); + let _ = emitter.emit_footer(&mut writer); + assert_eq!(result.has_diff, false); + assert_eq!(&writer[..], b"[]\n"); + } + + #[test] + fn emits_empty_array_on_no_diffs_newline_windows_to_unix() { + let mut writer = Vec::new(); + let mut emitter = JsonEmitter::default(); + let _ = emitter.emit_header(&mut writer); + let result = emitter + .emit_formatted_file( + &mut writer, + FormattedFile { + filename: &FileName::Real(PathBuf::from("src/lib.rs")), + original_text: "fn main() {\r\n x = y;\r\n}\r\n", + formatted_text: "fn main() {\n x = y;\n}\n", + }, + NewlineStyle::Unix, + ) + .unwrap(); + let _ = emitter.emit_footer(&mut writer); + assert_eq!(result.has_diff, false); + assert_eq!(&writer[..], b"[]\n"); + } + + #[test] + fn emits_array_with_with_diffs_newline_windows_to_windows() { + let file_name = "src/bin.rs"; + let mut writer = Vec::new(); + let mut emitter = JsonEmitter::default(); + let _ = emitter.emit_header(&mut writer); + let result = emitter + .emit_formatted_file( + &mut writer, + FormattedFile { + filename: &FileName::Real(PathBuf::from(file_name)), + original_text: "fn main() {\r\nx = y;\r\n}\r\n", + formatted_text: "fn main() {\r\n x = y;\r\n}\r\n", + }, + NewlineStyle::Windows, + ) + .unwrap(); + let _ = emitter.emit_footer(&mut writer); + let exp_json = to_json_string(&vec![MismatchedFile { + name: String::from(file_name), + mismatches: vec![MismatchedBlock { + original_begin_line: 2, + original_end_line: 2, + expected_begin_line: 2, + expected_end_line: 2, + original: String::from("x = y;\r\n"), + expected: String::from(" x = y;\r\n"), + }], + }]) + .unwrap(); + assert_eq!(result.has_diff, true); + assert_eq!(&writer[..], format!("{}\n", exp_json).as_bytes()); + } + + #[test] + fn emits_array_with_with_diffs_newline_unix_to_windows() { + let file_name = "src/bin.rs"; + let mut writer = Vec::new(); + let mut emitter = JsonEmitter::default(); + let _ = emitter.emit_header(&mut writer); + let result = emitter + .emit_formatted_file( + &mut writer, + FormattedFile { + filename: &FileName::Real(PathBuf::from(file_name)), + original_text: "fn main() {\nx = y;\n}\n", + formatted_text: "fn main() {\r\n x = y;\r\n}\r\n", + }, + NewlineStyle::Windows, + ) + .unwrap(); + let _ = emitter.emit_footer(&mut writer); + let exp_json = to_json_string(&vec![MismatchedFile { + name: String::from(file_name), + mismatches: vec![MismatchedBlock { + original_begin_line: 2, + original_end_line: 2, + expected_begin_line: 2, + expected_end_line: 2, + original: String::from("x = y;\n"), + expected: String::from(" x = y;\r\n"), + }], + }]) + .unwrap(); + assert_eq!(result.has_diff, true); + assert_eq!(&writer[..], format!("{}\n", exp_json).as_bytes()); + } + + #[test] + fn emits_array_with_with_diffs_newline_windows_to_unix() { + let file_name = "src/bin.rs"; + let mut writer = Vec::new(); + let mut emitter = JsonEmitter::default(); + let _ = emitter.emit_header(&mut writer); + let result = emitter + .emit_formatted_file( + &mut writer, + FormattedFile { + filename: &FileName::Real(PathBuf::from(file_name)), + original_text: "fn main() {\r\nx = y;\r\n}\r\n", + formatted_text: "fn main() {\n x = y;\n}\n", + }, + NewlineStyle::Unix, + ) + .unwrap(); + let _ = emitter.emit_footer(&mut writer); + let exp_json = to_json_string(&vec![MismatchedFile { + name: String::from(file_name), + mismatches: vec![MismatchedBlock { + original_begin_line: 2, + original_end_line: 2, + expected_begin_line: 2, + expected_end_line: 2, + original: String::from("x = y;\r\n"), + expected: String::from(" x = y;\n"), + }], + }]) + .unwrap(); + assert_eq!(result.has_diff, true); + assert_eq!(&writer[..], format!("{}\n", exp_json).as_bytes()); + } } diff --git a/src/emitter/modified_lines.rs b/src/emitter/modified_lines.rs index 70be0bcd193..80091d907f6 100644 --- a/src/emitter/modified_lines.rs +++ b/src/emitter/modified_lines.rs @@ -13,6 +13,7 @@ impl Emitter for ModifiedLinesEmitter { formatted_text, .. }: FormattedFile<'_>, + _newline_style: NewlineStyle, ) -> Result { const CONTEXT_SIZE: usize = 0; let mismatch = make_diff(original_text, formatted_text, CONTEXT_SIZE); diff --git a/src/emitter/stdout.rs b/src/emitter/stdout.rs index e2cd1fd19bb..e18829af941 100644 --- a/src/emitter/stdout.rs +++ b/src/emitter/stdout.rs @@ -25,6 +25,7 @@ impl Emitter for StdoutEmitter { formatted_text, .. }: FormattedFile<'_>, + _newline_style: NewlineStyle, ) -> Result { if self.verbosity != Verbosity::Quiet { writeln!(output, "{}:\n", filename)?; diff --git a/src/formatting.rs b/src/formatting.rs index 0a78dc4a930..0d171c1c95f 100644 --- a/src/formatting.rs +++ b/src/formatting.rs @@ -39,7 +39,7 @@ mod macros; mod matches; mod missed_spans; pub(crate) mod modules; -mod newline_style; +pub(crate) mod newline_style; mod overflow; mod pairs; mod patterns; diff --git a/src/formatting/newline_style.rs b/src/formatting/newline_style.rs index 97c4fc16d6f..4fe49788851 100644 --- a/src/formatting/newline_style.rs +++ b/src/formatting/newline_style.rs @@ -1,5 +1,10 @@ use crate::NewlineStyle; +const LINE_FEED: char = '\n'; +const CARRIAGE_RETURN: char = '\r'; +const WINDOWS_NEWLINE: &str = "\r\n"; +const UNIX_NEWLINE: &str = "\n"; + /// 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. @@ -17,6 +22,22 @@ pub(crate) fn apply_newline_style( } } +/// Get the newline string based on the requested style and the original text +pub(crate) fn get_newline_string(newline_style: NewlineStyle, raw_input_text: &str) -> String { + match effective_newline_style(newline_style, raw_input_text) { + EffectiveNewlineStyle::Windows => WINDOWS_NEWLINE.to_string(), + _ => UNIX_NEWLINE.to_string(), + } +} + +/// Get the newline string based on input original text +pub(crate) fn get_newline_string_of_text(raw_input_text: &str) -> String { + match auto_detect_newline_style(raw_input_text) { + EffectiveNewlineStyle::Windows => WINDOWS_NEWLINE.to_string(), + _ => UNIX_NEWLINE.to_string(), + } +} + #[derive(Debug, Copy, Clone, PartialEq, Eq)] enum EffectiveNewlineStyle { Windows, @@ -35,11 +56,6 @@ fn effective_newline_style( } } -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 { diff --git a/src/rustfmt/main.rs b/src/rustfmt/main.rs index 571eb072b2c..9f1df105d60 100644 --- a/src/rustfmt/main.rs +++ b/src/rustfmt/main.rs @@ -2,6 +2,9 @@ #[macro_use] extern crate lazy_static; +#[macro_use] +extern crate log; + use std::collections::HashMap; use std::env; use std::fmt; @@ -13,6 +16,8 @@ use anyhow::{format_err, Result}; use structopt::StructOpt; use thiserror::Error; +use rustfmt_nightly::NewlineStyle; + use rustfmt_nightly::{ emitter::{emit_format_report, EmitMode, EmitterConfig, Verbosity}, format_inputs, load_config, CliOptions, Config, Edition, FileLines, FileName, @@ -167,7 +172,11 @@ impl Opt { } } - fn emitter_config(&self, default_emit_mode: EmitMode) -> EmitterConfig { + fn emitter_config( + &self, + default_emit_mode: EmitMode, + newline_style: NewlineStyle, + ) -> EmitterConfig { let emit_mode = if self.check { EmitMode::Diff } else { @@ -177,6 +186,7 @@ impl Opt { emit_mode, verbosity: self.verbosity(), print_filename: self.files_with_diff, + newline_style: newline_style, ..EmitterConfig::default() } } @@ -456,7 +466,11 @@ fn format_string(input: String, opt: Opt) -> Result { } } - let has_diff = emit_format_report(report, out, opt.emitter_config(EmitMode::Stdout))?; + let has_diff = emit_format_report( + report, + out, + opt.emitter_config(EmitMode::Stdout, config.newline_style()), + )?; Ok(if opt.check && has_diff { 1 } else { 0 }) } @@ -592,7 +606,7 @@ fn format(opt: Opt) -> Result { let has_diff = emit_format_report( format_report, &mut stdout(), - opt.emitter_config(EmitMode::Files), + opt.emitter_config(EmitMode::Files, default_config.newline_style()), )?; Ok(if opt.check && has_diff { 1 } else { 0 })