#![allow(clippy::doc_markdown)]
use std::collections::BTreeMap;
use std::io::{self, Write};
use crate::output::offenders::{OffenderRecord, TOOL_ID, warn_non_utf8_path};
pub fn write_checkstyle<W: Write>(offenders: &[OffenderRecord], mut writer: W) -> io::Result<()> {
writer.write_all(b"<?xml version=\"1.0\" encoding=\"UTF-8\"?>\n")?;
let mut by_file: BTreeMap<&str, Vec<&OffenderRecord>> = BTreeMap::new();
for record in offenders {
let Some(path_str) = warn_non_utf8_path("Checkstyle", &record.path) else {
continue;
};
by_file.entry(path_str).or_default().push(record);
}
if by_file.is_empty() {
writer.write_all(b"<checkstyle version=\"4.3\"/>\n")?;
return Ok(());
}
writer.write_all(b"<checkstyle version=\"4.3\">\n")?;
for (path_str, records) in by_file {
writeln!(writer, " <file name=\"{}\">", XmlAttr(path_str))?;
for record in records {
write_error(&mut writer, record)?;
}
writer.write_all(b" </file>\n")?;
}
writer.write_all(b"</checkstyle>\n")
}
fn write_error<W: Write>(writer: &mut W, record: &OffenderRecord) -> io::Result<()> {
let message = record.default_message();
write!(writer, " <error line=\"{}\"", record.start_line.max(1))?;
if let Some(col) = record.start_col {
write!(writer, " column=\"{}\"", col.max(1))?;
}
writeln!(
writer,
" severity=\"{}\" message=\"{}\" source=\"{}.{}\"/>",
record.severity.as_str(),
XmlAttr(&message),
TOOL_ID,
XmlAttr(&record.metric),
)
}
struct XmlAttr<'a>(&'a str);
impl std::fmt::Display for XmlAttr<'_> {
fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
let mut buf = [0u8; 4];
for ch in self.0.chars() {
let escaped: &str = match ch {
'&' => "&",
'<' => "<",
'>' => ">",
'"' => """,
'\'' => "'",
'\t' => "	",
'\n' => "
",
'\r' => "
",
c if (c as u32) < 0x20 || ((c as u32) & 0xFFFF) >= 0xFFFE => "?",
c => c.encode_utf8(&mut buf),
};
f.write_str(escaped)?;
}
Ok(())
}
}
#[cfg(test)]
#[allow(
clippy::float_cmp,
clippy::cast_precision_loss,
clippy::cast_possible_truncation,
clippy::cast_sign_loss,
clippy::similar_names,
clippy::doc_markdown,
clippy::needless_raw_string_hashes,
clippy::too_many_lines
)]
#[path = "checkstyle_tests.rs"]
mod tests;