From 681c69d0ec138642fd4f1e8788ba6ecaf4ba6a0e Mon Sep 17 00:00:00 2001 From: Charlie Marsh Date: Sun, 25 Dec 2022 18:26:55 -0500 Subject: [PATCH] Add a --fix-only option --- README.md | 17 +++++++ flake8_to_ruff/src/converter.rs | 7 +++ ruff.schema.json | 7 +++ src/cli.rs | 8 ++++ src/main.rs | 25 ++++++---- src/printer.rs | 82 ++++++++++++++++++++++++--------- src/settings/configuration.rs | 6 +++ src/settings/mod.rs | 4 ++ src/settings/options.rs | 3 ++ src/settings/pyproject.rs | 6 +++ 10 files changed, 134 insertions(+), 31 deletions(-) diff --git a/README.md b/README.md index b5b69de58949d..ec50a952f370b 100644 --- a/README.md +++ b/README.md @@ -1798,6 +1798,23 @@ fix = true --- +#### [`fix-only`](#fix-only) + +Like `fix`, but disables reporting on leftover violation. Implies `fix`. + +**Default value**: `false` + +**Type**: `bool` + +**Example usage**: + +```toml +[tool.ruff] +fix-only = true +``` + +--- + #### [`fixable`](#fixable) A list of check code prefixes to consider autofix-able. diff --git a/flake8_to_ruff/src/converter.rs b/flake8_to_ruff/src/converter.rs index b03409f3072a4..ae09fd75c7389 100644 --- a/flake8_to_ruff/src/converter.rs +++ b/flake8_to_ruff/src/converter.rs @@ -295,6 +295,7 @@ mod tests { extend_select: None, external: None, fix: None, + fix_only: None, fixable: None, format: None, force_exclude: None, @@ -350,6 +351,7 @@ mod tests { extend_select: None, external: None, fix: None, + fix_only: None, fixable: None, format: None, force_exclude: None, @@ -405,6 +407,7 @@ mod tests { extend_select: None, external: None, fix: None, + fix_only: None, fixable: None, format: None, force_exclude: None, @@ -460,6 +463,7 @@ mod tests { extend_select: None, external: None, fix: None, + fix_only: None, fixable: None, format: None, force_exclude: None, @@ -515,6 +519,7 @@ mod tests { extend_select: None, external: None, fix: None, + fix_only: None, fixable: None, format: None, force_exclude: None, @@ -578,6 +583,7 @@ mod tests { extend_select: None, external: None, fix: None, + fix_only: None, fixable: None, format: None, force_exclude: None, @@ -669,6 +675,7 @@ mod tests { extend_select: None, external: None, fix: None, + fix_only: None, fixable: None, format: None, force_exclude: None, diff --git a/ruff.schema.json b/ruff.schema.json index 3c43892cf1b32..a2a69c596a7c5 100644 --- a/ruff.schema.json +++ b/ruff.schema.json @@ -93,6 +93,13 @@ "null" ] }, + "fix-only": { + "description": "Like `fix`, but disables reporting on leftover violation. Implies `fix`.", + "type": [ + "boolean", + "null" + ] + }, "fixable": { "description": "A list of check code prefixes to consider autofix-able.", "type": [ diff --git a/src/cli.rs b/src/cli.rs index b9b7cd80e5717..7f518d7f8370c 100644 --- a/src/cli.rs +++ b/src/cli.rs @@ -43,6 +43,12 @@ pub struct Cli { fix: bool, #[clap(long, overrides_with("fix"), hide = true)] no_fix: bool, + /// Fix any fixable lint errors, but don't report on leftover violations. + /// Implies `--fix`. + #[arg(long, overrides_with("no_fix_only"))] + fix_only: bool, + #[clap(long, overrides_with("fix_only"), hide = true)] + no_fix_only: bool, /// Disable cache reads. #[arg(short, long)] pub no_cache: bool, @@ -181,6 +187,7 @@ impl Cli { unfixable: self.unfixable, // TODO(charlie): Included in `pyproject.toml`, but not inherited. fix: resolve_bool_arg(self.fix, self.no_fix), + fix_only: resolve_bool_arg(self.fix_only, self.no_fix_only), format: self.format, force_exclude: resolve_bool_arg(self.force_exclude, self.no_force_exclude), cache_dir: self.cache_dir, @@ -240,6 +247,7 @@ pub struct Overrides { pub unfixable: Option>, // TODO(charlie): Captured in pyproject.toml as a default, but not part of `Settings`. pub fix: Option, + pub fix_only: Option, pub format: Option, pub force_exclude: Option, pub cache_dir: Option, diff --git a/src/main.rs b/src/main.rs index 4c790757164e0..1ab21ca0b2476 100644 --- a/src/main.rs +++ b/src/main.rs @@ -20,7 +20,7 @@ use ::ruff::autofix::fixer; use ::ruff::cli::{extract_log_level, Cli, Overrides}; use ::ruff::commands; use ::ruff::logging::{set_up_logging, LogLevel}; -use ::ruff::printer::Printer; +use ::ruff::printer::{Printer, Violations}; use ::ruff::resolver::{resolve_settings, FileDiscovery, PyprojectDiscovery, Relativity}; use ::ruff::settings::configuration::Configuration; use ::ruff::settings::types::SerializationFormat; @@ -112,17 +112,24 @@ fn inner_main() -> Result { PyprojectDiscovery::Hierarchical(settings) => settings.respect_gitignore, }, }; - let (fix, format) = match &pyproject_strategy { - PyprojectDiscovery::Fixed(settings) => (settings.fix, settings.format), - PyprojectDiscovery::Hierarchical(settings) => (settings.fix, settings.format), + let (fix, fix_only, format) = match &pyproject_strategy { + PyprojectDiscovery::Fixed(settings) => (settings.fix, settings.fix_only, settings.format), + PyprojectDiscovery::Hierarchical(settings) => { + (settings.fix, settings.fix_only, settings.format) + } }; - let autofix = if fix { + let autofix = if fix || fix_only { fixer::Mode::Apply } else if matches!(format, SerializationFormat::Json) { fixer::Mode::Generate } else { fixer::Mode::None }; + let violations = if fix_only { + Violations::Hide + } else { + Violations::Show + }; let cache = !cli.no_cache; if let Some(code) = cli.explain { @@ -138,13 +145,13 @@ fn inner_main() -> Result { return Ok(ExitCode::SUCCESS); } - let printer = Printer::new(&format, &log_level); + let printer = Printer::new(&format, &log_level, &autofix, &violations); if cli.watch { if matches!(autofix, fixer::Mode::Generate | fixer::Mode::Apply) { eprintln!("Warning: --fix is not enabled in watch mode."); } if cli.add_noqa { - eprintln!("Warning: --no-qa is not enabled in watch mode."); + eprintln!("Warning: --add-noqa is not enabled in watch mode."); } if cli.autoformat { eprintln!("Warning: --autoformat is not enabled in watch mode."); @@ -240,7 +247,7 @@ fn inner_main() -> Result { // unless we're writing fixes via stdin (in which case, the transformed // source code goes to stdout). if !(is_stdin && matches!(autofix, fixer::Mode::Apply)) { - printer.write_once(&diagnostics, autofix)?; + printer.write_once(&diagnostics)?; } // Check for updates if we're in a non-silent log level. @@ -249,7 +256,7 @@ fn inner_main() -> Result { drop(updates::check_for_updates()); } - if !diagnostics.messages.is_empty() && !cli.exit_zero { + if !diagnostics.messages.is_empty() && !cli.exit_zero && !fix_only { return Ok(ExitCode::FAILURE); } } diff --git a/src/printer.rs b/src/printer.rs index b3dd8ede15665..280622a0bc485 100644 --- a/src/printer.rs +++ b/src/printer.rs @@ -18,6 +18,12 @@ use crate::message::Message; use crate::settings::types::SerializationFormat; use crate::tell_user; +/// Enum to control whether lint violations are shown to the user. +pub enum Violations { + Show, + Hide, +} + #[derive(Serialize)] struct ExpandedMessage<'a> { code: &'a CheckCode, @@ -31,11 +37,23 @@ struct ExpandedMessage<'a> { pub struct Printer<'a> { format: &'a SerializationFormat, log_level: &'a LogLevel, + autofix: &'a fixer::Mode, + violations: &'a Violations, } impl<'a> Printer<'a> { - pub fn new(format: &'a SerializationFormat, log_level: &'a LogLevel) -> Self { - Self { format, log_level } + pub fn new( + format: &'a SerializationFormat, + log_level: &'a LogLevel, + autofix: &'a fixer::Mode, + violations: &'a Violations, + ) -> Self { + Self { + format, + log_level, + autofix, + violations, + } } pub fn write_to_user(&self, message: &str) { @@ -44,35 +62,55 @@ impl<'a> Printer<'a> { } } - fn post_text(&self, diagnostics: &Diagnostics, autofix: fixer::Mode) { + fn post_text(&self, diagnostics: &Diagnostics) { if self.log_level >= &LogLevel::Default { - let fixed = diagnostics.fixed; - let remaining = diagnostics.messages.len(); - let total = fixed + remaining; - if fixed > 0 { - println!("Found {total} error(s) ({fixed} fixed, {remaining} remaining)."); - } else if remaining > 0 { - println!("Found {remaining} error(s)."); - } + match self.violations { + Violations::Show => { + let fixed = diagnostics.fixed; + let remaining = diagnostics.messages.len(); + let total = fixed + remaining; + if fixed > 0 { + println!("Found {total} error(s) ({fixed} fixed, {remaining} remaining)."); + } else if remaining > 0 { + println!("Found {remaining} error(s)."); + } - if !matches!(autofix, fixer::Mode::Apply) { - let num_fixable = diagnostics - .messages - .iter() - .filter(|message| message.kind.fixable()) - .count(); - if num_fixable > 0 { - println!("{num_fixable} potentially fixable with the --fix option."); + if !matches!(self.autofix, fixer::Mode::Apply) { + let num_fixable = diagnostics + .messages + .iter() + .filter(|message| message.kind.fixable()) + .count(); + if num_fixable > 0 { + println!("{num_fixable} potentially fixable with the --fix option."); + } + } + } + Violations::Hide => { + let fixed = diagnostics.fixed; + if fixed > 0 { + println!("Fixed {fixed} error(s)."); + } } } } } - pub fn write_once(&self, diagnostics: &Diagnostics, autofix: fixer::Mode) -> Result<()> { + pub fn write_once(&self, diagnostics: &Diagnostics) -> Result<()> { if matches!(self.log_level, LogLevel::Silent) { return Ok(()); } + if matches!(self.violations, Violations::Hide) { + if matches!( + self.format, + SerializationFormat::Text | SerializationFormat::Grouped + ) { + self.post_text(diagnostics); + } + return Ok(()); + } + match self.format { SerializationFormat::Json => { println!( @@ -142,7 +180,7 @@ impl<'a> Printer<'a> { print_message(message); } - self.post_text(diagnostics, autofix); + self.post_text(diagnostics); } SerializationFormat::Grouped => { // Group by filename. @@ -182,7 +220,7 @@ impl<'a> Printer<'a> { println!(); } - self.post_text(diagnostics, autofix); + self.post_text(diagnostics); } SerializationFormat::Github => { // Generate error workflow command in GitHub Actions format diff --git a/src/settings/configuration.rs b/src/settings/configuration.rs index 632d161588a5f..0b6f1a6e4b3ca 100644 --- a/src/settings/configuration.rs +++ b/src/settings/configuration.rs @@ -33,6 +33,7 @@ pub struct Configuration { pub extend_select: Vec>, pub external: Option>, pub fix: Option, + pub fix_only: Option, pub fixable: Option>, pub format: Option, pub force_exclude: Option, @@ -107,6 +108,7 @@ impl Configuration { extend_select: vec![options.extend_select.unwrap_or_default()], external: options.external, fix: options.fix, + fix_only: options.fix_only, fixable: options.fixable, format: options.format, force_exclude: options.force_exclude, @@ -179,6 +181,7 @@ impl Configuration { .collect(), external: self.external.or(config.external), fix: self.fix.or(config.fix), + fix_only: self.fix_only.or(config.fix_only), fixable: self.fixable.or(config.fixable), format: self.format.or(config.format), force_exclude: self.force_exclude.or(config.force_exclude), @@ -226,6 +229,9 @@ impl Configuration { if let Some(fix) = overrides.fix { self.fix = Some(fix); } + if let Some(fix_only) = overrides.fix_only { + self.fix_only = Some(fix_only); + } if let Some(fixable) = overrides.fixable { self.fixable = Some(fixable); } diff --git a/src/settings/mod.rs b/src/settings/mod.rs index de360546b7c4b..44a119ca4bde0 100644 --- a/src/settings/mod.rs +++ b/src/settings/mod.rs @@ -40,6 +40,7 @@ pub struct Settings { pub extend_exclude: GlobSet, pub external: FxHashSet, pub fix: bool, + pub fix_only: bool, pub fixable: FxHashSet, pub format: SerializationFormat, pub force_exclude: bool, @@ -122,6 +123,7 @@ impl Settings { extend_exclude: resolve_globset(config.extend_exclude)?, external: FxHashSet::from_iter(config.external.unwrap_or_default()), fix: config.fix.unwrap_or(false), + fix_only: config.fix_only.unwrap_or(false), fixable: resolve_codes( [CheckCodeSpec { select: &config.fixable.unwrap_or_else(|| CATEGORIES.to_vec()), @@ -202,6 +204,7 @@ impl Settings { extend_exclude: GlobSet::empty(), external: FxHashSet::default(), fix: false, + fix_only: false, fixable: FxHashSet::from_iter([check_code]), format: SerializationFormat::Text, force_exclude: false, @@ -236,6 +239,7 @@ impl Settings { extend_exclude: GlobSet::empty(), external: FxHashSet::default(), fix: false, + fix_only: false, fixable: FxHashSet::from_iter(check_codes), format: SerializationFormat::Text, force_exclude: false, diff --git a/src/settings/options.rs b/src/settings/options.rs index 5fc9d4333835d..e258334b97aa9 100644 --- a/src/settings/options.rs +++ b/src/settings/options.rs @@ -132,6 +132,9 @@ pub struct Options { /// Enable autofix behavior by-default when running `ruff` (overridden /// by the `--fix` and `--no-fix` command-line flags). pub fix: Option, + #[option(default = "false", value_type = "bool", example = "fix-only = true")] + /// Like `fix`, but disables reporting on leftover violation. Implies `fix`. + pub fix_only: Option, #[option( default = r#"["A", "ANN", "ARG", "B", "BLE", "C", "D", "E", "ERA", "F", "FBT", "I", "ICN", "N", "PGH", "PLC", "PLE", "PLR", "PLW", "Q", "RET", "RUF", "S", "T", "TID", "UP", "W", "YTT"]"#, value_type = "Vec", diff --git a/src/settings/pyproject.rs b/src/settings/pyproject.rs index e7321ee9b530d..b49001b298f8f 100644 --- a/src/settings/pyproject.rs +++ b/src/settings/pyproject.rs @@ -128,6 +128,7 @@ mod tests { extend_select: None, external: None, fix: None, + fix_only: None, fixable: None, format: None, force_exclude: None, @@ -177,6 +178,7 @@ line-length = 79 extend_select: None, external: None, fix: None, + fix_only: None, fixable: None, force_exclude: None, format: None, @@ -226,6 +228,7 @@ exclude = ["foo.py"] extend_select: None, external: None, fix: None, + fix_only: None, fixable: None, force_exclude: None, format: None, @@ -275,6 +278,7 @@ select = ["E501"] extend_select: None, external: None, fix: None, + fix_only: None, fixable: None, force_exclude: None, format: None, @@ -325,6 +329,7 @@ ignore = ["E501"] extend_select: Some(vec![CheckCodePrefix::RUF100]), external: None, fix: None, + fix_only: None, fixable: None, force_exclude: None, format: None, @@ -403,6 +408,7 @@ other-attribute = 1 allowed_confusables: Some(vec!['−', 'ρ', '∗']), line_length: Some(88), fix: None, + fix_only: None, exclude: None, extend: None, extend_exclude: Some(vec![