Skip to main content

rustfmt_nightly/emitter/
json.rs

1use super::*;
2use crate::rustfmt_diff::{DiffLine, Mismatch, make_diff};
3use serde::Serialize;
4use serde_json::to_writer as to_json_writer;
5
6#[derive(Debug, Default)]
7pub(crate) struct JsonEmitter {
8    mismatched_files: Vec<MismatchedFile>,
9}
10
11#[derive(Debug, Default, PartialEq, Serialize)]
12struct MismatchedBlock {
13    original_begin_line: u32,
14    original_end_line: u32,
15    expected_begin_line: u32,
16    expected_end_line: u32,
17    original: String,
18    expected: String,
19}
20
21#[derive(Debug, Default, PartialEq, Serialize)]
22struct MismatchedFile {
23    name: String,
24    mismatches: Vec<MismatchedBlock>,
25}
26
27impl Emitter for JsonEmitter {
28    fn emit_footer(&self, output: &mut dyn Write) -> Result<(), io::Error> {
29        to_json_writer(&mut *output, &self.mismatched_files)?;
30        writeln!(output)
31    }
32
33    fn emit_formatted_file(
34        &mut self,
35        _output: &mut dyn Write,
36        FormattedFile {
37            filename,
38            original_text,
39            formatted_text,
40        }: FormattedFile<'_>,
41    ) -> Result<EmitterResult, io::Error> {
42        const CONTEXT_SIZE: usize = 0;
43        let diff = make_diff(original_text, formatted_text, CONTEXT_SIZE);
44        let has_diff = !diff.is_empty();
45
46        if has_diff {
47            self.add_misformatted_file(filename, diff)?;
48        }
49
50        Ok(EmitterResult { has_diff })
51    }
52}
53
54impl JsonEmitter {
55    fn add_misformatted_file(
56        &mut self,
57        filename: &FileName,
58        diff: Vec<Mismatch>,
59    ) -> Result<(), io::Error> {
60        let mut mismatches = Vec::with_capacity(diff.len());
61        for mismatch in diff {
62            let original_begin_line = mismatch.line_number_orig;
63            let expected_begin_line = mismatch.line_number;
64            let mut original_end_line = original_begin_line;
65            let mut expected_end_line = expected_begin_line;
66            let mut original_line_counter = 0;
67            let mut expected_line_counter = 0;
68            let mut original = String::new();
69            let mut expected = String::new();
70
71            for line in mismatch.lines {
72                match line {
73                    DiffLine::Expected(msg) => {
74                        expected_end_line = expected_begin_line + expected_line_counter;
75                        expected_line_counter += 1;
76                        expected.push_str(&msg);
77                        expected.push('\n');
78                    }
79                    DiffLine::Resulting(msg) => {
80                        original_end_line = original_begin_line + original_line_counter;
81                        original_line_counter += 1;
82                        original.push_str(&msg);
83                        original.push('\n');
84                    }
85                    DiffLine::Context(_) => continue,
86                }
87            }
88
89            mismatches.push(MismatchedBlock {
90                original_begin_line,
91                original_end_line,
92                expected_begin_line,
93                expected_end_line,
94                original,
95                expected,
96            });
97        }
98        self.mismatched_files.push(MismatchedFile {
99            name: format!("{filename}"),
100            mismatches,
101        });
102        Ok(())
103    }
104}
105
106#[cfg(test)]
107mod tests {
108    use super::*;
109    use std::path::PathBuf;
110
111    #[test]
112    fn expected_line_range_correct_when_single_line_split() {
113        let mut emitter = JsonEmitter {
114            mismatched_files: vec![],
115        };
116        let file = "foo/bar.rs";
117        let mismatched_file = MismatchedFile {
118            name: String::from(file),
119            mismatches: vec![MismatchedBlock {
120                original_begin_line: 79,
121                original_end_line: 79,
122                expected_begin_line: 79,
123                expected_end_line: 82,
124                original: String::from("fn Foo<T>() where T: Bar {\n"),
125                expected: String::from("fn Foo<T>()\nwhere\n    T: Bar,\n{\n"),
126            }],
127        };
128        let mismatch = Mismatch {
129            line_number: 79,
130            line_number_orig: 79,
131            lines: vec![
132                DiffLine::Resulting(String::from("fn Foo<T>() where T: Bar {")),
133                DiffLine::Expected(String::from("fn Foo<T>()")),
134                DiffLine::Expected(String::from("where")),
135                DiffLine::Expected(String::from("    T: Bar,")),
136                DiffLine::Expected(String::from("{")),
137            ],
138        };
139
140        let _ = emitter
141            .add_misformatted_file(&FileName::Real(PathBuf::from(file)), vec![mismatch])
142            .unwrap();
143
144        assert_eq!(emitter.mismatched_files.len(), 1);
145        assert_eq!(emitter.mismatched_files[0], mismatched_file);
146    }
147
148    #[test]
149    fn context_lines_ignored() {
150        let mut emitter = JsonEmitter {
151            mismatched_files: vec![],
152        };
153        let file = "src/lib.rs";
154        let mismatched_file = MismatchedFile {
155            name: String::from(file),
156            mismatches: vec![MismatchedBlock {
157                original_begin_line: 5,
158                original_end_line: 5,
159                expected_begin_line: 5,
160                expected_end_line: 5,
161                original: String::from(
162                    "fn foo(_x: &u64) -> Option<&(dyn::std::error::Error + 'static)> {\n",
163                ),
164                expected: String::from(
165                    "fn foo(_x: &u64) -> Option<&(dyn ::std::error::Error + 'static)> {\n",
166                ),
167            }],
168        };
169        let mismatch = Mismatch {
170            line_number: 5,
171            line_number_orig: 5,
172            lines: vec![
173                DiffLine::Context(String::new()),
174                DiffLine::Resulting(String::from(
175                    "fn foo(_x: &u64) -> Option<&(dyn::std::error::Error + 'static)> {",
176                )),
177                DiffLine::Context(String::new()),
178                DiffLine::Expected(String::from(
179                    "fn foo(_x: &u64) -> Option<&(dyn ::std::error::Error + 'static)> {",
180                )),
181                DiffLine::Context(String::new()),
182            ],
183        };
184
185        let _ = emitter
186            .add_misformatted_file(&FileName::Real(PathBuf::from(file)), vec![mismatch])
187            .unwrap();
188
189        assert_eq!(emitter.mismatched_files.len(), 1);
190        assert_eq!(emitter.mismatched_files[0], mismatched_file);
191    }
192
193    #[test]
194    fn emits_empty_array_on_no_diffs() {
195        let mut writer = Vec::new();
196        let mut emitter = JsonEmitter::default();
197        let _ = emitter.emit_header(&mut writer);
198        let result = emitter
199            .emit_formatted_file(
200                &mut writer,
201                FormattedFile {
202                    filename: &FileName::Real(PathBuf::from("src/lib.rs")),
203                    original_text: "fn empty() {}\n",
204                    formatted_text: "fn empty() {}\n",
205                },
206            )
207            .unwrap();
208        let _ = emitter.emit_footer(&mut writer);
209        assert_eq!(result.has_diff, false);
210        assert_eq!(&writer[..], "[]\n".as_bytes());
211    }
212
213    #[test]
214    fn emits_array_with_files_with_diffs() {
215        let file_name = "src/bin.rs";
216        let original = [
217            "fn main() {",
218            "println!(\"Hello, world!\");",
219            "}",
220            "",
221            "#[cfg(test)]",
222            "mod tests {",
223            "#[test]",
224            "fn it_works() {",
225            "    assert_eq!(2 + 2, 4);",
226            "}",
227            "}",
228        ];
229        let formatted = [
230            "fn main() {",
231            "    println!(\"Hello, world!\");",
232            "}",
233            "",
234            "#[cfg(test)]",
235            "mod tests {",
236            "    #[test]",
237            "    fn it_works() {",
238            "        assert_eq!(2 + 2, 4);",
239            "    }",
240            "}",
241        ];
242        let mut writer = Vec::new();
243        let mut emitter = JsonEmitter::default();
244        let _ = emitter.emit_header(&mut writer);
245        let result = emitter
246            .emit_formatted_file(
247                &mut writer,
248                FormattedFile {
249                    filename: &FileName::Real(PathBuf::from(file_name)),
250                    original_text: &original.join("\n"),
251                    formatted_text: &formatted.join("\n"),
252                },
253            )
254            .unwrap();
255        let _ = emitter.emit_footer(&mut writer);
256        let exp_json = serde_json::to_string(&vec![MismatchedFile {
257            name: String::from(file_name),
258            mismatches: vec![
259                MismatchedBlock {
260                    original_begin_line: 2,
261                    original_end_line: 2,
262                    expected_begin_line: 2,
263                    expected_end_line: 2,
264                    original: String::from("println!(\"Hello, world!\");\n"),
265                    expected: String::from("    println!(\"Hello, world!\");\n"),
266                },
267                MismatchedBlock {
268                    original_begin_line: 7,
269                    original_end_line: 10,
270                    expected_begin_line: 7,
271                    expected_end_line: 10,
272                    original: String::from(
273                        "#[test]\nfn it_works() {\n    assert_eq!(2 + 2, 4);\n}\n",
274                    ),
275                    expected: String::from(
276                        "    #[test]\n    fn it_works() {\n        assert_eq!(2 + 2, 4);\n    }\n",
277                    ),
278                },
279            ],
280        }])
281        .unwrap();
282        assert_eq!(result.has_diff, true);
283        assert_eq!(&writer[..], format!("{exp_json}\n").as_bytes());
284    }
285
286    #[test]
287    fn emits_valid_json_with_multiple_files() {
288        let bin_file = "src/bin.rs";
289        let bin_original = ["fn main() {", "println!(\"Hello, world!\");", "}"];
290        let bin_formatted = ["fn main() {", "    println!(\"Hello, world!\");", "}"];
291        let lib_file = "src/lib.rs";
292        let lib_original = ["fn greet() {", "println!(\"Greetings!\");", "}"];
293        let lib_formatted = ["fn greet() {", "    println!(\"Greetings!\");", "}"];
294        let mut writer = Vec::new();
295        let mut emitter = JsonEmitter::default();
296        let _ = emitter.emit_header(&mut writer);
297        let _ = emitter
298            .emit_formatted_file(
299                &mut writer,
300                FormattedFile {
301                    filename: &FileName::Real(PathBuf::from(bin_file)),
302                    original_text: &bin_original.join("\n"),
303                    formatted_text: &bin_formatted.join("\n"),
304                },
305            )
306            .unwrap();
307        let _ = emitter
308            .emit_formatted_file(
309                &mut writer,
310                FormattedFile {
311                    filename: &FileName::Real(PathBuf::from(lib_file)),
312                    original_text: &lib_original.join("\n"),
313                    formatted_text: &lib_formatted.join("\n"),
314                },
315            )
316            .unwrap();
317        let _ = emitter.emit_footer(&mut writer);
318        let exp_bin = MismatchedFile {
319            name: String::from(bin_file),
320            mismatches: vec![MismatchedBlock {
321                original_begin_line: 2,
322                original_end_line: 2,
323                expected_begin_line: 2,
324                expected_end_line: 2,
325                original: String::from("println!(\"Hello, world!\");\n"),
326                expected: String::from("    println!(\"Hello, world!\");\n"),
327            }],
328        };
329
330        let exp_lib = MismatchedFile {
331            name: String::from(lib_file),
332            mismatches: vec![MismatchedBlock {
333                original_begin_line: 2,
334                original_end_line: 2,
335                expected_begin_line: 2,
336                expected_end_line: 2,
337                original: String::from("println!(\"Greetings!\");\n"),
338                expected: String::from("    println!(\"Greetings!\");\n"),
339            }],
340        };
341
342        let exp_json = serde_json::to_string(&vec![exp_bin, exp_lib]).unwrap();
343        assert_eq!(&writer[..], format!("{exp_json}\n").as_bytes());
344    }
345}