| 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}; |
| |
| #[derive(Debug, Default)] |
| pub(crate) struct JsonEmitter { |
| mismatched_files: Vec<MismatchedFile>, |
| } |
| |
| #[derive(Debug, Default, PartialEq, 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, PartialEq, Serialize)] |
| struct MismatchedFile { |
| name: String, |
| mismatches: Vec<MismatchedBlock>, |
| } |
| |
| impl Emitter for JsonEmitter { |
| fn emit_footer(&self, output: &mut dyn Write) -> Result<(), io::Error> { |
| writeln!(output, "{}", &to_json_string(&self.mismatched_files)?) |
| } |
| |
| 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 diff = make_diff(original_text, formatted_text, CONTEXT_SIZE); |
| let has_diff = !diff.is_empty(); |
| |
| if has_diff { |
| self.add_misformatted_file(filename, diff)?; |
| } |
| |
| Ok(EmitterResult { has_diff }) |
| } |
| } |
| |
| impl JsonEmitter { |
| fn add_misformatted_file( |
| &mut self, |
| filename: &FileName, |
| diff: Vec<Mismatch>, |
| ) -> Result<(), io::Error> { |
| 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 = String::new(); |
| let mut expected = String::new(); |
| |
| for line in mismatch.lines { |
| match line { |
| DiffLine::Expected(msg) => { |
| expected_end_line = expected_begin_line + expected_line_counter; |
| expected_line_counter += 1; |
| expected.push_str(&msg); |
| expected.push('\n'); |
| } |
| DiffLine::Resulting(msg) => { |
| original_end_line = original_begin_line + original_line_counter; |
| original_line_counter += 1; |
| original.push_str(&msg); |
| original.push('\n'); |
| } |
| DiffLine::Context(_) => continue, |
| } |
| } |
| |
| mismatches.push(MismatchedBlock { |
| original_begin_line, |
| original_end_line, |
| expected_begin_line, |
| expected_end_line, |
| original, |
| expected, |
| }); |
| } |
| self.mismatched_files.push(MismatchedFile { |
| name: format!("{filename}"), |
| mismatches, |
| }); |
| 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 mut emitter = JsonEmitter { |
| mismatched_files: vec![], |
| }; |
| 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 {\n"), |
| expected: String::from("fn Foo<T>()\nwhere\n T: Bar,\n{\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 _ = emitter |
| .add_misformatted_file(&FileName::Real(PathBuf::from(file)), vec![mismatch]) |
| .unwrap(); |
| |
| assert_eq!(emitter.mismatched_files.len(), 1); |
| assert_eq!(emitter.mismatched_files[0], mismatched_file); |
| } |
| |
| #[test] |
| fn context_lines_ignored() { |
| let mut emitter = JsonEmitter { |
| mismatched_files: vec![], |
| }; |
| 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)> {\n", |
| ), |
| expected: String::from( |
| "fn foo(_x: &u64) -> Option<&(dyn ::std::error::Error + 'static)> {\n", |
| ), |
| }], |
| }; |
| 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 _ = emitter |
| .add_misformatted_file(&FileName::Real(PathBuf::from(file)), vec![mismatch]) |
| .unwrap(); |
| |
| assert_eq!(emitter.mismatched_files.len(), 1); |
| assert_eq!(emitter.mismatched_files[0], mismatched_file); |
| } |
| |
| #[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[..], "[]\n".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(&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("println!(\"Hello, world!\");\n"), |
| expected: String::from(" println!(\"Hello, world!\");\n"), |
| }, |
| 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}\n", |
| ), |
| expected: String::from( |
| " #[test]\n fn it_works() {\n assert_eq!(2 + 2, 4);\n }\n", |
| ), |
| }, |
| ], |
| }]) |
| .unwrap(); |
| assert_eq!(result.has_diff, true); |
| assert_eq!(&writer[..], format!("{exp_json}\n").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 = 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!\");\n"), |
| expected: String::from(" println!(\"Hello, world!\");\n"), |
| }], |
| }; |
| |
| let exp_lib = 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!\");\n"), |
| expected: String::from(" println!(\"Greetings!\");\n"), |
| }], |
| }; |
| |
| let exp_json = to_json_string(&vec![exp_bin, exp_lib]).unwrap(); |
| assert_eq!(&writer[..], format!("{exp_json}\n").as_bytes()); |
| } |
| } |