rustfmt_nightly/emitter/
checkstyle.rs

1use self::xml::XmlEscaped;
2use super::*;
3use crate::rustfmt_diff::{DiffLine, Mismatch, make_diff};
4
5mod xml;
6
7#[derive(Debug, Default)]
8pub(crate) struct CheckstyleEmitter;
9
10impl Emitter for CheckstyleEmitter {
11    fn emit_header(&self, output: &mut dyn Write) -> Result<(), io::Error> {
12        writeln!(output, r#"<?xml version="1.0" encoding="utf-8"?>"#)?;
13        write!(output, r#"<checkstyle version="4.3">"#)?;
14        Ok(())
15    }
16
17    fn emit_footer(&self, output: &mut dyn Write) -> Result<(), io::Error> {
18        writeln!(output, "</checkstyle>")
19    }
20
21    fn emit_formatted_file(
22        &mut self,
23        output: &mut dyn Write,
24        FormattedFile {
25            filename,
26            original_text,
27            formatted_text,
28        }: FormattedFile<'_>,
29    ) -> Result<EmitterResult, io::Error> {
30        const CONTEXT_SIZE: usize = 0;
31        let diff = make_diff(original_text, formatted_text, CONTEXT_SIZE);
32        output_checkstyle_file(output, filename, diff)?;
33        Ok(EmitterResult::default())
34    }
35}
36
37pub(crate) fn output_checkstyle_file<T>(
38    mut writer: T,
39    filename: &FileName,
40    diff: Vec<Mismatch>,
41) -> Result<(), io::Error>
42where
43    T: Write,
44{
45    write!(writer, r#"<file name="{filename}">"#)?;
46    for mismatch in diff {
47        let begin_line = mismatch.line_number;
48        let mut current_line;
49        let mut line_counter = 0;
50        for line in mismatch.lines {
51            // Do nothing with `DiffLine::Context` and `DiffLine::Resulting`.
52            if let DiffLine::Expected(message) = line {
53                current_line = begin_line + line_counter;
54                line_counter += 1;
55                write!(
56                    writer,
57                    r#"<error line="{}" severity="warning" message="Should be `{}`" />"#,
58                    current_line,
59                    XmlEscaped(&message)
60                )?;
61            }
62        }
63    }
64    write!(writer, "</file>")?;
65    Ok(())
66}
67
68#[cfg(test)]
69mod tests {
70    use super::*;
71    use std::path::PathBuf;
72
73    #[test]
74    fn emits_empty_record_on_file_with_no_mismatches() {
75        let file_name = "src/well_formatted.rs";
76        let mut writer = Vec::new();
77        let _ = output_checkstyle_file(
78            &mut writer,
79            &FileName::Real(PathBuf::from(file_name)),
80            vec![],
81        );
82        assert_eq!(
83            &writer[..],
84            format!(r#"<file name="{file_name}"></file>"#).as_bytes()
85        );
86    }
87
88    // https://github.com/rust-lang/rustfmt/issues/1636
89    #[test]
90    fn emits_single_xml_tree_containing_all_files() {
91        let bin_file = "src/bin.rs";
92        let bin_original = ["fn main() {", "println!(\"Hello, world!\");", "}"];
93        let bin_formatted = ["fn main() {", "    println!(\"Hello, world!\");", "}"];
94        let lib_file = "src/lib.rs";
95        let lib_original = ["fn greet() {", "println!(\"Greetings!\");", "}"];
96        let lib_formatted = ["fn greet() {", "    println!(\"Greetings!\");", "}"];
97        let mut writer = Vec::new();
98        let mut emitter = CheckstyleEmitter::default();
99        let _ = emitter.emit_header(&mut writer);
100        let _ = emitter
101            .emit_formatted_file(
102                &mut writer,
103                FormattedFile {
104                    filename: &FileName::Real(PathBuf::from(bin_file)),
105                    original_text: &bin_original.join("\n"),
106                    formatted_text: &bin_formatted.join("\n"),
107                },
108            )
109            .unwrap();
110        let _ = emitter
111            .emit_formatted_file(
112                &mut writer,
113                FormattedFile {
114                    filename: &FileName::Real(PathBuf::from(lib_file)),
115                    original_text: &lib_original.join("\n"),
116                    formatted_text: &lib_formatted.join("\n"),
117                },
118            )
119            .unwrap();
120        let _ = emitter.emit_footer(&mut writer);
121        let exp_bin_xml = [
122            format!(r#"<file name="{}">"#, bin_file),
123            format!(
124                r#"<error line="2" severity="warning" message="Should be `{}`" />"#,
125                XmlEscaped(r#"    println!("Hello, world!");"#),
126            ),
127            String::from("</file>"),
128        ];
129        let exp_lib_xml = [
130            format!(r#"<file name="{}">"#, lib_file),
131            format!(
132                r#"<error line="2" severity="warning" message="Should be `{}`" />"#,
133                XmlEscaped(r#"    println!("Greetings!");"#),
134            ),
135            String::from("</file>"),
136        ];
137        assert_eq!(
138            String::from_utf8(writer).unwrap(),
139            [
140                r#"<?xml version="1.0" encoding="utf-8"?>"#,
141                "\n",
142                r#"<checkstyle version="4.3">"#,
143                &format!("{}{}", exp_bin_xml.join(""), exp_lib_xml.join("")),
144                "</checkstyle>\n",
145            ]
146            .join(""),
147        );
148    }
149}