diff --git a/crates/ruff_linter/resources/test/fixtures/ruff/RUF051.py b/crates/ruff_linter/resources/test/fixtures/ruff/RUF051.py new file mode 100644 index 0000000000..7233bef5b5 --- /dev/null +++ b/crates/ruff_linter/resources/test/fixtures/ruff/RUF051.py @@ -0,0 +1,130 @@ +d = {} +l = [] + + +### Errors + +if k in d: # Bare name + del d[k] + +if '' in d: # String + del d[""] # Different quotes + +if b"" in d: # Bytes + del d[ # Multiline slice + b'''''' # Triple quotes + ] + +if 0 in d: del d[0] # Single-line statement + +if 3j in d: # Complex + del d[3j] + +if 0.1234 in d: # Float + del d[.1_2_3_4] # Number separators and shorthand syntax + +if True in d: # True + del d[True] + +if False in d: # False + del d[False] + +if None in d: # None + del d[ + # Comment in the middle + None + ] + +if ... in d: # Ellipsis + del d[ + # Comment in the middle, indented + ...] + +if "a" "bc" in d: # String concatenation + del d['abc'] + +if r"\foo" in d: # Raw string + del d['\\foo'] + +if b'yt' b'es' in d: # Bytes concatenation + del d[rb"""ytes"""] # Raw bytes + +if k in d: + # comment that gets dropped + del d[k] + +### Safely fixable + +if k in d: + del d[k] + +if '' in d: + del d[""] + +if b"" in d: + del d[ + b'''''' + ] + +if 0 in d: del d[0] + +if 3j in d: + del d[3j] + +if 0.1234 in d: + del d[.1_2_3_4] + +if True in d: + del d[True] + +if False in d: + del d[False] + +if None in d: + del d[ + None + ] + +if ... in d: + del d[ + ...] + +if "a" "bc" in d: + del d['abc'] + +if r"\foo" in d: + del d['\\foo'] + +if b'yt' b'es' in d: + del d[rb"""ytes"""] # This should not make the fix unsafe + + + +### No errors + +if k in l: # Not a dict + del l[k] + +if d.__contains__(k): # Explicit dunder call + del d[k] + +if a.k in d: # Attribute + del d[a.k] + +if (a, b) in d: # Tuple + del d[a, b] + +if 2 in d: # Different key value (int) + del d[3] + +if 2_4j in d: # Different key value (complex) + del d[3.6] # Different key value (float) + +if 0.1 + 0.2 in d: # Complex expression + del d[0.3] + +if f"0" in d: # f-string + del d[f"0"] + +if k in a.d: # Attribute dict + del a.d[k] diff --git a/crates/ruff_linter/src/checkers/ast/analyze/statement.rs b/crates/ruff_linter/src/checkers/ast/analyze/statement.rs index 73c86c2b33..655aa3db9e 100644 --- a/crates/ruff_linter/src/checkers/ast/analyze/statement.rs +++ b/crates/ruff_linter/src/checkers/ast/analyze/statement.rs @@ -1235,6 +1235,9 @@ pub(crate) fn statement(stmt: &Stmt, checker: &mut Checker) { } } } + if checker.enabled(Rule::IfKeyInDictDel) { + ruff::rules::if_key_in_dict_del(checker, if_); + } } Stmt::Assert( assert_stmt @ ast::StmtAssert { diff --git a/crates/ruff_linter/src/checkers/ast/mod.rs b/crates/ruff_linter/src/checkers/ast/mod.rs index edad3ff05d..17c8d8d33e 100644 --- a/crates/ruff_linter/src/checkers/ast/mod.rs +++ b/crates/ruff_linter/src/checkers/ast/mod.rs @@ -222,7 +222,7 @@ pub(crate) struct Checker<'a> { analyze: deferred::Analyze, /// The cumulative set of diagnostics computed across all lint rules. pub(crate) diagnostics: Vec, - /// The list of names already seen by flake8-bugbear diagnostics, to avoid duplicate violations.. + /// The list of names already seen by flake8-bugbear diagnostics, to avoid duplicate violations. pub(crate) flake8_bugbear_seen: Vec, /// The end offset of the last visited statement. last_stmt_end: TextSize, diff --git a/crates/ruff_linter/src/codes.rs b/crates/ruff_linter/src/codes.rs index c673a0987d..1dc247110d 100644 --- a/crates/ruff_linter/src/codes.rs +++ b/crates/ruff_linter/src/codes.rs @@ -987,6 +987,7 @@ pub fn code_to_rule(linter: Linter, code: &str) -> Option<(RuleGroup, Rule)> { (Ruff, "041") => (RuleGroup::Preview, rules::ruff::rules::UnnecessaryNestedLiteral), (Ruff, "046") => (RuleGroup::Preview, rules::ruff::rules::UnnecessaryCastToInt), (Ruff, "048") => (RuleGroup::Preview, rules::ruff::rules::MapIntVersionParsing), + (Ruff, "051") => (RuleGroup::Preview, rules::ruff::rules::IfKeyInDictDel), (Ruff, "052") => (RuleGroup::Preview, rules::ruff::rules::UsedDummyVariable), (Ruff, "055") => (RuleGroup::Preview, rules::ruff::rules::UnnecessaryRegularExpression), (Ruff, "100") => (RuleGroup::Stable, rules::ruff::rules::UnusedNOQA), diff --git a/crates/ruff_linter/src/rules/ruff/mod.rs b/crates/ruff_linter/src/rules/ruff/mod.rs index 69a3ea3053..d2de6345cb 100644 --- a/crates/ruff_linter/src/rules/ruff/mod.rs +++ b/crates/ruff_linter/src/rules/ruff/mod.rs @@ -71,6 +71,7 @@ mod tests { #[test_case(Rule::InvalidAssertMessageLiteralArgument, Path::new("RUF040.py"))] #[test_case(Rule::UnnecessaryNestedLiteral, Path::new("RUF041.py"))] #[test_case(Rule::UnnecessaryNestedLiteral, Path::new("RUF041.pyi"))] + #[test_case(Rule::IfKeyInDictDel, Path::new("RUF051.py"))] #[test_case(Rule::UsedDummyVariable, Path::new("RUF052.py"))] fn rules(rule_code: Rule, path: &Path) -> Result<()> { let snapshot = format!("{}_{}", rule_code.noqa_code(), path.to_string_lossy()); diff --git a/crates/ruff_linter/src/rules/ruff/rules/if_key_in_dict_del.rs b/crates/ruff_linter/src/rules/ruff/rules/if_key_in_dict_del.rs new file mode 100644 index 0000000000..10781c46c2 --- /dev/null +++ b/crates/ruff_linter/src/rules/ruff/rules/if_key_in_dict_del.rs @@ -0,0 +1,154 @@ +use crate::checkers::ast::Checker; +use ruff_diagnostics::{AlwaysFixableViolation, Applicability, Diagnostic, Edit, Fix}; +use ruff_macros::{derive_message_formats, ViolationMetadata}; +use ruff_python_ast::{CmpOp, Expr, ExprName, ExprSubscript, Stmt, StmtIf}; +use ruff_python_semantic::analyze::typing; +use ruff_python_semantic::SemanticModel; + +type Key = Expr; +type Dict = ExprName; + +/// ## What it does +/// Checks for `if key in dictionary: del dictionary[key]`. +/// +/// ## Why is this bad? +/// To remove a key-value pair from a dictionary, it's more concise to use `.pop(..., None)`. +/// +/// ## Example +/// +/// ```python +/// if key in dictionary: +/// del dictionary[key] +/// ``` +/// +/// Use instead: +/// +/// ```python +/// dictionary.pop(key, None) +/// ``` +/// +/// ## Fix safety +/// This rule's fix is marked as safe, unless the if statement contains comments. +#[derive(ViolationMetadata)] +pub(crate) struct IfKeyInDictDel; + +impl AlwaysFixableViolation for IfKeyInDictDel { + #[derive_message_formats] + fn message(&self) -> String { + "Use `pop` instead of `key in dict` followed by `delete dict[key]`".to_string() + } + + fn fix_title(&self) -> String { + "Replace `if` statement with `.pop(..., None)`".to_string() + } +} + +/// RUF051 +pub(crate) fn if_key_in_dict_del(checker: &mut Checker, stmt: &StmtIf) { + let [Stmt::Delete(delete)] = &stmt.body[..] else { + return; + }; + + let Some((test_dict, test_key)) = extract_dict_and_key_from_test(&stmt.test) else { + return; + }; + let Some((del_dict, del_key)) = extract_dict_and_key_from_del(&delete.targets) else { + return; + }; + + if !is_same_key(test_key, del_key) || !is_same_dict(test_dict, del_dict) { + return; + } + + if !is_known_to_be_of_type_dict(checker.semantic(), test_dict) { + return; + } + + let fix = replace_with_dict_pop_fix(checker, stmt, test_dict, test_key); + + let diagnostic = Diagnostic::new(IfKeyInDictDel, delete.range); + + checker.diagnostics.push(diagnostic.with_fix(fix)); +} + +fn extract_dict_and_key_from_test(test: &Expr) -> Option<(&Dict, &Key)> { + let Expr::Compare(comp) = test else { + return None; + }; + + let [Expr::Name(dict)] = comp.comparators.as_ref() else { + return None; + }; + + if !matches!(comp.ops.as_ref(), [CmpOp::In]) { + return None; + } + + Some((dict, &comp.left)) +} + +fn extract_dict_and_key_from_del(targets: &[Expr]) -> Option<(&Dict, &Key)> { + let [Expr::Subscript(ExprSubscript { value, slice, .. })] = targets else { + return None; + }; + + let Expr::Name(dict) = value.as_ref() else { + return None; + }; + + Some((dict, slice)) +} + +fn is_same_key(test: &Key, del: &Key) -> bool { + match (test, del) { + (Expr::Name(ExprName { id: test, .. }), Expr::Name(ExprName { id: del, .. })) => { + test.as_str() == del.as_str() + } + + (Expr::NoneLiteral(..), Expr::NoneLiteral(..)) => true, + (Expr::EllipsisLiteral(..), Expr::EllipsisLiteral(..)) => true, + + (Expr::BooleanLiteral(test), Expr::BooleanLiteral(del)) => test.value == del.value, + (Expr::NumberLiteral(test), Expr::NumberLiteral(del)) => test.value == del.value, + + (Expr::BytesLiteral(test), Expr::BytesLiteral(del)) => { + Iterator::eq(test.value.bytes(), del.value.bytes()) + } + + (Expr::StringLiteral(test), Expr::StringLiteral(del)) => { + Iterator::eq(test.value.chars(), del.value.chars()) + } + + _ => false, + } +} + +fn is_same_dict(test: &Dict, del: &Dict) -> bool { + test.id.as_str() == del.id.as_str() +} + +fn is_known_to_be_of_type_dict(semantic: &SemanticModel, dict: &Dict) -> bool { + let Some(binding) = semantic.only_binding(dict).map(|id| semantic.binding(id)) else { + return false; + }; + + typing::is_dict(binding, semantic) +} + +fn replace_with_dict_pop_fix(checker: &Checker, stmt: &StmtIf, dict: &Dict, key: &Key) -> Fix { + let locator = checker.locator(); + let dict_expr = locator.slice(dict); + let key_expr = locator.slice(key); + + let replacement = format!("{dict_expr}.pop({key_expr}, None)"); + let edit = Edit::range_replacement(replacement, stmt.range); + + let comment_ranges = checker.comment_ranges(); + let applicability = if comment_ranges.intersects(stmt.range) { + Applicability::Unsafe + } else { + Applicability::Safe + }; + + Fix::applicable_edit(edit, applicability) +} diff --git a/crates/ruff_linter/src/rules/ruff/rules/mod.rs b/crates/ruff_linter/src/rules/ruff/rules/mod.rs index ac0a17445c..b83156e3d6 100644 --- a/crates/ruff_linter/src/rules/ruff/rules/mod.rs +++ b/crates/ruff_linter/src/rules/ruff/rules/mod.rs @@ -7,6 +7,7 @@ pub(crate) use decimal_from_float_literal::*; pub(crate) use default_factory_kwarg::*; pub(crate) use explicit_f_string_type_conversion::*; pub(crate) use function_call_in_dataclass_default::*; +pub(crate) use if_key_in_dict_del::*; pub(crate) use implicit_optional::*; pub(crate) use incorrectly_parenthesized_tuple_in_subscript::*; pub(crate) use invalid_assert_message_literal_argument::*; @@ -54,6 +55,7 @@ mod default_factory_kwarg; mod explicit_f_string_type_conversion; mod function_call_in_dataclass_default; mod helpers; +mod if_key_in_dict_del; mod implicit_optional; mod incorrectly_parenthesized_tuple_in_subscript; mod invalid_assert_message_literal_argument; diff --git a/crates/ruff_linter/src/rules/ruff/snapshots/ruff_linter__rules__ruff__tests__RUF051_RUF051.py.snap b/crates/ruff_linter/src/rules/ruff/snapshots/ruff_linter__rules__ruff__tests__RUF051_RUF051.py.snap new file mode 100644 index 0000000000..96d8f1d940 --- /dev/null +++ b/crates/ruff_linter/src/rules/ruff/snapshots/ruff_linter__rules__ruff__tests__RUF051_RUF051.py.snap @@ -0,0 +1,600 @@ +--- +source: crates/ruff_linter/src/rules/ruff/mod.rs +snapshot_kind: text +--- +RUF051.py:8:5: RUF051 [*] Use `pop` instead of `key in dict` followed by `delete dict[key]` + | + 7 | if k in d: # Bare name + 8 | del d[k] + | ^^^^^^^^ RUF051 + 9 | +10 | if '' in d: # String + | + = help: Replace `if` statement with `.pop(..., None)` + +ℹ Unsafe fix +4 4 | +5 5 | ### Errors +6 6 | +7 |-if k in d: # Bare name +8 |- del d[k] + 7 |+d.pop(k, None) +9 8 | +10 9 | if '' in d: # String +11 10 | del d[""] # Different quotes + +RUF051.py:11:5: RUF051 [*] Use `pop` instead of `key in dict` followed by `delete dict[key]` + | +10 | if '' in d: # String +11 | del d[""] # Different quotes + | ^^^^^^^^^ RUF051 +12 | +13 | if b"" in d: # Bytes + | + = help: Replace `if` statement with `.pop(..., None)` + +ℹ Unsafe fix +7 7 | if k in d: # Bare name +8 8 | del d[k] +9 9 | +10 |-if '' in d: # String +11 |- del d[""] # Different quotes + 10 |+d.pop('', None) # Different quotes +12 11 | +13 12 | if b"" in d: # Bytes +14 13 | del d[ # Multiline slice + +RUF051.py:14:5: RUF051 [*] Use `pop` instead of `key in dict` followed by `delete dict[key]` + | +13 | if b"" in d: # Bytes +14 | del d[ # Multiline slice + | _____^ +15 | | b'''''' # Triple quotes +16 | | ] + | |_____^ RUF051 +17 | +18 | if 0 in d: del d[0] # Single-line statement + | + = help: Replace `if` statement with `.pop(..., None)` + +ℹ Unsafe fix +10 10 | if '' in d: # String +11 11 | del d[""] # Different quotes +12 12 | +13 |-if b"" in d: # Bytes +14 |- del d[ # Multiline slice +15 |- b'''''' # Triple quotes +16 |- ] + 13 |+d.pop(b"", None) +17 14 | +18 15 | if 0 in d: del d[0] # Single-line statement +19 16 | + +RUF051.py:18:12: RUF051 [*] Use `pop` instead of `key in dict` followed by `delete dict[key]` + | +16 | ] +17 | +18 | if 0 in d: del d[0] # Single-line statement + | ^^^^^^^^ RUF051 +19 | +20 | if 3j in d: # Complex + | + = help: Replace `if` statement with `.pop(..., None)` + +ℹ Safe fix +15 15 | b'''''' # Triple quotes +16 16 | ] +17 17 | +18 |-if 0 in d: del d[0] # Single-line statement + 18 |+d.pop(0, None) # Single-line statement +19 19 | +20 20 | if 3j in d: # Complex +21 21 | del d[3j] + +RUF051.py:21:5: RUF051 [*] Use `pop` instead of `key in dict` followed by `delete dict[key]` + | +20 | if 3j in d: # Complex +21 | del d[3j] + | ^^^^^^^^^ RUF051 +22 | +23 | if 0.1234 in d: # Float + | + = help: Replace `if` statement with `.pop(..., None)` + +ℹ Unsafe fix +17 17 | +18 18 | if 0 in d: del d[0] # Single-line statement +19 19 | +20 |-if 3j in d: # Complex +21 |- del d[3j] + 20 |+d.pop(3j, None) +22 21 | +23 22 | if 0.1234 in d: # Float +24 23 | del d[.1_2_3_4] # Number separators and shorthand syntax + +RUF051.py:24:5: RUF051 [*] Use `pop` instead of `key in dict` followed by `delete dict[key]` + | +23 | if 0.1234 in d: # Float +24 | del d[.1_2_3_4] # Number separators and shorthand syntax + | ^^^^^^^^^^^^^^^ RUF051 +25 | +26 | if True in d: # True + | + = help: Replace `if` statement with `.pop(..., None)` + +ℹ Unsafe fix +20 20 | if 3j in d: # Complex +21 21 | del d[3j] +22 22 | +23 |-if 0.1234 in d: # Float +24 |- del d[.1_2_3_4] # Number separators and shorthand syntax + 23 |+d.pop(0.1234, None) # Number separators and shorthand syntax +25 24 | +26 25 | if True in d: # True +27 26 | del d[True] + +RUF051.py:27:5: RUF051 [*] Use `pop` instead of `key in dict` followed by `delete dict[key]` + | +26 | if True in d: # True +27 | del d[True] + | ^^^^^^^^^^^ RUF051 +28 | +29 | if False in d: # False + | + = help: Replace `if` statement with `.pop(..., None)` + +ℹ Unsafe fix +23 23 | if 0.1234 in d: # Float +24 24 | del d[.1_2_3_4] # Number separators and shorthand syntax +25 25 | +26 |-if True in d: # True +27 |- del d[True] + 26 |+d.pop(True, None) +28 27 | +29 28 | if False in d: # False +30 29 | del d[False] + +RUF051.py:30:5: RUF051 [*] Use `pop` instead of `key in dict` followed by `delete dict[key]` + | +29 | if False in d: # False +30 | del d[False] + | ^^^^^^^^^^^^ RUF051 +31 | +32 | if None in d: # None + | + = help: Replace `if` statement with `.pop(..., None)` + +ℹ Unsafe fix +26 26 | if True in d: # True +27 27 | del d[True] +28 28 | +29 |-if False in d: # False +30 |- del d[False] + 29 |+d.pop(False, None) +31 30 | +32 31 | if None in d: # None +33 32 | del d[ + +RUF051.py:33:5: RUF051 [*] Use `pop` instead of `key in dict` followed by `delete dict[key]` + | +32 | if None in d: # None +33 | del d[ + | _____^ +34 | | # Comment in the middle +35 | | None +36 | | ] + | |_____^ RUF051 +37 | +38 | if ... in d: # Ellipsis + | + = help: Replace `if` statement with `.pop(..., None)` + +ℹ Unsafe fix +29 29 | if False in d: # False +30 30 | del d[False] +31 31 | +32 |-if None in d: # None +33 |- del d[ +34 |- # Comment in the middle +35 |- None +36 |- ] + 32 |+d.pop(None, None) +37 33 | +38 34 | if ... in d: # Ellipsis +39 35 | del d[ + +RUF051.py:39:5: RUF051 [*] Use `pop` instead of `key in dict` followed by `delete dict[key]` + | +38 | if ... in d: # Ellipsis +39 | del d[ + | _____^ +40 | | # Comment in the middle, indented +41 | | ...] + | |____________^ RUF051 +42 | +43 | if "a" "bc" in d: # String concatenation + | + = help: Replace `if` statement with `.pop(..., None)` + +ℹ Unsafe fix +35 35 | None +36 36 | ] +37 37 | +38 |-if ... in d: # Ellipsis +39 |- del d[ +40 |- # Comment in the middle, indented +41 |- ...] + 38 |+d.pop(..., None) +42 39 | +43 40 | if "a" "bc" in d: # String concatenation +44 41 | del d['abc'] + +RUF051.py:44:5: RUF051 [*] Use `pop` instead of `key in dict` followed by `delete dict[key]` + | +43 | if "a" "bc" in d: # String concatenation +44 | del d['abc'] + | ^^^^^^^^^^^^ RUF051 +45 | +46 | if r"\foo" in d: # Raw string + | + = help: Replace `if` statement with `.pop(..., None)` + +ℹ Unsafe fix +40 40 | # Comment in the middle, indented +41 41 | ...] +42 42 | +43 |-if "a" "bc" in d: # String concatenation +44 |- del d['abc'] + 43 |+d.pop("a" "bc", None) +45 44 | +46 45 | if r"\foo" in d: # Raw string +47 46 | del d['\\foo'] + +RUF051.py:47:5: RUF051 [*] Use `pop` instead of `key in dict` followed by `delete dict[key]` + | +46 | if r"\foo" in d: # Raw string +47 | del d['\\foo'] + | ^^^^^^^^^^^^^^ RUF051 +48 | +49 | if b'yt' b'es' in d: # Bytes concatenation + | + = help: Replace `if` statement with `.pop(..., None)` + +ℹ Unsafe fix +43 43 | if "a" "bc" in d: # String concatenation +44 44 | del d['abc'] +45 45 | +46 |-if r"\foo" in d: # Raw string +47 |- del d['\\foo'] + 46 |+d.pop(r"\foo", None) +48 47 | +49 48 | if b'yt' b'es' in d: # Bytes concatenation +50 49 | del d[rb"""ytes"""] # Raw bytes + +RUF051.py:50:5: RUF051 [*] Use `pop` instead of `key in dict` followed by `delete dict[key]` + | +49 | if b'yt' b'es' in d: # Bytes concatenation +50 | del d[rb"""ytes"""] # Raw bytes + | ^^^^^^^^^^^^^^^^^^^ RUF051 +51 | +52 | if k in d: + | + = help: Replace `if` statement with `.pop(..., None)` + +ℹ Unsafe fix +46 46 | if r"\foo" in d: # Raw string +47 47 | del d['\\foo'] +48 48 | +49 |-if b'yt' b'es' in d: # Bytes concatenation +50 |- del d[rb"""ytes"""] # Raw bytes + 49 |+d.pop(b'yt' b'es', None) # Raw bytes +51 50 | +52 51 | if k in d: +53 52 | # comment that gets dropped + +RUF051.py:54:5: RUF051 [*] Use `pop` instead of `key in dict` followed by `delete dict[key]` + | +52 | if k in d: +53 | # comment that gets dropped +54 | del d[k] + | ^^^^^^^^ RUF051 +55 | +56 | ### Safely fixable + | + = help: Replace `if` statement with `.pop(..., None)` + +ℹ Unsafe fix +49 49 | if b'yt' b'es' in d: # Bytes concatenation +50 50 | del d[rb"""ytes"""] # Raw bytes +51 51 | +52 |-if k in d: +53 |- # comment that gets dropped +54 |- del d[k] + 52 |+d.pop(k, None) +55 53 | +56 54 | ### Safely fixable +57 55 | + +RUF051.py:59:5: RUF051 [*] Use `pop` instead of `key in dict` followed by `delete dict[key]` + | +58 | if k in d: +59 | del d[k] + | ^^^^^^^^ RUF051 +60 | +61 | if '' in d: + | + = help: Replace `if` statement with `.pop(..., None)` + +ℹ Safe fix +55 55 | +56 56 | ### Safely fixable +57 57 | +58 |-if k in d: +59 |- del d[k] + 58 |+d.pop(k, None) +60 59 | +61 60 | if '' in d: +62 61 | del d[""] + +RUF051.py:62:5: RUF051 [*] Use `pop` instead of `key in dict` followed by `delete dict[key]` + | +61 | if '' in d: +62 | del d[""] + | ^^^^^^^^^ RUF051 +63 | +64 | if b"" in d: + | + = help: Replace `if` statement with `.pop(..., None)` + +ℹ Safe fix +58 58 | if k in d: +59 59 | del d[k] +60 60 | +61 |-if '' in d: +62 |- del d[""] + 61 |+d.pop('', None) +63 62 | +64 63 | if b"" in d: +65 64 | del d[ + +RUF051.py:65:5: RUF051 [*] Use `pop` instead of `key in dict` followed by `delete dict[key]` + | +64 | if b"" in d: +65 | del d[ + | _____^ +66 | | b'''''' +67 | | ] + | |_____^ RUF051 +68 | +69 | if 0 in d: del d[0] + | + = help: Replace `if` statement with `.pop(..., None)` + +ℹ Safe fix +61 61 | if '' in d: +62 62 | del d[""] +63 63 | +64 |-if b"" in d: +65 |- del d[ +66 |- b'''''' +67 |- ] + 64 |+d.pop(b"", None) +68 65 | +69 66 | if 0 in d: del d[0] +70 67 | + +RUF051.py:69:12: RUF051 [*] Use `pop` instead of `key in dict` followed by `delete dict[key]` + | +67 | ] +68 | +69 | if 0 in d: del d[0] + | ^^^^^^^^ RUF051 +70 | +71 | if 3j in d: + | + = help: Replace `if` statement with `.pop(..., None)` + +ℹ Safe fix +66 66 | b'''''' +67 67 | ] +68 68 | +69 |-if 0 in d: del d[0] + 69 |+d.pop(0, None) +70 70 | +71 71 | if 3j in d: +72 72 | del d[3j] + +RUF051.py:72:5: RUF051 [*] Use `pop` instead of `key in dict` followed by `delete dict[key]` + | +71 | if 3j in d: +72 | del d[3j] + | ^^^^^^^^^ RUF051 +73 | +74 | if 0.1234 in d: + | + = help: Replace `if` statement with `.pop(..., None)` + +ℹ Safe fix +68 68 | +69 69 | if 0 in d: del d[0] +70 70 | +71 |-if 3j in d: +72 |- del d[3j] + 71 |+d.pop(3j, None) +73 72 | +74 73 | if 0.1234 in d: +75 74 | del d[.1_2_3_4] + +RUF051.py:75:5: RUF051 [*] Use `pop` instead of `key in dict` followed by `delete dict[key]` + | +74 | if 0.1234 in d: +75 | del d[.1_2_3_4] + | ^^^^^^^^^^^^^^^ RUF051 +76 | +77 | if True in d: + | + = help: Replace `if` statement with `.pop(..., None)` + +ℹ Safe fix +71 71 | if 3j in d: +72 72 | del d[3j] +73 73 | +74 |-if 0.1234 in d: +75 |- del d[.1_2_3_4] + 74 |+d.pop(0.1234, None) +76 75 | +77 76 | if True in d: +78 77 | del d[True] + +RUF051.py:78:5: RUF051 [*] Use `pop` instead of `key in dict` followed by `delete dict[key]` + | +77 | if True in d: +78 | del d[True] + | ^^^^^^^^^^^ RUF051 +79 | +80 | if False in d: + | + = help: Replace `if` statement with `.pop(..., None)` + +ℹ Safe fix +74 74 | if 0.1234 in d: +75 75 | del d[.1_2_3_4] +76 76 | +77 |-if True in d: +78 |- del d[True] + 77 |+d.pop(True, None) +79 78 | +80 79 | if False in d: +81 80 | del d[False] + +RUF051.py:81:5: RUF051 [*] Use `pop` instead of `key in dict` followed by `delete dict[key]` + | +80 | if False in d: +81 | del d[False] + | ^^^^^^^^^^^^ RUF051 +82 | +83 | if None in d: + | + = help: Replace `if` statement with `.pop(..., None)` + +ℹ Safe fix +77 77 | if True in d: +78 78 | del d[True] +79 79 | +80 |-if False in d: +81 |- del d[False] + 80 |+d.pop(False, None) +82 81 | +83 82 | if None in d: +84 83 | del d[ + +RUF051.py:84:5: RUF051 [*] Use `pop` instead of `key in dict` followed by `delete dict[key]` + | +83 | if None in d: +84 | del d[ + | _____^ +85 | | None +86 | | ] + | |_____^ RUF051 +87 | +88 | if ... in d: + | + = help: Replace `if` statement with `.pop(..., None)` + +ℹ Safe fix +80 80 | if False in d: +81 81 | del d[False] +82 82 | +83 |-if None in d: +84 |- del d[ +85 |- None +86 |- ] + 83 |+d.pop(None, None) +87 84 | +88 85 | if ... in d: +89 86 | del d[ + +RUF051.py:89:5: RUF051 [*] Use `pop` instead of `key in dict` followed by `delete dict[key]` + | +88 | if ... in d: +89 | del d[ + | _____^ +90 | | ...] + | |____________^ RUF051 +91 | +92 | if "a" "bc" in d: + | + = help: Replace `if` statement with `.pop(..., None)` + +ℹ Safe fix +85 85 | None +86 86 | ] +87 87 | +88 |-if ... in d: +89 |- del d[ +90 |- ...] + 88 |+d.pop(..., None) +91 89 | +92 90 | if "a" "bc" in d: +93 91 | del d['abc'] + +RUF051.py:93:5: RUF051 [*] Use `pop` instead of `key in dict` followed by `delete dict[key]` + | +92 | if "a" "bc" in d: +93 | del d['abc'] + | ^^^^^^^^^^^^ RUF051 +94 | +95 | if r"\foo" in d: + | + = help: Replace `if` statement with `.pop(..., None)` + +ℹ Safe fix +89 89 | del d[ +90 90 | ...] +91 91 | +92 |-if "a" "bc" in d: +93 |- del d['abc'] + 92 |+d.pop("a" "bc", None) +94 93 | +95 94 | if r"\foo" in d: +96 95 | del d['\\foo'] + +RUF051.py:96:5: RUF051 [*] Use `pop` instead of `key in dict` followed by `delete dict[key]` + | +95 | if r"\foo" in d: +96 | del d['\\foo'] + | ^^^^^^^^^^^^^^ RUF051 +97 | +98 | if b'yt' b'es' in d: + | + = help: Replace `if` statement with `.pop(..., None)` + +ℹ Safe fix +92 92 | if "a" "bc" in d: +93 93 | del d['abc'] +94 94 | +95 |-if r"\foo" in d: +96 |- del d['\\foo'] + 95 |+d.pop(r"\foo", None) +97 96 | +98 97 | if b'yt' b'es' in d: +99 98 | del d[rb"""ytes"""] # This should not make the fix unsafe + +RUF051.py:99:5: RUF051 [*] Use `pop` instead of `key in dict` followed by `delete dict[key]` + | +98 | if b'yt' b'es' in d: +99 | del d[rb"""ytes"""] # This should not make the fix unsafe + | ^^^^^^^^^^^^^^^^^^^ RUF051 + | + = help: Replace `if` statement with `.pop(..., None)` + +ℹ Safe fix +95 95 | if r"\foo" in d: +96 96 | del d['\\foo'] +97 97 | +98 |-if b'yt' b'es' in d: +99 |- del d[rb"""ytes"""] # This should not make the fix unsafe + 98 |+d.pop(b'yt' b'es', None) # This should not make the fix unsafe +100 99 | +101 100 | +102 101 | diff --git a/ruff.schema.json b/ruff.schema.json index a464a0b142..78fabb2df7 100644 --- a/ruff.schema.json +++ b/ruff.schema.json @@ -3850,6 +3850,7 @@ "RUF046", "RUF048", "RUF05", + "RUF051", "RUF052", "RUF055", "RUF1",