ruff_db: add a vector for configuring diagnostic output (#16118)

For now, the only thing one can configure is whether color is enabled or
not. This avoids needing to ask the `colored` crate whether colors have
been globally enabled or disabled. And, more crucially, avoids the need
to _set_ this global flag for testing diagnostic output. Doing so can
have unintended consequences, as outlined in #16115.

Fixes #16115
This commit is contained in:
Andrew Gallant 2025-02-12 09:38:05 -05:00 committed by GitHub
parent 03f08283ad
commit a9671e7008
No known key found for this signature in database
GPG key ID: B5690EEEBB952194
6 changed files with 38 additions and 21 deletions

1
Cargo.lock generated
View file

@ -2755,7 +2755,6 @@ name = "ruff_db"
version = "0.0.0"
dependencies = [
"camino",
"colored 3.0.0",
"countme",
"dashmap 6.1.0",
"dunce",

View file

@ -15,7 +15,7 @@ use red_knot_project::watch::ProjectWatcher;
use red_knot_project::{watch, Db};
use red_knot_project::{ProjectDatabase, ProjectMetadata};
use red_knot_server::run_server;
use ruff_db::diagnostic::{Diagnostic, Severity};
use ruff_db::diagnostic::{Diagnostic, DisplayDiagnosticConfig, Severity};
use ruff_db::system::{OsSystem, System, SystemPath, SystemPathBuf};
use salsa::plumbing::ZalsaDatabase;
@ -231,6 +231,9 @@ impl MainLoop {
result,
revision: check_revision,
} => {
let display_config = DisplayDiagnosticConfig::default()
.color(colored::control::SHOULD_COLORIZE.should_colorize());
let min_error_severity =
if db.project().settings(db).terminal().error_on_warning {
Severity::Warning
@ -245,7 +248,7 @@ impl MainLoop {
if check_revision == revision {
#[allow(clippy::print_stdout)]
for diagnostic in result {
println!("{}", diagnostic.display(db));
println!("{}", diagnostic.display(db, &display_config));
}
} else {
tracing::debug!(

View file

@ -5,7 +5,7 @@ use colored::Colorize;
use parser as test_parser;
use red_knot_python_semantic::types::check_types;
use red_knot_python_semantic::{Program, ProgramSettings, SearchPathSettings, SitePackages};
use ruff_db::diagnostic::{Diagnostic, ParseDiagnostic};
use ruff_db::diagnostic::{Diagnostic, DisplayDiagnosticConfig, ParseDiagnostic};
use ruff_db::files::{system_path_to_file, File, Files};
use ruff_db::panic::catch_unwind;
use ruff_db::parsed::parsed_module;
@ -300,9 +300,7 @@ fn create_diagnostic_snapshot<D: Diagnostic>(
test: &parser::MarkdownTest,
diagnostics: impl IntoIterator<Item = D>,
) -> String {
// TODO(ag): Do something better than requiring this
// global state to be twiddled everywhere.
colored::control::set_override(false);
let display_config = DisplayDiagnosticConfig::default().color(false);
let mut snapshot = String::new();
writeln!(snapshot).unwrap();
@ -340,7 +338,7 @@ fn create_diagnostic_snapshot<D: Diagnostic>(
writeln!(snapshot).unwrap();
}
writeln!(snapshot, "```").unwrap();
writeln!(snapshot, "{}", diag.display(db)).unwrap();
writeln!(snapshot, "{}", diag.display(db, &display_config)).unwrap();
writeln!(snapshot, "```").unwrap();
}
snapshot

View file

@ -7,7 +7,7 @@ use red_knot_project::metadata::options::{EnvironmentOptions, Options};
use red_knot_project::metadata::value::RangedValue;
use red_knot_project::ProjectMetadata;
use red_knot_project::{Db, ProjectDatabase};
use ruff_db::diagnostic::Diagnostic;
use ruff_db::diagnostic::{Diagnostic, DisplayDiagnosticConfig};
use ruff_db::files::{system_path_to_file, File};
use ruff_db::system::walk_directory::WalkDirectoryBuilder;
use ruff_db::system::{
@ -114,9 +114,10 @@ impl Workspace {
pub fn check_file(&self, file_id: &FileHandle) -> Result<Vec<String>, Error> {
let result = self.db.check_file(file_id.file).map_err(into_error)?;
let display_config = DisplayDiagnosticConfig::default().color(false);
Ok(result
.into_iter()
.map(|diagnostic| diagnostic.display(&self.db).to_string())
.map(|diagnostic| diagnostic.display(&self.db, &display_config).to_string())
.collect())
}
@ -124,9 +125,10 @@ impl Workspace {
pub fn check(&self) -> Result<Vec<String>, Error> {
let result = self.db.check().map_err(into_error)?;
let display_config = DisplayDiagnosticConfig::default().color(false);
Ok(result
.into_iter()
.map(|diagnostic| diagnostic.display(&self.db).to_string())
.map(|diagnostic| diagnostic.display(&self.db, &display_config).to_string())
.collect())
}

View file

@ -21,7 +21,6 @@ ruff_source_file = { workspace = true }
ruff_text_size = { workspace = true }
camino = { workspace = true }
colored = { workspace = true }
countme = { workspace = true }
dashmap = { workspace = true }
dunce = { workspace = true }

View file

@ -172,13 +172,18 @@ pub trait Diagnostic: Send + Sync + std::fmt::Debug {
fn severity(&self) -> Severity;
fn display<'a>(&'a self, db: &'a dyn Db) -> DisplayDiagnostic<'a>
fn display<'db, 'diag, 'config>(
&'diag self,
db: &'db dyn Db,
config: &'config DisplayDiagnosticConfig,
) -> DisplayDiagnostic<'db, 'diag, 'config>
where
Self: Sized,
{
DisplayDiagnostic {
db,
diagnostic: self,
config,
}
}
}
@ -232,18 +237,29 @@ pub enum Severity {
Fatal,
}
pub struct DisplayDiagnostic<'db> {
db: &'db dyn Db,
diagnostic: &'db dyn Diagnostic,
/// Configuration for rendering diagnostics.
#[derive(Clone, Debug, Default)]
pub struct DisplayDiagnosticConfig {
/// Whether to enable colors or not.
///
/// Disabled by default.
color: bool,
}
impl<'db> DisplayDiagnostic<'db> {
pub fn new(db: &'db dyn Db, diagnostic: &'db dyn Diagnostic) -> Self {
Self { db, diagnostic }
impl DisplayDiagnosticConfig {
/// Whether to enable colors or not.
pub fn color(self, yes: bool) -> DisplayDiagnosticConfig {
DisplayDiagnosticConfig { color: yes }
}
}
impl std::fmt::Display for DisplayDiagnostic<'_> {
pub struct DisplayDiagnostic<'db, 'diag, 'config> {
db: &'db dyn Db,
diagnostic: &'diag dyn Diagnostic,
config: &'config DisplayDiagnosticConfig,
}
impl std::fmt::Display for DisplayDiagnostic<'_, '_, '_> {
fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
let level = match self.diagnostic.severity() {
Severity::Info => Level::Info,
@ -260,7 +276,7 @@ impl std::fmt::Display for DisplayDiagnostic<'_> {
};
let render = |f: &mut std::fmt::Formatter, message| {
let renderer = if !cfg!(test) && colored::control::SHOULD_COLORIZE.should_colorize() {
let renderer = if self.config.color {
Renderer::styled()
} else {
Renderer::plain()