mirror of
https://github.com/astral-sh/ruff.git
synced 2025-09-30 22:01:18 +00:00
Update CLI to respect fix applicability (#7769)
Rebase of https://github.com/astral-sh/ruff/pull/5119 authored by @evanrittenhouse with additional refinements. ## Changes - Adds `--unsafe-fixes` / `--no-unsafe-fixes` flags to `ruff check` - Violations with unsafe fixes are not shown as fixable unless opted-in - Fix applicability is respected now - `Applicability::Never` fixes are no longer applied - `Applicability::Sometimes` fixes require opt-in - `Applicability::Always` fixes are unchanged - Hints for availability of `--unsafe-fixes` added to `ruff check` output ## Examples Check hints at hidden unsafe fixes ``` ❯ ruff check example.py --no-cache --select F601,W292 example.py:1:14: F601 Dictionary key literal `'a'` repeated example.py:2:15: W292 [*] No newline at end of file Found 2 errors. [*] 1 fixable with the `--fix` option (1 hidden fix can be enabled with the `--unsafe-fixes` option). ``` We could add an indicator for which violations have hidden fixes in the future. Check treats unsafe fixes as applicable with opt-in ``` ❯ ruff check example.py --no-cache --select F601,W292 --unsafe-fixes example.py:1:14: F601 [*] Dictionary key literal `'a'` repeated example.py:2:15: W292 [*] No newline at end of file Found 2 errors. [*] 2 fixable with the --fix option. ``` Also can be enabled in the config file ``` ❯ cat ruff.toml unsafe-fixes = true ``` And opted-out per invocation ``` ❯ ruff check example.py --no-cache --select F601,W292 --no-unsafe-fixes example.py:1:14: F601 Dictionary key literal `'a'` repeated example.py:2:15: W292 [*] No newline at end of file Found 2 errors. [*] 1 fixable with the `--fix` option (1 hidden fix can be enabled with the `--unsafe-fixes` option). ``` Diff does not include unsafe fixes ``` ❯ ruff check example.py --no-cache --select F601,W292 --diff --- example.py +++ example.py @@ -1,2 +1,2 @@ x = {'a': 1, 'a': 1} -print(('foo')) +print(('foo')) \ No newline at end of file Would fix 1 error. ``` Unless there is opt-in ``` ❯ ruff check example.py --no-cache --select F601,W292 --diff --unsafe-fixes --- example.py +++ example.py @@ -1,2 +1,2 @@ -x = {'a': 1} -print(('foo')) +x = {'a': 1, 'a': 1} +print(('foo')) \ No newline at end of file Would fix 2 errors. ``` https://github.com/astral-sh/ruff/pull/7790 will improve the diff messages following this pull request Similarly, `--fix` and `--fix-only` require the `--unsafe-fixes` flag to apply unsafe fixes. ## Related Replaces #5119 Closes https://github.com/astral-sh/ruff/issues/4185 Closes https://github.com/astral-sh/ruff/issues/7214 Closes https://github.com/astral-sh/ruff/issues/4845 Closes https://github.com/astral-sh/ruff/issues/3863 Addresses https://github.com/astral-sh/ruff/issues/6835 Addresses https://github.com/astral-sh/ruff/issues/7019 Needs follow-up https://github.com/astral-sh/ruff/issues/6962 Needs follow-up https://github.com/astral-sh/ruff/issues/4845 Needs follow-up https://github.com/astral-sh/ruff/issues/7436 Needs follow-up https://github.com/astral-sh/ruff/issues/7025 Needs follow-up https://github.com/astral-sh/ruff/issues/6434 Follow-up #7790 Follow-up https://github.com/astral-sh/ruff/pull/7792 --------- Co-authored-by: Evan Rittenhouse <evanrittenhouse@gmail.com>
This commit is contained in:
parent
e8d2cbc3f6
commit
22e18741bd
37 changed files with 704 additions and 150 deletions
|
@ -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<PathBuf>,
|
||||
/// 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<PathBuf>,
|
||||
pub fix: Option<bool>,
|
||||
pub fix_only: Option<bool>,
|
||||
pub unsafe_fixes: Option<UnsafeFixes>,
|
||||
pub force_exclude: Option<bool>,
|
||||
pub output_format: Option<SerializationFormat>,
|
||||
pub show_fixes: Option<bool>,
|
||||
|
@ -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
|
||||
|
|
|
@ -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,
|
||||
)
|
||||
}
|
||||
}
|
||||
|
|
|
@ -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<Diagnostics> {
|
||||
// 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<Diagnostics> {
|
||||
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();
|
||||
|
|
|
@ -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<Diagnostics> {
|
||||
// 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,
|
||||
|
|
|
@ -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<ExitStatus> {
|
|||
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<ExitStatus> {
|
|||
|
||||
// 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<ExitStatus> {
|
|||
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<ExitStatus> {
|
|||
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<ExitStatus> {
|
|||
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<ExitStatus> {
|
|||
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 {
|
||||
|
|
|
@ -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<String, FixTable>) -> Result<()> {
|
||||
|
@ -439,3 +437,80 @@ fn print_fix_summary(writer: &mut dyn Write, fixed: &FxHashMap<String, FixTable>
|
|||
}
|
||||
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<Self> {
|
||||
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
|
||||
)
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
|
@ -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 -----
|
||||
"###
|
||||
);
|
||||
}
|
||||
|
|
|
@ -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 -----
|
||||
"###);
|
||||
|
|
|
@ -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
|
||||
}
|
||||
}
|
||||
|
|
|
@ -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<FixResult> {
|
||||
/// 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<FixResult> {
|
||||
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() {
|
||||
|
|
|
@ -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.
|
||||
|
|
|
@ -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);
|
||||
}
|
||||
}
|
||||
|
|
|
@ -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."""
|
||||
|
|
|
@ -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
|
||||
|
|
||||
|
||||
|
|
@ -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."""
|
||||
|
|
|
@ -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
|
||||
|
|
||||
|
||||
|
|
@ -16,6 +16,7 @@ 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)]
|
||||
|
@ -32,6 +33,7 @@ bitflags! {
|
|||
#[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,21 +143,27 @@ 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 && self.message.fix.is_some() {
|
||||
write!(
|
||||
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,
|
||||
)
|
||||
} else {
|
||||
);
|
||||
}
|
||||
}
|
||||
};
|
||||
|
||||
write!(
|
||||
f,
|
||||
"{code} {body}",
|
||||
|
@ -157,7 +172,6 @@ impl Display for RuleCodeAndBody<'_> {
|
|||
)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
pub(super) struct MessageCodeFrame<'a> {
|
||||
pub(crate) message: &'a Message,
|
||||
|
@ -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);
|
||||
}
|
||||
}
|
||||
|
|
|
@ -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,
|
||||
|
|
|
@ -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 |
|
||||
|
|
|
@ -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 |
|
||||
|
|
|
@ -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
|
||||
|
|
|
@ -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=[]):
|
||||
|
|
|
@ -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
|
||||
|
|
|
@ -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 |
|
||||
|
|
|
@ -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 |
|
||||
|
|
|
@ -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
|
||||
|
|
|
@ -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
|
||||
|
|
|
@ -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)
|
||||
|
|
|
@ -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
|
||||
|
|
|
@ -60,6 +60,7 @@ pub struct LinterSettings {
|
|||
pub tab_size: TabSize,
|
||||
pub task_tags: Vec<String>,
|
||||
pub typing_modules: Vec<String>,
|
||||
|
||||
// Plugins
|
||||
pub flake8_annotations: flake8_annotations::settings::Settings,
|
||||
pub flake8_bandit: flake8_bandit::settings::Settings,
|
||||
|
|
|
@ -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<bool> for PreviewMode {
|
|||
}
|
||||
}
|
||||
|
||||
#[derive(Debug, Copy, Clone, CacheKey, Default, PartialEq, Eq, is_macro::Is)]
|
||||
pub enum UnsafeFixes {
|
||||
#[default]
|
||||
Disabled,
|
||||
Enabled,
|
||||
}
|
||||
|
||||
impl From<bool> 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),
|
||||
|
|
|
@ -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,
|
||||
|
|
|
@ -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<PathBuf>,
|
||||
pub fix: Option<bool>,
|
||||
pub fix_only: Option<bool>,
|
||||
pub unsafe_fixes: Option<UnsafeFixes>,
|
||||
pub output_format: Option<SerializationFormat>,
|
||||
pub preview: Option<PreviewMode>,
|
||||
pub required_version: Option<Version>,
|
||||
|
@ -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),
|
||||
|
|
|
@ -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<bool>,
|
||||
|
||||
/// Enable application of unsafe fixes.
|
||||
#[option(
|
||||
default = "false",
|
||||
value_type = "bool",
|
||||
example = "unsafe-fixes = true"
|
||||
)]
|
||||
pub unsafe_fixes: Option<bool>,
|
||||
|
||||
/// Like `fix`, but disables reporting on leftover violation. Implies `fix`.
|
||||
#[option(default = "false", value_type = "bool", example = "fix-only = true")]
|
||||
pub fix_only: Option<bool>,
|
||||
|
|
|
@ -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(),
|
||||
|
|
|
@ -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 <OUTPUT_FORMAT>
|
||||
|
|
9
ruff.schema.json
generated
9
ruff.schema.json
generated
|
@ -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,
|
||||
|
|
Loading…
Add table
Add a link
Reference in a new issue