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