Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Add a --fix-only command-line and pyproject.toml option #1375

Merged
merged 1 commit into from Dec 25, 2022
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Jump to
Jump to file
Failed to load files.
Diff view
Diff view
17 changes: 17 additions & 0 deletions README.md
Expand Up @@ -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.
Expand Down
7 changes: 7 additions & 0 deletions flake8_to_ruff/src/converter.rs
Expand Up @@ -295,6 +295,7 @@ mod tests {
extend_select: None,
external: None,
fix: None,
fix_only: None,
fixable: None,
format: None,
force_exclude: None,
Expand Down Expand Up @@ -350,6 +351,7 @@ mod tests {
extend_select: None,
external: None,
fix: None,
fix_only: None,
fixable: None,
format: None,
force_exclude: None,
Expand Down Expand Up @@ -405,6 +407,7 @@ mod tests {
extend_select: None,
external: None,
fix: None,
fix_only: None,
fixable: None,
format: None,
force_exclude: None,
Expand Down Expand Up @@ -460,6 +463,7 @@ mod tests {
extend_select: None,
external: None,
fix: None,
fix_only: None,
fixable: None,
format: None,
force_exclude: None,
Expand Down Expand Up @@ -515,6 +519,7 @@ mod tests {
extend_select: None,
external: None,
fix: None,
fix_only: None,
fixable: None,
format: None,
force_exclude: None,
Expand Down Expand Up @@ -578,6 +583,7 @@ mod tests {
extend_select: None,
external: None,
fix: None,
fix_only: None,
fixable: None,
format: None,
force_exclude: None,
Expand Down Expand Up @@ -669,6 +675,7 @@ mod tests {
extend_select: None,
external: None,
fix: None,
fix_only: None,
fixable: None,
format: None,
force_exclude: None,
Expand Down
7 changes: 7 additions & 0 deletions ruff.schema.json
Expand Up @@ -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": [
Expand Down
8 changes: 8 additions & 0 deletions src/cli.rs
Expand Up @@ -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,
Expand Down Expand Up @@ -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,
Expand Down Expand Up @@ -240,6 +247,7 @@ pub struct Overrides {
pub unfixable: Option<Vec<CheckCodePrefix>>,
// TODO(charlie): Captured in pyproject.toml as a default, but not part of `Settings`.
pub fix: Option<bool>,
pub fix_only: Option<bool>,
pub format: Option<SerializationFormat>,
pub force_exclude: Option<bool>,
pub cache_dir: Option<PathBuf>,
Expand Down
25 changes: 16 additions & 9 deletions src/main.rs
Expand Up @@ -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;
Expand Down Expand Up @@ -112,17 +112,24 @@ fn inner_main() -> Result<ExitCode> {
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 {
Expand All @@ -138,13 +145,13 @@ fn inner_main() -> Result<ExitCode> {
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.");
Expand Down Expand Up @@ -240,7 +247,7 @@ fn inner_main() -> Result<ExitCode> {
// 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.
Expand All @@ -249,7 +256,7 @@ fn inner_main() -> Result<ExitCode> {
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);
}
}
Expand Down
82 changes: 60 additions & 22 deletions src/printer.rs
Expand Up @@ -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,
Expand All @@ -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) {
Expand All @@ -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!(
Expand Down Expand Up @@ -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.
Expand Down Expand Up @@ -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
Expand Down
6 changes: 6 additions & 0 deletions src/settings/configuration.rs
Expand Up @@ -33,6 +33,7 @@ pub struct Configuration {
pub extend_select: Vec<Vec<CheckCodePrefix>>,
pub external: Option<Vec<String>>,
pub fix: Option<bool>,
pub fix_only: Option<bool>,
pub fixable: Option<Vec<CheckCodePrefix>>,
pub format: Option<SerializationFormat>,
pub force_exclude: Option<bool>,
Expand Down Expand Up @@ -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,
Expand Down Expand Up @@ -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),
Expand Down Expand Up @@ -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);
}
Expand Down
4 changes: 4 additions & 0 deletions src/settings/mod.rs
Expand Up @@ -40,6 +40,7 @@ pub struct Settings {
pub extend_exclude: GlobSet,
pub external: FxHashSet<String>,
pub fix: bool,
pub fix_only: bool,
pub fixable: FxHashSet<CheckCode>,
pub format: SerializationFormat,
pub force_exclude: bool,
Expand Down Expand Up @@ -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()),
Expand Down Expand Up @@ -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,
Expand Down Expand Up @@ -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,
Expand Down