Skip to content

Commit

Permalink
[RUF021]: Add an autofix (#9449)
Browse files Browse the repository at this point in the history
## Summary

This adds an autofix for the newly added RUF021 (see #9440).
  • Loading branch information
AlexWaygood authored Jan 9, 2024
1 parent ad1ca72 commit 6be7332
Show file tree
Hide file tree
Showing 3 changed files with 249 additions and 49 deletions.
18 changes: 17 additions & 1 deletion crates/ruff_linter/resources/test/fixtures/ruff/RUF021.py
Original file line number Diff line number Diff line change
Expand Up @@ -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`
Expand All @@ -30,7 +31,8 @@
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`
Expand All @@ -39,6 +41,20 @@
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
#############################################
Expand Down
Original file line number Diff line number Diff line change
@@ -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;
Expand Down Expand Up @@ -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
Expand Down Expand Up @@ -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,
Expand Down
Original file line number Diff line number Diff line change
@@ -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
|
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
|
14 | a, b, c = 0, 1, 2
15 | y = a and b or c # RUF021: => `(a and b) or c`
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

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:18:14: RUF021 Parenthesize `a and b` expressions when chaining `and` and `or` together, to make the precedence clear
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

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:29:7: RUF021 Parenthesize `a and b` expressions when chaining `and` and `or` together, to make the precedence clear
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

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:29:18: RUF021 Parenthesize `a and b` expressions when chaining `and` and `or` together, to make the precedence clear
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

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:36:8: RUF021 Parenthesize `a and b` expressions when chaining `and` and `or` together, to make the precedence clear
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

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:38:4: RUF021 Parenthesize `a and b` expressions when chaining `and` and `or` together, to make the precedence clear
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 | ):


0 comments on commit 6be7332

Please sign in to comment.