Include actual conditions in E712 diagnostics (#10254)

## Summary

Changes the generic recommendation to replace

```python
if foo == True: ...
```

with `if cond:` to `if foo:`.

Still uses a generic message for compound comparisons as a specific
message starts to become confusing. For example,

```python
if foo == True != False: ...
```

produces two recommendations, one of which would recommend `if True:`,
which is confusing.

Resolves [recommendation in a previous
PR](https://github.com/astral-sh/ruff/pull/8613/files#r1514915070).

## Test Plan

`cargo nextest run`
This commit is contained in:
Tom Kuson 2024-03-08 01:20:56 +00:00 committed by GitHub
parent 57be3fce90
commit 72c9f7e4c9
No known key found for this signature in database
GPG key ID: B5690EEEBB952194
3 changed files with 120 additions and 66 deletions

View file

@ -9,6 +9,7 @@ use ruff_text_size::Ranged;
use crate::checkers::ast::Checker; use crate::checkers::ast::Checker;
use crate::codes::Rule; use crate::codes::Rule;
use crate::fix::snippet::SourceCodeSnippet;
#[derive(Debug, PartialEq, Eq, Copy, Clone)] #[derive(Debug, PartialEq, Eq, Copy, Clone)]
enum EqCmpOp { enum EqCmpOp {
@ -102,39 +103,52 @@ impl AlwaysFixableViolation for NoneComparison {
/// ///
/// [PEP 8]: https://peps.python.org/pep-0008/#programming-recommendations /// [PEP 8]: https://peps.python.org/pep-0008/#programming-recommendations
#[violation] #[violation]
pub struct TrueFalseComparison(bool, EqCmpOp); pub struct TrueFalseComparison {
value: bool,
op: EqCmpOp,
cond: Option<SourceCodeSnippet>,
}
impl AlwaysFixableViolation for TrueFalseComparison { impl AlwaysFixableViolation for TrueFalseComparison {
#[derive_message_formats] #[derive_message_formats]
fn message(&self) -> String { fn message(&self) -> String {
let TrueFalseComparison(value, op) = self; let TrueFalseComparison { value, op, cond } = self;
let Some(cond) = cond else {
return "Avoid equality comparisons to `True` or `False`".to_string();
};
let cond = cond.truncated_display();
match (value, op) { match (value, op) {
(true, EqCmpOp::Eq) => { (true, EqCmpOp::Eq) => {
format!("Avoid equality comparisons to `True`; use `if cond:` for truth checks") format!("Avoid equality comparisons to `True`; use `if {cond}:` for truth checks")
} }
(true, EqCmpOp::NotEq) => { (true, EqCmpOp::NotEq) => {
format!( format!(
"Avoid inequality comparisons to `True`; use `if not cond:` for false checks" "Avoid inequality comparisons to `True`; use `if not {cond}:` for false checks"
) )
} }
(false, EqCmpOp::Eq) => { (false, EqCmpOp::Eq) => {
format!( format!(
"Avoid equality comparisons to `False`; use `if not cond:` for false checks" "Avoid equality comparisons to `False`; use `if not {cond}:` for false checks"
) )
} }
(false, EqCmpOp::NotEq) => { (false, EqCmpOp::NotEq) => {
format!("Avoid inequality comparisons to `False`; use `if cond:` for truth checks") format!(
"Avoid inequality comparisons to `False`; use `if {cond}:` for truth checks"
)
} }
} }
} }
fn fix_title(&self) -> String { fn fix_title(&self) -> String {
let TrueFalseComparison(value, op) = self; let TrueFalseComparison { value, op, cond } = self;
let Some(cond) = cond.as_ref().and_then(|cond| cond.full_display()) else {
return "Replace comparison".to_string();
};
match (value, op) { match (value, op) {
(true, EqCmpOp::Eq) => "Replace with `cond`".to_string(), (true, EqCmpOp::Eq) => format!("Replace with `{cond}`"),
(true, EqCmpOp::NotEq) => "Replace with `not cond`".to_string(), (true, EqCmpOp::NotEq) => format!("Replace with `not {cond}`"),
(false, EqCmpOp::Eq) => "Replace with `not cond`".to_string(), (false, EqCmpOp::Eq) => format!("Replace with `not {cond}`"),
(false, EqCmpOp::NotEq) => "Replace with `cond`".to_string(), (false, EqCmpOp::NotEq) => format!("Replace with `{cond}`"),
} }
} }
} }
@ -178,17 +192,35 @@ pub(crate) fn literal_comparisons(checker: &mut Checker, compare: &ast::ExprComp
if let Expr::BooleanLiteral(ast::ExprBooleanLiteral { value, .. }) = comparator { if let Expr::BooleanLiteral(ast::ExprBooleanLiteral { value, .. }) = comparator {
match op { match op {
EqCmpOp::Eq => { EqCmpOp::Eq => {
let cond = if compare.ops.len() == 1 {
Some(SourceCodeSnippet::from_str(checker.locator().slice(next)))
} else {
None
};
let diagnostic = Diagnostic::new( let diagnostic = Diagnostic::new(
TrueFalseComparison(*value, op), TrueFalseComparison {
comparator.range(), value: *value,
op,
cond,
},
compare.range(),
); );
bad_ops.insert(0, CmpOp::Is); bad_ops.insert(0, CmpOp::Is);
diagnostics.push(diagnostic); diagnostics.push(diagnostic);
} }
EqCmpOp::NotEq => { EqCmpOp::NotEq => {
let cond = if compare.ops.len() == 1 {
Some(SourceCodeSnippet::from_str(checker.locator().slice(next)))
} else {
None
};
let diagnostic = Diagnostic::new( let diagnostic = Diagnostic::new(
TrueFalseComparison(*value, op), TrueFalseComparison {
comparator.range(), value: *value,
op,
cond,
},
compare.range(),
); );
bad_ops.insert(0, CmpOp::IsNot); bad_ops.insert(0, CmpOp::IsNot);
diagnostics.push(diagnostic); diagnostics.push(diagnostic);
@ -231,14 +263,40 @@ pub(crate) fn literal_comparisons(checker: &mut Checker, compare: &ast::ExprComp
if let Expr::BooleanLiteral(ast::ExprBooleanLiteral { value, .. }) = next { if let Expr::BooleanLiteral(ast::ExprBooleanLiteral { value, .. }) = next {
match op { match op {
EqCmpOp::Eq => { EqCmpOp::Eq => {
let diagnostic = let cond = if compare.ops.len() == 1 {
Diagnostic::new(TrueFalseComparison(*value, op), next.range()); Some(SourceCodeSnippet::from_str(
checker.locator().slice(comparator),
))
} else {
None
};
let diagnostic = Diagnostic::new(
TrueFalseComparison {
value: *value,
op,
cond,
},
compare.range(),
);
bad_ops.insert(index, CmpOp::Is); bad_ops.insert(index, CmpOp::Is);
diagnostics.push(diagnostic); diagnostics.push(diagnostic);
} }
EqCmpOp::NotEq => { EqCmpOp::NotEq => {
let diagnostic = let cond = if compare.ops.len() == 1 {
Diagnostic::new(TrueFalseComparison(*value, op), next.range()); Some(SourceCodeSnippet::from_str(
checker.locator().slice(comparator),
))
} else {
None
};
let diagnostic = Diagnostic::new(
TrueFalseComparison {
value: *value,
op,
cond,
},
compare.range(),
);
bad_ops.insert(index, CmpOp::IsNot); bad_ops.insert(index, CmpOp::IsNot);
diagnostics.push(diagnostic); diagnostics.push(diagnostic);
} }

View file

@ -1,15 +1,15 @@
--- ---
source: crates/ruff_linter/src/rules/pycodestyle/mod.rs source: crates/ruff_linter/src/rules/pycodestyle/mod.rs
--- ---
E712.py:2:11: E712 [*] Avoid equality comparisons to `True`; use `if cond:` for truth checks E712.py:2:4: E712 [*] Avoid equality comparisons to `True`; use `if res:` for truth checks
| |
1 | #: E712 1 | #: E712
2 | if res == True: 2 | if res == True:
| ^^^^ E712 | ^^^^^^^^^^^ E712
3 | pass 3 | pass
4 | #: E712 4 | #: E712
| |
= help: Replace with `cond` = help: Replace with `res`
Unsafe fix Unsafe fix
1 1 | #: E712 1 1 | #: E712
@ -19,16 +19,16 @@ E712.py:2:11: E712 [*] Avoid equality comparisons to `True`; use `if cond:` for
4 4 | #: E712 4 4 | #: E712
5 5 | if res != False: 5 5 | if res != False:
E712.py:5:11: E712 [*] Avoid inequality comparisons to `False`; use `if cond:` for truth checks E712.py:5:4: E712 [*] Avoid inequality comparisons to `False`; use `if res:` for truth checks
| |
3 | pass 3 | pass
4 | #: E712 4 | #: E712
5 | if res != False: 5 | if res != False:
| ^^^^^ E712 | ^^^^^^^^^^^^ E712
6 | pass 6 | pass
7 | #: E712 7 | #: E712
| |
= help: Replace with `cond` = help: Replace with `res`
Unsafe fix Unsafe fix
2 2 | if res == True: 2 2 | if res == True:
@ -40,16 +40,16 @@ E712.py:5:11: E712 [*] Avoid inequality comparisons to `False`; use `if cond:` f
7 7 | #: E712 7 7 | #: E712
8 8 | if True != res: 8 8 | if True != res:
E712.py:8:4: E712 [*] Avoid inequality comparisons to `True`; use `if not cond:` for false checks E712.py:8:4: E712 [*] Avoid inequality comparisons to `True`; use `if not res:` for false checks
| |
6 | pass 6 | pass
7 | #: E712 7 | #: E712
8 | if True != res: 8 | if True != res:
| ^^^^ E712 | ^^^^^^^^^^^ E712
9 | pass 9 | pass
10 | #: E712 10 | #: E712
| |
= help: Replace with `not cond` = help: Replace with `not res`
Unsafe fix Unsafe fix
5 5 | if res != False: 5 5 | if res != False:
@ -61,16 +61,16 @@ E712.py:8:4: E712 [*] Avoid inequality comparisons to `True`; use `if not cond:`
10 10 | #: E712 10 10 | #: E712
11 11 | if False == res: 11 11 | if False == res:
E712.py:11:4: E712 [*] Avoid equality comparisons to `False`; use `if not cond:` for false checks E712.py:11:4: E712 [*] Avoid equality comparisons to `False`; use `if not res:` for false checks
| |
9 | pass 9 | pass
10 | #: E712 10 | #: E712
11 | if False == res: 11 | if False == res:
| ^^^^^ E712 | ^^^^^^^^^^^^ E712
12 | pass 12 | pass
13 | #: E712 13 | #: E712
| |
= help: Replace with `not cond` = help: Replace with `not res`
Unsafe fix Unsafe fix
8 8 | if True != res: 8 8 | if True != res:
@ -82,16 +82,16 @@ E712.py:11:4: E712 [*] Avoid equality comparisons to `False`; use `if not cond:`
13 13 | #: E712 13 13 | #: E712
14 14 | if res[1] == True: 14 14 | if res[1] == True:
E712.py:14:14: E712 [*] Avoid equality comparisons to `True`; use `if cond:` for truth checks E712.py:14:4: E712 [*] Avoid equality comparisons to `True`; use `if res[1]:` for truth checks
| |
12 | pass 12 | pass
13 | #: E712 13 | #: E712
14 | if res[1] == True: 14 | if res[1] == True:
| ^^^^ E712 | ^^^^^^^^^^^^^^ E712
15 | pass 15 | pass
16 | #: E712 16 | #: E712
| |
= help: Replace with `cond` = help: Replace with `res[1]`
Unsafe fix Unsafe fix
11 11 | if False == res: 11 11 | if False == res:
@ -103,16 +103,16 @@ E712.py:14:14: E712 [*] Avoid equality comparisons to `True`; use `if cond:` for
16 16 | #: E712 16 16 | #: E712
17 17 | if res[1] != False: 17 17 | if res[1] != False:
E712.py:17:14: E712 [*] Avoid inequality comparisons to `False`; use `if cond:` for truth checks E712.py:17:4: E712 [*] Avoid inequality comparisons to `False`; use `if res[1]:` for truth checks
| |
15 | pass 15 | pass
16 | #: E712 16 | #: E712
17 | if res[1] != False: 17 | if res[1] != False:
| ^^^^^ E712 | ^^^^^^^^^^^^^^^ E712
18 | pass 18 | pass
19 | #: E712 19 | #: E712
| |
= help: Replace with `cond` = help: Replace with `res[1]`
Unsafe fix Unsafe fix
14 14 | if res[1] == True: 14 14 | if res[1] == True:
@ -124,12 +124,12 @@ E712.py:17:14: E712 [*] Avoid inequality comparisons to `False`; use `if cond:`
19 19 | #: E712 19 19 | #: E712
20 20 | var = 1 if cond == True else -1 if cond == False else cond 20 20 | var = 1 if cond == True else -1 if cond == False else cond
E712.py:20:20: E712 [*] Avoid equality comparisons to `True`; use `if cond:` for truth checks E712.py:20:12: E712 [*] Avoid equality comparisons to `True`; use `if cond:` for truth checks
| |
18 | pass 18 | pass
19 | #: E712 19 | #: E712
20 | var = 1 if cond == True else -1 if cond == False else cond 20 | var = 1 if cond == True else -1 if cond == False else cond
| ^^^^ E712 | ^^^^^^^^^^^^ E712
21 | #: E712 21 | #: E712
22 | if (True) == TrueElement or x == TrueElement: 22 | if (True) == TrueElement or x == TrueElement:
| |
@ -145,12 +145,12 @@ E712.py:20:20: E712 [*] Avoid equality comparisons to `True`; use `if cond:` for
22 22 | if (True) == TrueElement or x == TrueElement: 22 22 | if (True) == TrueElement or x == TrueElement:
23 23 | pass 23 23 | pass
E712.py:20:44: E712 [*] Avoid equality comparisons to `False`; use `if not cond:` for false checks E712.py:20:36: E712 [*] Avoid equality comparisons to `False`; use `if not cond:` for false checks
| |
18 | pass 18 | pass
19 | #: E712 19 | #: E712
20 | var = 1 if cond == True else -1 if cond == False else cond 20 | var = 1 if cond == True else -1 if cond == False else cond
| ^^^^^ E712 | ^^^^^^^^^^^^^ E712
21 | #: E712 21 | #: E712
22 | if (True) == TrueElement or x == TrueElement: 22 | if (True) == TrueElement or x == TrueElement:
| |
@ -166,15 +166,15 @@ E712.py:20:44: E712 [*] Avoid equality comparisons to `False`; use `if not cond:
22 22 | if (True) == TrueElement or x == TrueElement: 22 22 | if (True) == TrueElement or x == TrueElement:
23 23 | pass 23 23 | pass
E712.py:22:5: E712 [*] Avoid equality comparisons to `True`; use `if cond:` for truth checks E712.py:22:4: E712 [*] Avoid equality comparisons to `True`; use `if TrueElement:` for truth checks
| |
20 | var = 1 if cond == True else -1 if cond == False else cond 20 | var = 1 if cond == True else -1 if cond == False else cond
21 | #: E712 21 | #: E712
22 | if (True) == TrueElement or x == TrueElement: 22 | if (True) == TrueElement or x == TrueElement:
| ^^^^ E712 | ^^^^^^^^^^^^^^^^^^^^^ E712
23 | pass 23 | pass
| |
= help: Replace with `cond` = help: Replace with `TrueElement`
Unsafe fix Unsafe fix
19 19 | #: E712 19 19 | #: E712
@ -186,15 +186,15 @@ E712.py:22:5: E712 [*] Avoid equality comparisons to `True`; use `if cond:` for
24 24 | 24 24 |
25 25 | if res == True != False: 25 25 | if res == True != False:
E712.py:25:11: E712 [*] Avoid equality comparisons to `True`; use `if cond:` for truth checks E712.py:25:4: E712 [*] Avoid equality comparisons to `True` or `False`
| |
23 | pass 23 | pass
24 | 24 |
25 | if res == True != False: 25 | if res == True != False:
| ^^^^ E712 | ^^^^^^^^^^^^^^^^^^^^ E712
26 | pass 26 | pass
| |
= help: Replace with `cond` = help: Replace comparison
Unsafe fix Unsafe fix
22 22 | if (True) == TrueElement or x == TrueElement: 22 22 | if (True) == TrueElement or x == TrueElement:
@ -206,15 +206,15 @@ E712.py:25:11: E712 [*] Avoid equality comparisons to `True`; use `if cond:` for
27 27 | 27 27 |
28 28 | if(True) == TrueElement or x == TrueElement: 28 28 | if(True) == TrueElement or x == TrueElement:
E712.py:25:19: E712 [*] Avoid inequality comparisons to `False`; use `if cond:` for truth checks E712.py:25:4: E712 [*] Avoid equality comparisons to `True` or `False`
| |
23 | pass 23 | pass
24 | 24 |
25 | if res == True != False: 25 | if res == True != False:
| ^^^^^ E712 | ^^^^^^^^^^^^^^^^^^^^ E712
26 | pass 26 | pass
| |
= help: Replace with `cond` = help: Replace comparison
Unsafe fix Unsafe fix
22 22 | if (True) == TrueElement or x == TrueElement: 22 22 | if (True) == TrueElement or x == TrueElement:
@ -226,15 +226,15 @@ E712.py:25:19: E712 [*] Avoid inequality comparisons to `False`; use `if cond:`
27 27 | 27 27 |
28 28 | if(True) == TrueElement or x == TrueElement: 28 28 | if(True) == TrueElement or x == TrueElement:
E712.py:28:4: E712 [*] Avoid equality comparisons to `True`; use `if cond:` for truth checks E712.py:28:3: E712 [*] Avoid equality comparisons to `True`; use `if TrueElement:` for truth checks
| |
26 | pass 26 | pass
27 | 27 |
28 | if(True) == TrueElement or x == TrueElement: 28 | if(True) == TrueElement or x == TrueElement:
| ^^^^ E712 | ^^^^^^^^^^^^^^^^^^^^^ E712
29 | pass 29 | pass
| |
= help: Replace with `cond` = help: Replace with `TrueElement`
Unsafe fix Unsafe fix
25 25 | if res == True != False: 25 25 | if res == True != False:
@ -246,15 +246,15 @@ E712.py:28:4: E712 [*] Avoid equality comparisons to `True`; use `if cond:` for
30 30 | 30 30 |
31 31 | if (yield i) == True: 31 31 | if (yield i) == True:
E712.py:31:17: E712 [*] Avoid equality comparisons to `True`; use `if cond:` for truth checks E712.py:31:4: E712 [*] Avoid equality comparisons to `True`; use `if yield i:` for truth checks
| |
29 | pass 29 | pass
30 | 30 |
31 | if (yield i) == True: 31 | if (yield i) == True:
| ^^^^ E712 | ^^^^^^^^^^^^^^^^^ E712
32 | print("even") 32 | print("even")
| |
= help: Replace with `cond` = help: Replace with `yield i`
Unsafe fix Unsafe fix
28 28 | if(True) == TrueElement or x == TrueElement: 28 28 | if(True) == TrueElement or x == TrueElement:
@ -265,5 +265,3 @@ E712.py:31:17: E712 [*] Avoid equality comparisons to `True`; use `if cond:` for
32 32 | print("even") 32 32 | print("even")
33 33 | 33 33 |
34 34 | #: Okay 34 34 | #: Okay

View file

@ -106,16 +106,16 @@ constant_literals.py:12:4: F632 [*] Use `==` to compare constant literals
14 14 | if False == None: # E711, E712 (fix) 14 14 | if False == None: # E711, E712 (fix)
15 15 | pass 15 15 | pass
constant_literals.py:14:4: E712 [*] Avoid equality comparisons to `False`; use `if not cond:` for false checks constant_literals.py:14:4: E712 [*] Avoid equality comparisons to `False`; use `if not None:` for false checks
| |
12 | if False is "abc": # F632 (fix, but leaves behind unfixable E712) 12 | if False is "abc": # F632 (fix, but leaves behind unfixable E712)
13 | pass 13 | pass
14 | if False == None: # E711, E712 (fix) 14 | if False == None: # E711, E712 (fix)
| ^^^^^ E712 | ^^^^^^^^^^^^^ E712
15 | pass 15 | pass
16 | if None == False: # E711, E712 (fix) 16 | if None == False: # E711, E712 (fix)
| |
= help: Replace with `not cond` = help: Replace with `not None`
Unsafe fix Unsafe fix
11 11 | pass 11 11 | pass
@ -168,15 +168,15 @@ constant_literals.py:16:4: E711 [*] Comparison to `None` should be `cond is None
18 18 | 18 18 |
19 19 | named_var = [] 19 19 | named_var = []
constant_literals.py:16:12: E712 [*] Avoid equality comparisons to `False`; use `if not cond:` for false checks constant_literals.py:16:4: E712 [*] Avoid equality comparisons to `False`; use `if not None:` for false checks
| |
14 | if False == None: # E711, E712 (fix) 14 | if False == None: # E711, E712 (fix)
15 | pass 15 | pass
16 | if None == False: # E711, E712 (fix) 16 | if None == False: # E711, E712 (fix)
| ^^^^^ E712 | ^^^^^^^^^^^^^ E712
17 | pass 17 | pass
| |
= help: Replace with `not cond` = help: Replace with `not None`
Unsafe fix Unsafe fix
13 13 | pass 13 13 | pass
@ -187,5 +187,3 @@ constant_literals.py:16:12: E712 [*] Avoid equality comparisons to `False`; use
17 17 | pass 17 17 | pass
18 18 | 18 18 |
19 19 | named_var = [] 19 19 | named_var = []