mirror of https://github.com/astral-sh/ruff
parent
6ddfe50ac4
commit
a122d95ef5
|
|
@ -1009,7 +1009,7 @@ For more, see [flake8-simplify](https://pypi.org/project/flake8-simplify/0.19.3/
|
||||||
| SIM105 | UseContextlibSuppress | Use `contextlib.suppress(...)` instead of try-except-pass | |
|
| SIM105 | UseContextlibSuppress | Use `contextlib.suppress(...)` instead of try-except-pass | |
|
||||||
| SIM107 | ReturnInTryExceptFinally | Don't use `return` in `try`/`except` and `finally` | |
|
| SIM107 | ReturnInTryExceptFinally | Don't use `return` in `try`/`except` and `finally` | |
|
||||||
| SIM108 | UseTernaryOperator | Use ternary operator `...` instead of if-else-block | 🛠 |
|
| SIM108 | UseTernaryOperator | Use ternary operator `...` instead of if-else-block | 🛠 |
|
||||||
| SIM109 | CompareWithTuple | Use `value in (..., ...)` instead of `value == ... or value == ...` | 🛠 |
|
| SIM109 | CompareWithTuple | Use `value in (... ,...)` instead of multiple equality comparisons | 🛠 |
|
||||||
| SIM110 | ConvertLoopToAny | Use `return any(x for x in y)` instead of `for` loop | 🛠 |
|
| SIM110 | ConvertLoopToAny | Use `return any(x for x in y)` instead of `for` loop | 🛠 |
|
||||||
| SIM111 | ConvertLoopToAll | Use `return all(x for x in y)` instead of `for` loop | 🛠 |
|
| SIM111 | ConvertLoopToAll | Use `return all(x for x in y)` instead of `for` loop | 🛠 |
|
||||||
| SIM112 | UseCapitalEnvironmentVariables | Use capitalized environment variable `...` instead of `...` | 🛠 |
|
| SIM112 | UseCapitalEnvironmentVariables | Use capitalized environment variable `...` instead of `...` | 🛠 |
|
||||||
|
|
|
||||||
|
|
@ -1,7 +1,31 @@
|
||||||
# Bad
|
# SIM109
|
||||||
if a == b or a == c:
|
if a == b or a == c:
|
||||||
d
|
d
|
||||||
|
|
||||||
# Good
|
# SIM109
|
||||||
|
if (a == b or a == c) and None:
|
||||||
|
d
|
||||||
|
|
||||||
|
# SIM109
|
||||||
|
if a == b or a == c or None:
|
||||||
|
d
|
||||||
|
|
||||||
|
# SIM109
|
||||||
|
if a == b or None or a == c:
|
||||||
|
d
|
||||||
|
|
||||||
|
# OK
|
||||||
if a in (b, c):
|
if a in (b, c):
|
||||||
d
|
d
|
||||||
|
|
||||||
|
# OK
|
||||||
|
if a == b or a == c():
|
||||||
|
d
|
||||||
|
|
||||||
|
# OK
|
||||||
|
if (
|
||||||
|
a == b
|
||||||
|
# This comment prevents us from raising SIM109
|
||||||
|
or a == c
|
||||||
|
):
|
||||||
|
d
|
||||||
|
|
|
||||||
|
|
@ -2,10 +2,9 @@ use std::collections::BTreeMap;
|
||||||
use std::iter;
|
use std::iter;
|
||||||
|
|
||||||
use itertools::Either::{Left, Right};
|
use itertools::Either::{Left, Right};
|
||||||
use rustc_hash::FxHashMap;
|
|
||||||
use rustpython_ast::{Boolop, Cmpop, Constant, Expr, ExprContext, ExprKind, Unaryop};
|
use rustpython_ast::{Boolop, Cmpop, Constant, Expr, ExprContext, ExprKind, Unaryop};
|
||||||
|
|
||||||
use crate::ast::helpers::{contains_effect, create_expr, unparse_expr};
|
use crate::ast::helpers::{contains_effect, create_expr, has_comments_in, unparse_expr};
|
||||||
use crate::ast::types::Range;
|
use crate::ast::types::Range;
|
||||||
use crate::checkers::ast::Checker;
|
use crate::checkers::ast::Checker;
|
||||||
use crate::fix::Fix;
|
use crate::fix::Fix;
|
||||||
|
|
@ -30,7 +29,7 @@ pub fn duplicate_isinstance_call(checker: &mut Checker, expr: &Expr) {
|
||||||
|
|
||||||
// Locate duplicate `isinstance` calls, represented as a map from argument name
|
// Locate duplicate `isinstance` calls, represented as a map from argument name
|
||||||
// to indices of the relevant `Expr` instances in `values`.
|
// to indices of the relevant `Expr` instances in `values`.
|
||||||
let mut duplicates = FxHashMap::default();
|
let mut duplicates = BTreeMap::new();
|
||||||
for (index, call) in values.iter().enumerate() {
|
for (index, call) in values.iter().enumerate() {
|
||||||
// Verify that this is an `isinstance` call.
|
// Verify that this is an `isinstance` call.
|
||||||
let ExprKind::Call { func, args, keywords } = &call.node else {
|
let ExprKind::Call { func, args, keywords } = &call.node else {
|
||||||
|
|
@ -136,62 +135,98 @@ pub fn duplicate_isinstance_call(checker: &mut Checker, expr: &Expr) {
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
fn match_eq_target(expr: &Expr) -> Option<(&str, &Expr)> {
|
||||||
|
let ExprKind::Compare { left, ops, comparators } = &expr.node else {
|
||||||
|
return None;
|
||||||
|
};
|
||||||
|
if ops.len() != 1 || comparators.len() != 1 {
|
||||||
|
return None;
|
||||||
|
}
|
||||||
|
if !matches!(&ops[0], Cmpop::Eq) {
|
||||||
|
return None;
|
||||||
|
}
|
||||||
|
let ExprKind::Name { id, .. } = &left.node else {
|
||||||
|
return None;
|
||||||
|
};
|
||||||
|
let comparator = &comparators[0];
|
||||||
|
if !matches!(&comparator.node, ExprKind::Name { .. }) {
|
||||||
|
return None;
|
||||||
|
}
|
||||||
|
Some((id, comparator))
|
||||||
|
}
|
||||||
|
|
||||||
/// SIM109
|
/// SIM109
|
||||||
pub fn compare_with_tuple(checker: &mut Checker, expr: &Expr) {
|
pub fn compare_with_tuple(checker: &mut Checker, expr: &Expr) {
|
||||||
let ExprKind::BoolOp { op: Boolop::Or, values } = &expr.node else {
|
let ExprKind::BoolOp { op: Boolop::Or, values } = &expr.node else {
|
||||||
return;
|
return;
|
||||||
};
|
};
|
||||||
|
|
||||||
let mut id_to_values = BTreeMap::<&str, Vec<&Expr>>::new();
|
// Given `a == "foo" or a == "bar"`, we generate `{"a": [(0, "foo"), (1,
|
||||||
for value in values {
|
// "bar")]}`.
|
||||||
let ExprKind::Compare { left, ops, comparators } = &value.node else {
|
let mut id_to_comparators: BTreeMap<&str, Vec<(usize, &Expr)>> = BTreeMap::new();
|
||||||
continue;
|
for (index, value) in values.iter().enumerate() {
|
||||||
};
|
if let Some((id, comparator)) = match_eq_target(value) {
|
||||||
if ops.len() != 1 || comparators.len() != 1 {
|
id_to_comparators
|
||||||
continue;
|
.entry(id)
|
||||||
|
.or_default()
|
||||||
|
.push((index, comparator));
|
||||||
}
|
}
|
||||||
if !matches!(&ops[0], Cmpop::Eq) {
|
|
||||||
continue;
|
|
||||||
}
|
|
||||||
let ExprKind::Name { id, .. } = &left.node else {
|
|
||||||
continue;
|
|
||||||
};
|
|
||||||
let comparator = &comparators[0];
|
|
||||||
if !matches!(&comparator.node, ExprKind::Name { .. }) {
|
|
||||||
continue;
|
|
||||||
}
|
|
||||||
id_to_values.entry(id).or_default().push(comparator);
|
|
||||||
}
|
}
|
||||||
|
|
||||||
for (value, values) in id_to_values {
|
for (id, matches) in id_to_comparators {
|
||||||
if values.len() == 1 {
|
if matches.len() == 1 {
|
||||||
continue;
|
continue;
|
||||||
}
|
}
|
||||||
let str_values = values
|
|
||||||
|
let (indices, comparators): (Vec<_>, Vec<_>) = matches.iter().copied().unzip();
|
||||||
|
|
||||||
|
// Avoid rewriting (e.g.) `a == "foo" or a == f()`.
|
||||||
|
if comparators
|
||||||
.iter()
|
.iter()
|
||||||
.map(|value| unparse_expr(value, checker.stylist))
|
.any(|expr| contains_effect(checker, expr))
|
||||||
.collect();
|
{
|
||||||
|
continue;
|
||||||
|
}
|
||||||
|
|
||||||
|
// Avoid removing comments.
|
||||||
|
if has_comments_in(Range::from_located(expr), checker.locator) {
|
||||||
|
continue;
|
||||||
|
}
|
||||||
|
|
||||||
|
// Create a `x in (a, b)` expression.
|
||||||
|
let in_expr = create_expr(ExprKind::Compare {
|
||||||
|
left: Box::new(create_expr(ExprKind::Name {
|
||||||
|
id: id.to_string(),
|
||||||
|
ctx: ExprContext::Load,
|
||||||
|
})),
|
||||||
|
ops: vec![Cmpop::In],
|
||||||
|
comparators: vec![create_expr(ExprKind::Tuple {
|
||||||
|
elts: comparators.into_iter().map(Clone::clone).collect(),
|
||||||
|
ctx: ExprContext::Load,
|
||||||
|
})],
|
||||||
|
});
|
||||||
let mut diagnostic = Diagnostic::new(
|
let mut diagnostic = Diagnostic::new(
|
||||||
violations::CompareWithTuple(
|
violations::CompareWithTuple {
|
||||||
value.to_string(),
|
replacement: unparse_expr(&in_expr, checker.stylist),
|
||||||
str_values,
|
},
|
||||||
unparse_expr(expr, checker.stylist),
|
|
||||||
),
|
|
||||||
Range::from_located(expr),
|
Range::from_located(expr),
|
||||||
);
|
);
|
||||||
if checker.patch(&Rule::CompareWithTuple) {
|
if checker.patch(&Rule::CompareWithTuple) {
|
||||||
// Create a `x in (a, b)` compare expr.
|
let unmatched: Vec<Expr> = values
|
||||||
let in_expr = create_expr(ExprKind::Compare {
|
.iter()
|
||||||
left: Box::new(create_expr(ExprKind::Name {
|
.enumerate()
|
||||||
id: value.to_string(),
|
.filter(|(index, _)| !indices.contains(index))
|
||||||
ctx: ExprContext::Load,
|
.map(|(_, elt)| elt.clone())
|
||||||
})),
|
.collect();
|
||||||
ops: vec![Cmpop::In],
|
let in_expr = if unmatched.is_empty() {
|
||||||
comparators: vec![create_expr(ExprKind::Tuple {
|
in_expr
|
||||||
elts: values.into_iter().map(Clone::clone).collect(),
|
} else {
|
||||||
ctx: ExprContext::Load,
|
// Wrap in a `x in (a, b) or ...` boolean operation.
|
||||||
})],
|
create_expr(ExprKind::BoolOp {
|
||||||
});
|
op: Boolop::Or,
|
||||||
|
values: iter::once(in_expr).chain(unmatched).collect(),
|
||||||
|
})
|
||||||
|
};
|
||||||
diagnostic.amend(Fix::replacement(
|
diagnostic.amend(Fix::replacement(
|
||||||
unparse_expr(&in_expr, checker.stylist),
|
unparse_expr(&in_expr, checker.stylist),
|
||||||
expr.location,
|
expr.location,
|
||||||
|
|
|
||||||
|
|
@ -4,10 +4,7 @@ expression: diagnostics
|
||||||
---
|
---
|
||||||
- kind:
|
- kind:
|
||||||
CompareWithTuple:
|
CompareWithTuple:
|
||||||
- a
|
replacement: "a in (b, c)"
|
||||||
- - b
|
|
||||||
- c
|
|
||||||
- a == b or a == c
|
|
||||||
location:
|
location:
|
||||||
row: 2
|
row: 2
|
||||||
column: 3
|
column: 3
|
||||||
|
|
@ -23,4 +20,58 @@ expression: diagnostics
|
||||||
row: 2
|
row: 2
|
||||||
column: 19
|
column: 19
|
||||||
parent: ~
|
parent: ~
|
||||||
|
- kind:
|
||||||
|
CompareWithTuple:
|
||||||
|
replacement: "a in (b, c)"
|
||||||
|
location:
|
||||||
|
row: 6
|
||||||
|
column: 4
|
||||||
|
end_location:
|
||||||
|
row: 6
|
||||||
|
column: 20
|
||||||
|
fix:
|
||||||
|
content: "a in (b, c)"
|
||||||
|
location:
|
||||||
|
row: 6
|
||||||
|
column: 4
|
||||||
|
end_location:
|
||||||
|
row: 6
|
||||||
|
column: 20
|
||||||
|
parent: ~
|
||||||
|
- kind:
|
||||||
|
CompareWithTuple:
|
||||||
|
replacement: "a in (b, c)"
|
||||||
|
location:
|
||||||
|
row: 10
|
||||||
|
column: 3
|
||||||
|
end_location:
|
||||||
|
row: 10
|
||||||
|
column: 27
|
||||||
|
fix:
|
||||||
|
content: "a in (b, c) or None"
|
||||||
|
location:
|
||||||
|
row: 10
|
||||||
|
column: 3
|
||||||
|
end_location:
|
||||||
|
row: 10
|
||||||
|
column: 27
|
||||||
|
parent: ~
|
||||||
|
- kind:
|
||||||
|
CompareWithTuple:
|
||||||
|
replacement: "a in (b, c)"
|
||||||
|
location:
|
||||||
|
row: 14
|
||||||
|
column: 3
|
||||||
|
end_location:
|
||||||
|
row: 14
|
||||||
|
column: 27
|
||||||
|
fix:
|
||||||
|
content: "a in (b, c) or None"
|
||||||
|
location:
|
||||||
|
row: 14
|
||||||
|
column: 3
|
||||||
|
end_location:
|
||||||
|
row: 14
|
||||||
|
column: 27
|
||||||
|
parent: ~
|
||||||
|
|
||||||
|
|
|
||||||
|
|
@ -2894,27 +2894,25 @@ impl AlwaysAutofixableViolation for UseTernaryOperator {
|
||||||
}
|
}
|
||||||
|
|
||||||
define_violation!(
|
define_violation!(
|
||||||
pub struct CompareWithTuple(pub String, pub Vec<String>, pub String);
|
pub struct CompareWithTuple {
|
||||||
|
pub replacement: String,
|
||||||
|
}
|
||||||
);
|
);
|
||||||
impl AlwaysAutofixableViolation for CompareWithTuple {
|
impl AlwaysAutofixableViolation for CompareWithTuple {
|
||||||
fn message(&self) -> String {
|
fn message(&self) -> String {
|
||||||
let CompareWithTuple(value, values, or_op) = self;
|
let CompareWithTuple { replacement } = self;
|
||||||
let values = values.join(", ");
|
format!("Use `{replacement}` instead of multiple equality comparisons")
|
||||||
format!("Use `{value} in ({values})` instead of `{or_op}`")
|
|
||||||
}
|
}
|
||||||
|
|
||||||
fn autofix_title(&self) -> String {
|
fn autofix_title(&self) -> String {
|
||||||
let CompareWithTuple(value, values, or_op) = self;
|
let CompareWithTuple { replacement, .. } = self;
|
||||||
let values = values.join(", ");
|
format!("Replace with `{replacement}`")
|
||||||
format!("Replace `{or_op}` with `{value} in {values}`")
|
|
||||||
}
|
}
|
||||||
|
|
||||||
fn placeholder() -> Self {
|
fn placeholder() -> Self {
|
||||||
CompareWithTuple(
|
CompareWithTuple {
|
||||||
"value".to_string(),
|
replacement: "value in (... ,...)".to_string(),
|
||||||
vec!["...".to_string(), "...".to_string()],
|
}
|
||||||
"value == ... or value == ...".to_string(),
|
|
||||||
)
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
|
||||||
Loading…
Reference in New Issue