mirror of https://github.com/astral-sh/ruff
Respect operator precedence in `FURB110` (#11464)
## Summary Ensures that we parenthesize expressions (if necessary) to preserve operator precedence in `FURB110`. Closes https://github.com/astral-sh/ruff/issues/11398.
This commit is contained in:
parent
24899efe50
commit
48b0660228
|
|
@ -38,3 +38,12 @@ z = (
|
||||||
else
|
else
|
||||||
y
|
y
|
||||||
)
|
)
|
||||||
|
|
||||||
|
# FURB110
|
||||||
|
z = (
|
||||||
|
x
|
||||||
|
if x
|
||||||
|
else y
|
||||||
|
if y > 0
|
||||||
|
else None
|
||||||
|
)
|
||||||
|
|
|
||||||
|
|
@ -1,9 +1,14 @@
|
||||||
|
use std::borrow::Cow;
|
||||||
|
|
||||||
use ruff_diagnostics::{Applicability, Diagnostic, Edit, Fix, FixAvailability, Violation};
|
use ruff_diagnostics::{Applicability, Diagnostic, Edit, Fix, FixAvailability, Violation};
|
||||||
use ruff_macros::{derive_message_formats, violation};
|
use ruff_macros::{derive_message_formats, violation};
|
||||||
use ruff_python_ast as ast;
|
use ruff_python_ast as ast;
|
||||||
use ruff_python_ast::comparable::ComparableExpr;
|
use ruff_python_ast::comparable::ComparableExpr;
|
||||||
use ruff_python_ast::helpers::contains_effect;
|
use ruff_python_ast::helpers::contains_effect;
|
||||||
use ruff_python_ast::parenthesize::parenthesized_range;
|
use ruff_python_ast::parenthesize::parenthesized_range;
|
||||||
|
use ruff_python_ast::Expr;
|
||||||
|
use ruff_python_index::Indexer;
|
||||||
|
use ruff_source_file::Locator;
|
||||||
use ruff_text_size::Ranged;
|
use ruff_text_size::Ranged;
|
||||||
|
|
||||||
use crate::checkers::ast::Checker;
|
use crate::checkers::ast::Checker;
|
||||||
|
|
@ -64,29 +69,13 @@ pub(crate) fn if_exp_instead_of_or_operator(checker: &mut Checker, if_expr: &ast
|
||||||
|
|
||||||
let mut diagnostic = Diagnostic::new(IfExpInsteadOfOrOperator, *range);
|
let mut diagnostic = Diagnostic::new(IfExpInsteadOfOrOperator, *range);
|
||||||
|
|
||||||
// Grab the range of the `test` and `orelse` expressions.
|
|
||||||
let left = parenthesized_range(
|
|
||||||
test.into(),
|
|
||||||
if_expr.into(),
|
|
||||||
checker.indexer().comment_ranges(),
|
|
||||||
checker.locator().contents(),
|
|
||||||
)
|
|
||||||
.unwrap_or(test.range());
|
|
||||||
let right = parenthesized_range(
|
|
||||||
orelse.into(),
|
|
||||||
if_expr.into(),
|
|
||||||
checker.indexer().comment_ranges(),
|
|
||||||
checker.locator().contents(),
|
|
||||||
)
|
|
||||||
.unwrap_or(orelse.range());
|
|
||||||
|
|
||||||
// Replace with `{test} or {orelse}`.
|
// Replace with `{test} or {orelse}`.
|
||||||
diagnostic.set_fix(Fix::applicable_edit(
|
diagnostic.set_fix(Fix::applicable_edit(
|
||||||
Edit::range_replacement(
|
Edit::range_replacement(
|
||||||
format!(
|
format!(
|
||||||
"{} or {}",
|
"{} or {}",
|
||||||
checker.locator().slice(left),
|
parenthesize_test(test, if_expr, checker.indexer(), checker.locator()),
|
||||||
checker.locator().slice(right),
|
parenthesize_test(orelse, if_expr, checker.indexer(), checker.locator()),
|
||||||
),
|
),
|
||||||
if_expr.range(),
|
if_expr.range(),
|
||||||
),
|
),
|
||||||
|
|
@ -99,3 +88,30 @@ pub(crate) fn if_exp_instead_of_or_operator(checker: &mut Checker, if_expr: &ast
|
||||||
|
|
||||||
checker.diagnostics.push(diagnostic);
|
checker.diagnostics.push(diagnostic);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/// Parenthesize an expression for use in an `or` operator (e.g., parenthesize `x` in `x or y`),
|
||||||
|
/// if it's required to maintain the correct order of operations.
|
||||||
|
///
|
||||||
|
/// If the expression is already parenthesized, it will be returned as-is regardless of whether
|
||||||
|
/// the parentheses are required.
|
||||||
|
///
|
||||||
|
/// See: <https://docs.python.org/3/reference/expressions.html#operator-precedence>
|
||||||
|
fn parenthesize_test<'a>(
|
||||||
|
expr: &Expr,
|
||||||
|
if_expr: &ast::ExprIf,
|
||||||
|
indexer: &Indexer,
|
||||||
|
locator: &Locator<'a>,
|
||||||
|
) -> Cow<'a, str> {
|
||||||
|
if let Some(range) = parenthesized_range(
|
||||||
|
expr.into(),
|
||||||
|
if_expr.into(),
|
||||||
|
indexer.comment_ranges(),
|
||||||
|
locator.contents(),
|
||||||
|
) {
|
||||||
|
Cow::Borrowed(locator.slice(range))
|
||||||
|
} else if matches!(expr, Expr::If(_) | Expr::Lambda(_) | Expr::Named(_)) {
|
||||||
|
Cow::Owned(format!("({})", locator.slice(expr.range())))
|
||||||
|
} else {
|
||||||
|
Cow::Borrowed(locator.slice(expr.range()))
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
|
||||||
|
|
@ -177,3 +177,33 @@ FURB110.py:34:5: FURB110 [*] Replace ternary `if` expression with `or` operator
|
||||||
39 |- y
|
39 |- y
|
||||||
34 |+ x or y
|
34 |+ x or y
|
||||||
40 35 | )
|
40 35 | )
|
||||||
|
41 36 |
|
||||||
|
42 37 | # FURB110
|
||||||
|
|
||||||
|
FURB110.py:44:5: FURB110 [*] Replace ternary `if` expression with `or` operator
|
||||||
|
|
|
||||||
|
42 | # FURB110
|
||||||
|
43 | z = (
|
||||||
|
44 | x
|
||||||
|
| _____^
|
||||||
|
45 | | if x
|
||||||
|
46 | | else y
|
||||||
|
47 | | if y > 0
|
||||||
|
48 | | else None
|
||||||
|
| |_____________^ FURB110
|
||||||
|
49 | )
|
||||||
|
|
|
||||||
|
= help: Replace with `or` operator
|
||||||
|
|
||||||
|
ℹ Safe fix
|
||||||
|
41 41 |
|
||||||
|
42 42 | # FURB110
|
||||||
|
43 43 | z = (
|
||||||
|
44 |- x
|
||||||
|
45 |- if x
|
||||||
|
46 |- else y
|
||||||
|
44 |+ x or (y
|
||||||
|
47 45 | if y > 0
|
||||||
|
48 |- else None
|
||||||
|
46 |+ else None)
|
||||||
|
49 47 | )
|
||||||
|
|
|
||||||
Loading…
Reference in New Issue