diff --git a/crates/ruff_linter/resources/test/fixtures/ruff/RUF021.py b/crates/ruff_linter/resources/test/fixtures/ruff/RUF021.py index 4a77355f10..b10f3fae24 100644 --- a/crates/ruff_linter/resources/test/fixtures/ruff/RUF021.py +++ b/crates/ruff_linter/resources/test/fixtures/ruff/RUF021.py @@ -10,6 +10,7 @@ a, b, c = 1, 0, 2 x = a or b and c # RUF021: => `a or (b and c)` +x = a or b and c # looooooooooooooooooooooooooooooong comment but it won't prevent an autofix a, b, c = 0, 1, 2 y = a and b or c # RUF021: => `(a and b) or c` @@ -30,7 +31,8 @@ while a and b or c and d: # RUF021: => `(and b) or (c and d)` pass b, c, d, e = 2, 3, 0, 4 -z = [a for a in range(5) if a or b or c or d and e] # RUF021: => `a or b or c or (d and e)` +# RUF021: => `a or b or c or (d and e)`: +z = [a for a in range(5) if a or b or c or d and e] a, b, c, d = 0, 1, 3, 0 assert not a and b or c or d # RUF021: => `(not a and b) or c or d` @@ -39,6 +41,20 @@ if (not a) and b or c or d: # RUF021: => `((not a) and b) or c or d` if (not a and b) or c or d: # OK pass +if ( + some_reasonably_long_condition + or some_other_reasonably_long_condition + and some_third_reasonably_long_condition + or some_fourth_reasonably_long_condition + and some_fifth_reasonably_long_condition + # a commment + and some_sixth_reasonably_long_condition + and some_seventh_reasonably_long_condition + # another comment + or some_eighth_reasonably_long_condition +): + pass + ############################################# # If they're all the same operator, it's fine ############################################# diff --git a/crates/ruff_linter/src/rules/ruff/rules/parenthesize_logical_operators.rs b/crates/ruff_linter/src/rules/ruff/rules/parenthesize_logical_operators.rs index 0373a440c2..4a96af1cdf 100644 --- a/crates/ruff_linter/src/rules/ruff/rules/parenthesize_logical_operators.rs +++ b/crates/ruff_linter/src/rules/ruff/rules/parenthesize_logical_operators.rs @@ -1,4 +1,4 @@ -use ruff_diagnostics::{Diagnostic, Violation}; +use ruff_diagnostics::{AlwaysFixableViolation, Diagnostic, Edit, Fix}; use ruff_macros::{derive_message_formats, violation}; use ruff_python_ast as ast; use ruff_python_ast::parenthesize::parenthesized_range; @@ -36,13 +36,17 @@ use crate::checkers::ast::Checker; #[violation] pub struct ParenthesizeChainedOperators; -impl Violation for ParenthesizeChainedOperators { +impl AlwaysFixableViolation for ParenthesizeChainedOperators { #[derive_message_formats] fn message(&self) -> String { format!( "Parenthesize `a and b` expressions when chaining `and` and `or` together, to make the precedence clear" ) } + + fn fix_title(&self) -> String { + "Parenthesize the `and` subexpression".to_string() + } } /// RUF021 @@ -75,18 +79,22 @@ pub(crate) fn parenthesize_chained_logical_operators( .. }, ) => { + let locator = checker.locator(); + let source_range = bool_op.range(); if parenthesized_range( bool_op.into(), expr.into(), checker.indexer().comment_ranges(), - checker.locator().contents(), + locator.contents(), ) .is_none() { - checker.diagnostics.push(Diagnostic::new( - ParenthesizeChainedOperators, - bool_op.range(), - )); + let new_source = format!("({})", locator.slice(source_range)); + let edit = Edit::range_replacement(new_source, source_range); + checker.diagnostics.push( + Diagnostic::new(ParenthesizeChainedOperators, source_range) + .with_fix(Fix::safe_edit(edit)), + ); } } _ => continue, diff --git a/crates/ruff_linter/src/rules/ruff/snapshots/ruff_linter__rules__ruff__tests__RUF021_RUF021.py.snap b/crates/ruff_linter/src/rules/ruff/snapshots/ruff_linter__rules__ruff__tests__RUF021_RUF021.py.snap index a0c8166a9b..06850c81d2 100644 --- a/crates/ruff_linter/src/rules/ruff/snapshots/ruff_linter__rules__ruff__tests__RUF021_RUF021.py.snap +++ b/crates/ruff_linter/src/rules/ruff/snapshots/ruff_linter__rules__ruff__tests__RUF021_RUF021.py.snap @@ -1,83 +1,259 @@ --- source: crates/ruff_linter/src/rules/ruff/mod.rs --- -RUF021.py:12:10: RUF021 Parenthesize `a and b` expressions when chaining `and` and `or` together, to make the precedence clear +RUF021.py:12:10: RUF021 [*] Parenthesize `a and b` expressions when chaining `and` and `or` together, to make the precedence clear | 11 | a, b, c = 1, 0, 2 12 | x = a or b and c # RUF021: => `a or (b and c)` | ^^^^^^^ RUF021 -13 | -14 | a, b, c = 0, 1, 2 +13 | x = a or b and c # looooooooooooooooooooooooooooooong comment but it won't prevent an autofix | + = help: Parenthesize the `and` subexpression -RUF021.py:15:5: RUF021 Parenthesize `a and b` expressions when chaining `and` and `or` together, to make the precedence clear +ℹ Safe fix +9 9 | # as part of a chain. +10 10 | +11 11 | a, b, c = 1, 0, 2 +12 |-x = a or b and c # RUF021: => `a or (b and c)` + 12 |+x = a or (b and c) # RUF021: => `a or (b and c)` +13 13 | x = a or b and c # looooooooooooooooooooooooooooooong comment but it won't prevent an autofix +14 14 | +15 15 | a, b, c = 0, 1, 2 + +RUF021.py:13:10: RUF021 [*] Parenthesize `a and b` expressions when chaining `and` and `or` together, to make the precedence clear | -14 | a, b, c = 0, 1, 2 -15 | y = a and b or c # RUF021: => `(a and b) or c` +11 | a, b, c = 1, 0, 2 +12 | x = a or b and c # RUF021: => `a or (b and c)` +13 | x = a or b and c # looooooooooooooooooooooooooooooong comment but it won't prevent an autofix + | ^^^^^^^ RUF021 +14 | +15 | a, b, c = 0, 1, 2 + | + = help: Parenthesize the `and` subexpression + +ℹ Safe fix +10 10 | +11 11 | a, b, c = 1, 0, 2 +12 12 | x = a or b and c # RUF021: => `a or (b and c)` +13 |-x = a or b and c # looooooooooooooooooooooooooooooong comment but it won't prevent an autofix + 13 |+x = a or (b and c) # looooooooooooooooooooooooooooooong comment but it won't prevent an autofix +14 14 | +15 15 | a, b, c = 0, 1, 2 +16 16 | y = a and b or c # RUF021: => `(a and b) or c` + +RUF021.py:16:5: RUF021 [*] Parenthesize `a and b` expressions when chaining `and` and `or` together, to make the precedence clear + | +15 | a, b, c = 0, 1, 2 +16 | y = a and b or c # RUF021: => `(a and b) or c` | ^^^^^^^ RUF021 -16 | -17 | a, b, c, d = 1, 2, 0, 3 +17 | +18 | a, b, c, d = 1, 2, 0, 3 | + = help: Parenthesize the `and` subexpression -RUF021.py:18:14: RUF021 Parenthesize `a and b` expressions when chaining `and` and `or` together, to make the precedence clear +ℹ Safe fix +13 13 | x = a or b and c # looooooooooooooooooooooooooooooong comment but it won't prevent an autofix +14 14 | +15 15 | a, b, c = 0, 1, 2 +16 |-y = a and b or c # RUF021: => `(a and b) or c` + 16 |+y = (a and b) or c # RUF021: => `(a and b) or c` +17 17 | +18 18 | a, b, c, d = 1, 2, 0, 3 +19 19 | if a or b or c and d: # RUF021: => `a or b or (c and d)` + +RUF021.py:19:14: RUF021 [*] Parenthesize `a and b` expressions when chaining `and` and `or` together, to make the precedence clear | -17 | a, b, c, d = 1, 2, 0, 3 -18 | if a or b or c and d: # RUF021: => `a or b or (c and d)` +18 | a, b, c, d = 1, 2, 0, 3 +19 | if a or b or c and d: # RUF021: => `a or b or (c and d)` | ^^^^^^^ RUF021 -19 | pass +20 | pass | + = help: Parenthesize the `and` subexpression -RUF021.py:25:11: RUF021 Parenthesize `a and b` expressions when chaining `and` and `or` together, to make the precedence clear +ℹ Safe fix +16 16 | y = a and b or c # RUF021: => `(a and b) or c` +17 17 | +18 18 | a, b, c, d = 1, 2, 0, 3 +19 |-if a or b or c and d: # RUF021: => `a or b or (c and d)` + 19 |+if a or b or (c and d): # RUF021: => `a or b or (c and d)` +20 20 | pass +21 21 | +22 22 | a, b, c, d = 0, 0, 2, 3 + +RUF021.py:26:11: RUF021 [*] Parenthesize `a and b` expressions when chaining `and` and `or` together, to make the precedence clear | -23 | if bool(): -24 | pass -25 | elif a or b and c or d: # RUF021: => `a or (b and c) or d` +24 | if bool(): +25 | pass +26 | elif a or b and c or d: # RUF021: => `a or (b and c) or d` | ^^^^^^^ RUF021 -26 | pass +27 | pass | + = help: Parenthesize the `and` subexpression -RUF021.py:29:7: RUF021 Parenthesize `a and b` expressions when chaining `and` and `or` together, to make the precedence clear +ℹ Safe fix +23 23 | +24 24 | if bool(): +25 25 | pass +26 |-elif a or b and c or d: # RUF021: => `a or (b and c) or d` + 26 |+elif a or (b and c) or d: # RUF021: => `a or (b and c) or d` +27 27 | pass +28 28 | +29 29 | a, b, c, d = 0, 1, 0, 2 + +RUF021.py:30:7: RUF021 [*] Parenthesize `a and b` expressions when chaining `and` and `or` together, to make the precedence clear | -28 | a, b, c, d = 0, 1, 0, 2 -29 | while a and b or c and d: # RUF021: => `(and b) or (c and d)` +29 | a, b, c, d = 0, 1, 0, 2 +30 | while a and b or c and d: # RUF021: => `(and b) or (c and d)` | ^^^^^^^ RUF021 -30 | pass +31 | pass | + = help: Parenthesize the `and` subexpression -RUF021.py:29:18: RUF021 Parenthesize `a and b` expressions when chaining `and` and `or` together, to make the precedence clear +ℹ Safe fix +27 27 | pass +28 28 | +29 29 | a, b, c, d = 0, 1, 0, 2 +30 |-while a and b or c and d: # RUF021: => `(and b) or (c and d)` + 30 |+while (a and b) or c and d: # RUF021: => `(and b) or (c and d)` +31 31 | pass +32 32 | +33 33 | b, c, d, e = 2, 3, 0, 4 + +RUF021.py:30:18: RUF021 [*] Parenthesize `a and b` expressions when chaining `and` and `or` together, to make the precedence clear | -28 | a, b, c, d = 0, 1, 0, 2 -29 | while a and b or c and d: # RUF021: => `(and b) or (c and d)` +29 | a, b, c, d = 0, 1, 0, 2 +30 | while a and b or c and d: # RUF021: => `(and b) or (c and d)` | ^^^^^^^ RUF021 -30 | pass +31 | pass | + = help: Parenthesize the `and` subexpression -RUF021.py:33:44: RUF021 Parenthesize `a and b` expressions when chaining `and` and `or` together, to make the precedence clear +ℹ Safe fix +27 27 | pass +28 28 | +29 29 | a, b, c, d = 0, 1, 0, 2 +30 |-while a and b or c and d: # RUF021: => `(and b) or (c and d)` + 30 |+while a and b or (c and d): # RUF021: => `(and b) or (c and d)` +31 31 | pass +32 32 | +33 33 | b, c, d, e = 2, 3, 0, 4 + +RUF021.py:35:44: RUF021 [*] Parenthesize `a and b` expressions when chaining `and` and `or` together, to make the precedence clear | -32 | b, c, d, e = 2, 3, 0, 4 -33 | z = [a for a in range(5) if a or b or c or d and e] # RUF021: => `a or b or c or (d and e)` +33 | b, c, d, e = 2, 3, 0, 4 +34 | # RUF021: => `a or b or c or (d and e)`: +35 | z = [a for a in range(5) if a or b or c or d and e] | ^^^^^^^ RUF021 -34 | -35 | a, b, c, d = 0, 1, 3, 0 +36 | +37 | a, b, c, d = 0, 1, 3, 0 | + = help: Parenthesize the `and` subexpression -RUF021.py:36:8: RUF021 Parenthesize `a and b` expressions when chaining `and` and `or` together, to make the precedence clear +ℹ Safe fix +32 32 | +33 33 | b, c, d, e = 2, 3, 0, 4 +34 34 | # RUF021: => `a or b or c or (d and e)`: +35 |-z = [a for a in range(5) if a or b or c or d and e] + 35 |+z = [a for a in range(5) if a or b or c or (d and e)] +36 36 | +37 37 | a, b, c, d = 0, 1, 3, 0 +38 38 | assert not a and b or c or d # RUF021: => `(not a and b) or c or d` + +RUF021.py:38:8: RUF021 [*] Parenthesize `a and b` expressions when chaining `and` and `or` together, to make the precedence clear | -35 | a, b, c, d = 0, 1, 3, 0 -36 | assert not a and b or c or d # RUF021: => `(not a and b) or c or d` +37 | a, b, c, d = 0, 1, 3, 0 +38 | assert not a and b or c or d # RUF021: => `(not a and b) or c or d` | ^^^^^^^^^^^ RUF021 -37 | -38 | if (not a) and b or c or d: # RUF021: => `((not a) and b) or c or d` +39 | +40 | if (not a) and b or c or d: # RUF021: => `((not a) and b) or c or d` | + = help: Parenthesize the `and` subexpression -RUF021.py:38:4: RUF021 Parenthesize `a and b` expressions when chaining `and` and `or` together, to make the precedence clear +ℹ Safe fix +35 35 | z = [a for a in range(5) if a or b or c or d and e] +36 36 | +37 37 | a, b, c, d = 0, 1, 3, 0 +38 |-assert not a and b or c or d # RUF021: => `(not a and b) or c or d` + 38 |+assert (not a and b) or c or d # RUF021: => `(not a and b) or c or d` +39 39 | +40 40 | if (not a) and b or c or d: # RUF021: => `((not a) and b) or c or d` +41 41 | if (not a and b) or c or d: # OK + +RUF021.py:40:4: RUF021 [*] Parenthesize `a and b` expressions when chaining `and` and `or` together, to make the precedence clear | -36 | assert not a and b or c or d # RUF021: => `(not a and b) or c or d` -37 | -38 | if (not a) and b or c or d: # RUF021: => `((not a) and b) or c or d` +38 | assert not a and b or c or d # RUF021: => `(not a and b) or c or d` +39 | +40 | if (not a) and b or c or d: # RUF021: => `((not a) and b) or c or d` | ^^^^^^^^^^^^^ RUF021 -39 | if (not a and b) or c or d: # OK -40 | pass +41 | if (not a and b) or c or d: # OK +42 | pass | + = help: Parenthesize the `and` subexpression + +ℹ Safe fix +37 37 | a, b, c, d = 0, 1, 3, 0 +38 38 | assert not a and b or c or d # RUF021: => `(not a and b) or c or d` +39 39 | +40 |-if (not a) and b or c or d: # RUF021: => `((not a) and b) or c or d` + 40 |+if ((not a) and b) or c or d: # RUF021: => `((not a) and b) or c or d` +41 41 | if (not a and b) or c or d: # OK +42 42 | pass +43 43 | + +RUF021.py:46:8: RUF021 [*] Parenthesize `a and b` expressions when chaining `and` and `or` together, to make the precedence clear + | +44 | if ( +45 | some_reasonably_long_condition +46 | or some_other_reasonably_long_condition + | ________^ +47 | | and some_third_reasonably_long_condition + | |____________________________________________^ RUF021 +48 | or some_fourth_reasonably_long_condition +49 | and some_fifth_reasonably_long_condition + | + = help: Parenthesize the `and` subexpression + +ℹ Safe fix +43 43 | +44 44 | if ( +45 45 | some_reasonably_long_condition +46 |- or some_other_reasonably_long_condition +47 |- and some_third_reasonably_long_condition + 46 |+ or (some_other_reasonably_long_condition + 47 |+ and some_third_reasonably_long_condition) +48 48 | or some_fourth_reasonably_long_condition +49 49 | and some_fifth_reasonably_long_condition +50 50 | # a commment + +RUF021.py:48:8: RUF021 [*] Parenthesize `a and b` expressions when chaining `and` and `or` together, to make the precedence clear + | +46 | or some_other_reasonably_long_condition +47 | and some_third_reasonably_long_condition +48 | or some_fourth_reasonably_long_condition + | ________^ +49 | | and some_fifth_reasonably_long_condition +50 | | # a commment +51 | | and some_sixth_reasonably_long_condition +52 | | and some_seventh_reasonably_long_condition + | |______________________________________________^ RUF021 +53 | # another comment +54 | or some_eighth_reasonably_long_condition + | + = help: Parenthesize the `and` subexpression + +ℹ Safe fix +45 45 | some_reasonably_long_condition +46 46 | or some_other_reasonably_long_condition +47 47 | and some_third_reasonably_long_condition +48 |- or some_fourth_reasonably_long_condition + 48 |+ or (some_fourth_reasonably_long_condition +49 49 | and some_fifth_reasonably_long_condition +50 50 | # a commment +51 51 | and some_sixth_reasonably_long_condition +52 |- and some_seventh_reasonably_long_condition + 52 |+ and some_seventh_reasonably_long_condition) +53 53 | # another comment +54 54 | or some_eighth_reasonably_long_condition +55 55 | ):