From a0d2af87bf0addb8556c065e43bbd9d6a1f04bd8 Mon Sep 17 00:00:00 2001 From: Claudiu Popa Date: Wed, 17 Jul 2019 09:41:19 +0200 Subject: [PATCH] Added a new check, ``self-assigning-variable`` This check is emitted when we detect that a variable is assigned to itself, which might indicate a potential bug in the code application. Close #2930 --- ChangeLog | 6 ++++ doc/whatsnew/2.4.rst | 16 +++++++++ pylint/checkers/base.py | 37 ++++++++++++++++++++ tests/functional/self_assigning_variable.py | 20 +++++++++++ tests/functional/self_assigning_variable.txt | 2 ++ tests/functional/unused_import.py | 2 +- 6 files changed, 82 insertions(+), 1 deletion(-) create mode 100644 tests/functional/self_assigning_variable.py create mode 100644 tests/functional/self_assigning_variable.txt diff --git a/ChangeLog b/ChangeLog index c4510509c8..8bd4347c09 100644 --- a/ChangeLog +++ b/ChangeLog @@ -7,6 +7,12 @@ What's New in Pylint 2.4.0? Release date: TBA +* Added a new check, ``self-assigning-variable`` + + This check is emitted when we detect that a variable is assigned + to itself, which might indicate a potential bug in the code application. + Close #2930 + * Added a new check, ``property-with-parameters``. This check is emitted when we detect that a defined property also diff --git a/doc/whatsnew/2.4.rst b/doc/whatsnew/2.4.rst index bf4a8f068f..6b8eb12c17 100644 --- a/doc/whatsnew/2.4.rst +++ b/doc/whatsnew/2.4.rst @@ -20,6 +20,22 @@ New checkers Close #2905 +* Added a new check, ``self-assigning-variable`` + + This check is emitted when we detect that a variable is assigned + to itself, which might indicate a potential bug in the code application. + + For example, the following would raise this warning:: + + def new_a(attr, attr2): + a_inst = Aclass() + a_inst.attr2 = attr2 + # should be: a_inst.attr = attr, but have a typo + attr = attr + return a_inst + + Close #2930 + * Added a new check ``property-with-parameters`` which detects when a property has more than a single argument. diff --git a/pylint/checkers/base.py b/pylint/checkers/base.py index bd8fcb25b0..68333f1ded 100644 --- a/pylint/checkers/base.py +++ b/pylint/checkers/base.py @@ -981,6 +981,11 @@ class BasicChecker(_BasicChecker): "Emitted when a conditional statement (If or ternary if) " "seems to wrongly call a function due to missing parentheses", ), + "W0127": ( + "Assigning the same variable %r to itself", + "self-assigning-variable", + "Emitted when we detect that a variable is assigned to itself", + ), "E0111": ( "The first reversed() argument is not a sequence", "bad-reversed-sequence", @@ -1497,6 +1502,38 @@ def visit_with(self, node): # we assume it's a nested "with" self.add_message("confusing-with-statement", node=node) + @utils.check_messages("self-assigning-variable") + def visit_assign(self, node): + + # Detect assigning to the same variable. + rhs_names = [] + targets = node.targets + if isinstance(targets[0], astroid.Tuple): + if len(targets) != 1: + # A complex assignment, so bail out early. + return + targets = targets[0].elts + + if isinstance(node.value, astroid.Name): + if len(targets) != 1: + return + rhs_names = [node.value] + elif isinstance(node.value, astroid.Tuple): + rhs_count = len(node.value.elts) + if len(targets) != rhs_count or rhs_count == 1: + return + rhs_names = node.value.elts + + for target, lhs_name in zip(targets, rhs_names): + if not isinstance(lhs_name, astroid.Name): + continue + if not isinstance(target, astroid.AssignName): + continue + if target.name == lhs_name.name: + self.add_message( + "self-assigning-variable", args=(target.name,), node=target + ) + KNOWN_NAME_TYPES = { "module", diff --git a/tests/functional/self_assigning_variable.py b/tests/functional/self_assigning_variable.py new file mode 100644 index 0000000000..2c5148f63e --- /dev/null +++ b/tests/functional/self_assigning_variable.py @@ -0,0 +1,20 @@ +# pylint: disable=missing-docstring,too-few-public-methods +# pylint: disable=unpacking-non-sequence,attribute-defined-outside-init,invalid-name + +class Class: + pass + +CLS = Class() +FIRST = 1 +# Not enough values on the right hand side +FIRST, SECOND = FIRST +# Not enough values on the left hand side +FIRST = FIRST, SECOND +# Not equivalent to a self assignment +FIRST = (FIRST, ) +# Not assigning to an attribute +CLS.FIRST = FIRST +# Not a name on the right hand side +FIRST = Class() +FIRST = FIRST # [self-assigning-variable] +FIRST, SECOND = FIRST, CLS.FIRST # [self-assigning-variable] diff --git a/tests/functional/self_assigning_variable.txt b/tests/functional/self_assigning_variable.txt new file mode 100644 index 0000000000..1787f1f474 --- /dev/null +++ b/tests/functional/self_assigning_variable.txt @@ -0,0 +1,2 @@ +self-assigning-variable:19::Assigning the same variable 'FIRST' to itself +self-assigning-variable:20::Assigning the same variable 'FIRST' to itself diff --git a/tests/functional/unused_import.py b/tests/functional/unused_import.py index 4f265277a1..752f07ca8f 100644 --- a/tests/functional/unused_import.py +++ b/tests/functional/unused_import.py @@ -9,7 +9,7 @@ from collections import deque, OrderedDict, Counter import re, html.parser # [unused-import] DATA = Counter() - +# pylint: disable=self-assigning-variable from fake import SomeName, SomeOtherName # [unused-import] class SomeClass(object): SomeName = SomeName # https://bitbucket.org/logilab/pylint/issue/475