mirror of https://github.com/astral-sh/ruff
Update CLI to respect fix applicability
This commit is contained in:
parent
90c259beb9
commit
d01969c974
|
|
@ -72,7 +72,6 @@ pub enum Command {
|
||||||
|
|
||||||
// The `Parser` derive is for ruff_dev, for ruff_cli `Args` would be sufficient
|
// The `Parser` derive is for ruff_dev, for ruff_cli `Args` would be sufficient
|
||||||
#[derive(Clone, Debug, clap::Parser)]
|
#[derive(Clone, Debug, clap::Parser)]
|
||||||
#[allow(clippy::struct_excessive_bools)]
|
|
||||||
pub struct CheckCommand {
|
pub struct CheckCommand {
|
||||||
/// List of files or directories to check.
|
/// List of files or directories to check.
|
||||||
pub files: Vec<PathBuf>,
|
pub files: Vec<PathBuf>,
|
||||||
|
|
@ -80,7 +79,10 @@ pub struct CheckCommand {
|
||||||
/// Use `--no-fix` to disable.
|
/// Use `--no-fix` to disable.
|
||||||
#[arg(long, overrides_with("no_fix"))]
|
#[arg(long, overrides_with("no_fix"))]
|
||||||
fix: bool,
|
fix: bool,
|
||||||
#[clap(long, overrides_with("fix"), hide = true)]
|
/// Attempt to automatically fix both automatic and suggested lint violations.
|
||||||
|
#[arg(long, overrides_with_all(["fix", "no_fix"]))]
|
||||||
|
fix_suggested: bool,
|
||||||
|
#[clap(long, overrides_with_all(["fix", "fix_suggested"]), hide = true)]
|
||||||
no_fix: bool,
|
no_fix: bool,
|
||||||
/// Show violations with source code.
|
/// Show violations with source code.
|
||||||
/// Use `--no-show-source` to disable.
|
/// Use `--no-show-source` to disable.
|
||||||
|
|
@ -497,6 +499,7 @@ impl CheckCommand {
|
||||||
cache_dir: self.cache_dir,
|
cache_dir: self.cache_dir,
|
||||||
fix: resolve_bool_arg(self.fix, self.no_fix),
|
fix: resolve_bool_arg(self.fix, self.no_fix),
|
||||||
fix_only: resolve_bool_arg(self.fix_only, self.no_fix_only),
|
fix_only: resolve_bool_arg(self.fix_only, self.no_fix_only),
|
||||||
|
fix_suggested: Some(self.fix_suggested),
|
||||||
force_exclude: resolve_bool_arg(self.force_exclude, self.no_force_exclude),
|
force_exclude: resolve_bool_arg(self.force_exclude, self.no_force_exclude),
|
||||||
output_format: self.output_format.or(self.format),
|
output_format: self.output_format.or(self.format),
|
||||||
show_fixes: resolve_bool_arg(self.show_fixes, self.no_show_fixes),
|
show_fixes: resolve_bool_arg(self.show_fixes, self.no_show_fixes),
|
||||||
|
|
@ -599,6 +602,7 @@ pub struct CliOverrides {
|
||||||
pub cache_dir: Option<PathBuf>,
|
pub cache_dir: Option<PathBuf>,
|
||||||
pub fix: Option<bool>,
|
pub fix: Option<bool>,
|
||||||
pub fix_only: Option<bool>,
|
pub fix_only: Option<bool>,
|
||||||
|
pub fix_suggested: Option<bool>,
|
||||||
pub force_exclude: Option<bool>,
|
pub force_exclude: Option<bool>,
|
||||||
pub output_format: Option<SerializationFormat>,
|
pub output_format: Option<SerializationFormat>,
|
||||||
pub show_fixes: Option<bool>,
|
pub show_fixes: Option<bool>,
|
||||||
|
|
@ -624,6 +628,9 @@ impl ConfigurationTransformer for CliOverrides {
|
||||||
if let Some(fix_only) = &self.fix_only {
|
if let Some(fix_only) = &self.fix_only {
|
||||||
config.fix_only = Some(*fix_only);
|
config.fix_only = Some(*fix_only);
|
||||||
}
|
}
|
||||||
|
if self.fix_suggested.is_some() {
|
||||||
|
config.fix_suggested = self.fix_suggested;
|
||||||
|
}
|
||||||
config.lint.rule_selections.push(RuleSelection {
|
config.lint.rule_selections.push(RuleSelection {
|
||||||
select: self.select.clone(),
|
select: self.select.clone(),
|
||||||
ignore: self
|
ignore: self
|
||||||
|
|
|
||||||
|
|
@ -409,7 +409,7 @@ mod tests {
|
||||||
&settings.linter,
|
&settings.linter,
|
||||||
Some(&cache),
|
Some(&cache),
|
||||||
flags::Noqa::Enabled,
|
flags::Noqa::Enabled,
|
||||||
flags::FixMode::Generate,
|
flags::FixMode::Generate(flags::SuggestedFixes::Apply),
|
||||||
)
|
)
|
||||||
.unwrap();
|
.unwrap();
|
||||||
if diagnostics
|
if diagnostics
|
||||||
|
|
@ -454,7 +454,7 @@ mod tests {
|
||||||
&settings.linter,
|
&settings.linter,
|
||||||
Some(&cache),
|
Some(&cache),
|
||||||
flags::Noqa::Enabled,
|
flags::Noqa::Enabled,
|
||||||
flags::FixMode::Generate,
|
flags::FixMode::Generate(flags::SuggestedFixes::Apply),
|
||||||
)
|
)
|
||||||
.unwrap();
|
.unwrap();
|
||||||
}
|
}
|
||||||
|
|
@ -711,7 +711,7 @@ mod tests {
|
||||||
&self.settings.linter,
|
&self.settings.linter,
|
||||||
Some(cache),
|
Some(cache),
|
||||||
flags::Noqa::Enabled,
|
flags::Noqa::Enabled,
|
||||||
flags::FixMode::Generate,
|
flags::FixMode::Generate(flags::SuggestedFixes::Apply),
|
||||||
)
|
)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
|
||||||
|
|
@ -284,7 +284,7 @@ mod test {
|
||||||
&CliOverrides::default(),
|
&CliOverrides::default(),
|
||||||
flags::Cache::Disabled,
|
flags::Cache::Disabled,
|
||||||
flags::Noqa::Disabled,
|
flags::Noqa::Disabled,
|
||||||
flags::FixMode::Generate,
|
flags::FixMode::Generate(flags::SuggestedFixes::Apply),
|
||||||
)
|
)
|
||||||
.unwrap();
|
.unwrap();
|
||||||
let mut output = Vec::new();
|
let mut output = Vec::new();
|
||||||
|
|
|
||||||
|
|
@ -241,16 +241,24 @@ pub(crate) fn lint_path(
|
||||||
error: parse_error,
|
error: parse_error,
|
||||||
},
|
},
|
||||||
fixed,
|
fixed,
|
||||||
) = if matches!(fix_mode, flags::FixMode::Apply | flags::FixMode::Diff) {
|
) = match fix_mode {
|
||||||
|
flags::FixMode::Apply(fix_suggested) | flags::FixMode::Diff(fix_suggested) => {
|
||||||
if let Ok(FixerResult {
|
if let Ok(FixerResult {
|
||||||
result,
|
result,
|
||||||
transformed,
|
transformed,
|
||||||
fixed,
|
fixed,
|
||||||
}) = lint_fix(path, package, noqa, settings, &source_kind, source_type)
|
}) = lint_fix(
|
||||||
{
|
path,
|
||||||
|
package,
|
||||||
|
noqa,
|
||||||
|
settings,
|
||||||
|
&source_kind,
|
||||||
|
source_type,
|
||||||
|
fix_suggested,
|
||||||
|
) {
|
||||||
if !fixed.is_empty() {
|
if !fixed.is_empty() {
|
||||||
match fix_mode {
|
match fix_mode {
|
||||||
flags::FixMode::Apply => match transformed.as_ref() {
|
flags::FixMode::Apply(_) => match transformed.as_ref() {
|
||||||
SourceKind::Python(transformed) => {
|
SourceKind::Python(transformed) => {
|
||||||
write(path, transformed.as_bytes())?;
|
write(path, transformed.as_bytes())?;
|
||||||
}
|
}
|
||||||
|
|
@ -259,13 +267,16 @@ pub(crate) fn lint_path(
|
||||||
notebook.write(&mut writer)?;
|
notebook.write(&mut writer)?;
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
flags::FixMode::Diff => {
|
flags::FixMode::Diff(_) => {
|
||||||
match transformed.as_ref() {
|
match transformed.as_ref() {
|
||||||
SourceKind::Python(transformed) => {
|
SourceKind::Python(transformed) => {
|
||||||
let mut stdout = io::stdout().lock();
|
let mut stdout = io::stdout().lock();
|
||||||
TextDiff::from_lines(source_kind.source_code(), transformed)
|
TextDiff::from_lines(source_kind.source_code(), transformed)
|
||||||
.unified_diff()
|
.unified_diff()
|
||||||
.header(&fs::relativize_path(path), &fs::relativize_path(path))
|
.header(
|
||||||
|
&fs::relativize_path(path),
|
||||||
|
&fs::relativize_path(path),
|
||||||
|
)
|
||||||
.to_writer(&mut stdout)?;
|
.to_writer(&mut stdout)?;
|
||||||
stdout.write_all(b"\n")?;
|
stdout.write_all(b"\n")?;
|
||||||
stdout.flush()?;
|
stdout.flush()?;
|
||||||
|
|
@ -275,9 +286,10 @@ pub(crate) fn lint_path(
|
||||||
// mutated it.
|
// mutated it.
|
||||||
let src_notebook = source_kind.as_ipy_notebook().unwrap();
|
let src_notebook = source_kind.as_ipy_notebook().unwrap();
|
||||||
let mut stdout = io::stdout().lock();
|
let mut stdout = io::stdout().lock();
|
||||||
// Cell indices are 1-based.
|
for ((idx, src_cell), dest_cell) in src_notebook
|
||||||
for ((idx, src_cell), dest_cell) in (1u32..)
|
.cells()
|
||||||
.zip(src_notebook.cells().iter())
|
.iter()
|
||||||
|
.enumerate()
|
||||||
.zip(dest_notebook.cells().iter())
|
.zip(dest_notebook.cells().iter())
|
||||||
{
|
{
|
||||||
let (Cell::Code(src_code_cell), Cell::Code(dest_code_cell)) =
|
let (Cell::Code(src_code_cell), Cell::Code(dest_code_cell)) =
|
||||||
|
|
@ -317,7 +329,7 @@ pub(crate) fn lint_path(
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
flags::FixMode::Generate => {}
|
flags::FixMode::Generate(_) => {}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
(result, fixed)
|
(result, fixed)
|
||||||
|
|
@ -327,10 +339,12 @@ pub(crate) fn lint_path(
|
||||||
let fixed = FxHashMap::default();
|
let fixed = FxHashMap::default();
|
||||||
(result, fixed)
|
(result, fixed)
|
||||||
}
|
}
|
||||||
} else {
|
}
|
||||||
|
flags::FixMode::Generate(_) => {
|
||||||
let result = lint_only(path, package, settings, noqa, &source_kind, source_type);
|
let result = lint_only(path, package, settings, noqa, &source_kind, source_type);
|
||||||
let fixed = FxHashMap::default();
|
let fixed = FxHashMap::default();
|
||||||
(result, fixed)
|
(result, fixed)
|
||||||
|
}
|
||||||
};
|
};
|
||||||
|
|
||||||
let imports = imports.unwrap_or_default();
|
let imports = imports.unwrap_or_default();
|
||||||
|
|
@ -392,7 +406,7 @@ pub(crate) fn lint_stdin(
|
||||||
};
|
};
|
||||||
|
|
||||||
// Extract the sources from the file.
|
// Extract the sources from the file.
|
||||||
let source_kind = match SourceKind::from_source_code(contents, source_type) {
|
let source_kind = match SourceKind::from_source_code(contents.clone(), source_type) {
|
||||||
Ok(Some(source_kind)) => source_kind,
|
Ok(Some(source_kind)) => source_kind,
|
||||||
Ok(None) => return Ok(Diagnostics::default()),
|
Ok(None) => return Ok(Diagnostics::default()),
|
||||||
Err(err) => {
|
Err(err) => {
|
||||||
|
|
@ -407,7 +421,8 @@ pub(crate) fn lint_stdin(
|
||||||
error: parse_error,
|
error: parse_error,
|
||||||
},
|
},
|
||||||
fixed,
|
fixed,
|
||||||
) = if matches!(fix_mode, flags::FixMode::Apply | flags::FixMode::Diff) {
|
) = match fix_mode {
|
||||||
|
flags::FixMode::Apply(fix_suggested) | flags::FixMode::Diff(fix_suggested) => {
|
||||||
if let Ok(FixerResult {
|
if let Ok(FixerResult {
|
||||||
result,
|
result,
|
||||||
transformed,
|
transformed,
|
||||||
|
|
@ -419,13 +434,14 @@ pub(crate) fn lint_stdin(
|
||||||
&settings.linter,
|
&settings.linter,
|
||||||
&source_kind,
|
&source_kind,
|
||||||
source_type,
|
source_type,
|
||||||
|
fix_suggested,
|
||||||
) {
|
) {
|
||||||
match fix_mode {
|
match fix_mode {
|
||||||
flags::FixMode::Apply => {
|
flags::FixMode::Apply(_) => {
|
||||||
// Write the contents to stdout, regardless of whether any errors were fixed.
|
// Write the contents to stdout, regardless of whether any errors were fixed.
|
||||||
transformed.write(&mut io::stdout().lock())?;
|
io::stdout().write_all(transformed.source_code().as_bytes())?;
|
||||||
}
|
}
|
||||||
flags::FixMode::Diff => {
|
flags::FixMode::Diff(_) => {
|
||||||
// But only write a diff if it's non-empty.
|
// But only write a diff if it's non-empty.
|
||||||
if !fixed.is_empty() {
|
if !fixed.is_empty() {
|
||||||
let text_diff = TextDiff::from_lines(
|
let text_diff = TextDiff::from_lines(
|
||||||
|
|
@ -444,7 +460,7 @@ pub(crate) fn lint_stdin(
|
||||||
stdout.flush()?;
|
stdout.flush()?;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
flags::FixMode::Generate => {}
|
flags::FixMode::Generate(_) => {}
|
||||||
}
|
}
|
||||||
|
|
||||||
(result, fixed)
|
(result, fixed)
|
||||||
|
|
@ -462,12 +478,13 @@ pub(crate) fn lint_stdin(
|
||||||
|
|
||||||
// Write the contents to stdout anyway.
|
// Write the contents to stdout anyway.
|
||||||
if fix_mode.is_apply() {
|
if fix_mode.is_apply() {
|
||||||
source_kind.write(&mut io::stdout().lock())?;
|
io::stdout().write_all(contents.as_bytes())?;
|
||||||
}
|
}
|
||||||
|
|
||||||
(result, fixed)
|
(result, fixed)
|
||||||
}
|
}
|
||||||
} else {
|
}
|
||||||
|
flags::FixMode::Generate(_) => {
|
||||||
let result = lint_only(
|
let result = lint_only(
|
||||||
path.unwrap_or_else(|| Path::new("-")),
|
path.unwrap_or_else(|| Path::new("-")),
|
||||||
package,
|
package,
|
||||||
|
|
@ -478,6 +495,7 @@ pub(crate) fn lint_stdin(
|
||||||
);
|
);
|
||||||
let fixed = FxHashMap::default();
|
let fixed = FxHashMap::default();
|
||||||
(result, fixed)
|
(result, fixed)
|
||||||
|
}
|
||||||
};
|
};
|
||||||
|
|
||||||
let imports = imports.unwrap_or_default();
|
let imports = imports.unwrap_or_default();
|
||||||
|
|
|
||||||
|
|
@ -10,7 +10,7 @@ use log::warn;
|
||||||
use notify::{recommended_watcher, RecursiveMode, Watcher};
|
use notify::{recommended_watcher, RecursiveMode, Watcher};
|
||||||
|
|
||||||
use ruff_linter::logging::{set_up_logging, LogLevel};
|
use ruff_linter::logging::{set_up_logging, LogLevel};
|
||||||
use ruff_linter::settings::flags;
|
use ruff_linter::settings::flags::{FixMode, SuggestedFixes};
|
||||||
use ruff_linter::settings::types::SerializationFormat;
|
use ruff_linter::settings::types::SerializationFormat;
|
||||||
use ruff_linter::{fs, warn_user, warn_user_once};
|
use ruff_linter::{fs, warn_user, warn_user_once};
|
||||||
use ruff_workspace::Settings;
|
use ruff_workspace::Settings;
|
||||||
|
|
@ -228,6 +228,7 @@ pub fn check(args: CheckCommand, log_level: LogLevel) -> Result<ExitStatus> {
|
||||||
let Settings {
|
let Settings {
|
||||||
fix,
|
fix,
|
||||||
fix_only,
|
fix_only,
|
||||||
|
fix_suggested,
|
||||||
output_format,
|
output_format,
|
||||||
show_fixes,
|
show_fixes,
|
||||||
show_source,
|
show_source,
|
||||||
|
|
@ -237,16 +238,28 @@ pub fn check(args: CheckCommand, log_level: LogLevel) -> Result<ExitStatus> {
|
||||||
// Fix rules are as follows:
|
// Fix rules are as follows:
|
||||||
// - By default, generate all fixes, but don't apply them to the filesystem.
|
// - 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, always apply fixes to the filesystem (or
|
||||||
// print them to stdout, if we're reading from stdin).
|
// print them to stdout, if we're reading from stdin) for [`Applicability::Automatic`] rules
|
||||||
|
// only.
|
||||||
// - If `--diff` or `--fix-only` are set, don't print any violations (only
|
// - If `--diff` or `--fix-only` are set, don't print any violations (only
|
||||||
// fixes).
|
// fixes) for [`Applicability::Automatic`] rules only.
|
||||||
let fix_mode = if cli.diff {
|
// - If `--fix--suggested` is set, the above rules will apply to both [`Applicability::Suggested`] and
|
||||||
flags::FixMode::Diff
|
// [`Applicability::Automatic`] fixes.
|
||||||
} else if fix || fix_only {
|
// TODO: can't fix this until @zanieb changes the CLI
|
||||||
flags::FixMode::Apply
|
let fix_suggested = if fix_suggested {
|
||||||
|
SuggestedFixes::Apply
|
||||||
} else {
|
} else {
|
||||||
flags::FixMode::Generate
|
SuggestedFixes::Disable
|
||||||
};
|
};
|
||||||
|
|
||||||
|
let fix_mode = if cli.diff {
|
||||||
|
FixMode::Diff(fix_suggested)
|
||||||
|
} else if fix || fix_only {
|
||||||
|
FixMode::Apply(fix_suggested)
|
||||||
|
} else {
|
||||||
|
// We'll always generate all fixes, regardless of [`Applicability`], in `generate` mode
|
||||||
|
FixMode::Generate(SuggestedFixes::Apply)
|
||||||
|
};
|
||||||
|
|
||||||
let cache = !cli.no_cache;
|
let cache = !cli.no_cache;
|
||||||
let noqa = !cli.ignore_noqa;
|
let noqa = !cli.ignore_noqa;
|
||||||
let mut printer_flags = PrinterFlags::empty();
|
let mut printer_flags = PrinterFlags::empty();
|
||||||
|
|
@ -382,7 +395,7 @@ pub fn check(args: CheckCommand, log_level: LogLevel) -> Result<ExitStatus> {
|
||||||
// Always try to print violations (the printer itself may suppress output),
|
// Always try to print violations (the printer itself may suppress output),
|
||||||
// unless we're writing fixes via stdin (in which case, the transformed
|
// unless we're writing fixes via stdin (in which case, the transformed
|
||||||
// source code goes to stdout).
|
// 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 {
|
if cli.statistics {
|
||||||
printer.write_statistics(&diagnostics, &mut writer)?;
|
printer.write_statistics(&diagnostics, &mut writer)?;
|
||||||
} else {
|
} else {
|
||||||
|
|
|
||||||
|
|
@ -7,6 +7,7 @@ use anyhow::Result;
|
||||||
use bitflags::bitflags;
|
use bitflags::bitflags;
|
||||||
use colored::Colorize;
|
use colored::Colorize;
|
||||||
use itertools::{iterate, Itertools};
|
use itertools::{iterate, Itertools};
|
||||||
|
use ruff_diagnostics::Applicability;
|
||||||
use rustc_hash::FxHashMap;
|
use rustc_hash::FxHashMap;
|
||||||
use serde::Serialize;
|
use serde::Serialize;
|
||||||
|
|
||||||
|
|
@ -19,7 +20,7 @@ use ruff_linter::message::{
|
||||||
};
|
};
|
||||||
use ruff_linter::notify_user;
|
use ruff_linter::notify_user;
|
||||||
use ruff_linter::registry::{AsRule, Rule};
|
use ruff_linter::registry::{AsRule, Rule};
|
||||||
use ruff_linter::settings::flags;
|
use ruff_linter::settings::flags::{self, SuggestedFixes};
|
||||||
use ruff_linter::settings::types::SerializationFormat;
|
use ruff_linter::settings::types::SerializationFormat;
|
||||||
|
|
||||||
use crate::diagnostics::Diagnostics;
|
use crate::diagnostics::Diagnostics;
|
||||||
|
|
@ -118,19 +119,10 @@ impl Printer {
|
||||||
writeln!(writer, "Found {remaining} error{s}.")?;
|
writeln!(writer, "Found {remaining} error{s}.")?;
|
||||||
}
|
}
|
||||||
|
|
||||||
if show_fix_status(self.fix_mode) {
|
let fixables = FixableStatistics::new(diagnostics, self.fix_mode.suggested_fixes());
|
||||||
let num_fixable = diagnostics
|
|
||||||
.messages
|
if !fixables.is_empty() {
|
||||||
.iter()
|
writeln!(writer, "{}", fixables.violation_string())?;
|
||||||
.filter(|message| message.fix.is_some())
|
|
||||||
.count();
|
|
||||||
if num_fixable > 0 {
|
|
||||||
writeln!(
|
|
||||||
writer,
|
|
||||||
"[{}] {num_fixable} potentially fixable with the --fix option.",
|
|
||||||
"*".cyan(),
|
|
||||||
)?;
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
} else {
|
} else {
|
||||||
let fixed = diagnostics
|
let fixed = diagnostics
|
||||||
|
|
@ -178,6 +170,7 @@ impl Printer {
|
||||||
}
|
}
|
||||||
|
|
||||||
let context = EmitterContext::new(&diagnostics.notebook_indexes);
|
let context = EmitterContext::new(&diagnostics.notebook_indexes);
|
||||||
|
let fixables = FixableStatistics::new(diagnostics, self.fix_mode.suggested_fixes());
|
||||||
|
|
||||||
match self.format {
|
match self.format {
|
||||||
SerializationFormat::Json => {
|
SerializationFormat::Json => {
|
||||||
|
|
@ -191,9 +184,9 @@ impl Printer {
|
||||||
}
|
}
|
||||||
SerializationFormat::Text => {
|
SerializationFormat::Text => {
|
||||||
TextEmitter::default()
|
TextEmitter::default()
|
||||||
.with_show_fix_status(show_fix_status(self.fix_mode))
|
.with_show_fix_status(show_fix_status(self.fix_mode, fixables))
|
||||||
.with_show_fix_diff(self.flags.intersects(Flags::SHOW_FIX_DIFF))
|
.with_show_fix_diff(self.flags.contains(Flags::SHOW_FIX_DIFF))
|
||||||
.with_show_source(self.flags.intersects(Flags::SHOW_SOURCE))
|
.with_show_source(self.flags.contains(Flags::SHOW_SOURCE))
|
||||||
.emit(writer, &diagnostics.messages, &context)?;
|
.emit(writer, &diagnostics.messages, &context)?;
|
||||||
|
|
||||||
if self.flags.intersects(Flags::SHOW_FIX_SUMMARY) {
|
if self.flags.intersects(Flags::SHOW_FIX_SUMMARY) {
|
||||||
|
|
@ -209,7 +202,7 @@ impl Printer {
|
||||||
SerializationFormat::Grouped => {
|
SerializationFormat::Grouped => {
|
||||||
GroupedEmitter::default()
|
GroupedEmitter::default()
|
||||||
.with_show_source(self.flags.intersects(Flags::SHOW_SOURCE))
|
.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))
|
||||||
.emit(writer, &diagnostics.messages, &context)?;
|
.emit(writer, &diagnostics.messages, &context)?;
|
||||||
|
|
||||||
if self.flags.intersects(Flags::SHOW_FIX_SUMMARY) {
|
if self.flags.intersects(Flags::SHOW_FIX_SUMMARY) {
|
||||||
|
|
@ -359,6 +352,8 @@ impl Printer {
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
let fixables = FixableStatistics::new(diagnostics, self.fix_mode.suggested_fixes());
|
||||||
|
|
||||||
if !diagnostics.messages.is_empty() {
|
if !diagnostics.messages.is_empty() {
|
||||||
if self.log_level >= LogLevel::Default {
|
if self.log_level >= LogLevel::Default {
|
||||||
writeln!(writer)?;
|
writeln!(writer)?;
|
||||||
|
|
@ -366,7 +361,7 @@ impl Printer {
|
||||||
|
|
||||||
let context = EmitterContext::new(&diagnostics.notebook_indexes);
|
let context = EmitterContext::new(&diagnostics.notebook_indexes);
|
||||||
TextEmitter::default()
|
TextEmitter::default()
|
||||||
.with_show_fix_status(show_fix_status(self.fix_mode))
|
.with_show_fix_status(show_fix_status(self.fix_mode, fixables))
|
||||||
.with_show_source(self.flags.intersects(Flags::SHOW_SOURCE))
|
.with_show_source(self.flags.intersects(Flags::SHOW_SOURCE))
|
||||||
.emit(writer, &diagnostics.messages, &context)?;
|
.emit(writer, &diagnostics.messages, &context)?;
|
||||||
}
|
}
|
||||||
|
|
@ -390,13 +385,13 @@ fn num_digits(n: usize) -> usize {
|
||||||
}
|
}
|
||||||
|
|
||||||
/// Return `true` if the [`Printer`] should indicate that a rule is fixable.
|
/// 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: FixableStatistics) -> bool {
|
||||||
// If we're in application mode, avoid indicating that a rule is fixable.
|
// 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
|
// If the specific violation were truly fixable, it would've been fixed in
|
||||||
// this pass! (We're occasionally unable to determine whether a specific
|
// this pass! (We're occasionally unable to determine whether a specific
|
||||||
// violation is fixable without trying to fix it, so if fix is not
|
// violation is fixable without trying to fix it, so if fix is not
|
||||||
// enabled, we may inadvertently indicate that a rule is fixable.)
|
// enabled, we may inadvertently indicate that a rule is fixable.)
|
||||||
!fix_mode.is_apply()
|
(!fix_mode.is_apply()) && fixables.fixes_are_applicable()
|
||||||
}
|
}
|
||||||
|
|
||||||
fn print_fix_summary(writer: &mut dyn Write, fixed: &FxHashMap<String, FixTable>) -> Result<()> {
|
fn print_fix_summary(writer: &mut dyn Write, fixed: &FxHashMap<String, FixTable>) -> Result<()> {
|
||||||
|
|
@ -439,3 +434,73 @@ fn print_fix_summary(writer: &mut dyn Write, fixed: &FxHashMap<String, FixTable>
|
||||||
}
|
}
|
||||||
Ok(())
|
Ok(())
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/// Contains the number of [`Applicability::Automatic`] and [`Applicability::Suggested`] fixes
|
||||||
|
struct FixableStatistics<'a> {
|
||||||
|
automatic: u32,
|
||||||
|
suggested: u32,
|
||||||
|
apply_suggested: &'a SuggestedFixes,
|
||||||
|
}
|
||||||
|
|
||||||
|
impl<'a> FixableStatistics<'a> {
|
||||||
|
fn new(diagnostics: &Diagnostics, apply_suggested: &'a SuggestedFixes) -> Self {
|
||||||
|
let mut automatic = 0;
|
||||||
|
let mut suggested = 0;
|
||||||
|
|
||||||
|
for message in &diagnostics.messages {
|
||||||
|
if let Some(fix) = &message.fix {
|
||||||
|
if fix.applicability() == Applicability::Suggested {
|
||||||
|
suggested += 1;
|
||||||
|
} else if fix.applicability() == Applicability::Automatic {
|
||||||
|
automatic += 1;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
Self {
|
||||||
|
automatic,
|
||||||
|
suggested,
|
||||||
|
apply_suggested,
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
fn fixes_are_applicable(&self) -> bool {
|
||||||
|
match self.apply_suggested {
|
||||||
|
SuggestedFixes::Apply => self.automatic > 0 || self.suggested > 0,
|
||||||
|
SuggestedFixes::Disable => self.automatic > 0,
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/// Returns [`true`] if there aren't any fixes to be displayed
|
||||||
|
fn is_empty(&self) -> bool {
|
||||||
|
self.automatic == 0 && self.suggested == 0
|
||||||
|
}
|
||||||
|
|
||||||
|
/// Build the displayed fix status message depending on the types of the remaining fixes.
|
||||||
|
fn violation_string(&self) -> String {
|
||||||
|
let prefix = format!("[{}]", "*".cyan());
|
||||||
|
let mut fix_status = prefix;
|
||||||
|
|
||||||
|
if self.automatic > 0 {
|
||||||
|
fix_status = format!(
|
||||||
|
"{fix_status} {} potentially fixable with the --fix option.",
|
||||||
|
self.automatic
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
if self.suggested > 0 {
|
||||||
|
let (line_break, extra_prefix) = if self.automatic > 0 {
|
||||||
|
("\n", format!("[{}]", "*".cyan()))
|
||||||
|
} else {
|
||||||
|
("", String::new())
|
||||||
|
};
|
||||||
|
|
||||||
|
let total = self.automatic + self.suggested;
|
||||||
|
fix_status = format!(
|
||||||
|
"{fix_status}{line_break}{extra_prefix} {total} potentially fixable with the --fix-suggested option."
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
fix_status
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
|
||||||
|
|
@ -241,81 +241,9 @@ fn stdin_fix_jupyter() {
|
||||||
success: true
|
success: true
|
||||||
exit_code: 0
|
exit_code: 0
|
||||||
----- stdout -----
|
----- stdout -----
|
||||||
{
|
print(1)
|
||||||
"cells": [
|
|
||||||
{
|
|
||||||
"cell_type": "code",
|
|
||||||
"execution_count": 1,
|
|
||||||
"id": "dccc687c-96e2-4604-b957-a8a89b5bec06",
|
|
||||||
"metadata": {},
|
|
||||||
"outputs": [],
|
|
||||||
"source": []
|
|
||||||
},
|
|
||||||
{
|
|
||||||
"cell_type": "markdown",
|
|
||||||
"id": "19e1b029-f516-4662-a9b9-623b93edac1a",
|
|
||||||
"metadata": {},
|
|
||||||
"source": [
|
|
||||||
"Foo"
|
|
||||||
]
|
|
||||||
},
|
|
||||||
{
|
|
||||||
"cell_type": "code",
|
|
||||||
"execution_count": 2,
|
|
||||||
"id": "cdce7b92-b0fb-4c02-86f6-e233b26fa84f",
|
|
||||||
"metadata": {},
|
|
||||||
"outputs": [],
|
|
||||||
"source": []
|
|
||||||
},
|
|
||||||
{
|
|
||||||
"cell_type": "code",
|
|
||||||
"execution_count": 3,
|
|
||||||
"id": "e40b33d2-7fe4-46c5-bdf0-8802f3052565",
|
|
||||||
"metadata": {},
|
|
||||||
"outputs": [
|
|
||||||
{
|
|
||||||
"name": "stdout",
|
|
||||||
"output_type": "stream",
|
|
||||||
"text": [
|
|
||||||
"1\n"
|
|
||||||
]
|
|
||||||
}
|
|
||||||
],
|
|
||||||
"source": [
|
|
||||||
"print(1)"
|
|
||||||
]
|
|
||||||
},
|
|
||||||
{
|
|
||||||
"cell_type": "code",
|
|
||||||
"execution_count": null,
|
|
||||||
"id": "a1899bc8-d46f-4ec0-b1d1-e1ca0f04bf60",
|
|
||||||
"metadata": {},
|
|
||||||
"outputs": [],
|
|
||||||
"source": []
|
|
||||||
}
|
|
||||||
],
|
|
||||||
"metadata": {
|
|
||||||
"kernelspec": {
|
|
||||||
"display_name": "Python 3 (ipykernel)",
|
|
||||||
"language": "python",
|
|
||||||
"name": "python3"
|
|
||||||
},
|
|
||||||
"language_info": {
|
|
||||||
"codemirror_mode": {
|
|
||||||
"name": "ipython",
|
|
||||||
"version": 3
|
|
||||||
},
|
|
||||||
"file_extension": ".py",
|
|
||||||
"mimetype": "text/x-python",
|
|
||||||
"name": "python",
|
|
||||||
"nbconvert_exporter": "python",
|
|
||||||
"pygments_lexer": "ipython3",
|
|
||||||
"version": "3.11.2"
|
|
||||||
}
|
|
||||||
},
|
|
||||||
"nbformat": 4,
|
|
||||||
"nbformat_minor": 5
|
|
||||||
}
|
|
||||||
----- stderr -----
|
----- stderr -----
|
||||||
"###);
|
"###);
|
||||||
}
|
}
|
||||||
|
|
@ -869,3 +797,232 @@ fn check_input_from_argfile() -> Result<()> {
|
||||||
|
|
||||||
Ok(())
|
Ok(())
|
||||||
}
|
}
|
||||||
|
|
||||||
|
#[test]
|
||||||
|
fn displays_fix_applicability_levels() -> Result<()> {
|
||||||
|
// `--fix` should only apply automatic fixes, but should tell the user about `--fix --unautomatic` if
|
||||||
|
// there are remaining unautomatic fixes.
|
||||||
|
// TODO: this should be a failure but we don't have a way to track that
|
||||||
|
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 potentially fixable with the --fix option.
|
||||||
|
[*] 2 potentially fixable with the --fix-suggested option.
|
||||||
|
|
||||||
|
----- stderr -----
|
||||||
|
"###);
|
||||||
|
|
||||||
|
Ok(())
|
||||||
|
}
|
||||||
|
|
||||||
|
#[test]
|
||||||
|
fn displays_remaining_suggested_fixes() -> Result<()> {
|
||||||
|
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 potentially fixable with the --fix-suggested option.
|
||||||
|
|
||||||
|
----- stderr -----
|
||||||
|
"###);
|
||||||
|
|
||||||
|
Ok(())
|
||||||
|
}
|
||||||
|
|
||||||
|
#[test]
|
||||||
|
fn fix_applies_automatic_fixes_only() -> Result<()> {
|
||||||
|
// `--fix` should only apply automatic fixes. Since we're runnnig in `stdin` mode, output shouldn't
|
||||||
|
// be printed.
|
||||||
|
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 -----
|
||||||
|
"###);
|
||||||
|
|
||||||
|
Ok(())
|
||||||
|
}
|
||||||
|
|
||||||
|
#[test]
|
||||||
|
fn fix_applies_automatic_and_suggested_fixes_with_fix_suggested() -> Result<()> {
|
||||||
|
assert_cmd_snapshot!(
|
||||||
|
Command::new(get_cargo_bin(BIN_NAME))
|
||||||
|
.args([
|
||||||
|
"-",
|
||||||
|
"--output-format",
|
||||||
|
"text",
|
||||||
|
"--isolated","--no-cache",
|
||||||
|
"--select",
|
||||||
|
"F601,UP034",
|
||||||
|
"--fix",
|
||||||
|
"--fix-suggested",
|
||||||
|
])
|
||||||
|
.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 potentially fixable with the --fix option.
|
||||||
|
[*] 2 potentially fixable with the --fix-suggested option.
|
||||||
|
|
||||||
|
----- stderr -----
|
||||||
|
"###);
|
||||||
|
|
||||||
|
Ok(())
|
||||||
|
}
|
||||||
|
|
||||||
|
#[test]
|
||||||
|
fn diff_shows_automatic_and_suggested_fixes_with_fix_suggested() -> Result<()> {
|
||||||
|
assert_cmd_snapshot!(
|
||||||
|
Command::new(get_cargo_bin(BIN_NAME))
|
||||||
|
.args([
|
||||||
|
"-",
|
||||||
|
"--output-format",
|
||||||
|
"text",
|
||||||
|
"--isolated","--no-cache",
|
||||||
|
"--select",
|
||||||
|
"F601,UP034",
|
||||||
|
"--diff",
|
||||||
|
"--fix-suggested",
|
||||||
|
])
|
||||||
|
.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'))
|
||||||
|
+x = {'a': 1}
|
||||||
|
+print('foo')
|
||||||
|
|
||||||
|
|
||||||
|
----- stderr -----
|
||||||
|
"###
|
||||||
|
);
|
||||||
|
|
||||||
|
Ok(())
|
||||||
|
}
|
||||||
|
|
||||||
|
#[test]
|
||||||
|
fn diff_shows_automatic_fixes_only() -> Result<()> {
|
||||||
|
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 -----
|
||||||
|
"###
|
||||||
|
);
|
||||||
|
|
||||||
|
Ok(())
|
||||||
|
}
|
||||||
|
|
||||||
|
#[test]
|
||||||
|
fn fix_only_applies_automatic_and_suggested_fixes_with_fix_suggested() -> Result<()> {
|
||||||
|
assert_cmd_snapshot!(
|
||||||
|
Command::new(get_cargo_bin(BIN_NAME))
|
||||||
|
.args([
|
||||||
|
"-",
|
||||||
|
"--output-format",
|
||||||
|
"text",
|
||||||
|
"--isolated","--no-cache",
|
||||||
|
"--select",
|
||||||
|
"F601,UP034",
|
||||||
|
"--fix-only",
|
||||||
|
"--fix-suggested",
|
||||||
|
])
|
||||||
|
.pass_stdin("x = {'a': 1, 'a': 1}\nprint(('foo'))\n"),
|
||||||
|
@r###"
|
||||||
|
success: true
|
||||||
|
exit_code: 0
|
||||||
|
----- stdout -----
|
||||||
|
x = {'a': 1}
|
||||||
|
print('foo')
|
||||||
|
|
||||||
|
----- stderr -----
|
||||||
|
"###);
|
||||||
|
|
||||||
|
Ok(())
|
||||||
|
}
|
||||||
|
|
||||||
|
#[test]
|
||||||
|
fn fix_only_applies_automatic_fixes_only() -> Result<()> {
|
||||||
|
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 -----
|
||||||
|
"###);
|
||||||
|
|
||||||
|
Ok(())
|
||||||
|
}
|
||||||
|
|
|
||||||
|
|
@ -5,27 +5,30 @@ use ruff_text_size::{Ranged, TextSize};
|
||||||
|
|
||||||
use crate::edit::Edit;
|
use crate::edit::Edit;
|
||||||
|
|
||||||
/// Indicates confidence in the correctness of a suggested fix.
|
/// Indicates confidence in the correctness of a suggested fix. Rust internally allows comparison
|
||||||
|
/// of enum values based on their order (see Rust's [enum
|
||||||
|
/// documentation](https://doc.rust-lang.org/reference/items/enumerations.html)). This allows us to
|
||||||
|
/// apply [`Fix`]es based on their [`Applicability`].
|
||||||
#[derive(Default, Copy, Clone, Debug, Hash, PartialEq, Eq, PartialOrd, Ord)]
|
#[derive(Default, Copy, Clone, Debug, Hash, PartialEq, Eq, PartialOrd, Ord)]
|
||||||
#[cfg_attr(feature = "serde", derive(Serialize, Deserialize))]
|
#[cfg_attr(feature = "serde", derive(Serialize, Deserialize))]
|
||||||
pub enum Applicability {
|
pub enum Applicability {
|
||||||
/// The fix is definitely what the user intended, or maintains the exact meaning of the code.
|
/// The fix has a good chance of being incorrect or the code be incomplete.
|
||||||
/// This fix should be automatically applied.
|
/// The fix may result in invalid code if it is applied.
|
||||||
Automatic,
|
/// The fix should only be manually applied by the user.
|
||||||
|
Manual,
|
||||||
|
|
||||||
|
/// The applicability of the fix is unknown or not relevant.
|
||||||
|
#[default]
|
||||||
|
Unspecified,
|
||||||
|
|
||||||
/// The fix may be what the user intended, but it is uncertain.
|
/// The fix may be what the user intended, but it is uncertain.
|
||||||
/// The fix should result in valid code if it is applied.
|
/// The fix should result in valid code if it is applied.
|
||||||
/// The fix can be applied with user opt-in.
|
/// The fix can be applied with user opt-in.
|
||||||
Suggested,
|
Suggested,
|
||||||
|
|
||||||
/// The fix has a good chance of being incorrect or the code be incomplete.
|
/// The fix is definitely what the user intended, or maintains the exact meaning of the code.
|
||||||
/// The fix may result in invalid code if it is applied.
|
/// This fix should be automatically applied.
|
||||||
/// The fix should only be manually applied by the user.
|
Automatic,
|
||||||
Manual,
|
|
||||||
|
|
||||||
/// The applicability of the fix is unknown.
|
|
||||||
#[default]
|
|
||||||
Unspecified,
|
|
||||||
}
|
}
|
||||||
|
|
||||||
/// Indicates the level of isolation required to apply a fix.
|
/// Indicates the level of isolation required to apply a fix.
|
||||||
|
|
@ -162,4 +165,9 @@ impl Fix {
|
||||||
self.isolation_level = isolation;
|
self.isolation_level = isolation;
|
||||||
self
|
self
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/// Return [`true`] if this [`Fix`] should be applied with a given [`Applicability`].
|
||||||
|
pub fn is_applied(&self, applicability: Applicability) -> bool {
|
||||||
|
self.applicability >= applicability
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
|
||||||
|
|
@ -4,11 +4,12 @@ use std::collections::BTreeSet;
|
||||||
use ruff_text_size::{Ranged, TextLen, TextRange, TextSize};
|
use ruff_text_size::{Ranged, TextLen, TextRange, TextSize};
|
||||||
use rustc_hash::{FxHashMap, FxHashSet};
|
use rustc_hash::{FxHashMap, FxHashSet};
|
||||||
|
|
||||||
use ruff_diagnostics::{Diagnostic, Edit, Fix, IsolationLevel, SourceMap};
|
use ruff_diagnostics::{Applicability, Diagnostic, Edit, Fix, IsolationLevel, SourceMap};
|
||||||
use ruff_source_file::Locator;
|
use ruff_source_file::Locator;
|
||||||
|
|
||||||
use crate::linter::FixTable;
|
use crate::linter::FixTable;
|
||||||
use crate::registry::{AsRule, Rule};
|
use crate::registry::{AsRule, Rule};
|
||||||
|
use crate::settings::flags::SuggestedFixes;
|
||||||
|
|
||||||
pub(crate) mod codemods;
|
pub(crate) mod codemods;
|
||||||
pub(crate) mod edits;
|
pub(crate) mod edits;
|
||||||
|
|
@ -24,10 +25,25 @@ pub(crate) struct FixResult {
|
||||||
}
|
}
|
||||||
|
|
||||||
/// Auto-fix errors in a file, and write the fixed source code to disk.
|
/// Auto-fix errors in a file, and write the fixed source code to disk.
|
||||||
pub(crate) fn fix_file(diagnostics: &[Diagnostic], locator: &Locator) -> Option<FixResult> {
|
pub(crate) fn fix_file(
|
||||||
|
diagnostics: &[Diagnostic],
|
||||||
|
locator: &Locator,
|
||||||
|
fix_suggested: SuggestedFixes,
|
||||||
|
) -> Option<FixResult> {
|
||||||
let mut with_fixes = diagnostics
|
let mut with_fixes = diagnostics
|
||||||
.iter()
|
.iter()
|
||||||
.filter(|diag| diag.fix.is_some())
|
.filter(|diag| {
|
||||||
|
let Some(ref fix) = diag.fix else {
|
||||||
|
return false;
|
||||||
|
};
|
||||||
|
|
||||||
|
// change this
|
||||||
|
if matches!(fix_suggested, SuggestedFixes::Apply) {
|
||||||
|
fix.applicability() >= Applicability::Suggested
|
||||||
|
} else {
|
||||||
|
fix.applicability() == Applicability::Automatic
|
||||||
|
}
|
||||||
|
})
|
||||||
.peekable();
|
.peekable();
|
||||||
|
|
||||||
if with_fixes.peek().is_none() {
|
if with_fixes.peek().is_none() {
|
||||||
|
|
|
||||||
|
|
@ -415,6 +415,7 @@ fn diagnostics_to_messages(
|
||||||
|
|
||||||
/// Generate `Diagnostic`s from source code content, iteratively fixing
|
/// Generate `Diagnostic`s from source code content, iteratively fixing
|
||||||
/// until stable.
|
/// until stable.
|
||||||
|
#[allow(clippy::too_many_arguments)]
|
||||||
pub fn lint_fix<'a>(
|
pub fn lint_fix<'a>(
|
||||||
path: &Path,
|
path: &Path,
|
||||||
package: Option<&Path>,
|
package: Option<&Path>,
|
||||||
|
|
@ -422,6 +423,7 @@ pub fn lint_fix<'a>(
|
||||||
settings: &LinterSettings,
|
settings: &LinterSettings,
|
||||||
source_kind: &'a SourceKind,
|
source_kind: &'a SourceKind,
|
||||||
source_type: PySourceType,
|
source_type: PySourceType,
|
||||||
|
fix_suggested: flags::SuggestedFixes,
|
||||||
) -> Result<FixerResult<'a>> {
|
) -> Result<FixerResult<'a>> {
|
||||||
let mut transformed = Cow::Borrowed(source_kind);
|
let mut transformed = Cow::Borrowed(source_kind);
|
||||||
|
|
||||||
|
|
@ -494,7 +496,7 @@ pub fn lint_fix<'a>(
|
||||||
code: fixed_contents,
|
code: fixed_contents,
|
||||||
fixes: applied,
|
fixes: applied,
|
||||||
source_map,
|
source_map,
|
||||||
}) = fix_file(&result.data.0, &locator)
|
}) = fix_file(&result.data.0, &locator, fix_suggested)
|
||||||
{
|
{
|
||||||
if iterations < MAX_ITERATIONS {
|
if iterations < MAX_ITERATIONS {
|
||||||
// Count the number of fixed errors.
|
// Count the number of fixed errors.
|
||||||
|
|
|
||||||
|
|
@ -1,8 +1,24 @@
|
||||||
#[derive(Debug, Copy, Clone, Hash, is_macro::Is)]
|
#[derive(Debug, Copy, Clone, Hash, is_macro::Is)]
|
||||||
pub enum FixMode {
|
pub enum FixMode {
|
||||||
Generate,
|
Generate(SuggestedFixes),
|
||||||
|
Apply(SuggestedFixes),
|
||||||
|
Diff(SuggestedFixes),
|
||||||
|
}
|
||||||
|
|
||||||
|
impl FixMode {
|
||||||
|
pub fn suggested_fixes(&self) -> &SuggestedFixes {
|
||||||
|
match self {
|
||||||
|
FixMode::Generate(suggested) => suggested,
|
||||||
|
FixMode::Apply(suggested) => suggested,
|
||||||
|
FixMode::Diff(suggested) => suggested,
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
#[derive(Debug, Copy, Clone, Hash, is_macro::Is)]
|
||||||
|
pub enum SuggestedFixes {
|
||||||
Apply,
|
Apply,
|
||||||
Diff,
|
Disable,
|
||||||
}
|
}
|
||||||
|
|
||||||
#[derive(Debug, Copy, Clone, Hash, result_like::BoolLike)]
|
#[derive(Debug, Copy, Clone, Hash, result_like::BoolLike)]
|
||||||
|
|
|
||||||
|
|
@ -155,8 +155,11 @@ pub(crate) fn test_contents<'a>(
|
||||||
code: fixed_contents,
|
code: fixed_contents,
|
||||||
source_map,
|
source_map,
|
||||||
..
|
..
|
||||||
}) = fix_file(&diagnostics, &Locator::new(transformed.source_code()))
|
}) = fix_file(
|
||||||
{
|
&diagnostics,
|
||||||
|
&Locator::new(transformed.source_code()),
|
||||||
|
flags::SuggestedFixes::Apply,
|
||||||
|
) {
|
||||||
if iterations < max_iterations() {
|
if iterations < max_iterations() {
|
||||||
iterations += 1;
|
iterations += 1;
|
||||||
} else {
|
} else {
|
||||||
|
|
|
||||||
|
|
@ -63,6 +63,7 @@ pub struct Configuration {
|
||||||
pub cache_dir: Option<PathBuf>,
|
pub cache_dir: Option<PathBuf>,
|
||||||
pub extend: Option<PathBuf>,
|
pub extend: Option<PathBuf>,
|
||||||
pub fix: Option<bool>,
|
pub fix: Option<bool>,
|
||||||
|
pub fix_suggested: Option<bool>,
|
||||||
pub fix_only: Option<bool>,
|
pub fix_only: Option<bool>,
|
||||||
pub output_format: Option<SerializationFormat>,
|
pub output_format: Option<SerializationFormat>,
|
||||||
pub preview: Option<PreviewMode>,
|
pub preview: Option<PreviewMode>,
|
||||||
|
|
@ -136,6 +137,7 @@ impl Configuration {
|
||||||
.clone()
|
.clone()
|
||||||
.unwrap_or_else(|| cache_dir(project_root)),
|
.unwrap_or_else(|| cache_dir(project_root)),
|
||||||
fix: self.fix.unwrap_or(false),
|
fix: self.fix.unwrap_or(false),
|
||||||
|
fix_suggested: self.fix_suggested.unwrap_or(false),
|
||||||
fix_only: self.fix_only.unwrap_or(false),
|
fix_only: self.fix_only.unwrap_or(false),
|
||||||
output_format: self.output_format.unwrap_or_default(),
|
output_format: self.output_format.unwrap_or_default(),
|
||||||
show_fixes: self.show_fixes.unwrap_or(false),
|
show_fixes: self.show_fixes.unwrap_or(false),
|
||||||
|
|
@ -365,6 +367,7 @@ impl Configuration {
|
||||||
}),
|
}),
|
||||||
fix: options.fix,
|
fix: options.fix,
|
||||||
fix_only: options.fix_only,
|
fix_only: options.fix_only,
|
||||||
|
fix_suggested: options.fix_suggested,
|
||||||
output_format: options.output_format.or_else(|| {
|
output_format: options.output_format.or_else(|| {
|
||||||
options
|
options
|
||||||
.format
|
.format
|
||||||
|
|
@ -418,6 +421,7 @@ impl Configuration {
|
||||||
include: self.include.or(config.include),
|
include: self.include.or(config.include),
|
||||||
fix: self.fix.or(config.fix),
|
fix: self.fix.or(config.fix),
|
||||||
fix_only: self.fix_only.or(config.fix_only),
|
fix_only: self.fix_only.or(config.fix_only),
|
||||||
|
fix_suggested: self.fix_suggested.or(config.fix_suggested),
|
||||||
output_format: self.output_format.or(config.output_format),
|
output_format: self.output_format.or(config.output_format),
|
||||||
force_exclude: self.force_exclude.or(config.force_exclude),
|
force_exclude: self.force_exclude.or(config.force_exclude),
|
||||||
line_length: self.line_length.or(config.line_length),
|
line_length: self.line_length.or(config.line_length),
|
||||||
|
|
|
||||||
|
|
@ -89,9 +89,18 @@ pub struct Options {
|
||||||
|
|
||||||
/// Enable fix behavior by-default when running `ruff` (overridden
|
/// Enable fix behavior by-default when running `ruff` (overridden
|
||||||
/// by the `--fix` and `--no-fix` command-line flags).
|
/// by the `--fix` and `--no-fix` command-line flags).
|
||||||
|
/// Only includes automatic fixes unless `--fix-suggested` is provided.
|
||||||
#[option(default = "false", value_type = "bool", example = "fix = true")]
|
#[option(default = "false", value_type = "bool", example = "fix = true")]
|
||||||
pub fix: Option<bool>,
|
pub fix: Option<bool>,
|
||||||
|
|
||||||
|
/// Enable application of suggested fixes.
|
||||||
|
#[option(
|
||||||
|
default = "false",
|
||||||
|
value_type = "bool",
|
||||||
|
example = "fix-suggested = true"
|
||||||
|
)]
|
||||||
|
pub fix_suggested: Option<bool>,
|
||||||
|
|
||||||
/// Like `fix`, but disables reporting on leftover violation. Implies `fix`.
|
/// Like `fix`, but disables reporting on leftover violation. Implies `fix`.
|
||||||
#[option(default = "false", value_type = "bool", example = "fix-only = true")]
|
#[option(default = "false", value_type = "bool", example = "fix-only = true")]
|
||||||
pub fix_only: Option<bool>,
|
pub fix_only: Option<bool>,
|
||||||
|
|
|
||||||
|
|
@ -17,6 +17,8 @@ pub struct Settings {
|
||||||
#[cache_key(ignore)]
|
#[cache_key(ignore)]
|
||||||
pub fix: bool,
|
pub fix: bool,
|
||||||
#[cache_key(ignore)]
|
#[cache_key(ignore)]
|
||||||
|
pub fix_suggested: bool,
|
||||||
|
#[cache_key(ignore)]
|
||||||
pub fix_only: bool,
|
pub fix_only: bool,
|
||||||
#[cache_key(ignore)]
|
#[cache_key(ignore)]
|
||||||
pub output_format: SerializationFormat,
|
pub output_format: SerializationFormat,
|
||||||
|
|
@ -36,6 +38,7 @@ impl Default for Settings {
|
||||||
Self {
|
Self {
|
||||||
cache_dir: cache_dir(project_root),
|
cache_dir: cache_dir(project_root),
|
||||||
fix: false,
|
fix: false,
|
||||||
|
fix_suggested: false,
|
||||||
fix_only: false,
|
fix_only: false,
|
||||||
output_format: SerializationFormat::default(),
|
output_format: SerializationFormat::default(),
|
||||||
show_fixes: false,
|
show_fixes: false,
|
||||||
|
|
|
||||||
|
|
@ -194,6 +194,8 @@ Arguments:
|
||||||
Options:
|
Options:
|
||||||
--fix
|
--fix
|
||||||
Attempt to automatically fix lint violations. Use `--no-fix` to disable
|
Attempt to automatically fix lint violations. Use `--no-fix` to disable
|
||||||
|
--fix-suggested
|
||||||
|
Attempt to automatically fix both automatic and suggested lint violations
|
||||||
--show-source
|
--show-source
|
||||||
Show violations with source code. Use `--no-show-source` to disable
|
Show violations with source code. Use `--no-show-source` to disable
|
||||||
--show-fixes
|
--show-fixes
|
||||||
|
|
|
||||||
|
|
@ -128,7 +128,7 @@
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
"fix": {
|
"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 `--fix-suggested` is provided.",
|
||||||
"type": [
|
"type": [
|
||||||
"boolean",
|
"boolean",
|
||||||
"null"
|
"null"
|
||||||
|
|
@ -141,6 +141,13 @@
|
||||||
"null"
|
"null"
|
||||||
]
|
]
|
||||||
},
|
},
|
||||||
|
"fix-suggested": {
|
||||||
|
"description": "Enable application of suggested fixes.",
|
||||||
|
"type": [
|
||||||
|
"boolean",
|
||||||
|
"null"
|
||||||
|
]
|
||||||
|
},
|
||||||
"fixable": {
|
"fixable": {
|
||||||
"description": "A list of rule codes or prefixes to consider fixable. By default, all rules are considered fixable.",
|
"description": "A list of rule codes or prefixes to consider fixable. By default, all rules are considered fixable.",
|
||||||
"type": [
|
"type": [
|
||||||
|
|
|
||||||
Loading…
Reference in New Issue