|  | use super::*; | 
|  | use crate::rustfmt_diff::{make_diff, DiffLine, Mismatch}; | 
|  | use serde::Serialize; | 
|  | use serde_json::to_string as to_json_string; | 
|  | use std::io::{self, Write}; | 
|  | use std::path::Path; | 
|  |  | 
|  | #[derive(Debug, Default)] | 
|  | pub(crate) struct JsonEmitter { | 
|  | num_files: u32, | 
|  | } | 
|  |  | 
|  | #[derive(Debug, Default, Serialize)] | 
|  | struct MismatchedBlock { | 
|  | original_begin_line: u32, | 
|  | original_end_line: u32, | 
|  | expected_begin_line: u32, | 
|  | expected_end_line: u32, | 
|  | original: String, | 
|  | expected: String, | 
|  | } | 
|  |  | 
|  | #[derive(Debug, Default, Serialize)] | 
|  | struct MismatchedFile { | 
|  | name: String, | 
|  | mismatches: Vec<MismatchedBlock>, | 
|  | } | 
|  |  | 
|  | impl Emitter for JsonEmitter { | 
|  | fn emit_header(&self, output: &mut dyn Write) -> Result<(), io::Error> { | 
|  | write!(output, "[")?; | 
|  | Ok(()) | 
|  | } | 
|  |  | 
|  | fn emit_footer(&self, output: &mut dyn Write) -> Result<(), io::Error> { | 
|  | write!(output, "]")?; | 
|  | Ok(()) | 
|  | } | 
|  |  | 
|  | fn emit_formatted_file( | 
|  | &mut self, | 
|  | output: &mut dyn Write, | 
|  | FormattedFile { | 
|  | filename, | 
|  | original_text, | 
|  | formatted_text, | 
|  | }: FormattedFile<'_>, | 
|  | ) -> Result<EmitterResult, io::Error> { | 
|  | const CONTEXT_SIZE: usize = 0; | 
|  | let filename = ensure_real_path(filename); | 
|  | let diff = make_diff(original_text, formatted_text, CONTEXT_SIZE); | 
|  | let has_diff = !diff.is_empty(); | 
|  |  | 
|  | if has_diff { | 
|  | output_json_file(output, filename, diff, self.num_files)?; | 
|  | self.num_files += 1; | 
|  | } | 
|  |  | 
|  | Ok(EmitterResult { has_diff }) | 
|  | } | 
|  | } | 
|  |  | 
|  | fn output_json_file<T>( | 
|  | mut writer: T, | 
|  | filename: &Path, | 
|  | diff: Vec<Mismatch>, | 
|  | num_emitted_files: u32, | 
|  | ) -> Result<(), io::Error> | 
|  | where | 
|  | T: Write, | 
|  | { | 
|  | let mut mismatches = vec![]; | 
|  | for mismatch in diff { | 
|  | let original_begin_line = mismatch.line_number_orig; | 
|  | let expected_begin_line = mismatch.line_number; | 
|  | let mut original_end_line = original_begin_line; | 
|  | let mut expected_end_line = expected_begin_line; | 
|  | let mut original_line_counter = 0; | 
|  | let mut expected_line_counter = 0; | 
|  | let mut original_lines = vec![]; | 
|  | let mut expected_lines = vec![]; | 
|  |  | 
|  | for line in mismatch.lines { | 
|  | match line { | 
|  | DiffLine::Expected(msg) => { | 
|  | expected_end_line = expected_begin_line + expected_line_counter; | 
|  | expected_line_counter += 1; | 
|  | expected_lines.push(msg) | 
|  | } | 
|  | DiffLine::Resulting(msg) => { | 
|  | original_end_line = original_begin_line + original_line_counter; | 
|  | original_line_counter += 1; | 
|  | original_lines.push(msg) | 
|  | } | 
|  | DiffLine::Context(_) => continue, | 
|  | } | 
|  | } | 
|  |  | 
|  | mismatches.push(MismatchedBlock { | 
|  | original_begin_line, | 
|  | original_end_line, | 
|  | expected_begin_line, | 
|  | expected_end_line, | 
|  | original: original_lines.join("\n"), | 
|  | expected: expected_lines.join("\n"), | 
|  | }); | 
|  | } | 
|  | let json = to_json_string(&MismatchedFile { | 
|  | name: String::from(filename.to_str().unwrap()), | 
|  | mismatches, | 
|  | })?; | 
|  | let prefix = if num_emitted_files > 0 { "," } else { "" }; | 
|  | write!(writer, "{}{}", prefix, &json)?; | 
|  | Ok(()) | 
|  | } | 
|  |  | 
|  | #[cfg(test)] | 
|  | mod tests { | 
|  | use super::*; | 
|  | use crate::FileName; | 
|  | use std::path::PathBuf; | 
|  |  | 
|  | #[test] | 
|  | fn expected_line_range_correct_when_single_line_split() { | 
|  | let file = "foo/bar.rs"; | 
|  | let mismatched_file = MismatchedFile { | 
|  | name: String::from(file), | 
|  | mismatches: vec![MismatchedBlock { | 
|  | original_begin_line: 79, | 
|  | original_end_line: 79, | 
|  | expected_begin_line: 79, | 
|  | expected_end_line: 82, | 
|  | original: String::from("fn Foo<T>() where T: Bar {"), | 
|  | expected: String::from("fn Foo<T>()\nwhere\n    T: Bar,\n{"), | 
|  | }], | 
|  | }; | 
|  | let mismatch = Mismatch { | 
|  | line_number: 79, | 
|  | line_number_orig: 79, | 
|  | lines: vec![ | 
|  | DiffLine::Resulting(String::from("fn Foo<T>() where T: Bar {")), | 
|  | DiffLine::Expected(String::from("fn Foo<T>()")), | 
|  | DiffLine::Expected(String::from("where")), | 
|  | DiffLine::Expected(String::from("    T: Bar,")), | 
|  | DiffLine::Expected(String::from("{")), | 
|  | ], | 
|  | }; | 
|  |  | 
|  | let mut writer = Vec::new(); | 
|  | let exp_json = to_json_string(&mismatched_file).unwrap(); | 
|  | let _ = output_json_file(&mut writer, &PathBuf::from(file), vec![mismatch], 0); | 
|  | assert_eq!(&writer[..], format!("{}", exp_json).as_bytes()); | 
|  | } | 
|  |  | 
|  | #[test] | 
|  | fn context_lines_ignored() { | 
|  | let file = "src/lib.rs"; | 
|  | let mismatched_file = MismatchedFile { | 
|  | name: String::from(file), | 
|  | mismatches: vec![MismatchedBlock { | 
|  | original_begin_line: 5, | 
|  | original_end_line: 5, | 
|  | expected_begin_line: 5, | 
|  | expected_end_line: 5, | 
|  | original: String::from( | 
|  | "fn foo(_x: &u64) -> Option<&(dyn::std::error::Error + 'static)> {", | 
|  | ), | 
|  | expected: String::from( | 
|  | "fn foo(_x: &u64) -> Option<&(dyn ::std::error::Error + 'static)> {", | 
|  | ), | 
|  | }], | 
|  | }; | 
|  | let mismatch = Mismatch { | 
|  | line_number: 5, | 
|  | line_number_orig: 5, | 
|  | lines: vec![ | 
|  | DiffLine::Context(String::new()), | 
|  | DiffLine::Resulting(String::from( | 
|  | "fn foo(_x: &u64) -> Option<&(dyn::std::error::Error + 'static)> {", | 
|  | )), | 
|  | DiffLine::Context(String::new()), | 
|  | DiffLine::Expected(String::from( | 
|  | "fn foo(_x: &u64) -> Option<&(dyn ::std::error::Error + 'static)> {", | 
|  | )), | 
|  | DiffLine::Context(String::new()), | 
|  | ], | 
|  | }; | 
|  |  | 
|  | let mut writer = Vec::new(); | 
|  | let exp_json = to_json_string(&mismatched_file).unwrap(); | 
|  | let _ = output_json_file(&mut writer, &PathBuf::from(file), vec![mismatch], 0); | 
|  | assert_eq!(&writer[..], format!("{}", exp_json).as_bytes()); | 
|  | } | 
|  |  | 
|  | #[test] | 
|  | fn emits_empty_array_on_no_diffs() { | 
|  | 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 empty() {}\n", | 
|  | formatted_text: "fn empty() {}\n", | 
|  | }, | 
|  | ) | 
|  | .unwrap(); | 
|  | let _ = emitter.emit_footer(&mut writer); | 
|  | assert_eq!(result.has_diff, false); | 
|  | assert_eq!(&writer[..], "[]".as_bytes()); | 
|  | } | 
|  |  | 
|  | #[test] | 
|  | fn emits_array_with_files_with_diffs() { | 
|  | let file_name = "src/bin.rs"; | 
|  | let original = vec![ | 
|  | "fn main() {", | 
|  | "println!(\"Hello, world!\");", | 
|  | "}", | 
|  | "", | 
|  | "#[cfg(test)]", | 
|  | "mod tests {", | 
|  | "#[test]", | 
|  | "fn it_works() {", | 
|  | "    assert_eq!(2 + 2, 4);", | 
|  | "}", | 
|  | "}", | 
|  | ]; | 
|  | let formatted = vec![ | 
|  | "fn main() {", | 
|  | "    println!(\"Hello, world!\");", | 
|  | "}", | 
|  | "", | 
|  | "#[cfg(test)]", | 
|  | "mod tests {", | 
|  | "    #[test]", | 
|  | "    fn it_works() {", | 
|  | "        assert_eq!(2 + 2, 4);", | 
|  | "    }", | 
|  | "}", | 
|  | ]; | 
|  | 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: &original.join("\n"), | 
|  | formatted_text: &formatted.join("\n"), | 
|  | }, | 
|  | ) | 
|  | .unwrap(); | 
|  | let _ = emitter.emit_footer(&mut writer); | 
|  | let exp_json = to_json_string(&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("println!(\"Hello, world!\");"), | 
|  | expected: String::from("    println!(\"Hello, world!\");"), | 
|  | }, | 
|  | MismatchedBlock { | 
|  | original_begin_line: 7, | 
|  | original_end_line: 10, | 
|  | expected_begin_line: 7, | 
|  | expected_end_line: 10, | 
|  | original: String::from( | 
|  | "#[test]\nfn it_works() {\n    assert_eq!(2 + 2, 4);\n}", | 
|  | ), | 
|  | expected: String::from( | 
|  | "    #[test]\n    fn it_works() {\n        assert_eq!(2 + 2, 4);\n    }", | 
|  | ), | 
|  | }, | 
|  | ], | 
|  | }) | 
|  | .unwrap(); | 
|  | assert_eq!(result.has_diff, true); | 
|  | assert_eq!(&writer[..], format!("[{}]", exp_json).as_bytes()); | 
|  | } | 
|  |  | 
|  | #[test] | 
|  | fn emits_valid_json_with_multiple_files() { | 
|  | let bin_file = "src/bin.rs"; | 
|  | let bin_original = vec!["fn main() {", "println!(\"Hello, world!\");", "}"]; | 
|  | let bin_formatted = vec!["fn main() {", "    println!(\"Hello, world!\");", "}"]; | 
|  | let lib_file = "src/lib.rs"; | 
|  | let lib_original = vec!["fn greet() {", "println!(\"Greetings!\");", "}"]; | 
|  | let lib_formatted = vec!["fn greet() {", "    println!(\"Greetings!\");", "}"]; | 
|  | let mut writer = Vec::new(); | 
|  | let mut emitter = JsonEmitter::default(); | 
|  | let _ = emitter.emit_header(&mut writer); | 
|  | let _ = emitter | 
|  | .emit_formatted_file( | 
|  | &mut writer, | 
|  | FormattedFile { | 
|  | filename: &FileName::Real(PathBuf::from(bin_file)), | 
|  | original_text: &bin_original.join("\n"), | 
|  | formatted_text: &bin_formatted.join("\n"), | 
|  | }, | 
|  | ) | 
|  | .unwrap(); | 
|  | let _ = emitter | 
|  | .emit_formatted_file( | 
|  | &mut writer, | 
|  | FormattedFile { | 
|  | filename: &FileName::Real(PathBuf::from(lib_file)), | 
|  | original_text: &lib_original.join("\n"), | 
|  | formatted_text: &lib_formatted.join("\n"), | 
|  | }, | 
|  | ) | 
|  | .unwrap(); | 
|  | let _ = emitter.emit_footer(&mut writer); | 
|  | let exp_bin_json = to_json_string(&MismatchedFile { | 
|  | name: String::from(bin_file), | 
|  | mismatches: vec![MismatchedBlock { | 
|  | original_begin_line: 2, | 
|  | original_end_line: 2, | 
|  | expected_begin_line: 2, | 
|  | expected_end_line: 2, | 
|  | original: String::from("println!(\"Hello, world!\");"), | 
|  | expected: String::from("    println!(\"Hello, world!\");"), | 
|  | }], | 
|  | }) | 
|  | .unwrap(); | 
|  | let exp_lib_json = to_json_string(&MismatchedFile { | 
|  | name: String::from(lib_file), | 
|  | mismatches: vec![MismatchedBlock { | 
|  | original_begin_line: 2, | 
|  | original_end_line: 2, | 
|  | expected_begin_line: 2, | 
|  | expected_end_line: 2, | 
|  | original: String::from("println!(\"Greetings!\");"), | 
|  | expected: String::from("    println!(\"Greetings!\");"), | 
|  | }], | 
|  | }) | 
|  | .unwrap(); | 
|  | assert_eq!( | 
|  | &writer[..], | 
|  | format!("[{},{}]", exp_bin_json, exp_lib_json).as_bytes() | 
|  | ); | 
|  | } | 
|  | } |