Skip to content
Merged
Show file tree
Hide file tree
Changes from 2 commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
6 changes: 5 additions & 1 deletion crates/biome_cli/src/cli_options.rs
Original file line number Diff line number Diff line change
Expand Up @@ -56,7 +56,7 @@ pub struct CliOptions {
/// Allows to change how diagnostics and summary are reported.
#[bpaf(
long("reporter"),
argument("json|json-pretty|github|junit|summary|gitlab"),
argument("json|json-pretty|github|junit|summary|gitlab|checkstyle"),
fallback(CliReporter::default())
)]
pub reporter: CliReporter,
Expand Down Expand Up @@ -149,6 +149,8 @@ pub enum CliReporter {
Summary,
/// Reports linter diagnostics using the [GitLab Code Quality report](https://docs.gitlab.com/ee/ci/testing/code_quality.html#implement-a-custom-tool).
GitLab,
/// Reports diagnostics in Checkstyle XML format
Checkstyle,
}

impl CliReporter {
Expand All @@ -168,6 +170,7 @@ impl FromStr for CliReporter {
"github" => Ok(Self::GitHub),
"junit" => Ok(Self::Junit),
"gitlab" => Ok(Self::GitLab),
"checkstyle" => Ok(Self::Checkstyle),
_ => Err(format!(
"value {s:?} is not valid for the --reporter argument"
)),
Expand All @@ -185,6 +188,7 @@ impl Display for CliReporter {
Self::GitHub => f.write_str("github"),
Self::Junit => f.write_str("junit"),
Self::GitLab => f.write_str("gitlab"),
Self::Checkstyle => f.write_str("checkstyle"),
}
}
}
Expand Down
15 changes: 15 additions & 0 deletions crates/biome_cli/src/execute/mod.rs
Original file line number Diff line number Diff line change
Expand Up @@ -247,6 +247,8 @@ pub enum ReportMode {
Junit,
/// Reports information in the [GitLab Code Quality](https://docs.gitlab.com/ee/ci/testing/code_quality.html#implement-a-custom-tool) format.
GitLab,
/// Reports diagnostics in Checkstyle XML format
Checkstyle,
}

impl Default for ReportMode {
Expand All @@ -269,6 +271,7 @@ impl From<CliReporter> for ReportMode {
CliReporter::GitHub => Self::GitHub,
CliReporter::Junit => Self::Junit,
CliReporter::GitLab => Self::GitLab {},
CliReporter::Checkstyle => Self::Checkstyle,
}
}
}
Expand Down Expand Up @@ -686,6 +689,18 @@ pub fn execute_mode(
{buffer}
});
}
ReportMode::Checkstyle => {
let reporter = crate::reporter::checkstyle::CheckstyleReporter {
summary,
diagnostics_payload: DiagnosticsPayload {
verbose: cli_options.verbose,
diagnostic_level: cli_options.diagnostic_level,
diagnostics,
},
execution: execution.clone(),
};
reporter.write(&mut crate::reporter::checkstyle::CheckstyleReporterVisitor::new(console))?;
}
}
ReportMode::GitHub => {
let reporter = GithubReporter {
Expand Down
105 changes: 105 additions & 0 deletions crates/biome_cli/src/reporter/checkstyle.rs
Original file line number Diff line number Diff line change
@@ -0,0 +1,105 @@
use crate::{DiagnosticsPayload, Execution, Reporter, ReporterVisitor, TraversalSummary};
use biome_console::{markup, Console, ConsoleExt};
use biome_diagnostics::{Error, Resource, Severity};
use std::collections::BTreeMap;
use std::io::{self, Write};

pub struct CheckstyleReporter {
pub summary: TraversalSummary,
pub diagnostics_payload: DiagnosticsPayload,
pub execution: Execution,
}

impl Reporter for CheckstyleReporter {
fn write(self, visitor: &mut dyn ReporterVisitor) -> io::Result<()> {
visitor.report_summary(&self.execution, self.summary)?;
visitor.report_diagnostics(&self.execution, self.diagnostics_payload)?;
Ok(())
}
}

pub struct CheckstyleReporterVisitor<'a> {
console: &'a mut dyn Console,
}

impl<'a> CheckstyleReporterVisitor<'a> {
pub fn new(console: &'a mut dyn Console) -> Self {
Self { console }
}
}

impl<'a> ReporterVisitor for CheckstyleReporterVisitor<'a> {
fn report_summary(&mut self, _execution: &Execution, _summary: TraversalSummary) -> io::Result<()> {
// Checkstyle does not require a summary
Ok(())
}

fn report_diagnostics(&mut self, _execution: &Execution, payload: DiagnosticsPayload) -> io::Result<()> {
let mut files: BTreeMap<String, Vec<&Error>> = BTreeMap::new();
for diagnostic in &payload.diagnostics {
if diagnostic.severity() >= payload.diagnostic_level {
if diagnostic.tags().is_verbose() && !payload.verbose {
continue;
}
let path = match diagnostic.location().resource {
Some(Resource::File(file)) => file.to_string(),
_ => "<unknown>".to_string(),
};
files.entry(path).or_default().push(diagnostic);
}
}
let mut output = Vec::new();
writeln!(output, "<?xml version=\"1.0\" encoding=\"utf-8\"?>")?;
writeln!(output, "<checkstyle version=\"4.3\">")?;
for (file, diagnostics) in files {
writeln!(output, " <file name=\"{}\">", xml_escape(&file))?;
for diagnostic in diagnostics {
let location = diagnostic.location();
let (line, column) = if let (Some(span), Some(source_code)) = (location.span, location.source_code) {
let source = biome_diagnostics::display::SourceFile::new(source_code);
if let Ok(start) = source.location(span.start()) {
(start.line_number.get(), start.column_number.get())
} else {
(0, 0)
}
} else {
(0, 0)
};
let severity = match diagnostic.severity() {
Severity::Error => "error",
Severity::Warning => "warning",
Severity::Information | Severity::Hint => "info",
Severity::Fatal => "error",
};
let message = get_error_description(diagnostic);
let source = diagnostic.category().map(|c| c.name()).unwrap_or("");
writeln!(output,
" <error line=\"{}\" column=\"{}\" severity=\"{}\" message=\"{}\" source=\"{}\" />",
line,
column,
severity,
xml_escape(&message),
xml_escape(source),
)?;
}
writeln!(output, " </file>")?;
}
writeln!(output, "</checkstyle>")?;
self.console.log(markup! {{
(String::from_utf8_lossy(&output))
}});
Ok(())
}
}

fn xml_escape(input: &str) -> String {
input.replace('&', "&amp;")
.replace('<', "&lt;")
.replace('>', "&gt;")
.replace('"', "&quot;")
.replace('\'', "&apos;")
}

fn get_error_description(error: &Error) -> String {
format!("{:?}", error)
}
1 change: 1 addition & 0 deletions crates/biome_cli/src/reporter/mod.rs
Original file line number Diff line number Diff line change
Expand Up @@ -4,6 +4,7 @@ pub(crate) mod json;
pub(crate) mod junit;
pub(crate) mod summary;
pub(crate) mod terminal;
pub(crate) mod checkstyle;

use crate::cli_options::MaxDiagnostics;
use crate::execute::Execution;
Expand Down
Loading