Remove per-diagnostic check for fixability (#7919)

## Summary

Throughout the codebase, we have this pattern:

```rust
let mut diagnostic = ...
if checker.patch(Rule::UnusedVariable) {
    // Do the fix.
}
diagnostics.push(diagnostic)
```

This was helpful when we computed fixes lazily; however, we now compute
fixes eagerly, and this is _only_ used to ensure that we don't generate
fixes for rules marked as unfixable.

We often forget to add this, and it leads to bugs in enforcing
`--unfixable`.

This PR instead removes all of these checks, moving the responsibility
of enforcing `--unfixable` up to `check_path`. This is similar to how
@zanieb handled the `--extend-unsafe` logic: we post-process the
diagnostics to remove any fixes that should be ignored.
This commit is contained in:
Charlie Marsh 2023-10-11 12:09:47 -04:00 committed by GitHub
parent 1835d7bb45
commit c38617fa27
No known key found for this signature in database
GPG key ID: 4AEE18F83AFDEB23
189 changed files with 2433 additions and 3210 deletions

View file

@ -32,15 +32,10 @@ pub(crate) fn bindings(checker: &mut Checker) {
}, },
binding.range(), binding.range(),
); );
if checker.patch(Rule::UnusedVariable) { diagnostic.try_set_fix(|| {
diagnostic.try_set_fix(|| { pyflakes::fixes::remove_exception_handler_assignment(binding, checker.locator)
pyflakes::fixes::remove_exception_handler_assignment(
binding,
checker.locator,
)
.map(Fix::safe_edit) .map(Fix::safe_edit)
}); });
}
checker.diagnostics.push(diagnostic); checker.diagnostics.push(diagnostic);
} }
} }

View file

@ -143,11 +143,6 @@ impl<'a> Checker<'a> {
} }
impl<'a> Checker<'a> { impl<'a> Checker<'a> {
/// Return `true` if a patch should be generated for a given [`Rule`].
pub(crate) fn patch(&self, code: Rule) -> bool {
self.settings.rules.should_fix(code)
}
/// Return `true` if a [`Rule`] is disabled by a `noqa` directive. /// Return `true` if a [`Rule`] is disabled by a `noqa` directive.
pub(crate) fn rule_is_ignored(&self, code: Rule, offset: TextSize) -> bool { pub(crate) fn rule_is_ignored(&self, code: Rule, offset: TextSize) -> bool {
// TODO(charlie): `noqa` directives are mostly enforced in `check_lines.rs`. // TODO(charlie): `noqa` directives are mostly enforced in `check_lines.rs`.

View file

@ -5,7 +5,7 @@ use ruff_python_parser::TokenKind;
use ruff_source_file::Locator; use ruff_source_file::Locator;
use ruff_text_size::{Ranged, TextRange}; use ruff_text_size::{Ranged, TextRange};
use crate::registry::{AsRule, Rule}; use crate::registry::AsRule;
use crate::rules::pycodestyle::rules::logical_lines::{ use crate::rules::pycodestyle::rules::logical_lines::{
extraneous_whitespace, indentation, missing_whitespace, missing_whitespace_after_keyword, extraneous_whitespace, indentation, missing_whitespace, missing_whitespace_after_keyword,
missing_whitespace_around_operator, space_after_comma, space_around_operator, missing_whitespace_around_operator, space_after_comma, space_around_operator,
@ -38,17 +38,6 @@ pub(crate) fn check_logical_lines(
) -> Vec<Diagnostic> { ) -> Vec<Diagnostic> {
let mut context = LogicalLinesContext::new(settings); let mut context = LogicalLinesContext::new(settings);
let should_fix_missing_whitespace = settings.rules.should_fix(Rule::MissingWhitespace);
let should_fix_whitespace_before_parameters =
settings.rules.should_fix(Rule::WhitespaceBeforeParameters);
let should_fix_whitespace_after_open_bracket =
settings.rules.should_fix(Rule::WhitespaceAfterOpenBracket);
let should_fix_whitespace_before_close_bracket = settings
.rules
.should_fix(Rule::WhitespaceBeforeCloseBracket);
let should_fix_whitespace_before_punctuation =
settings.rules.should_fix(Rule::WhitespaceBeforePunctuation);
let mut prev_line = None; let mut prev_line = None;
let mut prev_indent_level = None; let mut prev_indent_level = None;
let indent_char = stylist.indentation().as_char(); let indent_char = stylist.indentation().as_char();
@ -58,7 +47,7 @@ pub(crate) fn check_logical_lines(
space_around_operator(&line, &mut context); space_around_operator(&line, &mut context);
whitespace_around_named_parameter_equals(&line, &mut context); whitespace_around_named_parameter_equals(&line, &mut context);
missing_whitespace_around_operator(&line, &mut context); missing_whitespace_around_operator(&line, &mut context);
missing_whitespace(&line, should_fix_missing_whitespace, &mut context); missing_whitespace(&line, &mut context);
} }
if line.flags().contains(TokenFlags::PUNCTUATION) { if line.flags().contains(TokenFlags::PUNCTUATION) {
space_after_comma(&line, &mut context); space_after_comma(&line, &mut context);
@ -68,13 +57,7 @@ pub(crate) fn check_logical_lines(
.flags() .flags()
.intersects(TokenFlags::OPERATOR | TokenFlags::BRACKET | TokenFlags::PUNCTUATION) .intersects(TokenFlags::OPERATOR | TokenFlags::BRACKET | TokenFlags::PUNCTUATION)
{ {
extraneous_whitespace( extraneous_whitespace(&line, &mut context);
&line,
&mut context,
should_fix_whitespace_after_open_bracket,
should_fix_whitespace_before_close_bracket,
should_fix_whitespace_before_punctuation,
);
} }
if line.flags().contains(TokenFlags::KEYWORD) { if line.flags().contains(TokenFlags::KEYWORD) {
@ -87,11 +70,7 @@ pub(crate) fn check_logical_lines(
} }
if line.flags().contains(TokenFlags::BRACKET) { if line.flags().contains(TokenFlags::BRACKET) {
whitespace_before_parameters( whitespace_before_parameters(&line, &mut context);
&line,
should_fix_whitespace_before_parameters,
&mut context,
);
} }
// Extract the indentation level. // Extract the indentation level.

View file

@ -109,10 +109,8 @@ pub(crate) fn check_noqa(
if line.matches.is_empty() { if line.matches.is_empty() {
let mut diagnostic = let mut diagnostic =
Diagnostic::new(UnusedNOQA { codes: None }, directive.range()); Diagnostic::new(UnusedNOQA { codes: None }, directive.range());
if settings.rules.should_fix(diagnostic.kind.rule()) { diagnostic.set_fix(Fix::safe_edit(delete_noqa(directive.range(), locator)));
diagnostic
.set_fix(Fix::safe_edit(delete_noqa(directive.range(), locator)));
}
diagnostics.push(diagnostic); diagnostics.push(diagnostic);
} }
} }
@ -173,18 +171,14 @@ pub(crate) fn check_noqa(
}, },
directive.range(), directive.range(),
); );
if settings.rules.should_fix(diagnostic.kind.rule()) { if valid_codes.is_empty() {
if valid_codes.is_empty() { diagnostic
diagnostic.set_fix(Fix::safe_edit(delete_noqa( .set_fix(Fix::safe_edit(delete_noqa(directive.range(), locator)));
directive.range(), } else {
locator, diagnostic.set_fix(Fix::safe_edit(Edit::range_replacement(
))); format!("# noqa: {}", valid_codes.join(", ")),
} else { directive.range(),
diagnostic.set_fix(Fix::safe_edit(Edit::range_replacement( )));
format!("# noqa: {}", valid_codes.join(", ")),
directive.range(),
)));
}
} }
diagnostics.push(diagnostic); diagnostics.push(diagnostic);
} }

View file

@ -71,11 +71,7 @@ pub(crate) fn check_physical_lines(
} }
if enforce_no_newline_at_end_of_file { if enforce_no_newline_at_end_of_file {
if let Some(diagnostic) = no_newline_at_end_of_file( if let Some(diagnostic) = no_newline_at_end_of_file(locator, stylist) {
locator,
stylist,
settings.rules.should_fix(Rule::MissingNewlineAtEndOfFile),
) {
diagnostics.push(diagnostic); diagnostics.push(diagnostic);
} }
} }

View file

@ -72,7 +72,7 @@ pub(crate) fn check_tokens(
} }
if settings.rules.enabled(Rule::UTF8EncodingDeclaration) { if settings.rules.enabled(Rule::UTF8EncodingDeclaration) {
pyupgrade::rules::unnecessary_coding_comment(&mut diagnostics, locator, indexer, settings); pyupgrade::rules::unnecessary_coding_comment(&mut diagnostics, locator, indexer);
} }
if settings.rules.enabled(Rule::InvalidEscapeSequence) { if settings.rules.enabled(Rule::InvalidEscapeSequence) {
@ -83,7 +83,6 @@ pub(crate) fn check_tokens(
indexer, indexer,
tok, tok,
*range, *range,
settings.rules.should_fix(Rule::InvalidEscapeSequence),
); );
} }
} }
@ -109,13 +108,7 @@ pub(crate) fn check_tokens(
Rule::MultipleStatementsOnOneLineSemicolon, Rule::MultipleStatementsOnOneLineSemicolon,
Rule::UselessSemicolon, Rule::UselessSemicolon,
]) { ]) {
pycodestyle::rules::compound_statements( pycodestyle::rules::compound_statements(&mut diagnostics, tokens, locator, indexer);
&mut diagnostics,
tokens,
locator,
indexer,
settings,
);
} }
if settings.rules.enabled(Rule::AvoidableEscapedQuote) && settings.flake8_quotes.avoid_escape { if settings.rules.enabled(Rule::AvoidableEscapedQuote) && settings.flake8_quotes.avoid_escape {
@ -148,11 +141,11 @@ pub(crate) fn check_tokens(
Rule::TrailingCommaOnBareTuple, Rule::TrailingCommaOnBareTuple,
Rule::ProhibitedTrailingComma, Rule::ProhibitedTrailingComma,
]) { ]) {
flake8_commas::rules::trailing_commas(&mut diagnostics, tokens, locator, settings); flake8_commas::rules::trailing_commas(&mut diagnostics, tokens, locator);
} }
if settings.rules.enabled(Rule::ExtraneousParentheses) { if settings.rules.enabled(Rule::ExtraneousParentheses) {
pyupgrade::rules::extraneous_parentheses(&mut diagnostics, tokens, locator, settings); pyupgrade::rules::extraneous_parentheses(&mut diagnostics, tokens, locator);
} }
if is_stub && settings.rules.enabled(Rule::TypeCommentInStub) { if is_stub && settings.rules.enabled(Rule::TypeCommentInStub) {
@ -166,7 +159,7 @@ pub(crate) fn check_tokens(
Rule::ShebangNotFirstLine, Rule::ShebangNotFirstLine,
Rule::ShebangMissingPython, Rule::ShebangMissingPython,
]) { ]) {
flake8_executable::rules::from_tokens(tokens, path, locator, settings, &mut diagnostics); flake8_executable::rules::from_tokens(tokens, path, locator, &mut diagnostics);
} }
if settings.rules.any_enabled(&[ if settings.rules.any_enabled(&[
@ -191,7 +184,7 @@ pub(crate) fn check_tokens(
TodoComment::from_comment(comment, *comment_range, i) TodoComment::from_comment(comment, *comment_range, i)
}) })
.collect(); .collect();
flake8_todos::rules::todos(&mut diagnostics, &todo_comments, locator, indexer, settings); flake8_todos::rules::todos(&mut diagnostics, &todo_comments, locator, indexer);
flake8_fixme::rules::todos(&mut diagnostics, &todo_comments); flake8_fixme::rules::todos(&mut diagnostics, &todo_comments);
} }

View file

@ -260,6 +260,13 @@ pub fn check_path(
} }
} }
// Remove fixes for any rules marked as unfixable.
for diagnostic in &mut diagnostics {
if !settings.rules.should_fix(diagnostic.kind.rule()) {
diagnostic.fix = None;
}
}
// Update fix applicability to account for overrides // Update fix applicability to account for overrides
if !settings.extend_safe_fixes.is_empty() || !settings.extend_unsafe_fixes.is_empty() { if !settings.extend_safe_fixes.is_empty() || !settings.extend_unsafe_fixes.is_empty() {
for diagnostic in &mut diagnostics { for diagnostic in &mut diagnostics {

View file

@ -3,7 +3,6 @@ use ruff_macros::{derive_message_formats, violation};
use ruff_python_index::Indexer; use ruff_python_index::Indexer;
use ruff_source_file::Locator; use ruff_source_file::Locator;
use crate::registry::Rule;
use crate::settings::LinterSettings; use crate::settings::LinterSettings;
use super::super::detection::comment_contains_code; use super::super::detection::comment_contains_code;
@ -67,11 +66,9 @@ pub(crate) fn commented_out_code(
if is_standalone_comment(line) && comment_contains_code(line, &settings.task_tags[..]) { if is_standalone_comment(line) && comment_contains_code(line, &settings.task_tags[..]) {
let mut diagnostic = Diagnostic::new(CommentedOutCode, *range); let mut diagnostic = Diagnostic::new(CommentedOutCode, *range);
if settings.rules.should_fix(Rule::CommentedOutCode) { diagnostic.set_fix(Fix::display_edit(Edit::range_deletion(
diagnostic.set_fix(Fix::display_edit(Edit::range_deletion( locator.full_lines_range(*range),
locator.full_lines_range(*range), )));
)));
}
diagnostics.push(diagnostic); diagnostics.push(diagnostic);
} }
} }

View file

@ -11,7 +11,7 @@ use ruff_python_stdlib::typing::simple_magic_return_type;
use ruff_text_size::Ranged; use ruff_text_size::Ranged;
use crate::checkers::ast::Checker; use crate::checkers::ast::Checker;
use crate::registry::{AsRule, Rule}; use crate::registry::Rule;
use crate::rules::ruff::typing::type_hint_resolves_to_any; use crate::rules::ruff::typing::type_hint_resolves_to_any;
/// ## What it does /// ## What it does
@ -702,12 +702,10 @@ pub(crate) fn definition(
}, },
function.identifier(), function.identifier(),
); );
if checker.patch(diagnostic.kind.rule()) { diagnostic.set_fix(Fix::unsafe_edit(Edit::insertion(
diagnostic.set_fix(Fix::unsafe_edit(Edit::insertion( " -> None".to_string(),
" -> None".to_string(), function.parameters.range().end(),
function.parameters.range().end(), )));
)));
}
diagnostics.push(diagnostic); diagnostics.push(diagnostic);
} }
} }
@ -719,13 +717,11 @@ pub(crate) fn definition(
}, },
function.identifier(), function.identifier(),
); );
if checker.patch(diagnostic.kind.rule()) { if let Some(return_type) = simple_magic_return_type(name) {
if let Some(return_type) = simple_magic_return_type(name) { diagnostic.set_fix(Fix::unsafe_edit(Edit::insertion(
diagnostic.set_fix(Fix::unsafe_edit(Edit::insertion( format!(" -> {return_type}"),
format!(" -> {return_type}"), function.parameters.range().end(),
function.parameters.range().end(), )));
)));
}
} }
diagnostics.push(diagnostic); diagnostics.push(diagnostic);
} }

View file

@ -6,7 +6,6 @@ use ruff_macros::{derive_message_formats, violation};
use ruff_python_ast::helpers::is_const_false; use ruff_python_ast::helpers::is_const_false;
use crate::checkers::ast::Checker; use crate::checkers::ast::Checker;
use crate::registry::AsRule;
/// ## What it does /// ## What it does
/// Checks for uses of `assert False`. /// Checks for uses of `assert False`.
@ -75,11 +74,9 @@ pub(crate) fn assert_false(checker: &mut Checker, stmt: &Stmt, test: &Expr, msg:
} }
let mut diagnostic = Diagnostic::new(AssertFalse, test.range()); let mut diagnostic = Diagnostic::new(AssertFalse, test.range());
if checker.patch(diagnostic.kind.rule()) { diagnostic.set_fix(Fix::unsafe_edit(Edit::range_replacement(
diagnostic.set_fix(Fix::unsafe_edit(Edit::range_replacement( checker.generator().stmt(&assertion_error(msg)),
checker.generator().stmt(&assertion_error(msg)), stmt.range(),
stmt.range(), )));
)));
}
checker.diagnostics.push(diagnostic); checker.diagnostics.push(diagnostic);
} }

View file

@ -11,7 +11,7 @@ use ruff_python_ast::call_path::CallPath;
use crate::checkers::ast::Checker; use crate::checkers::ast::Checker;
use crate::fix::edits::pad; use crate::fix::edits::pad;
use crate::registry::{AsRule, Rule}; use crate::registry::Rule;
/// ## What it does /// ## What it does
/// Checks for `try-except` blocks with duplicate exception handlers. /// Checks for `try-except` blocks with duplicate exception handlers.
@ -146,24 +146,22 @@ fn duplicate_handler_exceptions<'a>(
}, },
expr.range(), expr.range(),
); );
if checker.patch(diagnostic.kind.rule()) { diagnostic.set_fix(Fix::safe_edit(Edit::range_replacement(
diagnostic.set_fix(Fix::safe_edit(Edit::range_replacement( // Single exceptions don't require parentheses, but since we're _removing_
// Single exceptions don't require parentheses, but since we're _removing_ // parentheses, insert whitespace as needed.
// parentheses, insert whitespace as needed. if let [elt] = unique_elts.as_slice() {
if let [elt] = unique_elts.as_slice() { pad(
pad( checker.generator().expr(elt),
checker.generator().expr(elt), expr.range(),
expr.range(), checker.locator(),
checker.locator(), )
) } else {
} else { // Multiple exceptions must always be parenthesized. This is done
// Multiple exceptions must always be parenthesized. This is done // manually as the generator never parenthesizes lone tuples.
// manually as the generator never parenthesizes lone tuples. format!("({})", checker.generator().expr(&type_pattern(unique_elts)))
format!("({})", checker.generator().expr(&type_pattern(unique_elts))) },
}, expr.range(),
expr.range(), )));
)));
}
checker.diagnostics.push(diagnostic); checker.diagnostics.push(diagnostic);
} }
} }

View file

@ -6,7 +6,6 @@ use ruff_python_stdlib::identifiers::{is_identifier, is_mangled_private};
use ruff_text_size::Ranged; use ruff_text_size::Ranged;
use crate::checkers::ast::Checker; use crate::checkers::ast::Checker;
use crate::registry::AsRule;
/// ## What it does /// ## What it does
/// Checks for uses of `getattr` that take a constant attribute value as an /// Checks for uses of `getattr` that take a constant attribute value as an
@ -85,26 +84,24 @@ pub(crate) fn getattr_with_constant(
} }
let mut diagnostic = Diagnostic::new(GetAttrWithConstant, expr.range()); let mut diagnostic = Diagnostic::new(GetAttrWithConstant, expr.range());
if checker.patch(diagnostic.kind.rule()) { diagnostic.set_fix(Fix::safe_edit(Edit::range_replacement(
diagnostic.set_fix(Fix::safe_edit(Edit::range_replacement( pad(
pad( if matches!(
if matches!( obj,
obj, Expr::Name(_) | Expr::Attribute(_) | Expr::Subscript(_) | Expr::Call(_)
Expr::Name(_) | Expr::Attribute(_) | Expr::Subscript(_) | Expr::Call(_) ) && !checker.locator().contains_line_break(obj.range())
) && !checker.locator().contains_line_break(obj.range()) {
{ format!("{}.{}", checker.locator().slice(obj), value)
format!("{}.{}", checker.locator().slice(obj), value) } else {
} else { // Defensively parenthesize any other expressions. For example, attribute accesses
// Defensively parenthesize any other expressions. For example, attribute accesses // on `int` literals must be parenthesized, e.g., `getattr(1, "real")` becomes
// on `int` literals must be parenthesized, e.g., `getattr(1, "real")` becomes // `(1).real`. The same is true for named expressions and others.
// `(1).real`. The same is true for named expressions and others. format!("({}).{}", checker.locator().slice(obj), value)
format!("({}).{}", checker.locator().slice(obj), value) },
},
expr.range(),
checker.locator(),
),
expr.range(), expr.range(),
))); checker.locator(),
} ),
expr.range(),
)));
checker.diagnostics.push(diagnostic); checker.diagnostics.push(diagnostic);
} }

View file

@ -11,7 +11,6 @@ use ruff_source_file::Locator;
use ruff_text_size::Ranged; use ruff_text_size::Ranged;
use crate::checkers::ast::Checker; use crate::checkers::ast::Checker;
use crate::registry::AsRule;
/// ## What it does /// ## What it does
/// Checks for uses of mutable objects as function argument defaults. /// Checks for uses of mutable objects as function argument defaults.
@ -110,18 +109,16 @@ pub(crate) fn mutable_argument_default(checker: &mut Checker, function_def: &ast
let mut diagnostic = Diagnostic::new(MutableArgumentDefault, default.range()); let mut diagnostic = Diagnostic::new(MutableArgumentDefault, default.range());
// If the function body is on the same line as the function def, do not fix // If the function body is on the same line as the function def, do not fix
if checker.patch(diagnostic.kind.rule()) { if let Some(fix) = move_initialization(
if let Some(fix) = move_initialization( function_def,
function_def, parameter,
parameter, default,
default, checker.locator(),
checker.locator(), checker.stylist(),
checker.stylist(), checker.indexer(),
checker.indexer(), checker.generator(),
checker.generator(), ) {
) { diagnostic.set_fix(fix);
diagnostic.set_fix(fix);
}
} }
checker.diagnostics.push(diagnostic); checker.diagnostics.push(diagnostic);
} }

View file

@ -6,7 +6,6 @@ use ruff_text_size::Ranged;
use crate::checkers::ast::Checker; use crate::checkers::ast::Checker;
use crate::fix::edits::pad; use crate::fix::edits::pad;
use crate::registry::AsRule;
/// ## What it does /// ## What it does
/// Checks for single-element tuples in exception handlers (e.g., /// Checks for single-element tuples in exception handlers (e.g.,
@ -77,23 +76,21 @@ pub(crate) fn redundant_tuple_in_exception_handler(
}, },
type_.range(), type_.range(),
); );
if checker.patch(diagnostic.kind.rule()) { // If there's no space between the `except` and the tuple, we need to insert a space,
// If there's no space between the `except` and the tuple, we need to insert a space, // as in:
// as in: // ```python
// ```python // except(ValueError,):
// except(ValueError,): // ```
// ``` // Otherwise, the output will be invalid syntax, since we're removing a set of
// Otherwise, the output will be invalid syntax, since we're removing a set of // parentheses.
// parentheses. diagnostic.set_fix(Fix::safe_edit(Edit::range_replacement(
diagnostic.set_fix(Fix::safe_edit(Edit::range_replacement( pad(
pad( checker.generator().expr(elt),
checker.generator().expr(elt),
type_.range(),
checker.locator(),
),
type_.range(), type_.range(),
))); checker.locator(),
} ),
type_.range(),
)));
checker.diagnostics.push(diagnostic); checker.diagnostics.push(diagnostic);
} }
} }

View file

