From c8589d0eb1da386fdcb0fb804ab5255d6d3257d5 Mon Sep 17 00:00:00 2001 From: Brent Westbrook <36778786+ntBre@users.noreply.github.com> Date: Fri, 23 Oct 2026 09:38:24 -0400 Subject: [PATCH] Move diagnostic format dispatch into Ruff and ty (#29647) Summary -- This addresses the suggestion from [#29635]. `DisplayDiagnosticConfig` previously determined the `DiagnosticFormat` for diagnostics coming from both Ruff and ty, which meant that it had to have a shared enum between the two, despite each program supporting a different subset of the available formats. This PR eliminates the shared `DiagnosticFormat` and dispatch through `DisplayDiagnostics`, leaving Ruff and ty to construct `Renderer`s directly and do their own dispatch on the output formats they actually support. This will be especially helpful in #29635, where we want to provide slightly different arguments to each new `SarifRenderer`. The `Renderer` trait itself isn't strictly necessary, but it made the calls on the Ruff side a lot neater since the trait provides a default `write` method based on the required `render` method. Codex also threw in a default `display` method that helps in a couple of places too. Test Plan -- Existing tests [#29635]: https://github.com/astral-sh/ruff/pull/29635#discussion_r4352505588 --- crates/ruff/src/commands/check.rs | 16 +- crates/ruff/src/commands/format.rs | 2 +- crates/ruff/src/printer.rs | 4 +- crates/ruff_db/src/diagnostic/mod.rs | 87 +------- crates/ruff_db/src/diagnostic/render.rs | 211 ++++++++---------- crates/ruff_db/src/diagnostic/render/azure.rs | 22 +- .../ruff_db/src/diagnostic/render/concise.rs | 23 +- crates/ruff_db/src/diagnostic/render/full.rs | 20 +- .../ruff_db/src/diagnostic/render/github.rs | 19 +- .../ruff_db/src/diagnostic/render/gitlab.rs | 19 +- crates/ruff_db/src/diagnostic/render/json.rs | 26 +-- .../src/diagnostic/render/json_lines.rs | 27 ++- crates/ruff_db/src/diagnostic/render/junit.rs | 22 +- .../ruff_db/src/diagnostic/render/pylint.rs | 22 +- .../ruff_db/src/diagnostic/render/rdjson.rs | 20 +- crates/ruff_linter/src/message/mod.rs | 56 ++--- crates/ruff_linter/src/settings/types.rs | 28 --- crates/ruff_linter/src/test.rs | 20 +- .../ruff_python_formatter/tests/fixtures.rs | 26 +-- crates/ty/src/lib.rs | 9 +- crates/ty/tests/file_watching.rs | 15 +- crates/ty_ide/src/code_action.rs | 9 +- crates/ty_ide/src/hover.rs | 8 +- crates/ty_ide/src/inlay_hints.rs | 9 +- crates/ty_ide/src/lib.rs | 9 +- crates/ty_project/src/metadata/options.rs | 45 ++-- crates/ty_python_semantic/src/fixes.rs | 28 +-- .../src/types/dedicated/pytest/collection.rs | 14 +- .../src/types/dedicated/pytest/fixtures.rs | 16 +- .../src/types/ide_support/unreachable_code.rs | 14 +- 30 files changed, 331 insertions(+), 515 deletions(-) diff --git a/crates/ruff/src/commands/check.rs b/crates/ruff/src/commands/check.rs index a5bbdee9b4..50d6ecc888 100644 --- a/crates/ruff/src/commands/check.rs +++ b/crates/ruff/src/commands/check.rs @@ -224,7 +224,8 @@ mod test { use rustc_hash::FxHashMap; use tempfile::TempDir; - use ruff_db::diagnostic::{DiagnosticFormat, DisplayDiagnosticConfig, DisplayDiagnostics}; + use ruff_db::diagnostic::DisplayDiagnosticConfig; + use ruff_db::diagnostic::render::{ConciseRenderer, Renderer}; use ruff_linter::message::EmitterContext; use ruff_linter::registry::Rule; use ruff_linter::settings::types::UnsafeFixes; @@ -279,15 +280,10 @@ mod test { ) .unwrap(); - let config = DisplayDiagnosticConfig::new("ruff") - .format(DiagnosticFormat::Concise) - .hide_severity(true); - let messages = DisplayDiagnostics::new( - &EmitterContext::new(&FxHashMap::default()), - &config, - &diagnostics.inner, - ) - .to_string(); + let config = DisplayDiagnosticConfig::new("ruff").hide_severity(true); + let messages = ConciseRenderer::new(&EmitterContext::new(&FxHashMap::default()), &config) + .display(&diagnostics.inner) + .to_string(); insta::with_settings!({ omit_expression => true, diff --git a/crates/ruff/src/commands/format.rs b/crates/ruff/src/commands/format.rs index c2685d001b..2857638848 100644 --- a/crates/ruff/src/commands/format.rs +++ b/crates/ruff/src/commands/format.rs @@ -603,7 +603,7 @@ impl<'a> FormatResults<'a> { .hide_severity(true) .color(!cfg!(test) && colored::control::SHOULD_COLORIZE.should_colorize()); - render_diagnostics(f, output_format, config, &context, &diagnostics) + render_diagnostics(f, output_format, &config, &context, &diagnostics) } /// Write a summary of the formatting results to the given writer. diff --git a/crates/ruff/src/printer.rs b/crates/ruff/src/printer.rs index 9d9a23711c..f5c13106f3 100644 --- a/crates/ruff/src/printer.rs +++ b/crates/ruff/src/printer.rs @@ -250,7 +250,7 @@ impl Printer { .with_show_fix_status(show_fix_status(self.fix_mode, fixables.as_ref())) .with_fix_applicability(self.unsafe_fixes.required_applicability()); - render_diagnostics(writer, self.format, config, &context, &diagnostics.inner)?; + render_diagnostics(writer, self.format, &config, &context, &diagnostics.inner)?; if matches!( self.format, @@ -435,7 +435,7 @@ impl Printer { .color(!cfg!(test) && colored::control::SHOULD_COLORIZE.should_colorize()) .with_show_fix_status(show_fix_status(self.fix_mode, fixables.as_ref())) .with_fix_applicability(self.unsafe_fixes.required_applicability()); - render_diagnostics(writer, self.format, config, &context, &diagnostics.inner)?; + render_diagnostics(writer, self.format, &config, &context, &diagnostics.inner)?; } writer.flush()?; diff --git a/crates/ruff_db/src/diagnostic/mod.rs b/crates/ruff_db/src/diagnostic/mod.rs index 40f5184666..6f7e696ce9 100644 --- a/crates/ruff_db/src/diagnostic/mod.rs +++ b/crates/ruff_db/src/diagnostic/mod.rs @@ -1,4 +1,4 @@ -use std::fmt::{Display, Formatter}; +use std::fmt::{self, Display, Formatter}; use std::{borrow::Cow, path::Path, sync::Arc}; use ruff_diagnostics::{Applicability, Fix}; @@ -9,14 +9,13 @@ use ruff_text_size::{Ranged, TextRange, TextSize}; #[cfg(feature = "serde")] use serde::Serialize; -pub use self::render::{ - DisplayDiagnostic, DisplayDiagnostics, DummyFileResolver, FileResolver, Input, -}; +pub use self::render::{DummyFileResolver, FileResolver, Input}; +use self::render::{FullRenderer, Renderer}; pub use self::stylesheet::{DiagnosticStylesheet, fmt_with_hyperlink}; use crate::cancellation::CancellationToken; use crate::{Db, files::File}; -mod render; +pub mod render; mod stylesheet; /// A collection of information that can be rendered into a diagnostic. @@ -206,7 +205,7 @@ impl Diagnostic { } /// Return a `std::fmt::Display` implementation that renders this - /// diagnostic into a human readable format. + /// diagnostic in the full, human-readable format. /// /// Note that this `Display` impl includes a trailing line terminator, so /// callers should prefer using this with `write!` instead of `writeln!`. @@ -214,8 +213,10 @@ impl Diagnostic { &'a self, resolver: &'a dyn FileResolver, config: &'a DisplayDiagnosticConfig, - ) -> DisplayDiagnostic<'a> { - DisplayDiagnostic::new(resolver, config, self) + ) -> impl Display + 'a { + fmt::from_fn(move |f| { + FullRenderer::new(resolver, config).render(f, std::slice::from_ref(self)) + }) } /// Returns the identifier for this diagnostic. @@ -1422,10 +1423,6 @@ pub enum HyperlinkMode { pub struct DisplayDiagnosticConfig { /// The program name used in structured output formats (e.g., JUnit, GitHub). program: &'static str, - /// The format to use for diagnostic rendering. - /// - /// This uses the "full" format by default. - format: DiagnosticFormat, /// Whether to enable colors or not. /// /// Disabled by default. @@ -1473,7 +1470,6 @@ impl DisplayDiagnosticConfig { pub fn new(program: &'static str) -> DisplayDiagnosticConfig { DisplayDiagnosticConfig { program, - format: DiagnosticFormat::default(), color: false, hyperlinks: HyperlinkMode::Auto, anonymized_line_numbers: false, @@ -1488,11 +1484,6 @@ impl DisplayDiagnosticConfig { } } - /// Whether to enable concise diagnostic output or not. - pub fn format(self, format: DiagnosticFormat) -> DisplayDiagnosticConfig { - DisplayDiagnosticConfig { format, ..self } - } - /// Whether to enable colors or not. pub fn color(self, yes: bool) -> DisplayDiagnosticConfig { DisplayDiagnosticConfig { color: yes, ..self } @@ -1609,66 +1600,6 @@ impl DisplayDiagnosticConfig { } } -/// The diagnostic output format. -#[derive(Copy, Clone, Debug, Default, Eq, Hash, PartialEq, PartialOrd, Ord)] -#[cfg_attr(feature = "serde", derive(serde::Deserialize, serde::Serialize))] -#[cfg_attr(feature = "serde", serde(rename_all = "kebab-case"))] -#[cfg_attr(feature = "schemars", derive(schemars::JsonSchema))] -pub enum DiagnosticFormat { - /// The default full mode will print "pretty" diagnostics. - /// - /// That is, color will be used when printing to a `tty`. - /// Moreover, diagnostic messages may include additional - /// context and annotations on the input to help understand - /// the message. - #[default] - Full, - /// Print diagnostics in a concise mode. - /// - /// This will guarantee that each diagnostic is printed on - /// a single line. Only the most important or primary aspects - /// of the diagnostic are included. Contextual information is - /// dropped. - /// - /// 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, - /// Print diagnostics in JSON format. - /// - /// Unlike `json-lines`, this prints all of the diagnostics as a JSON array. - #[cfg(feature = "serde")] - Json, - /// Print diagnostics in JSON format, one per line. - /// - /// This will print each diagnostic as a separate JSON object on its own line. See the `json` - /// format for an array of all diagnostics. See for more details. - #[cfg(feature = "serde")] - JsonLines, - /// Print diagnostics in the JSON format expected by [reviewdog]. - /// - /// [reviewdog]: https://github.com/reviewdog/reviewdog - #[cfg(feature = "serde")] - Rdjson, - /// Print diagnostics in the format emitted by Pylint. - Pylint, - /// Print diagnostics in the format expected by JUnit. - #[cfg(feature = "junit")] - Junit, - /// Print diagnostics in the JSON format used by GitLab [Code Quality] reports. - /// - /// [Code Quality]: https://docs.gitlab.com/ci/testing/code_quality/#code-quality-report-format - #[cfg(feature = "serde")] - Gitlab, - - /// Print diagnostics in the format used by [GitHub Actions] workflow error annotations. - /// - /// [GitHub Actions]: https://docs.github.com/en/actions/reference/workflows-and-actions/workflow-commands#setting-an-error-message - Github, -} - /// A representation of the kinds of messages inside a diagnostic. pub enum ConciseMessage<'a> { /// A diagnostic contains a non-empty headline message and an empty diff --git a/crates/ruff_db/src/diagnostic/render.rs b/crates/ruff_db/src/diagnostic/render.rs index 08d76e61ec..8f77f3af93 100644 --- a/crates/ruff_db/src/diagnostic/render.rs +++ b/crates/ruff_db/src/diagnostic/render.rs @@ -6,7 +6,6 @@ use annotate_snippets::{ Annotation as AnnotateAnnotation, AnnotationKind, Group as AnnotateGroup, Level as AnnotateLevel, Snippet as AnnotateSnippet, }; -use full::FullRenderer; use ruff_notebook::{Notebook, NotebookIndex}; use ruff_source_file::{LineIndex, OneIndexed, SourceCode}; use ruff_text_size::{TextLen, TextRange, TextSize}; @@ -18,19 +17,29 @@ use crate::{ }; use super::{ - Annotation, Diagnostic, DiagnosticFormat, DiagnosticSource, DisplayDiagnosticConfig, - SubDiagnostic, UnifiedFile, + Annotation, Diagnostic, DiagnosticSource, DisplayDiagnosticConfig, SubDiagnostic, UnifiedFile, }; -use azure::AzureRenderer; -use concise::ConciseRenderer; -use github::GithubRenderer; -use pylint::PylintRenderer; +pub use azure::AzureRenderer; +pub use concise::ConciseRenderer; +pub use full::FullRenderer; +pub use github::GithubRenderer; +#[cfg(feature = "serde")] +pub use gitlab::GitlabRenderer; +#[cfg(feature = "serde")] +pub use json::JsonRenderer; +#[cfg(feature = "serde")] +pub use json_lines::JsonLinesRenderer; +#[cfg(feature = "junit")] +pub use junit::JunitRenderer; +pub use pylint::PylintRenderer; +#[cfg(feature = "serde")] +pub use rdjson::RdjsonRenderer; mod azure; mod concise; mod full; -pub mod github; +mod github; #[cfg(feature = "serde")] mod gitlab; #[cfg(feature = "serde")] @@ -43,115 +52,23 @@ mod pylint; #[cfg(feature = "serde")] mod rdjson; -/// A type that implements `std::fmt::Display` for diagnostic rendering. -/// -/// It is created via [`Diagnostic::display`]. -/// -/// The lifetime parameter, `'a`, refers to the shorter of: -/// -/// * The lifetime of the rendering configuration. -/// * The lifetime of the resolver used to load the contents of `Span` -/// values. When using Salsa, this most commonly corresponds to the lifetime -/// of a Salsa `Db`. -/// * The lifetime of the diagnostic being rendered. -pub struct DisplayDiagnostic<'a> { - config: &'a DisplayDiagnosticConfig, - resolver: &'a dyn FileResolver, - diag: &'a Diagnostic, -} +/// Renders diagnostics in a particular output format. +pub trait Renderer { + /// Renders diagnostics to a formatter. + fn render(&self, f: &mut std::fmt::Formatter, diagnostics: &[Diagnostic]) -> std::fmt::Result; -impl<'a> DisplayDiagnostic<'a> { - pub(crate) fn new( - resolver: &'a dyn FileResolver, - config: &'a DisplayDiagnosticConfig, - diag: &'a Diagnostic, - ) -> DisplayDiagnostic<'a> { - DisplayDiagnostic { - config, - resolver, - diag, - } + /// Returns a display implementation for these diagnostics. + fn display<'a>(&'a self, diagnostics: &'a [Diagnostic]) -> impl std::fmt::Display + 'a { + std::fmt::from_fn(move |f| self.render(f, diagnostics)) } -} -impl std::fmt::Display for DisplayDiagnostic<'_> { - fn fmt(&self, f: &mut std::fmt::Formatter) -> std::fmt::Result { - DisplayDiagnostics::new(self.resolver, self.config, std::slice::from_ref(self.diag)).fmt(f) - } -} - -/// A type that implements `std::fmt::Display` for rendering a collection of diagnostics. -/// -/// It is intended for collections of diagnostics that need to be serialized together, as is the -/// case for JSON, for example. -/// -/// See [`DisplayDiagnostic`] for rendering individual `Diagnostic`s and details about the lifetime -/// constraints. -pub struct DisplayDiagnostics<'a> { - config: &'a DisplayDiagnosticConfig, - resolver: &'a dyn FileResolver, - diagnostics: &'a [Diagnostic], -} - -impl<'a> DisplayDiagnostics<'a> { - pub fn new( - resolver: &'a dyn FileResolver, - config: &'a DisplayDiagnosticConfig, - diagnostics: &'a [Diagnostic], - ) -> DisplayDiagnostics<'a> { - DisplayDiagnostics { - config, - resolver, - diagnostics, - } - } -} - -impl std::fmt::Display for DisplayDiagnostics<'_> { - fn fmt(&self, f: &mut std::fmt::Formatter) -> std::fmt::Result { - match self.config.format { - DiagnosticFormat::Concise => { - ConciseRenderer::new(self.resolver, self.config).render(f, self.diagnostics)?; - } - DiagnosticFormat::Full => { - FullRenderer::new(self.resolver, self.config).render(f, self.diagnostics)?; - } - DiagnosticFormat::Azure => { - AzureRenderer::new(self.resolver, self.config).render(f, self.diagnostics)?; - } - #[cfg(feature = "serde")] - DiagnosticFormat::Json => { - json::JsonRenderer::new(self.resolver, self.config).render(f, self.diagnostics)?; - } - #[cfg(feature = "serde")] - DiagnosticFormat::JsonLines => { - json_lines::JsonLinesRenderer::new(self.resolver, self.config) - .render(f, self.diagnostics)?; - } - #[cfg(feature = "serde")] - DiagnosticFormat::Rdjson => { - rdjson::RdjsonRenderer::new(self.resolver, self.config) - .render(f, self.diagnostics)?; - } - DiagnosticFormat::Pylint => { - PylintRenderer::new(self.resolver, self.config).render(f, self.diagnostics)?; - } - #[cfg(feature = "junit")] - DiagnosticFormat::Junit => { - junit::JunitRenderer::new(self.resolver, self.config) - .render(f, self.diagnostics)?; - } - #[cfg(feature = "serde")] - DiagnosticFormat::Gitlab => { - gitlab::GitlabRenderer::new(self.resolver, self.config) - .render(f, self.diagnostics)?; - } - DiagnosticFormat::Github => { - GithubRenderer::new(self.resolver, self.config).render(f, self.diagnostics)?; - } - } - - Ok(()) + /// Writes diagnostics to an I/O writer. + fn write( + &self, + writer: &mut dyn std::io::Write, + diagnostics: &[Diagnostic], + ) -> std::io::Result<()> { + write!(writer, "{}", self.display(diagnostics)) } } @@ -2537,11 +2454,32 @@ watermelon ); } + #[derive(Default)] + pub(super) enum DiagnosticFormat { + #[default] + Full, + Concise, + Azure, + Github, + Pylint, + #[cfg(feature = "serde")] + Json, + #[cfg(feature = "serde")] + JsonLines, + #[cfg(feature = "serde")] + Rdjson, + #[cfg(feature = "serde")] + Gitlab, + #[cfg(feature = "junit")] + Junit, + } + /// A small harness for setting up an environment specifically for testing /// diagnostic rendering. pub(super) struct TestEnvironment { db: TestDb, config: DisplayDiagnosticConfig, + format: DiagnosticFormat, } impl TestEnvironment { @@ -2552,6 +2490,7 @@ watermelon let mut env = TestEnvironment { db: TestDb::new(), config: DisplayDiagnosticConfig::new("ty"), + format: DiagnosticFormat::default(), }; // Default to a merge window of 0 for testing purposes, // even though this is not the default for user-facing diagnostics. @@ -2581,8 +2520,7 @@ watermelon /// Set the output format to use in diagnostic rendering. pub(super) fn format(&mut self, format: DiagnosticFormat) { - let config = self.config.clone(); - self.config = config.format(format); + self.format = format; } /// Enable preview functionality for diagnostic rendering. @@ -2710,7 +2648,7 @@ watermelon /// /// (This will set the "printed" flag on `Diagnostic`.) pub(super) fn render(&self, diag: &Diagnostic) -> String { - diag.display(&self.db, &self.config).to_string() + self.render_diagnostics(std::slice::from_ref(diag)) } /// Render the given diagnostics into a `String`. @@ -2719,7 +2657,44 @@ watermelon /// /// (This will set the "printed" flag on `Diagnostic`.) pub(super) fn render_diagnostics(&self, diagnostics: &[Diagnostic]) -> String { - DisplayDiagnostics::new(&self.db, &self.config, diagnostics).to_string() + std::fmt::from_fn(|f| match self.format { + DiagnosticFormat::Full => { + FullRenderer::new(&self.db, &self.config).render(f, diagnostics) + } + DiagnosticFormat::Concise => { + ConciseRenderer::new(&self.db, &self.config).render(f, diagnostics) + } + DiagnosticFormat::Azure => { + AzureRenderer::new(&self.db, &self.config).render(f, diagnostics) + } + DiagnosticFormat::Github => { + GithubRenderer::new(&self.db, &self.config).render(f, diagnostics) + } + DiagnosticFormat::Pylint => { + PylintRenderer::new(&self.db, &self.config).render(f, diagnostics) + } + #[cfg(feature = "serde")] + DiagnosticFormat::Json => { + JsonRenderer::new(&self.db, &self.config).render(f, diagnostics) + } + #[cfg(feature = "serde")] + DiagnosticFormat::JsonLines => { + JsonLinesRenderer::new(&self.db, &self.config).render(f, diagnostics) + } + #[cfg(feature = "serde")] + DiagnosticFormat::Rdjson => { + RdjsonRenderer::new(&self.db, &self.config).render(f, diagnostics) + } + #[cfg(feature = "serde")] + DiagnosticFormat::Gitlab => { + GitlabRenderer::new(&self.db, &self.config).render(f, diagnostics) + } + #[cfg(feature = "junit")] + DiagnosticFormat::Junit => { + JunitRenderer::new(&self.db, &self.config).render(f, diagnostics) + } + }) + .to_string() } } diff --git a/crates/ruff_db/src/diagnostic/render/azure.rs b/crates/ruff_db/src/diagnostic/render/azure.rs index c1724aa6a5..05128c9d88 100644 --- a/crates/ruff_db/src/diagnostic/render/azure.rs +++ b/crates/ruff_db/src/diagnostic/render/azure.rs @@ -2,25 +2,24 @@ use ruff_source_file::LineColumn; use crate::diagnostic::{Diagnostic, DisplayDiagnosticConfig, Severity}; -use super::FileResolver; +use super::{FileResolver, Renderer}; -pub(super) struct AzureRenderer<'a> { +/// 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 +pub struct AzureRenderer<'a> { resolver: &'a dyn FileResolver, config: &'a DisplayDiagnosticConfig, } impl<'a> AzureRenderer<'a> { - pub(super) fn new(resolver: &'a dyn FileResolver, config: &'a DisplayDiagnosticConfig) -> Self { + pub fn new(resolver: &'a dyn FileResolver, config: &'a DisplayDiagnosticConfig) -> Self { Self { resolver, config } } } -impl AzureRenderer<'_> { - pub(super) fn render( - &self, - f: &mut std::fmt::Formatter, - diagnostics: &[Diagnostic], - ) -> std::fmt::Result { +impl Renderer for AzureRenderer<'_> { + fn render(&self, f: &mut std::fmt::Formatter, diagnostics: &[Diagnostic]) -> std::fmt::Result { for diag in diagnostics { let severity = match diag.severity() { Severity::Info | Severity::Warning => "warning", @@ -63,9 +62,8 @@ impl AzureRenderer<'_> { #[cfg(test)] mod tests { - use crate::diagnostic::{ - DiagnosticFormat, - render::tests::{create_diagnostics, create_syntax_error_diagnostics}, + use crate::diagnostic::render::tests::{ + DiagnosticFormat, create_diagnostics, create_syntax_error_diagnostics, }; #[test] diff --git a/crates/ruff_db/src/diagnostic/render/concise.rs b/crates/ruff_db/src/diagnostic/render/concise.rs index 0e437011d3..dd2da2d23d 100644 --- a/crates/ruff_db/src/diagnostic/render/concise.rs +++ b/crates/ruff_db/src/diagnostic/render/concise.rs @@ -3,23 +3,21 @@ use crate::diagnostic::{ stylesheet::{DiagnosticStylesheet, fmt_styled, fmt_with_hyperlink}, }; -use super::FileResolver; +use super::{FileResolver, Renderer}; -pub(super) struct ConciseRenderer<'a> { +pub struct ConciseRenderer<'a> { resolver: &'a dyn FileResolver, config: &'a DisplayDiagnosticConfig, } impl<'a> ConciseRenderer<'a> { - pub(super) fn new(resolver: &'a dyn FileResolver, config: &'a DisplayDiagnosticConfig) -> Self { + pub fn new(resolver: &'a dyn FileResolver, config: &'a DisplayDiagnosticConfig) -> Self { Self { resolver, config } } +} - pub(super) fn render( - &self, - f: &mut std::fmt::Formatter, - diagnostics: &[Diagnostic], - ) -> std::fmt::Result { +impl Renderer for ConciseRenderer<'_> { + fn render(&self, f: &mut std::fmt::Formatter, diagnostics: &[Diagnostic]) -> std::fmt::Result { let stylesheet = if self.config.color { DiagnosticStylesheet::styled().hyperlinks(self.config.hyperlinks) } else { @@ -132,12 +130,9 @@ impl<'a> ConciseRenderer<'a> { mod tests { use ruff_diagnostics::Applicability; - use crate::diagnostic::{ - DiagnosticFormat, - render::tests::{ - TestEnvironment, create_diagnostics, create_notebook_diagnostics, - create_syntax_error_diagnostics, - }, + use crate::diagnostic::render::tests::{ + DiagnosticFormat, TestEnvironment, create_diagnostics, create_notebook_diagnostics, + create_syntax_error_diagnostics, }; #[test] diff --git a/crates/ruff_db/src/diagnostic/render/full.rs b/crates/ruff_db/src/diagnostic/render/full.rs index 2201592db9..4a30d129c8 100644 --- a/crates/ruff_db/src/diagnostic/render/full.rs +++ b/crates/ruff_db/src/diagnostic/render/full.rs @@ -11,25 +11,23 @@ use ruff_notebook::NotebookIndex; use ruff_source_file::OneIndexed; use ruff_text_size::{Ranged, TextLen, TextRange, TextSize}; -use crate::diagnostic::render::{FileResolver, Resolved}; +use crate::diagnostic::render::{FileResolver, Renderer, Resolved}; use crate::diagnostic::stylesheet::{DiagnosticStylesheet, fmt_styled}; use crate::diagnostic::{Diagnostic, DiagnosticSource, DisplayDiagnosticConfig}; -pub(super) struct FullRenderer<'a> { +pub struct FullRenderer<'a> { resolver: &'a dyn FileResolver, config: &'a DisplayDiagnosticConfig, } impl<'a> FullRenderer<'a> { - pub(super) fn new(resolver: &'a dyn FileResolver, config: &'a DisplayDiagnosticConfig) -> Self { + pub fn new(resolver: &'a dyn FileResolver, config: &'a DisplayDiagnosticConfig) -> Self { Self { resolver, config } } +} - pub(super) fn render( - &self, - f: &mut std::fmt::Formatter, - diagnostics: &[Diagnostic], - ) -> std::fmt::Result { +impl Renderer for FullRenderer<'_> { + fn render(&self, f: &mut std::fmt::Formatter, diagnostics: &[Diagnostic]) -> std::fmt::Result { let stylesheet = if self.config.color { DiagnosticStylesheet::styled().hyperlinks(self.config.hyperlinks) } else { @@ -385,10 +383,10 @@ mod tests { use ruff_text_size::{TextLen, TextRange, TextSize}; use crate::diagnostic::{ - Annotation, DiagnosticFormat, Severity, + Annotation, Severity, render::tests::{ - NOTEBOOK, TestEnvironment, create_diagnostics, create_notebook_diagnostics, - create_syntax_error_diagnostics, + DiagnosticFormat, NOTEBOOK, TestEnvironment, create_diagnostics, + create_notebook_diagnostics, create_syntax_error_diagnostics, }, }; diff --git a/crates/ruff_db/src/diagnostic/render/github.rs b/crates/ruff_db/src/diagnostic/render/github.rs index 0d8b862e42..6be37b3b09 100644 --- a/crates/ruff_db/src/diagnostic/render/github.rs +++ b/crates/ruff_db/src/diagnostic/render/github.rs @@ -5,21 +5,21 @@ use crate::diagnostic::{ UnifiedFile, }; -pub(super) struct GithubRenderer<'a> { +use super::Renderer; + +pub struct GithubRenderer<'a> { resolver: &'a dyn FileResolver, config: &'a DisplayDiagnosticConfig, } impl<'a> GithubRenderer<'a> { - pub(super) fn new(resolver: &'a dyn FileResolver, config: &'a DisplayDiagnosticConfig) -> Self { + pub fn new(resolver: &'a dyn FileResolver, config: &'a DisplayDiagnosticConfig) -> Self { Self { resolver, config } } +} - pub(super) fn render( - &self, - f: &mut std::fmt::Formatter, - diagnostics: &[Diagnostic], - ) -> std::fmt::Result { +impl Renderer for GithubRenderer<'_> { + fn render(&self, f: &mut std::fmt::Formatter, diagnostics: &[Diagnostic]) -> std::fmt::Result { for diagnostic in diagnostics { let severity = match diagnostic.severity() { Severity::Info => "notice", @@ -190,9 +190,8 @@ impl std::fmt::Display for GithubAnnotation<'_> { #[cfg(test)] mod tests { - use crate::diagnostic::{ - DiagnosticFormat, - render::tests::{TestEnvironment, create_diagnostics, create_syntax_error_diagnostics}, + use crate::diagnostic::render::tests::{ + DiagnosticFormat, TestEnvironment, create_diagnostics, create_syntax_error_diagnostics, }; #[test] diff --git a/crates/ruff_db/src/diagnostic/render/gitlab.rs b/crates/ruff_db/src/diagnostic/render/gitlab.rs index f330e268dc..5fef761917 100644 --- a/crates/ruff_db/src/diagnostic/render/gitlab.rs +++ b/crates/ruff_db/src/diagnostic/render/gitlab.rs @@ -9,25 +9,21 @@ use serde::{Serialize, Serializer, ser::SerializeSeq}; use crate::diagnostic::{Diagnostic, DisplayDiagnosticConfig, Severity}; -use super::FileResolver; +use super::{FileResolver, Renderer}; -pub(super) struct GitlabRenderer<'a> { +pub struct GitlabRenderer<'a> { resolver: &'a dyn FileResolver, config: &'a DisplayDiagnosticConfig, } impl<'a> GitlabRenderer<'a> { - pub(super) fn new(resolver: &'a dyn FileResolver, config: &'a DisplayDiagnosticConfig) -> Self { + pub fn new(resolver: &'a dyn FileResolver, config: &'a DisplayDiagnosticConfig) -> Self { Self { resolver, config } } } -impl GitlabRenderer<'_> { - pub(super) fn render( - &self, - f: &mut std::fmt::Formatter, - diagnostics: &[Diagnostic], - ) -> std::fmt::Result { +impl Renderer for GitlabRenderer<'_> { + fn render(&self, f: &mut std::fmt::Formatter, diagnostics: &[Diagnostic]) -> std::fmt::Result { write!( f, "{}", @@ -184,9 +180,8 @@ fn relativize_path_to, R: AsRef>(path: P, project_root: R) #[cfg(test)] mod tests { - use crate::diagnostic::{ - DiagnosticFormat, - render::tests::{create_diagnostics, create_syntax_error_diagnostics}, + use crate::diagnostic::render::tests::{ + DiagnosticFormat, create_diagnostics, create_syntax_error_diagnostics, }; const FINGERPRINT_FILTERS: [(&str, &str); 1] = [( diff --git a/crates/ruff_db/src/diagnostic/render/json.rs b/crates/ruff_db/src/diagnostic/render/json.rs index dba41f8a0b..3c952d2b04 100644 --- a/crates/ruff_db/src/diagnostic/render/json.rs +++ b/crates/ruff_db/src/diagnostic/render/json.rs @@ -10,25 +10,24 @@ use crate::diagnostic::{ ConciseMessage, Diagnostic, DiagnosticSource, DisplayDiagnosticConfig, Severity, }; -use super::FileResolver; +use super::{FileResolver, Renderer}; -pub(super) struct JsonRenderer<'a> { +/// Print diagnostics in JSON format. +/// +/// Unlike [`JsonLinesRenderer`](super::JsonLinesRenderer), this prints all diagnostics as a JSON array. +pub struct JsonRenderer<'a> { resolver: &'a dyn FileResolver, config: &'a DisplayDiagnosticConfig, } impl<'a> JsonRenderer<'a> { - pub(super) fn new(resolver: &'a dyn FileResolver, config: &'a DisplayDiagnosticConfig) -> Self { + pub fn new(resolver: &'a dyn FileResolver, config: &'a DisplayDiagnosticConfig) -> Self { Self { resolver, config } } } -impl JsonRenderer<'_> { - pub(super) fn render( - &self, - f: &mut std::fmt::Formatter, - diagnostics: &[Diagnostic], - ) -> std::fmt::Result { +impl Renderer for JsonRenderer<'_> { + fn render(&self, f: &mut std::fmt::Formatter, diagnostics: &[Diagnostic]) -> std::fmt::Result { write!( f, "{:#}", @@ -252,12 +251,9 @@ struct JsonEdit<'a> { #[cfg(test)] mod tests { - use crate::diagnostic::{ - DiagnosticFormat, - render::tests::{ - TestEnvironment, create_diagnostics, create_notebook_diagnostics, - create_syntax_error_diagnostics, - }, + use crate::diagnostic::render::tests::{ + DiagnosticFormat, TestEnvironment, create_diagnostics, create_notebook_diagnostics, + create_syntax_error_diagnostics, }; #[test] diff --git a/crates/ruff_db/src/diagnostic/render/json_lines.rs b/crates/ruff_db/src/diagnostic/render/json_lines.rs index 44c270e564..34bff04d85 100644 --- a/crates/ruff_db/src/diagnostic/render/json_lines.rs +++ b/crates/ruff_db/src/diagnostic/render/json_lines.rs @@ -1,24 +1,25 @@ use crate::diagnostic::{Diagnostic, DisplayDiagnosticConfig, render::json::diagnostic_to_json}; -use super::FileResolver; +use super::{FileResolver, Renderer}; -pub(super) struct JsonLinesRenderer<'a> { +/// Print diagnostics in JSON format, one per line. +/// +/// This prints each diagnostic as a separate JSON object on its own line. See +/// [`JsonRenderer`](super::JsonRenderer) for an array of all diagnostics. +/// See for more details. +pub struct JsonLinesRenderer<'a> { resolver: &'a dyn FileResolver, config: &'a DisplayDiagnosticConfig, } impl<'a> JsonLinesRenderer<'a> { - pub(super) fn new(resolver: &'a dyn FileResolver, config: &'a DisplayDiagnosticConfig) -> Self { + pub fn new(resolver: &'a dyn FileResolver, config: &'a DisplayDiagnosticConfig) -> Self { Self { resolver, config } } } -impl JsonLinesRenderer<'_> { - pub(super) fn render( - &self, - f: &mut std::fmt::Formatter, - diagnostics: &[Diagnostic], - ) -> std::fmt::Result { +impl Renderer for JsonLinesRenderer<'_> { + fn render(&self, f: &mut std::fmt::Formatter, diagnostics: &[Diagnostic]) -> std::fmt::Result { for diag in diagnostics { writeln!( f, @@ -32,11 +33,9 @@ impl JsonLinesRenderer<'_> { } #[cfg(test)] mod tests { - use crate::diagnostic::{ - DiagnosticFormat, - render::tests::{ - create_diagnostics, create_notebook_diagnostics, create_syntax_error_diagnostics, - }, + use crate::diagnostic::render::tests::{ + DiagnosticFormat, create_diagnostics, create_notebook_diagnostics, + create_syntax_error_diagnostics, }; #[test] diff --git a/crates/ruff_db/src/diagnostic/render/junit.rs b/crates/ruff_db/src/diagnostic/render/junit.rs index f52f4b1950..aa4be4a418 100644 --- a/crates/ruff_db/src/diagnostic/render/junit.rs +++ b/crates/ruff_db/src/diagnostic/render/junit.rs @@ -5,7 +5,10 @@ use quick_junit::{NonSuccessKind, Report, TestCase, TestCaseStatus, TestSuite, X use ruff_source_file::LineColumn; -use crate::diagnostic::{Diagnostic, DisplayDiagnosticConfig, Severity, render::FileResolver}; +use crate::diagnostic::{ + Diagnostic, DisplayDiagnosticConfig, Severity, + render::{FileResolver, Renderer}, +}; /// Print diagnostics as a JUnit-style XML report. /// @@ -15,21 +18,19 @@ use crate::diagnostic::{Diagnostic, DisplayDiagnosticConfig, Severity, render::F /// [`junit.xsd`]: https://github.com/junit-team/junit-framework/blob/2870b7d8fd5bf7c1efe489d3991d3ed3900e82bb/platform-tests/src/test/resources/jenkins-junit.xsd /// [version]: https://llg.cubic.org/docs/junit/ /// [`quick_junit`]: https://docs.rs/quick-junit/latest/quick_junit/ -pub(super) struct JunitRenderer<'a> { +pub struct JunitRenderer<'a> { resolver: &'a dyn FileResolver, config: &'a DisplayDiagnosticConfig, } impl<'a> JunitRenderer<'a> { - pub(super) fn new(resolver: &'a dyn FileResolver, config: &'a DisplayDiagnosticConfig) -> Self { + pub fn new(resolver: &'a dyn FileResolver, config: &'a DisplayDiagnosticConfig) -> Self { Self { resolver, config } } +} - pub(super) fn render( - &self, - f: &mut std::fmt::Formatter, - diagnostics: &[Diagnostic], - ) -> std::fmt::Result { +impl Renderer for JunitRenderer<'_> { + fn render(&self, f: &mut std::fmt::Formatter, diagnostics: &[Diagnostic]) -> std::fmt::Result { let package = format!("org.{}", self.config.program); let mut report = Report::new(self.config.program); @@ -195,9 +196,8 @@ impl std::io::Write for FmtAdapter<'_> { #[cfg(test)] mod tests { - use crate::diagnostic::{ - DiagnosticFormat, - render::tests::{create_diagnostics, create_syntax_error_diagnostics}, + use crate::diagnostic::render::tests::{ + DiagnosticFormat, create_diagnostics, create_syntax_error_diagnostics, }; #[test] diff --git a/crates/ruff_db/src/diagnostic/render/pylint.rs b/crates/ruff_db/src/diagnostic/render/pylint.rs index 2dd4b9f8e6..70dbec15ae 100644 --- a/crates/ruff_db/src/diagnostic/render/pylint.rs +++ b/crates/ruff_db/src/diagnostic/render/pylint.rs @@ -1,4 +1,7 @@ -use crate::diagnostic::{Diagnostic, DisplayDiagnosticConfig, render::FileResolver}; +use crate::diagnostic::{ + Diagnostic, DisplayDiagnosticConfig, + render::{FileResolver, Renderer}, +}; /// Generate violations in Pylint format. /// @@ -9,23 +12,19 @@ use crate::diagnostic::{Diagnostic, DisplayDiagnosticConfig, render::FileResolve /// ``` /// /// See: [Flake8 documentation](https://flake8.pycqa.org/en/latest/internal/formatters.html#pylint-formatter) -pub(super) struct PylintRenderer<'a> { +pub struct PylintRenderer<'a> { resolver: &'a dyn FileResolver, config: &'a DisplayDiagnosticConfig, } impl<'a> PylintRenderer<'a> { - pub(super) fn new(resolver: &'a dyn FileResolver, config: &'a DisplayDiagnosticConfig) -> Self { + pub fn new(resolver: &'a dyn FileResolver, config: &'a DisplayDiagnosticConfig) -> Self { Self { resolver, config } } } -impl PylintRenderer<'_> { - pub(super) fn render( - &self, - f: &mut std::fmt::Formatter, - diagnostics: &[Diagnostic], - ) -> std::fmt::Result { +impl Renderer for PylintRenderer<'_> { + fn render(&self, f: &mut std::fmt::Formatter, diagnostics: &[Diagnostic]) -> std::fmt::Result { for diagnostic in diagnostics { let (filename, row) = diagnostic .primary_span_ref() @@ -68,9 +67,8 @@ impl PylintRenderer<'_> { #[cfg(test)] mod tests { - use crate::diagnostic::{ - DiagnosticFormat, - render::tests::{TestEnvironment, create_diagnostics, create_syntax_error_diagnostics}, + use crate::diagnostic::render::tests::{ + DiagnosticFormat, TestEnvironment, create_diagnostics, create_syntax_error_diagnostics, }; #[test] diff --git a/crates/ruff_db/src/diagnostic/render/rdjson.rs b/crates/ruff_db/src/diagnostic/render/rdjson.rs index 5ab30cbf86..d880bf1a10 100644 --- a/crates/ruff_db/src/diagnostic/render/rdjson.rs +++ b/crates/ruff_db/src/diagnostic/render/rdjson.rs @@ -7,23 +7,24 @@ use ruff_text_size::Ranged; use crate::diagnostic::{ConciseMessage, Diagnostic, DisplayDiagnosticConfig}; -use super::FileResolver; +use super::{FileResolver, Renderer}; +/// Print diagnostics in the JSON format expected by [reviewdog]. +/// +/// [reviewdog]: https://github.com/reviewdog/reviewdog pub struct RdjsonRenderer<'a> { resolver: &'a dyn FileResolver, config: &'a DisplayDiagnosticConfig, } impl<'a> RdjsonRenderer<'a> { - pub(super) fn new(resolver: &'a dyn FileResolver, config: &'a DisplayDiagnosticConfig) -> Self { + pub fn new(resolver: &'a dyn FileResolver, config: &'a DisplayDiagnosticConfig) -> Self { Self { resolver, config } } +} - pub(super) fn render( - &self, - f: &mut std::fmt::Formatter, - diagnostics: &[Diagnostic], - ) -> std::fmt::Result { +impl Renderer for RdjsonRenderer<'_> { + fn render(&self, f: &mut std::fmt::Formatter, diagnostics: &[Diagnostic]) -> std::fmt::Result { write!( f, "{:#}", @@ -208,9 +209,8 @@ struct RdjsonSuggestion<'a> { #[cfg(test)] mod tests { - use crate::diagnostic::{ - DiagnosticFormat, - render::tests::{TestEnvironment, create_diagnostics, create_syntax_error_diagnostics}, + use crate::diagnostic::render::tests::{ + DiagnosticFormat, TestEnvironment, create_diagnostics, create_syntax_error_diagnostics, }; #[test] diff --git a/crates/ruff_linter/src/message/mod.rs b/crates/ruff_linter/src/message/mod.rs index 47c5cc5b50..36355f6a28 100644 --- a/crates/ruff_linter/src/message/mod.rs +++ b/crates/ruff_linter/src/message/mod.rs @@ -6,10 +6,13 @@ use std::path::Path; use ruff_db::panic::PanicError; use rustc_hash::FxHashMap; +use ruff_db::diagnostic::render::{ + AzureRenderer, ConciseRenderer, FullRenderer, GithubRenderer, GitlabRenderer, + JsonLinesRenderer, JsonRenderer, JunitRenderer, PylintRenderer, RdjsonRenderer, Renderer, +}; use ruff_db::diagnostic::{ - Annotation, Diagnostic, DiagnosticFormat, DiagnosticId, DisplayDiagnosticConfig, - DisplayDiagnostics, FileResolver, Input, LintName, SecondaryCode, Severity, Span, - SubDiagnostic, SubDiagnosticSeverity, UnifiedFile, + Annotation, Diagnostic, DiagnosticId, DisplayDiagnosticConfig, FileResolver, Input, LintName, + SecondaryCode, Severity, Span, SubDiagnostic, SubDiagnosticSeverity, UnifiedFile, }; use ruff_db::files::File; @@ -21,7 +24,7 @@ pub(crate) use sarif::SarifEmitter; use crate::Fix; use crate::registry::Rule; -use crate::settings::types::{OutputFormat, RuffOutputFormat}; +use crate::settings::types::OutputFormat; mod grouped; mod sarif; @@ -196,33 +199,34 @@ impl<'a> EmitterContext<'a> { pub fn render_diagnostics( writer: &mut dyn Write, format: OutputFormat, - config: DisplayDiagnosticConfig, + config: &DisplayDiagnosticConfig, context: &EmitterContext<'_>, diagnostics: &[Diagnostic], ) -> std::io::Result<()> { - match DiagnosticFormat::try_from(format) { - Ok(format) => { - let config = config.format(format); - let value = DisplayDiagnostics::new(context, &config, diagnostics); - write!(writer, "{value}")?; - } - Err(RuffOutputFormat::Grouped) => { - GroupedEmitter::default() - .with_show_fix_status(config.show_fix_status()) - .with_applicability(config.fix_applicability()) - .with_preview(config.preview_enabled()) - .with_prefer_rule_codes(config.is_prefer_rule_codes_enabled()) - .emit(writer, diagnostics, context) - .map_err(std::io::Error::other)?; - } - Err(RuffOutputFormat::Sarif) => { - SarifEmitter::new(&config) - .emit(writer, diagnostics, context) - .map_err(std::io::Error::other)?; + match format { + OutputFormat::Concise => ConciseRenderer::new(context, config).write(writer, diagnostics), + OutputFormat::Full => FullRenderer::new(context, config).write(writer, diagnostics), + OutputFormat::Json => JsonRenderer::new(context, config).write(writer, diagnostics), + OutputFormat::JsonLines => { + JsonLinesRenderer::new(context, config).write(writer, diagnostics) } + OutputFormat::Junit => JunitRenderer::new(context, config).write(writer, diagnostics), + OutputFormat::Github => GithubRenderer::new(context, config).write(writer, diagnostics), + OutputFormat::Gitlab => GitlabRenderer::new(context, config).write(writer, diagnostics), + OutputFormat::Pylint => PylintRenderer::new(context, config).write(writer, diagnostics), + OutputFormat::Rdjson => RdjsonRenderer::new(context, config).write(writer, diagnostics), + OutputFormat::Azure => AzureRenderer::new(context, config).write(writer, diagnostics), + OutputFormat::Grouped => GroupedEmitter::default() + .with_show_fix_status(config.show_fix_status()) + .with_applicability(config.fix_applicability()) + .with_preview(config.preview_enabled()) + .with_prefer_rule_codes(config.is_prefer_rule_codes_enabled()) + .emit(writer, diagnostics, context) + .map_err(std::io::Error::other), + OutputFormat::Sarif => SarifEmitter::new(config) + .emit(writer, diagnostics, context) + .map_err(std::io::Error::other), } - - Ok(()) } #[cfg(test)] diff --git a/crates/ruff_linter/src/settings/types.rs b/crates/ruff_linter/src/settings/types.rs index 5812a7f894..782bfdc284 100644 --- a/crates/ruff_linter/src/settings/types.rs +++ b/crates/ruff_linter/src/settings/types.rs @@ -10,7 +10,6 @@ use anyhow::{Context, Result, bail}; use globset::{Candidate, Glob, GlobMatcher, GlobSet, GlobSetBuilder}; use log::debug; use pep440_rs::{VersionSpecifier, VersionSpecifiers}; -use ruff_db::diagnostic::DiagnosticFormat; use rustc_hash::FxHashMap; use serde::{Deserialize, Serialize}; use strum_macros::EnumIter; @@ -601,33 +600,6 @@ impl Display for OutputFormat { } } -/// The subset of output formats only implemented in Ruff, not in `ruff_db` via `DisplayDiagnostics`. -pub enum RuffOutputFormat { - Grouped, - Sarif, -} - -impl TryFrom for DiagnosticFormat { - type Error = RuffOutputFormat; - - fn try_from(format: OutputFormat) -> std::result::Result { - match format { - OutputFormat::Concise => Ok(DiagnosticFormat::Concise), - OutputFormat::Full => Ok(DiagnosticFormat::Full), - OutputFormat::Json => Ok(DiagnosticFormat::Json), - OutputFormat::JsonLines => Ok(DiagnosticFormat::JsonLines), - OutputFormat::Junit => Ok(DiagnosticFormat::Junit), - OutputFormat::Gitlab => Ok(DiagnosticFormat::Gitlab), - OutputFormat::Pylint => Ok(DiagnosticFormat::Pylint), - OutputFormat::Rdjson => Ok(DiagnosticFormat::Rdjson), - OutputFormat::Azure => Ok(DiagnosticFormat::Azure), - OutputFormat::Github => Ok(DiagnosticFormat::Github), - OutputFormat::Grouped => Err(RuffOutputFormat::Grouped), - OutputFormat::Sarif => Err(RuffOutputFormat::Sarif), - } - } -} - #[derive(Clone, Debug, PartialEq, Eq, Serialize, Deserialize, Hash)] #[serde(try_from = "String")] pub struct RequiredVersion(VersionSpecifiers); diff --git a/crates/ruff_linter/src/test.rs b/crates/ruff_linter/src/test.rs index 08a9607b64..de8cb25cc2 100644 --- a/crates/ruff_linter/src/test.rs +++ b/crates/ruff_linter/src/test.rs @@ -9,9 +9,8 @@ use anyhow::Result; use itertools::Itertools; use rustc_hash::FxHashMap; -use ruff_db::diagnostic::{ - Diagnostic, DiagnosticFormat, DisplayDiagnosticConfig, DisplayDiagnostics, Span, -}; +use ruff_db::diagnostic::render::{FullRenderer, Renderer}; +use ruff_db::diagnostic::{Diagnostic, DisplayDiagnosticConfig, Span}; use ruff_notebook::Notebook; #[cfg(test)] use ruff_notebook::NotebookError; @@ -486,35 +485,30 @@ pub(crate) fn print_jupyter_messages( notebook: &Notebook, ) -> String { let config = DisplayDiagnosticConfig::new("ruff") - .format(DiagnosticFormat::Full) .hide_severity(true) .with_show_fix_status(true) .with_fix_applicability(Applicability::DisplayOnly); - DisplayDiagnostics::new( + FullRenderer::new( &EmitterContext::new(&FxHashMap::from_iter([( path.file_name().unwrap().to_string_lossy().to_string(), notebook.index().clone(), )])), &config, - diagnostics, ) + .display(diagnostics) .to_string() } pub(crate) fn print_messages(diagnostics: &[Diagnostic]) -> String { let config = DisplayDiagnosticConfig::new("ruff") - .format(DiagnosticFormat::Full) .hide_severity(true) .with_show_fix_status(true) .with_fix_applicability(Applicability::DisplayOnly); - DisplayDiagnostics::new( - &EmitterContext::new(&FxHashMap::default()), - &config, - diagnostics, - ) - .to_string() + FullRenderer::new(&EmitterContext::new(&FxHashMap::default()), &config) + .display(diagnostics) + .to_string() } #[macro_export] diff --git a/crates/ruff_python_formatter/tests/fixtures.rs b/crates/ruff_python_formatter/tests/fixtures.rs index 0ee9bd0977..d5dd7f0b24 100644 --- a/crates/ruff_python_formatter/tests/fixtures.rs +++ b/crates/ruff_python_formatter/tests/fixtures.rs @@ -2,9 +2,10 @@ use crate::normalizer::Normalizer; use anyhow::anyhow; use datatest_stable::Utf8Path; use insta::assert_snapshot; +use ruff_db::diagnostic::render::{FullRenderer, Renderer}; use ruff_db::diagnostic::{ - Annotation, Diagnostic, DiagnosticFormat, DiagnosticId, DisplayDiagnosticConfig, - DisplayDiagnostics, DummyFileResolver, Severity, Span, SubDiagnostic, SubDiagnosticSeverity, + Annotation, Diagnostic, DiagnosticId, DisplayDiagnosticConfig, DummyFileResolver, Severity, + Span, SubDiagnostic, SubDiagnosticSeverity, }; use ruff_formatter::FormatOptions; use ruff_python_ast::Mod; @@ -164,11 +165,8 @@ fn black_compatibility(input_path: &Utf8Path, content: String) -> datatest_stabl writeln!( snapshot, "{}", - DisplayDiagnostics::new( - &DummyFileResolver, - &DisplayDiagnosticConfig::new("ruff").format(DiagnosticFormat::Full), - &unsupported_syntax_errors - ) + FullRenderer::new(&DummyFileResolver, &DisplayDiagnosticConfig::new("ruff")) + .display(&unsupported_syntax_errors) ) .unwrap(); } @@ -243,11 +241,8 @@ fn format(input_path: &Utf8Path, content: String) -> datatest_stable::Result<()> writeln!( snapshot, "### Unsupported Syntax Errors\n{}", - DisplayDiagnostics::new( - &DummyFileResolver, - &DisplayDiagnosticConfig::new("ruff").format(DiagnosticFormat::Full), - &unsupported_syntax_errors - ) + FullRenderer::new(&DummyFileResolver, &DisplayDiagnosticConfig::new("ruff")) + .display(&unsupported_syntax_errors) ) .unwrap(); } @@ -289,11 +284,8 @@ fn format(input_path: &Utf8Path, content: String) -> datatest_stable::Result<()> writeln!( snapshot, "## Unsupported Syntax Errors\n{}", - DisplayDiagnostics::new( - &DummyFileResolver, - &DisplayDiagnosticConfig::new("ruff").format(DiagnosticFormat::Full), - &unsupported_syntax_errors - ) + FullRenderer::new(&DummyFileResolver, &DisplayDiagnosticConfig::new("ruff")) + .display(&unsupported_syntax_errors) ) .unwrap(); } diff --git a/crates/ty/src/lib.rs b/crates/ty/src/lib.rs index 3b78464131..487a8c445f 100644 --- a/crates/ty/src/lib.rs +++ b/crates/ty/src/lib.rs @@ -18,9 +18,7 @@ use colored::Colorize; use crossbeam::channel as crossbeam_channel; use rayon::ThreadPoolBuilder; use ruff_db::cancellation::{Canceled, CancellationToken, CancellationTokenSource}; -use ruff_db::diagnostic::{ - Diagnostic, DiagnosticId, DisplayDiagnosticConfig, DisplayDiagnostics, Severity, -}; +use ruff_db::diagnostic::{Diagnostic, DiagnosticId, DisplayDiagnosticConfig, Severity}; use ruff_db::files::File; use ruff_db::system::{OsSystem, System, SystemPath, SystemPathBuf}; use ruff_db::{STACK_SIZE, max_parallelism}; @@ -632,7 +630,6 @@ impl MainLoop { if stdout.is_enabled() { let mut stdout = BufWriter::new(stdout); let display_config = DisplayDiagnosticConfig::new("ty") - .format(terminal_settings.output_format.into()) .color(colored::control::SHOULD_COLORIZE.should_colorize()) .with_cancellation_token(Some(self.cancellation_token.clone())) .context(0); @@ -640,7 +637,9 @@ impl MainLoop { write!( stdout, "{}", - DisplayDiagnostics::new(db, &display_config, diagnostics) + terminal_settings + .output_format + .display(db, &display_config, diagnostics) )?; stdout.flush()?; } diff --git a/crates/ty/tests/file_watching.rs b/crates/ty/tests/file_watching.rs index 61e5c05a01..4d787c25bb 100644 --- a/crates/ty/tests/file_watching.rs +++ b/crates/ty/tests/file_watching.rs @@ -7,9 +7,7 @@ use insta::assert_snapshot; #[cfg(feature = "test-uv")] use insta::internals::SettingsBindDropGuard; use ruff_db::Db as _; -use ruff_db::diagnostic::{ - Diagnostic, DiagnosticFormat, DisplayDiagnosticConfig, DisplayDiagnostics, -}; +use ruff_db::diagnostic::{Diagnostic, DisplayDiagnosticConfig}; use ruff_db::files::{File, FileError, system_path_to_file}; use ruff_db::source::source_text; use ruff_db::system::{ @@ -20,7 +18,7 @@ use ruff_python_ast::PythonVersion; use ruff_python_trivia::textwrap::dedent; use ruff_ranged_value::{RangedValue, ValueSource}; use ty_module_resolver::{Module, ModuleName}; -use ty_project::metadata::options::{EnvironmentOptions, Options, SrcOptions}; +use ty_project::metadata::options::{EnvironmentOptions, Options, OutputFormat, SrcOptions}; use ty_project::metadata::pyproject::{PyProject, Tool}; use ty_project::metadata::python_version::SupportedPythonVersion; use ty_project::metadata::value::{RelativeGlobPattern, RelativePathBuf}; @@ -318,12 +316,9 @@ impl TestCase { } fn render_diagnostics(&self, diagnostics: &[Diagnostic]) -> String { - DisplayDiagnostics::new( - self.db(), - &DisplayDiagnosticConfig::new("ty").format(DiagnosticFormat::Concise), - diagnostics, - ) - .to_string() + OutputFormat::Concise + .display(self.db(), &DisplayDiagnosticConfig::new("ty"), diagnostics) + .to_string() } } diff --git a/crates/ty_ide/src/code_action.rs b/crates/ty_ide/src/code_action.rs index 113f18be11..decb37a9d5 100644 --- a/crates/ty_ide/src/code_action.rs +++ b/crates/ty_ide/src/code_action.rs @@ -76,8 +76,8 @@ mod tests { use insta::assert_snapshot; use ruff_db::{ diagnostic::{ - Annotation, Diagnostic, DiagnosticFormat, DiagnosticId, DisplayDiagnosticConfig, - LintName, Span, SubDiagnostic, + Annotation, Diagnostic, DiagnosticId, DisplayDiagnosticConfig, LintName, Span, + SubDiagnostic, }, files::{File, system_path_to_file}, system::{DbWithWritableSystem, SystemPathBuf}, @@ -966,10 +966,7 @@ mod tests { let mut buf = String::new(); - let config = DisplayDiagnosticConfig::new("ty") - .color(false) - .context(0) - .format(DiagnosticFormat::Full); + let config = DisplayDiagnosticConfig::new("ty").color(false).context(0); for mut action in code_actions( &self.db, diff --git a/crates/ty_ide/src/hover.rs b/crates/ty_ide/src/hover.rs index 967f0a1a98..f29d26994e 100644 --- a/crates/ty_ide/src/hover.rs +++ b/crates/ty_ide/src/hover.rs @@ -507,8 +507,7 @@ mod tests { use insta::assert_snapshot; use ruff_db::diagnostic::{ - Annotation, Diagnostic, DiagnosticFormat, DiagnosticId, DisplayDiagnosticConfig, LintName, - Severity, Span, + Annotation, Diagnostic, DiagnosticId, DisplayDiagnosticConfig, LintName, Severity, Span, }; use ruff_python_ast::PythonVersion; use ruff_text_size::{Ranged, TextRange}; @@ -7451,10 +7450,7 @@ type U = MyType ) .unwrap(); - let config = DisplayDiagnosticConfig::new("ty") - .color(false) - .context(0) - .format(DiagnosticFormat::Full); + let config = DisplayDiagnosticConfig::new("ty").color(false).context(0); let mut diagnostic = Diagnostic::new( DiagnosticId::Lint(LintName::of("hover")), diff --git a/crates/ty_ide/src/inlay_hints.rs b/crates/ty_ide/src/inlay_hints.rs index 23fa6292ab..9426e99b22 100644 --- a/crates/ty_ide/src/inlay_hints.rs +++ b/crates/ty_ide/src/inlay_hints.rs @@ -852,8 +852,8 @@ mod tests { use itertools::Itertools; use ruff_db::{ diagnostic::{ - Annotation, Diagnostic, DiagnosticFormat, DiagnosticId, DisplayDiagnosticConfig, - LintName, Severity, Span, SubDiagnostic, SubDiagnosticSeverity, + Annotation, Diagnostic, DiagnosticId, DisplayDiagnosticConfig, LintName, Severity, + Span, SubDiagnostic, SubDiagnosticSeverity, }, files::{File, FileRange, system_path_to_file}, source::source_text, @@ -1406,10 +1406,7 @@ Source with applied edits: let mut buf = String::new(); - let config = DisplayDiagnosticConfig::new("ty") - .color(false) - .context(0) - .format(DiagnosticFormat::Full); + let config = DisplayDiagnosticConfig::new("ty").color(false).context(0); let diag = diagnostic.into_diagnostic(); let config = diff --git a/crates/ty_ide/src/lib.rs b/crates/ty_ide/src/lib.rs index 48a125a725..0fd3f36878 100644 --- a/crates/ty_ide/src/lib.rs +++ b/crates/ty_ide/src/lib.rs @@ -407,9 +407,7 @@ mod tests { use insta::internals::SettingsBindDropGuard; use ruff_db::Db; - use ruff_db::diagnostic::{ - Annotation, Diagnostic, DiagnosticFormat, DisplayDiagnosticConfig, UnifiedFile, - }; + use ruff_db::diagnostic::{Annotation, Diagnostic, DisplayDiagnosticConfig, UnifiedFile}; use ruff_db::files::{File, FileRootKind, system_path_to_file}; use ruff_db::parsed::{ParsedModuleRef, parsed_module}; use ruff_db::source::{SourceText, source_text}; @@ -462,10 +460,7 @@ mod tests { let mut buf = String::new(); - let config = DisplayDiagnosticConfig::new("ty") - .color(false) - .context(0) - .format(DiagnosticFormat::Full); + let config = DisplayDiagnosticConfig::new("ty").color(false).context(0); for diagnostic in diagnostics { let diag = diagnostic.into_diagnostic(); let config = config diff --git a/crates/ty_project/src/metadata/options.rs b/crates/ty_project/src/metadata/options.rs index afdb3f5ed9..5ebb93e593 100644 --- a/crates/ty_project/src/metadata/options.rs +++ b/crates/ty_project/src/metadata/options.rs @@ -9,9 +9,14 @@ use crate::metadata::value::{RelativeGlobPattern, RelativePathBuf}; use ordermap::OrderMap; use pep440_rs::VersionSpecifiers; use ruff_db::RustDoc; +#[cfg(feature = "junit")] +use ruff_db::diagnostic::render::JunitRenderer; +use ruff_db::diagnostic::render::{ + ConciseRenderer, FileResolver, FullRenderer, GithubRenderer, GitlabRenderer, Renderer, +}; use ruff_db::diagnostic::{ - Annotation, Diagnostic, DiagnosticFormat, DiagnosticId, DisplayDiagnosticConfig, Severity, - Span, SubDiagnostic, SubDiagnosticSeverity, + Annotation, Diagnostic, DiagnosticId, DisplayDiagnosticConfig, Severity, Span, SubDiagnostic, + SubDiagnosticSeverity, }; use ruff_db::system::{System, SystemPath, SystemPathBuf}; use ruff_db::vendored::VendoredFileSystem; @@ -1473,18 +1478,22 @@ impl OutputFormat { pub const fn is_human_readable(&self) -> bool { matches!(self, OutputFormat::Full | OutputFormat::Concise) } -} -impl From for DiagnosticFormat { - fn from(value: OutputFormat) -> Self { - match value { - OutputFormat::Full => Self::Full, - OutputFormat::Concise => Self::Concise, - OutputFormat::Gitlab => Self::Gitlab, - OutputFormat::Github => Self::Github, + /// Display diagnostics in this output format. + pub fn display<'a>( + self, + resolver: &'a dyn FileResolver, + config: &'a DisplayDiagnosticConfig, + diagnostics: &'a [Diagnostic], + ) -> impl Display + use<'a> { + fmt::from_fn(move |f| match self { + Self::Full => FullRenderer::new(resolver, config).render(f, diagnostics), + Self::Concise => ConciseRenderer::new(resolver, config).render(f, diagnostics), + Self::Gitlab => GitlabRenderer::new(resolver, config).render(f, diagnostics), + Self::Github => GithubRenderer::new(resolver, config).render(f, diagnostics), #[cfg(feature = "junit")] - OutputFormat::Junit => Self::Junit, - } + Self::Junit => JunitRenderer::new(resolver, config).render(f, diagnostics), + }) } } @@ -2276,16 +2285,16 @@ impl ToSettingsError { let db: &dyn ruff_db::Db = db; fmt::from_fn(move |f| { - let display_config = DisplayDiagnosticConfig::new("ty") - .format(self.output_format.into()) - .color(self.color); + let display_config = DisplayDiagnosticConfig::new("ty").color(self.color); write!( f, "{}", - self.diagnostic - .to_diagnostic() - .display(&db, &display_config) + self.output_format.display( + &db, + &display_config, + &[self.diagnostic.to_diagnostic()] + ) ) }) } diff --git a/crates/ty_python_semantic/src/fixes.rs b/crates/ty_python_semantic/src/fixes.rs index fcbbdbaf15..f404ed91b9 100644 --- a/crates/ty_python_semantic/src/fixes.rs +++ b/crates/ty_python_semantic/src/fixes.rs @@ -1,7 +1,8 @@ use crate::{SuppressFix, is_unused_ignore_comment_lint, suppress_all}; use ruff_db::PythonFile; use ruff_db::cancellation::{Canceled, CancellationToken}; -use ruff_db::diagnostic::{DisplayDiagnosticConfig, DisplayDiagnostics}; +use ruff_db::diagnostic::DisplayDiagnosticConfig; +use ruff_db::diagnostic::render::{FullRenderer, Renderer}; use ruff_db::parsed::{ParsedModuleRef, parsed_module}; use ruff_db::source::SourceText; use ruff_db::system::{SystemPath, SystemPathBuf, WritableSystem}; @@ -315,11 +316,8 @@ fn create_fix_introduced_syntax_error_diagnostic( diag.info(format_args!( "Introduced syntax errors:\n\n{}", - DisplayDiagnostics::new( - &file_db, - &DisplayDiagnosticConfig::new("ty"), - &parse_diagnostics - ) + FullRenderer::new(&file_db, &DisplayDiagnosticConfig::new("ty")) + .display(&parse_diagnostics) )); diag @@ -807,9 +805,9 @@ where mod tests { use insta::assert_snapshot; use ruff_db::cancellation::CancellationTokenSource; + use ruff_db::diagnostic::render::{FullRenderer, Renderer}; use ruff_db::diagnostic::{ - Annotation, Diagnostic, DiagnosticId, DisplayDiagnosticConfig, DisplayDiagnostics, - Severity, Span, + Annotation, Diagnostic, DiagnosticId, DisplayDiagnosticConfig, Severity, Span, }; use ruff_db::files::{File, system_path_to_file}; use ruff_db::parsed::parsed_module; @@ -2073,11 +2071,8 @@ class B(A): writeln!( output, "## Diagnostics after applying fixes\n\n{diagnostics}\n", - diagnostics = DisplayDiagnostics::new( - &db, - &DisplayDiagnosticConfig::new("ty"), - &fixes.diagnostics - ) + diagnostics = FullRenderer::new(&db, &DisplayDiagnosticConfig::new("ty")) + .display(&fixes.diagnostics) ) .unwrap(); } @@ -2094,11 +2089,8 @@ class B(A): writeln!( &mut output, "## New diagnostics after re-checking file\n\n{diagnostics}\n", - diagnostics = DisplayDiagnostics::new( - &db, - &DisplayDiagnosticConfig::new("ty"), - &new_diagnostics - ) + diagnostics = FullRenderer::new(&db, &DisplayDiagnosticConfig::new("ty")) + .display(&new_diagnostics) ) .unwrap(); } diff --git a/crates/ty_python_semantic/src/types/dedicated/pytest/collection.rs b/crates/ty_python_semantic/src/types/dedicated/pytest/collection.rs index 35ab56c6d3..cb7b426084 100644 --- a/crates/ty_python_semantic/src/types/dedicated/pytest/collection.rs +++ b/crates/ty_python_semantic/src/types/dedicated/pytest/collection.rs @@ -498,8 +498,9 @@ fn is_default_pytest_test_file(db: &dyn Db, file: ProgramFile<'_>) -> bool { #[cfg(test)] mod tests { use insta::assert_snapshot; + use ruff_db::diagnostic::render::{FullRenderer, Renderer}; use ruff_db::diagnostic::{ - Annotation, Diagnostic, DiagnosticId, DisplayDiagnosticConfig, DisplayDiagnostics, Severity, + Annotation, Diagnostic, DiagnosticId, DisplayDiagnosticConfig, Severity, }; use ruff_db::files::{FileRange, system_path_to_file}; use ruff_db::parsed::parsed_module; @@ -1320,13 +1321,10 @@ mark: MarkGenerator }) .collect::>(); - DisplayDiagnostics::new( - db, - &DisplayDiagnosticConfig::new("ty").context(0), - &diagnostics, - ) - .to_string() - .replace('\\', "/") + FullRenderer::new(db, &DisplayDiagnosticConfig::new("ty").context(0)) + .display(&diagnostics) + .to_string() + .replace('\\', "/") } fn program_file<'db>(db: &'db TestDb, path: &str) -> ProgramFile<'db> { diff --git a/crates/ty_python_semantic/src/types/dedicated/pytest/fixtures.rs b/crates/ty_python_semantic/src/types/dedicated/pytest/fixtures.rs index aee22dfc0c..461d433113 100644 --- a/crates/ty_python_semantic/src/types/dedicated/pytest/fixtures.rs +++ b/crates/ty_python_semantic/src/types/dedicated/pytest/fixtures.rs @@ -1582,9 +1582,10 @@ fn end_of_scope_definition<'db>( #[cfg(test)] mod tests { use insta::assert_snapshot; + use ruff_db::diagnostic::render::{FullRenderer, Renderer}; use ruff_db::diagnostic::{ - Annotation, Diagnostic, DiagnosticId, DisplayDiagnosticConfig, DisplayDiagnostics, - Severity, SubDiagnostic, SubDiagnosticSeverity, + Annotation, Diagnostic, DiagnosticId, DisplayDiagnosticConfig, Severity, SubDiagnostic, + SubDiagnosticSeverity, }; use ruff_db::files::system_path_to_file; use ruff_db::parsed::parsed_module; @@ -3527,13 +3528,10 @@ resource = None } diagnostic.sub(resolved); - DisplayDiagnostics::new( - db, - &DisplayDiagnosticConfig::new("ty").context(0), - &[diagnostic], - ) - .to_string() - .replace('\\', "/") + FullRenderer::new(db, &DisplayDiagnosticConfig::new("ty").context(0)) + .display(&[diagnostic]) + .to_string() + .replace('\\', "/") } fn parameter_definition<'db>(&'db self, parameter_name: &str) -> Definition<'db> { diff --git a/crates/ty_python_semantic/src/types/ide_support/unreachable_code.rs b/crates/ty_python_semantic/src/types/ide_support/unreachable_code.rs index 3da1ea8858..c58081a08a 100644 --- a/crates/ty_python_semantic/src/types/ide_support/unreachable_code.rs +++ b/crates/ty_python_semantic/src/types/ide_support/unreachable_code.rs @@ -94,8 +94,9 @@ mod tests { use super::{UnreachableKind, unreachable_ranges}; use crate::db::tests::{TestDb, TestDbBuilder}; use insta::assert_snapshot; + use ruff_db::diagnostic::render::{FullRenderer, Renderer}; use ruff_db::diagnostic::{ - Annotation, Diagnostic, DiagnosticId, DisplayDiagnosticConfig, DisplayDiagnostics, Severity, + Annotation, Diagnostic, DiagnosticId, DisplayDiagnosticConfig, Severity, }; use ruff_db::files::{FileRange, system_path_to_file}; use ruff_python_ast::PythonVersion; @@ -168,13 +169,10 @@ mod tests { }) .collect::>(); - DisplayDiagnostics::new( - db, - &DisplayDiagnosticConfig::new("ty").context(0), - &diagnostics, - ) - .to_string() - .replace('\\', "/") + FullRenderer::new(db, &DisplayDiagnosticConfig::new("ty").context(0)) + .display(&diagnostics) + .to_string() + .replace('\\', "/") } #[test]