diff --git a/crates/ruff/tests/integration_test.rs b/crates/ruff/tests/integration_test.rs index 150fd24b49ca8..2d3e1dc661395 100644 --- a/crates/ruff/tests/integration_test.rs +++ b/crates/ruff/tests/integration_test.rs @@ -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 diff --git a/crates/ruff_dev/src/generate_docs.rs b/crates/ruff_dev/src/generate_docs.rs index 45421ba1907d4..309a61a459fcb 100644 --- a/crates/ruff_dev/src/generate_docs.rs +++ b/crates/ruff_dev/src/generate_docs.rs @@ -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, diff --git a/crates/ruff_dev/src/generate_rules_table.rs b/crates/ruff_dev/src/generate_rules_table.rs index ad0aaa4e658f0..c167c018d05c2 100644 --- a/crates/ruff_dev/src/generate_rules_table.rs +++ b/crates/ruff_dev/src/generate_rules_table.rs @@ -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, table_out.push('\n'); for rule in rules { let status_token = match rule.group() { + RuleGroup::Removed => { + format!("{REMOVED_SYMBOL}") + } RuleGroup::Deprecated => { format!("{WARNING_SYMBOL}") } @@ -62,9 +66,20 @@ fn generate_table(table_out: &mut String, rules: impl IntoIterator, Cow::Borrowed(message) }; + // Start and end of style spans + let mut ss = ""; + let mut se = ""; + if rule.is_removed() { + ss = ""; + se = ""; + } else if rule.is_deprecated() { + ss = ""; + se = ""; + } + #[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("
"); + table_out.push_str(&format!( + "{SPACER}{REMOVED_SYMBOL}{SPACER} The rule has been removed only the documentation is available." + )); + table_out.push_str("
"); + table_out.push_str(&format!( "{SPACER}{FIX_SYMBOL}{SPACER} The rule is automatically fixable by the `--fix` command-line option." )); diff --git a/crates/ruff_linter/resources/test/fixtures/pylint/and_or_ternary.py b/crates/ruff_linter/resources/test/fixtures/pylint/and_or_ternary.py deleted file mode 100644 index a65f0b02a0dc1..0000000000000 --- a/crates/ruff_linter/resources/test/fixtures/pylint/and_or_ternary.py +++ /dev/null @@ -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 -] diff --git a/crates/ruff_linter/src/checkers/ast/analyze/expression.rs b/crates/ruff_linter/src/checkers/ast/analyze/expression.rs index ad4c9733aa988..ba1bea34f1a8c 100644 --- a/crates/ruff_linter/src/checkers/ast/analyze/expression.rs +++ b/crates/ruff_linter/src/checkers/ast/analyze/expression.rs @@ -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); } diff --git a/crates/ruff_linter/src/codes.rs b/crates/ruff_linter/src/codes.rs index 1d68317a9a237..d52f5db348a4b 100644 --- a/crates/ruff_linter/src/codes.rs +++ b/crates/ruff_linter/src/codes.rs @@ -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), diff --git a/crates/ruff_linter/src/rule_selector.rs b/crates/ruff_linter/src/rule_selector.rs index 9cfdd52eadb98..4dec3ebc0e927 100644 --- a/crates/ruff_linter/src/rule_selector.rs +++ b/crates/ruff_linter/src/rule_selector.rs @@ -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(), diff --git a/crates/ruff_linter/src/rules/pylint/mod.rs b/crates/ruff_linter/src/rules/pylint/mod.rs index 7f06f2b78dff5..518dd781459d5 100644 --- a/crates/ruff_linter/src/rules/pylint/mod.rs +++ b/crates/ruff_linter/src/rules/pylint/mod.rs @@ -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"))] diff --git a/crates/ruff_linter/src/rules/pylint/rules/and_or_ternary.rs b/crates/ruff_linter/src/rules/pylint/rules/and_or_ternary.rs index 00f2b5194d96b..c13ea44495764 100644 --- a/crates/ruff_linter/src/rules/pylint/rules/and_or_ternary.rs +++ b/crates/ruff_linter/src/rules/pylint/rules/and_or_ternary.rs @@ -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, -} +pub struct AndOrTernary; +/// PLR1706 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 { - 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; + unreachable!("PLR1706 has been removed"); } - 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())) -} -/// PLR1706 -pub(crate) fn and_or_ternary(checker: &mut Checker, bool_op: &ExprBoolOp) { - if checker.semantic().current_statement().is_if_stmt() { - return; - } - 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); } diff --git a/crates/ruff_linter/src/rules/pylint/snapshots/ruff_linter__rules__pylint__tests__PLR1706_and_or_ternary.py.snap b/crates/ruff_linter/src/rules/pylint/snapshots/ruff_linter__rules__pylint__tests__PLR1706_and_or_ternary.py.snap deleted file mode 100644 index 1bade935083f2..0000000000000 --- a/crates/ruff_linter/src/rules/pylint/snapshots/ruff_linter__rules__pylint__tests__PLR1706_and_or_ternary.py.snap +++ /dev/null @@ -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 | ] - - diff --git a/crates/ruff_macros/src/map_codes.rs b/crates/ruff_macros/src/map_codes.rs index 9aa4fbf25373d..bcbba7276fe35 100644 --- a/crates/ruff_macros/src/map_codes.rs +++ b/crates/ruff_macros/src/map_codes.rs @@ -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 { diff --git a/crates/ruff_workspace/src/configuration.rs b/crates/ruff_workspace/src/configuration.rs index 0baf593eec39d..bd10b39a91b86 100644 --- a/crates/ruff_workspace/src/configuration.rs +++ b/crates/ruff_workspace/src/configuration.rs @@ -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::>(); + 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, diff --git a/ruff.schema.json b/ruff.schema.json index 6509aa01bf743..49ee661d7c86b 100644 --- a/ruff.schema.json +++ b/ruff.schema.json @@ -3267,7 +3267,6 @@ "PLR1701", "PLR1702", "PLR1704", - "PLR1706", "PLR171", "PLR1711", "PLR1714",