@ -7,7 +7,6 @@ use ruff_python_codegen::Generator;
use ruff_python_stdlib::identifiers::{is_identifier, is_mangled_private}; use ruff_python_stdlib::identifiers::{is_identifier, is_mangled_private};
use crate::checkers::ast::Checker; use crate::checkers::ast::Checker;
use crate::registry::AsRule;
/// ## What it does /// ## What it does
/// Checks for uses of `setattr` that take a constant attribute value as an /// Checks for uses of `setattr` that take a constant attribute value as an
@ -108,12 +107,10 @@ pub(crate) fn setattr_with_constant(
{ {
if expr == child.as_ref() { if expr == child.as_ref() {
let mut diagnostic = Diagnostic::new(SetAttrWithConstant, expr.range()); let mut diagnostic = Diagnostic::new(SetAttrWithConstant, expr.range());
if checker.patch(diagnostic.kind.rule()) { diagnostic.set_fix(Fix::safe_edit(Edit::range_replacement(
diagnostic.set_fix(Fix::safe_edit(Edit::range_replacement( assignment(obj, name, value, checker.generator()),
assignment(obj, name, value, checker.generator()), expr.range(),
expr.range(), )));
)));
}
checker.diagnostics.push(diagnostic); checker.diagnostics.push(diagnostic);
} }
} }

View file

@ -5,7 +5,6 @@ use ruff_macros::{derive_message_formats, violation};
use ruff_text_size::Ranged; use ruff_text_size::Ranged;
use crate::checkers::ast::Checker; use crate::checkers::ast::Checker;
use crate::registry::AsRule;
/// ## What it does /// ## What it does
/// Checks for uses of `hasattr` to test if an object is callable (e.g., /// Checks for uses of `hasattr` to test if an object is callable (e.g.,
@ -80,14 +79,12 @@ pub(crate) fn unreliable_callable_check(
} }
let mut diagnostic = Diagnostic::new(UnreliableCallableCheck, expr.range()); let mut diagnostic = Diagnostic::new(UnreliableCallableCheck, expr.range());
if checker.patch(diagnostic.kind.rule()) { if id == "hasattr" {
if id == "hasattr" { if checker.semantic().is_builtin("callable") {
if checker.semantic().is_builtin("callable") { diagnostic.set_fix(Fix::safe_edit(Edit::range_replacement(
diagnostic.set_fix(Fix::safe_edit(Edit::range_replacement( format!("callable({})", checker.locator().slice(obj)),
format!("callable({})", checker.locator().slice(obj)), expr.range(),
expr.range(), )));
)));
}
} }
} }
checker.diagnostics.push(diagnostic); checker.diagnostics.push(diagnostic);

View file

@ -8,7 +8,6 @@ use ruff_python_ast::{helpers, visitor};
use ruff_text_size::Ranged; use ruff_text_size::Ranged;
use crate::checkers::ast::Checker; use crate::checkers::ast::Checker;
use crate::registry::AsRule;
#[derive(Debug, Copy, Clone, PartialEq, Eq, result_like::BoolLike)] #[derive(Debug, Copy, Clone, PartialEq, Eq, result_like::BoolLike)]
enum Certainty { enum Certainty {
@ -156,23 +155,21 @@ pub(crate) fn unused_loop_control_variable(checker: &mut Checker, stmt_for: &ast
}, },
expr.range(), expr.range(),
); );
if checker.patch(diagnostic.kind.rule()) { if let Some(rename) = rename {
if let Some(rename) = rename { if certainty.into() {
if certainty.into() { // Avoid fixing if the variable, or any future bindings to the variable, are
// Avoid fixing if the variable, or any future bindings to the variable, are // used _after_ the loop.
// used _after_ the loop. let scope = checker.semantic().current_scope();
let scope = checker.semantic().current_scope(); if scope
if scope .get_all(name)
.get_all(name) .map(|binding_id| checker.semantic().binding(binding_id))
.map(|binding_id| checker.semantic().binding(binding_id)) .filter(|binding| binding.start() >= expr.start())
.filter(|binding| binding.start() >= expr.start()) .all(|binding| !binding.is_used())
.all(|binding| !binding.is_used()) {
{ diagnostic.set_fix(Fix::unsafe_edit(Edit::range_replacement(
diagnostic.set_fix(Fix::unsafe_edit(Edit::range_replacement( rename,
rename, expr.range(),
expr.range(), )));
)));
}
} }
} }
} }

View file

@ -8,9 +8,6 @@ use ruff_python_parser::Tok;
use ruff_source_file::Locator; use ruff_source_file::Locator;
use ruff_text_size::{Ranged, TextRange}; use ruff_text_size::{Ranged, TextRange};
use crate::registry::Rule;
use crate::settings::LinterSettings;
/// Simplified token type. /// Simplified token type.
#[derive(Copy, Clone, PartialEq, Eq)] #[derive(Copy, Clone, PartialEq, Eq)]
enum TokenType { enum TokenType {
@ -225,7 +222,6 @@ pub(crate) fn trailing_commas(
diagnostics: &mut Vec<Diagnostic>, diagnostics: &mut Vec<Diagnostic>,
tokens: &[LexResult], tokens: &[LexResult],
locator: &Locator, locator: &Locator,
settings: &LinterSettings,
) { ) {
let tokens = tokens let tokens = tokens
.iter() .iter()
@ -324,9 +320,7 @@ pub(crate) fn trailing_commas(
if comma_prohibited { if comma_prohibited {
let comma = prev.spanned.unwrap(); let comma = prev.spanned.unwrap();
let mut diagnostic = Diagnostic::new(ProhibitedTrailingComma, comma.1); let mut diagnostic = Diagnostic::new(ProhibitedTrailingComma, comma.1);
if settings.rules.should_fix(Rule::ProhibitedTrailingComma) { diagnostic.set_fix(Fix::safe_edit(Edit::range_deletion(diagnostic.range())));
diagnostic.set_fix(Fix::safe_edit(Edit::range_deletion(diagnostic.range())));
}
diagnostics.push(diagnostic); diagnostics.push(diagnostic);
} }
@ -359,17 +353,15 @@ pub(crate) fn trailing_commas(
MissingTrailingComma, MissingTrailingComma,
TextRange::empty(missing_comma.1.end()), TextRange::empty(missing_comma.1.end()),
); );
if settings.rules.should_fix(Rule::MissingTrailingComma) { // Create a replacement that includes the final bracket (or other token),
// Create a replacement that includes the final bracket (or other token), // rather than just inserting a comma at the end. This prevents the UP034 fix
// rather than just inserting a comma at the end. This prevents the UP034 fix // removing any brackets in the same linter pass - doing both at the same time could
// removing any brackets in the same linter pass - doing both at the same time could // lead to a syntax error.
// lead to a syntax error. let contents = locator.slice(missing_comma.1);
let contents = locator.slice(missing_comma.1); diagnostic.set_fix(Fix::safe_edit(Edit::range_replacement(
diagnostic.set_fix(Fix::safe_edit(Edit::range_replacement( format!("{contents},"),
format!("{contents},"), missing_comma.1,
missing_comma.1, )));
)));
}
diagnostics.push(diagnostic); diagnostics.push(diagnostic);
} }

View file

@ -4,7 +4,7 @@ use ruff_python_ast::{self as ast, Expr};
use ruff_text_size::Ranged; use ruff_text_size::Ranged;
use crate::checkers::ast::Checker; use crate::checkers::ast::Checker;
use crate::registry::AsRule;
use crate::rules::flake8_comprehensions::fixes; use crate::rules::flake8_comprehensions::fixes;
/// ## What it does /// ## What it does
@ -82,19 +82,14 @@ pub(crate) fn unnecessary_call_around_sorted(
}, },
expr.range(), expr.range(),
); );
if checker.patch(diagnostic.kind.rule()) { diagnostic.try_set_fix(|| {
diagnostic.try_set_fix(|| { let edit =
let edit = fixes::fix_unnecessary_call_around_sorted( fixes::fix_unnecessary_call_around_sorted(expr, checker.locator(), checker.stylist())?;
expr, if outer.id == "reversed" {
checker.locator(), Ok(Fix::unsafe_edit(edit))
checker.stylist(), } else {
)?; Ok(Fix::safe_edit(edit))
if outer.id == "reversed" { }
Ok(Fix::unsafe_edit(edit)) });
} else {
Ok(Fix::safe_edit(edit))
}
});
}
checker.diagnostics.push(diagnostic); checker.diagnostics.push(diagnostic);
} }

View file

@ -4,7 +4,7 @@ use ruff_python_ast::{Expr, Keyword};
use ruff_text_size::Ranged; use ruff_text_size::Ranged;
use crate::checkers::ast::Checker; use crate::checkers::ast::Checker;
use crate::registry::AsRule;
use crate::rules::flake8_comprehensions::fixes; use crate::rules::flake8_comprehensions::fixes;
use crate::rules::flake8_comprehensions::settings::Settings; use crate::rules::flake8_comprehensions::settings::Settings;
@ -86,10 +86,8 @@ pub(crate) fn unnecessary_collection_call(
}, },
expr.range(), expr.range(),
); );
if checker.patch(diagnostic.kind.rule()) { diagnostic.try_set_fix(|| {
diagnostic.try_set_fix(|| { fixes::fix_unnecessary_collection_call(expr, checker).map(Fix::unsafe_edit)
fixes::fix_unnecessary_collection_call(expr, checker).map(Fix::unsafe_edit) });
});
}
checker.diagnostics.push(diagnostic); checker.diagnostics.push(diagnostic);
} }

View file

@ -5,7 +5,7 @@ use ruff_python_ast::{self as ast, Comprehension, Expr};
use ruff_text_size::Ranged; use ruff_text_size::Ranged;
use crate::checkers::ast::Checker; use crate::checkers::ast::Checker;
use crate::registry::AsRule;
use crate::rules::flake8_comprehensions::fixes; use crate::rules::flake8_comprehensions::fixes;
/// ## What it does /// ## What it does
@ -64,12 +64,10 @@ fn add_diagnostic(checker: &mut Checker, expr: &Expr) {
}, },
expr.range(), expr.range(),
); );
if checker.patch(diagnostic.kind.rule()) { diagnostic.try_set_fix(|| {
diagnostic.try_set_fix(|| { fixes::fix_unnecessary_comprehension(expr, checker.locator(), checker.stylist())
fixes::fix_unnecessary_comprehension(expr, checker.locator(), checker.stylist()) .map(Fix::unsafe_edit)
.map(Fix::unsafe_edit) });
});
}
checker.diagnostics.push(diagnostic); checker.diagnostics.push(diagnostic);
} }

View file

@ -7,7 +7,7 @@ use ruff_python_ast::helpers::any_over_expr;
use ruff_text_size::Ranged; use ruff_text_size::Ranged;
use crate::checkers::ast::Checker; use crate::checkers::ast::Checker;
use crate::registry::AsRule;
use crate::rules::flake8_comprehensions::fixes; use crate::rules::flake8_comprehensions::fixes;
/// ## What it does /// ## What it does
@ -89,11 +89,9 @@ pub(crate) fn unnecessary_comprehension_any_all(
} }
let mut diagnostic = Diagnostic::new(UnnecessaryComprehensionAnyAll, arg.range()); let mut diagnostic = Diagnostic::new(UnnecessaryComprehensionAnyAll, arg.range());
if checker.patch(diagnostic.kind.rule()) { diagnostic.try_set_fix(|| {
diagnostic.try_set_fix(|| { fixes::fix_unnecessary_comprehension_any_all(expr, checker.locator(), checker.stylist())
fixes::fix_unnecessary_comprehension_any_all(expr, checker.locator(), checker.stylist()) });
});
}
checker.diagnostics.push(diagnostic); checker.diagnostics.push(diagnostic);
} }

View file

@ -5,7 +5,7 @@ use ruff_python_ast::{self as ast, Arguments, Expr, Keyword};
use ruff_text_size::Ranged; use ruff_text_size::Ranged;
use crate::checkers::ast::Checker; use crate::checkers::ast::Checker;
use crate::registry::AsRule;
use crate::rules::flake8_comprehensions::fixes; use crate::rules::flake8_comprehensions::fixes;
/// ## What it does /// ## What it does
@ -130,16 +130,14 @@ pub(crate) fn unnecessary_double_cast_or_process(
}, },
expr.range(), expr.range(),
); );
if checker.patch(diagnostic.kind.rule()) { diagnostic.try_set_fix(|| {
diagnostic.try_set_fix(|| { fixes::fix_unnecessary_double_cast_or_process(
fixes::fix_unnecessary_double_cast_or_process( expr,
expr, checker.locator(),
checker.locator(), checker.stylist(),
checker.stylist(), )
) .map(Fix::unsafe_edit)
.map(Fix::unsafe_edit) });
});
}
checker.diagnostics.push(diagnostic); checker.diagnostics.push(diagnostic);
} }
} }

View file

@ -4,7 +4,7 @@ use ruff_python_ast::{self as ast, Expr, Keyword};
use ruff_text_size::Ranged; use ruff_text_size::Ranged;
use crate::checkers::ast::Checker; use crate::checkers::ast::Checker;
use crate::registry::AsRule;
use crate::rules::flake8_comprehensions::fixes; use crate::rules::flake8_comprehensions::fixes;
use super::helpers; use super::helpers;
@ -67,10 +67,7 @@ pub(crate) fn unnecessary_generator_dict(
return; return;
} }
let mut diagnostic = Diagnostic::new(UnnecessaryGeneratorDict, expr.range()); let mut diagnostic = Diagnostic::new(UnnecessaryGeneratorDict, expr.range());
if checker.patch(diagnostic.kind.rule()) { diagnostic
diagnostic.try_set_fix(|| { .try_set_fix(|| fixes::fix_unnecessary_generator_dict(expr, checker).map(Fix::unsafe_edit));
fixes::fix_unnecessary_generator_dict(expr, checker).map(Fix::unsafe_edit)
});
}
checker.diagnostics.push(diagnostic); checker.diagnostics.push(diagnostic);
} }

View file

@ -5,7 +5,7 @@ use ruff_macros::{derive_message_formats, violation};
use ruff_text_size::Ranged; use ruff_text_size::Ranged;
use crate::checkers::ast::Checker; use crate::checkers::ast::Checker;
use crate::registry::AsRule;
use crate::rules::flake8_comprehensions::fixes; use crate::rules::flake8_comprehensions::fixes;
use super::helpers; use super::helpers;
@ -60,12 +60,10 @@ pub(crate) fn unnecessary_generator_list(
} }
if let Expr::GeneratorExp(_) = argument { if let Expr::GeneratorExp(_) = argument {
let mut diagnostic = Diagnostic::new(UnnecessaryGeneratorList, expr.range()); let mut diagnostic = Diagnostic::new(UnnecessaryGeneratorList, expr.range());
if checker.patch(diagnostic.kind.rule()) { diagnostic.try_set_fix(|| {
diagnostic.try_set_fix(|| { fixes::fix_unnecessary_generator_list(expr, checker.locator(), checker.stylist())
fixes::fix_unnecessary_generator_list(expr, checker.locator(), checker.stylist()) .map(Fix::unsafe_edit)
.map(Fix::unsafe_edit) });
});
}
checker.diagnostics.push(diagnostic); checker.diagnostics.push(diagnostic);
} }
} }

View file

@ -5,7 +5,7 @@ use ruff_macros::{derive_message_formats, violation};
use ruff_text_size::Ranged; use ruff_text_size::Ranged;
use crate::checkers::ast::Checker; use crate::checkers::ast::Checker;
use crate::registry::AsRule;
use crate::rules::flake8_comprehensions::fixes; use crate::rules::flake8_comprehensions::fixes;
use super::helpers; use super::helpers;
@ -60,11 +60,9 @@ pub(crate) fn unnecessary_generator_set(
} }
if let Expr::GeneratorExp(_) = argument { if let Expr::GeneratorExp(_) = argument {
let mut diagnostic = Diagnostic::new(UnnecessaryGeneratorSet, expr.range()); let mut diagnostic = Diagnostic::new(UnnecessaryGeneratorSet, expr.range());
if checker.patch(diagnostic.kind.rule()) { diagnostic.try_set_fix(|| {
diagnostic.try_set_fix(|| { fixes::fix_unnecessary_generator_set(expr, checker).map(Fix::unsafe_edit)
fixes::fix_unnecessary_generator_set(expr, checker).map(Fix::unsafe_edit) });
});
}
checker.diagnostics.push(diagnostic); checker.diagnostics.push(diagnostic);
} }
} }

View file

@ -5,7 +5,7 @@ use ruff_macros::{derive_message_formats, violation};
use ruff_text_size::Ranged; use ruff_text_size::Ranged;
use crate::checkers::ast::Checker; use crate::checkers::ast::Checker;
use crate::registry::AsRule;
use crate::rules::flake8_comprehensions::fixes; use crate::rules::flake8_comprehensions::fixes;
use super::helpers; use super::helpers;
@ -56,11 +56,9 @@ pub(crate) fn unnecessary_list_call(
return; return;
} }
let mut diagnostic = Diagnostic::new(UnnecessaryListCall, expr.range()); let mut diagnostic = Diagnostic::new(UnnecessaryListCall, expr.range());
if checker.patch(diagnostic.kind.rule()) { diagnostic.try_set_fix(|| {
diagnostic.try_set_fix(|| { fixes::fix_unnecessary_list_call(expr, checker.locator(), checker.stylist())
fixes::fix_unnecessary_list_call(expr, checker.locator(), checker.stylist()) .map(Fix::unsafe_edit)
.map(Fix::unsafe_edit) });
});
}
checker.diagnostics.push(diagnostic); checker.diagnostics.push(diagnostic);
} }

View file

@ -4,7 +4,7 @@ use ruff_python_ast::{self as ast, Expr, Keyword};
use ruff_text_size::Ranged; use ruff_text_size::Ranged;
use crate::checkers::ast::Checker; use crate::checkers::ast::Checker;
use crate::registry::AsRule;
use crate::rules::flake8_comprehensions::fixes; use crate::rules::flake8_comprehensions::fixes;
use super::helpers; use super::helpers;
@ -65,10 +65,8 @@ pub(crate) fn unnecessary_list_comprehension_dict(
return; return;
} }
let mut diagnostic = Diagnostic::new(UnnecessaryListComprehensionDict, expr.range()); let mut diagnostic = Diagnostic::new(UnnecessaryListComprehensionDict, expr.range());
if checker.patch(diagnostic.kind.rule()) { diagnostic.try_set_fix(|| {
diagnostic.try_set_fix(|| { fixes::fix_unnecessary_list_comprehension_dict(expr, checker).map(Fix::unsafe_edit)
fixes::fix_unnecessary_list_comprehension_dict(expr, checker).map(Fix::unsafe_edit) });
});
}
checker.diagnostics.push(diagnostic); checker.diagnostics.push(diagnostic);
} }

View file

@ -5,7 +5,7 @@ use ruff_macros::{derive_message_formats, violation};
use ruff_text_size::Ranged; use ruff_text_size::Ranged;
use crate::checkers::ast::Checker; use crate::checkers::ast::Checker;
use crate::registry::AsRule;
use crate::rules::flake8_comprehensions::fixes; use crate::rules::flake8_comprehensions::fixes;
use super::helpers; use super::helpers;
@ -58,11 +58,9 @@ pub(crate) fn unnecessary_list_comprehension_set(
} }
if argument.is_list_comp_expr() { if argument.is_list_comp_expr() {
let mut diagnostic = Diagnostic::new(UnnecessaryListComprehensionSet, expr.range()); let mut diagnostic = Diagnostic::new(UnnecessaryListComprehensionSet, expr.range());
if checker.patch(diagnostic.kind.rule()) { diagnostic.try_set_fix(|| {
diagnostic.try_set_fix(|| { fixes::fix_unnecessary_list_comprehension_set(expr, checker).map(Fix::unsafe_edit)
fixes::fix_unnecessary_list_comprehension_set(expr, checker).map(Fix::unsafe_edit) });
});
}
checker.diagnostics.push(diagnostic); checker.diagnostics.push(diagnostic);
} }
} }

View file

@ -4,7 +4,7 @@ use ruff_python_ast::{self as ast, Expr, Keyword};
use ruff_text_size::Ranged; use ruff_text_size::Ranged;
use crate::checkers::ast::Checker; use crate::checkers::ast::Checker;
use crate::registry::AsRule;
use crate::rules::flake8_comprehensions::fixes; use crate::rules::flake8_comprehensions::fixes;
use super::helpers; use super::helpers;
@ -80,10 +80,7 @@ pub(crate) fn unnecessary_literal_dict(
}, },
expr.range(), expr.range(),
); );
if checker.patch(diagnostic.kind.rule()) { diagnostic
diagnostic.try_set_fix(|| { .try_set_fix(|| fixes::fix_unnecessary_literal_dict(expr, checker).map(Fix::unsafe_edit));
fixes::fix_unnecessary_literal_dict(expr, checker).map(Fix::unsafe_edit)
});
}
checker.diagnostics.push(diagnostic); checker.diagnostics.push(diagnostic);
} }

View file

@ -5,7 +5,7 @@ use ruff_macros::{derive_message_formats, violation};
use ruff_text_size::Ranged; use ruff_text_size::Ranged;
use crate::checkers::ast::Checker; use crate::checkers::ast::Checker;
use crate::registry::AsRule;
use crate::rules::flake8_comprehensions::fixes; use crate::rules::flake8_comprehensions::fixes;
use super::helpers; use super::helpers;
@ -75,10 +75,7 @@ pub(crate) fn unnecessary_literal_set(
}, },
expr.range(), expr.range(),
); );
if checker.patch(diagnostic.kind.rule()) { diagnostic
diagnostic.try_set_fix(|| { .try_set_fix(|| fixes::fix_unnecessary_literal_set(expr, checker).map(Fix::unsafe_edit));
fixes::fix_unnecessary_literal_set(expr, checker).map(Fix::unsafe_edit)
});
}
checker.diagnostics.push(diagnostic); checker.diagnostics.push(diagnostic);
} }

View file

@ -7,7 +7,7 @@ use ruff_macros::{derive_message_formats, violation};
use ruff_text_size::Ranged; use ruff_text_size::Ranged;
use crate::checkers::ast::Checker; use crate::checkers::ast::Checker;
use crate::registry::AsRule;
use crate::rules::flake8_comprehensions::fixes; use crate::rules::flake8_comprehensions::fixes;
use super::helpers; use super::helpers;
@ -91,15 +91,9 @@ pub(crate) fn unnecessary_literal_within_dict_call(
}, },
expr.range(), expr.range(),
); );
if checker.patch(diagnostic.kind.rule()) { diagnostic.try_set_fix(|| {
diagnostic.try_set_fix(|| { fixes::fix_unnecessary_literal_within_dict_call(expr, checker.locator(), checker.stylist())
fixes::fix_unnecessary_literal_within_dict_call(
expr,
checker.locator(),
checker.stylist(),
)
.map(Fix::unsafe_edit) .map(Fix::unsafe_edit)
}); });
}
checker.diagnostics.push(diagnostic); checker.diagnostics.push(diagnostic);
} }

View file

@ -4,7 +4,7 @@ use ruff_python_ast::{Expr, Keyword};
use ruff_text_size::Ranged; use ruff_text_size::Ranged;
use crate::checkers::ast::Checker; use crate::checkers::ast::Checker;
use crate::registry::AsRule;
use crate::rules::flake8_comprehensions::fixes; use crate::rules::flake8_comprehensions::fixes;
use super::helpers; use super::helpers;
@ -93,15 +93,9 @@ pub(crate) fn unnecessary_literal_within_list_call(
}, },
expr.range(), expr.range(),
); );
if checker.patch(diagnostic.kind.rule()) { diagnostic.try_set_fix(|| {
diagnostic.try_set_fix(|| { fixes::fix_unnecessary_literal_within_list_call(expr, checker.locator(), checker.stylist())
fixes::fix_unnecessary_literal_within_list_call(
expr,
checker.locator(),
checker.stylist(),
)
.map(Fix::unsafe_edit) .map(Fix::unsafe_edit)
}); });
}
checker.diagnostics.push(diagnostic); checker.diagnostics.push(diagnostic);
} }

View file

@ -5,7 +5,7 @@ use ruff_macros::{derive_message_formats, violation};
use ruff_text_size::Ranged; use ruff_text_size::Ranged;
use crate::checkers::ast::Checker; use crate::checkers::ast::Checker;
use crate::registry::AsRule;
use crate::rules::flake8_comprehensions::fixes; use crate::rules::flake8_comprehensions::fixes;
use super::helpers; use super::helpers;
@ -95,15 +95,9 @@ pub(crate) fn unnecessary_literal_within_tuple_call(
}, },
expr.range(), expr.range(),
); );
if checker.patch(diagnostic.kind.rule()) { diagnostic.try_set_fix(|| {
diagnostic.try_set_fix(|| { fixes::fix_unnecessary_literal_within_tuple_call(expr, checker.locator(), checker.stylist())
fixes::fix_unnecessary_literal_within_tuple_call(
expr,
checker.locator(),
checker.stylist(),
)
.map(Fix::unsafe_edit) .map(Fix::unsafe_edit)
}); });
}
checker.diagnostics.push(diagnostic); checker.diagnostics.push(diagnostic);
} }

View file

