Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

[RUF021]: Add an autofix #9449

Merged
merged 3 commits into from
Jan 9, 2024
Merged
Show file tree
Hide file tree
Changes from 2 commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
5 changes: 4 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,8 @@

a, b, c = 1, 0, 2
x = a or b and c # RUF021: => `a or (b and c)`
x = a or b and c # looooooooooooong comment but not long enough to prevent an autofix
x = a or b and c # looooooooong comment to prevent an autofix (line would be too long)

a, b, c = 0, 1, 2
y = a and b or c # RUF021: => `(a and b) or c`
Expand All @@ -30,7 +32,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 Down
Original file line number Diff line number Diff line change
@@ -1,10 +1,13 @@
use ruff_diagnostics::{Diagnostic, Violation};
use ruff_diagnostics::{Applicability, Diagnostic, Edit, Fix, FixAvailability, Violation};
use ruff_macros::{derive_message_formats, violation};
use ruff_python_ast as ast;
use ruff_python_ast::parenthesize::parenthesized_range;
use ruff_text_size::Ranged;
use ruff_source_file::Locator;
use ruff_text_size::{Ranged, TextRange};

use crate::checkers::ast::Checker;
use crate::line_width::LineWidthBuilder;
use crate::settings::LinterSettings;

/// ## What it does
/// Checks for chained operators where adding parentheses could improve the
Expand Down Expand Up @@ -37,12 +40,20 @@ use crate::checkers::ast::Checker;
pub struct ParenthesizeChainedOperators;

impl Violation for ParenthesizeChainedOperators {
const FIX_AVAILABILITY: FixAvailability = FixAvailability::Sometimes;

#[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) -> Option<String> {
Some(
"Put parentheses around the `and` subexpression inside the `or` expression".to_string(),
)
AlexWaygood marked this conversation as resolved.
Show resolved Hide resolved
}
}

/// RUF021
Expand Down Expand Up @@ -75,21 +86,47 @@ 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 mut diagnostic =
Diagnostic::new(ParenthesizeChainedOperators, source_range);
if is_single_line_expr_with_narrow_width(
locator,
source_range,
checker.settings,
) {
let new_source = format!("({})", locator.slice(source_range));
diagnostic.set_fix(Fix::applicable_edit(
Edit::range_replacement(new_source, source_range),
AlexWaygood marked this conversation as resolved.
Show resolved Hide resolved
Applicability::Safe,
));
AlexWaygood marked this conversation as resolved.
Show resolved Hide resolved
}
checker.diagnostics.push(diagnostic);
AlexWaygood marked this conversation as resolved.
Show resolved Hide resolved
}
}
_ => continue,
};
}
}

fn is_single_line_expr_with_narrow_width(
locator: &Locator,
source_range: TextRange,
settings: &LinterSettings,
) -> bool {
if locator.contains_line_break(source_range) {
return false;
}
let width_with_fix = LineWidthBuilder::new(settings.tab_size)
AlexWaygood marked this conversation as resolved.
Show resolved Hide resolved
.add_str(locator.full_line(source_range.start()).trim_end())
.add_str("()");
width_with_fix <= settings.line_length
}
AlexWaygood marked this conversation as resolved.
Show resolved Hide resolved
Original file line number Diff line number Diff line change
@@ -1,83 +1,214 @@
---
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 # looooooooooooong comment but not long enough to prevent an autofix
14 | x = a or b and c # looooooooong comment to prevent an autofix (line would be too long)
|
= help: Put parentheses around the `and` subexpression inside the `or` expression

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 # looooooooooooong comment but not long enough to prevent an autofix
14 14 | x = a or b and c # looooooooong comment to prevent an autofix (line would be too long)
15 15 |

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 # looooooooooooong comment but not long enough to prevent an autofix
| ^^^^^^^ RUF021
14 | x = a or b and c # looooooooong comment to prevent an autofix (line would be too long)
|
= help: Put parentheses around the `and` subexpression inside the `or` expression

ℹ 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 # looooooooooooong comment but not long enough to prevent an autofix
13 |+x = a or (b and c) # looooooooooooong comment but not long enough to prevent an autofix
14 14 | x = a or b and c # looooooooong comment to prevent an autofix (line would be too long)
15 15 |
16 16 | a, b, c = 0, 1, 2

RUF021.py:14:10: RUF021 Parenthesize `a and b` expressions when chaining `and` and `or` together, to make the precedence clear
|
12 | x = a or b and c # RUF021: => `a or (b and c)`
13 | x = a or b and c # looooooooooooong comment but not long enough to prevent an autofix
14 | x = a or b and c # looooooooong comment to prevent an autofix (line would be too long)
| ^^^^^^^ RUF021
15 |
16 | a, b, c = 0, 1, 2
|
= help: Put parentheses around the `and` subexpression inside the `or` expression

RUF021.py:17: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`
16 | a, b, c = 0, 1, 2
17 | y = a and b or c # RUF021: => `(a and b) or c`
| ^^^^^^^ RUF021
16 |
17 | a, b, c, d = 1, 2, 0, 3
18 |
19 | a, b, c, d = 1, 2, 0, 3
|
= help: Put parentheses around the `and` subexpression inside the `or` expression

RUF021.py:18:14: RUF021 Parenthesize `a and b` expressions when chaining `and` and `or` together, to make the precedence clear
ℹ Safe fix
14 14 | x = a or b and c # looooooooong comment to prevent an autofix (line would be too long)
15 15 |
16 16 | a, b, c = 0, 1, 2
17 |-y = a and b or c # RUF021: => `(a and b) or c`
17 |+y = (a and b) or c # RUF021: => `(a and b) or c`
18 18 |
19 19 | a, b, c, d = 1, 2, 0, 3
20 20 | if a or b or c and d: # RUF021: => `a or b or (c and d)`

RUF021.py:20: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)`
19 | a, b, c, d = 1, 2, 0, 3
20 | if a or b or c and d: # RUF021: => `a or b or (c and d)`
| ^^^^^^^ RUF021
19 | pass
21 | pass
|
= help: Put parentheses around the `and` subexpression inside the `or` expression

