diff --git a/crates/ruff/resources/test/fixtures/pylint/self_assigning_variable.py b/crates/ruff/resources/test/fixtures/pylint/self_assigning_variable.py new file mode 100644 index 0000000000000..288d6d6c6272e --- /dev/null +++ b/crates/ruff/resources/test/fixtures/pylint/self_assigning_variable.py @@ -0,0 +1,41 @@ +foo = 1 +bar = 2 +baz = 3 + +# Errors. +foo = foo +bar = bar +foo, bar = foo, bar +bar, foo = bar, foo +(foo, bar) = (foo, bar) +(bar, foo) = (bar, foo) +foo, (bar, baz) = foo, (bar, baz) +bar, (foo, baz) = bar, (foo, baz) +(foo, bar), baz = (foo, bar), baz +(foo, (bar, baz)) = (foo, (bar, baz)) +foo, bar = foo, 1 +bar, foo = bar, 1 +(foo, bar) = (foo, 1) +(bar, foo) = (bar, 1) +foo, (bar, baz) = foo, (bar, 1) +bar, (foo, baz) = bar, (foo, 1) +(foo, bar), baz = (foo, bar), 1 +(foo, (bar, baz)) = (foo, (bar, 1)) +foo: int = foo +bar: int = bar + +# Non-errors. +foo = bar +bar = foo +foo, bar = bar, foo +foo, bar = bar, foo +(foo, bar) = (bar, foo) +foo, bar = bar, 1 +bar, foo = foo, 1 +foo: int = bar +bar: int = 1 + + +class Foo: + foo = foo + bar = bar diff --git a/crates/ruff/src/checkers/ast/mod.rs b/crates/ruff/src/checkers/ast/mod.rs index b862b56b7003c..4cc48804c0fbb 100644 --- a/crates/ruff/src/checkers/ast/mod.rs +++ b/crates/ruff/src/checkers/ast/mod.rs @@ -1583,6 +1583,11 @@ where self.diagnostics.push(diagnostic); } } + if self.settings.rules.enabled(Rule::SelfAssigningVariable) { + if let [target] = targets.as_slice() { + pylint::rules::self_assigning_variable(self, target, value); + } + } if self.settings.rules.enabled(Rule::TypeParamNameMismatch) { pylint::rules::type_param_name_mismatch(self, value, targets); } @@ -1635,8 +1640,8 @@ where annotation, .. }) => { - if self.enabled(Rule::LambdaAssignment) { - if let Some(value) = value { + if let Some(value) = value { + if self.enabled(Rule::LambdaAssignment) { pycodestyle::rules::lambda_assignment( self, target, @@ -1645,6 +1650,9 @@ where stmt, ); } + if self.enabled(Rule::SelfAssigningVariable) { + pylint::rules::self_assigning_variable(self, target, value); + } } if self.enabled(Rule::UnintentionalTypeAnnotation) { flake8_bugbear::rules::unintentional_type_annotation( diff --git a/crates/ruff/src/codes.rs b/crates/ruff/src/codes.rs index f9d1de032dcc0..c08c86733a1dd 100644 --- a/crates/ruff/src/codes.rs +++ b/crates/ruff/src/codes.rs @@ -214,6 +214,7 @@ pub fn code_to_rule(linter: Linter, code: &str) -> Option<(RuleGroup, Rule)> { (Pylint, "R2004") => (RuleGroup::Unspecified, rules::pylint::rules::MagicValueComparison), (Pylint, "R5501") => (RuleGroup::Unspecified, rules::pylint::rules::CollapsibleElseIf), (Pylint, "W0120") => (RuleGroup::Unspecified, rules::pylint::rules::UselessElseOnLoop), + (Pylint, "W0127") => (RuleGroup::Unspecified, rules::pylint::rules::SelfAssigningVariable), (Pylint, "W0129") => (RuleGroup::Unspecified, rules::pylint::rules::AssertOnStringLiteral), (Pylint, "W0131") => (RuleGroup::Unspecified, rules::pylint::rules::NamedExprWithoutContext), (Pylint, "W0406") => (RuleGroup::Unspecified, rules::pylint::rules::ImportSelf), diff --git a/crates/ruff/src/rules/pylint/mod.rs b/crates/ruff/src/rules/pylint/mod.rs index c1e07788a62dd..af32c98ea55c9 100644 --- a/crates/ruff/src/rules/pylint/mod.rs +++ b/crates/ruff/src/rules/pylint/mod.rs @@ -116,6 +116,7 @@ mod tests { Rule::RepeatedEqualityComparisonTarget, Path::new("repeated_equality_comparison_target.py") )] + #[test_case(Rule::SelfAssigningVariable, Path::new("self_assigning_variable.py"))] #[test_case( Rule::SubprocessPopenPreexecFn, Path::new("subprocess_popen_preexec_fn.py") diff --git a/crates/ruff/src/rules/pylint/rules/mod.rs b/crates/ruff/src/rules/pylint/rules/mod.rs index a2911766a33a8..9536ba6909644 100644 --- a/crates/ruff/src/rules/pylint/rules/mod.rs +++ b/crates/ruff/src/rules/pylint/rules/mod.rs @@ -32,6 +32,7 @@ pub(crate) use redefined_loop_name::*; pub(crate) use repeated_equality_comparison_target::*; pub(crate) use repeated_isinstance_calls::*; pub(crate) use return_in_init::*; +pub(crate) use self_assigning_variable::*; pub(crate) use single_string_slots::*; pub(crate) use subprocess_popen_preexec_fn::*; pub(crate) use sys_exit_alias::*; @@ -84,6 +85,7 @@ mod redefined_loop_name; mod repeated_equality_comparison_target; mod repeated_isinstance_calls; mod return_in_init; +mod self_assigning_variable; mod single_string_slots; mod subprocess_popen_preexec_fn; mod sys_exit_alias; diff --git a/crates/ruff/src/rules/pylint/rules/self_assigning_variable.rs b/crates/ruff/src/rules/pylint/rules/self_assigning_variable.rs new file mode 100644 index 0000000000000..1cc8f1712df53 --- /dev/null +++ b/crates/ruff/src/rules/pylint/rules/self_assigning_variable.rs @@ -0,0 +1,70 @@ +use rustpython_parser::ast::{self, Expr, Ranged}; + +use ruff_diagnostics::{Diagnostic, Violation}; +use ruff_macros::{derive_message_formats, violation}; + +use crate::checkers::ast::Checker; + +/// ## What it does +/// Checks for self-assignment of variables. +/// +/// ## Why is this bad? +/// Self-assignment of variables is redundant and likely a mistake. +/// +/// ## Example +/// ```python +/// country = "Poland" +/// country = country +/// ``` +/// +/// Use instead: +/// ```python +/// country = "Poland" +/// ``` +#[violation] +pub struct SelfAssigningVariable { + name: String, +} + +impl Violation for SelfAssigningVariable { + #[derive_message_formats] + fn message(&self) -> String { + let SelfAssigningVariable { name } = self; + format!("Self-assignment of variable `{name}`") + } +} + +/// PLW0127 +pub(crate) fn self_assigning_variable(checker: &mut Checker, target: &Expr, value: &Expr) { + fn inner(left: &Expr, right: &Expr, diagnostics: &mut Vec) { + match (left, right) { + ( + Expr::Tuple(ast::ExprTuple { elts: lhs_elts, .. }), + Expr::Tuple(ast::ExprTuple { elts: rhs_elts, .. }), + ) if lhs_elts.len() == rhs_elts.len() => lhs_elts + .iter() + .zip(rhs_elts.iter()) + .for_each(|(lhs, rhs)| inner(lhs, rhs, diagnostics)), + ( + Expr::Name(ast::ExprName { id: lhs_name, .. }), + Expr::Name(ast::ExprName { id: rhs_name, .. }), + ) if lhs_name == rhs_name => { + diagnostics.push(Diagnostic::new( + SelfAssigningVariable { + name: lhs_name.to_string(), + }, + left.range(), + )); + } + _ => {} + } + } + + // Assignments in class bodies are attributes (e.g., `x = x` assigns `x` to `self.x`, and thus + // is not a self-assignment). + if checker.semantic().scope().kind.is_class() { + return; + } + + inner(target, value, &mut checker.diagnostics); +} diff --git a/crates/ruff/src/rules/pylint/snapshots/ruff__rules__pylint__tests__PLW0127_self_assigning_variable.py.snap b/crates/ruff/src/rules/pylint/snapshots/ruff__rules__pylint__tests__PLW0127_self_assigning_variable.py.snap new file mode 100644 index 0000000000000..331144501f707 --- /dev/null +++ b/crates/ruff/src/rules/pylint/snapshots/ruff__rules__pylint__tests__PLW0127_self_assigning_variable.py.snap @@ -0,0 +1,362 @@ +--- +source: crates/ruff/src/rules/pylint/mod.rs +--- +self_assigning_variable.py:6:1: PLW0127 Self-assignment of variable `foo` + | +5 | # Errors. +6 | foo = foo + | ^^^ PLW0127 +7 | bar = bar +8 | foo, bar = foo, bar + | + +self_assigning_variable.py:7:1: PLW0127 Self-assignment of variable `bar` + | +5 | # Errors. +6 | foo = foo +7 | bar = bar + | ^^^ PLW0127 +8 | foo, bar = foo, bar +9 | bar, foo = bar, foo + | + +self_assigning_variable.py:8:1: PLW0127 Self-assignment of variable `foo` + | + 6 | foo = foo + 7 | bar = bar + 8 | foo, bar = foo, bar + | ^^^ PLW0127 + 9 | bar, foo = bar, foo +10 | (foo, bar) = (foo, bar) + | + +self_assigning_variable.py:8:6: PLW0127 Self-assignment of variable `bar` + | + 6 | foo = foo + 7 | bar = bar + 8 | foo, bar = foo, bar + | ^^^ PLW0127 + 9 | bar, foo = bar, foo +10 | (foo, bar) = (foo, bar) + | + +self_assigning_variable.py:9:1: PLW0127 Self-assignment of variable `bar` + | + 7 | bar = bar + 8 | foo, bar = foo, bar + 9 | bar, foo = bar, foo + | ^^^ PLW0127 +10 | (foo, bar) = (foo, bar) +11 | (bar, foo) = (bar, foo) + | + +self_assigning_variable.py:9:6: PLW0127 Self-assignment of variable `foo` + | + 7 | bar = bar + 8 | foo, bar = foo, bar + 9 | bar, foo = bar, foo + | ^^^ PLW0127 +10 | (foo, bar) = (foo, bar) +11 | (bar, foo) = (bar, foo) + | + +self_assigning_variable.py:10:2: PLW0127 Self-assignment of variable `foo` + | + 8 | foo, bar = foo, bar + 9 | bar, foo = bar, foo +10 | (foo, bar) = (foo, bar) + | ^^^ PLW0127 +11 | (bar, foo) = (bar, foo) +12 | foo, (bar, baz) = foo, (bar, baz) + | + +self_assigning_variable.py:10:7: PLW0127 Self-assignment of variable `bar` + | + 8 | foo, bar = foo, bar + 9 | bar, foo = bar, foo +10 | (foo, bar) = (foo, bar) + | ^^^ PLW0127 +11 | (bar, foo) = (bar, foo) +12 | foo, (bar, baz) = foo, (bar, baz) + | + +self_assigning_variable.py:11:2: PLW0127 Self-assignment of variable `bar` + | + 9 | bar, foo = bar, foo +10 | (foo, bar) = (foo, bar) +11 | (bar, foo) = (bar, foo) + | ^^^ PLW0127 +12 | foo, (bar, baz) = foo, (bar, baz) +13 | bar, (foo, baz) = bar, (foo, baz) + | + +self_assigning_variable.py:11:7: PLW0127 Self-assignment of variable `foo` + | + 9 | bar, foo = bar, foo +10 | (foo, bar) = (foo, bar) +11 | (bar, foo) = (bar, foo) + | ^^^ PLW0127 +12 | foo, (bar, baz) = foo, (bar, baz) +13 | bar, (foo, baz) = bar, (foo, baz) + | + +self_assigning_variable.py:12:1: PLW0127 Self-assignment of variable `foo` + | +10 | (foo, bar) = (foo, bar) +11 | (bar, foo) = (bar, foo) +12 | foo, (bar, baz) = foo, (bar, baz) + | ^^^ PLW0127 +13 | bar, (foo, baz) = bar, (foo, baz) +14 | (foo, bar), baz = (foo, bar), baz + | + +self_assigning_variable.py:12:7: PLW0127 Self-assignment of variable `bar` + | +10 | (foo, bar) = (foo, bar) +11 | (bar, foo) = (bar, foo) +12 | foo, (bar, baz) = foo, (bar, baz) + | ^^^ PLW0127 +13 | bar, (foo, baz) = bar, (foo, baz) +14 | (foo, bar), baz = (foo, bar), baz + | + +self_assigning_variable.py:12:12: PLW0127 Self-assignment of variable `baz` + | +10 | (foo, bar) = (foo, bar) +11 | (bar, foo) = (bar, foo) +12 | foo, (bar, baz) = foo, (bar, baz) + | ^^^ PLW0127 +13 | bar, (foo, baz) = bar, (foo, baz) +14 | (foo, bar), baz = (foo, bar), baz + | + +self_assigning_variable.py:13:1: PLW0127 Self-assignment of variable `bar` + | +11 | (bar, foo) = (bar, foo) +12 | foo, (bar, baz) = foo, (bar, baz) +13 | bar, (foo, baz) = bar, (foo, baz) + | ^^^ PLW0127 +14 | (foo, bar), baz = (foo, bar), baz +15 | (foo, (bar, baz)) = (foo, (bar, baz)) + | + +self_assigning_variable.py:13:7: PLW0127 Self-assignment of variable `foo` + | +11 | (bar, foo) = (bar, foo) +12 | foo, (bar, baz) = foo, (bar, baz) +13 | bar, (foo, baz) = bar, (foo, baz) + | ^^^ PLW0127 +14 | (foo, bar), baz = (foo, bar), baz +15 | (foo, (bar, baz)) = (foo, (bar, baz)) + | + +self_assigning_variable.py:13:12: PLW0127 Self-assignment of variable `baz` + | +11 | (bar, foo) = (bar, foo) +12 | foo, (bar, baz) = foo, (bar, baz) +13 | bar, (foo, baz) = bar, (foo, baz) + | ^^^ PLW0127 +14 | (foo, bar), baz = (foo, bar), baz +15 | (foo, (bar, baz)) = (foo, (bar, baz)) + | + +self_assigning_variable.py:14:2: PLW0127 Self-assignment of variable `foo` + | +12 | foo, (bar, baz) = foo, (bar, baz) +13 | bar, (foo, baz) = bar, (foo, baz) +14 | (foo, bar), baz = (foo, bar), baz + | ^^^ PLW0127 +15 | (foo, (bar, baz)) = (foo, (bar, baz)) +16 | foo, bar = foo, 1 + | + +self_assigning_variable.py:14:7: PLW0127 Self-assignment of variable `bar` + | +12 | foo, (bar, baz) = foo, (bar, baz) +13 | bar, (foo, baz) = bar, (foo, baz) +14 | (foo, bar), baz = (foo, bar), baz + | ^^^ PLW0127 +15 | (foo, (bar, baz)) = (foo, (bar, baz)) +16 | foo, bar = foo, 1 + | + +self_assigning_variable.py:14:13: PLW0127 Self-assignment of variable `baz` + | +12 | foo, (bar, baz) = foo, (bar, baz) +13 | bar, (foo, baz) = bar, (foo, baz) +14 | (foo, bar), baz = (foo, bar), baz + | ^^^ PLW0127 +15 | (foo, (bar, baz)) = (foo, (bar, baz)) +16 | foo, bar = foo, 1 + | + +self_assigning_variable.py:15:2: PLW0127 Self-assignment of variable `foo` + | +13 | bar, (foo, baz) = bar, (foo, baz) +14 | (foo, bar), baz = (foo, bar), baz +15 | (foo, (bar, baz)) = (foo, (bar, baz)) + | ^^^ PLW0127 +16 | foo, bar = foo, 1 +17 | bar, foo = bar, 1 + | + +self_assigning_variable.py:15:8: PLW0127 Self-assignment of variable `bar` + | +13 | bar, (foo, baz) = bar, (foo, baz) +14 | (foo, bar), baz = (foo, bar), baz +15 | (foo, (bar, baz)) = (foo, (bar, baz)) + | ^^^ PLW0127 +16 | foo, bar = foo, 1 +17 | bar, foo = bar, 1 + | + +self_assigning_variable.py:15:13: PLW0127 Self-assignment of variable `baz` + | +13 | bar, (foo, baz) = bar, (foo, baz) +14 | (foo, bar), baz = (foo, bar), baz +15 | (foo, (bar, baz)) = (foo, (bar, baz)) + | ^^^ PLW0127 +16 | foo, bar = foo, 1 +17 | bar, foo = bar, 1 + | + +self_assigning_variable.py:16:1: PLW0127 Self-assignment of variable `foo` + | +14 | (foo, bar), baz = (foo, bar), baz +15 | (foo, (bar, baz)) = (foo, (bar, baz)) +16 | foo, bar = foo, 1 + | ^^^ PLW0127 +17 | bar, foo = bar, 1 +18 | (foo, bar) = (foo, 1) + | + +self_assigning_variable.py:17:1: PLW0127 Self-assignment of variable `bar` + | +15 | (foo, (bar, baz)) = (foo, (bar, baz)) +16 | foo, bar = foo, 1 +17 | bar, foo = bar, 1 + | ^^^ PLW0127 +18 | (foo, bar) = (foo, 1) +19 | (bar, foo) = (bar, 1) + | + +self_assigning_variable.py:18:2: PLW0127 Self-assignment of variable `foo` + | +16 | foo, bar = foo, 1 +17 | bar, foo = bar, 1 +18 | (foo, bar) = (foo, 1) + | ^^^ PLW0127 +19 | (bar, foo) = (bar, 1) +20 | foo, (bar, baz) = foo, (bar, 1) + | + +self_assigning_variable.py:19:2: PLW0127 Self-assignment of variable `bar` + | +17 | bar, foo = bar, 1 +18 | (foo, bar) = (foo, 1) +19 | (bar, foo) = (bar, 1) + | ^^^ PLW0127 +20 | foo, (bar, baz) = foo, (bar, 1) +21 | bar, (foo, baz) = bar, (foo, 1) + | + +self_assigning_variable.py:20:1: PLW0127 Self-assignment of variable `foo` + | +18 | (foo, bar) = (foo, 1) +19 | (bar, foo) = (bar, 1) +20 | foo, (bar, baz) = foo, (bar, 1) + | ^^^ PLW0127 +21 | bar, (foo, baz) = bar, (foo, 1) +22 | (foo, bar), baz = (foo, bar), 1 + | + +self_assigning_variable.py:20:7: PLW0127 Self-assignment of variable `bar` + | +18 | (foo, bar) = (foo, 1) +19 | (bar, foo) = (bar, 1) +20 | foo, (bar, baz) = foo, (bar, 1) + | ^^^ PLW0127 +21 | bar, (foo, baz) = bar, (foo, 1) +22 | (foo, bar), baz = (foo, bar), 1 + | + +self_assigning_variable.py:21:1: PLW0127 Self-assignment of variable `bar` + | +19 | (bar, foo) = (bar, 1) +20 | foo, (bar, baz) = foo, (bar, 1) +21 | bar, (foo, baz) = bar, (foo, 1) + | ^^^ PLW0127 +22 | (foo, bar), baz = (foo, bar), 1 +23 | (foo, (bar, baz)) = (foo, (bar, 1)) + | + +self_assigning_variable.py:21:7: PLW0127 Self-assignment of variable `foo` + | +19 | (bar, foo) = (bar, 1) +20 | foo, (bar, baz) = foo, (bar, 1) +21 | bar, (foo, baz) = bar, (foo, 1) + | ^^^ PLW0127 +22 | (foo, bar), baz = (foo, bar), 1 +23 | (foo, (bar, baz)) = (foo, (bar, 1)) + | + +self_assigning_variable.py:22:2: PLW0127 Self-assignment of variable `foo` + | +20 | foo, (bar, baz) = foo, (bar, 1) +21 | bar, (foo, baz) = bar, (foo, 1) +22 | (foo, bar), baz = (foo, bar), 1 + | ^^^ PLW0127 +23 | (foo, (bar, baz)) = (foo, (bar, 1)) +24 | foo: int = foo + | + +self_assigning_variable.py:22:7: PLW0127 Self-assignment of variable `bar` + | +20 | foo, (bar, baz) = foo, (bar, 1) +21 | bar, (foo, baz) = bar, (foo, 1) +22 | (foo, bar), baz = (foo, bar), 1 + | ^^^ PLW0127 +23 | (foo, (bar, baz)) = (foo, (bar, 1)) +24 | foo: int = foo + | + +self_assigning_variable.py:23:2: PLW0127 Self-assignment of variable `foo` + | +21 | bar, (foo, baz) = bar, (foo, 1) +22 | (foo, bar), baz = (foo, bar), 1 +23 | (foo, (bar, baz)) = (foo, (bar, 1)) + | ^^^ PLW0127 +24 | foo: int = foo +25 | bar: int = bar + | + +self_assigning_variable.py:23:8: PLW0127 Self-assignment of variable `bar` + | +21 | bar, (foo, baz) = bar, (foo, 1) +22 | (foo, bar), baz = (foo, bar), 1 +23 | (foo, (bar, baz)) = (foo, (bar, 1)) + | ^^^ PLW0127 +24 | foo: int = foo +25 | bar: int = bar + | + +self_assigning_variable.py:24:1: PLW0127 Self-assignment of variable `foo` + | +22 | (foo, bar), baz = (foo, bar), 1 +23 | (foo, (bar, baz)) = (foo, (bar, 1)) +24 | foo: int = foo + | ^^^ PLW0127 +25 | bar: int = bar + | + +self_assigning_variable.py:25:1: PLW0127 Self-assignment of variable `bar` + | +23 | (foo, (bar, baz)) = (foo, (bar, 1)) +24 | foo: int = foo +25 | bar: int = bar + | ^^^ PLW0127 +26 | +27 | # Non-errors. + | + + diff --git a/ruff.schema.json b/ruff.schema.json index 5eae79f20eb05..f19fd6bc5b013 100644 --- a/ruff.schema.json +++ b/ruff.schema.json @@ -2241,6 +2241,7 @@ "PLW01", "PLW012", "PLW0120", + "PLW0127", "PLW0129", "PLW013", "PLW0131",