@ -9,7 +9,7 @@ use ruff_python_ast::{self as ast, Arguments, Expr, ExprContext, Parameters, Stm
use ruff_text_size::Ranged; use ruff_text_size::Ranged;
use crate::checkers::ast::Checker; use crate::checkers::ast::Checker;
use crate::registry::AsRule;
use crate::rules::flake8_comprehensions::fixes; use crate::rules::flake8_comprehensions::fixes;
use super::helpers; use super::helpers;
@ -221,18 +221,16 @@ pub(crate) fn unnecessary_map(
}; };
let mut diagnostic = Diagnostic::new(UnnecessaryMap { object_type }, expr.range()); let mut diagnostic = Diagnostic::new(UnnecessaryMap { object_type }, expr.range());
if checker.patch(diagnostic.kind.rule()) { diagnostic.try_set_fix(|| {
diagnostic.try_set_fix(|| { fixes::fix_unnecessary_map(
fixes::fix_unnecessary_map( expr,
expr, parent,
parent, object_type,
object_type, checker.locator(),
checker.locator(), checker.stylist(),
checker.stylist(), )
) .map(Fix::unsafe_edit)
.map(Fix::unsafe_edit) });
});
}
checker.diagnostics.push(diagnostic); checker.diagnostics.push(diagnostic);
} }

View file

@ -7,7 +7,7 @@ use ruff_python_ast::whitespace;
use ruff_python_codegen::{Generator, Stylist}; use ruff_python_codegen::{Generator, Stylist};
use crate::checkers::ast::Checker; use crate::checkers::ast::Checker;
use crate::registry::{AsRule, Rule}; use crate::registry::Rule;
/// ## What it does /// ## What it does
/// Checks for the use of string literals in exception constructors. /// Checks for the use of string literals in exception constructors.
@ -190,30 +190,6 @@ pub(crate) fn string_in_exception(checker: &mut Checker, stmt: &Stmt, exc: &Expr
if string.len() >= checker.settings.flake8_errmsg.max_string_length { if string.len() >= checker.settings.flake8_errmsg.max_string_length {
let mut diagnostic = let mut diagnostic =
Diagnostic::new(RawStringInException, first.range()); Diagnostic::new(RawStringInException, first.range());
if checker.patch(diagnostic.kind.rule()) {
if let Some(indentation) =
whitespace::indentation(checker.locator(), stmt)
{
if checker.semantic().is_available("msg") {
diagnostic.set_fix(generate_fix(
stmt,
first,
indentation,
checker.stylist(),
checker.generator(),
));
}
}
}
checker.diagnostics.push(diagnostic);
}
}
}
// Check for f-strings.
Expr::FString(_) => {
if checker.enabled(Rule::FStringInException) {
let mut diagnostic = Diagnostic::new(FStringInException, first.range());
if checker.patch(diagnostic.kind.rule()) {
if let Some(indentation) = if let Some(indentation) =
whitespace::indentation(checker.locator(), stmt) whitespace::indentation(checker.locator(), stmt)
{ {
@ -227,6 +203,25 @@ pub(crate) fn string_in_exception(checker: &mut Checker, stmt: &Stmt, exc: &Expr
)); ));
} }
} }
checker.diagnostics.push(diagnostic);
}
}
}
// Check for f-strings.
Expr::FString(_) => {
if checker.enabled(Rule::FStringInException) {
let mut diagnostic = Diagnostic::new(FStringInException, first.range());
if let Some(indentation) = whitespace::indentation(checker.locator(), stmt)
{
if checker.semantic().is_available("msg") {
diagnostic.set_fix(generate_fix(
stmt,
first,
indentation,
checker.stylist(),
checker.generator(),
));
}
} }
checker.diagnostics.push(diagnostic); checker.diagnostics.push(diagnostic);
} }
@ -240,19 +235,17 @@ pub(crate) fn string_in_exception(checker: &mut Checker, stmt: &Stmt, exc: &Expr
if attr == "format" && value.is_constant_expr() { if attr == "format" && value.is_constant_expr() {
let mut diagnostic = let mut diagnostic =
Diagnostic::new(DotFormatInException, first.range()); Diagnostic::new(DotFormatInException, first.range());
if checker.patch(diagnostic.kind.rule()) { if let Some(indentation) =
if let Some(indentation) = whitespace::indentation(checker.locator(), stmt)
whitespace::indentation(checker.locator(), stmt) {
{ if checker.semantic().is_available("msg") {
if checker.semantic().is_available("msg") { diagnostic.set_fix(generate_fix(
diagnostic.set_fix(generate_fix( stmt,
stmt, first,
first, indentation,
indentation, checker.stylist(),
checker.stylist(), checker.generator(),
checker.generator(), ));
));
}
} }
} }
checker.diagnostics.push(diagnostic); checker.diagnostics.push(diagnostic);

View file

@ -12,7 +12,6 @@ pub(crate) use shebang_not_executable::*;
pub(crate) use shebang_not_first_line::*; pub(crate) use shebang_not_first_line::*;
use crate::comments::shebang::ShebangDirective; use crate::comments::shebang::ShebangDirective;
use crate::settings::LinterSettings;
mod shebang_leading_whitespace; mod shebang_leading_whitespace;
mod shebang_missing_executable_file; mod shebang_missing_executable_file;
@ -24,7 +23,6 @@ pub(crate) fn from_tokens(
tokens: &[LexResult], tokens: &[LexResult],
path: &Path, path: &Path,
locator: &Locator, locator: &Locator,
settings: &LinterSettings,
diagnostics: &mut Vec<Diagnostic>, diagnostics: &mut Vec<Diagnostic>,
) { ) {
let mut has_any_shebang = false; let mut has_any_shebang = false;
@ -41,7 +39,7 @@ pub(crate) fn from_tokens(
diagnostics.push(diagnostic); diagnostics.push(diagnostic);
} }
if let Some(diagnostic) = shebang_leading_whitespace(*range, locator, settings) { if let Some(diagnostic) = shebang_leading_whitespace(*range, locator) {
diagnostics.push(diagnostic); diagnostics.push(diagnostic);
} }

View file

@ -5,9 +5,6 @@ use ruff_macros::{derive_message_formats, violation};
use ruff_python_trivia::is_python_whitespace; use ruff_python_trivia::is_python_whitespace;
use ruff_source_file::Locator; use ruff_source_file::Locator;
use crate::registry::AsRule;
use crate::settings::LinterSettings;
/// ## What it does /// ## What it does
/// Checks for whitespace before a shebang directive. /// Checks for whitespace before a shebang directive.
/// ///
@ -50,7 +47,6 @@ impl AlwaysFixableViolation for ShebangLeadingWhitespace {
pub(crate) fn shebang_leading_whitespace( pub(crate) fn shebang_leading_whitespace(
range: TextRange, range: TextRange,
locator: &Locator, locator: &Locator,
settings: &LinterSettings,
) -> Option<Diagnostic> { ) -> Option<Diagnostic> {
// If the shebang is at the beginning of the file, abort. // If the shebang is at the beginning of the file, abort.
if range.start() == TextSize::from(0) { if range.start() == TextSize::from(0) {
@ -68,8 +64,6 @@ pub(crate) fn shebang_leading_whitespace(
let prefix = TextRange::up_to(range.start()); let prefix = TextRange::up_to(range.start());
let mut diagnostic = Diagnostic::new(ShebangLeadingWhitespace, prefix); let mut diagnostic = Diagnostic::new(ShebangLeadingWhitespace, prefix);
if settings.rules.should_fix(diagnostic.kind.rule()) { diagnostic.set_fix(Fix::safe_edit(Edit::range_deletion(prefix)));
diagnostic.set_fix(Fix::safe_edit(Edit::range_deletion(prefix)));
}
Some(diagnostic) Some(diagnostic)
} }

View file

@ -9,7 +9,6 @@ use ruff_python_parser::Tok;
use ruff_source_file::Locator; use ruff_source_file::Locator;
use ruff_text_size::{Ranged, TextRange}; use ruff_text_size::{Ranged, TextRange};
use crate::registry::AsRule;
use crate::settings::LinterSettings; use crate::settings::LinterSettings;
/// ## What it does /// ## What it does
@ -137,10 +136,8 @@ pub(crate) fn implicit(
TextRange::new(a_range.start(), b_range.end()), TextRange::new(a_range.start(), b_range.end()),
); );
if settings.rules.should_fix(diagnostic.kind.rule()) { if let Some(fix) = concatenate_strings(a_range, b_range, locator) {
if let Some(fix) = concatenate_strings(a_range, b_range, locator) { diagnostic.set_fix(fix);
diagnostic.set_fix(fix);
}
} }
diagnostics.push(diagnostic); diagnostics.push(diagnostic);

View file

@ -6,7 +6,7 @@ use ruff_python_semantic::{Binding, Imported};
use ruff_text_size::Ranged; use ruff_text_size::Ranged;
use crate::checkers::ast::Checker; use crate::checkers::ast::Checker;
use crate::registry::AsRule;
use crate::renamer::Renamer; use crate::renamer::Renamer;
/// ## What it does /// ## What it does
@ -79,16 +79,14 @@ pub(crate) fn unconventional_import_alias(
}, },
binding.range(), binding.range(),
); );
if checker.patch(diagnostic.kind.rule()) { if !import.is_submodule_import() {
if !import.is_submodule_import() { if checker.semantic().is_available(expected_alias) {
if checker.semantic().is_available(expected_alias) { diagnostic.try_set_fix(|| {
diagnostic.try_set_fix(|| { let scope = &checker.semantic().scopes[binding.scope];
let scope = &checker.semantic().scopes[binding.scope]; let (edit, rest) =
let (edit, rest) = Renamer::rename(name, expected_alias, scope, checker.semantic())?;
Renamer::rename(name, expected_alias, scope, checker.semantic())?; Ok(Fix::unsafe_edits(edit, rest))
Ok(Fix::unsafe_edits(edit, rest)) });
});
}
} }
} }
Some(diagnostic) Some(diagnostic)

View file

@ -5,7 +5,6 @@ use ruff_text_size::Ranged;
use crate::checkers::ast::Checker; use crate::checkers::ast::Checker;
use crate::importer::ImportRequest; use crate::importer::ImportRequest;
use crate::registry::AsRule;
/// ## What it does /// ## What it does
/// Checks for direct instantiation of `logging.Logger`, as opposed to using /// Checks for direct instantiation of `logging.Logger`, as opposed to using
@ -61,17 +60,15 @@ pub(crate) fn direct_logger_instantiation(checker: &mut Checker, call: &ast::Exp
.is_some_and(|call_path| matches!(call_path.as_slice(), ["logging", "Logger"])) .is_some_and(|call_path| matches!(call_path.as_slice(), ["logging", "Logger"]))
{ {
let mut diagnostic = Diagnostic::new(DirectLoggerInstantiation, call.func.range()); let mut diagnostic = Diagnostic::new(DirectLoggerInstantiation, call.func.range());
if checker.patch(diagnostic.kind.rule()) { diagnostic.try_set_fix(|| {
diagnostic.try_set_fix(|| { let (import_edit, binding) = checker.importer().get_or_import_symbol(
let (import_edit, binding) = checker.importer().get_or_import_symbol( &ImportRequest::import("logging", "getLogger"),
&ImportRequest::import("logging", "getLogger"), call.func.start(),
call.func.start(), checker.semantic(),
checker.semantic(), )?;
)?; let reference_edit = Edit::range_replacement(binding, call.func.range());
let reference_edit = Edit::range_replacement(binding, call.func.range()); Ok(Fix::unsafe_edits(import_edit, [reference_edit]))
Ok(Fix::unsafe_edits(import_edit, [reference_edit])) });
});
}
checker.diagnostics.push(diagnostic); checker.diagnostics.push(diagnostic);
} }
} }

View file

@ -4,7 +4,6 @@ use ruff_python_ast::{self as ast, Expr};
use ruff_text_size::Ranged; use ruff_text_size::Ranged;
use crate::checkers::ast::Checker; use crate::checkers::ast::Checker;
use crate::registry::AsRule;
/// ## What it does /// ## What it does
/// Checks for any usage of `__cached__` and `__file__` as an argument to /// Checks for any usage of `__cached__` and `__file__` as an argument to
@ -80,13 +79,11 @@ pub(crate) fn invalid_get_logger_argument(checker: &mut Checker, call: &ast::Exp
} }
let mut diagnostic = Diagnostic::new(InvalidGetLoggerArgument, expr.range()); let mut diagnostic = Diagnostic::new(InvalidGetLoggerArgument, expr.range());
if checker.patch(diagnostic.kind.rule()) { if checker.semantic().is_builtin("__name__") {
if checker.semantic().is_builtin("__name__") { diagnostic.set_fix(Fix::unsafe_edit(Edit::range_replacement(
diagnostic.set_fix(Fix::unsafe_edit(Edit::range_replacement( "__name__".to_string(),
"__name__".to_string(), expr.range(),
expr.range(), )));
)));
}
} }
checker.diagnostics.push(diagnostic); checker.diagnostics.push(diagnostic);
} }

View file

@ -6,7 +6,6 @@ use ruff_text_size::Ranged;
use crate::checkers::ast::Checker; use crate::checkers::ast::Checker;
use crate::importer::ImportRequest; use crate::importer::ImportRequest;
use crate::registry::AsRule;
/// ## What it does /// ## What it does
/// Checks for uses of `logging.WARN`. /// Checks for uses of `logging.WARN`.
@ -55,17 +54,15 @@ pub(crate) fn undocumented_warn(checker: &mut Checker, expr: &Expr) {
.is_some_and(|call_path| matches!(call_path.as_slice(), ["logging", "WARN"])) .is_some_and(|call_path| matches!(call_path.as_slice(), ["logging", "WARN"]))
{ {
let mut diagnostic = Diagnostic::new(UndocumentedWarn, expr.range()); let mut diagnostic = Diagnostic::new(UndocumentedWarn, expr.range());
if checker.patch(diagnostic.kind.rule()) { diagnostic.try_set_fix(|| {
diagnostic.try_set_fix(|| { let (import_edit, binding) = checker.importer().get_or_import_symbol(
let (import_edit, binding) = checker.importer().get_or_import_symbol( &ImportRequest::import("logging", "WARNING"),
&ImportRequest::import("logging", "WARNING"), expr.start(),
expr.start(), checker.semantic(),
checker.semantic(), )?;
)?; let reference_edit = Edit::range_replacement(binding, expr.range());
let reference_edit = Edit::range_replacement(binding, expr.range()); Ok(Fix::safe_edits(import_edit, [reference_edit]))
Ok(Fix::safe_edits(import_edit, [reference_edit])) });
});
}
checker.diagnostics.push(diagnostic); checker.diagnostics.push(diagnostic);
} }
} }

View file

@ -5,7 +5,7 @@ use ruff_python_stdlib::logging::LoggingLevel;
use ruff_text_size::Ranged; use ruff_text_size::Ranged;
use crate::checkers::ast::Checker; use crate::checkers::ast::Checker;
use crate::registry::{AsRule, Rule}; use crate::registry::Rule;
use crate::rules::flake8_logging_format::violations::{ use crate::rules::flake8_logging_format::violations::{
LoggingExcInfo, LoggingExtraAttrClash, LoggingFString, LoggingPercentFormat, LoggingExcInfo, LoggingExtraAttrClash, LoggingFString, LoggingPercentFormat,
LoggingRedundantExcInfo, LoggingStringConcat, LoggingStringFormat, LoggingWarn, LoggingRedundantExcInfo, LoggingStringConcat, LoggingStringFormat, LoggingWarn,
@ -196,12 +196,10 @@ pub(crate) fn logging_call(checker: &mut Checker, call: &ast::ExprCall) {
LoggingCallType::LevelCall(LoggingLevel::Warn) LoggingCallType::LevelCall(LoggingLevel::Warn)
) { ) {
let mut diagnostic = Diagnostic::new(LoggingWarn, range); let mut diagnostic = Diagnostic::new(LoggingWarn, range);
if checker.patch(diagnostic.kind.rule()) { diagnostic.set_fix(Fix::safe_edit(Edit::range_replacement(
diagnostic.set_fix(Fix::safe_edit(Edit::range_replacement( "warning".to_string(),
"warning".to_string(), range,
range, )));
)));
}
checker.diagnostics.push(diagnostic); checker.diagnostics.push(diagnostic);
} }
} }

View file

@ -8,7 +8,6 @@ use ruff_text_size::Ranged;
use crate::checkers::ast::Checker; use crate::checkers::ast::Checker;
use crate::fix; use crate::fix;
use crate::registry::AsRule;
/// ## What it does /// ## What it does
/// Checks for duplicate field definitions in classes. /// Checks for duplicate field definitions in classes.
@ -79,13 +78,11 @@ pub(crate) fn duplicate_class_field_definition(checker: &mut Checker, body: &[St
}, },
stmt.range(), stmt.range(),
); );
if checker.patch(diagnostic.kind.rule()) { let edit =
let edit = fix::edits::delete_stmt(stmt, Some(stmt), checker.locator(), checker.indexer());
fix::edits::delete_stmt(stmt, Some(stmt), checker.locator(), checker.indexer()); diagnostic.set_fix(Fix::unsafe_edit(edit).isolate(Checker::isolation(
diagnostic.set_fix(Fix::unsafe_edit(edit).isolate(Checker::isolation( checker.semantic().current_statement_id(),
checker.semantic().current_statement_id(), )));
)));
}
checker.diagnostics.push(diagnostic); checker.diagnostics.push(diagnostic);
} }
} }

View file

@ -12,7 +12,6 @@ use ruff_diagnostics::{Diagnostic, Edit, Fix};
use ruff_macros::{derive_message_formats, violation}; use ruff_macros::{derive_message_formats, violation};
use crate::checkers::ast::Checker; use crate::checkers::ast::Checker;
use crate::registry::AsRule;
/// ## What it does /// ## What it does
/// Checks for `startswith` or `endswith` calls on the same value with /// Checks for `startswith` or `endswith` calls on the same value with
@ -115,92 +114,90 @@ pub(crate) fn multiple_starts_ends_with(checker: &mut Checker, expr: &Expr) {
}, },
expr.range(), expr.range(),
); );
if checker.patch(diagnostic.kind.rule()) { let words: Vec<&Expr> = indices
let words: Vec<&Expr> = indices .iter()
.map(|index| &values[*index])
.map(|expr| {
let Expr::Call(ast::ExprCall {
func: _,
arguments:
Arguments {
args,
keywords: _,
range: _,
},
range: _,
}) = expr
else {
unreachable!(
"{}",
format!("Indices should only contain `{attr_name}` calls")
)
};
args.get(0)
.unwrap_or_else(|| panic!("`{attr_name}` should have one argument"))
})
.collect();
let node = Expr::Tuple(ast::ExprTuple {
elts: words
.iter() .iter()
.map(|index| &values[*index]) .flat_map(|value| {
.map(|expr| { if let Expr::Tuple(ast::ExprTuple { elts, .. }) = value {
let Expr::Call(ast::ExprCall { Left(elts.iter())
func: _, } else {
arguments: Right(iter::once(*value))
Arguments { }
args,
keywords: _,
range: _,
},
range: _,
}) = expr
else {
unreachable!(
"{}",
format!("Indices should only contain `{attr_name}` calls")
)
};
args.get(0)
.unwrap_or_else(|| panic!("`{attr_name}` should have one argument"))
}) })
.collect(); .map(Clone::clone)
.collect(),
ctx: ExprContext::Load,
range: TextRange::default(),
});
let node1 = Expr::Name(ast::ExprName {
id: arg_name.into(),
ctx: ExprContext::Load,
range: TextRange::default(),
});
let node2 = Expr::Attribute(ast::ExprAttribute {
value: Box::new(node1),
attr: Identifier::new(attr_name.to_string(), TextRange::default()),
ctx: ExprContext::Load,
range: TextRange::default(),
});
let node3 = Expr::Call(ast::ExprCall {
func: Box::new(node2),
arguments: Arguments {
args: vec![node],
keywords: vec![],
range: TextRange::default(),
},
range: TextRange::default(),
});
let call = node3;
let node = Expr::Tuple(ast::ExprTuple { // Generate the combined `BoolOp`.
elts: words let mut call = Some(call);
.iter() let node = Expr::BoolOp(ast::ExprBoolOp {
.flat_map(|value| { op: BoolOp::Or,
if let Expr::Tuple(ast::ExprTuple { elts, .. }) = value { values: values
Left(elts.iter()) .iter()
} else { .enumerate()
Right(iter::once(*value)) .filter_map(|(index, elt)| {
} if indices.contains(&index) {
}) std::mem::take(&mut call)
.map(Clone::clone) } else {
.collect(), Some(elt.clone())
ctx: ExprContext::Load, }
range: TextRange::default(), })
}); .collect(),
let node1 = Expr::Name(ast::ExprName { range: TextRange::default(),
id: arg_name.into(), });
ctx: ExprContext::Load, let bool_op = node;
range: TextRange::default(), diagnostic.set_fix(Fix::unsafe_edit(Edit::range_replacement(
}); checker.generator().expr(&bool_op),
let node2 = Expr::Attribute(ast::ExprAttribute { expr.range(),
value: Box::new(node1), )));
attr: Identifier::new(attr_name.to_string(), TextRange::default()),
ctx: ExprContext::Load,
range: TextRange::default(),
});
let node3 = Expr::Call(ast::ExprCall {
func: Box::new(node2),
arguments: Arguments {
args: vec![node],
keywords: vec![],
range: TextRange::default(),
},
range: TextRange::default(),
});
let call = node3;
// Generate the combined `BoolOp`.
let mut call = Some(call);
let node = Expr::BoolOp(ast::ExprBoolOp {
op: BoolOp::Or,
values: values
.iter()
.enumerate()
.filter_map(|(index, elt)| {
if indices.contains(&index) {
std::mem::take(&mut call)
} else {
Some(elt.clone())
}
})
.collect(),
range: TextRange::default(),
});
let bool_op = node;
diagnostic.set_fix(Fix::unsafe_edit(Edit::range_replacement(
checker.generator().expr(&bool_op),
expr.range(),
)));
}
checker.diagnostics.push(diagnostic); checker.diagnostics.push(diagnostic);
} }
} }

View file

