diff --git a/crates/ruff/src/commands/check.rs b/crates/ruff/src/commands/check.rs index f6e7c57ddb..e8b5817db4 100644 --- a/crates/ruff/src/commands/check.rs +++ b/crates/ruff/src/commands/check.rs @@ -227,7 +227,8 @@ mod test { use rustc_hash::FxHashMap; use tempfile::TempDir; - use ruff_linter::message::{Emitter, EmitterContext, TextEmitter}; + use ruff_db::diagnostic::{DiagnosticFormat, DisplayDiagnosticConfig, DisplayDiagnostics}; + use ruff_linter::message::EmitterContext; use ruff_linter::registry::Rule; use ruff_linter::settings::types::UnsafeFixes; use ruff_linter::settings::{LinterSettings, flags}; @@ -280,19 +281,16 @@ mod test { UnsafeFixes::Enabled, ) .unwrap(); - let mut output = Vec::new(); - TextEmitter::default() - .with_show_fix_status(true) - .with_color(false) - .emit( - &mut output, - &diagnostics.inner, - &EmitterContext::new(&FxHashMap::default()), - ) - .unwrap(); - - let messages = String::from_utf8(output).unwrap(); + let config = DisplayDiagnosticConfig::default() + .format(DiagnosticFormat::Concise) + .hide_severity(true); + let messages = DisplayDiagnostics::new( + &EmitterContext::new(&FxHashMap::default()), + &config, + &diagnostics.inner, + ) + .to_string(); insta::with_settings!({ omit_expression => true, diff --git a/crates/ruff/src/printer.rs b/crates/ruff/src/printer.rs index 2cfc0e7492..5c1b1d0e6a 100644 --- a/crates/ruff/src/printer.rs +++ b/crates/ruff/src/printer.rs @@ -10,12 +10,11 @@ use ruff_linter::linter::FixTable; use serde::Serialize; use ruff_db::diagnostic::{ - Diagnostic, DiagnosticFormat, DisplayDiagnosticConfig, DisplayDiagnostics, - DisplayGithubDiagnostics, GithubRenderer, SecondaryCode, + Diagnostic, DiagnosticFormat, DisplayDiagnosticConfig, DisplayDiagnostics, SecondaryCode, }; use ruff_linter::fs::relativize_path; use ruff_linter::logging::LogLevel; -use ruff_linter::message::{Emitter, EmitterContext, GroupedEmitter, SarifEmitter, TextEmitter}; +use ruff_linter::message::{EmitterContext, render_diagnostics}; use ruff_linter::notify_user; use ruff_linter::settings::flags::{self}; use ruff_linter::settings::types::{OutputFormat, UnsafeFixes}; @@ -225,86 +224,28 @@ impl Printer { let context = EmitterContext::new(&diagnostics.notebook_indexes); let fixables = FixableStatistics::try_from(diagnostics, self.unsafe_fixes); - let config = DisplayDiagnosticConfig::default().preview(preview); + let config = DisplayDiagnosticConfig::default() + .preview(preview) + .hide_severity(true) + .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()) + .show_fix_diff(preview); - match self.format { - OutputFormat::Json => { - let config = config.format(DiagnosticFormat::Json); - let value = DisplayDiagnostics::new(&context, &config, &diagnostics.inner); - write!(writer, "{value}")?; - } - OutputFormat::Rdjson => { - let config = config.format(DiagnosticFormat::Rdjson); - let value = DisplayDiagnostics::new(&context, &config, &diagnostics.inner); - write!(writer, "{value}")?; - } - OutputFormat::JsonLines => { - let config = config.format(DiagnosticFormat::JsonLines); - let value = DisplayDiagnostics::new(&context, &config, &diagnostics.inner); - write!(writer, "{value}")?; - } - OutputFormat::Junit => { - let config = config.format(DiagnosticFormat::Junit); - let value = DisplayDiagnostics::new(&context, &config, &diagnostics.inner); - write!(writer, "{value}")?; - } - OutputFormat::Concise | OutputFormat::Full => { - TextEmitter::default() - .with_show_fix_status(show_fix_status(self.fix_mode, fixables.as_ref())) - .with_show_fix_diff(self.format == OutputFormat::Full && preview) - .with_show_source(self.format == OutputFormat::Full) - .with_fix_applicability(self.unsafe_fixes.required_applicability()) - .with_preview(preview) - .emit(writer, &diagnostics.inner, &context)?; + render_diagnostics(writer, self.format, config, &context, &diagnostics.inner)?; - if self.flags.intersects(Flags::SHOW_FIX_SUMMARY) { - if !diagnostics.fixed.is_empty() { - writeln!(writer)?; - print_fix_summary(writer, &diagnostics.fixed)?; - writeln!(writer)?; - } + if matches!( + self.format, + OutputFormat::Full | OutputFormat::Concise | OutputFormat::Grouped + ) { + if self.flags.intersects(Flags::SHOW_FIX_SUMMARY) { + if !diagnostics.fixed.is_empty() { + writeln!(writer)?; + print_fix_summary(writer, &diagnostics.fixed)?; + writeln!(writer)?; } - - self.write_summary_text(writer, diagnostics)?; - } - OutputFormat::Grouped => { - GroupedEmitter::default() - .with_show_fix_status(show_fix_status(self.fix_mode, fixables.as_ref())) - .with_unsafe_fixes(self.unsafe_fixes) - .emit(writer, &diagnostics.inner, &context)?; - - if self.flags.intersects(Flags::SHOW_FIX_SUMMARY) { - if !diagnostics.fixed.is_empty() { - writeln!(writer)?; - print_fix_summary(writer, &diagnostics.fixed)?; - writeln!(writer)?; - } - } - self.write_summary_text(writer, diagnostics)?; - } - OutputFormat::Github => { - let renderer = GithubRenderer::new(&context, "Ruff"); - let value = DisplayGithubDiagnostics::new(&renderer, &diagnostics.inner); - write!(writer, "{value}")?; - } - OutputFormat::Gitlab => { - let config = config.format(DiagnosticFormat::Gitlab); - let value = DisplayDiagnostics::new(&context, &config, &diagnostics.inner); - write!(writer, "{value}")?; - } - OutputFormat::Pylint => { - let config = config.format(DiagnosticFormat::Pylint); - let value = DisplayDiagnostics::new(&context, &config, &diagnostics.inner); - write!(writer, "{value}")?; - } - OutputFormat::Azure => { - let config = config.format(DiagnosticFormat::Azure); - let value = DisplayDiagnostics::new(&context, &config, &diagnostics.inner); - write!(writer, "{value}")?; - } - OutputFormat::Sarif => { - SarifEmitter.emit(writer, &diagnostics.inner, &context)?; } + self.write_summary_text(writer, diagnostics)?; } writer.flush()?; @@ -448,11 +389,22 @@ impl Printer { } let context = EmitterContext::new(&diagnostics.notebook_indexes); - TextEmitter::default() + let format = if preview { + DiagnosticFormat::Full + } else { + DiagnosticFormat::Concise + }; + let config = DisplayDiagnosticConfig::default() + .hide_severity(true) + .color(!cfg!(test) && colored::control::SHOULD_COLORIZE.should_colorize()) .with_show_fix_status(show_fix_status(self.fix_mode, fixables.as_ref())) - .with_show_source(preview) - .with_fix_applicability(self.unsafe_fixes.required_applicability()) - .emit(writer, &diagnostics.inner, &context)?; + .format(format) + .with_fix_applicability(self.unsafe_fixes.required_applicability()); + write!( + writer, + "{}", + DisplayDiagnostics::new(&context, &config, &diagnostics.inner) + )?; } writer.flush()?; diff --git a/crates/ruff/tests/lint.rs b/crates/ruff/tests/lint.rs index 75e267e1b4..c44f7b9ecd 100644 --- a/crates/ruff/tests/lint.rs +++ b/crates/ruff/tests/lint.rs @@ -6199,6 +6199,36 @@ match 42: # invalid-syntax Ok(()) } +#[test_case::test_case("concise"; "concise_show_fixes")] +#[test_case::test_case("full"; "full_show_fixes")] +#[test_case::test_case("grouped"; "grouped_show_fixes")] +fn output_format_show_fixes(output_format: &str) -> Result<()> { + let tempdir = TempDir::new()?; + let input = tempdir.path().join("input.py"); + fs::write(&input, "import os # F401")?; + + let snapshot = format!("output_format_show_fixes_{output_format}"); + + assert_cmd_snapshot!( + snapshot, + Command::new(get_cargo_bin(BIN_NAME)) + .args([ + "check", + "--no-cache", + "--output-format", + output_format, + "--select", + "F401", + "--fix", + "--show-fixes", + "input.py", + ]) + .current_dir(&tempdir), + ); + + Ok(()) +} + #[test] fn up045_nested_optional_flatten_all() { let contents = "\ diff --git a/crates/ruff/tests/snapshots/lint__output_format_show_fixes_concise.snap b/crates/ruff/tests/snapshots/lint__output_format_show_fixes_concise.snap new file mode 100644 index 0000000000..e72b277305 --- /dev/null +++ b/crates/ruff/tests/snapshots/lint__output_format_show_fixes_concise.snap @@ -0,0 +1,26 @@ +--- +source: crates/ruff/tests/lint.rs +info: + program: ruff + args: + - check + - "--no-cache" + - "--output-format" + - concise + - "--select" + - F401 + - "--fix" + - "--show-fixes" + - input.py +--- +success: true +exit_code: 0 +----- stdout ----- + +Fixed 1 error: +- input.py: + 1 × F401 (unused-import) + +Found 1 error (1 fixed, 0 remaining). + +----- stderr ----- diff --git a/crates/ruff/tests/snapshots/lint__output_format_show_fixes_full.snap b/crates/ruff/tests/snapshots/lint__output_format_show_fixes_full.snap new file mode 100644 index 0000000000..96bbccb76e --- /dev/null +++ b/crates/ruff/tests/snapshots/lint__output_format_show_fixes_full.snap @@ -0,0 +1,26 @@ +--- +source: crates/ruff/tests/lint.rs +info: + program: ruff + args: + - check + - "--no-cache" + - "--output-format" + - full + - "--select" + - F401 + - "--fix" + - "--show-fixes" + - input.py +--- +success: true +exit_code: 0 +----- stdout ----- + +Fixed 1 error: +- input.py: + 1 × F401 (unused-import) + +Found 1 error (1 fixed, 0 remaining). + +----- stderr ----- diff --git a/crates/ruff/tests/snapshots/lint__output_format_show_fixes_grouped.snap b/crates/ruff/tests/snapshots/lint__output_format_show_fixes_grouped.snap new file mode 100644 index 0000000000..ce0e16ff12 --- /dev/null +++ b/crates/ruff/tests/snapshots/lint__output_format_show_fixes_grouped.snap @@ -0,0 +1,26 @@ +--- +source: crates/ruff/tests/lint.rs +info: + program: ruff + args: + - check + - "--no-cache" + - "--output-format" + - grouped + - "--select" + - F401 + - "--fix" + - "--show-fixes" + - input.py +--- +success: true +exit_code: 0 +----- stdout ----- + +Fixed 1 error: +- input.py: + 1 × F401 (unused-import) + +Found 1 error (1 fixed, 0 remaining). + +----- stderr ----- diff --git a/crates/ruff_db/src/diagnostic/mod.rs b/crates/ruff_db/src/diagnostic/mod.rs index 99b8b9d83b..413e08cbe2 100644 --- a/crates/ruff_db/src/diagnostic/mod.rs +++ b/crates/ruff_db/src/diagnostic/mod.rs @@ -1353,7 +1353,7 @@ impl DisplayDiagnosticConfig { } /// Whether to show a fix's availability or not. - pub fn show_fix_status(self, yes: bool) -> DisplayDiagnosticConfig { + pub fn with_show_fix_status(self, yes: bool) -> DisplayDiagnosticConfig { DisplayDiagnosticConfig { show_fix_status: yes, ..self @@ -1374,12 +1374,20 @@ impl DisplayDiagnosticConfig { /// availability for unsafe or display-only fixes. /// /// Note that this option is currently ignored when `hide_severity` is false. - pub fn fix_applicability(self, applicability: Applicability) -> DisplayDiagnosticConfig { + pub fn with_fix_applicability(self, applicability: Applicability) -> DisplayDiagnosticConfig { DisplayDiagnosticConfig { fix_applicability: applicability, ..self } } + + pub fn show_fix_status(&self) -> bool { + self.show_fix_status + } + + pub fn fix_applicability(&self) -> Applicability { + self.fix_applicability + } } impl Default for DisplayDiagnosticConfig { diff --git a/crates/ruff_db/src/diagnostic/render.rs b/crates/ruff_db/src/diagnostic/render.rs index 86b04fef00..8f1b9184a8 100644 --- a/crates/ruff_db/src/diagnostic/render.rs +++ b/crates/ruff_db/src/diagnostic/render.rs @@ -2618,7 +2618,7 @@ watermelon /// Show fix availability when rendering. pub(super) fn show_fix_status(&mut self, yes: bool) { let mut config = std::mem::take(&mut self.config); - config = config.show_fix_status(yes); + config = config.with_show_fix_status(yes); self.config = config; } @@ -2632,7 +2632,7 @@ watermelon /// The lowest fix applicability to show when rendering. pub(super) fn fix_applicability(&mut self, applicability: Applicability) { let mut config = std::mem::take(&mut self.config); - config = config.fix_applicability(applicability); + config = config.with_fix_applicability(applicability); self.config = config; } diff --git a/crates/ruff_linter/src/message/grouped.rs b/crates/ruff_linter/src/message/grouped.rs index 4626ab43cf..0426697728 100644 --- a/crates/ruff_linter/src/message/grouped.rs +++ b/crates/ruff_linter/src/message/grouped.rs @@ -6,17 +6,25 @@ use std::num::NonZeroUsize; use colored::Colorize; use ruff_db::diagnostic::Diagnostic; +use ruff_diagnostics::Applicability; use ruff_notebook::NotebookIndex; use ruff_source_file::{LineColumn, OneIndexed}; use crate::fs::relativize_path; use crate::message::{Emitter, EmitterContext}; -use crate::settings::types::UnsafeFixes; -#[derive(Default)] pub struct GroupedEmitter { show_fix_status: bool, - unsafe_fixes: UnsafeFixes, + applicability: Applicability, +} + +impl Default for GroupedEmitter { + fn default() -> Self { + Self { + show_fix_status: false, + applicability: Applicability::Safe, + } + } } impl GroupedEmitter { @@ -27,8 +35,8 @@ impl GroupedEmitter { } #[must_use] - pub fn with_unsafe_fixes(mut self, unsafe_fixes: UnsafeFixes) -> Self { - self.unsafe_fixes = unsafe_fixes; + pub fn with_applicability(mut self, applicability: Applicability) -> Self { + self.applicability = applicability; self } } @@ -67,7 +75,7 @@ impl Emitter for GroupedEmitter { notebook_index: context.notebook_index(&message.expect_ruff_filename()), message, show_fix_status: self.show_fix_status, - unsafe_fixes: self.unsafe_fixes, + applicability: self.applicability, row_length, column_length, } @@ -114,7 +122,7 @@ fn group_diagnostics_by_filename( struct DisplayGroupedMessage<'a> { message: MessageWithLocation<'a>, show_fix_status: bool, - unsafe_fixes: UnsafeFixes, + applicability: Applicability, row_length: NonZeroUsize, column_length: NonZeroUsize, notebook_index: Option<&'a NotebookIndex>, @@ -162,7 +170,7 @@ impl Display for DisplayGroupedMessage<'_> { code_and_body = RuleCodeAndBody { message, show_fix_status: self.show_fix_status, - unsafe_fixes: self.unsafe_fixes + applicability: self.applicability }, )?; @@ -173,7 +181,7 @@ impl Display for DisplayGroupedMessage<'_> { pub(super) struct RuleCodeAndBody<'a> { pub(crate) message: &'a Diagnostic, pub(crate) show_fix_status: bool, - pub(crate) unsafe_fixes: UnsafeFixes, + pub(crate) applicability: Applicability, } impl Display for RuleCodeAndBody<'_> { @@ -181,7 +189,7 @@ impl Display for RuleCodeAndBody<'_> { if self.show_fix_status { if let Some(fix) = self.message.fix() { // Do not display an indicator for inapplicable fixes - if fix.applies(self.unsafe_fixes.required_applicability()) { + if fix.applies(self.applicability) { if let Some(code) = self.message.secondary_code() { write!(f, "{} ", code.red().bold())?; } @@ -217,11 +225,12 @@ impl Display for RuleCodeAndBody<'_> { mod tests { use insta::assert_snapshot; + use ruff_diagnostics::Applicability; + use crate::message::GroupedEmitter; use crate::message::tests::{ capture_emitter_output, create_diagnostics, create_syntax_error_diagnostics, }; - use crate::settings::types::UnsafeFixes; #[test] fn default() { @@ -251,7 +260,7 @@ mod tests { fn fix_status_unsafe() { let mut emitter = GroupedEmitter::default() .with_show_fix_status(true) - .with_unsafe_fixes(UnsafeFixes::Enabled); + .with_applicability(Applicability::Unsafe); let content = capture_emitter_output(&mut emitter, &create_diagnostics()); assert_snapshot!(content); diff --git a/crates/ruff_linter/src/message/mod.rs b/crates/ruff_linter/src/message/mod.rs index 994dc81296..5ac4712a61 100644 --- a/crates/ruff_linter/src/message/mod.rs +++ b/crates/ruff_linter/src/message/mod.rs @@ -4,8 +4,9 @@ use std::io::Write; use rustc_hash::FxHashMap; use ruff_db::diagnostic::{ - Annotation, Diagnostic, DiagnosticId, FileResolver, Input, LintName, SecondaryCode, Severity, - Span, UnifiedFile, + Annotation, Diagnostic, DiagnosticFormat, DiagnosticId, DisplayDiagnosticConfig, + DisplayDiagnostics, DisplayGithubDiagnostics, FileResolver, GithubRenderer, Input, LintName, + SecondaryCode, Severity, Span, UnifiedFile, }; use ruff_db::files::File; @@ -14,14 +15,13 @@ use ruff_notebook::NotebookIndex; use ruff_source_file::SourceFile; use ruff_text_size::{Ranged, TextRange, TextSize}; pub use sarif::SarifEmitter; -pub use text::TextEmitter; use crate::Fix; use crate::registry::Rule; +use crate::settings::types::{OutputFormat, RuffOutputFormat}; mod grouped; mod sarif; -mod text; /// Creates a `Diagnostic` from a syntax error, with the format expected by Ruff. /// @@ -160,14 +160,48 @@ impl<'a> EmitterContext<'a> { } } +pub fn render_diagnostics( + writer: &mut dyn Write, + format: OutputFormat, + 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::Github) => { + let renderer = GithubRenderer::new(context, "Ruff"); + let value = DisplayGithubDiagnostics::new(&renderer, diagnostics); + write!(writer, "{value}")?; + } + Err(RuffOutputFormat::Grouped) => { + GroupedEmitter::default() + .with_show_fix_status(config.show_fix_status()) + .with_applicability(config.fix_applicability()) + .emit(writer, diagnostics, context) + .map_err(std::io::Error::other)?; + } + Err(RuffOutputFormat::Sarif) => { + SarifEmitter + .emit(writer, diagnostics, context) + .map_err(std::io::Error::other)?; + } + } + + Ok(()) +} + #[cfg(test)] mod tests { use rustc_hash::FxHashMap; use ruff_db::diagnostic::Diagnostic; - use ruff_notebook::NotebookIndex; use ruff_python_parser::{Mode, ParseOptions, parse_unchecked}; - use ruff_source_file::{OneIndexed, SourceFileBuilder}; + use ruff_source_file::SourceFileBuilder; use ruff_text_size::{TextRange, TextSize}; use crate::codes::Rule; @@ -257,104 +291,6 @@ def fibonacci(n): vec![unused_import, unused_variable, undefined_name] } - pub(super) fn create_notebook_diagnostics() - -> (Vec, FxHashMap) { - let notebook = r"# cell 1 -import os -# cell 2 -import math - -print('hello world') -# cell 3 -def foo(): - print() - x = 1 -"; - - let notebook_source = SourceFileBuilder::new("notebook.ipynb", notebook).finish(); - - let unused_import_os_start = TextSize::from(16); - let unused_import_os = create_lint_diagnostic( - "`os` imported but unused", - Some("Remove unused import: `os`"), - TextRange::new(unused_import_os_start, TextSize::from(18)), - Some(Fix::safe_edit(Edit::range_deletion(TextRange::new( - TextSize::from(9), - TextSize::from(19), - )))), - None, - notebook_source.clone(), - Some(unused_import_os_start), - Rule::UnusedImport, - ); - - let unused_import_math_start = TextSize::from(35); - let unused_import_math = create_lint_diagnostic( - "`math` imported but unused", - Some("Remove unused import: `math`"), - TextRange::new(unused_import_math_start, TextSize::from(39)), - Some(Fix::safe_edit(Edit::range_deletion(TextRange::new( - TextSize::from(28), - TextSize::from(40), - )))), - None, - notebook_source.clone(), - Some(unused_import_math_start), - Rule::UnusedImport, - ); - - let unused_variable_start = TextSize::from(98); - let unused_variable = create_lint_diagnostic( - "Local variable `x` is assigned to but never used", - Some("Remove assignment to unused variable `x`"), - TextRange::new(unused_variable_start, TextSize::from(99)), - Some(Fix::unsafe_edit(Edit::deletion( - TextSize::from(94), - TextSize::from(104), - ))), - None, - notebook_source, - Some(unused_variable_start), - Rule::UnusedVariable, - ); - - let mut notebook_indexes = FxHashMap::default(); - notebook_indexes.insert( - "notebook.ipynb".to_string(), - NotebookIndex::new( - vec![ - OneIndexed::from_zero_indexed(0), - OneIndexed::from_zero_indexed(0), - OneIndexed::from_zero_indexed(1), - OneIndexed::from_zero_indexed(1), - OneIndexed::from_zero_indexed(1), - OneIndexed::from_zero_indexed(1), - OneIndexed::from_zero_indexed(2), - OneIndexed::from_zero_indexed(2), - OneIndexed::from_zero_indexed(2), - OneIndexed::from_zero_indexed(2), - ], - vec![ - OneIndexed::from_zero_indexed(0), - OneIndexed::from_zero_indexed(1), - OneIndexed::from_zero_indexed(0), - OneIndexed::from_zero_indexed(1), - OneIndexed::from_zero_indexed(2), - OneIndexed::from_zero_indexed(3), - OneIndexed::from_zero_indexed(0), - OneIndexed::from_zero_indexed(1), - OneIndexed::from_zero_indexed(2), - OneIndexed::from_zero_indexed(3), - ], - ), - ); - - ( - vec![unused_import_os, unused_import_math, unused_variable], - notebook_indexes, - ) - } - pub(super) fn capture_emitter_output( emitter: &mut dyn Emitter, diagnostics: &[Diagnostic], @@ -366,16 +302,4 @@ def foo(): String::from_utf8(output).expect("Output to be valid UTF-8") } - - pub(super) fn capture_emitter_notebook_output( - emitter: &mut dyn Emitter, - diagnostics: &[Diagnostic], - notebook_indexes: &FxHashMap, - ) -> String { - let context = EmitterContext::new(notebook_indexes); - let mut output: Vec = Vec::new(); - emitter.emit(&mut output, diagnostics, &context).unwrap(); - - String::from_utf8(output).expect("Output to be valid UTF-8") - } } diff --git a/crates/ruff_linter/src/message/snapshots/ruff_linter__message__text__tests__default.snap b/crates/ruff_linter/src/message/snapshots/ruff_linter__message__text__tests__default.snap deleted file mode 100644 index 480dd582a7..0000000000 --- a/crates/ruff_linter/src/message/snapshots/ruff_linter__message__text__tests__default.snap +++ /dev/null @@ -1,30 +0,0 @@ ---- -source: crates/ruff_linter/src/message/text.rs -expression: content ---- -F401 `os` imported but unused - --> fib.py:1:8 - | -1 | import os - | ^^ - | -help: Remove unused import: `os` - -F841 Local variable `x` is assigned to but never used - --> fib.py:6:5 - | -4 | def fibonacci(n): -5 | """Compute the nth number in the Fibonacci sequence.""" -6 | x = 1 - | ^ -7 | if n == 0: -8 | return 0 - | -help: Remove assignment to unused variable `x` - -F821 Undefined name `a` - --> undef.py:1:4 - | -1 | if a == 1: pass - | ^ - | diff --git a/crates/ruff_linter/src/message/snapshots/ruff_linter__message__text__tests__fix_status.snap b/crates/ruff_linter/src/message/snapshots/ruff_linter__message__text__tests__fix_status.snap deleted file mode 100644 index 480dd582a7..0000000000 --- a/crates/ruff_linter/src/message/snapshots/ruff_linter__message__text__tests__fix_status.snap +++ /dev/null @@ -1,30 +0,0 @@ ---- -source: crates/ruff_linter/src/message/text.rs -expression: content ---- -F401 `os` imported but unused - --> fib.py:1:8 - | -1 | import os - | ^^ - | -help: Remove unused import: `os` - -F841 Local variable `x` is assigned to but never used - --> fib.py:6:5 - | -4 | def fibonacci(n): -5 | """Compute the nth number in the Fibonacci sequence.""" -6 | x = 1 - | ^ -7 | if n == 0: -8 | return 0 - | -help: Remove assignment to unused variable `x` - -F821 Undefined name `a` - --> undef.py:1:4 - | -1 | if a == 1: pass - | ^ - | diff --git a/crates/ruff_linter/src/message/snapshots/ruff_linter__message__text__tests__fix_status_unsafe.snap b/crates/ruff_linter/src/message/snapshots/ruff_linter__message__text__tests__fix_status_unsafe.snap deleted file mode 100644 index adee375b6c..0000000000 --- a/crates/ruff_linter/src/message/snapshots/ruff_linter__message__text__tests__fix_status_unsafe.snap +++ /dev/null @@ -1,30 +0,0 @@ ---- -source: crates/ruff_linter/src/message/text.rs -expression: content ---- -F401 [*] `os` imported but unused - --> fib.py:1:8 - | -1 | import os - | ^^ - | -help: Remove unused import: `os` - -F841 [*] Local variable `x` is assigned to but never used - --> fib.py:6:5 - | -4 | def fibonacci(n): -5 | """Compute the nth number in the Fibonacci sequence.""" -6 | x = 1 - | ^ -7 | if n == 0: -8 | return 0 - | -help: Remove assignment to unused variable `x` - -F821 Undefined name `a` - --> undef.py:1:4 - | -1 | if a == 1: pass - | ^ - | diff --git a/crates/ruff_linter/src/message/snapshots/ruff_linter__message__text__tests__notebook_output.snap b/crates/ruff_linter/src/message/snapshots/ruff_linter__message__text__tests__notebook_output.snap deleted file mode 100644 index 969d44dc7e..0000000000 --- a/crates/ruff_linter/src/message/snapshots/ruff_linter__message__text__tests__notebook_output.snap +++ /dev/null @@ -1,33 +0,0 @@ ---- -source: crates/ruff_linter/src/message/text.rs -expression: content ---- -F401 [*] `os` imported but unused - --> notebook.ipynb:cell 1:2:8 - | -1 | # cell 1 -2 | import os - | ^^ - | -help: Remove unused import: `os` - -F401 [*] `math` imported but unused - --> notebook.ipynb:cell 2:2:8 - | -1 | # cell 2 -2 | import math - | ^^^^ -3 | -4 | print('hello world') - | -help: Remove unused import: `math` - -F841 [*] Local variable `x` is assigned to but never used - --> notebook.ipynb:cell 3:4:5 - | -2 | def foo(): -3 | print() -4 | x = 1 - | ^ - | -help: Remove assignment to unused variable `x` diff --git a/crates/ruff_linter/src/message/snapshots/ruff_linter__message__text__tests__syntax_errors.snap b/crates/ruff_linter/src/message/snapshots/ruff_linter__message__text__tests__syntax_errors.snap deleted file mode 100644 index af19a135dd..0000000000 --- a/crates/ruff_linter/src/message/snapshots/ruff_linter__message__text__tests__syntax_errors.snap +++ /dev/null @@ -1,23 +0,0 @@ ---- -source: crates/ruff_linter/src/message/text.rs -expression: content ---- -invalid-syntax: Expected one or more symbol names after import - --> syntax_errors.py:1:15 - | -1 | from os import - | ^ -2 | -3 | if call(foo - | - -invalid-syntax: Expected ')', found newline - --> syntax_errors.py:3:12 - | -1 | from os import -2 | -3 | if call(foo - | ^ -4 | def bar(): -5 | pass - | diff --git a/crates/ruff_linter/src/message/text.rs b/crates/ruff_linter/src/message/text.rs deleted file mode 100644 index 4f6478266c..0000000000 --- a/crates/ruff_linter/src/message/text.rs +++ /dev/null @@ -1,143 +0,0 @@ -use std::io::Write; - -use ruff_db::diagnostic::{ - Diagnostic, DiagnosticFormat, DisplayDiagnosticConfig, DisplayDiagnostics, -}; -use ruff_diagnostics::Applicability; - -use crate::message::{Emitter, EmitterContext}; - -pub struct TextEmitter { - config: DisplayDiagnosticConfig, -} - -impl Default for TextEmitter { - fn default() -> Self { - Self { - config: DisplayDiagnosticConfig::default() - .format(DiagnosticFormat::Concise) - .hide_severity(true) - .color(!cfg!(test) && colored::control::SHOULD_COLORIZE.should_colorize()), - } - } -} - -impl TextEmitter { - #[must_use] - pub fn with_show_fix_status(mut self, show_fix_status: bool) -> Self { - self.config = self.config.show_fix_status(show_fix_status); - self - } - - #[must_use] - pub fn with_show_fix_diff(mut self, show_fix_diff: bool) -> Self { - self.config = self.config.show_fix_diff(show_fix_diff); - self - } - - #[must_use] - pub fn with_show_source(mut self, show_source: bool) -> Self { - self.config = self.config.format(if show_source { - DiagnosticFormat::Full - } else { - DiagnosticFormat::Concise - }); - self - } - - #[must_use] - pub fn with_fix_applicability(mut self, applicability: Applicability) -> Self { - self.config = self.config.fix_applicability(applicability); - self - } - - #[must_use] - pub fn with_preview(mut self, preview: bool) -> Self { - self.config = self.config.preview(preview); - self - } - - #[must_use] - pub fn with_color(mut self, color: bool) -> Self { - self.config = self.config.color(color); - self - } -} - -impl Emitter for TextEmitter { - fn emit( - &mut self, - writer: &mut dyn Write, - diagnostics: &[Diagnostic], - context: &EmitterContext, - ) -> anyhow::Result<()> { - write!( - writer, - "{}", - DisplayDiagnostics::new(context, &self.config, diagnostics) - )?; - - Ok(()) - } -} - -#[cfg(test)] -mod tests { - use insta::assert_snapshot; - use ruff_diagnostics::Applicability; - - use crate::message::TextEmitter; - use crate::message::tests::{ - capture_emitter_notebook_output, capture_emitter_output, create_diagnostics, - create_notebook_diagnostics, create_syntax_error_diagnostics, - }; - - #[test] - fn default() { - let mut emitter = TextEmitter::default().with_show_source(true); - let content = capture_emitter_output(&mut emitter, &create_diagnostics()); - - assert_snapshot!(content); - } - - #[test] - fn fix_status() { - let mut emitter = TextEmitter::default() - .with_show_fix_status(true) - .with_show_source(true); - let content = capture_emitter_output(&mut emitter, &create_diagnostics()); - - assert_snapshot!(content); - } - - #[test] - fn fix_status_unsafe() { - let mut emitter = TextEmitter::default() - .with_show_fix_status(true) - .with_show_source(true) - .with_fix_applicability(Applicability::Unsafe); - let content = capture_emitter_output(&mut emitter, &create_diagnostics()); - - assert_snapshot!(content); - } - - #[test] - fn notebook_output() { - let mut emitter = TextEmitter::default() - .with_show_fix_status(true) - .with_show_source(true) - .with_fix_applicability(Applicability::Unsafe); - let (messages, notebook_indexes) = create_notebook_diagnostics(); - let content = capture_emitter_notebook_output(&mut emitter, &messages, ¬ebook_indexes); - - assert_snapshot!(content); - } - - #[test] - fn syntax_errors() { - let mut emitter = TextEmitter::default().with_show_source(true); - let content = capture_emitter_output(&mut emitter, &create_syntax_error_diagnostics()); - - assert_snapshot!(content); - } -} diff --git a/crates/ruff_linter/src/settings/types.rs b/crates/ruff_linter/src/settings/types.rs index a72e80284a..760422a0ce 100644 --- a/crates/ruff_linter/src/settings/types.rs +++ b/crates/ruff_linter/src/settings/types.rs @@ -9,6 +9,7 @@ use anyhow::{Context, Result, bail}; use globset::{Glob, GlobMatcher, GlobSet, GlobSetBuilder}; use log::debug; use pep440_rs::{VersionSpecifier, VersionSpecifiers}; +use ruff_db::diagnostic::DiagnosticFormat; use rustc_hash::FxHashMap; use serde::{Deserialize, Deserializer, Serialize, de}; use strum_macros::EnumIter; @@ -553,6 +554,34 @@ impl Display for OutputFormat { } } +/// The subset of output formats only implemented in Ruff, not in `ruff_db` via `DisplayDiagnostics`. +pub enum RuffOutputFormat { + Github, + 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 => Err(RuffOutputFormat::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 b63e23fc87..2ebb244d62 100644 --- a/crates/ruff_linter/src/test.rs +++ b/crates/ruff_linter/src/test.rs @@ -10,7 +10,9 @@ use anyhow::Result; use itertools::Itertools; use rustc_hash::FxHashMap; -use ruff_db::diagnostic::{Diagnostic, Span}; +use ruff_db::diagnostic::{ + Diagnostic, DiagnosticFormat, DisplayDiagnosticConfig, DisplayDiagnostics, Span, +}; use ruff_notebook::Notebook; #[cfg(not(fuzzing))] use ruff_notebook::NotebookError; @@ -24,7 +26,7 @@ use ruff_source_file::SourceFileBuilder; use crate::codes::Rule; use crate::fix::{FixResult, fix_file}; use crate::linter::check_path; -use crate::message::{Emitter, EmitterContext, TextEmitter, create_syntax_error_diagnostic}; +use crate::message::{EmitterContext, create_syntax_error_diagnostic}; use crate::package::PackageRoot; use crate::packaging::detect_package_root; use crate::settings::types::UnsafeFixes; @@ -444,42 +446,38 @@ pub(crate) fn print_jupyter_messages( path: &Path, notebook: &Notebook, ) -> String { - let mut output = Vec::new(); - - TextEmitter::default() + let config = DisplayDiagnosticConfig::default() + .format(DiagnosticFormat::Full) + .hide_severity(true) .with_show_fix_status(true) - .with_show_fix_diff(true) - .with_show_source(true) - .with_fix_applicability(Applicability::DisplayOnly) - .emit( - &mut output, - diagnostics, - &EmitterContext::new(&FxHashMap::from_iter([( - path.file_name().unwrap().to_string_lossy().to_string(), - notebook.index().clone(), - )])), - ) - .unwrap(); + .show_fix_diff(true) + .with_fix_applicability(Applicability::DisplayOnly); - String::from_utf8(output).unwrap() + DisplayDiagnostics::new( + &EmitterContext::new(&FxHashMap::from_iter([( + path.file_name().unwrap().to_string_lossy().to_string(), + notebook.index().clone(), + )])), + &config, + diagnostics, + ) + .to_string() } pub(crate) fn print_messages(diagnostics: &[Diagnostic]) -> String { - let mut output = Vec::new(); - - TextEmitter::default() + let config = DisplayDiagnosticConfig::default() + .format(DiagnosticFormat::Full) + .hide_severity(true) .with_show_fix_status(true) - .with_show_fix_diff(true) - .with_show_source(true) - .with_fix_applicability(Applicability::DisplayOnly) - .emit( - &mut output, - diagnostics, - &EmitterContext::new(&FxHashMap::default()), - ) - .unwrap(); + .show_fix_diff(true) + .with_fix_applicability(Applicability::DisplayOnly); - String::from_utf8(output).unwrap() + DisplayDiagnostics::new( + &EmitterContext::new(&FxHashMap::default()), + &config, + diagnostics, + ) + .to_string() } #[macro_export]