diff --git a/crates/ruff_cli/src/args.rs b/crates/ruff_cli/src/args.rs index 28581fb92090a..fc3ab5dc25907 100644 --- a/crates/ruff_cli/src/args.rs +++ b/crates/ruff_cli/src/args.rs @@ -9,6 +9,7 @@ use ruff_linter::logging::LogLevel; use ruff_linter::registry::Rule; use ruff_linter::settings::types::{ FilePattern, PatternPrefixPair, PerFileIgnore, PreviewMode, PythonVersion, SerializationFormat, + UnsafeFixes, }; use ruff_linter::{RuleParser, RuleSelector, RuleSelectorParser}; use ruff_workspace::configuration::{Configuration, RuleSelection}; @@ -76,12 +77,18 @@ pub enum Command { pub struct CheckCommand { /// List of files or directories to check. pub files: Vec, - /// Attempt to automatically fix lint violations. - /// Use `--no-fix` to disable. + /// Apply fixes to resolve lint violations. + /// Use `--no-fix` to disable or `--unsafe-fixes` to include unsafe fixes. #[arg(long, overrides_with("no_fix"))] fix: bool, #[clap(long, overrides_with("fix"), hide = true)] no_fix: bool, + /// Include fixes that may not retain the original intent of the code. + /// Use `--no-unsafe-fixes` to disable. + #[arg(long, overrides_with("no_unsafe_fixes"))] + unsafe_fixes: bool, + #[arg(long, overrides_with("unsafe_fixes"), hide = true)] + no_unsafe_fixes: bool, /// Show violations with source code. /// Use `--no-show-source` to disable. #[arg(long, overrides_with("no_show_source"))] @@ -100,8 +107,8 @@ pub struct CheckCommand { /// Run in watch mode by re-running whenever files change. #[arg(short, long)] pub watch: bool, - /// Fix any fixable lint violations, but don't report on leftover violations. Implies `--fix`. - /// Use `--no-fix-only` to disable. + /// Apply fixes to resolve lint violations, but don't report on leftover violations. Implies `--fix`. + /// Use `--no-fix-only` to disable or `--unsafe-fixes` to include unsafe fixes. #[arg(long, overrides_with("no_fix_only"))] fix_only: bool, #[clap(long, overrides_with("fix_only"), hide = true)] @@ -497,6 +504,8 @@ impl CheckCommand { cache_dir: self.cache_dir, fix: resolve_bool_arg(self.fix, self.no_fix), fix_only: resolve_bool_arg(self.fix_only, self.no_fix_only), + unsafe_fixes: resolve_bool_arg(self.unsafe_fixes, self.no_unsafe_fixes) + .map(UnsafeFixes::from), force_exclude: resolve_bool_arg(self.force_exclude, self.no_force_exclude), output_format: self.output_format.or(self.format), show_fixes: resolve_bool_arg(self.show_fixes, self.no_show_fixes), @@ -599,6 +608,7 @@ pub struct CliOverrides { pub cache_dir: Option, pub fix: Option, pub fix_only: Option, + pub unsafe_fixes: Option, pub force_exclude: Option, pub output_format: Option, pub show_fixes: Option, @@ -624,6 +634,9 @@ impl ConfigurationTransformer for CliOverrides { if let Some(fix_only) = &self.fix_only { config.fix_only = Some(*fix_only); } + if self.unsafe_fixes.is_some() { + config.unsafe_fixes = self.unsafe_fixes; + } config.lint.rule_selections.push(RuleSelection { select: self.select.clone(), ignore: self diff --git a/crates/ruff_cli/src/cache.rs b/crates/ruff_cli/src/cache.rs index 3a2b851451f6e..f6595c43370a5 100644 --- a/crates/ruff_cli/src/cache.rs +++ b/crates/ruff_cli/src/cache.rs @@ -338,6 +338,7 @@ pub(crate) fn init(path: &Path) -> Result<()> { #[cfg(test)] mod tests { use filetime::{set_file_mtime, FileTime}; + use ruff_linter::settings::types::UnsafeFixes; use std::env::temp_dir; use std::fs; use std::io; @@ -410,6 +411,7 @@ mod tests { Some(&cache), flags::Noqa::Enabled, flags::FixMode::Generate, + UnsafeFixes::Enabled, ) .unwrap(); if diagnostics @@ -455,6 +457,7 @@ mod tests { Some(&cache), flags::Noqa::Enabled, flags::FixMode::Generate, + UnsafeFixes::Enabled, ) .unwrap(); } @@ -712,6 +715,7 @@ mod tests { Some(cache), flags::Noqa::Enabled, flags::FixMode::Generate, + UnsafeFixes::Enabled, ) } } diff --git a/crates/ruff_cli/src/commands/check.rs b/crates/ruff_cli/src/commands/check.rs index f19c8d80d4c11..a6ecb66d5d2f7 100644 --- a/crates/ruff_cli/src/commands/check.rs +++ b/crates/ruff_cli/src/commands/check.rs @@ -11,6 +11,7 @@ use itertools::Itertools; use log::{debug, error, warn}; #[cfg(not(target_family = "wasm"))] use rayon::prelude::*; +use ruff_linter::settings::types::UnsafeFixes; use rustc_hash::FxHashMap; use ruff_diagnostics::Diagnostic; @@ -36,6 +37,7 @@ pub(crate) fn check( cache: flags::Cache, noqa: flags::Noqa, fix_mode: flags::FixMode, + unsafe_fixes: UnsafeFixes, ) -> Result { // Collect all the Python files to check. let start = Instant::now(); @@ -119,7 +121,16 @@ pub(crate) fn check( } }); - lint_path(path, package, &settings.linter, cache, noqa, fix_mode).map_err(|e| { + lint_path( + path, + package, + &settings.linter, + cache, + noqa, + fix_mode, + unsafe_fixes, + ) + .map_err(|e| { (Some(path.to_owned()), { let mut error = e.to_string(); for cause in e.chain() { @@ -199,9 +210,10 @@ fn lint_path( cache: Option<&Cache>, noqa: flags::Noqa, fix_mode: flags::FixMode, + unsafe_fixes: UnsafeFixes, ) -> Result { let result = catch_unwind(|| { - crate::diagnostics::lint_path(path, package, settings, cache, noqa, fix_mode) + crate::diagnostics::lint_path(path, package, settings, cache, noqa, fix_mode, unsafe_fixes) }); match result { @@ -233,6 +245,8 @@ mod test { use std::os::unix::fs::OpenOptionsExt; use anyhow::Result; + + use ruff_linter::settings::types::UnsafeFixes; use rustc_hash::FxHashMap; use tempfile::TempDir; @@ -285,6 +299,7 @@ mod test { flags::Cache::Disabled, flags::Noqa::Disabled, flags::FixMode::Generate, + UnsafeFixes::Enabled, ) .unwrap(); let mut output = Vec::new(); diff --git a/crates/ruff_cli/src/diagnostics.rs b/crates/ruff_cli/src/diagnostics.rs index f228d09c4bda0..c4ea03c55ba81 100644 --- a/crates/ruff_cli/src/diagnostics.rs +++ b/crates/ruff_cli/src/diagnostics.rs @@ -11,6 +11,7 @@ use anyhow::{Context, Result}; use colored::Colorize; use filetime::FileTime; use log::{debug, error, warn}; +use ruff_linter::settings::types::UnsafeFixes; use rustc_hash::FxHashMap; use ruff_diagnostics::Diagnostic; @@ -168,6 +169,7 @@ pub(crate) fn lint_path( cache: Option<&Cache>, noqa: flags::Noqa, fix_mode: flags::FixMode, + unsafe_fixes: UnsafeFixes, ) -> Result { // Check the cache. // TODO(charlie): `fixer::Mode::Apply` and `fixer::Mode::Diff` both have @@ -244,8 +246,15 @@ pub(crate) fn lint_path( result, transformed, fixed, - }) = lint_fix(path, package, noqa, settings, &source_kind, source_type) - { + }) = lint_fix( + path, + package, + noqa, + unsafe_fixes, + settings, + &source_kind, + source_type, + ) { if !fixed.is_empty() { match fix_mode { flags::FixMode::Apply => transformed.write(&mut File::create(path)?)?, @@ -355,6 +364,7 @@ pub(crate) fn lint_stdin( path.unwrap_or_else(|| Path::new("-")), package, noqa, + settings.unsafe_fixes, &settings.linter, &source_kind, source_type, diff --git a/crates/ruff_cli/src/lib.rs b/crates/ruff_cli/src/lib.rs index f9dbb164df0d3..c74279392b564 100644 --- a/crates/ruff_cli/src/lib.rs +++ b/crates/ruff_cli/src/lib.rs @@ -10,7 +10,7 @@ use log::warn; use notify::{recommended_watcher, RecursiveMode, Watcher}; use ruff_linter::logging::{set_up_logging, LogLevel}; -use ruff_linter::settings::flags; +use ruff_linter::settings::flags::FixMode; use ruff_linter::settings::types::SerializationFormat; use ruff_linter::{fs, warn_user, warn_user_once}; use ruff_workspace::Settings; @@ -228,6 +228,7 @@ pub fn check(args: CheckCommand, log_level: LogLevel) -> Result { let Settings { fix, fix_only, + unsafe_fixes, output_format, show_fixes, show_source, @@ -236,17 +237,20 @@ pub fn check(args: CheckCommand, log_level: LogLevel) -> Result { // Fix rules are as follows: // - By default, generate all fixes, but don't apply them to the filesystem. - // - If `--fix` or `--fix-only` is set, always apply fixes to the filesystem (or + // - If `--fix` or `--fix-only` is set, apply applicable fixes to the filesystem (or // print them to stdout, if we're reading from stdin). - // - If `--diff` or `--fix-only` are set, don't print any violations (only - // fixes). + // - If `--diff` or `--fix-only` are set, don't print any violations (only applicable fixes) + // - By default, applicable fixes only include [`Applicablility::Automatic`], but if + // `--unsafe-fixes` is set, then [`Applicablility::Suggested`] fixes are included. + let fix_mode = if cli.diff { - flags::FixMode::Diff + FixMode::Diff } else if fix || fix_only { - flags::FixMode::Apply + FixMode::Apply } else { - flags::FixMode::Generate + FixMode::Generate }; + let cache = !cli.no_cache; let noqa = !cli.ignore_noqa; let mut printer_flags = PrinterFlags::empty(); @@ -290,7 +294,13 @@ pub fn check(args: CheckCommand, log_level: LogLevel) -> Result { return Ok(ExitStatus::Success); } - let printer = Printer::new(output_format, log_level, fix_mode, printer_flags); + let printer = Printer::new( + output_format, + log_level, + fix_mode, + unsafe_fixes, + printer_flags, + ); if cli.watch { if output_format != SerializationFormat::Text { @@ -318,6 +328,7 @@ pub fn check(args: CheckCommand, log_level: LogLevel) -> Result { cache.into(), noqa.into(), fix_mode, + unsafe_fixes, )?; printer.write_continuously(&mut writer, &messages)?; @@ -350,6 +361,7 @@ pub fn check(args: CheckCommand, log_level: LogLevel) -> Result { cache.into(), noqa.into(), fix_mode, + unsafe_fixes, )?; printer.write_continuously(&mut writer, &messages)?; } @@ -376,13 +388,14 @@ pub fn check(args: CheckCommand, log_level: LogLevel) -> Result { cache.into(), noqa.into(), fix_mode, + unsafe_fixes, )? }; // Always try to print violations (the printer itself may suppress output), // unless we're writing fixes via stdin (in which case, the transformed // source code goes to stdout). - if !(is_stdin && matches!(fix_mode, flags::FixMode::Apply | flags::FixMode::Diff)) { + if !(is_stdin && matches!(fix_mode, FixMode::Apply | FixMode::Diff)) { if cli.statistics { printer.write_statistics(&diagnostics, &mut writer)?; } else { diff --git a/crates/ruff_cli/src/printer.rs b/crates/ruff_cli/src/printer.rs index 7700df9749466..d1d5c36a84175 100644 --- a/crates/ruff_cli/src/printer.rs +++ b/crates/ruff_cli/src/printer.rs @@ -19,8 +19,8 @@ use ruff_linter::message::{ }; use ruff_linter::notify_user; use ruff_linter::registry::{AsRule, Rule}; -use ruff_linter::settings::flags; -use ruff_linter::settings::types::SerializationFormat; +use ruff_linter::settings::flags::{self}; +use ruff_linter::settings::types::{SerializationFormat, UnsafeFixes}; use crate::diagnostics::Diagnostics; @@ -73,6 +73,7 @@ pub(crate) struct Printer { format: SerializationFormat, log_level: LogLevel, fix_mode: flags::FixMode, + unsafe_fixes: UnsafeFixes, flags: Flags, } @@ -81,12 +82,14 @@ impl Printer { format: SerializationFormat, log_level: LogLevel, fix_mode: flags::FixMode, + unsafe_fixes: UnsafeFixes, flags: Flags, ) -> Self { Self { format, log_level, fix_mode, + unsafe_fixes, flags, } } @@ -118,19 +121,8 @@ impl Printer { writeln!(writer, "Found {remaining} error{s}.")?; } - if show_fix_status(self.fix_mode) { - let num_fixable = diagnostics - .messages - .iter() - .filter(|message| message.fix.is_some()) - .count(); - if num_fixable > 0 { - writeln!( - writer, - "[{}] {num_fixable} potentially fixable with the --fix option.", - "*".cyan(), - )?; - } + if let Some(fixables) = FixableSummary::try_from(diagnostics, self.unsafe_fixes) { + writeln!(writer, "{fixables}")?; } } else { let fixed = diagnostics @@ -178,6 +170,7 @@ impl Printer { } let context = EmitterContext::new(&diagnostics.notebook_indexes); + let fixables = FixableSummary::try_from(diagnostics, self.unsafe_fixes); match self.format { SerializationFormat::Json => { @@ -191,9 +184,10 @@ impl Printer { } SerializationFormat::Text => { TextEmitter::default() - .with_show_fix_status(show_fix_status(self.fix_mode)) + .with_show_fix_status(show_fix_status(self.fix_mode, fixables.as_ref())) .with_show_fix_diff(self.flags.intersects(Flags::SHOW_FIX_DIFF)) .with_show_source(self.flags.intersects(Flags::SHOW_SOURCE)) + .with_unsafe_fixes(self.unsafe_fixes) .emit(writer, &diagnostics.messages, &context)?; if self.flags.intersects(Flags::SHOW_FIX_SUMMARY) { @@ -209,7 +203,8 @@ impl Printer { SerializationFormat::Grouped => { GroupedEmitter::default() .with_show_source(self.flags.intersects(Flags::SHOW_SOURCE)) - .with_show_fix_status(show_fix_status(self.fix_mode)) + .with_show_fix_status(show_fix_status(self.fix_mode, fixables.as_ref())) + .with_unsafe_fixes(self.unsafe_fixes) .emit(writer, &diagnostics.messages, &context)?; if self.flags.intersects(Flags::SHOW_FIX_SUMMARY) { @@ -359,6 +354,8 @@ impl Printer { ); } + let fixables = FixableSummary::try_from(diagnostics, self.unsafe_fixes); + if !diagnostics.messages.is_empty() { if self.log_level >= LogLevel::Default { writeln!(writer)?; @@ -366,8 +363,9 @@ impl Printer { let context = EmitterContext::new(&diagnostics.notebook_indexes); TextEmitter::default() - .with_show_fix_status(show_fix_status(self.fix_mode)) + .with_show_fix_status(show_fix_status(self.fix_mode, fixables.as_ref())) .with_show_source(self.flags.intersects(Flags::SHOW_SOURCE)) + .with_unsafe_fixes(self.unsafe_fixes) .emit(writer, &diagnostics.messages, &context)?; } writer.flush()?; @@ -390,13 +388,13 @@ fn num_digits(n: usize) -> usize { } /// Return `true` if the [`Printer`] should indicate that a rule is fixable. -const fn show_fix_status(fix_mode: flags::FixMode) -> bool { +fn show_fix_status(fix_mode: flags::FixMode, fixables: Option<&FixableSummary>) -> bool { // If we're in application mode, avoid indicating that a rule is fixable. // If the specific violation were truly fixable, it would've been fixed in // this pass! (We're occasionally unable to determine whether a specific // violation is fixable without trying to fix it, so if fix is not // enabled, we may inadvertently indicate that a rule is fixable.) - !fix_mode.is_apply() + (!fix_mode.is_apply()) && fixables.is_some_and(FixableSummary::any_applicable_fixes) } fn print_fix_summary(writer: &mut dyn Write, fixed: &FxHashMap) -> Result<()> { @@ -439,3 +437,80 @@ fn print_fix_summary(writer: &mut dyn Write, fixed: &FxHashMap } Ok(()) } + +/// Summarizes [applicable][ruff_diagnostics::Applicability] fixes. +#[derive(Debug)] +struct FixableSummary { + applicable: u32, + unapplicable: u32, + unsafe_fixes: UnsafeFixes, +} + +impl FixableSummary { + fn try_from(diagnostics: &Diagnostics, unsafe_fixes: UnsafeFixes) -> Option { + let mut applicable = 0; + let mut unapplicable = 0; + + for message in &diagnostics.messages { + if let Some(fix) = &message.fix { + if fix.applies(unsafe_fixes.required_applicability()) { + applicable += 1; + } else { + unapplicable += 1; + } + } + } + + if applicable == 0 && unapplicable == 0 { + None + } else { + Some(Self { + applicable, + unapplicable, + unsafe_fixes, + }) + } + } + + fn any_applicable_fixes(&self) -> bool { + self.applicable > 0 + } +} + +impl Display for FixableSummary { + fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result { + let fix_prefix = format!("[{}]", "*".cyan()); + + if self.unsafe_fixes.is_enabled() { + write!( + f, + "{fix_prefix} {} fixable with the --fix option.", + self.applicable + ) + } else { + if self.applicable > 0 && self.unapplicable > 0 { + let es = if self.unapplicable == 1 { "" } else { "es" }; + write!( + f, + "{fix_prefix} {} fixable with the `--fix` option ({} hidden fix{es} can be enabled with the `--unsafe-fixes` option).", + self.applicable, self.unapplicable + ) + } else if self.applicable > 0 { + // Only applicable fixes + write!( + f, + "{fix_prefix} {} fixable with the `--fix` option.", + self.applicable, + ) + } else { + // Only unapplicable fixes + let es = if self.unapplicable == 1 { "" } else { "es" }; + write!( + f, + "{} hidden fix{es} can be enabled with the `--unsafe-fixes` option.", + self.unapplicable + ) + } + } + } +} diff --git a/crates/ruff_cli/tests/integration_test.rs b/crates/ruff_cli/tests/integration_test.rs index a47a974d8175b..28a34d7453c47 100644 --- a/crates/ruff_cli/tests/integration_test.rs +++ b/crates/ruff_cli/tests/integration_test.rs @@ -46,16 +46,16 @@ fn stdin_success() { fn stdin_error() { assert_cmd_snapshot!(Command::new(get_cargo_bin(BIN_NAME)) .args(STDIN_BASE_OPTIONS) - .pass_stdin("import os\n"), @r#" + .pass_stdin("import os\n"), @r###" success: false exit_code: 1 ----- stdout ----- -:1:8: F401 [*] `os` imported but unused Found 1 error. - [*] 1 potentially fixable with the --fix option. + [*] 1 fixable with the `--fix` option. ----- stderr ----- - "#); + "###); } #[test] @@ -69,7 +69,7 @@ fn stdin_filename() { ----- stdout ----- F401.py:1:8: F401 [*] `os` imported but unused Found 1 error. - [*] 1 potentially fixable with the --fix option. + [*] 1 fixable with the `--fix` option. ----- stderr ----- "###); @@ -87,7 +87,7 @@ fn stdin_source_type_py() { ----- stdout ----- TCH.py:1:8: F401 [*] `os` imported but unused Found 1 error. - [*] 1 potentially fixable with the --fix option. + [*] 1 fixable with the `--fix` option. ----- stderr ----- "###); @@ -861,7 +861,7 @@ fn check_input_from_argfile() -> Result<()> { ----- stdout ----- /path/to/a.py:1:8: F401 [*] `os` imported but unused Found 1 error. - [*] 1 potentially fixable with the --fix option. + [*] 1 fixable with the `--fix` option. ----- stderr ----- "###); @@ -869,3 +869,239 @@ fn check_input_from_argfile() -> Result<()> { Ok(()) } + +#[test] +fn check_hints_hidden_unsafe_fixes() { + assert_cmd_snapshot!(Command::new(get_cargo_bin(BIN_NAME)) + .args([ + "-", + "--output-format=text", + "--isolated", + "--select", + "F601,UP034", + "--no-cache", + ]) + .pass_stdin("x = {'a': 1, 'a': 1}\nprint(('foo'))\n"), + @r###" + success: false + exit_code: 1 + ----- stdout ----- + -:1:14: F601 Dictionary key literal `'a'` repeated + -:2:7: UP034 [*] Avoid extraneous parentheses + Found 2 errors. + [*] 1 fixable with the `--fix` option (1 hidden fix can be enabled with the `--unsafe-fixes` option). + + ----- stderr ----- + "###); +} + +#[test] +fn check_hints_hidden_unsafe_fixes_with_no_safe_fixes() { + assert_cmd_snapshot!(Command::new(get_cargo_bin(BIN_NAME)) + .args(["-", "--output-format", "text", "--no-cache", "--isolated", "--select", "F601"]) + .pass_stdin("x = {'a': 1, 'a': 1}\n"), + @r###" + success: false + exit_code: 1 + ----- stdout ----- + -:1:14: F601 Dictionary key literal `'a'` repeated + Found 1 error. + 1 hidden fix can be enabled with the `--unsafe-fixes` option. + + ----- stderr ----- + "###); +} + +#[test] +fn check_shows_unsafe_fixes_with_opt_in() { + assert_cmd_snapshot!(Command::new(get_cargo_bin(BIN_NAME)) + .args([ + "-", + "--output-format=text", + "--isolated", + "--select", + "F601,UP034", + "--no-cache", + "--unsafe-fixes", + ]) + .pass_stdin("x = {'a': 1, 'a': 1}\nprint(('foo'))\n"), + @r###" + success: false + exit_code: 1 + ----- stdout ----- + -:1:14: F601 [*] Dictionary key literal `'a'` repeated + -:2:7: UP034 [*] Avoid extraneous parentheses + Found 2 errors. + [*] 2 fixable with the --fix option. + + ----- stderr ----- + "###); +} + +#[test] +fn fix_applies_safe_fixes_by_default() { + assert_cmd_snapshot!( + Command::new(get_cargo_bin(BIN_NAME)) + .args([ + "-", + "--output-format", + "text", + "--isolated", + "--no-cache", + "--select", + "F601,UP034", + "--fix", + ]) + .pass_stdin("x = {'a': 1, 'a': 1}\nprint(('foo'))\n"), + @r###" + success: false + exit_code: 1 + ----- stdout ----- + x = {'a': 1, 'a': 1} + print('foo') + + ----- stderr ----- + "###); +} + +#[test] +fn fix_applies_unsafe_fixes_with_opt_in() { + assert_cmd_snapshot!( + Command::new(get_cargo_bin(BIN_NAME)) + .args([ + "-", + "--output-format", + "text", + "--isolated", + "--no-cache", + "--select", + "F601,UP034", + "--fix", + "--unsafe-fixes", + ]) + .pass_stdin("x = {'a': 1, 'a': 1}\nprint(('foo'))\n"), + @r###" + success: true + exit_code: 0 + ----- stdout ----- + x = {'a': 1} + print('foo') + + ----- stderr ----- + "###); +} + +#[test] +fn fix_only_flag_applies_safe_fixes_by_default() { + assert_cmd_snapshot!( + Command::new(get_cargo_bin(BIN_NAME)) + .args([ + "-", + "--output-format", + "text", + "--isolated", + "--no-cache", + "--select", + "F601,UP034", + "--fix-only", + ]) + .pass_stdin("x = {'a': 1, 'a': 1}\nprint(('foo'))\n"), + @r###" + success: true + exit_code: 0 + ----- stdout ----- + x = {'a': 1, 'a': 1} + print('foo') + + ----- stderr ----- + "###); +} + +#[test] +fn fix_only_flag_applies_unsafe_fixes_with_opt_in() { + assert_cmd_snapshot!( + Command::new(get_cargo_bin(BIN_NAME)) + .args([ + "-", + "--output-format", + "text", + "--isolated", + "--no-cache", + "--select", + "F601,UP034", + "--fix-only", + "--unsafe-fixes", + ]) + .pass_stdin("x = {'a': 1, 'a': 1}\nprint(('foo'))\n"), + @r###" + success: true + exit_code: 0 + ----- stdout ----- + x = {'a': 1} + print('foo') + + ----- stderr ----- + "###); +} + +#[test] +fn diff_shows_safe_fixes_by_default() { + assert_cmd_snapshot!( + Command::new(get_cargo_bin(BIN_NAME)) + .args([ + "-", + "--output-format", + "text", + "--isolated", + "--no-cache", + "--select", + "F601,UP034", + "--diff", + ]) + .pass_stdin("x = {'a': 1, 'a': 1}\nprint(('foo'))\n"), + @r###" + success: false + exit_code: 1 + ----- stdout ----- + @@ -1,2 +1,2 @@ + x = {'a': 1, 'a': 1} + -print('foo') + +print(('foo')) + + + ----- stderr ----- + "### + ); +} + +#[test] +fn diff_shows_unsafe_fixes_with_opt_in() { + assert_cmd_snapshot!( + Command::new(get_cargo_bin(BIN_NAME)) + .args([ + "-", + "--output-format", + "text", + "--isolated", + "--no-cache", + "--select", + "F601,UP034", + "--diff", + "--unsafe-fixes", + ]) + .pass_stdin("x = {'a': 1, 'a': 1}\nprint(('foo'))\n"), + @r###" + success: false + exit_code: 1 + ----- stdout ----- + @@ -1,2 +1,2 @@ + -x = {'a': 1} + -print('foo') + +x = {'a': 1, 'a': 1} + +print(('foo')) + + + ----- stderr ----- + "### + ); +} diff --git a/crates/ruff_cli/tests/lint.rs b/crates/ruff_cli/tests/lint.rs index 2cfae9668738c..bef1cb7f26175 100644 --- a/crates/ruff_cli/tests/lint.rs +++ b/crates/ruff_cli/tests/lint.rs @@ -40,7 +40,7 @@ inline-quotes = "single" -:1:5: B005 Using `.strip()` with multi-character strings is misleading -:1:19: Q000 [*] Double quotes found but single quotes preferred Found 3 errors. - [*] 2 potentially fixable with the --fix option. + [*] 2 fixable with the `--fix` option. ----- stderr ----- "###); @@ -75,7 +75,7 @@ inline-quotes = "single" -:1:5: B005 Using `.strip()` with multi-character strings is misleading -:1:19: Q000 [*] Double quotes found but single quotes preferred Found 3 errors. - [*] 2 potentially fixable with the --fix option. + [*] 2 fixable with the `--fix` option. ----- stderr ----- "###); @@ -110,7 +110,7 @@ inline-quotes = "single" -:1:5: B005 Using `.strip()` with multi-character strings is misleading -:1:19: Q000 [*] Double quotes found but single quotes preferred Found 3 errors. - [*] 2 potentially fixable with the --fix option. + [*] 2 fixable with the `--fix` option. ----- stderr ----- "###); @@ -149,7 +149,7 @@ inline-quotes = "single" -:1:5: B005 Using `.strip()` with multi-character strings is misleading -:1:19: Q000 [*] Double quotes found but single quotes preferred Found 3 errors. - [*] 2 potentially fixable with the --fix option. + [*] 2 fixable with the `--fix` option. ----- stderr ----- "###); diff --git a/crates/ruff_diagnostics/src/fix.rs b/crates/ruff_diagnostics/src/fix.rs index 5979286256ddb..cbc6995b37098 100644 --- a/crates/ruff_diagnostics/src/fix.rs +++ b/crates/ruff_diagnostics/src/fix.rs @@ -6,21 +6,21 @@ use ruff_text_size::{Ranged, TextSize}; use crate::edit::Edit; /// Indicates if a fix can be applied. -#[derive(Copy, Clone, Debug, Hash, PartialEq, Eq)] +#[derive(Copy, Clone, Debug, Hash, PartialEq, Eq, PartialOrd, Ord)] #[cfg_attr(feature = "serde", derive(Serialize, Deserialize))] #[cfg_attr(feature = "serde", serde(rename_all = "lowercase"))] pub enum Applicability { - /// The fix is safe and can always be applied. - /// The fix is definitely what the user intended, or it maintains the exact meaning of the code. - Always, + /// The fix is unsafe and should only be manually applied by the user. + /// The fix is likely to be incorrect or the resulting code may have invalid syntax. + Never, /// The fix is unsafe and should only be applied with user opt-in. /// The fix may be what the user intended, but it is uncertain; the resulting code will have valid syntax. Sometimes, - /// The fix is unsafe and should only be manually applied by the user. - /// The fix is likely to be incorrect or the resulting code may have invalid syntax. - Never, + /// The fix is safe and can always be applied. + /// The fix is definitely what the user intended, or it maintains the exact meaning of the code. + Always, } /// Indicates the level of isolation required to apply a fix. @@ -133,4 +133,9 @@ impl Fix { self.isolation_level = isolation; self } + + /// Return [`true`] if this [`Fix`] should be applied with at a given [`Applicability`]. + pub fn applies(&self, applicability: Applicability) -> bool { + self.applicability >= applicability + } } diff --git a/crates/ruff_linter/src/fix/mod.rs b/crates/ruff_linter/src/fix/mod.rs index 770631e9f7c57..09b9a2def7fcd 100644 --- a/crates/ruff_linter/src/fix/mod.rs +++ b/crates/ruff_linter/src/fix/mod.rs @@ -9,6 +9,7 @@ use ruff_source_file::Locator; use crate::linter::FixTable; use crate::registry::{AsRule, Rule}; +use crate::settings::types::UnsafeFixes; pub(crate) mod codemods; pub(crate) mod edits; @@ -23,11 +24,22 @@ pub(crate) struct FixResult { pub(crate) source_map: SourceMap, } -/// Auto-fix errors in a file, and write the fixed source code to disk. -pub(crate) fn fix_file(diagnostics: &[Diagnostic], locator: &Locator) -> Option { +/// Fix errors in a file, and write the fixed source code to disk. +pub(crate) fn fix_file( + diagnostics: &[Diagnostic], + locator: &Locator, + unsafe_fixes: UnsafeFixes, +) -> Option { + let required_applicability = unsafe_fixes.required_applicability(); + let mut with_fixes = diagnostics .iter() - .filter(|diag| diag.fix.is_some()) + .filter(|diagnostic| { + diagnostic + .fix + .as_ref() + .map_or(false, |fix| fix.applies(required_applicability)) + }) .peekable(); if with_fixes.peek().is_none() { diff --git a/crates/ruff_linter/src/linter.rs b/crates/ruff_linter/src/linter.rs index 69b466cbb3d3b..9219bfa8ef774 100644 --- a/crates/ruff_linter/src/linter.rs +++ b/crates/ruff_linter/src/linter.rs @@ -32,6 +32,7 @@ use crate::message::Message; use crate::noqa::add_noqa; use crate::registry::{AsRule, Rule}; use crate::rules::pycodestyle; +use crate::settings::types::UnsafeFixes; use crate::settings::{flags, LinterSettings}; use crate::source_kind::SourceKind; use crate::{directives, fs}; @@ -415,10 +416,12 @@ fn diagnostics_to_messages( /// Generate `Diagnostic`s from source code content, iteratively fixing /// until stable. +#[allow(clippy::too_many_arguments)] pub fn lint_fix<'a>( path: &Path, package: Option<&Path>, noqa: flags::Noqa, + unsafe_fixes: UnsafeFixes, settings: &LinterSettings, source_kind: &'a SourceKind, source_type: PySourceType, @@ -494,7 +497,7 @@ pub fn lint_fix<'a>( code: fixed_contents, fixes: applied, source_map, - }) = fix_file(&result.data.0, &locator) + }) = fix_file(&result.data.0, &locator, unsafe_fixes) { if iterations < MAX_ITERATIONS { // Count the number of fixed errors. diff --git a/crates/ruff_linter/src/message/grouped.rs b/crates/ruff_linter/src/message/grouped.rs index 41e3f52cd118f..df01b8a9a4107 100644 --- a/crates/ruff_linter/src/message/grouped.rs +++ b/crates/ruff_linter/src/message/grouped.rs @@ -13,11 +13,13 @@ use crate::message::text::{MessageCodeFrame, RuleCodeAndBody}; use crate::message::{ group_messages_by_filename, Emitter, EmitterContext, Message, MessageWithLocation, }; +use crate::settings::types::UnsafeFixes; #[derive(Default)] pub struct GroupedEmitter { show_fix_status: bool, show_source: bool, + unsafe_fixes: UnsafeFixes, } impl GroupedEmitter { @@ -32,6 +34,12 @@ impl GroupedEmitter { self.show_source = show_source; self } + + #[must_use] + pub fn with_unsafe_fixes(mut self, unsafe_fixes: UnsafeFixes) -> Self { + self.unsafe_fixes = unsafe_fixes; + self + } } impl Emitter for GroupedEmitter { @@ -68,6 +76,7 @@ impl Emitter for GroupedEmitter { notebook_index: context.notebook_index(message.filename()), message, show_fix_status: self.show_fix_status, + unsafe_fixes: self.unsafe_fixes, show_source: self.show_source, row_length, column_length, @@ -89,6 +98,7 @@ impl Emitter for GroupedEmitter { struct DisplayGroupedMessage<'a> { message: MessageWithLocation<'a>, show_fix_status: bool, + unsafe_fixes: UnsafeFixes, show_source: bool, row_length: NonZeroUsize, column_length: NonZeroUsize, @@ -138,7 +148,8 @@ impl Display for DisplayGroupedMessage<'_> { ), code_and_body = RuleCodeAndBody { message, - show_fix_status: self.show_fix_status + show_fix_status: self.show_fix_status, + unsafe_fixes: self.unsafe_fixes }, )?; @@ -196,6 +207,7 @@ mod tests { use crate::message::tests::{capture_emitter_output, create_messages}; use crate::message::GroupedEmitter; + use crate::settings::types::UnsafeFixes; #[test] fn default() { @@ -222,4 +234,15 @@ mod tests { assert_snapshot!(content); } + + #[test] + fn fix_status_unsafe() { + let mut emitter = GroupedEmitter::default() + .with_show_fix_status(true) + .with_show_source(true) + .with_unsafe_fixes(UnsafeFixes::Enabled); + let content = capture_emitter_output(&mut emitter, &create_messages()); + + assert_snapshot!(content); + } } diff --git a/crates/ruff_linter/src/message/snapshots/ruff_linter__message__grouped__tests__fix_status.snap b/crates/ruff_linter/src/message/snapshots/ruff_linter__message__grouped__tests__fix_status.snap index 453cf1eda49e2..37344ef4884d3 100644 --- a/crates/ruff_linter/src/message/snapshots/ruff_linter__message__grouped__tests__fix_status.snap +++ b/crates/ruff_linter/src/message/snapshots/ruff_linter__message__grouped__tests__fix_status.snap @@ -3,14 +3,14 @@ source: crates/ruff_linter/src/message/grouped.rs expression: content --- fib.py: - 1:8 F401 [*] `os` imported but unused + 1:8 F401 `os` imported but unused | 1 | import os | ^^ F401 | = help: Remove unused import: `os` - 6:5 F841 [*] Local variable `x` is assigned to but never used + 6:5 F841 Local variable `x` is assigned to but never used | 4 | def fibonacci(n): 5 | """Compute the nth number in the Fibonacci sequence.""" diff --git a/crates/ruff_linter/src/message/snapshots/ruff_linter__message__grouped__tests__fix_status_unsafe.snap b/crates/ruff_linter/src/message/snapshots/ruff_linter__message__grouped__tests__fix_status_unsafe.snap new file mode 100644 index 0000000000000..453cf1eda49e2 --- /dev/null +++ b/crates/ruff_linter/src/message/snapshots/ruff_linter__message__grouped__tests__fix_status_unsafe.snap @@ -0,0 +1,31 @@ +--- +source: crates/ruff_linter/src/message/grouped.rs +expression: content +--- +fib.py: + 1:8 F401 [*] `os` imported but unused + | + 1 | import os + | ^^ F401 + | + = help: Remove unused import: `os` + + 6:5 F841 [*] Local variable `x` is assigned to but never used + | + 4 | def fibonacci(n): + 5 | """Compute the nth number in the Fibonacci sequence.""" + 6 | x = 1 + | ^ F841 + 7 | if n == 0: + 8 | return 0 + | + = help: Remove assignment to unused variable `x` + +undef.py: + 1:4 F821 Undefined name `a` + | + 1 | if a == 1: pass + | ^ F821 + | + + 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 index a53420329c119..77cd92056a7de 100644 --- 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 @@ -2,14 +2,14 @@ source: crates/ruff_linter/src/message/text.rs expression: content --- -fib.py:1:8: F401 [*] `os` imported but unused +fib.py:1:8: F401 `os` imported but unused | 1 | import os | ^^ F401 | = help: Remove unused import: `os` -fib.py:6:5: F841 [*] Local variable `x` is assigned to but never used +fib.py:6:5: F841 Local variable `x` is assigned to but never used | 4 | def fibonacci(n): 5 | """Compute the nth number in the Fibonacci sequence.""" 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 new file mode 100644 index 0000000000000..a53420329c119 --- /dev/null +++ b/crates/ruff_linter/src/message/snapshots/ruff_linter__message__text__tests__fix_status_unsafe.snap @@ -0,0 +1,29 @@ +--- +source: crates/ruff_linter/src/message/text.rs +expression: content +--- +fib.py:1:8: F401 [*] `os` imported but unused + | +1 | import os + | ^^ F401 + | + = help: Remove unused import: `os` + +fib.py:6:5: F841 [*] Local variable `x` is assigned to but never used + | +4 | def fibonacci(n): +5 | """Compute the nth number in the Fibonacci sequence.""" +6 | x = 1 + | ^ F841 +7 | if n == 0: +8 | return 0 + | + = help: Remove assignment to unused variable `x` + +undef.py:1:4: F821 Undefined name `a` + | +1 | if a == 1: pass + | ^ F821 + | + + diff --git a/crates/ruff_linter/src/message/text.rs b/crates/ruff_linter/src/message/text.rs index ee22415d4a47a..867f92bc031de 100644 --- a/crates/ruff_linter/src/message/text.rs +++ b/crates/ruff_linter/src/message/text.rs @@ -16,22 +16,24 @@ use crate::line_width::{LineWidthBuilder, TabSize}; use crate::message::diff::Diff; use crate::message::{Emitter, EmitterContext, Message}; use crate::registry::AsRule; +use crate::settings::types::UnsafeFixes; bitflags! { #[derive(Default)] struct EmitterFlags: u8 { /// Whether to show the fix status of a diagnostic. - const SHOW_FIX_STATUS = 0b0000_0001; + const SHOW_FIX_STATUS = 0b0000_0001; /// Whether to show the diff of a fix, for diagnostics that have a fix. - const SHOW_FIX_DIFF = 0b0000_0010; + const SHOW_FIX_DIFF = 0b0000_0010; /// Whether to show the source code of a diagnostic. - const SHOW_SOURCE = 0b0000_0100; + const SHOW_SOURCE = 0b0000_0100; } } #[derive(Default)] pub struct TextEmitter { flags: EmitterFlags, + unsafe_fixes: UnsafeFixes, } impl TextEmitter { @@ -53,6 +55,12 @@ impl TextEmitter { self.flags.set(EmitterFlags::SHOW_SOURCE, show_source); self } + + #[must_use] + pub fn with_unsafe_fixes(mut self, unsafe_fixes: UnsafeFixes) -> Self { + self.unsafe_fixes = unsafe_fixes; + self + } } impl Emitter for TextEmitter { @@ -105,7 +113,8 @@ impl Emitter for TextEmitter { sep = ":".cyan(), code_and_body = RuleCodeAndBody { message, - show_fix_status: self.flags.intersects(EmitterFlags::SHOW_FIX_STATUS) + show_fix_status: self.flags.intersects(EmitterFlags::SHOW_FIX_STATUS), + unsafe_fixes: self.unsafe_fixes, } )?; @@ -134,28 +143,33 @@ impl Emitter for TextEmitter { pub(super) struct RuleCodeAndBody<'a> { pub(crate) message: &'a Message, pub(crate) show_fix_status: bool, + pub(crate) unsafe_fixes: UnsafeFixes, } impl Display for RuleCodeAndBody<'_> { fn fmt(&self, f: &mut Formatter<'_>) -> std::fmt::Result { let kind = &self.message.kind; + if self.show_fix_status { + if let Some(fix) = self.message.fix.as_ref() { + // Do not display an indicator for unapplicable fixes + if fix.applies(self.unsafe_fixes.required_applicability()) { + return write!( + f, + "{code} {fix}{body}", + code = kind.rule().noqa_code().to_string().red().bold(), + fix = format_args!("[{}] ", "*".cyan()), + body = kind.body, + ); + } + } + }; - if self.show_fix_status && self.message.fix.is_some() { - write!( - f, - "{code} {fix}{body}", - code = kind.rule().noqa_code().to_string().red().bold(), - fix = format_args!("[{}] ", "*".cyan()), - body = kind.body, - ) - } else { - write!( - f, - "{code} {body}", - code = kind.rule().noqa_code().to_string().red().bold(), - body = kind.body, - ) - } + write!( + f, + "{code} {body}", + code = kind.rule().noqa_code().to_string().red().bold(), + body = kind.body, + ) } } @@ -341,6 +355,7 @@ mod tests { use crate::message::tests::{capture_emitter_output, create_messages}; use crate::message::TextEmitter; + use crate::settings::types::UnsafeFixes; #[test] fn default() { @@ -359,4 +374,15 @@ mod tests { assert_snapshot!(content); } + + #[test] + fn fix_status_unsafe() { + let mut emitter = TextEmitter::default() + .with_show_fix_status(true) + .with_show_source(true) + .with_unsafe_fixes(UnsafeFixes::Enabled); + let content = capture_emitter_output(&mut emitter, &create_messages()); + + assert_snapshot!(content); + } } diff --git a/crates/ruff_linter/src/rules/eradicate/snapshots/ruff_linter__rules__eradicate__tests__ERA001_ERA001.py.snap b/crates/ruff_linter/src/rules/eradicate/snapshots/ruff_linter__rules__eradicate__tests__ERA001_ERA001.py.snap index c33d3b0978978..ae9b9588724c8 100644 --- a/crates/ruff_linter/src/rules/eradicate/snapshots/ruff_linter__rules__eradicate__tests__ERA001_ERA001.py.snap +++ b/crates/ruff_linter/src/rules/eradicate/snapshots/ruff_linter__rules__eradicate__tests__ERA001_ERA001.py.snap @@ -1,7 +1,7 @@ --- source: crates/ruff_linter/src/rules/eradicate/mod.rs --- -ERA001.py:1:1: ERA001 [*] Found commented-out code +ERA001.py:1:1: ERA001 Found commented-out code | 1 | #import os | ^^^^^^^^^^ ERA001 @@ -16,7 +16,7 @@ ERA001.py:1:1: ERA001 [*] Found commented-out code 3 2 | #a = 3 4 3 | a = 4 -ERA001.py:2:1: ERA001 [*] Found commented-out code +ERA001.py:2:1: ERA001 Found commented-out code | 1 | #import os 2 | # from foo import junk @@ -33,7 +33,7 @@ ERA001.py:2:1: ERA001 [*] Found commented-out code 4 3 | a = 4 5 4 | #foo(1, 2, 3) -ERA001.py:3:1: ERA001 [*] Found commented-out code +ERA001.py:3:1: ERA001 Found commented-out code | 1 | #import os 2 | # from foo import junk @@ -52,7 +52,7 @@ ERA001.py:3:1: ERA001 [*] Found commented-out code 5 4 | #foo(1, 2, 3) 6 5 | -ERA001.py:5:1: ERA001 [*] Found commented-out code +ERA001.py:5:1: ERA001 Found commented-out code | 3 | #a = 3 4 | a = 4 @@ -72,7 +72,7 @@ ERA001.py:5:1: ERA001 [*] Found commented-out code 7 6 | def foo(x, y, z): 8 7 | content = 1 # print('hello') -ERA001.py:13:5: ERA001 [*] Found commented-out code +ERA001.py:13:5: ERA001 Found commented-out code | 11 | # This is a real comment. 12 | # # This is a (nested) comment. @@ -91,7 +91,7 @@ ERA001.py:13:5: ERA001 [*] Found commented-out code 15 14 | 16 15 | #import os # noqa: ERA001 -ERA001.py:21:5: ERA001 [*] Found commented-out code +ERA001.py:21:5: ERA001 Found commented-out code | 19 | class A(): 20 | pass @@ -109,7 +109,7 @@ ERA001.py:21:5: ERA001 [*] Found commented-out code 23 22 | 24 23 | dictionary = { -ERA001.py:26:5: ERA001 [*] Found commented-out code +ERA001.py:26:5: ERA001 Found commented-out code | 24 | dictionary = { 25 | # "key1": 123, # noqa: ERA001 @@ -129,7 +129,7 @@ ERA001.py:26:5: ERA001 [*] Found commented-out code 28 27 | } 29 28 | -ERA001.py:27:5: ERA001 [*] Found commented-out code +ERA001.py:27:5: ERA001 Found commented-out code | 25 | # "key1": 123, # noqa: ERA001 26 | # "key2": 456, diff --git a/crates/ruff_linter/src/rules/flake8_bugbear/snapshots/ruff_linter__rules__flake8_bugbear__tests__B006_B006_1.py.snap b/crates/ruff_linter/src/rules/flake8_bugbear/snapshots/ruff_linter__rules__flake8_bugbear__tests__B006_B006_1.py.snap index 5bf88bfe614de..35b0a379d0b5b 100644 --- a/crates/ruff_linter/src/rules/flake8_bugbear/snapshots/ruff_linter__rules__flake8_bugbear__tests__B006_B006_1.py.snap +++ b/crates/ruff_linter/src/rules/flake8_bugbear/snapshots/ruff_linter__rules__flake8_bugbear__tests__B006_B006_1.py.snap @@ -1,7 +1,7 @@ --- source: crates/ruff_linter/src/rules/flake8_bugbear/mod.rs --- -B006_1.py:3:22: B006 [*] Do not use mutable data structures for argument defaults +B006_1.py:3:22: B006 Do not use mutable data structures for argument defaults | 1 | # Docstring followed by a newline 2 | diff --git a/crates/ruff_linter/src/rules/flake8_bugbear/snapshots/ruff_linter__rules__flake8_bugbear__tests__B006_B006_2.py.snap b/crates/ruff_linter/src/rules/flake8_bugbear/snapshots/ruff_linter__rules__flake8_bugbear__tests__B006_B006_2.py.snap index 5cf776a5985f9..c925bc5c88a8a 100644 --- a/crates/ruff_linter/src/rules/flake8_bugbear/snapshots/ruff_linter__rules__flake8_bugbear__tests__B006_B006_2.py.snap +++ b/crates/ruff_linter/src/rules/flake8_bugbear/snapshots/ruff_linter__rules__flake8_bugbear__tests__B006_B006_2.py.snap @@ -1,7 +1,7 @@ --- source: crates/ruff_linter/src/rules/flake8_bugbear/mod.rs --- -B006_2.py:4:22: B006 [*] Do not use mutable data structures for argument defaults +B006_2.py:4:22: B006 Do not use mutable data structures for argument defaults | 2 | # Regression test for https://github.com/astral-sh/ruff/issues/7155 3 | diff --git a/crates/ruff_linter/src/rules/flake8_bugbear/snapshots/ruff_linter__rules__flake8_bugbear__tests__B006_B006_3.py.snap b/crates/ruff_linter/src/rules/flake8_bugbear/snapshots/ruff_linter__rules__flake8_bugbear__tests__B006_B006_3.py.snap index aed13094fb804..2c821d5416561 100644 --- a/crates/ruff_linter/src/rules/flake8_bugbear/snapshots/ruff_linter__rules__flake8_bugbear__tests__B006_B006_3.py.snap +++ b/crates/ruff_linter/src/rules/flake8_bugbear/snapshots/ruff_linter__rules__flake8_bugbear__tests__B006_B006_3.py.snap @@ -1,7 +1,7 @@ --- source: crates/ruff_linter/src/rules/flake8_bugbear/mod.rs --- -B006_3.py:4:22: B006 [*] Do not use mutable data structures for argument defaults +B006_3.py:4:22: B006 Do not use mutable data structures for argument defaults | 4 | def foobar(foor, bar={}): | ^^ B006 diff --git a/crates/ruff_linter/src/rules/flake8_bugbear/snapshots/ruff_linter__rules__flake8_bugbear__tests__B006_B006_4.py.snap b/crates/ruff_linter/src/rules/flake8_bugbear/snapshots/ruff_linter__rules__flake8_bugbear__tests__B006_B006_4.py.snap index d1452087066df..bc73ff2c98b22 100644 --- a/crates/ruff_linter/src/rules/flake8_bugbear/snapshots/ruff_linter__rules__flake8_bugbear__tests__B006_B006_4.py.snap +++ b/crates/ruff_linter/src/rules/flake8_bugbear/snapshots/ruff_linter__rules__flake8_bugbear__tests__B006_B006_4.py.snap @@ -1,7 +1,7 @@ --- source: crates/ruff_linter/src/rules/flake8_bugbear/mod.rs --- -B006_4.py:7:26: B006 [*] Do not use mutable data structures for argument defaults +B006_4.py:7:26: B006 Do not use mutable data structures for argument defaults | 6 | class FormFeedIndent: 7 | def __init__(self, a=[]): diff --git a/crates/ruff_linter/src/rules/flake8_bugbear/snapshots/ruff_linter__rules__flake8_bugbear__tests__B006_B006_5.py.snap b/crates/ruff_linter/src/rules/flake8_bugbear/snapshots/ruff_linter__rules__flake8_bugbear__tests__B006_B006_5.py.snap index d741dc3258eba..85fce0a188420 100644 --- a/crates/ruff_linter/src/rules/flake8_bugbear/snapshots/ruff_linter__rules__flake8_bugbear__tests__B006_B006_5.py.snap +++ b/crates/ruff_linter/src/rules/flake8_bugbear/snapshots/ruff_linter__rules__flake8_bugbear__tests__B006_B006_5.py.snap @@ -1,7 +1,7 @@ --- source: crates/ruff_linter/src/rules/flake8_bugbear/mod.rs --- -B006_5.py:5:49: B006 [*] Do not use mutable data structures for argument defaults +B006_5.py:5:49: B006 Do not use mutable data structures for argument defaults | 5 | def import_module_wrong(value: dict[str, str] = {}): | ^^ B006 @@ -22,7 +22,7 @@ B006_5.py:5:49: B006 [*] Do not use mutable data structures for argument default 8 10 | 9 11 | def import_module_with_values_wrong(value: dict[str, str] = {}): -B006_5.py:9:61: B006 [*] Do not use mutable data structures for argument defaults +B006_5.py:9:61: B006 Do not use mutable data structures for argument defaults | 9 | def import_module_with_values_wrong(value: dict[str, str] = {}): | ^^ B006 @@ -44,7 +44,7 @@ B006_5.py:9:61: B006 [*] Do not use mutable data structures for argument default 13 15 | 14 16 | -B006_5.py:15:50: B006 [*] Do not use mutable data structures for argument defaults +B006_5.py:15:50: B006 Do not use mutable data structures for argument defaults | 15 | def import_modules_wrong(value: dict[str, str] = {}): | ^^ B006 @@ -68,7 +68,7 @@ B006_5.py:15:50: B006 [*] Do not use mutable data structures for argument defaul 20 22 | 21 23 | def from_import_module_wrong(value: dict[str, str] = {}): -B006_5.py:21:54: B006 [*] Do not use mutable data structures for argument defaults +B006_5.py:21:54: B006 Do not use mutable data structures for argument defaults | 21 | def from_import_module_wrong(value: dict[str, str] = {}): | ^^ B006 @@ -89,7 +89,7 @@ B006_5.py:21:54: B006 [*] Do not use mutable data structures for argument defaul 24 26 | 25 27 | def from_imports_module_wrong(value: dict[str, str] = {}): -B006_5.py:25:55: B006 [*] Do not use mutable data structures for argument defaults +B006_5.py:25:55: B006 Do not use mutable data structures for argument defaults | 25 | def from_imports_module_wrong(value: dict[str, str] = {}): | ^^ B006 @@ -112,7 +112,7 @@ B006_5.py:25:55: B006 [*] Do not use mutable data structures for argument defaul 29 31 | 30 32 | def import_and_from_imports_module_wrong(value: dict[str, str] = {}): -B006_5.py:30:66: B006 [*] Do not use mutable data structures for argument defaults +B006_5.py:30:66: B006 Do not use mutable data structures for argument defaults | 30 | def import_and_from_imports_module_wrong(value: dict[str, str] = {}): | ^^ B006 @@ -135,7 +135,7 @@ B006_5.py:30:66: B006 [*] Do not use mutable data structures for argument defaul 34 36 | 35 37 | def import_docstring_module_wrong(value: dict[str, str] = {}): -B006_5.py:35:59: B006 [*] Do not use mutable data structures for argument defaults +B006_5.py:35:59: B006 Do not use mutable data structures for argument defaults | 35 | def import_docstring_module_wrong(value: dict[str, str] = {}): | ^^ B006 @@ -158,7 +158,7 @@ B006_5.py:35:59: B006 [*] Do not use mutable data structures for argument defaul 39 41 | 40 42 | def import_module_wrong(value: dict[str, str] = {}): -B006_5.py:40:49: B006 [*] Do not use mutable data structures for argument defaults +B006_5.py:40:49: B006 Do not use mutable data structures for argument defaults | 40 | def import_module_wrong(value: dict[str, str] = {}): | ^^ B006 @@ -181,7 +181,7 @@ B006_5.py:40:49: B006 [*] Do not use mutable data structures for argument defaul 44 46 | 45 47 | def import_module_wrong(value: dict[str, str] = {}): -B006_5.py:45:49: B006 [*] Do not use mutable data structures for argument defaults +B006_5.py:45:49: B006 Do not use mutable data structures for argument defaults | 45 | def import_module_wrong(value: dict[str, str] = {}): | ^^ B006 @@ -203,7 +203,7 @@ B006_5.py:45:49: B006 [*] Do not use mutable data structures for argument defaul 48 50 | 49 51 | -B006_5.py:50:49: B006 [*] Do not use mutable data structures for argument defaults +B006_5.py:50:49: B006 Do not use mutable data structures for argument defaults | 50 | def import_module_wrong(value: dict[str, str] = {}): | ^^ B006 @@ -226,7 +226,7 @@ B006_5.py:50:49: B006 [*] Do not use mutable data structures for argument defaul 54 56 | 55 57 | def import_module_wrong(value: dict[str, str] = {}): -B006_5.py:55:49: B006 [*] Do not use mutable data structures for argument defaults +B006_5.py:55:49: B006 Do not use mutable data structures for argument defaults | 55 | def import_module_wrong(value: dict[str, str] = {}): | ^^ B006 @@ -247,7 +247,7 @@ B006_5.py:55:49: B006 [*] Do not use mutable data structures for argument defaul 58 60 | 59 61 | def import_module_wrong(value: dict[str, str] = {}): -B006_5.py:59:49: B006 [*] Do not use mutable data structures for argument defaults +B006_5.py:59:49: B006 Do not use mutable data structures for argument defaults | 59 | def import_module_wrong(value: dict[str, str] = {}): | ^^ B006 @@ -267,7 +267,7 @@ B006_5.py:59:49: B006 [*] Do not use mutable data structures for argument defaul 61 63 | 62 64 | -B006_5.py:63:49: B006 [*] Do not use mutable data structures for argument defaults +B006_5.py:63:49: B006 Do not use mutable data structures for argument defaults | 63 | def import_module_wrong(value: dict[str, str] = {}): | ^^ B006 diff --git a/crates/ruff_linter/src/rules/flake8_bugbear/snapshots/ruff_linter__rules__flake8_bugbear__tests__B006_B006_6.py.snap b/crates/ruff_linter/src/rules/flake8_bugbear/snapshots/ruff_linter__rules__flake8_bugbear__tests__B006_B006_6.py.snap index f1af067d82f64..f858253ec8d07 100644 --- a/crates/ruff_linter/src/rules/flake8_bugbear/snapshots/ruff_linter__rules__flake8_bugbear__tests__B006_B006_6.py.snap +++ b/crates/ruff_linter/src/rules/flake8_bugbear/snapshots/ruff_linter__rules__flake8_bugbear__tests__B006_B006_6.py.snap @@ -1,7 +1,7 @@ --- source: crates/ruff_linter/src/rules/flake8_bugbear/mod.rs --- -B006_6.py:4:22: B006 [*] Do not use mutable data structures for argument defaults +B006_6.py:4:22: B006 Do not use mutable data structures for argument defaults | 2 | # Same as B006_2.py, but import instead of docstring 3 | diff --git a/crates/ruff_linter/src/rules/flake8_bugbear/snapshots/ruff_linter__rules__flake8_bugbear__tests__B006_B006_7.py.snap b/crates/ruff_linter/src/rules/flake8_bugbear/snapshots/ruff_linter__rules__flake8_bugbear__tests__B006_B006_7.py.snap index fbe42a7285e85..b5418395f5505 100644 --- a/crates/ruff_linter/src/rules/flake8_bugbear/snapshots/ruff_linter__rules__flake8_bugbear__tests__B006_B006_7.py.snap +++ b/crates/ruff_linter/src/rules/flake8_bugbear/snapshots/ruff_linter__rules__flake8_bugbear__tests__B006_B006_7.py.snap @@ -1,7 +1,7 @@ --- source: crates/ruff_linter/src/rules/flake8_bugbear/mod.rs --- -B006_7.py:4:22: B006 [*] Do not use mutable data structures for argument defaults +B006_7.py:4:22: B006 Do not use mutable data structures for argument defaults | 2 | # Same as B006_3.py, but import instead of docstring 3 | diff --git a/crates/ruff_linter/src/rules/flake8_bugbear/snapshots/ruff_linter__rules__flake8_bugbear__tests__B006_B006_B008.py.snap b/crates/ruff_linter/src/rules/flake8_bugbear/snapshots/ruff_linter__rules__flake8_bugbear__tests__B006_B006_B008.py.snap index 6f868ac491c64..2f8e3285bb24a 100644 --- a/crates/ruff_linter/src/rules/flake8_bugbear/snapshots/ruff_linter__rules__flake8_bugbear__tests__B006_B006_B008.py.snap +++ b/crates/ruff_linter/src/rules/flake8_bugbear/snapshots/ruff_linter__rules__flake8_bugbear__tests__B006_B006_B008.py.snap @@ -1,7 +1,7 @@ --- source: crates/ruff_linter/src/rules/flake8_bugbear/mod.rs --- -B006_B008.py:63:25: B006 [*] Do not use mutable data structures for argument defaults +B006_B008.py:63:25: B006 Do not use mutable data structures for argument defaults | 63 | def this_is_wrong(value=[1, 2, 3]): | ^^^^^^^^^ B006 @@ -21,7 +21,7 @@ B006_B008.py:63:25: B006 [*] Do not use mutable data structures for argument def 65 67 | 66 68 | -B006_B008.py:67:30: B006 [*] Do not use mutable data structures for argument defaults +B006_B008.py:67:30: B006 Do not use mutable data structures for argument defaults | 67 | def this_is_also_wrong(value={}): | ^^ B006 @@ -41,7 +41,7 @@ B006_B008.py:67:30: B006 [*] Do not use mutable data structures for argument def 69 71 | 70 72 | -B006_B008.py:73:52: B006 [*] Do not use mutable data structures for argument defaults +B006_B008.py:73:52: B006 Do not use mutable data structures for argument defaults | 71 | class Foo: 72 | @staticmethod @@ -63,7 +63,7 @@ B006_B008.py:73:52: B006 [*] Do not use mutable data structures for argument def 75 77 | 76 78 | -B006_B008.py:77:31: B006 [*] Do not use mutable data structures for argument defaults +B006_B008.py:77:31: B006 Do not use mutable data structures for argument defaults | 77 | def multiline_arg_wrong(value={ | _______________________________^ @@ -97,7 +97,7 @@ B006_B008.py:82:36: B006 Do not use mutable data structures for argument default | = help: Replace with `None`; initialize within function -B006_B008.py:85:20: B006 [*] Do not use mutable data structures for argument defaults +B006_B008.py:85:20: B006 Do not use mutable data structures for argument defaults | 85 | def and_this(value=set()): | ^^^^^ B006 @@ -117,7 +117,7 @@ B006_B008.py:85:20: B006 [*] Do not use mutable data structures for argument def 87 89 | 88 90 | -B006_B008.py:89:20: B006 [*] Do not use mutable data structures for argument defaults +B006_B008.py:89:20: B006 Do not use mutable data structures for argument defaults | 89 | def this_too(value=collections.OrderedDict()): | ^^^^^^^^^^^^^^^^^^^^^^^^^ B006 @@ -137,7 +137,7 @@ B006_B008.py:89:20: B006 [*] Do not use mutable data structures for argument def 91 93 | 92 94 | -B006_B008.py:93:32: B006 [*] Do not use mutable data structures for argument defaults +B006_B008.py:93:32: B006 Do not use mutable data structures for argument defaults | 93 | async def async_this_too(value=collections.defaultdict()): | ^^^^^^^^^^^^^^^^^^^^^^^^^ B006 @@ -157,7 +157,7 @@ B006_B008.py:93:32: B006 [*] Do not use mutable data structures for argument def 95 97 | 96 98 | -B006_B008.py:97:26: B006 [*] Do not use mutable data structures for argument defaults +B006_B008.py:97:26: B006 Do not use mutable data structures for argument defaults | 97 | def dont_forget_me(value=collections.deque()): | ^^^^^^^^^^^^^^^^^^^ B006 @@ -177,7 +177,7 @@ B006_B008.py:97:26: B006 [*] Do not use mutable data structures for argument def 99 101 | 100 102 | -B006_B008.py:102:46: B006 [*] Do not use mutable data structures for argument defaults +B006_B008.py:102:46: B006 Do not use mutable data structures for argument defaults | 101 | # N.B. we're also flagging the function call in the comprehension 102 | def list_comprehension_also_not_okay(default=[i**2 for i in range(3)]): @@ -198,7 +198,7 @@ B006_B008.py:102:46: B006 [*] Do not use mutable data structures for argument de 104 106 | 105 107 | -B006_B008.py:106:46: B006 [*] Do not use mutable data structures for argument defaults +B006_B008.py:106:46: B006 Do not use mutable data structures for argument defaults | 106 | def dict_comprehension_also_not_okay(default={i: i**2 for i in range(3)}): | ^^^^^^^^^^^^^^^^^^^^^^^^^^^ B006 @@ -218,7 +218,7 @@ B006_B008.py:106:46: B006 [*] Do not use mutable data structures for argument de 108 110 | 109 111 | -B006_B008.py:110:45: B006 [*] Do not use mutable data structures for argument defaults +B006_B008.py:110:45: B006 Do not use mutable data structures for argument defaults | 110 | def set_comprehension_also_not_okay(default={i**2 for i in range(3)}): | ^^^^^^^^^^^^^^^^^^^^^^^^ B006 @@ -238,7 +238,7 @@ B006_B008.py:110:45: B006 [*] Do not use mutable data structures for argument de 112 114 | 113 115 | -B006_B008.py:114:33: B006 [*] Do not use mutable data structures for argument defaults +B006_B008.py:114:33: B006 Do not use mutable data structures for argument defaults | 114 | def kwonlyargs_mutable(*, value=[]): | ^^ B006 @@ -258,7 +258,7 @@ B006_B008.py:114:33: B006 [*] Do not use mutable data structures for argument de 116 118 | 117 119 | -B006_B008.py:239:20: B006 [*] Do not use mutable data structures for argument defaults +B006_B008.py:239:20: B006 Do not use mutable data structures for argument defaults | 237 | # B006 and B008 238 | # We should handle arbitrary nesting of these B008. @@ -280,7 +280,7 @@ B006_B008.py:239:20: B006 [*] Do not use mutable data structures for argument de 241 243 | 242 244 | -B006_B008.py:276:27: B006 [*] Do not use mutable data structures for argument defaults +B006_B008.py:276:27: B006 Do not use mutable data structures for argument defaults | 275 | def mutable_annotations( 276 | a: list[int] | None = [], @@ -306,7 +306,7 @@ B006_B008.py:276:27: B006 [*] Do not use mutable data structures for argument de 282 284 | 283 285 | -B006_B008.py:277:35: B006 [*] Do not use mutable data structures for argument defaults +B006_B008.py:277:35: B006 Do not use mutable data structures for argument defaults | 275 | def mutable_annotations( 276 | a: list[int] | None = [], @@ -332,7 +332,7 @@ B006_B008.py:277:35: B006 [*] Do not use mutable data structures for argument de 282 284 | 283 285 | -B006_B008.py:278:62: B006 [*] Do not use mutable data structures for argument defaults +B006_B008.py:278:62: B006 Do not use mutable data structures for argument defaults | 276 | a: list[int] | None = [], 277 | b: Optional[Dict[int, int]] = {}, @@ -357,7 +357,7 @@ B006_B008.py:278:62: B006 [*] Do not use mutable data structures for argument de 282 284 | 283 285 | -B006_B008.py:279:80: B006 [*] Do not use mutable data structures for argument defaults +B006_B008.py:279:80: B006 Do not use mutable data structures for argument defaults | 277 | b: Optional[Dict[int, int]] = {}, 278 | c: Annotated[Union[Set[str], abc.Sized], "annotation"] = set(), @@ -381,7 +381,7 @@ B006_B008.py:279:80: B006 [*] Do not use mutable data structures for argument de 282 284 | 283 285 | -B006_B008.py:284:52: B006 [*] Do not use mutable data structures for argument defaults +B006_B008.py:284:52: B006 Do not use mutable data structures for argument defaults | 284 | def single_line_func_wrong(value: dict[str, str] = {}): | ^^ B006 @@ -402,7 +402,7 @@ B006_B008.py:284:52: B006 [*] Do not use mutable data structures for argument de 287 289 | 288 290 | def single_line_func_wrong(value: dict[str, str] = {}): -B006_B008.py:288:52: B006 [*] Do not use mutable data structures for argument defaults +B006_B008.py:288:52: B006 Do not use mutable data structures for argument defaults | 288 | def single_line_func_wrong(value: dict[str, str] = {}): | ^^ B006 @@ -424,7 +424,7 @@ B006_B008.py:288:52: B006 [*] Do not use mutable data structures for argument de 291 293 | 292 294 | -B006_B008.py:293:52: B006 [*] Do not use mutable data structures for argument defaults +B006_B008.py:293:52: B006 Do not use mutable data structures for argument defaults | 293 | def single_line_func_wrong(value: dict[str, str] = {}): | ^^ B006 @@ -444,7 +444,7 @@ B006_B008.py:293:52: B006 [*] Do not use mutable data structures for argument de 295 297 | 296 298 | -B006_B008.py:297:52: B006 [*] Do not use mutable data structures for argument defaults +B006_B008.py:297:52: B006 Do not use mutable data structures for argument defaults | 297 | def single_line_func_wrong(value: dict[str, str] = {}): | ^^ B006 @@ -465,7 +465,7 @@ B006_B008.py:297:52: B006 [*] Do not use mutable data structures for argument de 299 301 | ... 300 302 | -B006_B008.py:302:52: B006 [*] Do not use mutable data structures for argument defaults +B006_B008.py:302:52: B006 Do not use mutable data structures for argument defaults | 302 | def single_line_func_wrong(value: dict[str, str] = { | ____________________________________________________^ @@ -500,7 +500,7 @@ B006_B008.py:308:52: B006 Do not use mutable data structures for argument defaul | = help: Replace with `None`; initialize within function -B006_B008.py:313:52: B006 [*] Do not use mutable data structures for argument defaults +B006_B008.py:313:52: B006 Do not use mutable data structures for argument defaults | 313 | def single_line_func_wrong(value: dict[str, str] = {}): | ^^ B006 diff --git a/crates/ruff_linter/src/rules/flake8_bugbear/snapshots/ruff_linter__rules__flake8_bugbear__tests__extend_immutable_calls_arg_annotation.snap b/crates/ruff_linter/src/rules/flake8_bugbear/snapshots/ruff_linter__rules__flake8_bugbear__tests__extend_immutable_calls_arg_annotation.snap index d191804dd6772..82d3a1f8a543f 100644 --- a/crates/ruff_linter/src/rules/flake8_bugbear/snapshots/ruff_linter__rules__flake8_bugbear__tests__extend_immutable_calls_arg_annotation.snap +++ b/crates/ruff_linter/src/rules/flake8_bugbear/snapshots/ruff_linter__rules__flake8_bugbear__tests__extend_immutable_calls_arg_annotation.snap @@ -1,7 +1,7 @@ --- source: crates/ruff_linter/src/rules/flake8_bugbear/mod.rs --- -B006_extended.py:17:55: B006 [*] Do not use mutable data structures for argument defaults +B006_extended.py:17:55: B006 Do not use mutable data structures for argument defaults | 17 | def error_due_to_missing_import(foo: ImmutableTypeA = []): | ^^ B006 diff --git a/crates/ruff_linter/src/rules/pycodestyle/snapshots/ruff_linter__rules__pycodestyle__tests__E731_E731.py.snap b/crates/ruff_linter/src/rules/pycodestyle/snapshots/ruff_linter__rules__pycodestyle__tests__E731_E731.py.snap index ce00ec0a9a768..b8456517e9faf 100644 --- a/crates/ruff_linter/src/rules/pycodestyle/snapshots/ruff_linter__rules__pycodestyle__tests__E731_E731.py.snap +++ b/crates/ruff_linter/src/rules/pycodestyle/snapshots/ruff_linter__rules__pycodestyle__tests__E731_E731.py.snap @@ -100,7 +100,7 @@ E731.py:24:5: E731 [*] Do not assign a `lambda` expression, use a `def` 26 27 | 27 28 | def scope(): -E731.py:57:5: E731 [*] Do not assign a `lambda` expression, use a `def` +E731.py:57:5: E731 Do not assign a `lambda` expression, use a `def` | 55 | class Scope: 56 | # E731 @@ -120,7 +120,7 @@ E731.py:57:5: E731 [*] Do not assign a `lambda` expression, use a `def` 59 60 | 60 61 | class Scope: -E731.py:64:5: E731 [*] Do not assign a `lambda` expression, use a `def` +E731.py:64:5: E731 Do not assign a `lambda` expression, use a `def` | 63 | # E731 64 | f: Callable[[int], int] = lambda x: 2 * x @@ -139,7 +139,7 @@ E731.py:64:5: E731 [*] Do not assign a `lambda` expression, use a `def` 66 67 | 67 68 | def scope(): -E731.py:73:9: E731 [*] Do not assign a `lambda` expression, use a `def` +E731.py:73:9: E731 Do not assign a `lambda` expression, use a `def` | 71 | x: Callable[[int], int] 72 | if True: @@ -161,7 +161,7 @@ E731.py:73:9: E731 [*] Do not assign a `lambda` expression, use a `def` 75 76 | x = lambda: 2 76 77 | return x -E731.py:75:9: E731 [*] Do not assign a `lambda` expression, use a `def` +E731.py:75:9: E731 Do not assign a `lambda` expression, use a `def` | 73 | x = lambda: 1 74 | else: @@ -322,7 +322,7 @@ E731.py:135:5: E731 [*] Do not assign a `lambda` expression, use a `def` 137 138 | 138 139 | class TemperatureScales(Enum): -E731.py:139:5: E731 [*] Do not assign a `lambda` expression, use a `def` +E731.py:139:5: E731 Do not assign a `lambda` expression, use a `def` | 138 | class TemperatureScales(Enum): 139 | CELSIUS = (lambda deg_c: deg_c) @@ -342,7 +342,7 @@ E731.py:139:5: E731 [*] Do not assign a `lambda` expression, use a `def` 141 142 | 142 143 | -E731.py:140:5: E731 [*] Do not assign a `lambda` expression, use a `def` +E731.py:140:5: E731 Do not assign a `lambda` expression, use a `def` | 138 | class TemperatureScales(Enum): 139 | CELSIUS = (lambda deg_c: deg_c) diff --git a/crates/ruff_linter/src/rules/ruff/snapshots/ruff_linter__rules__ruff__tests__ruf100_5.snap b/crates/ruff_linter/src/rules/ruff/snapshots/ruff_linter__rules__ruff__tests__ruf100_5.snap index 0808aaab058fe..6e2ade02471e9 100644 --- a/crates/ruff_linter/src/rules/ruff/snapshots/ruff_linter__rules__ruff__tests__ruf100_5.snap +++ b/crates/ruff_linter/src/rules/ruff/snapshots/ruff_linter__rules__ruff__tests__ruf100_5.snap @@ -1,7 +1,7 @@ --- source: crates/ruff_linter/src/rules/ruff/mod.rs --- -RUF100_5.py:7:5: ERA001 [*] Found commented-out code +RUF100_5.py:7:5: ERA001 Found commented-out code | 5 | # "key1": 123, # noqa: ERA001 6 | # "key2": 456, # noqa @@ -20,7 +20,7 @@ RUF100_5.py:7:5: ERA001 [*] Found commented-out code 9 8 | 10 9 | -RUF100_5.py:11:1: ERA001 [*] Found commented-out code +RUF100_5.py:11:1: ERA001 Found commented-out code | 11 | #import os # noqa: E501 | ^^^^^^^^^^^^^^^^^^^^^^^^ ERA001 diff --git a/crates/ruff_linter/src/settings/mod.rs b/crates/ruff_linter/src/settings/mod.rs index 48fdf907f8db0..881611cfc8d52 100644 --- a/crates/ruff_linter/src/settings/mod.rs +++ b/crates/ruff_linter/src/settings/mod.rs @@ -60,6 +60,7 @@ pub struct LinterSettings { pub tab_size: TabSize, pub task_tags: Vec, pub typing_modules: Vec, + // Plugins pub flake8_annotations: flake8_annotations::settings::Settings, pub flake8_bandit: flake8_bandit::settings::Settings, diff --git a/crates/ruff_linter/src/settings/types.rs b/crates/ruff_linter/src/settings/types.rs index dc8a1e0f4ffd1..0bdc8b9d587cf 100644 --- a/crates/ruff_linter/src/settings/types.rs +++ b/crates/ruff_linter/src/settings/types.rs @@ -7,6 +7,7 @@ use std::string::ToString; use anyhow::{bail, Result}; use globset::{Glob, GlobSet, GlobSetBuilder}; use pep440_rs::{Version as Pep440Version, VersionSpecifiers}; +use ruff_diagnostics::Applicability; use serde::{de, Deserialize, Deserializer, Serialize}; use strum::IntoEnumIterator; use strum_macros::EnumIter; @@ -99,7 +100,7 @@ impl PythonVersion { } } -#[derive(Clone, Copy, Debug, PartialEq, Eq, Default, CacheKey, is_macro::Is)] +#[derive(Debug, Clone, Copy, PartialEq, Eq, Default, CacheKey, is_macro::Is)] pub enum PreviewMode { #[default] Disabled, @@ -116,6 +117,32 @@ impl From for PreviewMode { } } +#[derive(Debug, Copy, Clone, CacheKey, Default, PartialEq, Eq, is_macro::Is)] +pub enum UnsafeFixes { + #[default] + Disabled, + Enabled, +} + +impl From for UnsafeFixes { + fn from(version: bool) -> Self { + if version { + UnsafeFixes::Enabled + } else { + UnsafeFixes::Disabled + } + } +} + +impl UnsafeFixes { + pub fn required_applicability(&self) -> Applicability { + match self { + Self::Enabled => Applicability::Sometimes, + Self::Disabled => Applicability::Always, + } + } +} + #[derive(Debug, Clone, CacheKey, PartialEq, PartialOrd, Eq, Ord)] pub enum FilePattern { Builtin(&'static str), diff --git a/crates/ruff_linter/src/test.rs b/crates/ruff_linter/src/test.rs index 75de40cd2e0d7..27c94224d9d92 100644 --- a/crates/ruff_linter/src/test.rs +++ b/crates/ruff_linter/src/test.rs @@ -26,6 +26,7 @@ use crate::message::{Emitter, EmitterContext, Message, TextEmitter}; use crate::packaging::detect_package_root; use crate::registry::AsRule; use crate::rules::pycodestyle::rules::syntax_error; +use crate::settings::types::UnsafeFixes; use crate::settings::{flags, LinterSettings}; use crate::source_kind::SourceKind; use ruff_notebook::Notebook; @@ -155,8 +156,11 @@ pub(crate) fn test_contents<'a>( code: fixed_contents, source_map, .. - }) = fix_file(&diagnostics, &Locator::new(transformed.source_code())) - { + }) = fix_file( + &diagnostics, + &Locator::new(transformed.source_code()), + UnsafeFixes::Enabled, + ) { if iterations < max_iterations() { iterations += 1; } else { @@ -294,6 +298,7 @@ pub(crate) fn print_jupyter_messages( .with_show_fix_status(true) .with_show_fix_diff(true) .with_show_source(true) + .with_unsafe_fixes(UnsafeFixes::Enabled) .emit( &mut output, messages, @@ -314,6 +319,7 @@ pub(crate) fn print_messages(messages: &[Message]) -> String { .with_show_fix_status(true) .with_show_fix_diff(true) .with_show_source(true) + .with_unsafe_fixes(UnsafeFixes::Enabled) .emit( &mut output, messages, diff --git a/crates/ruff_workspace/src/configuration.rs b/crates/ruff_workspace/src/configuration.rs index ad350a3346355..9733bbf197bdf 100644 --- a/crates/ruff_workspace/src/configuration.rs +++ b/crates/ruff_workspace/src/configuration.rs @@ -24,7 +24,7 @@ use ruff_linter::rule_selector::{PreviewOptions, Specificity}; use ruff_linter::settings::rule_table::RuleTable; use ruff_linter::settings::types::{ FilePattern, FilePatternSet, PerFileIgnore, PreviewMode, PythonVersion, SerializationFormat, - Version, + UnsafeFixes, Version, }; use ruff_linter::settings::{ resolve_per_file_ignores, LinterSettings, DUMMY_VARIABLE_RGX, PREFIXES, TASK_TAGS, @@ -64,6 +64,7 @@ pub struct Configuration { pub extend: Option, pub fix: Option, pub fix_only: Option, + pub unsafe_fixes: Option, pub output_format: Option, pub preview: Option, pub required_version: Option, @@ -137,6 +138,7 @@ impl Configuration { .unwrap_or_else(|| cache_dir(project_root)), fix: self.fix.unwrap_or(false), fix_only: self.fix_only.unwrap_or(false), + unsafe_fixes: self.unsafe_fixes.unwrap_or_default(), output_format: self.output_format.unwrap_or_default(), show_fixes: self.show_fixes.unwrap_or(false), show_source: self.show_source.unwrap_or(false), @@ -365,6 +367,7 @@ impl Configuration { }), fix: options.fix, fix_only: options.fix_only, + unsafe_fixes: options.unsafe_fixes.map(UnsafeFixes::from), output_format: options.output_format.or_else(|| { options .format @@ -418,6 +421,7 @@ impl Configuration { include: self.include.or(config.include), fix: self.fix.or(config.fix), 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), force_exclude: self.force_exclude.or(config.force_exclude), line_length: self.line_length.or(config.line_length), diff --git a/crates/ruff_workspace/src/options.rs b/crates/ruff_workspace/src/options.rs index a376aea45ff11..1a79575c52ae4 100644 --- a/crates/ruff_workspace/src/options.rs +++ b/crates/ruff_workspace/src/options.rs @@ -89,9 +89,18 @@ pub struct Options { /// 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. #[option(default = "false", value_type = "bool", example = "fix = true")] pub fix: Option, + /// Enable application of unsafe fixes. + #[option( + default = "false", + value_type = "bool", + example = "unsafe-fixes = true" + )] + pub unsafe_fixes: Option, + /// Like `fix`, but disables reporting on leftover violation. Implies `fix`. #[option(default = "false", value_type = "bool", example = "fix-only = true")] pub fix_only: Option, diff --git a/crates/ruff_workspace/src/settings.rs b/crates/ruff_workspace/src/settings.rs index 0e069a58c01dd..ec4b2834d70f6 100644 --- a/crates/ruff_workspace/src/settings.rs +++ b/crates/ruff_workspace/src/settings.rs @@ -1,7 +1,7 @@ use path_absolutize::path_dedot; use ruff_cache::cache_dir; use ruff_formatter::{FormatOptions, IndentStyle, LineWidth}; -use ruff_linter::settings::types::{FilePattern, FilePatternSet, SerializationFormat}; +use ruff_linter::settings::types::{FilePattern, FilePatternSet, SerializationFormat, UnsafeFixes}; use ruff_linter::settings::LinterSettings; use ruff_macros::CacheKey; use ruff_python_ast::PySourceType; @@ -19,6 +19,8 @@ pub struct Settings { #[cache_key(ignore)] pub fix_only: bool, #[cache_key(ignore)] + pub unsafe_fixes: UnsafeFixes, + #[cache_key(ignore)] pub output_format: SerializationFormat, #[cache_key(ignore)] pub show_fixes: bool, @@ -40,6 +42,7 @@ impl Default for Settings { output_format: SerializationFormat::default(), show_fixes: false, show_source: false, + unsafe_fixes: UnsafeFixes::default(), linter: LinterSettings::new(project_root), file_resolver: FileResolverSettings::new(project_root), formatter: FormatterSettings::default(), diff --git a/docs/configuration.md b/docs/configuration.md index e97374b62014a..e963c310e6000 100644 --- a/docs/configuration.md +++ b/docs/configuration.md @@ -193,7 +193,9 @@ Arguments: Options: --fix - Attempt to automatically fix lint violations. Use `--no-fix` to disable + Apply fixes to resolve lint violations. Use `--no-fix` to disable or `--unsafe-fixes` to include unsafe fixes + --unsafe-fixes + Include fixes that may not retain the original intent of the code. Use `--no-unsafe-fixes` to disable --show-source Show violations with source code. Use `--no-show-source` to disable --show-fixes @@ -203,7 +205,7 @@ Options: -w, --watch Run in watch mode by re-running whenever files change --fix-only - Fix any fixable lint violations, but don't report on leftover violations. Implies `--fix`. Use `--no-fix-only` to disable + Apply fixes to resolve lint violations, but don't report on leftover violations. Implies `--fix`. Use `--no-fix-only` to disable or `--unsafe-fixes` to include unsafe fixes --ignore-noqa Ignore any `# noqa` comments --output-format diff --git a/ruff.schema.json b/ruff.schema.json index 20199a2bd9304..de0bf5807ed9d 100644 --- a/ruff.schema.json +++ b/ruff.schema.json @@ -128,7 +128,7 @@ } }, "fix": { - "description": "Enable fix behavior by-default when running `ruff` (overridden by the `--fix` and `--no-fix` command-line flags).", + "description": "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.", "type": [ "boolean", "null" @@ -637,6 +637,13 @@ "items": { "$ref": "#/definitions/RuleSelector" } + }, + "unsafe-fixes": { + "description": "Enable application of unsafe fixes.", + "type": [ + "boolean", + "null" + ] } }, "additionalProperties": false,