@ -6,7 +6,6 @@ use ruff_macros::{derive_message_formats, violation};
use ruff_text_size::Ranged; use ruff_text_size::Ranged;
use crate::checkers::ast::Checker; use crate::checkers::ast::Checker;
use crate::registry::AsRule;
/// ## What it does /// ## What it does
/// Checks for lambdas that can be replaced with the `list` builtin. /// Checks for lambdas that can be replaced with the `list` builtin.
@ -64,13 +63,11 @@ pub(crate) fn reimplemented_list_builtin(checker: &mut Checker, expr: &ExprLambd
if let Expr::List(ast::ExprList { elts, .. }) = body.as_ref() { if let Expr::List(ast::ExprList { elts, .. }) = body.as_ref() {
if elts.is_empty() { if elts.is_empty() {
let mut diagnostic = Diagnostic::new(ReimplementedListBuiltin, expr.range()); let mut diagnostic = Diagnostic::new(ReimplementedListBuiltin, expr.range());
if checker.patch(diagnostic.kind.rule()) { if checker.semantic().is_builtin("list") {
if checker.semantic().is_builtin("list") { diagnostic.set_fix(Fix::safe_edit(Edit::range_replacement(
diagnostic.set_fix(Fix::safe_edit(Edit::range_replacement( "list".to_string(),
"list".to_string(), expr.range(),
expr.range(), )));
)));
}
} }
checker.diagnostics.push(diagnostic); checker.diagnostics.push(diagnostic);
} }

View file

@ -8,7 +8,6 @@ use ruff_text_size::Ranged;
use ruff_python_stdlib::identifiers::is_identifier; use ruff_python_stdlib::identifiers::is_identifier;
use crate::checkers::ast::Checker; use crate::checkers::ast::Checker;
use crate::registry::AsRule;
/// ## What it does /// ## What it does
/// Checks for unnecessary `dict` kwargs. /// Checks for unnecessary `dict` kwargs.
@ -68,12 +67,10 @@ pub(crate) fn unnecessary_dict_kwargs(checker: &mut Checker, expr: &Expr, kwargs
if matches!(keys.as_slice(), [None]) { if matches!(keys.as_slice(), [None]) {
let mut diagnostic = Diagnostic::new(UnnecessaryDictKwargs, expr.range()); let mut diagnostic = Diagnostic::new(UnnecessaryDictKwargs, expr.range());
if checker.patch(diagnostic.kind.rule()) { diagnostic.set_fix(Fix::safe_edit(Edit::range_replacement(
diagnostic.set_fix(Fix::safe_edit(Edit::range_replacement( format!("**{}", checker.locator().slice(values[0].range())),
format!("**{}", checker.locator().slice(values[0].range())), kw.range(),
kw.range(), )));
)));
}
checker.diagnostics.push(diagnostic); checker.diagnostics.push(diagnostic);
continue; continue;
@ -91,18 +88,16 @@ pub(crate) fn unnecessary_dict_kwargs(checker: &mut Checker, expr: &Expr, kwargs
let mut diagnostic = Diagnostic::new(UnnecessaryDictKwargs, expr.range()); let mut diagnostic = Diagnostic::new(UnnecessaryDictKwargs, expr.range());
if checker.patch(diagnostic.kind.rule()) { diagnostic.set_fix(Fix::safe_edit(Edit::range_replacement(
diagnostic.set_fix(Fix::safe_edit(Edit::range_replacement( kwargs
kwargs .iter()
.iter() .zip(values.iter())
.zip(values.iter()) .map(|(kwarg, value)| {
.map(|(kwarg, value)| { format!("{}={}", kwarg.value, checker.locator().slice(value.range()))
format!("{}={}", kwarg.value, checker.locator().slice(value.range())) })
}) .join(", "),
.join(", "), kw.range(),
kw.range(), )));
)));
}
checker.diagnostics.push(diagnostic); checker.diagnostics.push(diagnostic);
} }

View file

@ -8,7 +8,6 @@ use ruff_text_size::Ranged;
use crate::checkers::ast::Checker; use crate::checkers::ast::Checker;
use crate::fix; use crate::fix;
use crate::registry::AsRule;
/// ## What it does /// ## What it does
/// Checks for unnecessary `pass` statements in functions, classes, and other /// Checks for unnecessary `pass` statements in functions, classes, and other
@ -63,17 +62,14 @@ pub(crate) fn no_unnecessary_pass(checker: &mut Checker, body: &[Stmt]) {
.filter(|stmt| stmt.is_pass_stmt()) .filter(|stmt| stmt.is_pass_stmt())
.for_each(|stmt| { .for_each(|stmt| {
let mut diagnostic = Diagnostic::new(UnnecessaryPass, stmt.range()); let mut diagnostic = Diagnostic::new(UnnecessaryPass, stmt.range());
if checker.patch(diagnostic.kind.rule()) { let edit = if let Some(index) = trailing_comment_start_offset(stmt, checker.locator()) {
let edit = Edit::range_deletion(stmt.range().add_end(index))
if let Some(index) = trailing_comment_start_offset(stmt, checker.locator()) { } else {
Edit::range_deletion(stmt.range().add_end(index)) fix::edits::delete_stmt(stmt, None, checker.locator(), checker.indexer())
} else { };
fix::edits::delete_stmt(stmt, None, checker.locator(), checker.indexer()) diagnostic.set_fix(Fix::safe_edit(edit).isolate(Checker::isolation(
}; checker.semantic().current_statement_id(),
diagnostic.set_fix(Fix::safe_edit(edit).isolate(Checker::isolation( )));
checker.semantic().current_statement_id(),
)));
}
checker.diagnostics.push(diagnostic); checker.diagnostics.push(diagnostic);
}); });
} }

View file

@ -6,7 +6,6 @@ use ruff_text_size::Ranged;
use crate::checkers::ast::Checker; use crate::checkers::ast::Checker;
use crate::fix::edits::{remove_argument, Parentheses}; use crate::fix::edits::{remove_argument, Parentheses};
use crate::registry::AsRule;
/// ## What it does /// ## What it does
/// Checks for `range` calls with an unnecessary `start` argument. /// Checks for `range` calls with an unnecessary `start` argument.
@ -78,16 +77,14 @@ pub(crate) fn unnecessary_range_start(checker: &mut Checker, call: &ast::ExprCal
}; };
let mut diagnostic = Diagnostic::new(UnnecessaryRangeStart, start.range()); let mut diagnostic = Diagnostic::new(UnnecessaryRangeStart, start.range());
if checker.patch(diagnostic.kind.rule()) { diagnostic.try_set_fix(|| {
diagnostic.try_set_fix(|| { remove_argument(
remove_argument( &start,
&start, &call.arguments,
&call.arguments, Parentheses::Preserve,
Parentheses::Preserve, checker.locator().contents(),
checker.locator().contents(), )
) .map(Fix::safe_edit)
.map(Fix::safe_edit) });
});
}
checker.diagnostics.push(diagnostic); checker.diagnostics.push(diagnostic);
} }

View file

@ -5,7 +5,6 @@ use ruff_macros::{derive_message_formats, violation};
use ruff_text_size::Ranged; use ruff_text_size::Ranged;
use crate::checkers::ast::Checker; use crate::checkers::ast::Checker;
use crate::registry::AsRule;
/// ## What it does /// ## What it does
/// Checks for `__eq__` and `__ne__` implementations that use `typing.Any` as /// Checks for `__eq__` and `__ne__` implementations that use `typing.Any` as
@ -78,14 +77,12 @@ pub(crate) fn any_eq_ne_annotation(checker: &mut Checker, name: &str, parameters
}, },
annotation.range(), annotation.range(),
); );
if checker.patch(diagnostic.kind.rule()) { // Ex) `def __eq__(self, obj: Any): ...`
// Ex) `def __eq__(self, obj: Any): ...` if checker.semantic().is_builtin("object") {
if checker.semantic().is_builtin("object") { diagnostic.set_fix(Fix::safe_edit(Edit::range_replacement(
diagnostic.set_fix(Fix::safe_edit(Edit::range_replacement( "object".to_string(),
"object".to_string(), annotation.range(),
annotation.range(), )));
)));
}
} }
checker.diagnostics.push(diagnostic); checker.diagnostics.push(diagnostic);
} }

View file

@ -3,7 +3,7 @@ use rustc_hash::FxHashSet;
use std::collections::HashSet; use std::collections::HashSet;
use crate::checkers::ast::Checker; use crate::checkers::ast::Checker;
use crate::registry::AsRule;
use crate::rules::flake8_pyi::helpers::traverse_union; use crate::rules::flake8_pyi::helpers::traverse_union;
use ruff_diagnostics::{Diagnostic, Edit, Fix, FixAvailability, Violation}; use ruff_diagnostics::{Diagnostic, Edit, Fix, FixAvailability, Violation};
use ruff_macros::{derive_message_formats, violation}; use ruff_macros::{derive_message_formats, violation};
@ -64,19 +64,17 @@ pub(crate) fn duplicate_union_member<'a>(checker: &mut Checker, expr: &'a Expr)
}, },
expr.range(), expr.range(),
); );
if checker.patch(diagnostic.kind.rule()) { // Delete the "|" character as well as the duplicate value by reconstructing the
// Delete the "|" character as well as the duplicate value by reconstructing the // parent without the duplicate.
// parent without the duplicate.
// If the parent node is not a `BinOp` we will not perform a fix // If the parent node is not a `BinOp` we will not perform a fix
if let Some(parent @ Expr::BinOp(ast::ExprBinOp { left, right, .. })) = parent { if let Some(parent @ Expr::BinOp(ast::ExprBinOp { left, right, .. })) = parent {
// Replace the parent with its non-duplicate child. // Replace the parent with its non-duplicate child.
let child = if expr == left.as_ref() { right } else { left }; let child = if expr == left.as_ref() { right } else { left };
diagnostic.set_fix(Fix::safe_edit(Edit::range_replacement( diagnostic.set_fix(Fix::safe_edit(Edit::range_replacement(
checker.locator().slice(child.as_ref()).to_string(), checker.locator().slice(child.as_ref()).to_string(),
parent.range(), parent.range(),
))); )));
}
} }
diagnostics.push(diagnostic); diagnostics.push(diagnostic);
} }

View file

@ -5,7 +5,6 @@ use ruff_text_size::Ranged;
use crate::checkers::ast::Checker; use crate::checkers::ast::Checker;
use crate::fix; use crate::fix;
use crate::registry::AsRule;
/// ## What it does /// ## What it does
/// Removes ellipses (`...`) in otherwise non-empty class bodies. /// Removes ellipses (`...`) in otherwise non-empty class bodies.
@ -63,13 +62,11 @@ pub(crate) fn ellipsis_in_non_empty_class_body(checker: &mut Checker, body: &[St
}) })
) { ) {
let mut diagnostic = Diagnostic::new(EllipsisInNonEmptyClassBody, stmt.range()); let mut diagnostic = Diagnostic::new(EllipsisInNonEmptyClassBody, stmt.range());
if checker.patch(diagnostic.kind.rule()) { let edit =
let edit = fix::edits::delete_stmt(stmt, Some(stmt), checker.locator(), checker.indexer());
fix::edits::delete_stmt(stmt, Some(stmt), checker.locator(), checker.indexer()); diagnostic.set_fix(Fix::safe_edit(edit).isolate(Checker::isolation(
diagnostic.set_fix(Fix::safe_edit(edit).isolate(Checker::isolation( checker.semantic().current_statement_id(),
checker.semantic().current_statement_id(), )));
)));
}
checker.diagnostics.push(diagnostic); checker.diagnostics.push(diagnostic);
} }
} }

View file

@ -13,7 +13,6 @@ use ruff_python_semantic::SemanticModel;
use ruff_text_size::Ranged; use ruff_text_size::Ranged;
use crate::checkers::ast::Checker; use crate::checkers::ast::Checker;
use crate::registry::AsRule;
/// ## What it does /// ## What it does
/// Checks for incorrect function signatures on `__exit__` and `__aexit__` /// Checks for incorrect function signatures on `__exit__` and `__aexit__`
@ -175,13 +174,11 @@ fn check_short_args_list(checker: &mut Checker, parameters: &Parameters, func_ki
annotation.range(), annotation.range(),
); );
if checker.patch(diagnostic.kind.rule()) { if checker.semantic().is_builtin("object") {
if checker.semantic().is_builtin("object") { diagnostic.set_fix(Fix::safe_edit(Edit::range_replacement(
diagnostic.set_fix(Fix::safe_edit(Edit::range_replacement( "object".to_string(),
"object".to_string(), annotation.range(),
annotation.range(), )));
)));
}
} }
checker.diagnostics.push(diagnostic); checker.diagnostics.push(diagnostic);

View file

@ -5,7 +5,6 @@ use ruff_python_ast::{self as ast, Expr, Stmt};
use ruff_text_size::Ranged; use ruff_text_size::Ranged;
use crate::checkers::ast::Checker; use crate::checkers::ast::Checker;
use crate::registry::Rule;
/// ## What it does /// ## What it does
/// Checks for non-empty function stub bodies. /// Checks for non-empty function stub bodies.
@ -69,11 +68,9 @@ pub(crate) fn non_empty_stub_body(checker: &mut Checker, body: &[Stmt]) {
} }
let mut diagnostic = Diagnostic::new(NonEmptyStubBody, stmt.range()); let mut diagnostic = Diagnostic::new(NonEmptyStubBody, stmt.range());
if checker.patch(Rule::NonEmptyStubBody) { diagnostic.set_fix(Fix::safe_edit(Edit::range_replacement(
diagnostic.set_fix(Fix::safe_edit(Edit::range_replacement( format!("..."),
format!("..."), stmt.range(),
stmt.range(), )));
)));
};
checker.diagnostics.push(diagnostic); checker.diagnostics.push(diagnostic);
} }

View file

@ -5,7 +5,6 @@ use ruff_diagnostics::{AlwaysFixableViolation, Diagnostic, Edit, Fix};
use ruff_macros::{derive_message_formats, violation}; use ruff_macros::{derive_message_formats, violation};
use crate::checkers::ast::Checker; use crate::checkers::ast::Checker;
use crate::registry::AsRule;
/// ## What it does /// ## What it does
/// Checks for numeric literals with a string representation longer than ten /// Checks for numeric literals with a string representation longer than ten
@ -50,11 +49,9 @@ pub(crate) fn numeric_literal_too_long(checker: &mut Checker, expr: &Expr) {
} }
let mut diagnostic = Diagnostic::new(NumericLiteralTooLong, expr.range()); let mut diagnostic = Diagnostic::new(NumericLiteralTooLong, expr.range());
if checker.patch(diagnostic.kind.rule()) { diagnostic.set_fix(Fix::safe_edit(Edit::range_replacement(
diagnostic.set_fix(Fix::safe_edit(Edit::range_replacement( "...".to_string(),
"...".to_string(), expr.range(),
expr.range(), )));
)));
}
checker.diagnostics.push(diagnostic); checker.diagnostics.push(diagnostic);
} }

View file

@ -5,7 +5,6 @@ use ruff_text_size::Ranged;
use crate::checkers::ast::Checker; use crate::checkers::ast::Checker;
use crate::fix; use crate::fix;
use crate::registry::AsRule;
/// ## What it does /// ## What it does
/// Checks for the presence of the `pass` statement within a class body /// Checks for the presence of the `pass` statement within a class body
@ -60,13 +59,10 @@ pub(crate) fn pass_in_class_body(checker: &mut Checker, class_def: &ast::StmtCla
} }
let mut diagnostic = Diagnostic::new(PassInClassBody, stmt.range()); let mut diagnostic = Diagnostic::new(PassInClassBody, stmt.range());
if checker.patch(diagnostic.kind.rule()) { let edit = fix::edits::delete_stmt(stmt, Some(stmt), checker.locator(), checker.indexer());
let edit = diagnostic.set_fix(Fix::safe_edit(edit).isolate(Checker::isolation(
fix::edits::delete_stmt(stmt, Some(stmt), checker.locator(), checker.indexer()); checker.semantic().current_statement_id(),
diagnostic.set_fix(Fix::safe_edit(edit).isolate(Checker::isolation( )));
checker.semantic().current_statement_id(),
)));
}
checker.diagnostics.push(diagnostic); checker.diagnostics.push(diagnostic);
} }
} }

View file

@ -4,7 +4,6 @@ use ruff_python_ast::Stmt;
use ruff_text_size::Ranged; use ruff_text_size::Ranged;
use crate::checkers::ast::Checker; use crate::checkers::ast::Checker;
use crate::registry::Rule;
/// ## What it does /// ## What it does
/// Checks for `pass` statements in empty stub bodies. /// Checks for `pass` statements in empty stub bodies.
@ -47,11 +46,9 @@ pub(crate) fn pass_statement_stub_body(checker: &mut Checker, body: &[Stmt]) {
}; };
let mut diagnostic = Diagnostic::new(PassStatementStubBody, pass.range()); let mut diagnostic = Diagnostic::new(PassStatementStubBody, pass.range());
if checker.patch(Rule::PassStatementStubBody) { diagnostic.set_fix(Fix::safe_edit(Edit::range_replacement(
diagnostic.set_fix(Fix::safe_edit(Edit::range_replacement( format!("..."),
format!("..."), pass.range(),
pass.range(), )));
)));
};
checker.diagnostics.push(diagnostic); checker.diagnostics.push(diagnostic);
} }

View file

@ -4,7 +4,6 @@ use ruff_diagnostics::{AlwaysFixableViolation, Diagnostic, Edit, Fix};
use ruff_macros::{derive_message_formats, violation}; use ruff_macros::{derive_message_formats, violation};
use crate::checkers::ast::Checker; use crate::checkers::ast::Checker;
use crate::registry::Rule;
/// ## What it does /// ## What it does
/// Checks for quoted type annotations in stub (`.pyi`) files, which should be avoided. /// Checks for quoted type annotations in stub (`.pyi`) files, which should be avoided.
@ -43,11 +42,9 @@ impl AlwaysFixableViolation for QuotedAnnotationInStub {
/// PYI020 /// PYI020
pub(crate) fn quoted_annotation_in_stub(checker: &mut Checker, annotation: &str, range: TextRange) { pub(crate) fn quoted_annotation_in_stub(checker: &mut Checker, annotation: &str, range: TextRange) {
let mut diagnostic = Diagnostic::new(QuotedAnnotationInStub, range); let mut diagnostic = Diagnostic::new(QuotedAnnotationInStub, range);
if checker.patch(Rule::QuotedAnnotationInStub) { diagnostic.set_fix(Fix::safe_edit(Edit::range_replacement(
diagnostic.set_fix(Fix::safe_edit(Edit::range_replacement( annotation.to_string(),
annotation.to_string(), range,
range, )));
)));
}
checker.diagnostics.push(diagnostic); checker.diagnostics.push(diagnostic);
} }

View file

@ -11,7 +11,7 @@ use ruff_text_size::Ranged;
use crate::checkers::ast::Checker; use crate::checkers::ast::Checker;
use crate::importer::ImportRequest; use crate::importer::ImportRequest;
use crate::registry::AsRule;
use crate::rules::flake8_pyi::rules::TypingModule; use crate::rules::flake8_pyi::rules::TypingModule;
use crate::settings::types::PythonVersion; use crate::settings::types::PythonVersion;
@ -534,12 +534,10 @@ pub(crate) fn typed_argument_simple_defaults(checker: &mut Checker, parameters:
) { ) {
let mut diagnostic = Diagnostic::new(TypedArgumentDefaultInStub, default.range()); let mut diagnostic = Diagnostic::new(TypedArgumentDefaultInStub, default.range());
if checker.patch(diagnostic.kind.rule()) { diagnostic.set_fix(Fix::safe_edit(Edit::range_replacement(
diagnostic.set_fix(Fix::safe_edit(Edit::range_replacement( "...".to_string(),
"...".to_string(), default.range(),
default.range(), )));
)));
}
checker.diagnostics.push(diagnostic); checker.diagnostics.push(diagnostic);
} }
@ -571,12 +569,10 @@ pub(crate) fn argument_simple_defaults(checker: &mut Checker, parameters: &Param
) { ) {
let mut diagnostic = Diagnostic::new(ArgumentDefaultInStub, default.range()); let mut diagnostic = Diagnostic::new(ArgumentDefaultInStub, default.range());
if checker.patch(diagnostic.kind.rule()) { diagnostic.set_fix(Fix::safe_edit(Edit::range_replacement(
diagnostic.set_fix(Fix::safe_edit(Edit::range_replacement( "...".to_string(),
"...".to_string(), default.range(),
default.range(), )));
)));
}
checker.diagnostics.push(diagnostic); checker.diagnostics.push(diagnostic);
} }
@ -606,12 +602,10 @@ pub(crate) fn assignment_default_in_stub(checker: &mut Checker, targets: &[Expr]
} }
let mut diagnostic = Diagnostic::new(AssignmentDefaultInStub, value.range()); let mut diagnostic = Diagnostic::new(AssignmentDefaultInStub, value.range());
if checker.patch(diagnostic.kind.rule()) { diagnostic.set_fix(Fix::safe_edit(Edit::range_replacement(
diagnostic.set_fix(Fix::safe_edit(Edit::range_replacement( "...".to_string(),
"...".to_string(), value.range(),
value.range(), )));
)));
}
checker.diagnostics.push(diagnostic); checker.diagnostics.push(diagnostic);
} }
@ -642,12 +636,10 @@ pub(crate) fn annotated_assignment_default_in_stub(
} }
let mut diagnostic = Diagnostic::new(AssignmentDefaultInStub, value.range()); let mut diagnostic = Diagnostic::new(AssignmentDefaultInStub, value.range());
if checker.patch(diagnostic.kind.rule()) { diagnostic.set_fix(Fix::safe_edit(Edit::range_replacement(
diagnostic.set_fix(Fix::safe_edit(Edit::range_replacement( "...".to_string(),
"...".to_string(), value.range(),
value.range(), )));
)));
}
checker.diagnostics.push(diagnostic); checker.diagnostics.push(diagnostic);
} }
@ -741,18 +733,16 @@ pub(crate) fn type_alias_without_annotation(checker: &mut Checker, value: &Expr,
}, },
target.range(), target.range(),
); );
if checker.patch(diagnostic.kind.rule()) { diagnostic.try_set_fix(|| {
diagnostic.try_set_fix(|| { let (import_edit, binding) = checker.importer().get_or_import_symbol(
let (import_edit, binding) = checker.importer().get_or_import_symbol( &ImportRequest::import(module.as_str(), "TypeAlias"),
&ImportRequest::import(module.as_str(), "TypeAlias"), target.start(),
target.start(), checker.semantic(),
checker.semantic(), )?;
)?; Ok(Fix::safe_edits(
Ok(Fix::safe_edits( Edit::range_replacement(format!("{id}: {binding}"), target.range()),
Edit::range_replacement(format!("{id}: {binding}"), target.range()), [import_edit],
[import_edit], ))
)) });
});
}
checker.diagnostics.push(diagnostic); checker.diagnostics.push(diagnostic);
} }

View file

@ -8,7 +8,6 @@ use ruff_python_semantic::analyze::visibility::is_abstract;
use crate::checkers::ast::Checker; use crate::checkers::ast::Checker;
use crate::fix::edits::delete_stmt; use crate::fix::edits::delete_stmt;
use crate::registry::AsRule;
/// ## What it does /// ## What it does
/// Checks for redundant definitions of `__str__` or `__repr__` in stubs. /// Checks for redundant definitions of `__str__` or `__repr__` in stubs.
@ -95,13 +94,11 @@ pub(crate) fn str_or_repr_defined_in_stub(checker: &mut Checker, stmt: &Stmt) {
}, },
stmt.identifier(), stmt.identifier(),
); );
if checker.patch(diagnostic.kind.rule()) { let stmt = checker.semantic().current_statement();
let stmt = checker.semantic().current_statement(); let parent = checker.semantic().current_statement_parent();
let parent = checker.semantic().current_statement_parent(); let edit = delete_stmt(stmt, parent, checker.locator(), checker.indexer());
let edit = delete_stmt(stmt, parent, checker.locator(), checker.indexer()); diagnostic.set_fix(Fix::safe_edit(edit).isolate(Checker::isolation(
diagnostic.set_fix(Fix::safe_edit(edit).isolate(Checker::isolation( checker.semantic().current_statement_parent_id(),
checker.semantic().current_statement_parent_id(), )));
)));
}
checker.diagnostics.push(diagnostic); checker.diagnostics.push(diagnostic);
} }

View file

@ -6,7 +6,6 @@ use ruff_python_ast::helpers::is_docstring_stmt;
use ruff_text_size::Ranged; use ruff_text_size::Ranged;
use crate::checkers::ast::Checker; use crate::checkers::ast::Checker;
use crate::registry::AsRule;
/// ## What it does /// ## What it does
/// Checks for the use of string and bytes literals longer than 50 characters /// Checks for the use of string and bytes literals longer than 50 characters
@ -67,11 +66,9 @@ pub(crate) fn string_or_bytes_too_long(checker: &mut Checker, expr: &Expr) {
} }
let mut diagnostic = Diagnostic::new(StringOrBytesTooLong, expr.range()); let mut diagnostic = Diagnostic::new(StringOrBytesTooLong, expr.range());
if checker.patch(diagnostic.kind.rule()) { diagnostic.set_fix(Fix::safe_edit(Edit::range_replacement(
diagnostic.set_fix(Fix::safe_edit(Edit::range_replacement( "...".to_string(),
"...".to_string(), expr.range(),
expr.range(), )));
)));
}
checker.diagnostics.push(diagnostic); checker.diagnostics.push(diagnostic);
} }

View file

@ -5,7 +5,7 @@ use ruff_python_semantic::{Binding, BindingKind};
use ruff_text_size::Ranged; use ruff_text_size::Ranged;
use crate::checkers::ast::Checker; use crate::checkers::ast::Checker;
use crate::registry::AsRule;
use crate::renamer::Renamer; use crate::renamer::Renamer;
/// ## What it does /// ## What it does
@ -65,14 +65,12 @@ pub(crate) fn unaliased_collections_abc_set_import(
} }
let mut diagnostic = Diagnostic::new(UnaliasedCollectionsAbcSetImport, binding.range()); let mut diagnostic = Diagnostic::new(UnaliasedCollectionsAbcSetImport, binding.range());
if checker.patch(diagnostic.kind.rule()) { if checker.semantic().is_available("AbstractSet") {
if checker.semantic().is_available("AbstractSet") { diagnostic.try_set_fix(|| {
diagnostic.try_set_fix(|| { let scope = &checker.semantic().scopes[binding.scope];
let scope = &checker.semantic().scopes[binding.scope]; let (edit, rest) = Renamer::rename(name, "AbstractSet", scope, checker.semantic())?;
let (edit, rest) = Renamer::rename(name, "AbstractSet", scope, checker.semantic())?; Ok(Fix::unsafe_edits(edit, rest))
Ok(Fix::unsafe_edits(edit, rest)) });
});
}
} }
Some(diagnostic) Some(diagnostic)
} }