ℹ Safe fix
17 17 | y = a and b or c # RUF021: => `(a and b) or c`
18 18 |
19 19 | a, b, c, d = 1, 2, 0, 3
20 |-if a or b or c and d: # RUF021: => `a or b or (c and d)`
20 |+if a or b or (c and d): # RUF021: => `a or b or (c and d)`
21 21 | pass
22 22 |
23 23 | a, b, c, d = 0, 0, 2, 3

RUF021.py:25:11: RUF021 Parenthesize `a and b` expressions when chaining `and` and `or` together, to make the precedence clear
RUF021.py:27: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`
| ^^^^^^^ RUF021
25 | if bool():
26 | pass
27 | elif a or b and c or d: # RUF021: => `a or (b and c) or d`
| ^^^^^^^ RUF021
28 | pass
|
= help: Put parentheses around the `and` subexpression inside the `or` expression

RUF021.py:29:7: RUF021 Parenthesize `a and b` expressions when chaining `and` and `or` together, to make the precedence clear
ℹ Safe fix
24 24 |
25 25 | if bool():
26 26 | pass
27 |-elif a or b and c or d: # RUF021: => `a or (b and c) or d`
27 |+elif a or (b and c) or d: # RUF021: => `a or (b and c) or d`
28 28 | pass
29 29 |
30 30 | a, b, c, d = 0, 1, 0, 2

RUF021.py:31: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)`
30 | a, b, c, d = 0, 1, 0, 2
31 | while a and b or c and d: # RUF021: => `(and b) or (c and d)`
| ^^^^^^^ RUF021
30 | pass
32 | pass
|
= help: Put parentheses around the `and` subexpression inside the `or` expression

ℹ Safe fix
28 28 | pass
29 29 |
30 30 | a, b, c, d = 0, 1, 0, 2
31 |-while a and b or c and d: # RUF021: => `(and b) or (c and d)`
31 |+while (a and b) or c and d: # RUF021: => `(and b) or (c and d)`
32 32 | pass
33 33 |
34 34 | 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:31: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)`
30 | a, b, c, d = 0, 1, 0, 2
31 | while a and b or c and d: # RUF021: => `(and b) or (c and d)`
| ^^^^^^^ RUF021
30 | pass
32 | pass
|
= help: Put parentheses around the `and` subexpression inside the `or` expression

ℹ Safe fix
28 28 | pass
29 29 |
30 30 | a, b, c, d = 0, 1, 0, 2
31 |-while a and b or c and d: # RUF021: => `(and b) or (c and d)`
31 |+while a and b or (c and d): # RUF021: => `(and b) or (c and d)`
32 32 | pass
33 33 |
34 34 | b, c, d, e = 2, 3, 0, 4

RUF021.py:33:44: RUF021 Parenthesize `a and b` expressions when chaining `and` and `or` together, to make the precedence clear
RUF021.py:36: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)`
34 | b, c, d, e = 2, 3, 0, 4
35 | # RUF021: => `a or b or c or (d and e)`:
36 | 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
37 |
38 | a, b, c, d = 0, 1, 3, 0
|
= help: Put parentheses around the `and` subexpression inside the `or` expression

ℹ Safe fix
33 33 |
34 34 | b, c, d, e = 2, 3, 0, 4
35 35 | # RUF021: => `a or b or c or (d and e)`:
36 |-z = [a for a in range(5) if a or b or c or d and e]
36 |+z = [a for a in range(5) if a or b or c or (d and e)]
37 37 |
38 38 | a, b, c, d = 0, 1, 3, 0
39 39 | 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:39: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`
38 | a, b, c, d = 0, 1, 3, 0
39 | 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`
40 |
41 | if (not a) and b or c or d: # RUF021: => `((not a) and b) or c or d`
|
= help: Put parentheses around the `and` subexpression inside the `or` expression

RUF021.py:38:4: RUF021 Parenthesize `a and b` expressions when chaining `and` and `or` together, to make the precedence clear
ℹ Safe fix
36 36 | z = [a for a in range(5) if a or b or c or d and e]
37 37 |
38 38 | a, b, c, d = 0, 1, 3, 0
39 |-assert not a and b or c or d # RUF021: => `(not a and b) or c or d`
39 |+assert (not a and b) or c or d # RUF021: => `(not a and b) or c or d`
40 40 |
41 41 | if (not a) and b or c or d: # RUF021: => `((not a) and b) or c or d`
42 42 | if (not a and b) or c or d: # OK

RUF021.py:41: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`
39 | assert not a and b or c or d # RUF021: => `(not a and b) or c or d`
40 |
41 | 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
42 | if (not a and b) or c or d: # OK
43 | pass
|
= help: Put parentheses around the `and` subexpression inside the `or` expression

ℹ Safe fix
38 38 | a, b, c, d = 0, 1, 3, 0
39 39 | assert not a and b or c or d # RUF021: => `(not a and b) or c or d`
40 40 |
41 |-if (not a) and b or c or d: # RUF021: => `((not a) and b) or c or d`
41 |+if ((not a) and b) or c or d: # RUF021: => `((not a) and b) or c or d`
42 42 | if (not a and b) or c or d: # OK
43 43 | pass
44 44 |


Loading