mirror of
https://github.com/astral-sh/ruff.git
synced 2025-09-29 13:24:57 +00:00
Add rule removal infrastructure (#9691)
Similar to https://github.com/astral-sh/ruff/pull/9689 — retains removed rules for better error messages and documentation but removed rules _cannot_ be used in any context. Removes PLR1706 as a useful test case and something we want to accomplish in #9680 anyway. The rule was in preview so we do not need to deprecate it first. Closes https://github.com/astral-sh/ruff/issues/9007 ## Test plan <img width="1110" alt="Rules table" src="ac9fa682
-623c-44aa-8e51-d8ab0d308355"> <img width="1110" alt="Rule page" src="05850b2d
-7ca5-49bb-8df8-bb931bab25cd">
This commit is contained in:
parent
a0ef087e73
commit
e0bc08a758
13 changed files with 127 additions and 411 deletions
|
@ -1091,6 +1091,39 @@ fn preview_enabled_group_ignore() {
|
|||
"###);
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn removed_direct() {
|
||||
// Selection of a removed rule should fail
|
||||
let mut cmd = RuffCheck::default().args(["--select", "PLR1706"]).build();
|
||||
assert_cmd_snapshot!(cmd, @r###"
|
||||
success: false
|
||||
exit_code: 2
|
||||
----- stdout -----
|
||||
|
||||
----- stderr -----
|
||||
ruff failed
|
||||
Cause: Rule `PLR1706` was removed and cannot be selected.
|
||||
"###);
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn removed_indirect() {
|
||||
// Selection _including_ a removed rule without matching should not fail
|
||||
// nor should the rule be used
|
||||
let mut cmd = RuffCheck::default().args(["--select", "PLR"]).build();
|
||||
assert_cmd_snapshot!(cmd.pass_stdin(r###"
|
||||
# This would have been a PLR1706 violation
|
||||
x, y = 1, 2
|
||||
maximum = x >= y and x or y
|
||||
"""###), @r###"
|
||||
success: true
|
||||
exit_code: 0
|
||||
----- stdout -----
|
||||
|
||||
----- stderr -----
|
||||
"###);
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn deprecated_direct() {
|
||||
// Selection of a deprecated rule without preview enabled should still work
|
||||
|
|
|
@ -45,6 +45,14 @@ pub(crate) fn main(args: &Args) -> Result<()> {
|
|||
output.push('\n');
|
||||
}
|
||||
|
||||
if rule.is_removed() {
|
||||
output.push_str(
|
||||
r"**Warning: This rule has been removed and its documentation is only available for historical reasons.**",
|
||||
);
|
||||
output.push('\n');
|
||||
output.push('\n');
|
||||
}
|
||||
|
||||
let fix_availability = rule.fixable();
|
||||
if matches!(
|
||||
fix_availability,
|
||||
|
|
|
@ -15,6 +15,7 @@ use ruff_workspace::options_base::OptionsMetadata;
|
|||
|
||||
const FIX_SYMBOL: &str = "🛠️";
|
||||
const PREVIEW_SYMBOL: &str = "🧪";
|
||||
const REMOVED_SYMBOL: &str = "❌";
|
||||
const WARNING_SYMBOL: &str = "⚠️";
|
||||
const STABLE_SYMBOL: &str = "✔️";
|
||||
const SPACER: &str = " ";
|
||||
|
@ -26,6 +27,9 @@ fn generate_table(table_out: &mut String, rules: impl IntoIterator<Item = Rule>,
|
|||
table_out.push('\n');
|
||||
for rule in rules {
|
||||
let status_token = match rule.group() {
|
||||
RuleGroup::Removed => {
|
||||
format!("<span title='Rule has been removed'>{REMOVED_SYMBOL}</span>")
|
||||
}
|
||||
RuleGroup::Deprecated => {
|
||||
format!("<span title='Rule has been deprecated'>{WARNING_SYMBOL}</span>")
|
||||
}
|
||||
|
@ -62,9 +66,20 @@ fn generate_table(table_out: &mut String, rules: impl IntoIterator<Item = Rule>,
|
|||
Cow::Borrowed(message)
|
||||
};
|
||||
|
||||
// Start and end of style spans
|
||||
let mut ss = "";
|
||||
let mut se = "";
|
||||
if rule.is_removed() {
|
||||
ss = "<span style='opacity: 0.5', title='This rule has been removed'>";
|
||||
se = "</span>";
|
||||
} else if rule.is_deprecated() {
|
||||
ss = "<span style='opacity: 0.8', title='This rule has been deprecated'>";
|
||||
se = "</span>";
|
||||
}
|
||||
|
||||
#[allow(clippy::or_fun_call)]
|
||||
table_out.push_str(&format!(
|
||||
"| {0}{1} {{ #{0}{1} }} | {2} | {3} | {4} |",
|
||||
"| {ss}{0}{1}{se} {{ #{0}{1} }} | {ss}{2}{se} | {ss}{3}{se} | {ss}{4}{se} |",
|
||||
linter.common_prefix(),
|
||||
linter.code_for_rule(rule).unwrap(),
|
||||
rule.explanation()
|
||||
|
@ -101,6 +116,11 @@ pub(crate) fn generate() -> String {
|
|||
));
|
||||
table_out.push_str("<br />");
|
||||
|
||||
table_out.push_str(&format!(
|
||||
"{SPACER}{REMOVED_SYMBOL}{SPACER} The rule has been removed only the documentation is available."
|
||||
));
|
||||
table_out.push_str("<br />");
|
||||
|
||||
table_out.push_str(&format!(
|
||||
"{SPACER}{FIX_SYMBOL}{SPACER} The rule is automatically fixable by the `--fix` command-line option."
|
||||
));
|
||||
|
|
|
@ -1,73 +0,0 @@
|
|||
# OK
|
||||
|
||||
1<2 and 'b' and 'c'
|
||||
|
||||
1<2 or 'a' and 'b'
|
||||
|
||||
1<2 and 'a'
|
||||
|
||||
1<2 or 'a'
|
||||
|
||||
2>1
|
||||
|
||||
1<2 and 'a' or 'b' and 'c'
|
||||
|
||||
1<2 and 'a' or 'b' or 'c'
|
||||
|
||||
1<2 and 'a' or 'b' or 'c' or (lambda x: x+1)
|
||||
|
||||
1<2 and 'a' or 'b' or (lambda x: x+1) or 'c'
|
||||
|
||||
default = 'default'
|
||||
if (not isinstance(default, bool) and isinstance(default, int)) \
|
||||
or (isinstance(default, str) and default):
|
||||
pass
|
||||
|
||||
docid, token = None, None
|
||||
(docid is None and token is None) or (docid is not None and token is not None)
|
||||
|
||||
vendor, os_version = 'darwin', '14'
|
||||
vendor == "debian" and os_version in ["12"] or vendor == "ubuntu" and os_version in []
|
||||
|
||||
# Don't emit if the parent is an `if` statement.
|
||||
if (task_id in task_dict and task_dict[task_id] is not task) \
|
||||
or task_id in used_group_ids:
|
||||
pass
|
||||
|
||||
no_target, is_x64, target = True, False, 'target'
|
||||
if (no_target and not is_x64) or target == 'ARM_APPL_RUST_TARGET':
|
||||
pass
|
||||
|
||||
# Don't emit if the parent is a `bool_op` expression.
|
||||
isinstance(val, str) and ((len(val) == 7 and val[0] == "#") or val in enums.NamedColor)
|
||||
|
||||
# Errors
|
||||
|
||||
1<2 and 'a' or 'b'
|
||||
|
||||
(lambda x: x+1) and 'a' or 'b'
|
||||
|
||||
'a' and (lambda x: x+1) or 'orange'
|
||||
|
||||
val = '#0000FF'
|
||||
(len(val) == 7 and val[0] == "#") or val in {'green'}
|
||||
|
||||
marker = 'marker'
|
||||
isinstance(marker, dict) and 'field' in marker or marker in {}
|
||||
|
||||
def has_oranges(oranges, apples=None) -> bool:
|
||||
return apples and False or oranges
|
||||
|
||||
[x for x in l if a and b or c]
|
||||
|
||||
{x: y for x in l if a and b or c}
|
||||
|
||||
{x for x in l if a and b or c}
|
||||
|
||||
new_list = [
|
||||
x
|
||||
for sublist in all_lists
|
||||
if a and b or c
|
||||
for x in sublist
|
||||
if (isinstance(operator, list) and x in operator) or x != operator
|
||||
]
|
|
@ -1508,9 +1508,6 @@ pub(crate) fn expression(expr: &Expr, checker: &mut Checker) {
|
|||
if checker.enabled(Rule::RepeatedEqualityComparison) {
|
||||
pylint::rules::repeated_equality_comparison(checker, bool_op);
|
||||
}
|
||||
if checker.enabled(Rule::AndOrTernary) {
|
||||
pylint::rules::and_or_ternary(checker, bool_op);
|
||||
}
|
||||
if checker.enabled(Rule::UnnecessaryKeyCheck) {
|
||||
ruff::rules::unnecessary_key_check(checker, expr);
|
||||
}
|
||||
|
|
|
@ -55,6 +55,8 @@ pub enum RuleGroup {
|
|||
/// The rule has been deprecated, warnings will be displayed during selection in stable
|
||||
/// and errors will be raised if used with preview mode enabled.
|
||||
Deprecated,
|
||||
/// The rule has been removed, errors will be displayed on use.
|
||||
Removed,
|
||||
/// Legacy category for unstable rules, supports backwards compatible selection.
|
||||
#[deprecated(note = "Use `RuleGroup::Preview` for new rules instead")]
|
||||
Nursery,
|
||||
|
@ -268,7 +270,7 @@ pub fn code_to_rule(linter: Linter, code: &str) -> Option<(RuleGroup, Rule)> {
|
|||
(Pylint, "R1704") => (RuleGroup::Preview, rules::pylint::rules::RedefinedArgumentFromLocal),
|
||||
(Pylint, "R1711") => (RuleGroup::Stable, rules::pylint::rules::UselessReturn),
|
||||
(Pylint, "R1714") => (RuleGroup::Stable, rules::pylint::rules::RepeatedEqualityComparison),
|
||||
(Pylint, "R1706") => (RuleGroup::Preview, rules::pylint::rules::AndOrTernary),
|
||||
(Pylint, "R1706") => (RuleGroup::Removed, rules::pylint::rules::AndOrTernary),
|
||||
(Pylint, "R1722") => (RuleGroup::Stable, rules::pylint::rules::SysExitAlias),
|
||||
(Pylint, "R1733") => (RuleGroup::Preview, rules::pylint::rules::UnnecessaryDictIndexLookup),
|
||||
(Pylint, "R1736") => (RuleGroup::Preview, rules::pylint::rules::UnnecessaryListIndexLookup),
|
||||
|
|
|
@ -213,6 +213,8 @@ impl RuleSelector {
|
|||
|| (preview_enabled && (matches!(self, RuleSelector::Rule { .. }) || !preview_require_explicit))
|
||||
// Deprecated rules are excluded in preview mode unless explicitly selected
|
||||
|| (rule.is_deprecated() && (!preview_enabled || matches!(self, RuleSelector::Rule { .. })))
|
||||
// Removed rules are included if explicitly selected but will error downstream
|
||||
|| (rule.is_removed() && matches!(self, RuleSelector::Rule { .. }))
|
||||
})
|
||||
}
|
||||
}
|
||||
|
@ -247,6 +249,8 @@ pub struct PreviewOptions {
|
|||
|
||||
#[cfg(feature = "schemars")]
|
||||
mod schema {
|
||||
use std::str::FromStr;
|
||||
|
||||
use itertools::Itertools;
|
||||
use schemars::JsonSchema;
|
||||
use schemars::_serde_json::Value;
|
||||
|
@ -290,6 +294,16 @@ mod schema {
|
|||
(!prefix.is_empty()).then(|| prefix.to_string())
|
||||
})),
|
||||
)
|
||||
.filter(|p| {
|
||||
// Exclude any prefixes where all of the rules are removed
|
||||
if let Ok(Self::Rule { prefix, .. } | Self::Prefix { prefix, .. }) =
|
||||
RuleSelector::from_str(p)
|
||||
{
|
||||
!prefix.rules().all(|rule| rule.is_removed())
|
||||
} else {
|
||||
true
|
||||
}
|
||||
})
|
||||
.sorted()
|
||||
.map(Value::String)
|
||||
.collect(),
|
||||
|
|
|
@ -20,7 +20,6 @@ mod tests {
|
|||
use crate::settings::LinterSettings;
|
||||
use crate::test::test_path;
|
||||
|
||||
#[test_case(Rule::AndOrTernary, Path::new("and_or_ternary.py"))]
|
||||
#[test_case(Rule::AssertOnStringLiteral, Path::new("assert_on_string_literal.py"))]
|
||||
#[test_case(Rule::AwaitOutsideAsync, Path::new("await_outside_async.py"))]
|
||||
#[test_case(Rule::BadOpenMode, Path::new("bad_open_mode.py"))]
|
||||
|
|
|
@ -1,13 +1,10 @@
|
|||
use ruff_diagnostics::{Diagnostic, Edit, Fix, FixAvailability, Violation};
|
||||
use ruff_macros::{derive_message_formats, violation};
|
||||
use ruff_python_ast::{
|
||||
BoolOp, Expr, ExprBoolOp, ExprDictComp, ExprIfExp, ExprListComp, ExprSetComp,
|
||||
};
|
||||
use ruff_text_size::{Ranged, TextRange};
|
||||
|
||||
use crate::checkers::ast::Checker;
|
||||
use crate::fix::snippet::SourceCodeSnippet;
|
||||
use ruff_diagnostics::Violation;
|
||||
use ruff_macros::violation;
|
||||
|
||||
/// ## Removal
|
||||
/// This rule was removed from Ruff because it was common for it to introduce behavioral changes.
|
||||
/// See [#9007](https://github.com/astral-sh/ruff/issues/9007) for more information.
|
||||
///
|
||||
/// ## What it does
|
||||
/// Checks for uses of the known pre-Python 2.5 ternary syntax.
|
||||
///
|
||||
|
@ -31,100 +28,15 @@ use crate::fix::snippet::SourceCodeSnippet;
|
|||
/// maximum = x if x >= y else y
|
||||
/// ```
|
||||
#[violation]
|
||||
pub struct AndOrTernary {
|
||||
ternary: SourceCodeSnippet,
|
||||
}
|
||||
|
||||
impl Violation for AndOrTernary {
|
||||
const FIX_AVAILABILITY: FixAvailability = FixAvailability::Sometimes;
|
||||
|
||||
#[derive_message_formats]
|
||||
fn message(&self) -> String {
|
||||
if let Some(ternary) = self.ternary.full_display() {
|
||||
format!("Consider using if-else expression (`{ternary}`)")
|
||||
} else {
|
||||
format!("Consider using if-else expression")
|
||||
}
|
||||
}
|
||||
|
||||
fn fix_title(&self) -> Option<String> {
|
||||
Some(format!("Convert to if-else expression"))
|
||||
}
|
||||
}
|
||||
|
||||
/// Returns `Some((condition, true_value, false_value))`, if `bool_op` is of the form `condition and true_value or false_value`.
|
||||
fn parse_and_or_ternary(bool_op: &ExprBoolOp) -> Option<(&Expr, &Expr, &Expr)> {
|
||||
if bool_op.op != BoolOp::Or {
|
||||
return None;
|
||||
}
|
||||
let [expr, false_value] = bool_op.values.as_slice() else {
|
||||
return None;
|
||||
};
|
||||
let Some(and_op) = expr.as_bool_op_expr() else {
|
||||
return None;
|
||||
};
|
||||
if and_op.op != BoolOp::And {
|
||||
return None;
|
||||
}
|
||||
let [condition, true_value] = and_op.values.as_slice() else {
|
||||
return None;
|
||||
};
|
||||
if false_value.is_bool_op_expr() || true_value.is_bool_op_expr() {
|
||||
return None;
|
||||
}
|
||||
Some((condition, true_value, false_value))
|
||||
}
|
||||
|
||||
/// Returns `true` if the expression is used within a comprehension.
|
||||
fn is_comprehension_if(parent: Option<&Expr>, expr: &ExprBoolOp) -> bool {
|
||||
let comprehensions = match parent {
|
||||
Some(Expr::ListComp(ExprListComp { generators, .. })) => generators,
|
||||
Some(Expr::SetComp(ExprSetComp { generators, .. })) => generators,
|
||||
Some(Expr::DictComp(ExprDictComp { generators, .. })) => generators,
|
||||
_ => {
|
||||
return false;
|
||||
}
|
||||
};
|
||||
comprehensions
|
||||
.iter()
|
||||
.any(|comp| comp.ifs.iter().any(|ifs| ifs.range() == expr.range()))
|
||||
}
|
||||
pub struct AndOrTernary;
|
||||
|
||||
/// PLR1706
|
||||
pub(crate) fn and_or_ternary(checker: &mut Checker, bool_op: &ExprBoolOp) {
|
||||
if checker.semantic().current_statement().is_if_stmt() {
|
||||
return;
|
||||
impl Violation for AndOrTernary {
|
||||
fn message(&self) -> String {
|
||||
unreachable!("PLR1706 has been removed");
|
||||
}
|
||||
let parent_expr = checker.semantic().current_expression_parent();
|
||||
if parent_expr.is_some_and(Expr::is_bool_op_expr) {
|
||||
return;
|
||||
|
||||
fn message_formats() -> &'static [&'static str] {
|
||||
&["Consider using if-else expression"]
|
||||
}
|
||||
let Some((condition, true_value, false_value)) = parse_and_or_ternary(bool_op) else {
|
||||
return;
|
||||
};
|
||||
|
||||
let if_expr = Expr::IfExp(ExprIfExp {
|
||||
test: Box::new(condition.clone()),
|
||||
body: Box::new(true_value.clone()),
|
||||
orelse: Box::new(false_value.clone()),
|
||||
range: TextRange::default(),
|
||||
});
|
||||
|
||||
let ternary = if is_comprehension_if(parent_expr, bool_op) {
|
||||
format!("({})", checker.generator().expr(&if_expr))
|
||||
} else {
|
||||
checker.generator().expr(&if_expr)
|
||||
};
|
||||
|
||||
let mut diagnostic = Diagnostic::new(
|
||||
AndOrTernary {
|
||||
ternary: SourceCodeSnippet::new(ternary.clone()),
|
||||
},
|
||||
bool_op.range,
|
||||
);
|
||||
diagnostic.set_fix(Fix::unsafe_edit(Edit::range_replacement(
|
||||
ternary,
|
||||
bool_op.range,
|
||||
)));
|
||||
checker.diagnostics.push(diagnostic);
|
||||
}
|
||||
|
|
|
@ -1,229 +0,0 @@
|
|||
---
|
||||
source: crates/ruff_linter/src/rules/pylint/mod.rs
|
||||
---
|
||||
and_or_ternary.py:46:1: PLR1706 [*] Consider using if-else expression (`'a' if 1 < 2 else 'b'`)
|
||||
|
|
||||
44 | # Errors
|
||||
45 |
|
||||
46 | 1<2 and 'a' or 'b'
|
||||
| ^^^^^^^^^^^^^^^^^^ PLR1706
|
||||
47 |
|
||||
48 | (lambda x: x+1) and 'a' or 'b'
|
||||
|
|
||||
= help: Convert to if-else expression
|
||||
|
||||
ℹ Unsafe fix
|
||||
43 43 |
|
||||
44 44 | # Errors
|
||||
45 45 |
|
||||
46 |-1<2 and 'a' or 'b'
|
||||
46 |+'a' if 1 < 2 else 'b'
|
||||
47 47 |
|
||||
48 48 | (lambda x: x+1) and 'a' or 'b'
|
||||
49 49 |
|
||||
|
||||
and_or_ternary.py:48:1: PLR1706 [*] Consider using if-else expression (`'a' if (lambda x: x + 1) else 'b'`)
|
||||
|
|
||||
46 | 1<2 and 'a' or 'b'
|
||||
47 |
|
||||
48 | (lambda x: x+1) and 'a' or 'b'
|
||||
| ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ PLR1706
|
||||
49 |
|
||||
50 | 'a' and (lambda x: x+1) or 'orange'
|
||||
|
|
||||
= help: Convert to if-else expression
|
||||
|
||||
ℹ Unsafe fix
|
||||
45 45 |
|
||||
46 46 | 1<2 and 'a' or 'b'
|
||||
47 47 |
|
||||
48 |-(lambda x: x+1) and 'a' or 'b'
|
||||
48 |+'a' if (lambda x: x + 1) else 'b'
|
||||
49 49 |
|
||||
50 50 | 'a' and (lambda x: x+1) or 'orange'
|
||||
51 51 |
|
||||
|
||||
and_or_ternary.py:50:1: PLR1706 [*] Consider using if-else expression (`(lambda x: x + 1) if 'a' else 'orange'`)
|
||||
|
|
||||
48 | (lambda x: x+1) and 'a' or 'b'
|
||||
49 |
|
||||
50 | 'a' and (lambda x: x+1) or 'orange'
|
||||
| ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ PLR1706
|
||||
51 |
|
||||
52 | val = '#0000FF'
|
||||
|
|
||||
= help: Convert to if-else expression
|
||||
|
||||
ℹ Unsafe fix
|
||||
47 47 |
|
||||
48 48 | (lambda x: x+1) and 'a' or 'b'
|
||||
49 49 |
|
||||
50 |-'a' and (lambda x: x+1) or 'orange'
|
||||
50 |+(lambda x: x + 1) if 'a' else 'orange'
|
||||
51 51 |
|
||||
52 52 | val = '#0000FF'
|
||||
53 53 | (len(val) == 7 and val[0] == "#") or val in {'green'}
|
||||
|
||||
and_or_ternary.py:53:1: PLR1706 [*] Consider using if-else expression
|
||||
|
|
||||
52 | val = '#0000FF'
|
||||
53 | (len(val) == 7 and val[0] == "#") or val in {'green'}
|
||||
| ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ PLR1706
|
||||
54 |
|
||||
55 | marker = 'marker'
|
||||
|
|
||||
= help: Convert to if-else expression
|
||||
|
||||
ℹ Unsafe fix
|
||||
50 50 | 'a' and (lambda x: x+1) or 'orange'
|
||||
51 51 |
|
||||
52 52 | val = '#0000FF'
|
||||
53 |-(len(val) == 7 and val[0] == "#") or val in {'green'}
|
||||
53 |+val[0] == '#' if len(val) == 7 else val in {'green'}
|
||||
54 54 |
|
||||
55 55 | marker = 'marker'
|
||||
56 56 | isinstance(marker, dict) and 'field' in marker or marker in {}
|
||||
|
||||
and_or_ternary.py:56:1: PLR1706 [*] Consider using if-else expression
|
||||
|
|
||||
55 | marker = 'marker'
|
||||
56 | isinstance(marker, dict) and 'field' in marker or marker in {}
|
||||
| ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ PLR1706
|
||||
57 |
|
||||
58 | def has_oranges(oranges, apples=None) -> bool:
|
||||
|
|
||||
= help: Convert to if-else expression
|
||||
|
||||
ℹ Unsafe fix
|
||||
53 53 | (len(val) == 7 and val[0] == "#") or val in {'green'}
|
||||
54 54 |
|
||||
55 55 | marker = 'marker'
|
||||
56 |-isinstance(marker, dict) and 'field' in marker or marker in {}
|
||||
56 |+'field' in marker if isinstance(marker, dict) else marker in {}
|
||||
57 57 |
|
||||
58 58 | def has_oranges(oranges, apples=None) -> bool:
|
||||
59 59 | return apples and False or oranges
|
||||
|
||||
and_or_ternary.py:59:12: PLR1706 [*] Consider using if-else expression (`False if apples else oranges`)
|
||||
|
|
||||
58 | def has_oranges(oranges, apples=None) -> bool:
|
||||
59 | return apples and False or oranges
|
||||
| ^^^^^^^^^^^^^^^^^^^^^^^^^^^ PLR1706
|
||||
60 |
|
||||
61 | [x for x in l if a and b or c]
|
||||
|
|
||||
= help: Convert to if-else expression
|
||||
|
||||
ℹ Unsafe fix
|
||||
56 56 | isinstance(marker, dict) and 'field' in marker or marker in {}
|
||||
57 57 |
|
||||
58 58 | def has_oranges(oranges, apples=None) -> bool:
|
||||
59 |- return apples and False or oranges
|
||||
59 |+ return False if apples else oranges
|
||||
60 60 |
|
||||
61 61 | [x for x in l if a and b or c]
|
||||
62 62 |
|
||||
|
||||
and_or_ternary.py:61:18: PLR1706 [*] Consider using if-else expression (`(b if a else c)`)
|
||||
|
|
||||
59 | return apples and False or oranges
|
||||
60 |
|
||||
61 | [x for x in l if a and b or c]
|
||||
| ^^^^^^^^^^^^ PLR1706
|
||||
62 |
|
||||
63 | {x: y for x in l if a and b or c}
|
||||
|
|
||||
= help: Convert to if-else expression
|
||||
|
||||
ℹ Unsafe fix
|
||||
58 58 | def has_oranges(oranges, apples=None) -> bool:
|
||||
59 59 | return apples and False or oranges
|
||||
60 60 |
|
||||
61 |-[x for x in l if a and b or c]
|
||||
61 |+[x for x in l if (b if a else c)]
|
||||
62 62 |
|
||||
63 63 | {x: y for x in l if a and b or c}
|
||||
64 64 |
|
||||
|
||||
and_or_ternary.py:63:21: PLR1706 [*] Consider using if-else expression (`(b if a else c)`)
|
||||
|
|
||||
61 | [x for x in l if a and b or c]
|
||||
62 |
|
||||
63 | {x: y for x in l if a and b or c}
|
||||
| ^^^^^^^^^^^^ PLR1706
|
||||
64 |
|
||||
65 | {x for x in l if a and b or c}
|
||||
|
|
||||
= help: Convert to if-else expression
|
||||
|
||||
ℹ Unsafe fix
|
||||
60 60 |
|
||||
61 61 | [x for x in l if a and b or c]
|
||||
62 62 |
|
||||
63 |-{x: y for x in l if a and b or c}
|
||||
63 |+{x: y for x in l if (b if a else c)}
|
||||
64 64 |
|
||||
65 65 | {x for x in l if a and b or c}
|
||||
66 66 |
|
||||
|
||||
and_or_ternary.py:65:18: PLR1706 [*] Consider using if-else expression (`(b if a else c)`)
|
||||
|
|
||||
63 | {x: y for x in l if a and b or c}
|
||||
64 |
|
||||
65 | {x for x in l if a and b or c}
|
||||
| ^^^^^^^^^^^^ PLR1706
|
||||
66 |
|
||||
67 | new_list = [
|
||||
|
|
||||
= help: Convert to if-else expression
|
||||
|
||||
ℹ Unsafe fix
|
||||
62 62 |
|
||||
63 63 | {x: y for x in l if a and b or c}
|
||||
64 64 |
|
||||
65 |-{x for x in l if a and b or c}
|
||||
65 |+{x for x in l if (b if a else c)}
|
||||
66 66 |
|
||||
67 67 | new_list = [
|
||||
68 68 | x
|
||||
|
||||
and_or_ternary.py:70:8: PLR1706 [*] Consider using if-else expression (`(b if a else c)`)
|
||||
|
|
||||
68 | x
|
||||
69 | for sublist in all_lists
|
||||
70 | if a and b or c
|
||||
| ^^^^^^^^^^^^ PLR1706
|
||||
71 | for x in sublist
|
||||
72 | if (isinstance(operator, list) and x in operator) or x != operator
|
||||
|
|
||||
= help: Convert to if-else expression
|
||||
|
||||
ℹ Unsafe fix
|
||||
67 67 | new_list = [
|
||||
68 68 | x
|
||||
69 69 | for sublist in all_lists
|
||||
70 |- if a and b or c
|
||||
70 |+ if (b if a else c)
|
||||
71 71 | for x in sublist
|
||||
72 72 | if (isinstance(operator, list) and x in operator) or x != operator
|
||||
73 73 | ]
|
||||
|
||||
and_or_ternary.py:72:8: PLR1706 [*] Consider using if-else expression
|
||||
|
|
||||
70 | if a and b or c
|
||||
71 | for x in sublist
|
||||
72 | if (isinstance(operator, list) and x in operator) or x != operator
|
||||
| ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ PLR1706
|
||||
73 | ]
|
||||
|
|
||||
= help: Convert to if-else expression
|
||||
|
||||
ℹ Unsafe fix
|
||||
69 69 | for sublist in all_lists
|
||||
70 70 | if a and b or c
|
||||
71 71 | for x in sublist
|
||||
72 |- if (isinstance(operator, list) and x in operator) or x != operator
|
||||
72 |+ if (x in operator if isinstance(operator, list) else x != operator)
|
||||
73 73 | ]
|
||||
|
||||
|
|
@ -329,6 +329,10 @@ See also https://github.com/astral-sh/ruff/issues/2186.
|
|||
pub fn is_deprecated(&self) -> bool {
|
||||
matches!(self.group(), RuleGroup::Deprecated)
|
||||
}
|
||||
|
||||
pub fn is_removed(&self) -> bool {
|
||||
matches!(self.group(), RuleGroup::Removed)
|
||||
}
|
||||
}
|
||||
|
||||
impl Linter {
|
||||
|
|
|
@ -756,6 +756,7 @@ impl LintConfiguration {
|
|||
let mut redirects = FxHashMap::default();
|
||||
let mut deprecated_nursery_selectors = FxHashSet::default();
|
||||
let mut deprecated_selectors = FxHashSet::default();
|
||||
let mut removed_selectors = FxHashSet::default();
|
||||
let mut ignored_preview_selectors = FxHashSet::default();
|
||||
|
||||
// Track which docstring rules are specifically enabled
|
||||
|
@ -922,6 +923,13 @@ impl LintConfiguration {
|
|||
}
|
||||
}
|
||||
|
||||
// Removed rules
|
||||
if let RuleSelector::Rule { prefix, .. } = selector {
|
||||
if prefix.rules().any(|rule| rule.is_removed()) {
|
||||
removed_selectors.insert(selector);
|
||||
}
|
||||
}
|
||||
|
||||
// Redirected rules
|
||||
if let RuleSelector::Prefix {
|
||||
prefix,
|
||||
|
@ -933,6 +941,29 @@ impl LintConfiguration {
|
|||
}
|
||||
}
|
||||
|
||||
let removed_selectors = removed_selectors.iter().collect::<Vec<_>>();
|
||||
match removed_selectors.as_slice() {
|
||||
[] => (),
|
||||
[selection] => {
|
||||
let (prefix, code) = selection.prefix_and_code();
|
||||
return Err(anyhow!(
|
||||
"Rule `{prefix}{code}` was removed and cannot be selected."
|
||||
));
|
||||
}
|
||||
[..] => {
|
||||
let mut message =
|
||||
"The following rules have been removed and cannot be selected:".to_string();
|
||||
for selection in removed_selectors {
|
||||
let (prefix, code) = selection.prefix_and_code();
|
||||
message.push_str("\n - ");
|
||||
message.push_str(prefix);
|
||||
message.push_str(code);
|
||||
}
|
||||
message.push('\n');
|
||||
return Err(anyhow!(message));
|
||||
}
|
||||
}
|
||||
|
||||
for (from, target) in redirects {
|
||||
// TODO(martin): This belongs into the ruff crate.
|
||||
warn_user_once_by_id!(
|
||||
|
@ -1423,7 +1454,6 @@ mod tests {
|
|||
use std::str::FromStr;
|
||||
|
||||
const PREVIEW_RULES: &[Rule] = &[
|
||||
Rule::AndOrTernary,
|
||||
Rule::AssignmentInAssert,
|
||||
Rule::DirectLoggerInstantiation,
|
||||
Rule::InvalidGetLoggerArgument,
|
||||
|
|
1
ruff.schema.json
generated
1
ruff.schema.json
generated
|
@ -3267,7 +3267,6 @@
|
|||
"PLR1701",
|
||||
"PLR1702",
|
||||
"PLR1704",
|
||||
"PLR1706",
|
||||
"PLR171",
|
||||
"PLR1711",
|
||||
"PLR1714",
|
||||
|
|
Loading…
Add table
Add a link
Reference in a new issue