View file

@ -4,7 +4,7 @@ use ruff_python_ast::{self as ast, Expr};
use ruff_text_size::Ranged; use ruff_text_size::Ranged;
use crate::checkers::ast::Checker; use crate::checkers::ast::Checker;
use crate::registry::AsRule;
use crate::rules::flake8_pyi::helpers::traverse_union; use crate::rules::flake8_pyi::helpers::traverse_union;
/// ## What it does /// ## What it does
@ -76,12 +76,10 @@ pub(crate) fn unnecessary_literal_union<'a>(checker: &mut Checker, expr: &'a Exp
expr.range(), expr.range(),
); );
if checker.patch(diagnostic.kind.rule()) { diagnostic.set_fix(Fix::safe_edit(Edit::range_replacement(
diagnostic.set_fix(Fix::safe_edit(Edit::range_replacement( format!("Literal[{}]", literal_members.join(", ")),
format!("Literal[{}]", literal_members.join(", ")), expr.range(),
expr.range(), )));
)));
}
checker.diagnostics.push(diagnostic); checker.diagnostics.push(diagnostic);
} }

View file

@ -26,7 +26,6 @@ use crate::cst::matchers::match_indented_block;
use crate::cst::matchers::match_module; use crate::cst::matchers::match_module;
use crate::fix::codemods::CodegenStylist; use crate::fix::codemods::CodegenStylist;
use crate::importer::ImportRequest; use crate::importer::ImportRequest;
use crate::registry::AsRule;
use super::unittest_assert::UnittestAssert; use super::unittest_assert::UnittestAssert;
@ -284,25 +283,23 @@ pub(crate) fn unittest_assertion(
}, },
func.range(), func.range(),
); );
if checker.patch(diagnostic.kind.rule()) { // We're converting an expression to a statement, so avoid applying the fix if
// We're converting an expression to a statement, so avoid applying the fix if // the assertion is part of a larger expression.
// the assertion is part of a larger expression. if checker.semantic().current_statement().is_expr_stmt()
if checker.semantic().current_statement().is_expr_stmt() && checker.semantic().current_expression_parent().is_none()
&& checker.semantic().current_expression_parent().is_none() && !checker.indexer().comment_ranges().intersects(expr.range())
&& !checker.indexer().comment_ranges().intersects(expr.range()) {
{ if let Ok(stmt) = unittest_assert.generate_assert(args, keywords) {
if let Ok(stmt) = unittest_assert.generate_assert(args, keywords) { diagnostic.set_fix(Fix::unsafe_edit(Edit::range_replacement(
diagnostic.set_fix(Fix::unsafe_edit(Edit::range_replacement( checker.generator().stmt(&stmt),
checker.generator().stmt(&stmt), parenthesized_range(
parenthesized_range( expr.into(),
expr.into(), checker.semantic().current_statement().into(),
checker.semantic().current_statement().into(), checker.indexer().comment_ranges(),
checker.indexer().comment_ranges(), checker.locator().contents(),
checker.locator().contents(), )
) .unwrap_or(expr.range()),
.unwrap_or(expr.range()), )));
)));
}
} }
} }
Some(diagnostic) Some(diagnostic)
@ -390,9 +387,7 @@ pub(crate) fn unittest_raises_assertion(
}, },
call.func.range(), call.func.range(),
); );
if checker.patch(diagnostic.kind.rule()) if !checker.indexer().has_comments(call, checker.locator()) {
&& !checker.indexer().has_comments(call, checker.locator())
{
if let Some(args) = to_pytest_raises_args(checker, attr.as_str(), &call.arguments) { if let Some(args) = to_pytest_raises_args(checker, attr.as_str(), &call.arguments) {
diagnostic.try_set_fix(|| { diagnostic.try_set_fix(|| {
let (import_edit, binding) = checker.importer().get_or_import_symbol( let (import_edit, binding) = checker.importer().get_or_import_symbol(
@ -746,19 +741,17 @@ pub(crate) fn composite_condition(
let composite = is_composite_condition(test); let composite = is_composite_condition(test);
if matches!(composite, CompositionKind::Simple | CompositionKind::Mixed) { if matches!(composite, CompositionKind::Simple | CompositionKind::Mixed) {
let mut diagnostic = Diagnostic::new(PytestCompositeAssertion, stmt.range()); let mut diagnostic = Diagnostic::new(PytestCompositeAssertion, stmt.range());
if checker.patch(diagnostic.kind.rule()) { if matches!(composite, CompositionKind::Simple)
if matches!(composite, CompositionKind::Simple) && msg.is_none()
&& msg.is_none() && !checker.indexer().comment_ranges().intersects(stmt.range())
&& !checker.indexer().comment_ranges().intersects(stmt.range()) && !checker
&& !checker .indexer()
.indexer() .in_multi_statement_line(stmt, checker.locator())
.in_multi_statement_line(stmt, checker.locator()) {
{ diagnostic.try_set_fix(|| {
diagnostic.try_set_fix(|| { fix_composite_condition(stmt, checker.locator(), checker.stylist())
fix_composite_condition(stmt, checker.locator(), checker.stylist()) .map(Fix::unsafe_edit)
.map(Fix::unsafe_edit) });
});
}
} }
checker.diagnostics.push(diagnostic); checker.diagnostics.push(diagnostic);
} }

View file

@ -16,7 +16,7 @@ use ruff_text_size::{TextLen, TextRange};
use crate::checkers::ast::Checker; use crate::checkers::ast::Checker;
use crate::fix::edits; use crate::fix::edits;
use crate::registry::{AsRule, Rule}; use crate::registry::Rule;
use super::helpers::{ use super::helpers::{
get_mark_decorators, is_pytest_fixture, is_pytest_yield_fixture, keyword_is_literal, get_mark_decorators, is_pytest_fixture, is_pytest_yield_fixture, keyword_is_literal,
@ -681,9 +681,7 @@ fn pytest_fixture_parentheses(
PytestFixtureIncorrectParenthesesStyle { expected, actual }, PytestFixtureIncorrectParenthesesStyle { expected, actual },
decorator.range(), decorator.range(),
); );
if checker.patch(diagnostic.kind.rule()) { diagnostic.set_fix(fix);
diagnostic.set_fix(fix);
}
checker.diagnostics.push(diagnostic); checker.diagnostics.push(diagnostic);
} }
@ -727,17 +725,15 @@ fn check_fixture_decorator(checker: &mut Checker, func_name: &str, decorator: &D
if keyword_is_literal(keyword, "function") { if keyword_is_literal(keyword, "function") {
let mut diagnostic = let mut diagnostic =
Diagnostic::new(PytestExtraneousScopeFunction, keyword.range()); Diagnostic::new(PytestExtraneousScopeFunction, keyword.range());
if checker.patch(diagnostic.kind.rule()) { diagnostic.try_set_fix(|| {
diagnostic.try_set_fix(|| { edits::remove_argument(
edits::remove_argument( keyword,
keyword, arguments,
arguments, edits::Parentheses::Preserve,
edits::Parentheses::Preserve, checker.locator().contents(),
checker.locator().contents(), )
) .map(Fix::unsafe_edit)
.map(Fix::unsafe_edit) });
});
}
checker.diagnostics.push(diagnostic); checker.diagnostics.push(diagnostic);
} }
} }
@ -819,30 +815,28 @@ fn check_fixture_returns(
}, },
stmt.range(), stmt.range(),
); );
if checker.patch(diagnostic.kind.rule()) { let yield_edit = Edit::range_replacement(
let yield_edit = Edit::range_replacement( "return".to_string(),
"return".to_string(), TextRange::at(stmt.start(), "yield".text_len()),
TextRange::at(stmt.start(), "yield".text_len()), );
); let return_type_edit = returns.and_then(|returns| {
let return_type_edit = returns.and_then(|returns| { let ast::ExprSubscript { value, slice, .. } = returns.as_subscript_expr()?;
let ast::ExprSubscript { value, slice, .. } = returns.as_subscript_expr()?; let ast::ExprTuple { elts, .. } = slice.as_tuple_expr()?;
let ast::ExprTuple { elts, .. } = slice.as_tuple_expr()?; let [first, ..] = elts.as_slice() else {
let [first, ..] = elts.as_slice() else { return None;
return None; };
}; if !checker.semantic().match_typing_expr(value, "Generator") {
if !checker.semantic().match_typing_expr(value, "Generator") { return None;
return None;
}
Some(Edit::range_replacement(
checker.generator().expr(first),
returns.range(),
))
});
if let Some(return_type_edit) = return_type_edit {
diagnostic.set_fix(Fix::safe_edits(yield_edit, [return_type_edit]));
} else {
diagnostic.set_fix(Fix::safe_edit(yield_edit));
} }
Some(Edit::range_replacement(
checker.generator().expr(first),
returns.range(),
))
});
if let Some(return_type_edit) = return_type_edit {
diagnostic.set_fix(Fix::safe_edits(yield_edit, [return_type_edit]));
} else {
diagnostic.set_fix(Fix::safe_edit(yield_edit));
} }
checker.diagnostics.push(diagnostic); checker.diagnostics.push(diagnostic);
} }
@ -912,10 +906,8 @@ fn check_fixture_marks(checker: &mut Checker, decorators: &[Decorator]) {
if *name == "asyncio" { if *name == "asyncio" {
let mut diagnostic = let mut diagnostic =
Diagnostic::new(PytestUnnecessaryAsyncioMarkOnFixture, expr.range()); Diagnostic::new(PytestUnnecessaryAsyncioMarkOnFixture, expr.range());
if checker.patch(diagnostic.kind.rule()) { let range = checker.locator().full_lines_range(expr.range());
let range = checker.locator().full_lines_range(expr.range()); diagnostic.set_fix(Fix::safe_edit(Edit::range_deletion(range)));
diagnostic.set_fix(Fix::safe_edit(Edit::range_deletion(range)));
}
checker.diagnostics.push(diagnostic); checker.diagnostics.push(diagnostic);
} }
} }
@ -924,10 +916,8 @@ fn check_fixture_marks(checker: &mut Checker, decorators: &[Decorator]) {
if *name == "usefixtures" { if *name == "usefixtures" {
let mut diagnostic = let mut diagnostic =
Diagnostic::new(PytestErroneousUseFixturesOnFixture, expr.range()); Diagnostic::new(PytestErroneousUseFixturesOnFixture, expr.range());
if checker.patch(diagnostic.kind.rule()) { let line_range = checker.locator().full_lines_range(expr.range());
let line_range = checker.locator().full_lines_range(expr.range()); diagnostic.set_fix(Fix::safe_edit(Edit::range_deletion(line_range)));
diagnostic.set_fix(Fix::safe_edit(Edit::range_deletion(line_range)));
}
checker.diagnostics.push(diagnostic); checker.diagnostics.push(diagnostic);
} }
} }

View file

@ -6,7 +6,7 @@ use ruff_python_ast::call_path::CallPath;
use ruff_text_size::Ranged; use ruff_text_size::Ranged;
use crate::checkers::ast::Checker; use crate::checkers::ast::Checker;
use crate::registry::{AsRule, Rule}; use crate::registry::Rule;
use super::helpers::get_mark_decorators; use super::helpers::get_mark_decorators;
@ -130,9 +130,7 @@ fn pytest_mark_parentheses(
}, },
decorator.range(), decorator.range(),
); );
if checker.patch(diagnostic.kind.rule()) { diagnostic.set_fix(fix);
diagnostic.set_fix(fix);
}
checker.diagnostics.push(diagnostic); checker.diagnostics.push(diagnostic);
} }
@ -184,9 +182,7 @@ fn check_useless_usefixtures(checker: &mut Checker, decorator: &Decorator, call_
if !has_parameters { if !has_parameters {
let mut diagnostic = Diagnostic::new(PytestUseFixturesWithoutParameters, decorator.range()); let mut diagnostic = Diagnostic::new(PytestUseFixturesWithoutParameters, decorator.range());
if checker.patch(diagnostic.kind.rule()) { diagnostic.set_fix(Fix::unsafe_edit(Edit::range_deletion(decorator.range())));
diagnostic.set_fix(Fix::unsafe_edit(Edit::range_deletion(decorator.range())));
}
checker.diagnostics.push(diagnostic); checker.diagnostics.push(diagnostic);
} }
} }

View file

@ -14,7 +14,7 @@ use ruff_python_trivia::{SimpleTokenKind, SimpleTokenizer};
use ruff_text_size::{Ranged, TextRange, TextSize}; use ruff_text_size::{Ranged, TextRange, TextSize};
use crate::checkers::ast::Checker; use crate::checkers::ast::Checker;
use crate::registry::{AsRule, Rule}; use crate::registry::Rule;
use super::super::types; use super::super::types;
use super::helpers::{is_pytest_parametrize, split_names}; use super::helpers::{is_pytest_parametrize, split_names};
@ -338,25 +338,23 @@ fn check_names(checker: &mut Checker, decorator: &Decorator, expr: &Expr) {
}, },
name_range, name_range,
); );
if checker.patch(diagnostic.kind.rule()) { let node = Expr::Tuple(ast::ExprTuple {
let node = Expr::Tuple(ast::ExprTuple { elts: names
elts: names .iter()
.iter() .map(|name| {
.map(|name| { Expr::Constant(ast::ExprConstant {
Expr::Constant(ast::ExprConstant { value: (*name).to_string().into(),
value: (*name).to_string().into(), range: TextRange::default(),
range: TextRange::default(),
})
}) })
.collect(), })
ctx: ExprContext::Load, .collect(),
range: TextRange::default(), ctx: ExprContext::Load,
}); range: TextRange::default(),
diagnostic.set_fix(Fix::unsafe_edit(Edit::range_replacement( });
format!("({})", checker.generator().expr(&node)), diagnostic.set_fix(Fix::unsafe_edit(Edit::range_replacement(
name_range, format!("({})", checker.generator().expr(&node)),
))); name_range,
} )));
checker.diagnostics.push(diagnostic); checker.diagnostics.push(diagnostic);
} }
types::ParametrizeNameType::List => { types::ParametrizeNameType::List => {
@ -373,25 +371,23 @@ fn check_names(checker: &mut Checker, decorator: &Decorator, expr: &Expr) {
}, },
name_range, name_range,
); );
if checker.patch(diagnostic.kind.rule()) { let node = Expr::List(ast::ExprList {
let node = Expr::List(ast::ExprList { elts: names
elts: names .iter()
.iter() .map(|name| {
.map(|name| { Expr::Constant(ast::ExprConstant {
Expr::Constant(ast::ExprConstant { value: (*name).to_string().into(),
value: (*name).to_string().into(), range: TextRange::default(),
range: TextRange::default(),
})
}) })
.collect(), })
ctx: ExprContext::Load, .collect(),
range: TextRange::default(), ctx: ExprContext::Load,
}); range: TextRange::default(),
diagnostic.set_fix(Fix::unsafe_edit(Edit::range_replacement( });
checker.generator().expr(&node), diagnostic.set_fix(Fix::unsafe_edit(Edit::range_replacement(
name_range, checker.generator().expr(&node),
))); name_range,
} )));
checker.diagnostics.push(diagnostic); checker.diagnostics.push(diagnostic);
} }
types::ParametrizeNameType::Csv => {} types::ParametrizeNameType::Csv => {}
@ -413,17 +409,15 @@ fn check_names(checker: &mut Checker, decorator: &Decorator, expr: &Expr) {
}, },
expr.range(), expr.range(),
); );
if checker.patch(diagnostic.kind.rule()) { let node = Expr::List(ast::ExprList {
let node = Expr::List(ast::ExprList { elts: elts.clone(),
elts: elts.clone(), ctx: ExprContext::Load,
ctx: ExprContext::Load, range: TextRange::default(),
range: TextRange::default(), });
}); diagnostic.set_fix(Fix::unsafe_edit(Edit::range_replacement(
diagnostic.set_fix(Fix::unsafe_edit(Edit::range_replacement( checker.generator().expr(&node),
checker.generator().expr(&node), expr.range(),
expr.range(), )));
)));
}
checker.diagnostics.push(diagnostic); checker.diagnostics.push(diagnostic);
} }
types::ParametrizeNameType::Csv => { types::ParametrizeNameType::Csv => {
@ -433,13 +427,11 @@ fn check_names(checker: &mut Checker, decorator: &Decorator, expr: &Expr) {
}, },
expr.range(), expr.range(),
); );
if checker.patch(diagnostic.kind.rule()) { if let Some(content) = elts_to_csv(elts, checker.generator()) {
if let Some(content) = elts_to_csv(elts, checker.generator()) { diagnostic.set_fix(Fix::unsafe_edit(Edit::range_replacement(
diagnostic.set_fix(Fix::unsafe_edit(Edit::range_replacement( content,
content, expr.range(),
expr.range(), )));
)));
}
} }
checker.diagnostics.push(diagnostic); checker.diagnostics.push(diagnostic);
} }
@ -461,17 +453,15 @@ fn check_names(checker: &mut Checker, decorator: &Decorator, expr: &Expr) {
}, },
expr.range(), expr.range(),
); );
if checker.patch(diagnostic.kind.rule()) { let node = Expr::Tuple(ast::ExprTuple {
let node = Expr::Tuple(ast::ExprTuple { elts: elts.clone(),
elts: elts.clone(), ctx: ExprContext::Load,
ctx: ExprContext::Load, range: TextRange::default(),
range: TextRange::default(), });
}); diagnostic.set_fix(Fix::unsafe_edit(Edit::range_replacement(
diagnostic.set_fix(Fix::unsafe_edit(Edit::range_replacement( format!("({})", checker.generator().expr(&node)),
format!("({})", checker.generator().expr(&node)), expr.range(),
expr.range(), )));
)));
}
checker.diagnostics.push(diagnostic); checker.diagnostics.push(diagnostic);
} }
types::ParametrizeNameType::Csv => { types::ParametrizeNameType::Csv => {
@ -481,13 +471,11 @@ fn check_names(checker: &mut Checker, decorator: &Decorator, expr: &Expr) {
}, },
expr.range(), expr.range(),
); );
if checker.patch(diagnostic.kind.rule()) { if let Some(content) = elts_to_csv(elts, checker.generator()) {
if let Some(content) = elts_to_csv(elts, checker.generator()) { diagnostic.set_fix(Fix::unsafe_edit(Edit::range_replacement(
diagnostic.set_fix(Fix::unsafe_edit(Edit::range_replacement( content,
content, expr.range(),
expr.range(), )));
)));
}
} }
checker.diagnostics.push(diagnostic); checker.diagnostics.push(diagnostic);
} }
@ -585,22 +573,19 @@ fn check_duplicates(checker: &mut Checker, values: &Expr) {
PytestDuplicateParametrizeTestCases { index: *index }, PytestDuplicateParametrizeTestCases { index: *index },
element.range(), element.range(),
); );
if checker.patch(diagnostic.kind.rule()) { if let Some(prev) = prev {
if let Some(prev) = prev { let values_end = values.range().end() - TextSize::new(1);
let values_end = values.range().end() - TextSize::new(1); let previous_end =
let previous_end = trailing_comma(prev, checker.locator().contents()) trailing_comma(prev, checker.locator().contents()).unwrap_or(values_end);
.unwrap_or(values_end); let element_end =
let element_end = trailing_comma(element, checker.locator().contents()) trailing_comma(element, checker.locator().contents()).unwrap_or(values_end);
.unwrap_or(values_end); let deletion_range = TextRange::new(previous_end, element_end);
let deletion_range = TextRange::new(previous_end, element_end); if !checker
if !checker .indexer()
.indexer() .comment_ranges()
.comment_ranges() .intersects(deletion_range)
.intersects(deletion_range) {
{ diagnostic.set_fix(Fix::unsafe_edit(Edit::range_deletion(deletion_range)));
diagnostic
.set_fix(Fix::unsafe_edit(Edit::range_deletion(deletion_range)));
}
} }
} }
checker.diagnostics.push(diagnostic); checker.diagnostics.push(diagnostic);
@ -618,13 +603,11 @@ fn handle_single_name(checker: &mut Checker, expr: &Expr, value: &Expr) {
expr.range(), expr.range(),
); );
if checker.patch(diagnostic.kind.rule()) { let node = value.clone();
let node = value.clone(); diagnostic.set_fix(Fix::safe_edit(Edit::range_replacement(
diagnostic.set_fix(Fix::safe_edit(Edit::range_replacement( checker.generator().expr(&node),
checker.generator().expr(&node), expr.range(),
expr.range(), )));
)));
}
checker.diagnostics.push(diagnostic); checker.diagnostics.push(diagnostic);
} }

View file

@ -7,7 +7,7 @@ use ruff_source_file::Locator;
use ruff_text_size::TextRange; use ruff_text_size::TextRange;
use crate::lex::docstring_detection::StateMachine; use crate::lex::docstring_detection::StateMachine;
use crate::registry::AsRule;
use crate::settings::LinterSettings; use crate::settings::LinterSettings;
/// ## What it does /// ## What it does
@ -135,18 +135,16 @@ pub(crate) fn avoidable_escaped_quote(
&& !string_contents.contains(quotes_settings.inline_quotes.opposite().as_char()) && !string_contents.contains(quotes_settings.inline_quotes.opposite().as_char())
{ {
let mut diagnostic = Diagnostic::new(AvoidableEscapedQuote, tok_range); let mut diagnostic = Diagnostic::new(AvoidableEscapedQuote, tok_range);
if settings.rules.should_fix(diagnostic.kind.rule()) { let fixed_contents = format!(
let fixed_contents = format!( "{prefix}{quote}{value}{quote}",
"{prefix}{quote}{value}{quote}", prefix = kind.as_str(),
prefix = kind.as_str(), quote = quotes_settings.inline_quotes.opposite().as_char(),
quote = quotes_settings.inline_quotes.opposite().as_char(), value = unescape_string(string_contents)
value = unescape_string(string_contents) );
); diagnostic.set_fix(Fix::safe_edit(Edit::range_replacement(
diagnostic.set_fix(Fix::safe_edit(Edit::range_replacement( fixed_contents,
fixed_contents, tok_range,
tok_range, )));
)));
}
diagnostics.push(diagnostic); diagnostics.push(diagnostic);
} }
} }
@ -192,35 +190,33 @@ pub(crate) fn avoidable_escaped_quote(
AvoidableEscapedQuote, AvoidableEscapedQuote,
TextRange::new(context.start_range.start(), tok_range.end()), TextRange::new(context.start_range.start(), tok_range.end()),
); );
if settings.rules.should_fix(diagnostic.kind.rule()) { let fstring_start_edit = Edit::range_replacement(
let fstring_start_edit = Edit::range_replacement( // No need for `r`/`R` as we don't perform the checks
// No need for `r`/`R` as we don't perform the checks // for raw strings.
// for raw strings. format!("f{}", quotes_settings.inline_quotes.opposite().as_char()),
format!("f{}", quotes_settings.inline_quotes.opposite().as_char()), context.start_range,
context.start_range, );
); let fstring_middle_and_end_edits = context
let fstring_middle_and_end_edits = context .middle_ranges_with_escapes
.middle_ranges_with_escapes .iter()
.iter() .map(|&range| {
.map(|&range| { Edit::range_replacement(unescape_string(locator.slice(range)), range)
Edit::range_replacement(unescape_string(locator.slice(range)), range) })
}) .chain(std::iter::once(
.chain(std::iter::once( // `FStringEnd` edit
// `FStringEnd` edit Edit::range_replacement(
Edit::range_replacement( quotes_settings
quotes_settings .inline_quotes
.inline_quotes .opposite()
.opposite() .as_char()
.as_char() .to_string(),
.to_string(), tok_range,
tok_range, ),
),
));
diagnostic.set_fix(Fix::safe_edits(
fstring_start_edit,
fstring_middle_and_end_edits,
)); ));
} diagnostic.set_fix(Fix::safe_edits(
fstring_start_edit,
fstring_middle_and_end_edits,
));
diagnostics.push(diagnostic); diagnostics.push(diagnostic);
} }
_ => {} _ => {}

View file

