port azure output format

This commit is contained in:
Brent Westbrook 2025-07-03 14:07:40 -04:00
parent ddba240f4e
commit 8bc5b71079
5 changed files with 71 additions and 53 deletions

View file

@ -1232,6 +1232,10 @@ pub enum DiagnosticFormat {
///
/// This may use color when printing to a `tty`.
Concise,
/// Print diagnostics in the [Azure Pipelines] format.
///
/// [Azure Pipelines]: https://learn.microsoft.com/en-us/azure/devops/pipelines/scripts/logging-commands?view=azure-devops&tabs=bash#logissue-log-an-error-or-warning
Azure,
}
/// A representation of the kinds of messages inside a diagnostic.

View file

@ -67,43 +67,69 @@ impl std::fmt::Display for DisplayDiagnostic<'_> {
DiagnosticStylesheet::plain()
};
if matches!(self.config.format, DiagnosticFormat::Concise) {
let (severity, severity_style) = match self.diag.severity() {
Severity::Info => ("info", stylesheet.info),
Severity::Warning => ("warning", stylesheet.warning),
Severity::Error => ("error", stylesheet.error),
Severity::Fatal => ("fatal", stylesheet.error),
};
write!(
f,
"{severity}[{id}]",
severity = fmt_styled(severity, severity_style),
id = fmt_styled(self.diag.id(), stylesheet.emphasis)
)?;
if let Some(span) = self.diag.primary_span() {
match self.config.format {
DiagnosticFormat::Concise => {
let (severity, severity_style) = match self.diag.severity() {
Severity::Info => ("info", stylesheet.info),
Severity::Warning => ("warning", stylesheet.warning),
Severity::Error => ("error", stylesheet.error),
Severity::Fatal => ("fatal", stylesheet.error),
};
write!(
f,
" {path}",
path = fmt_styled(span.file().path(self.resolver), stylesheet.emphasis)
"{severity}[{id}]",
severity = fmt_styled(severity, severity_style),
id = fmt_styled(self.diag.id(), stylesheet.emphasis)
)?;
if let Some(range) = span.range() {
let diagnostic_source = span.file().diagnostic_source(self.resolver);
let start = diagnostic_source
.as_source_code()
.line_column(range.start());
if let Some(span) = self.diag.primary_span() {
write!(
f,
":{line}:{col}",
line = fmt_styled(start.line, stylesheet.emphasis),
col = fmt_styled(start.column, stylesheet.emphasis),
" {path}",
path = fmt_styled(span.file().path(self.resolver), stylesheet.emphasis)
)?;
if let Some(range) = span.range() {
let diagnostic_source = span.file().diagnostic_source(self.resolver);
let start = diagnostic_source
.as_source_code()
.line_column(range.start());
write!(
f,
":{line}:{col}",
line = fmt_styled(start.line, stylesheet.emphasis),
col = fmt_styled(start.column, stylesheet.emphasis),
)?;
}
write!(f, ":")?;
}
write!(f, ":")?;
return writeln!(f, " {message}", message = self.diag.concise_message());
}
return writeln!(f, " {message}", message = self.diag.concise_message());
DiagnosticFormat::Azure => {
if let Some(span) = self.diag.primary_span() {
if let Some(range) = span.range() {
let filename = span.file().path(self.resolver);
let location = span
.file()
.diagnostic_source(self.resolver)
.as_source_code()
.line_column(range.start());
return writeln!(
f,
"##vso[task.logissue type=error\
;sourcepath={filename};linenumber={line};columnnumber={col};{code}]{body}",
line = location.line,
col = location.column,
code = self
.diag
.secondary_code()
.map_or_else(String::new, |code| format!("code={code};")),
body = self.diag.body(),
);
}
}
}
_ => {}
}
let mut renderer = self.annotate_renderer.clone();

View file

@ -1,10 +1,11 @@
use std::io::Write;
use ruff_db::diagnostic::Diagnostic;
use ruff_source_file::LineColumn;
use ruff_db::diagnostic::{Diagnostic, DiagnosticFormat, DisplayDiagnosticConfig};
use crate::message::{Emitter, EmitterContext};
use super::DummyFileResolver;
/// Generate error logging commands for Azure Pipelines format.
/// See [documentation](https://learn.microsoft.com/en-us/azure/devops/pipelines/scripts/logging-commands?view=azure-devops&tabs=bash#logissue-log-an-error-or-warning)
#[derive(Default)]
@ -15,29 +16,12 @@ impl Emitter for AzureEmitter {
&mut self,
writer: &mut dyn Write,
diagnostics: &[Diagnostic],
context: &EmitterContext,
_context: &EmitterContext,
) -> anyhow::Result<()> {
let resolver = DummyFileResolver;
let config = DisplayDiagnosticConfig::default().format(DiagnosticFormat::Azure);
for diagnostic in diagnostics {
let filename = diagnostic.expect_ruff_filename();
let location = if context.is_notebook(&filename) {
// We can't give a reasonable location for the structured formats,
// so we show one that's clearly a fallback
LineColumn::default()
} else {
diagnostic.expect_ruff_start_location()
};
writeln!(
writer,
"##vso[task.logissue type=error\
;sourcepath={filename};linenumber={line};columnnumber={col};{code}]{body}",
line = location.line,
col = location.column,
code = diagnostic
.secondary_code()
.map_or_else(String::new, |code| format!("code={code};")),
body = diagnostic.body(),
)?;
write!(writer, "{}", diagnostic.display(&resolver, &config))?;
}
Ok(())

View file

@ -321,6 +321,9 @@ pub enum OutputFormat {
/// dropped.
#[value(name = "concise")]
Concise,
/// Print diagnostics in the format used for Azure Pipelines.
#[value(name = "azure")]
Azure,
}
impl From<OutputFormat> for ruff_db::diagnostic::DiagnosticFormat {
@ -328,6 +331,7 @@ impl From<OutputFormat> for ruff_db::diagnostic::DiagnosticFormat {
match format {
OutputFormat::Full => Self::Full,
OutputFormat::Concise => Self::Concise,
OutputFormat::Azure => Self::Azure,
}
}
}

View file

@ -990,7 +990,7 @@ pub struct TerminalOptions {
#[serde(skip_serializing_if = "Option::is_none")]
#[option(
default = r#"full"#,
value_type = "full | concise",
value_type = "full | concise | azure",
example = r#"
output-format = "concise"
"#