Avoid duplicates in if-with-same-arms (#2827)

This commit is contained in:
Charlie Marsh 2023-02-12 17:22:19 -05:00 committed by GitHub
parent 5a34504149
commit 8b35b052b8
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23
5 changed files with 99 additions and 106 deletions

View File

@ -1259,7 +1259,7 @@ For more, see [flake8-simplify](https://pypi.org/project/flake8-simplify/) on Py
| SIM110 | convert-loop-to-any | Use `{any}` instead of `for` loop | 🛠 | | SIM110 | convert-loop-to-any | Use `{any}` instead of `for` loop | 🛠 |
| SIM111 | convert-loop-to-all | Use `{all}` instead of `for` loop | 🛠 | | SIM111 | convert-loop-to-all | Use `{all}` instead of `for` loop | 🛠 |
| SIM112 | use-capital-environment-variables | Use capitalized environment variable `{expected}` instead of `{original}` | 🛠 | | SIM112 | use-capital-environment-variables | Use capitalized environment variable `{expected}` instead of `{original}` | 🛠 |
| SIM114 | [if-with-same-arms](https://github.com/charliermarsh/ruff/blob/main/docs/rules/if-with-same-arms.md) | Combine `if` statements using `or` | | | SIM114 | [if-with-same-arms](https://github.com/charliermarsh/ruff/blob/main/docs/rules/if-with-same-arms.md) | Combine `if` branches using logical `or` operator | |
| SIM115 | open-file-with-context-handler | Use context handler for opening files | | | SIM115 | open-file-with-context-handler | Use context handler for opening files | |
| SIM117 | multiple-with-statements | Use a single `with` statement with multiple contexts instead of nested `with` statements | 🛠 | | SIM117 | multiple-with-statements | Use a single `with` statement with multiple contexts instead of nested `with` statements | 🛠 |
| SIM118 | key-in-dict | Use `{key} in {dict}` instead of `{key} in {dict}.keys()` | 🛠 | | SIM118 | key-in-dict | Use `{key} in {dict}` instead of `{key} in {dict}.keys()` | 🛠 |

View File

@ -55,16 +55,13 @@ if (
elif 1 == 2: elif 1 == 2:
pass pass
failures = errors = skipped = disabled = 0
if result.eofs == "O": if result.eofs == "O":
pass pass
elif result.eofs == "S": elif result.eofs == "S":
skipped = 1 skipped = 1
elif result.eofs == "F": elif result.eofs == "F":
failures = 1
elif result.eofs == "E":
errors = 1 errors = 1
else: elif result.eofs == "E":
errors = 1 errors = 1
@ -91,3 +88,9 @@ elif b:
z = 2 z = 2
elif c: elif c:
z = 1 z = 1
# False negative (or arguably a different rule)
if result.eofs == "F":
errors = 1
else:
errors = 1

View File

@ -1513,7 +1513,11 @@ where
); );
} }
if self.settings.rules.enabled(&Rule::IfWithSameArms) { if self.settings.rules.enabled(&Rule::IfWithSameArms) {
flake8_simplify::rules::if_with_same_arms(self, body, orelse); flake8_simplify::rules::if_with_same_arms(
self,
stmt,
self.current_stmt_parent().map(std::convert::Into::into),
);
} }
if self.settings.rules.enabled(&Rule::NeedlessBool) { if self.settings.rules.enabled(&Rule::NeedlessBool) {
flake8_simplify::rules::return_bool_condition_directly(self, stmt); flake8_simplify::rules::return_bool_condition_directly(self, stmt);

View File

@ -134,7 +134,7 @@ define_violation!(
impl Violation for IfWithSameArms { impl Violation for IfWithSameArms {
#[derive_message_formats] #[derive_message_formats]
fn message(&self) -> String { fn message(&self) -> String {
format!("Combine `if` statements using `or`") format!("Combine `if` branches using logical `or` operator")
} }
} }
@ -497,57 +497,73 @@ pub fn use_ternary_operator(checker: &mut Checker, stmt: &Stmt, parent: Option<&
checker.diagnostics.push(diagnostic); checker.diagnostics.push(diagnostic);
} }
fn get_if_body_pairs(orelse: &[Stmt], result: &mut Vec<Vec<Stmt>>) { fn get_if_body_pairs<'a>(
if orelse.is_empty() { test: &'a Expr,
return; body: &'a [Stmt],
} orelse: &'a [Stmt],
let mut current_vec: Vec<Stmt> = vec![]; ) -> Vec<(&'a Expr, &'a [Stmt])> {
for if_line in orelse { let mut pairs = vec![(test, body)];
if let StmtKind::If { let mut orelse = orelse;
body: orelse_body, loop {
orelse: orelse_orelse, if orelse.len() != 1 {
.. break;
} = &if_line.node
{
if !current_vec.is_empty() {
result.push(current_vec.clone());
current_vec = vec![];
}
if !orelse_body.is_empty() {
result.push(orelse_body.clone());
}
get_if_body_pairs(orelse_orelse, result);
} else {
current_vec.push(if_line.clone());
} }
let StmtKind::If { test, body, orelse: orelse_orelse, .. } = &orelse[0].node else {
break;
};
pairs.push((test, body));
orelse = orelse_orelse;
} }
if !current_vec.is_empty() { pairs
result.push(current_vec.clone());
}
} }
/// SIM114 /// SIM114
pub fn if_with_same_arms(checker: &mut Checker, body: &[Stmt], orelse: &[Stmt]) { pub fn if_with_same_arms(checker: &mut Checker, stmt: &Stmt, parent: Option<&Stmt>) {
if orelse.is_empty() { let StmtKind::If { test, body, orelse } = &stmt.node else {
return; return;
};
// It's part of a bigger if-elif block:
// https://github.com/MartinThoma/flake8-simplify/issues/115
if let Some(StmtKind::If {
orelse: parent_orelse,
..
}) = parent.map(|parent| &parent.node)
{
if parent_orelse.len() == 1 && stmt == &parent_orelse[0] {
// TODO(charlie): These two cases have the same AST:
//
// if True:
// pass
// elif a:
// b = 1
// else:
// b = 2
//
// if True:
// pass
// else:
// if a:
// b = 1
// else:
// b = 2
//
// We want to flag the latter, but not the former. Right now, we flag neither.
return;
}
} }
// It's not all combinations because of this: let if_body_pairs = get_if_body_pairs(test, body, orelse);
// https://github.com/MartinThoma/flake8-simplify/issues/70#issuecomment-924074984 for i in 0..(if_body_pairs.len() - 1) {
let mut final_stmts: Vec<Vec<Stmt>> = vec![body.to_vec()]; let (test, body) = &if_body_pairs[i];
get_if_body_pairs(orelse, &mut final_stmts); let (.., next_body) = &if_body_pairs[i + 1];
let if_statements = final_stmts.len(); if compare_body(body, next_body) {
if if_statements <= 1 {
return;
}
for i in 0..(if_statements - 1) {
if compare_body(&final_stmts[i], &final_stmts[i + 1]) {
let first = &final_stmts[i].first().unwrap();
let last = &final_stmts[i].last().unwrap();
checker.diagnostics.push(Diagnostic::new( checker.diagnostics.push(Diagnostic::new(
IfWithSameArms, IfWithSameArms,
Range::new(first.location, last.end_location.unwrap()), Range::new(
if i == 0 { stmt.location } else { test.location },
next_body.last().unwrap().end_location.unwrap(),
),
)); ));
} }
} }

View File

@ -5,30 +5,40 @@ expression: diagnostics
- kind: - kind:
IfWithSameArms: ~ IfWithSameArms: ~
location: location:
row: 3 row: 2
column: 4 column: 0
end_location: end_location:
row: 3 row: 5
column: 5 column: 5
fix: ~ fix: ~
parent: ~ parent: ~
- kind: - kind:
IfWithSameArms: ~ IfWithSameArms: ~
location: location:
row: 8 row: 7
column: 4 column: 0
end_location: end_location:
row: 9 row: 12
column: 22 column: 22
fix: ~ fix: ~
parent: ~ parent: ~
- kind: - kind:
IfWithSameArms: ~ IfWithSameArms: ~
location: location:
row: 15 row: 14
column: 4 column: 0
end_location: end_location:
row: 17 row: 21
column: 26
fix: ~
parent: ~
- kind:
IfWithSameArms: ~
location:
row: 23
column: 0
end_location:
row: 36
column: 26 column: 26
fix: ~ fix: ~
parent: ~ parent: ~
@ -45,70 +55,30 @@ expression: diagnostics
- kind: - kind:
IfWithSameArms: ~ IfWithSameArms: ~
location: location:
row: 25 row: 31
column: 8 column: 4
end_location: end_location:
row: 26 row: 36
column: 26 column: 26
fix: ~ fix: ~
parent: ~ parent: ~
- kind: - kind:
IfWithSameArms: ~ IfWithSameArms: ~
location: location:
row: 32 row: 38
column: 8 column: 0
end_location: end_location:
row: 33 row: 56
column: 26
fix: ~
parent: ~
- kind:
IfWithSameArms: ~
location:
row: 54
column: 4
end_location:
row: 54
column: 8 column: 8
fix: ~ fix: ~
parent: ~ parent: ~
- kind: - kind:
IfWithSameArms: ~ IfWithSameArms: ~
location: location:
row: 66 row: 62
column: 4 column: 5
end_location: end_location:
row: 66 row: 65
column: 14
fix: ~
parent: ~
- kind:
IfWithSameArms: ~
location:
row: 66
column: 4
end_location:
row: 66
column: 14
fix: ~
parent: ~
- kind:
IfWithSameArms: ~
location:
row: 66
column: 4
end_location:
row: 66
column: 14
fix: ~
parent: ~
- kind:
IfWithSameArms: ~
location:
row: 66
column: 4
end_location:
row: 66
column: 14 column: 14
fix: ~ fix: ~
parent: ~ parent: ~