@ -7,7 +7,7 @@ use ruff_macros::{derive_message_formats, violation};
use ruff_source_file::Locator; use ruff_source_file::Locator;
use crate::lex::docstring_detection::StateMachine; use crate::lex::docstring_detection::StateMachine;
use crate::registry::Rule;
use crate::settings::LinterSettings; use crate::settings::LinterSettings;
use super::super::settings::Quote; use super::super::settings::Quote;
@ -230,21 +230,19 @@ fn docstring(locator: &Locator, range: TextRange, settings: &LinterSettings) ->
}, },
range, range,
); );
if settings.rules.should_fix(Rule::BadQuotesDocstring) { let quote_count = if trivia.is_multiline { 3 } else { 1 };
let quote_count = if trivia.is_multiline { 3 } else { 1 }; let string_contents = &trivia.raw_text[quote_count..trivia.raw_text.len() - quote_count];
let string_contents = &trivia.raw_text[quote_count..trivia.raw_text.len() - quote_count]; let quote = good_docstring(quotes_settings.docstring_quotes).repeat(quote_count);
let quote = good_docstring(quotes_settings.docstring_quotes).repeat(quote_count); let mut fixed_contents =
let mut fixed_contents = String::with_capacity(trivia.prefix.len() + string_contents.len() + quote.len() * 2);
String::with_capacity(trivia.prefix.len() + string_contents.len() + quote.len() * 2); fixed_contents.push_str(trivia.prefix);
fixed_contents.push_str(trivia.prefix); fixed_contents.push_str(&quote);
fixed_contents.push_str(&quote); fixed_contents.push_str(string_contents);
fixed_contents.push_str(string_contents); fixed_contents.push_str(&quote);
fixed_contents.push_str(&quote); diagnostic.set_fix(Fix::safe_edit(Edit::range_replacement(
diagnostic.set_fix(Fix::safe_edit(Edit::range_replacement( fixed_contents,
fixed_contents, range,
range, )));
)));
}
Some(diagnostic) Some(diagnostic)
} }
@ -307,21 +305,19 @@ fn strings(
*range, *range,
); );
if settings.rules.should_fix(Rule::BadQuotesMultilineString) { let string_contents = &trivia.raw_text[3..trivia.raw_text.len() - 3];
let string_contents = &trivia.raw_text[3..trivia.raw_text.len() - 3]; let quote = good_multiline(quotes_settings.multiline_quotes);
let quote = good_multiline(quotes_settings.multiline_quotes); let mut fixed_contents = String::with_capacity(
let mut fixed_contents = String::with_capacity( trivia.prefix.len() + string_contents.len() + quote.len() * 2,
trivia.prefix.len() + string_contents.len() + quote.len() * 2, );
); fixed_contents.push_str(trivia.prefix);
fixed_contents.push_str(trivia.prefix); fixed_contents.push_str(quote);
fixed_contents.push_str(quote); fixed_contents.push_str(string_contents);
fixed_contents.push_str(string_contents); fixed_contents.push_str(quote);
fixed_contents.push_str(quote); diagnostic.set_fix(Fix::safe_edit(Edit::range_replacement(
diagnostic.set_fix(Fix::safe_edit(Edit::range_replacement( fixed_contents,
fixed_contents, *range,
*range, )));
)));
}
diagnostics.push(diagnostic); diagnostics.push(diagnostic);
} else if trivia.last_quote_char != quotes_settings.inline_quotes.as_char() } else if trivia.last_quote_char != quotes_settings.inline_quotes.as_char()
// If we're not using the preferred type, only allow use to avoid escapes. // If we're not using the preferred type, only allow use to avoid escapes.
@ -333,20 +329,18 @@ fn strings(
}, },
*range, *range,
); );
if settings.rules.should_fix(Rule::BadQuotesInlineString) { let quote = quotes_settings.inline_quotes.as_char();
let quote = quotes_settings.inline_quotes.as_char(); let string_contents = &trivia.raw_text[1..trivia.raw_text.len() - 1];
let string_contents = &trivia.raw_text[1..trivia.raw_text.len() - 1]; let mut fixed_contents =
let mut fixed_contents = String::with_capacity(trivia.prefix.len() + string_contents.len() + 2);
String::with_capacity(trivia.prefix.len() + string_contents.len() + 2); fixed_contents.push_str(trivia.prefix);
fixed_contents.push_str(trivia.prefix); fixed_contents.push(quote);
fixed_contents.push(quote); fixed_contents.push_str(string_contents);
fixed_contents.push_str(string_contents); fixed_contents.push(quote);
fixed_contents.push(quote); diagnostic.set_fix(Fix::safe_edit(Edit::range_replacement(
diagnostic.set_fix(Fix::safe_edit(Edit::range_replacement( fixed_contents,
fixed_contents, *range,
*range, )));
)));
}
diagnostics.push(diagnostic); diagnostics.push(diagnostic);
} }
} }

View file

@ -4,7 +4,6 @@ use ruff_python_ast::{self as ast, Expr};
use ruff_text_size::Ranged; use ruff_text_size::Ranged;
use crate::checkers::ast::Checker; use crate::checkers::ast::Checker;
use crate::registry::AsRule;
/// ## What it does /// ## What it does
/// Checks for unnecessary parentheses on raised exceptions. /// Checks for unnecessary parentheses on raised exceptions.
@ -74,26 +73,24 @@ pub(crate) fn unnecessary_paren_on_raise_exception(checker: &mut Checker, expr:
} }
let mut diagnostic = Diagnostic::new(UnnecessaryParenOnRaiseException, arguments.range()); let mut diagnostic = Diagnostic::new(UnnecessaryParenOnRaiseException, arguments.range());
if checker.patch(diagnostic.kind.rule()) { // If the arguments are immediately followed by a `from`, insert whitespace to avoid
// If the arguments are immediately followed by a `from`, insert whitespace to avoid // a syntax error, as in:
// a syntax error, as in: // ```python
// ```python // raise IndexError()from ZeroDivisionError
// raise IndexError()from ZeroDivisionError // ```
// ``` if checker
if checker .locator()
.locator() .after(arguments.end())
.after(arguments.end()) .chars()
.chars() .next()
.next() .is_some_and(char::is_alphanumeric)
.is_some_and(char::is_alphanumeric) {
{ diagnostic.set_fix(Fix::safe_edit(Edit::range_replacement(
diagnostic.set_fix(Fix::safe_edit(Edit::range_replacement( " ".to_string(),
" ".to_string(), arguments.range(),
arguments.range(), )));
))); } else {
} else { diagnostic.set_fix(Fix::safe_edit(Edit::range_deletion(arguments.range())));
diagnostic.set_fix(Fix::safe_edit(Edit::range_deletion(arguments.range())));
}
} }
checker.diagnostics.push(diagnostic); checker.diagnostics.push(diagnostic);
} }

View file

@ -345,12 +345,10 @@ fn unnecessary_return_none(checker: &mut Checker, stack: &Stack) {
continue; continue;
} }
let mut diagnostic = Diagnostic::new(UnnecessaryReturnNone, stmt.range); let mut diagnostic = Diagnostic::new(UnnecessaryReturnNone, stmt.range);
if checker.patch(diagnostic.kind.rule()) { diagnostic.set_fix(Fix::safe_edit(Edit::range_replacement(
diagnostic.set_fix(Fix::safe_edit(Edit::range_replacement( "return".to_string(),
"return".to_string(), stmt.range(),
stmt.range(), )));
)));
}
checker.diagnostics.push(diagnostic); checker.diagnostics.push(diagnostic);
} }
} }
@ -362,12 +360,10 @@ fn implicit_return_value(checker: &mut Checker, stack: &Stack) {
continue; continue;
} }
let mut diagnostic = Diagnostic::new(ImplicitReturnValue, stmt.range); let mut diagnostic = Diagnostic::new(ImplicitReturnValue, stmt.range);
if checker.patch(diagnostic.kind.rule()) { diagnostic.set_fix(Fix::safe_edit(Edit::range_replacement(
diagnostic.set_fix(Fix::safe_edit(Edit::range_replacement( "return None".to_string(),
"return None".to_string(), stmt.range,
stmt.range, )));
)));
}
checker.diagnostics.push(diagnostic); checker.diagnostics.push(diagnostic);
} }
} }
@ -409,17 +405,15 @@ fn implicit_return(checker: &mut Checker, stmt: &Stmt) {
None | Some(ast::ElifElseClause { test: Some(_), .. }) None | Some(ast::ElifElseClause { test: Some(_), .. })
) { ) {
let mut diagnostic = Diagnostic::new(ImplicitReturn, stmt.range()); let mut diagnostic = Diagnostic::new(ImplicitReturn, stmt.range());
if checker.patch(diagnostic.kind.rule()) { if let Some(indent) = indentation(checker.locator(), stmt) {
if let Some(indent) = indentation(checker.locator(), stmt) { let mut content = String::new();
let mut content = String::new(); content.push_str(checker.stylist().line_ending().as_str());
content.push_str(checker.stylist().line_ending().as_str()); content.push_str(indent);
content.push_str(indent); content.push_str("return None");
content.push_str("return None"); diagnostic.set_fix(Fix::unsafe_edit(Edit::insertion(
diagnostic.set_fix(Fix::unsafe_edit(Edit::insertion( content,
content, end_of_last_statement(stmt, checker.locator()),
end_of_last_statement(stmt, checker.locator()), )));
)));
}
} }
checker.diagnostics.push(diagnostic); checker.diagnostics.push(diagnostic);
} }
@ -431,17 +425,15 @@ fn implicit_return(checker: &mut Checker, stmt: &Stmt) {
implicit_return(checker, last_stmt); implicit_return(checker, last_stmt);
} else { } else {
let mut diagnostic = Diagnostic::new(ImplicitReturn, stmt.range()); let mut diagnostic = Diagnostic::new(ImplicitReturn, stmt.range());
if checker.patch(diagnostic.kind.rule()) { if let Some(indent) = indentation(checker.locator(), stmt) {
if let Some(indent) = indentation(checker.locator(), stmt) { let mut content = String::new();
let mut content = String::new(); content.push_str(checker.stylist().line_ending().as_str());
content.push_str(checker.stylist().line_ending().as_str()); content.push_str(indent);
content.push_str(indent); content.push_str("return None");
content.push_str("return None"); diagnostic.set_fix(Fix::unsafe_edit(Edit::insertion(
diagnostic.set_fix(Fix::unsafe_edit(Edit::insertion( content,
content, end_of_last_statement(stmt, checker.locator()),
end_of_last_statement(stmt, checker.locator()), )));
)));
}
} }
checker.diagnostics.push(diagnostic); checker.diagnostics.push(diagnostic);
} }
@ -467,17 +459,15 @@ fn implicit_return(checker: &mut Checker, stmt: &Stmt) {
) => {} ) => {}
_ => { _ => {
let mut diagnostic = Diagnostic::new(ImplicitReturn, stmt.range()); let mut diagnostic = Diagnostic::new(ImplicitReturn, stmt.range());
if checker.patch(diagnostic.kind.rule()) { if let Some(indent) = indentation(checker.locator(), stmt) {
if let Some(indent) = indentation(checker.locator(), stmt) { let mut content = String::new();
let mut content = String::new(); content.push_str(checker.stylist().line_ending().as_str());
content.push_str(checker.stylist().line_ending().as_str()); content.push_str(indent);
content.push_str(indent); content.push_str("return None");
content.push_str("return None"); diagnostic.set_fix(Fix::unsafe_edit(Edit::insertion(
diagnostic.set_fix(Fix::unsafe_edit(Edit::insertion( content,
content, end_of_last_statement(stmt, checker.locator()),
end_of_last_statement(stmt, checker.locator()), )));
)));
}
} }
checker.diagnostics.push(diagnostic); checker.diagnostics.push(diagnostic);
} }
@ -529,47 +519,45 @@ fn unnecessary_assign(checker: &mut Checker, stack: &Stack) {
}, },
value.range(), value.range(),
); );
if checker.patch(diagnostic.kind.rule()) { diagnostic.try_set_fix(|| {
diagnostic.try_set_fix(|| { // Delete the `return` statement. There's no need to treat this as an isolated
// Delete the `return` statement. There's no need to treat this as an isolated // edit, since we're editing the preceding statement, so no conflicting edit would
// edit, since we're editing the preceding statement, so no conflicting edit would // be allowed to remove that preceding statement.
// be allowed to remove that preceding statement. let delete_return =
let delete_return = edits::delete_stmt(stmt, None, checker.locator(), checker.indexer());
edits::delete_stmt(stmt, None, checker.locator(), checker.indexer());
// Replace the `x = 1` statement with `return 1`. // Replace the `x = 1` statement with `return 1`.
let content = checker.locator().slice(assign); let content = checker.locator().slice(assign);
let equals_index = content let equals_index = content
.find('=') .find('=')
.ok_or(anyhow::anyhow!("expected '=' in assignment statement"))?; .ok_or(anyhow::anyhow!("expected '=' in assignment statement"))?;
let after_equals = equals_index + 1; let after_equals = equals_index + 1;
let replace_assign = Edit::range_replacement( let replace_assign = Edit::range_replacement(
// If necessary, add whitespace after the `return` keyword. // If necessary, add whitespace after the `return` keyword.
// Ex) Convert `x=y` to `return y` (instead of `returny`). // Ex) Convert `x=y` to `return y` (instead of `returny`).
if content[after_equals..] if content[after_equals..]
.chars() .chars()
.next() .next()
.is_some_and(is_python_whitespace) .is_some_and(is_python_whitespace)
{ {
"return".to_string() "return".to_string()
} else { } else {
"return ".to_string() "return ".to_string()
}, },
// Replace from the start of the assignment statement to the end of the equals // Replace from the start of the assignment statement to the end of the equals
// sign. // sign.
TextRange::new( TextRange::new(
assign.start(), assign.start(),
assign assign
.range() .range()
.start() .start()
.add(TextSize::try_from(after_equals)?), .add(TextSize::try_from(after_equals)?),
), ),
); );
Ok(Fix::unsafe_edits(replace_assign, [delete_return])) Ok(Fix::unsafe_edits(replace_assign, [delete_return]))
}); });
}
checker.diagnostics.push(diagnostic); checker.diagnostics.push(diagnostic);
} }
} }

View file

@ -15,7 +15,6 @@ use ruff_python_codegen::Generator;
use ruff_python_semantic::SemanticModel; use ruff_python_semantic::SemanticModel;
use crate::checkers::ast::Checker; use crate::checkers::ast::Checker;
use crate::registry::AsRule;
/// ## What it does /// ## What it does
/// Checks for multiple `isinstance` calls on the same target. /// Checks for multiple `isinstance` calls on the same target.
@ -394,78 +393,76 @@ pub(crate) fn duplicate_isinstance_call(checker: &mut Checker, expr: &Expr) {
}, },
expr.range(), expr.range(),
); );
if checker.patch(diagnostic.kind.rule()) { if !contains_effect(target, |id| checker.semantic().is_builtin(id)) {
if !contains_effect(target, |id| checker.semantic().is_builtin(id)) { // Grab the types used in each duplicate `isinstance` call (e.g., `int` and `str`
// Grab the types used in each duplicate `isinstance` call (e.g., `int` and `str` // in `isinstance(obj, int) or isinstance(obj, str)`).
// in `isinstance(obj, int) or isinstance(obj, str)`). let types: Vec<&Expr> = indices
let types: Vec<&Expr> = indices .iter()
.map(|index| &values[*index])
.map(|expr| {
let Expr::Call(ast::ExprCall {
arguments: Arguments { args, .. },
..
}) = expr
else {
unreachable!("Indices should only contain `isinstance` calls")
};
args.get(1).expect("`isinstance` should have two arguments")
})
.collect();
// Generate a single `isinstance` call.
let node = ast::ExprTuple {
// Flatten all the types used across the `isinstance` calls.
elts: types
.iter() .iter()
.map(|index| &values[*index]) .flat_map(|value| {
.map(|expr| { if let Expr::Tuple(ast::ExprTuple { elts, .. }) = value {
let Expr::Call(ast::ExprCall { Left(elts.iter())
arguments: Arguments { args, .. }, } else {
.. Right(iter::once(*value))
}) = expr }
else {
unreachable!("Indices should only contain `isinstance` calls")
};
args.get(1).expect("`isinstance` should have two arguments")
}) })
.collect(); .map(Clone::clone)
.collect(),
ctx: ExprContext::Load,
range: TextRange::default(),
};
let node1 = ast::ExprName {
id: "isinstance".into(),
ctx: ExprContext::Load,
range: TextRange::default(),
};
let node2 = ast::ExprCall {
func: Box::new(node1.into()),
arguments: Arguments {
args: vec![target.clone(), node.into()],
keywords: vec![],
range: TextRange::default(),
},
range: TextRange::default(),
};
let call = node2.into();
// Generate a single `isinstance` call. // Generate the combined `BoolOp`.
let node = ast::ExprTuple { let [first, .., last] = indices.as_slice() else {
// Flatten all the types used across the `isinstance` calls. unreachable!("Indices should have at least two elements")
elts: types };
.iter() let before = values.iter().take(*first).cloned();
.flat_map(|value| { let after = values.iter().skip(last + 1).cloned();
if let Expr::Tuple(ast::ExprTuple { elts, .. }) = value { let node = ast::ExprBoolOp {
Left(elts.iter()) op: BoolOp::Or,
} else { values: before.chain(iter::once(call)).chain(after).collect(),
Right(iter::once(*value)) range: TextRange::default(),
} };
}) let bool_op = node.into();
.map(Clone::clone)
.collect(),
ctx: ExprContext::Load,
range: TextRange::default(),
};
let node1 = ast::ExprName {
id: "isinstance".into(),
ctx: ExprContext::Load,
range: TextRange::default(),
};
let node2 = ast::ExprCall {
func: Box::new(node1.into()),
arguments: Arguments {
args: vec![target.clone(), node.into()],
keywords: vec![],
range: TextRange::default(),
},
range: TextRange::default(),
};
let call = node2.into();
// Generate the combined `BoolOp`. // Populate the `Fix`. Replace the _entire_ `BoolOp`. Note that if we have
let [first, .., last] = indices.as_slice() else { // multiple duplicates, the fixes will conflict.
unreachable!("Indices should have at least two elements") diagnostic.set_fix(Fix::unsafe_edit(Edit::range_replacement(
}; checker.generator().expr(&bool_op),
let before = values.iter().take(*first).cloned(); expr.range(),
let after = values.iter().skip(last + 1).cloned(); )));
let node = ast::ExprBoolOp {
op: BoolOp::Or,
values: before.chain(iter::once(call)).chain(after).collect(),
range: TextRange::default(),
};
let bool_op = node.into();
// Populate the `Fix`. Replace the _entire_ `BoolOp`. Note that if we have
// multiple duplicates, the fixes will conflict.
diagnostic.set_fix(Fix::unsafe_edit(Edit::range_replacement(
checker.generator().expr(&bool_op),
expr.range(),
)));
}
} }
checker.diagnostics.push(diagnostic); checker.diagnostics.push(diagnostic);
} }
@ -564,29 +561,27 @@ pub(crate) fn compare_with_tuple(checker: &mut Checker, expr: &Expr) {
}, },
expr.range(), expr.range(),
); );
if checker.patch(diagnostic.kind.rule()) { let unmatched: Vec<Expr> = values
let unmatched: Vec<Expr> = values .iter()
.iter() .enumerate()
.enumerate() .filter(|(index, _)| !indices.contains(index))
.filter(|(index, _)| !indices.contains(index)) .map(|(_, elt)| elt.clone())
.map(|(_, elt)| elt.clone()) .collect();
.collect(); let in_expr = if unmatched.is_empty() {
let in_expr = if unmatched.is_empty() { in_expr
in_expr } else {
} else { // Wrap in a `x in (a, b) or ...` boolean operation.
// Wrap in a `x in (a, b) or ...` boolean operation. let node = ast::ExprBoolOp {
let node = ast::ExprBoolOp { op: BoolOp::Or,
op: BoolOp::Or, values: iter::once(in_expr).chain(unmatched).collect(),
values: iter::once(in_expr).chain(unmatched).collect(), range: TextRange::default(),
range: TextRange::default(),
};
node.into()
}; };
diagnostic.set_fix(Fix::unsafe_edit(Edit::range_replacement( node.into()
checker.generator().expr(&in_expr), };
expr.range(), diagnostic.set_fix(Fix::unsafe_edit(Edit::range_replacement(
))); checker.generator().expr(&in_expr),
} expr.range(),
)));
checker.diagnostics.push(diagnostic); checker.diagnostics.push(diagnostic);
} }
} }
@ -638,12 +633,10 @@ pub(crate) fn expr_and_not_expr(checker: &mut Checker, expr: &Expr) {
}, },
expr.range(), expr.range(),
); );
if checker.patch(diagnostic.kind.rule()) { diagnostic.set_fix(Fix::unsafe_edit(Edit::range_replacement(
diagnostic.set_fix(Fix::unsafe_edit(Edit::range_replacement( "False".to_string(),
"False".to_string(), expr.range(),
expr.range(), )));
)));
}
checker.diagnostics.push(diagnostic); checker.diagnostics.push(diagnostic);
} }
} }
@ -697,12 +690,10 @@ pub(crate) fn expr_or_not_expr(checker: &mut Checker, expr: &Expr) {
}, },
expr.range(), expr.range(),
); );
if checker.patch(diagnostic.kind.rule()) { diagnostic.set_fix(Fix::unsafe_edit(Edit::range_replacement(
diagnostic.set_fix(Fix::unsafe_edit(Edit::range_replacement( "True".to_string(),
"True".to_string(), expr.range(),
expr.range(), )));
)));
}
checker.diagnostics.push(diagnostic); checker.diagnostics.push(diagnostic);
} }
} }
@ -850,9 +841,7 @@ pub(crate) fn expr_or_true(checker: &mut Checker, expr: &Expr) {
}, },
edit.range(), edit.range(),
); );
if checker.patch(diagnostic.kind.rule()) { diagnostic.set_fix(Fix::unsafe_edit(edit));
diagnostic.set_fix(Fix::unsafe_edit(edit));
}
checker.diagnostics.push(diagnostic); checker.diagnostics.push(diagnostic);
} }
} }
@ -867,9 +856,7 @@ pub(crate) fn expr_and_false(checker: &mut Checker, expr: &Expr) {
}, },
edit.range(), edit.range(),
); );
if checker.patch(diagnostic.kind.rule()) { diagnostic.set_fix(Fix::unsafe_edit(edit));
diagnostic.set_fix(Fix::unsafe_edit(edit));
}
checker.diagnostics.push(diagnostic); checker.diagnostics.push(diagnostic);
} }
} }

View file

@ -7,7 +7,6 @@ use ruff_macros::{derive_message_formats, violation};
use ruff_python_ast::helpers::is_const_none; use ruff_python_ast::helpers::is_const_none;
use crate::checkers::ast::Checker; use crate::checkers::ast::Checker;
use crate::registry::AsRule;
/// ## What it does /// ## What it does
/// Check for environment variables that are not capitalized. /// Check for environment variables that are not capitalized.
@ -207,21 +206,19 @@ fn check_os_environ_subscript(checker: &mut Checker, expr: &Expr) {
}, },
slice.range(), slice.range(),
); );
if checker.patch(diagnostic.kind.rule()) { let node = ast::ExprConstant {
let node = ast::ExprConstant { value: ast::Constant::Str(ast::StringConstant {
value: ast::Constant::Str(ast::StringConstant { value: capital_env_var,
value: capital_env_var, unicode: *unicode,
unicode: *unicode, implicit_concatenated: false,
implicit_concatenated: false, }),
}), range: TextRange::default(),
range: TextRange::default(), };
}; let new_env_var = node.into();
let new_env_var = node.into(); diagnostic.set_fix(Fix::unsafe_edit(Edit::range_replacement(
diagnostic.set_fix(Fix::unsafe_edit(Edit::range_replacement( checker.generator().expr(&new_env_var),
checker.generator().expr(&new_env_var), slice.range(),
slice.range(), )));
)));
}
checker.diagnostics.push(diagnostic); checker.diagnostics.push(diagnostic);
} }
@ -275,11 +272,9 @@ pub(crate) fn dict_get_with_none_default(checker: &mut Checker, expr: &Expr) {
expr.range(), expr.range(),
); );
if checker.patch(diagnostic.kind.rule()) { diagnostic.set_fix(Fix::safe_edit(Edit::range_replacement(
diagnostic.set_fix(Fix::safe_edit(Edit::range_replacement( expected,
expected, expr.range(),
expr.range(), )));
)));
}
checker.diagnostics.push(diagnostic); checker.diagnostics.push(diagnostic);
} }

