[red-knot] colorize concise output diagnostics (#17232) (#17479)

Co-authored-by: Micha Reiser <micha@reiser.io>
Co-authored-by: Andrew Gallant <andrew@astral.sh>
This commit is contained in:
Hans 2025-04-29 22:07:16 +08:00 committed by GitHub
parent 79f8473e51
commit 9b9d16c3ba
No known key found for this signature in database
GPG Key ID: B5690EEEBB952194
5 changed files with 130 additions and 12 deletions

1
Cargo.lock generated
View File

@ -2865,6 +2865,7 @@ dependencies = [
name = "ruff_db" name = "ruff_db"
version = "0.0.0" version = "0.0.0"
dependencies = [ dependencies = [
"anstyle",
"camino", "camino",
"countme", "countme",
"dashmap 6.1.0", "dashmap 6.1.0",

View File

@ -20,6 +20,7 @@ ruff_python_trivia = { workspace = true }
ruff_source_file = { workspace = true } ruff_source_file = { workspace = true }
ruff_text_size = { workspace = true } ruff_text_size = { workspace = true }
anstyle = { workspace = true }
camino = { workspace = true } camino = { workspace = true }
countme = { workspace = true } countme = { workspace = true }
dashmap = { workspace = true } dashmap = { workspace = true }

View File

@ -11,6 +11,7 @@ use crate::Db;
use self::render::FileResolver; use self::render::FileResolver;
mod render; mod render;
mod stylesheet;
/// A collection of information that can be rendered into a diagnostic. /// A collection of information that can be rendered into a diagnostic.
/// ///

View File

@ -7,6 +7,7 @@ use ruff_annotate_snippets::{
use ruff_source_file::{LineIndex, OneIndexed, SourceCode}; use ruff_source_file::{LineIndex, OneIndexed, SourceCode};
use ruff_text_size::{TextRange, TextSize}; use ruff_text_size::{TextRange, TextSize};
use crate::diagnostic::stylesheet::{fmt_styled, DiagnosticStylesheet};
use crate::{ use crate::{
files::File, files::File,
source::{line_index, source_text, SourceText}, source::{line_index, source_text, SourceText},
@ -48,6 +49,7 @@ impl<'a> DisplayDiagnostic<'a> {
} else { } else {
AnnotateRenderer::plain() AnnotateRenderer::plain()
}; };
DisplayDiagnostic { DisplayDiagnostic {
config, config,
resolver, resolver,
@ -59,31 +61,64 @@ impl<'a> DisplayDiagnostic<'a> {
impl std::fmt::Display for DisplayDiagnostic<'_> { impl std::fmt::Display for DisplayDiagnostic<'_> {
fn fmt(&self, f: &mut std::fmt::Formatter) -> std::fmt::Result { fn fmt(&self, f: &mut std::fmt::Formatter) -> std::fmt::Result {
if matches!(self.config.format, DiagnosticFormat::Concise) { let stylesheet = if self.config.color {
match self.diag.severity() { DiagnosticStylesheet::styled()
Severity::Info => f.write_str("info")?, } else {
Severity::Warning => f.write_str("warning")?, DiagnosticStylesheet::plain()
Severity::Error => f.write_str("error")?, };
Severity::Fatal => f.write_str("fatal")?,
} 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)
)?;
write!(f, "[{rule}]", rule = self.diag.id())?;
if let Some(span) = self.diag.primary_span() { if let Some(span) = self.diag.primary_span() {
write!(f, " {path}", path = self.resolver.path(span.file()))?; write!(
f,
" {path}",
path = fmt_styled(self.resolver.path(span.file()), stylesheet.emphasis)
)?;
if let Some(range) = span.range() { if let Some(range) = span.range() {
let input = self.resolver.input(span.file()); let input = self.resolver.input(span.file());
let start = input.as_source_code().line_column(range.start()); let start = input.as_source_code().line_column(range.start());
write!(f, ":{line}:{col}", line = start.line, col = start.column)?;
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, " {}", self.diag.concise_message()); return writeln!(f, " {message}", message = self.diag.concise_message());
} }
let mut renderer = self.annotate_renderer.clone();
renderer = renderer
.error(stylesheet.error)
.warning(stylesheet.warning)
.info(stylesheet.info)
.note(stylesheet.note)
.help(stylesheet.help)
.line_no(stylesheet.line_no)
.emphasis(stylesheet.emphasis)
.none(stylesheet.none);
let resolved = Resolved::new(&self.resolver, self.diag); let resolved = Resolved::new(&self.resolver, self.diag);
let renderable = resolved.to_renderable(self.config.context); let renderable = resolved.to_renderable(self.config.context);
for diag in renderable.diagnostics.iter() { for diag in renderable.diagnostics.iter() {
writeln!(f, "{}", self.annotate_renderer.render(diag.to_annotate()))?; writeln!(f, "{}", renderer.render(diag.to_annotate()))?;
} }
writeln!(f) writeln!(f)
} }

View File

@ -0,0 +1,80 @@
use anstyle::{AnsiColor, Effects, Style};
use std::fmt::Formatter;
pub(super) const fn fmt_styled<'a, T>(
content: T,
style: anstyle::Style,
) -> impl std::fmt::Display + 'a
where
T: std::fmt::Display + 'a,
{
struct FmtStyled<T> {
content: T,
style: anstyle::Style,
}
impl<T> std::fmt::Display for FmtStyled<T>
where
T: std::fmt::Display,
{
fn fmt(&self, f: &mut Formatter<'_>) -> std::fmt::Result {
write!(
f,
"{style_start}{content}{style_end}",
style_start = self.style.render(),
content = self.content,
style_end = self.style.render_reset()
)
}
}
FmtStyled { content, style }
}
#[derive(Clone, Debug)]
pub struct DiagnosticStylesheet {
pub(crate) error: Style,
pub(crate) warning: Style,
pub(crate) info: Style,
pub(crate) note: Style,
pub(crate) help: Style,
pub(crate) line_no: Style,
pub(crate) emphasis: Style,
pub(crate) none: Style,
}
impl Default for DiagnosticStylesheet {
fn default() -> Self {
Self::plain()
}
}
impl DiagnosticStylesheet {
/// Default terminal styling
pub fn styled() -> Self {
let bright_blue = AnsiColor::BrightBlue.on_default();
Self {
error: AnsiColor::BrightRed.on_default().effects(Effects::BOLD),
warning: AnsiColor::Yellow.on_default().effects(Effects::BOLD),
info: bright_blue.effects(Effects::BOLD),
note: AnsiColor::BrightGreen.on_default().effects(Effects::BOLD),
help: AnsiColor::BrightCyan.on_default().effects(Effects::BOLD),
line_no: bright_blue.effects(Effects::BOLD),
emphasis: Style::new().effects(Effects::BOLD),
none: Style::new(),
}
}
pub fn plain() -> Self {
Self {
error: Style::new(),
warning: Style::new(),
info: Style::new(),
note: Style::new(),
help: Style::new(),
line_no: Style::new(),
emphasis: Style::new(),
none: Style::new(),
}
}
}