]> git.proxmox.com Git - rustc.git/blob - src/tools/rustfmt/src/emitter/json.rs
New upstream version 1.49.0+dfsg1
[rustc.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 }