View file

@ -7,7 +7,6 @@ use ruff_python_ast::helpers::{is_const_false, is_const_true};
use ruff_python_ast::parenthesize::parenthesized_range; use ruff_python_ast::parenthesize::parenthesized_range;
use crate::checkers::ast::Checker; use crate::checkers::ast::Checker;
use crate::registry::AsRule;
/// ## What it does /// ## What it does
/// Checks for `if` expressions that can be replaced with `bool()` calls. /// Checks for `if` expressions that can be replaced with `bool()` calls.
@ -157,48 +156,46 @@ pub(crate) fn if_expr_with_true_false(
}, },
expr.range(), expr.range(),
); );
if checker.patch(diagnostic.kind.rule()) { if test.is_compare_expr() {
if test.is_compare_expr() { diagnostic.set_fix(Fix::unsafe_edit(Edit::range_replacement(
diagnostic.set_fix(Fix::unsafe_edit(Edit::range_replacement( checker
checker .locator()
.locator() .slice(
.slice( parenthesized_range(
parenthesized_range( test.into(),
test.into(), expr.into(),
expr.into(), checker.indexer().comment_ranges(),
checker.indexer().comment_ranges(), checker.locator().contents(),
checker.locator().contents(),
)
.unwrap_or(test.range()),
) )
.to_string(), .unwrap_or(test.range()),
expr.range(), )
))); .to_string(),
} else if checker.semantic().is_builtin("bool") { expr.range(),
diagnostic.set_fix(Fix::unsafe_edit(Edit::range_replacement( )));
checker.generator().expr( } else if checker.semantic().is_builtin("bool") {
&ast::ExprCall { diagnostic.set_fix(Fix::unsafe_edit(Edit::range_replacement(
func: Box::new( checker.generator().expr(
ast::ExprName { &ast::ExprCall {
id: "bool".into(), func: Box::new(
ctx: ExprContext::Load, ast::ExprName {
range: TextRange::default(), id: "bool".into(),
} ctx: ExprContext::Load,
.into(),
),
arguments: Arguments {
args: vec![test.clone()],
keywords: vec![],
range: TextRange::default(), range: TextRange::default(),
}, }
.into(),
),
arguments: Arguments {
args: vec![test.clone()],
keywords: vec![],
range: TextRange::default(), range: TextRange::default(),
} },
.into(), range: TextRange::default(),
), }
expr.range(), .into(),
))); ),
}; expr.range(),
} )));
};
checker.diagnostics.push(diagnostic); checker.diagnostics.push(diagnostic);
} }
@ -215,19 +212,17 @@ pub(crate) fn if_expr_with_false_true(
} }
let mut diagnostic = Diagnostic::new(IfExprWithFalseTrue, expr.range()); let mut diagnostic = Diagnostic::new(IfExprWithFalseTrue, expr.range());
if checker.patch(diagnostic.kind.rule()) { diagnostic.set_fix(Fix::unsafe_edit(Edit::range_replacement(
diagnostic.set_fix(Fix::unsafe_edit(Edit::range_replacement( checker.generator().expr(
checker.generator().expr( &ast::ExprUnaryOp {
&ast::ExprUnaryOp { op: UnaryOp::Not,
op: UnaryOp::Not, operand: Box::new(test.clone()),
operand: Box::new(test.clone()), range: TextRange::default(),
range: TextRange::default(), }
} .into(),
.into(), ),
), expr.range(),
expr.range(), )));
)));
}
checker.diagnostics.push(diagnostic); checker.diagnostics.push(diagnostic);
} }
@ -269,20 +264,18 @@ pub(crate) fn twisted_arms_in_ifexpr(
}, },
expr.range(), expr.range(),
); );
if checker.patch(diagnostic.kind.rule()) { let node = body.clone();
let node = body.clone(); let node1 = orelse.clone();
let node1 = orelse.clone(); let node2 = orelse.clone();
let node2 = orelse.clone(); let node3 = ast::ExprIfExp {
let node3 = ast::ExprIfExp { test: Box::new(node2),
test: Box::new(node2), body: Box::new(node1),
body: Box::new(node1), orelse: Box::new(node),
orelse: Box::new(node), range: TextRange::default(),
range: TextRange::default(), };
}; diagnostic.set_fix(Fix::unsafe_edit(Edit::range_replacement(
diagnostic.set_fix(Fix::unsafe_edit(Edit::range_replacement( checker.generator().expr(&node3.into()),
checker.generator().expr(&node3.into()), expr.range(),
expr.range(), )));
)));
}
checker.diagnostics.push(diagnostic); checker.diagnostics.push(diagnostic);
} }

View file

@ -6,7 +6,6 @@ use ruff_macros::{derive_message_formats, violation};
use ruff_python_semantic::ScopeKind; use ruff_python_semantic::ScopeKind;
use crate::checkers::ast::Checker; use crate::checkers::ast::Checker;
use crate::registry::AsRule;
/// ## What it does /// ## What it does
/// Checks for negated `==` operators. /// Checks for negated `==` operators.
@ -175,18 +174,16 @@ pub(crate) fn negation_with_equal_op(
}, },
expr.range(), expr.range(),
); );
if checker.patch(diagnostic.kind.rule()) { let node = ast::ExprCompare {
let node = ast::ExprCompare { left: left.clone(),
left: left.clone(), ops: vec![CmpOp::NotEq],
ops: vec![CmpOp::NotEq], comparators: comparators.clone(),
comparators: comparators.clone(), range: TextRange::default(),
range: TextRange::default(), };
}; diagnostic.set_fix(Fix::safe_edit(Edit::range_replacement(
diagnostic.set_fix(Fix::safe_edit(Edit::range_replacement( checker.generator().expr(&node.into()),
checker.generator().expr(&node.into()), expr.range(),
expr.range(), )));
)));
}
checker.diagnostics.push(diagnostic); checker.diagnostics.push(diagnostic);
} }
@ -232,18 +229,16 @@ pub(crate) fn negation_with_not_equal_op(
}, },
expr.range(), expr.range(),
); );
if checker.patch(diagnostic.kind.rule()) { let node = ast::ExprCompare {
let node = ast::ExprCompare { left: left.clone(),
left: left.clone(), ops: vec![CmpOp::Eq],
ops: vec![CmpOp::Eq], comparators: comparators.clone(),
comparators: comparators.clone(), range: TextRange::default(),
range: TextRange::default(), };
}; diagnostic.set_fix(Fix::safe_edit(Edit::range_replacement(
diagnostic.set_fix(Fix::safe_edit(Edit::range_replacement( checker.generator().expr(&node.into()),
checker.generator().expr(&node.into()), expr.range(),
expr.range(), )));
)));
}
checker.diagnostics.push(diagnostic); checker.diagnostics.push(diagnostic);
} }
@ -270,32 +265,30 @@ pub(crate) fn double_negation(checker: &mut Checker, expr: &Expr, op: UnaryOp, o
}, },
expr.range(), expr.range(),
); );
if checker.patch(diagnostic.kind.rule()) { if checker.semantic().in_boolean_test() {
if checker.semantic().in_boolean_test() { diagnostic.set_fix(Fix::safe_edit(Edit::range_replacement(
diagnostic.set_fix(Fix::safe_edit(Edit::range_replacement( checker.locator().slice(operand.as_ref()).to_string(),
checker.locator().slice(operand.as_ref()).to_string(), expr.range(),
expr.range(), )));
))); } else if checker.semantic().is_builtin("bool") {
} else if checker.semantic().is_builtin("bool") { let node = ast::ExprName {
let node = ast::ExprName { id: "bool".into(),
id: "bool".into(), ctx: ExprContext::Load,
ctx: ExprContext::Load, range: TextRange::default(),
range: TextRange::default(),
};
let node1 = ast::ExprCall {
func: Box::new(node.into()),
arguments: Arguments {
args: vec![*operand.clone()],
keywords: vec![],
range: TextRange::default(),
},
range: TextRange::default(),
};
diagnostic.set_fix(Fix::safe_edit(Edit::range_replacement(
checker.generator().expr(&node1.into()),
expr.range(),
)));
}; };
} let node1 = ast::ExprCall {
func: Box::new(node.into()),
arguments: Arguments {
args: vec![*operand.clone()],
keywords: vec![],
range: TextRange::default(),
},
range: TextRange::default(),
};
diagnostic.set_fix(Fix::safe_edit(Edit::range_replacement(
checker.generator().expr(&node1.into()),
expr.range(),
)));
};
checker.diagnostics.push(diagnostic); checker.diagnostics.push(diagnostic);
} }

View file

@ -9,7 +9,6 @@ use ruff_text_size::{Ranged, TextRange};
use crate::checkers::ast::Checker; use crate::checkers::ast::Checker;
use crate::fix::edits::fits; use crate::fix::edits::fits;
use crate::registry::AsRule;
use super::fix_with; use super::fix_with;
@ -124,32 +123,30 @@ pub(crate) fn multiple_with_statements(
MultipleWithStatements, MultipleWithStatements,
TextRange::new(with_stmt.start(), colon.end()), TextRange::new(with_stmt.start(), colon.end()),
); );
if checker.patch(diagnostic.kind.rule()) { if !checker
if !checker .indexer()
.indexer() .comment_ranges()
.comment_ranges() .intersects(TextRange::new(with_stmt.start(), with_stmt.body[0].start()))
.intersects(TextRange::new(with_stmt.start(), with_stmt.body[0].start())) {
{ match fix_with::fix_multiple_with_statements(
match fix_with::fix_multiple_with_statements( checker.locator(),
checker.locator(), checker.stylist(),
checker.stylist(), with_stmt,
with_stmt, ) {
) { Ok(edit) => {
Ok(edit) => { if edit.content().map_or(true, |content| {
if edit.content().map_or(true, |content| { fits(
fits( content,
content, with_stmt.into(),
with_stmt.into(), checker.locator(),
checker.locator(), checker.settings.line_length,
checker.settings.line_length, checker.settings.tab_size,
checker.settings.tab_size, )
) }) {
}) { diagnostic.set_fix(Fix::unsafe_edit(edit));
diagnostic.set_fix(Fix::unsafe_edit(edit));
}
} }
Err(err) => error!("Failed to fix nested with: {err}"),
} }
Err(err) => error!("Failed to fix nested with: {err}"),
} }
} }
checker.diagnostics.push(diagnostic); checker.diagnostics.push(diagnostic);

View file

@ -18,7 +18,6 @@ use crate::cst::helpers::space;
use crate::cst::matchers::{match_function_def, match_if, match_indented_block, match_statement}; use crate::cst::matchers::{match_function_def, match_if, match_indented_block, match_statement};
use crate::fix::codemods::CodegenStylist; use crate::fix::codemods::CodegenStylist;
use crate::fix::edits::fits; use crate::fix::edits::fits;
use crate::registry::AsRule;
/// ## What it does /// ## What it does
/// Checks for nested `if` statements that can be collapsed into a single `if` /// Checks for nested `if` statements that can be collapsed into a single `if`
@ -101,33 +100,31 @@ pub(crate) fn nested_if_statements(
CollapsibleIf, CollapsibleIf,
TextRange::new(nested_if.start(), colon.end()), TextRange::new(nested_if.start(), colon.end()),
); );
if checker.patch(diagnostic.kind.rule()) { // The fixer preserves comments in the nested body, but removes comments between
// The fixer preserves comments in the nested body, but removes comments between // the outer and inner if statements.
// the outer and inner if statements. if !checker
if !checker .indexer()
.indexer() .comment_ranges()
.comment_ranges() .intersects(TextRange::new(
.intersects(TextRange::new( nested_if.start(),
nested_if.start(), nested_if.body()[0].start(),
nested_if.body()[0].start(), ))
)) {
{ match collapse_nested_if(checker.locator(), checker.stylist(), nested_if) {
match collapse_nested_if(checker.locator(), checker.stylist(), nested_if) { Ok(edit) => {
Ok(edit) => { if edit.content().map_or(true, |content| {
if edit.content().map_or(true, |content| { fits(
fits( content,
content, (&nested_if).into(),
(&nested_if).into(), checker.locator(),
checker.locator(), checker.settings.line_length,
checker.settings.line_length, checker.settings.tab_size,
checker.settings.tab_size, )
) }) {
}) { diagnostic.set_fix(Fix::unsafe_edit(edit));
diagnostic.set_fix(Fix::unsafe_edit(edit));
}
} }
Err(err) => error!("Failed to fix nested if: {err}"),
} }
Err(err) => error!("Failed to fix nested if: {err}"),
} }
} }
checker.diagnostics.push(diagnostic); checker.diagnostics.push(diagnostic);

View file

@ -9,7 +9,6 @@ use ruff_text_size::{Ranged, TextRange};
use crate::checkers::ast::Checker; use crate::checkers::ast::Checker;
use crate::fix::edits::fits; use crate::fix::edits::fits;
use crate::registry::AsRule;
/// ## What it does /// ## What it does
/// Checks for `if` statements that can be replaced with `dict.get` calls. /// Checks for `if` statements that can be replaced with `dict.get` calls.
@ -184,13 +183,11 @@ pub(crate) fn use_dict_get_with_default(checker: &mut Checker, stmt_if: &ast::St
}, },
stmt_if.range(), stmt_if.range(),
); );
if checker.patch(diagnostic.kind.rule()) { if !checker.indexer().has_comments(stmt_if, checker.locator()) {
if !checker.indexer().has_comments(stmt_if, checker.locator()) { diagnostic.set_fix(Fix::unsafe_edit(Edit::range_replacement(
diagnostic.set_fix(Fix::unsafe_edit(Edit::range_replacement( contents,
contents, stmt_if.range(),
stmt_if.range(), )));
)));
}
} }
checker.diagnostics.push(diagnostic); checker.diagnostics.push(diagnostic);
} }

View file

@ -7,7 +7,6 @@ use ruff_text_size::{Ranged, TextRange};
use crate::checkers::ast::Checker; use crate::checkers::ast::Checker;
use crate::fix::edits::fits; use crate::fix::edits::fits;
use crate::registry::AsRule;
/// ## What it does /// ## What it does
/// Check for `if`-`else`-blocks that can be replaced with a ternary operator. /// Check for `if`-`else`-blocks that can be replaced with a ternary operator.
@ -143,13 +142,11 @@ pub(crate) fn use_ternary_operator(checker: &mut Checker, stmt: &Stmt) {
}, },
stmt.range(), stmt.range(),
); );
if checker.patch(diagnostic.kind.rule()) { if !checker.indexer().has_comments(stmt, checker.locator()) {
if !checker.indexer().has_comments(stmt, checker.locator()) { diagnostic.set_fix(Fix::unsafe_edit(Edit::range_replacement(
diagnostic.set_fix(Fix::unsafe_edit(Edit::range_replacement( contents,
contents, stmt.range(),
stmt.range(), )));
)));
}
} }
checker.diagnostics.push(diagnostic); checker.diagnostics.push(diagnostic);
} }

View file

@ -8,7 +8,6 @@ use ruff_python_trivia::{SimpleTokenKind, SimpleTokenizer};
use ruff_text_size::{Ranged, TextRange}; use ruff_text_size::{Ranged, TextRange};
use crate::checkers::ast::Checker; use crate::checkers::ast::Checker;
use crate::registry::AsRule;
/// ## What it does /// ## What it does
/// Checks for key-existence checks against `dict.keys()` calls. /// Checks for key-existence checks against `dict.keys()` calls.
@ -109,32 +108,30 @@ fn key_in_dict(
}, },
TextRange::new(left_range.start(), right_range.end()), TextRange::new(left_range.start(), right_range.end()),
); );
if checker.patch(diagnostic.kind.rule()) { // Delete from the start of the dot to the end of the expression.
// Delete from the start of the dot to the end of the expression. if let Some(dot) = SimpleTokenizer::starts_at(value.end(), checker.locator().contents())
if let Some(dot) = SimpleTokenizer::starts_at(value.end(), checker.locator().contents()) .skip_trivia()
.skip_trivia() .find(|token| token.kind == SimpleTokenKind::Dot)
.find(|token| token.kind == SimpleTokenKind::Dot) {
// If the `.keys()` is followed by (e.g.) a keyword, we need to insert a space,
// since we're removing parentheses, which could lead to invalid syntax, as in:
// ```python
// if key in foo.keys()and bar:
// ```
let range = TextRange::new(dot.start(), right.end());
if checker
.locator()
.after(range.end())
.chars()
.next()
.is_some_and(|char| char.is_ascii_alphabetic())
{ {
// If the `.keys()` is followed by (e.g.) a keyword, we need to insert a space, diagnostic.set_fix(Fix::unsafe_edit(Edit::range_replacement(
// since we're removing parentheses, which could lead to invalid syntax, as in: " ".to_string(),
// ```python range,
// if key in foo.keys()and bar: )));
// ``` } else {
let range = TextRange::new(dot.start(), right.end()); diagnostic.set_fix(Fix::unsafe_edit(Edit::range_deletion(range)));
if checker
.locator()
.after(range.end())
.chars()
.next()
.is_some_and(|char| char.is_ascii_alphabetic())
{
diagnostic.set_fix(Fix::unsafe_edit(Edit::range_replacement(
" ".to_string(),
range,
)));
} else {
diagnostic.set_fix(Fix::unsafe_edit(Edit::range_deletion(range)));
}
} }
} }
checker.diagnostics.push(diagnostic); checker.diagnostics.push(diagnostic);

View file

@ -4,7 +4,6 @@ use ruff_python_ast::{self as ast, Arguments, Constant, ElifElseClause, Expr, Ex
use ruff_text_size::{Ranged, TextRange}; use ruff_text_size::{Ranged, TextRange};
use crate::checkers::ast::Checker; use crate::checkers::ast::Checker;
use crate::registry::AsRule;
/// ## What it does /// ## What it does
/// Checks for `if` statements that can be replaced with `bool`. /// Checks for `if` statements that can be replaced with `bool`.
@ -100,49 +99,47 @@ pub(crate) fn needless_bool(checker: &mut Checker, stmt: &Stmt) {
let condition = checker.generator().expr(if_test); let condition = checker.generator().expr(if_test);
let mut diagnostic = Diagnostic::new(NeedlessBool { condition }, range); let mut diagnostic = Diagnostic::new(NeedlessBool { condition }, range);
if checker.patch(diagnostic.kind.rule()) { if matches!(if_return, Bool::True)
if matches!(if_return, Bool::True) && matches!(else_return, Bool::False)
&& matches!(else_return, Bool::False) && !checker.indexer().has_comments(&range, checker.locator())
&& !checker.indexer().has_comments(&range, checker.locator()) && (if_test.is_compare_expr() || checker.semantic().is_builtin("bool"))
&& (if_test.is_compare_expr() || checker.semantic().is_builtin("bool")) {
{ if if_test.is_compare_expr() {
if if_test.is_compare_expr() { // If the condition is a comparison, we can replace it with the condition.
// If the condition is a comparison, we can replace it with the condition. let node = ast::StmtReturn {
let node = ast::StmtReturn { value: Some(Box::new(if_test.clone())),
value: Some(Box::new(if_test.clone())), range: TextRange::default(),
range: TextRange::default(),
};
diagnostic.set_fix(Fix::unsafe_edit(Edit::range_replacement(
checker.generator().stmt(&node.into()),
range,
)));
} else {
// Otherwise, we need to wrap the condition in a call to `bool`. (We've already
// verified, above, that `bool` is a builtin.)
let node = ast::ExprName {
id: "bool".into(),
ctx: ExprContext::Load,
range: TextRange::default(),
};
let node1 = ast::ExprCall {
func: Box::new(node.into()),
arguments: Arguments {
args: vec![if_test.clone()],
keywords: vec![],
range: TextRange::default(),
},
range: TextRange::default(),
};
let node2 = ast::StmtReturn {
value: Some(Box::new(node1.into())),
range: TextRange::default(),
};
diagnostic.set_fix(Fix::unsafe_edit(Edit::range_replacement(
checker.generator().stmt(&node2.into()),
range,
)));
}; };
} diagnostic.set_fix(Fix::unsafe_edit(Edit::range_replacement(
checker.generator().stmt(&node.into()),
range,
)));
} else {
// Otherwise, we need to wrap the condition in a call to `bool`. (We've already
// verified, above, that `bool` is a builtin.)
let node = ast::ExprName {
id: "bool".into(),
ctx: ExprContext::Load,
range: TextRange::default(),
};
let node1 = ast::ExprCall {
func: Box::new(node.into()),
arguments: Arguments {
args: vec![if_test.clone()],
keywords: vec![],
range: TextRange::default(),
},
range: TextRange::default(),
};
let node2 = ast::StmtReturn {
value: Some(Box::new(node1.into())),
range: TextRange::default(),
};
diagnostic.set_fix(Fix::unsafe_edit(Edit::range_replacement(
checker.generator().stmt(&node2.into()),
range,
)));
};
} }
checker.diagnostics.push(diagnostic); checker.diagnostics.push(diagnostic);
} }

View file

