syncable_cli/analyzer/hadolint/formatter/
checkstyle.rs1use crate::analyzer::hadolint::formatter::Formatter;
6use crate::analyzer::hadolint::lint::LintResult;
7use crate::analyzer::hadolint::types::Severity;
8use std::io::Write;
9
10#[derive(Debug, Clone, Default)]
12pub struct CheckstyleFormatter;
13
14impl CheckstyleFormatter {
15 pub fn new() -> Self {
17 Self
18 }
19}
20
21fn escape_xml(s: &str) -> String {
22 s.replace('&', "&")
23 .replace('<', "<")
24 .replace('>', ">")
25 .replace('"', """)
26 .replace('\'', "'")
27}
28
29fn severity_to_checkstyle(severity: Severity) -> &'static str {
30 match severity {
31 Severity::Error => "error",
32 Severity::Warning => "warning",
33 Severity::Info => "info",
34 Severity::Style => "info",
35 Severity::Ignore => "info",
36 }
37}
38
39impl Formatter for CheckstyleFormatter {
40 fn format<W: Write>(
41 &self,
42 result: &LintResult,
43 filename: &str,
44 writer: &mut W,
45 ) -> std::io::Result<()> {
46 writeln!(writer, r#"<?xml version="1.0" encoding="UTF-8"?>"#)?;
47 writeln!(writer, r#"<checkstyle version="4.3">"#)?;
48
49 if !result.failures.is_empty() {
50 writeln!(writer, r#" <file name="{}">"#, escape_xml(filename))?;
51
52 for failure in &result.failures {
53 let col_attr = failure
54 .column
55 .map(|c| format!(r#" column="{}""#, c))
56 .unwrap_or_default();
57
58 writeln!(
59 writer,
60 r#" <error line="{}"{} severity="{}" message="{}" source="{}"/>"#,
61 failure.line,
62 col_attr,
63 severity_to_checkstyle(failure.severity),
64 escape_xml(&failure.message),
65 escape_xml(&failure.code.to_string())
66 )?;
67 }
68
69 writeln!(writer, " </file>")?;
70 }
71
72 writeln!(writer, "</checkstyle>")
73 }
74}
75
76#[cfg(test)]
77mod tests {
78 use super::*;
79 use crate::analyzer::hadolint::types::CheckFailure;
80
81 #[test]
82 fn test_checkstyle_output() {
83 let mut result = LintResult::new();
84 result.failures.push(CheckFailure::new(
85 "DL3008",
86 Severity::Warning,
87 "Pin versions in apt get install",
88 5,
89 ));
90
91 let formatter = CheckstyleFormatter::new();
92 let output = formatter.format_to_string(&result, "Dockerfile");
93
94 assert!(output.contains("<?xml version"));
95 assert!(output.contains("<checkstyle"));
96 assert!(output.contains(r#"<file name="Dockerfile">"#));
97 assert!(output.contains(r#"line="5""#));
98 assert!(output.contains(r#"severity="warning""#));
99 assert!(output.contains("DL3008"));
100 }
101
102 #[test]
103 fn test_xml_escaping() {
104 assert_eq!(escape_xml("a < b"), "a < b");
105 assert_eq!(escape_xml("a & b"), "a & b");
106 assert_eq!(escape_xml(r#"a "b""#), "a "b"");
107 }
108}