diff --git a/crates/ruff/src/lib.rs b/crates/ruff/src/lib.rs index 350e57f3b4917..f64359e5675ac 100644 --- a/crates/ruff/src/lib.rs +++ b/crates/ruff/src/lib.rs @@ -375,6 +375,7 @@ pub fn check(args: CheckCommand, global_options: GlobalConfigArgs) -> Result Result Result Result Result<()> { if matches!(self.log_level, LogLevel::Silent) { return Ok(()); @@ -223,7 +224,7 @@ impl Printer { if self.flags.intersects(Flags::SHOW_FIX_SUMMARY) { if !diagnostics.fixed.is_empty() { writeln!(writer)?; - print_fix_summary(writer, &diagnostics.fixed, preview)?; + print_fix_summary(writer, &diagnostics.fixed, preview, prefer_rule_codes)?; writeln!(writer)?; } } @@ -237,6 +238,7 @@ impl Printer { let config = DisplayDiagnosticConfig::new("ruff") .preview(preview.is_enabled()) + .prefer_rule_codes(prefer_rule_codes) .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())) @@ -251,7 +253,7 @@ impl Printer { if self.flags.intersects(Flags::SHOW_FIX_SUMMARY) { if !diagnostics.fixed.is_empty() { writeln!(writer)?; - print_fix_summary(writer, &diagnostics.fixed, preview)?; + print_fix_summary(writer, &diagnostics.fixed, preview, prefer_rule_codes)?; writeln!(writer)?; } } @@ -383,6 +385,7 @@ impl Printer { writer: &mut dyn Write, diagnostics: &Diagnostics, preview: PreviewMode, + prefer_rule_codes: bool, ) -> Result<()> { if matches!(self.log_level, LogLevel::Silent) { return Ok(()); @@ -410,6 +413,7 @@ impl Printer { let context = EmitterContext::new(&diagnostics.notebook_indexes); let config = DisplayDiagnosticConfig::new("ruff") .preview(preview.is_enabled()) + .prefer_rule_codes(prefer_rule_codes) .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())) @@ -445,7 +449,12 @@ fn show_fix_status(fix_mode: flags::FixMode, fixables: Option<&FixableStatistics (!fix_mode.is_apply()) && fixables.is_some_and(FixableStatistics::any_applicable_fixes) } -fn print_fix_summary(writer: &mut dyn Write, fixed: &FixMap, preview: PreviewMode) -> Result<()> { +fn print_fix_summary( + writer: &mut dyn Write, + fixed: &FixMap, + preview: PreviewMode, + prefer_rule_codes: bool, +) -> Result<()> { let total = fixed .values() .map(|table| table.counts().sum::()) @@ -475,7 +484,7 @@ fn print_fix_summary(writer: &mut dyn Write, fixed: &FixMap, preview: PreviewMod ":".cyan() )?; for (code, name, count) in table.iter().sorted_by_key(|(.., count)| Reverse(*count)) { - if is_human_readable_names_enabled(preview) { + if is_human_readable_names_enabled(preview) && !prefer_rule_codes { writeln!( writer, " {count:>num_digits$} × {name} ({code})", diff --git a/crates/ruff/tests/cli/lint.rs b/crates/ruff/tests/cli/lint.rs index 09559179928bd..d8e394cfb2bba 100644 --- a/crates/ruff/tests/cli/lint.rs +++ b/crates/ruff/tests/cli/lint.rs @@ -4273,6 +4273,31 @@ class Foo: ); } +#[test] +fn prefer_rule_codes_in_output() { + assert_cmd_snapshot!( + Command::new(get_cargo_bin(BIN_NAME)) + .args(STDIN_BASE_OPTIONS) + .args([ + "--preview", + "--config", + "output-prefer-rule-codes = true", + "--select=A001", + "-", + ]) + .pass_stdin("print = 1\n"), + @" + success: false + exit_code: 1 + ----- stdout ----- + -:1:1: A001 Variable `print` is shadowing a Python builtin + Found 1 error. + + ----- stderr ----- + " + ); +} + #[test_case::test_case("concise")] #[test_case::test_case("full")] #[test_case::test_case("json")] diff --git a/crates/ruff/tests/cli/snapshots/cli__lint__requires_python_extend_from_shared_config.snap b/crates/ruff/tests/cli/snapshots/cli__lint__requires_python_extend_from_shared_config.snap index 682462cd1db2f..dd44815b4237d 100644 --- a/crates/ruff/tests/cli/snapshots/cli__lint__requires_python_extend_from_shared_config.snap +++ b/crates/ruff/tests/cli/snapshots/cli__lint__requires_python_extend_from_shared_config.snap @@ -21,6 +21,7 @@ cache_dir = "[TMP]/.ruff_cache" fix = false fix_only = false output_format = concise +output_prefer_rule_codes = false show_fixes = false unsafe_fixes = hint diff --git a/crates/ruff/tests/cli/snapshots/cli__lint__requires_python_no_tool.snap b/crates/ruff/tests/cli/snapshots/cli__lint__requires_python_no_tool.snap index b45927e8dfc40..297f9e61abd13 100644 --- a/crates/ruff/tests/cli/snapshots/cli__lint__requires_python_no_tool.snap +++ b/crates/ruff/tests/cli/snapshots/cli__lint__requires_python_no_tool.snap @@ -23,6 +23,7 @@ cache_dir = "[TMP]/.ruff_cache" fix = false fix_only = false output_format = concise +output_prefer_rule_codes = false show_fixes = false unsafe_fixes = hint diff --git a/crates/ruff/tests/cli/snapshots/cli__lint__requires_python_no_tool_preview_enabled.snap b/crates/ruff/tests/cli/snapshots/cli__lint__requires_python_no_tool_preview_enabled.snap index 0a24b5e1d8085..4fa30c77cf9e7 100644 --- a/crates/ruff/tests/cli/snapshots/cli__lint__requires_python_no_tool_preview_enabled.snap +++ b/crates/ruff/tests/cli/snapshots/cli__lint__requires_python_no_tool_preview_enabled.snap @@ -24,6 +24,7 @@ cache_dir = "[TMP]/.ruff_cache" fix = false fix_only = false output_format = concise +output_prefer_rule_codes = false show_fixes = false unsafe_fixes = hint diff --git a/crates/ruff/tests/cli/snapshots/cli__lint__requires_python_no_tool_target_version_override.snap b/crates/ruff/tests/cli/snapshots/cli__lint__requires_python_no_tool_target_version_override.snap index 66a7714e49342..c0243ce499230 100644 --- a/crates/ruff/tests/cli/snapshots/cli__lint__requires_python_no_tool_target_version_override.snap +++ b/crates/ruff/tests/cli/snapshots/cli__lint__requires_python_no_tool_target_version_override.snap @@ -25,6 +25,7 @@ cache_dir = "[TMP]/.ruff_cache" fix = false fix_only = false output_format = concise +output_prefer_rule_codes = false show_fixes = false unsafe_fixes = hint diff --git a/crates/ruff/tests/cli/snapshots/cli__lint__requires_python_pyproject_toml_above.snap b/crates/ruff/tests/cli/snapshots/cli__lint__requires_python_pyproject_toml_above.snap index 0b3f80819c51f..0b134a091a223 100644 --- a/crates/ruff/tests/cli/snapshots/cli__lint__requires_python_pyproject_toml_above.snap +++ b/crates/ruff/tests/cli/snapshots/cli__lint__requires_python_pyproject_toml_above.snap @@ -22,6 +22,7 @@ cache_dir = "[TMP]/.ruff_cache" fix = false fix_only = false output_format = concise +output_prefer_rule_codes = false show_fixes = false unsafe_fixes = hint diff --git a/crates/ruff/tests/cli/snapshots/cli__lint__requires_python_pyproject_toml_above_with_tool.snap b/crates/ruff/tests/cli/snapshots/cli__lint__requires_python_pyproject_toml_above_with_tool.snap index 5f376667d4a31..94590c2c2c57e 100644 --- a/crates/ruff/tests/cli/snapshots/cli__lint__requires_python_pyproject_toml_above_with_tool.snap +++ b/crates/ruff/tests/cli/snapshots/cli__lint__requires_python_pyproject_toml_above_with_tool.snap @@ -23,6 +23,7 @@ cache_dir = "[TMP]/foo/.ruff_cache" fix = false fix_only = false output_format = concise +output_prefer_rule_codes = false show_fixes = false unsafe_fixes = hint diff --git a/crates/ruff/tests/cli/snapshots/cli__lint__requires_python_ruff_toml_above-2.snap b/crates/ruff/tests/cli/snapshots/cli__lint__requires_python_ruff_toml_above-2.snap index 0f9960f3fe573..ddc9873c88486 100644 --- a/crates/ruff/tests/cli/snapshots/cli__lint__requires_python_ruff_toml_above-2.snap +++ b/crates/ruff/tests/cli/snapshots/cli__lint__requires_python_ruff_toml_above-2.snap @@ -21,6 +21,7 @@ cache_dir = "[TMP]/.ruff_cache" fix = false fix_only = false output_format = concise +output_prefer_rule_codes = false show_fixes = false unsafe_fixes = hint diff --git a/crates/ruff/tests/cli/snapshots/cli__lint__requires_python_ruff_toml_above.snap b/crates/ruff/tests/cli/snapshots/cli__lint__requires_python_ruff_toml_above.snap index cef5cf6988dd3..f0e415ce41aeb 100644 --- a/crates/ruff/tests/cli/snapshots/cli__lint__requires_python_ruff_toml_above.snap +++ b/crates/ruff/tests/cli/snapshots/cli__lint__requires_python_ruff_toml_above.snap @@ -21,6 +21,7 @@ cache_dir = "[TMP]/.ruff_cache" fix = false fix_only = false output_format = concise +output_prefer_rule_codes = false show_fixes = false unsafe_fixes = hint diff --git a/crates/ruff/tests/cli/snapshots/cli__lint__requires_python_ruff_toml_no_target_fallback.snap b/crates/ruff/tests/cli/snapshots/cli__lint__requires_python_ruff_toml_no_target_fallback.snap index 14a16079b6a8b..a0f960289411b 100644 --- a/crates/ruff/tests/cli/snapshots/cli__lint__requires_python_ruff_toml_no_target_fallback.snap +++ b/crates/ruff/tests/cli/snapshots/cli__lint__requires_python_ruff_toml_no_target_fallback.snap @@ -21,6 +21,7 @@ cache_dir = "[TMP]/.ruff_cache" fix = false fix_only = false output_format = concise +output_prefer_rule_codes = false show_fixes = false unsafe_fixes = hint diff --git a/crates/ruff/tests/cli/snapshots/cli__show_settings__display_default_settings.snap b/crates/ruff/tests/cli/snapshots/cli__show_settings__display_default_settings.snap index 43a4d2768e826..915a5a077e538 100644 --- a/crates/ruff/tests/cli/snapshots/cli__show_settings__display_default_settings.snap +++ b/crates/ruff/tests/cli/snapshots/cli__show_settings__display_default_settings.snap @@ -18,6 +18,7 @@ cache_dir = "[TMP]/.ruff_cache" fix = false fix_only = false output_format = full +output_prefer_rule_codes = false show_fixes = false unsafe_fixes = hint diff --git a/crates/ruff/tests/cli/snapshots/cli__show_settings__display_settings_from_nested_directory.snap b/crates/ruff/tests/cli/snapshots/cli__show_settings__display_settings_from_nested_directory.snap index dafb9e2db830c..c991e1f61fa5e 100644 --- a/crates/ruff/tests/cli/snapshots/cli__show_settings__display_settings_from_nested_directory.snap +++ b/crates/ruff/tests/cli/snapshots/cli__show_settings__display_settings_from_nested_directory.snap @@ -18,6 +18,7 @@ cache_dir = "[TMP]/subdir/.ruff_cache" fix = false fix_only = false output_format = full +output_prefer_rule_codes = false show_fixes = false unsafe_fixes = hint diff --git a/crates/ruff_db/src/diagnostic/mod.rs b/crates/ruff_db/src/diagnostic/mod.rs index e45020770ae12..03d9319a2c8e0 100644 --- a/crates/ruff_db/src/diagnostic/mod.rs +++ b/crates/ruff_db/src/diagnostic/mod.rs @@ -1447,6 +1447,8 @@ pub struct DisplayDiagnosticConfig { merge_window: usize, /// Whether to use preview formatting for Ruff diagnostics. preview: bool, + /// Whether to prefer rule codes over human-readable rule names in Ruff diagnostic output. + prefer_rule_codes: bool, /// Whether to hide the real `Severity` of diagnostics. /// /// This is intended for temporary use by Ruff, which only has a single `error` severity at the @@ -1471,6 +1473,7 @@ impl DisplayDiagnosticConfig { context: 2, merge_window: 2, preview: false, + prefer_rule_codes: false, hide_severity: false, show_fix_status: false, fix_applicability: Applicability::Safe, @@ -1535,6 +1538,19 @@ impl DisplayDiagnosticConfig { self.preview } + /// Whether to prefer rule codes over human-readable rule names, even in preview mode. + pub fn prefer_rule_codes(self, yes: bool) -> DisplayDiagnosticConfig { + DisplayDiagnosticConfig { + prefer_rule_codes: yes, + ..self + } + } + + /// Whether rule codes are explicitly preferred over human-readable rule names. + pub fn is_prefer_rule_codes_enabled(&self) -> bool { + self.prefer_rule_codes + } + /// Whether to hide a diagnostic's severity or not. pub fn hide_severity(self, yes: bool) -> DisplayDiagnosticConfig { DisplayDiagnosticConfig { diff --git a/crates/ruff_db/src/diagnostic/render.rs b/crates/ruff_db/src/diagnostic/render.rs index 8621883ad87c1..e40ca5594d368 100644 --- a/crates/ruff_db/src/diagnostic/render.rs +++ b/crates/ruff_db/src/diagnostic/render.rs @@ -238,9 +238,8 @@ impl<'a> ResolvedDiagnostic<'a> { }) .collect(); - let id = if !config.preview - && let Some(code) = diag.secondary_code() - { + let use_code = !config.preview || config.prefer_rule_codes; + let id = if use_code && let Some(code) = diag.secondary_code() { code.to_string() } else if config.hide_severity { // When Ruff gets real severities, we should put the colon back in diff --git a/crates/ruff_db/src/diagnostic/render/azure.rs b/crates/ruff_db/src/diagnostic/render/azure.rs index 4183c2702cadd..c1724aa6a55eb 100644 --- a/crates/ruff_db/src/diagnostic/render/azure.rs +++ b/crates/ruff_db/src/diagnostic/render/azure.rs @@ -49,7 +49,7 @@ impl AzureRenderer<'_> { )?; } } - let code = if self.config.preview { + let code = if self.config.preview && !self.config.prefer_rule_codes { diag.id().as_str() } else { diag.secondary_code_or_id() diff --git a/crates/ruff_db/src/diagnostic/render/concise.rs b/crates/ruff_db/src/diagnostic/render/concise.rs index 2fa922a90011c..0e437011d3d11 100644 --- a/crates/ruff_db/src/diagnostic/render/concise.rs +++ b/crates/ruff_db/src/diagnostic/render/concise.rs @@ -67,10 +67,9 @@ impl<'a> ConciseRenderer<'a> { write!(f, "{sep} ")?; } + let use_name = self.config.preview && !self.config.prefer_rule_codes; if self.config.hide_severity { - if !self.config.preview - && let Some(code) = diag.secondary_code() - { + if !use_name && let Some(code) = diag.secondary_code() { write!( f, "{code} ", @@ -100,7 +99,7 @@ impl<'a> ConciseRenderer<'a> { Severity::Error => ("error", stylesheet.error), Severity::Fatal => ("fatal", stylesheet.error), }; - let id = if self.config.preview { + let id = if use_name { diag.id().as_str() } else { diag.secondary_code_or_id() diff --git a/crates/ruff_db/src/diagnostic/render/github.rs b/crates/ruff_db/src/diagnostic/render/github.rs index 052cfeb21f517..0d8b862e4253a 100644 --- a/crates/ruff_db/src/diagnostic/render/github.rs +++ b/crates/ruff_db/src/diagnostic/render/github.rs @@ -26,7 +26,8 @@ impl<'a> GithubRenderer<'a> { Severity::Warning => "warning", Severity::Error | Severity::Fatal => "error", }; - let code = if self.config.preview { + let use_name = self.config.preview && !self.config.prefer_rule_codes; + let code = if use_name { diagnostic.id().as_str() } else { diagnostic.secondary_code_or_id() @@ -90,9 +91,7 @@ impl<'a> GithubRenderer<'a> { write!(f, "::")?; } - if !self.config.preview - && let Some(code) = diagnostic.secondary_code() - { + if !use_name && let Some(code) = diagnostic.secondary_code() { write!(f, "{code}")?; } else { write!(f, "{id}:", id = diagnostic.id())?; diff --git a/crates/ruff_db/src/diagnostic/render/gitlab.rs b/crates/ruff_db/src/diagnostic/render/gitlab.rs index 88eab1185797b..f330e268dc4ac 100644 --- a/crates/ruff_db/src/diagnostic/render/gitlab.rs +++ b/crates/ruff_db/src/diagnostic/render/gitlab.rs @@ -102,7 +102,7 @@ impl Serialize for SerializedMessages<'_> { fingerprints.insert(message_fingerprint); let description = diagnostic.concise_message(); - let check_name = if self.config.preview { + let check_name = if self.config.preview && !self.config.prefer_rule_codes { diagnostic.id().as_str() } else { diagnostic.secondary_code_or_id() diff --git a/crates/ruff_db/src/diagnostic/render/junit.rs b/crates/ruff_db/src/diagnostic/render/junit.rs index ddb7c2e8f68b2..f52f4b19504c0 100644 --- a/crates/ruff_db/src/diagnostic/render/junit.rs +++ b/crates/ruff_db/src/diagnostic/render/junit.rs @@ -58,7 +58,7 @@ impl<'a> JunitRenderer<'a> { start_location: location, } = diagnostic; - let code = if self.config.preview { + let code = if self.config.preview && !self.config.prefer_rule_codes { diagnostic.id().as_str() } else { diagnostic.secondary_code_or_id() diff --git a/crates/ruff_db/src/diagnostic/render/pylint.rs b/crates/ruff_db/src/diagnostic/render/pylint.rs index d6a4f5d56d744..2dd4b9f8e6964 100644 --- a/crates/ruff_db/src/diagnostic/render/pylint.rs +++ b/crates/ruff_db/src/diagnostic/render/pylint.rs @@ -46,7 +46,7 @@ impl PylintRenderer<'_> { }) .unwrap_or_default(); - let code = if self.config.preview { + let code = if self.config.preview && !self.config.prefer_rule_codes { diagnostic.id().as_str() } else { diagnostic.secondary_code_or_id() diff --git a/crates/ruff_db/src/diagnostic/render/rdjson.rs b/crates/ruff_db/src/diagnostic/render/rdjson.rs index 620d8e8f790c9..5ab30cbf86c4b 100644 --- a/crates/ruff_db/src/diagnostic/render/rdjson.rs +++ b/crates/ruff_db/src/diagnostic/render/rdjson.rs @@ -86,7 +86,7 @@ fn diagnostic_to_rdjson<'a>( message: diagnostic.concise_message(), location, code: RdjsonCode { - value: if config.preview { + value: if config.preview && !config.prefer_rule_codes { diagnostic.id().as_str() } else { diagnostic.secondary_code_or_id() diff --git a/crates/ruff_linter/src/message/grouped.rs b/crates/ruff_linter/src/message/grouped.rs index 6c7752b939006..b22e6610126e7 100644 --- a/crates/ruff_linter/src/message/grouped.rs +++ b/crates/ruff_linter/src/message/grouped.rs @@ -17,6 +17,7 @@ pub struct GroupedEmitter { show_fix_status: bool, applicability: Applicability, preview: bool, + prefer_rule_codes: bool, } impl Default for GroupedEmitter { @@ -25,6 +26,7 @@ impl Default for GroupedEmitter { show_fix_status: false, applicability: Applicability::Safe, preview: false, + prefer_rule_codes: false, } } } @@ -47,6 +49,12 @@ impl GroupedEmitter { self.preview = preview; self } + + #[must_use] + pub fn with_prefer_rule_codes(mut self, prefer_rule_codes: bool) -> Self { + self.prefer_rule_codes = prefer_rule_codes; + self + } } impl Emitter for GroupedEmitter { @@ -87,6 +95,7 @@ impl Emitter for GroupedEmitter { row_length, column_length, preview: self.preview, + prefer_rule_codes: self.prefer_rule_codes, } )?; } @@ -136,6 +145,7 @@ struct DisplayGroupedMessage<'a> { column_length: NonZeroUsize, notebook_index: Option<&'a NotebookIndex>, preview: bool, + prefer_rule_codes: bool, } impl Display for DisplayGroupedMessage<'_> { @@ -182,6 +192,7 @@ impl Display for DisplayGroupedMessage<'_> { show_fix_status: self.show_fix_status, applicability: self.applicability, preview: self.preview, + prefer_rule_codes: self.prefer_rule_codes, }, )?; @@ -194,15 +205,17 @@ pub(super) struct RuleCodeAndBody<'a> { pub(crate) show_fix_status: bool, pub(crate) applicability: Applicability, pub(crate) preview: bool, + pub(crate) prefer_rule_codes: bool, } impl Display for RuleCodeAndBody<'_> { fn fmt(&self, f: &mut Formatter<'_>) -> std::fmt::Result { + let use_name = self.preview && !self.prefer_rule_codes; if self.show_fix_status { if let Some(fix) = self.message.fix() { // Do not display an indicator for inapplicable fixes if fix.applies(self.applicability) { - let code = if self.preview { + let code = if use_name { self.message.id().as_str() } else { self.message.secondary_code_or_id() @@ -218,9 +231,7 @@ impl Display for RuleCodeAndBody<'_> { } } - if !self.preview - && let Some(code) = self.message.secondary_code() - { + if !use_name && let Some(code) = self.message.secondary_code() { write!( f, "{code} {body}", diff --git a/crates/ruff_linter/src/message/mod.rs b/crates/ruff_linter/src/message/mod.rs index 9959c42f75a49..a25ad43b222f8 100644 --- a/crates/ruff_linter/src/message/mod.rs +++ b/crates/ruff_linter/src/message/mod.rs @@ -213,6 +213,7 @@ pub fn render_diagnostics( .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)?; } diff --git a/crates/ruff_linter/src/message/sarif.rs b/crates/ruff_linter/src/message/sarif.rs index 1d6a6edf86ade..375e544f1c1c4 100644 --- a/crates/ruff_linter/src/message/sarif.rs +++ b/crates/ruff_linter/src/message/sarif.rs @@ -194,7 +194,11 @@ impl Serialize for RuleCode<'_> { impl<'a> RuleCode<'a> { fn from_diagnostic(code: &'a Diagnostic, config: &'a DisplayDiagnosticConfig) -> Self { match code.secondary_code() { - Some(diagnostic) if !config.preview_enabled() => Self::SecondaryCode(diagnostic), + Some(diagnostic) + if !config.preview_enabled() || config.is_prefer_rule_codes_enabled() => + { + Self::SecondaryCode(diagnostic) + } _ => Self::LintId(code.id().as_str()), } } diff --git a/crates/ruff_server/src/lint.rs b/crates/ruff_server/src/lint.rs index 396e491b1b324..8579673c5cea3 100644 --- a/crates/ruff_server/src/lint.rs +++ b/crates/ruff_server/src/lint.rs @@ -4,6 +4,7 @@ use std::fmt::Write; use std::path::Path; use ruff_python_ast::SourceType; +use ruff_workspace::Settings; use rustc_hash::FxHashMap; use serde::{Deserialize, Serialize}; @@ -23,7 +24,7 @@ use ruff_linter::{ package::PackageRoot, packaging::detect_package_root, preview::is_human_readable_names_enabled, - settings::{LinterSettings, flags}, + settings::flags, source_kind::SourceKind, suppression::Suppressions, }; @@ -157,7 +158,9 @@ pub(crate) fn check( &directives.noqa_line_for, stylist.line_ending(), &suppressions, - if is_human_readable_names_enabled(settings.linter.preview) { + if is_human_readable_names_enabled(settings.linter.preview) + && !settings.output_prefer_rule_codes + { SuppressionKind::Ignore } else { SuppressionKind::Noqa @@ -172,7 +175,7 @@ pub(crate) fn check( document_uri: &document_uri, notebook, supports_related_information, - settings: &settings.linter, + settings, }; let mut diagnostics_map = DiagnosticsMap::default(); @@ -258,7 +261,7 @@ struct LspDiagnosticContext<'a> { document_uri: &'a lsp_types::Uri, notebook: Option<&'a NotebookDocument>, supports_related_information: bool, - settings: &'a LinterSettings, + settings: &'a Settings, } /// Generates an LSP diagnostic with an associated cell index for the diagnostic to go in. @@ -276,7 +279,9 @@ fn to_lsp_diagnostic( let (severity, code) = if let Some(code) = diagnostic.secondary_code() { let severity = severity(code); - let code = if is_human_readable_names_enabled(context.settings.preview) { + let code = if is_human_readable_names_enabled(context.settings.linter.preview) + && !context.settings.output_prefer_rule_codes + { name.to_string() } else { code.to_string() @@ -565,7 +570,7 @@ mod tests { }; let index = LineIndex::from_source_text(source); let uri = lsp_types::Uri::parse("file:///test.py").expect("URI to be valid"); - let settings = LinterSettings::default(); + let settings = Settings::default(); let context = LspDiagnosticContext { source_kind: &source_kind, index: &index, diff --git a/crates/ruff_wasm/src/lib.rs b/crates/ruff_wasm/src/lib.rs index d6f9560e55d16..8b7267a607990 100644 --- a/crates/ruff_wasm/src/lib.rs +++ b/crates/ruff_wasm/src/lib.rs @@ -429,7 +429,8 @@ impl Workspace { }) .collect(); - let code = if !is_human_readable_names_enabled(self.settings.linter.preview) + let code = if (!is_human_readable_names_enabled(self.settings.linter.preview) + || self.settings.output_prefer_rule_codes) && let Some(code) = msg.secondary_code() { code.as_str() diff --git a/crates/ruff_workspace/src/configuration.rs b/crates/ruff_workspace/src/configuration.rs index 8c8f5491b41d7..3790c9bb4cbe4 100644 --- a/crates/ruff_workspace/src/configuration.rs +++ b/crates/ruff_workspace/src/configuration.rs @@ -179,6 +179,7 @@ pub struct Configuration { pub fix_only: Option, pub unsafe_fixes: Option, pub output_format: Option, + pub output_prefer_rule_codes: Option, pub preview: Option, pub required_version: Option, pub extension: Option, @@ -325,6 +326,7 @@ impl Configuration { fix_only: self.fix_only.unwrap_or(false), unsafe_fixes: self.unsafe_fixes.unwrap_or_default(), output_format: self.output_format.unwrap_or_default(), + output_prefer_rule_codes: self.output_prefer_rule_codes.unwrap_or_default(), show_fixes: self.show_fixes.unwrap_or(false), file_resolver: FileResolverSettings { @@ -604,6 +606,7 @@ impl Configuration { fix_only: options.fix_only, unsafe_fixes: options.unsafe_fixes.map(UnsafeFixes::from), output_format: options.output_format, + output_prefer_rule_codes: options.output_prefer_rule_codes, force_exclude: options.force_exclude, line_length: options.line_length, indent_width: options.indent_width, @@ -667,6 +670,9 @@ impl Configuration { fix_only: self.fix_only.or(config.fix_only), unsafe_fixes: self.unsafe_fixes.or(config.unsafe_fixes), output_format: self.output_format.or(config.output_format), + output_prefer_rule_codes: self + .output_prefer_rule_codes + .or(config.output_prefer_rule_codes), force_exclude: self.force_exclude.or(config.force_exclude), line_length: self.line_length.or(config.line_length), indent_width: self.indent_width.or(config.indent_width), diff --git a/crates/ruff_workspace/src/options.rs b/crates/ruff_workspace/src/options.rs index 321bb99c504c2..e1403f98e3cc1 100644 --- a/crates/ruff_workspace/src/options.rs +++ b/crates/ruff_workspace/src/options.rs @@ -107,6 +107,30 @@ pub struct Options { )] pub output_format: Option, + /// Whether to prefer rule codes over human-readable rule names in diagnostic output, even + /// when preview mode is enabled. + /// + /// Diagnostics without rule codes, such as syntax errors and formatting diagnostics, will + /// continue to use the human-readable name, but those corresponding to lint rules will use the + /// rule's code. For example, the concise diagnostic for an unused import will use the code + /// `F401` instead of the name `unused-import`: + /// + /// ```console + /// $ ruff check --preview --config 'output-prefer-rule-codes = true' --output-format=concise example.py + /// example.py:1:8: F401 [*] `math` imported but unused + /// $ ruff check --preview --config 'output-prefer-rule-codes = false' --output-format=concise example.py + /// example.py:1:8: unused-import: [*] `math` imported but unused + /// ``` + #[option( + default = "false", + value_type = "bool", + example = r#" + # Display rule codes instead of human-readable rule names. + output-prefer-rule-codes = true + "# + )] + pub output_prefer_rule_codes: Option, + /// Enable fix behavior by-default when running `ruff` (overridden /// by the `--fix` and `--no-fix` command-line flags). /// Only includes automatic fixes unless `--unsafe-fixes` is provided. diff --git a/crates/ruff_workspace/src/settings.rs b/crates/ruff_workspace/src/settings.rs index a0b137ba87d40..8527e17aff6f0 100644 --- a/crates/ruff_workspace/src/settings.rs +++ b/crates/ruff_workspace/src/settings.rs @@ -18,6 +18,7 @@ use ruff_source_file::find_newline; use std::fmt; use std::path::{Path, PathBuf}; +#[expect(clippy::struct_excessive_bools)] #[derive(Debug, CacheKey)] pub struct Settings { #[cache_key(ignore)] @@ -31,6 +32,8 @@ pub struct Settings { #[cache_key(ignore)] pub output_format: OutputFormat, #[cache_key(ignore)] + pub output_prefer_rule_codes: bool, + #[cache_key(ignore)] pub show_fixes: bool, pub file_resolver: FileResolverSettings, @@ -47,6 +50,7 @@ impl Default for Settings { fix: false, fix_only: false, output_format: OutputFormat::default(), + output_prefer_rule_codes: false, show_fixes: false, unsafe_fixes: UnsafeFixes::default(), linter: LinterSettings::new(project_root), @@ -67,6 +71,7 @@ impl fmt::Display for Settings { self.fix, self.fix_only, self.output_format, + self.output_prefer_rule_codes, self.show_fixes, self.unsafe_fixes, self.file_resolver | nested, diff --git a/ruff.schema.json b/ruff.schema.json index 11b83aec21e82..6ecca20f034f0 100644 --- a/ruff.schema.json +++ b/ruff.schema.json @@ -562,6 +562,13 @@ } ] }, + "output-prefer-rule-codes": { + "description": "Whether to prefer rule codes over human-readable rule names in diagnostic output, even\nwhen preview mode is enabled.\n\nDiagnostics without rule codes, such as syntax errors and formatting diagnostics, will\ncontinue to use the human-readable name, but those corresponding to lint rules will use the\nrule's code. For example, the concise diagnostic for an unused import will use the code\n`F401` instead of the name `unused-import`:\n\n```console\n$ ruff check --preview --config 'output-prefer-rule-codes = true' --output-format=concise example.py\nexample.py:1:8: F401 [*] `math` imported but unused\n$ ruff check --preview --config 'output-prefer-rule-codes = false' --output-format=concise example.py\nexample.py:1:8: unused-import: [*] `math` imported but unused\n```", + "type": [ + "boolean", + "null" + ] + }, "pep8-naming": { "description": "Options for the `pep8-naming` plugin.", "anyOf": [