@ -11,7 +11,6 @@ use ruff_text_size::{Ranged, TextRange};
use crate::checkers::ast::Checker; use crate::checkers::ast::Checker;
use crate::fix::edits::fits; use crate::fix::edits::fits;
use crate::line_width::LineWidthBuilder; use crate::line_width::LineWidthBuilder;
use crate::registry::AsRule;
/// ## What it does /// ## What it does
/// Checks for `for` loops that can be replaced with a builtin function, like /// Checks for `for` loops that can be replaced with a builtin function, like
@ -114,7 +113,7 @@ pub(crate) fn convert_for_loop_to_any_all(checker: &mut Checker, stmt: &Stmt) {
}, },
TextRange::new(stmt.start(), terminal.stmt.end()), TextRange::new(stmt.start(), terminal.stmt.end()),
); );
if checker.patch(diagnostic.kind.rule()) && checker.semantic().is_builtin("any") { if checker.semantic().is_builtin("any") {
diagnostic.set_fix(Fix::unsafe_edit(Edit::replacement( diagnostic.set_fix(Fix::unsafe_edit(Edit::replacement(
contents, contents,
stmt.start(), stmt.start(),
@ -200,7 +199,7 @@ pub(crate) fn convert_for_loop_to_any_all(checker: &mut Checker, stmt: &Stmt) {
}, },
TextRange::new(stmt.start(), terminal.stmt.end()), TextRange::new(stmt.start(), terminal.stmt.end()),
); );
if checker.patch(diagnostic.kind.rule()) && checker.semantic().is_builtin("all") { if checker.semantic().is_builtin("all") {
diagnostic.set_fix(Fix::unsafe_edit(Edit::replacement( diagnostic.set_fix(Fix::unsafe_edit(Edit::replacement(
contents, contents,
stmt.start(), stmt.start(),

View file

@ -8,7 +8,6 @@ use ruff_text_size::{TextLen, TextRange};
use crate::checkers::ast::Checker; use crate::checkers::ast::Checker;
use crate::importer::ImportRequest; use crate::importer::ImportRequest;
use crate::registry::AsRule;
/// ## What it does /// ## What it does
/// Checks for `try`-`except`-`pass` blocks that can be replaced with the /// Checks for `try`-`except`-`pass` blocks that can be replaced with the
@ -132,28 +131,25 @@ pub(crate) fn suppressible_exception(
}, },
stmt.range(), stmt.range(),
); );
if checker.patch(diagnostic.kind.rule()) { if !checker.indexer().has_comments(stmt, checker.locator()) {
if !checker.indexer().has_comments(stmt, checker.locator()) { diagnostic.try_set_fix(|| {
diagnostic.try_set_fix(|| { // let range = statement_range(stmt, checker.locator(), checker.indexer());
// let range = statement_range(stmt, checker.locator(), checker.indexer());
let (import_edit, binding) = checker.importer().get_or_import_symbol( let (import_edit, binding) = checker.importer().get_or_import_symbol(
&ImportRequest::import("contextlib", "suppress"), &ImportRequest::import("contextlib", "suppress"),
stmt.start(), stmt.start(),
checker.semantic(), checker.semantic(),
)?; )?;
let replace_try = Edit::range_replacement( let replace_try = Edit::range_replacement(
format!("with {binding}({exception})"), format!("with {binding}({exception})"),
TextRange::at(stmt.start(), "try".text_len()), TextRange::at(stmt.start(), "try".text_len()),
); );
let remove_handler = let remove_handler = Edit::range_deletion(checker.locator().full_lines_range(*range));
Edit::range_deletion(checker.locator().full_lines_range(*range)); Ok(Fix::unsafe_edits(
Ok(Fix::unsafe_edits( import_edit,
import_edit, [replace_try, remove_handler],
[replace_try, remove_handler], ))
)) });
});
}
} }
checker.diagnostics.push(diagnostic); checker.diagnostics.push(diagnostic);
} }

View file

@ -14,7 +14,6 @@ use crate::cst::helpers::or_space;
use crate::cst::matchers::{match_comparison, transform_expression}; use crate::cst::matchers::{match_comparison, transform_expression};
use crate::fix::edits::pad; use crate::fix::edits::pad;
use crate::fix::snippet::SourceCodeSnippet; use crate::fix::snippet::SourceCodeSnippet;
use crate::registry::AsRule;
/// ## What it does /// ## What it does
/// Checks for conditions that position a constant on the left-hand side of the /// Checks for conditions that position a constant on the left-hand side of the
@ -193,12 +192,10 @@ pub(crate) fn yoda_conditions(
}, },
expr.range(), expr.range(),
); );
if checker.patch(diagnostic.kind.rule()) { diagnostic.set_fix(Fix::safe_edit(Edit::range_replacement(
diagnostic.set_fix(Fix::safe_edit(Edit::range_replacement( pad(suggestion, expr.range(), checker.locator()),
pad(suggestion, expr.range(), checker.locator()), expr.range(),
expr.range(), )));
)));
}
checker.diagnostics.push(diagnostic); checker.diagnostics.push(diagnostic);
} else { } else {
checker.diagnostics.push(Diagnostic::new( checker.diagnostics.push(Diagnostic::new(

View file

@ -8,7 +8,7 @@ use ruff_python_codegen::Generator;
use ruff_python_stdlib::identifiers::is_identifier; use ruff_python_stdlib::identifiers::is_identifier;
use crate::checkers::ast::Checker; use crate::checkers::ast::Checker;
use crate::registry::AsRule;
use crate::rules::flake8_tidy_imports::settings::Strictness; use crate::rules::flake8_tidy_imports::settings::Strictness;
/// ## What it does /// ## What it does
@ -124,13 +124,11 @@ pub(crate) fn banned_relative_import(
}; };
if level? > strictness_level { if level? > strictness_level {
let mut diagnostic = Diagnostic::new(RelativeImports { strictness }, stmt.range()); let mut diagnostic = Diagnostic::new(RelativeImports { strictness }, stmt.range());
if checker.patch(diagnostic.kind.rule()) { if let Some(fix) =
if let Some(fix) = fix_banned_relative_import(stmt, level, module, module_path, checker.generator())
fix_banned_relative_import(stmt, level, module, module_path, checker.generator()) {
{ diagnostic.set_fix(fix);
diagnostic.set_fix(fix); };
};
}
Some(diagnostic) Some(diagnostic)
} else { } else {
None None

View file

@ -7,11 +7,7 @@ use ruff_text_size::{TextLen, TextRange, TextSize};
use ruff_diagnostics::{AlwaysFixableViolation, Diagnostic, Edit, Fix, Violation}; use ruff_diagnostics::{AlwaysFixableViolation, Diagnostic, Edit, Fix, Violation};
use ruff_macros::{derive_message_formats, violation}; use ruff_macros::{derive_message_formats, violation};
use crate::settings::LinterSettings; use crate::directives::{TodoComment, TodoDirective, TodoDirectiveKind};
use crate::{
directives::{TodoComment, TodoDirective, TodoDirectiveKind},
registry::Rule,
};
/// ## What it does /// ## What it does
/// Checks that a TODO comment is labelled with "TODO". /// Checks that a TODO comment is labelled with "TODO".
@ -240,7 +236,6 @@ pub(crate) fn todos(
todo_comments: &[TodoComment], todo_comments: &[TodoComment],
locator: &Locator, locator: &Locator,
indexer: &Indexer, indexer: &Indexer,
settings: &LinterSettings,
) { ) {
for todo_comment in todo_comments { for todo_comment in todo_comments {
let TodoComment { let TodoComment {
@ -256,7 +251,7 @@ pub(crate) fn todos(
continue; continue;
} }
directive_errors(diagnostics, directive, settings); directive_errors(diagnostics, directive);
static_errors(diagnostics, content, range, directive); static_errors(diagnostics, content, range, directive);
let mut has_issue_link = false; let mut has_issue_link = false;
@ -300,11 +295,7 @@ pub(crate) fn todos(
} }
/// Check that the directive itself is valid. This function modifies `diagnostics` in-place. /// Check that the directive itself is valid. This function modifies `diagnostics` in-place.
fn directive_errors( fn directive_errors(diagnostics: &mut Vec<Diagnostic>, directive: &TodoDirective) {
diagnostics: &mut Vec<Diagnostic>,
directive: &TodoDirective,
settings: &LinterSettings,
) {
if directive.content == "TODO" { if directive.content == "TODO" {
return; return;
} }
@ -318,12 +309,10 @@ fn directive_errors(
directive.range, directive.range,
); );
if settings.rules.should_fix(Rule::InvalidTodoCapitalization) { diagnostic.set_fix(Fix::safe_edit(Edit::range_replacement(
diagnostic.set_fix(Fix::safe_edit(Edit::range_replacement( "TODO".to_string(),
"TODO".to_string(), directive.range,
directive.range, )));
)));
}
diagnostics.push(diagnostic); diagnostics.push(diagnostic);
} else { } else {

View file

@ -6,7 +6,6 @@ use ruff_text_size::Ranged;
use crate::checkers::ast::Checker; use crate::checkers::ast::Checker;
use crate::fix; use crate::fix;
use crate::registry::AsRule;
/// ## What it does /// ## What it does
/// Checks for an empty type-checking block. /// Checks for an empty type-checking block.
@ -56,14 +55,12 @@ pub(crate) fn empty_type_checking_block(checker: &mut Checker, stmt: &ast::StmtI
} }
let mut diagnostic = Diagnostic::new(EmptyTypeCheckingBlock, stmt.range()); let mut diagnostic = Diagnostic::new(EmptyTypeCheckingBlock, stmt.range());
if checker.patch(diagnostic.kind.rule()) { // Delete the entire type-checking block.
// Delete the entire type-checking block. let stmt = checker.semantic().current_statement();
let stmt = checker.semantic().current_statement(); let parent = checker.semantic().current_statement_parent();
let parent = checker.semantic().current_statement_parent(); let edit = fix::edits::delete_stmt(stmt, parent, checker.locator(), checker.indexer());
let edit = fix::edits::delete_stmt(stmt, parent, checker.locator(), checker.indexer()); diagnostic.set_fix(Fix::safe_edit(edit).isolate(Checker::isolation(
diagnostic.set_fix(Fix::safe_edit(edit).isolate(Checker::isolation( checker.semantic().current_statement_parent_id(),
checker.semantic().current_statement_parent_id(), )));
)));
}
checker.diagnostics.push(diagnostic); checker.diagnostics.push(diagnostic);
} }

View file

@ -126,11 +126,7 @@ pub(crate) fn runtime_import_in_type_checking_block(
// Generate a diagnostic for every import, but share a fix across all imports within the same // Generate a diagnostic for every import, but share a fix across all imports within the same
// statement (excluding those that are ignored). // statement (excluding those that are ignored).
for (node_id, imports) in errors_by_statement { for (node_id, imports) in errors_by_statement {
let fix = if checker.patch(Rule::RuntimeImportInTypeCheckingBlock) { let fix = fix_imports(checker, node_id, &imports).ok();
fix_imports(checker, node_id, &imports).ok()
} else {
None
};
for ImportBinding { for ImportBinding {
import, import,

View file

@ -334,11 +334,7 @@ pub(crate) fn typing_only_runtime_import(
// Generate a diagnostic for every import, but share a fix across all imports within the same // Generate a diagnostic for every import, but share a fix across all imports within the same
// statement (excluding those that are ignored). // statement (excluding those that are ignored).
for ((node_id, import_type), imports) in errors_by_statement { for ((node_id, import_type), imports) in errors_by_statement {
let fix = if checker.patch(rule_for(import_type)) { let fix = fix_imports(checker, node_id, &imports).ok();
fix_imports(checker, node_id, &imports).ok()
} else {
None
};
for ImportBinding { for ImportBinding {
import, import,

View file

@ -4,7 +4,6 @@ use ruff_diagnostics::{AlwaysFixableViolation, Diagnostic, Edit, Fix};
use ruff_macros::{derive_message_formats, violation}; use ruff_macros::{derive_message_formats, violation};
use crate::checkers::ast::Checker; use crate::checkers::ast::Checker;
use crate::registry::AsRule;
/// ## What it does /// ## What it does
/// Checks for `pathlib.Path` objects that are initialized with the current /// Checks for `pathlib.Path` objects that are initialized with the current
@ -76,9 +75,7 @@ pub(crate) fn path_constructor_current_directory(checker: &mut Checker, expr: &E
if matches!(value.as_str(), "" | ".") { if matches!(value.as_str(), "" | ".") {
let mut diagnostic = Diagnostic::new(PathConstructorCurrentDirectory, *range); let mut diagnostic = Diagnostic::new(PathConstructorCurrentDirectory, *range);
if checker.patch(diagnostic.kind.rule()) { diagnostic.set_fix(Fix::safe_edit(Edit::range_deletion(*range)));
diagnostic.set_fix(Fix::safe_edit(Edit::range_deletion(*range)));
}
checker.diagnostics.push(diagnostic); checker.diagnostics.push(diagnostic);
} }
} }

View file

@ -8,7 +8,7 @@ use ruff_text_size::{Ranged, TextRange};
use crate::checkers::ast::Checker; use crate::checkers::ast::Checker;
use crate::fix::snippet::SourceCodeSnippet; use crate::fix::snippet::SourceCodeSnippet;
use crate::registry::AsRule;
use crate::rules::flynt::helpers; use crate::rules::flynt::helpers;
/// ## What it does /// ## What it does
@ -154,11 +154,9 @@ pub(crate) fn static_join_to_fstring(checker: &mut Checker, expr: &Expr, joiner:
}, },
expr.range(), expr.range(),
); );
if checker.patch(diagnostic.kind.rule()) { diagnostic.set_fix(Fix::unsafe_edit(Edit::range_replacement(
diagnostic.set_fix(Fix::unsafe_edit(Edit::range_replacement( pad(contents, expr.range(), checker.locator()),
pad(contents, expr.range(), checker.locator()), expr.range(),
expr.range(), )));
)));
}
checker.diagnostics.push(diagnostic); checker.diagnostics.push(diagnostic);
} }

View file

@ -11,7 +11,7 @@ use ruff_source_file::Locator;
use ruff_text_size::{TextRange, TextSize}; use ruff_text_size::{TextRange, TextSize};
use crate::importer::Importer; use crate::importer::Importer;
use crate::registry::Rule;
use crate::settings::LinterSettings; use crate::settings::LinterSettings;
/// ## What it does /// ## What it does
@ -90,7 +90,6 @@ fn add_required_import(
python_ast: &Suite, python_ast: &Suite,
locator: &Locator, locator: &Locator,
stylist: &Stylist, stylist: &Stylist,
settings: &LinterSettings,
source_type: PySourceType, source_type: PySourceType,
) -> Option<Diagnostic> { ) -> Option<Diagnostic> {
// Don't add imports to semantically-empty files. // Don't add imports to semantically-empty files.
@ -116,12 +115,10 @@ fn add_required_import(
MissingRequiredImport(required_import.to_string()), MissingRequiredImport(required_import.to_string()),
TextRange::default(), TextRange::default(),
); );
if settings.rules.should_fix(Rule::MissingRequiredImport) { diagnostic.set_fix(Fix::safe_edit(
diagnostic.set_fix(Fix::safe_edit( Importer::new(python_ast, locator, stylist)
Importer::new(python_ast, locator, stylist) .add_import(required_import, TextSize::default()),
.add_import(required_import, TextSize::default()), ));
));
}
Some(diagnostic) Some(diagnostic)
} }
@ -171,7 +168,6 @@ pub(crate) fn add_required_imports(
python_ast, python_ast,
locator, locator,
stylist, stylist,
settings,
source_type, source_type,
) )
}) })
@ -189,7 +185,6 @@ pub(crate) fn add_required_imports(
python_ast, python_ast,
locator, locator,
stylist, stylist,
settings,
source_type, source_type,
) )
}) })

View file

@ -13,7 +13,7 @@ use ruff_source_file::{Locator, UniversalNewlines};
use ruff_text_size::{Ranged, TextRange}; use ruff_text_size::{Ranged, TextRange};
use crate::line_width::LineWidthBuilder; use crate::line_width::LineWidthBuilder;
use crate::registry::AsRule;
use crate::settings::LinterSettings; use crate::settings::LinterSettings;
use super::super::block::Block; use super::super::block::Block;
@ -138,11 +138,9 @@ pub(crate) fn organize_imports(
} }
let mut diagnostic = Diagnostic::new(UnsortedImports, range); let mut diagnostic = Diagnostic::new(UnsortedImports, range);
if settings.rules.should_fix(diagnostic.kind.rule()) { diagnostic.set_fix(Fix::safe_edit(Edit::range_replacement(
diagnostic.set_fix(Fix::safe_edit(Edit::range_replacement( indent(&expected, indentation).to_string(),
indent(&expected, indentation).to_string(), range,
range, )));
)));
}
Some(diagnostic) Some(diagnostic)
} }

View file

@ -5,7 +5,6 @@ use ruff_text_size::Ranged;
use crate::checkers::ast::Checker; use crate::checkers::ast::Checker;
use crate::importer::ImportRequest; use crate::importer::ImportRequest;
use crate::registry::AsRule;
/// ## What it does /// ## What it does
/// Checks for uses of deprecated NumPy functions. /// Checks for uses of deprecated NumPy functions.
@ -76,17 +75,15 @@ pub(crate) fn deprecated_function(checker: &mut Checker, expr: &Expr) {
}, },
expr.range(), expr.range(),
); );
if checker.patch(diagnostic.kind.rule()) { diagnostic.try_set_fix(|| {
diagnostic.try_set_fix(|| { let (import_edit, binding) = checker.importer().get_or_import_symbol(
let (import_edit, binding) = checker.importer().get_or_import_symbol( &ImportRequest::import_from("numpy", replacement),
&ImportRequest::import_from("numpy", replacement), expr.start(),
expr.start(), checker.semantic(),
checker.semantic(), )?;
)?; let replacement_edit = Edit::range_replacement(binding, expr.range());
let replacement_edit = Edit::range_replacement(binding, expr.range()); Ok(Fix::safe_edits(import_edit, [replacement_edit]))
Ok(Fix::safe_edits(import_edit, [replacement_edit])) });
});
}
checker.diagnostics.push(diagnostic); checker.diagnostics.push(diagnostic);
} }
} }

View file

@ -4,7 +4,6 @@ use ruff_python_ast::Expr;
use ruff_text_size::Ranged; use ruff_text_size::Ranged;
use crate::checkers::ast::Checker; use crate::checkers::ast::Checker;
use crate::registry::AsRule;
/// ## What it does /// ## What it does
/// Checks for deprecated NumPy type aliases. /// Checks for deprecated NumPy type aliases.
@ -73,18 +72,16 @@ pub(crate) fn deprecated_type_alias(checker: &mut Checker, expr: &Expr) {
}, },
expr.range(), expr.range(),
); );
if checker.patch(diagnostic.kind.rule()) { let type_name = match type_name {
let type_name = match type_name { "unicode" => "str",
"unicode" => "str", "long" => "int",
"long" => "int", _ => type_name,
_ => type_name, };
}; if checker.semantic().is_builtin(type_name) {
if checker.semantic().is_builtin(type_name) { diagnostic.set_fix(Fix::safe_edit(Edit::range_replacement(
diagnostic.set_fix(Fix::safe_edit(Edit::range_replacement( type_name.to_string(),
type_name.to_string(), expr.range(),
expr.range(), )));
)));
}
} }
checker.diagnostics.push(diagnostic); checker.diagnostics.push(diagnostic);
} }

View file

@ -9,7 +9,6 @@ use ruff_text_size::Ranged;
use crate::checkers::ast::Checker; use crate::checkers::ast::Checker;
use crate::fix::edits::{remove_argument, Parentheses}; use crate::fix::edits::{remove_argument, Parentheses};
use crate::registry::AsRule;
/// ## What it does /// ## What it does
/// Checks for `inplace=True` usages in `pandas` function and method /// Checks for `inplace=True` usages in `pandas` function and method
@ -71,26 +70,24 @@ pub(crate) fn inplace_argument(checker: &mut Checker, call: &ast::ExprCall) {
if arg == "inplace" { if arg == "inplace" {
if is_const_true(&keyword.value) { if is_const_true(&keyword.value) {
let mut diagnostic = Diagnostic::new(PandasUseOfInplaceArgument, keyword.range()); let mut diagnostic = Diagnostic::new(PandasUseOfInplaceArgument, keyword.range());
if checker.patch(diagnostic.kind.rule()) { // Avoid applying the fix if:
// Avoid applying the fix if: // 1. The keyword argument is followed by a star argument (we can't be certain that
// 1. The keyword argument is followed by a star argument (we can't be certain that // the star argument _doesn't_ contain an override).
// the star argument _doesn't_ contain an override). // 2. The call is part of a larger expression (we're converting an expression to a
// 2. The call is part of a larger expression (we're converting an expression to a // statement, and expressions can't contain statements).
// statement, and expressions can't contain statements). let statement = checker.semantic().current_statement();
let statement = checker.semantic().current_statement(); if !seen_star
if !seen_star && checker.semantic().current_expression_parent().is_none()
&& checker.semantic().current_expression_parent().is_none() && statement.is_expr_stmt()
&& statement.is_expr_stmt() {
{ if let Some(fix) = convert_inplace_argument_to_assignment(
if let Some(fix) = convert_inplace_argument_to_assignment( call,
call, keyword,
keyword, statement,
statement, checker.indexer().comment_ranges(),
checker.indexer().comment_ranges(), checker.locator(),
checker.locator(), ) {
) { diagnostic.set_fix(fix);
diagnostic.set_fix(fix);
}
} }
} }

View file

@ -8,7 +8,6 @@ use ruff_python_ast::{Arguments, Expr};
use ruff_text_size::Ranged; use ruff_text_size::Ranged;
use crate::checkers::ast::Checker; use crate::checkers::ast::Checker;
use crate::registry::AsRule;
/// ## What it does /// ## What it does
/// Checks for uses of `dict.items()` that discard either the key or the value /// Checks for uses of `dict.items()` that discard either the key or the value
@ -100,18 +99,16 @@ pub(crate) fn incorrect_dict_iterator(checker: &mut Checker, stmt_for: &ast::Stm
}, },
func.range(), func.range(),
); );
if checker.patch(diagnostic.kind.rule()) { let replace_attribute = Edit::range_replacement("values".to_string(), attr.range());
let replace_attribute = Edit::range_replacement("values".to_string(), attr.range()); let replace_target = Edit::range_replacement(
let replace_target = Edit::range_replacement( pad(
pad( checker.locator().slice(value).to_string(),
checker.locator().slice(value).to_string(),
stmt_for.target.range(),
checker.locator(),
),
stmt_for.target.range(), stmt_for.target.range(),
); checker.locator(),
diagnostic.set_fix(Fix::unsafe_edits(replace_attribute, [replace_target])); ),
} stmt_for.target.range(),
);
diagnostic.set_fix(Fix::unsafe_edits(replace_attribute, [replace_target]));
checker.diagnostics.push(diagnostic); checker.diagnostics.push(diagnostic);
} }
(false, true) => { (false, true) => {
@ -122,18 +119,16 @@ pub(crate) fn incorrect_dict_iterator(checker: &mut Checker, stmt_for: &ast::Stm
}, },
func.range(), func.range(),
); );
if checker.patch(diagnostic.kind.rule()) { let replace_attribute = Edit::range_replacement("keys".to_string(), attr.range());
let replace_attribute = Edit::range_replacement("keys".to_string(), attr.range()); let replace_target = Edit::range_replacement(
let replace_target = Edit::range_replacement( pad(
pad( checker.locator().slice(key).to_string(),
checker.locator().slice(key).to_string(),
stmt_for.target.range(),
checker.locator(),
),
stmt_for.target.range(), stmt_for.target.range(),
); checker.locator(),
diagnostic.set_fix(Fix::unsafe_edits(replace_attribute, [replace_target])); ),
} stmt_for.target.range(),
);
diagnostic.set_fix(Fix::unsafe_edits(replace_attribute, [replace_target]));
checker.diagnostics.push(diagnostic); checker.diagnostics.push(diagnostic);
} }
} }

View file

@ -5,7 +5,6 @@ use ruff_python_ast::{self as ast, Arguments, Expr};
use ruff_text_size::TextRange; use ruff_text_size::TextRange;
use crate::checkers::ast::Checker; use crate::checkers::ast::Checker;
use crate::registry::AsRule;
/// ## What it does /// ## What it does
/// Checks for explicit casts to `list` on for-loop iterables. /// Checks for explicit casts to `list` on for-loop iterables.
@ -91,9 +90,7 @@ pub(crate) fn unnecessary_list_cast(checker: &mut Checker, iter: &Expr) {
.. ..
}) => { }) => {
let mut diagnostic = Diagnostic::new(UnnecessaryListCast, *list_range); let mut diagnostic = Diagnostic::new(UnnecessaryListCast, *list_range);
if checker.patch(diagnostic.kind.rule()) { diagnostic.set_fix(remove_cast(*list_range, *iterable_range));
diagnostic.set_fix(remove_cast(*list_range, *iterable_range));
}
checker.diagnostics.push(diagnostic); checker.diagnostics.push(diagnostic);
} }
Expr::Name(ast::ExprName { Expr::Name(ast::ExprName {
@ -119,9 +116,7 @@ pub(crate) fn unnecessary_list_cast(checker: &mut Checker, iter: &Expr) {
) { ) {
let mut diagnostic = let mut diagnostic =
Diagnostic::new(UnnecessaryListCast, *list_range); Diagnostic::new(UnnecessaryListCast, *list_range);
if checker.patch(diagnostic.kind.rule()) { diagnostic.set_fix(remove_cast(*list_range, *iterable_range));
diagnostic.set_fix(remove_cast(*list_range, *iterable_range));
}
checker.diagnostics.push(diagnostic); checker.diagnostics.push(diagnostic);
} }
} }

View file

@ -8,9 +8,6 @@ use ruff_macros::{derive_message_formats, violation};
use ruff_python_index::Indexer; use ruff_python_index::Indexer;
use ruff_source_file::Locator; use ruff_source_file::Locator;
use crate::registry::Rule;
use crate::settings::LinterSettings;
/// ## What it does /// ## What it does
/// Checks for compound statements (multiple statements on the same line). /// Checks for compound statements (multiple statements on the same line).
/// ///
@ -104,7 +101,6 @@ pub(crate) fn compound_statements(
lxr: &[LexResult], lxr: &[LexResult],
locator: &Locator, locator: &Locator,
indexer: &Indexer, indexer: &Indexer,
settings: &LinterSettings,
) { ) {
// Track the last seen instance of a variety of tokens. // Track the last seen instance of a variety of tokens.
let mut colon = None; let mut colon = None;
@ -169,14 +165,12 @@ pub(crate) fn compound_statements(
if let Some((start, end)) = semi { if let Some((start, end)) = semi {
let mut diagnostic = let mut diagnostic =
Diagnostic::new(UselessSemicolon, TextRange::new(start, end)); Diagnostic::new(UselessSemicolon, TextRange::new(start, end));
if settings.rules.should_fix(Rule::UselessSemicolon) { diagnostic.set_fix(Fix::safe_edit(Edit::deletion(
diagnostic.set_fix(Fix::safe_edit(Edit::deletion( indexer
indexer .preceded_by_continuations(start, locator)
.preceded_by_continuations(start, locator) .unwrap_or(start),
.unwrap_or(start), end,
end, )));
)));
};
diagnostics.push(diagnostic); diagnostics.push(diagnostic);
} }

Some files were not shown because too many files have changed in this diff Show more