From 2da14d2ceee5d3d1727dc8abdf47af96e68bc63c Mon Sep 17 00:00:00 2001 From: cpopa Date: Wed, 3 Sep 2014 16:21:07 +0300 Subject: Extend the cases where 'undefined-variable' and 'used-before-assignment' can be detected. Closes issue #291. --- ChangeLog | 3 +++ checkers/variables.py | 38 +++++++++++++++++++++++++---- test/functional/undefined_variable.py | 12 +++++++++ test/functional/undefined_variable.txt | 1 + test/functional/undefined_variable_py30.py | 37 ++++++++++++++++++++++++++++ test/functional/undefined_variable_py30.txt | 3 +++ 6 files changed, 89 insertions(+), 5 deletions(-) create mode 100644 test/functional/undefined_variable_py30.py create mode 100644 test/functional/undefined_variable_py30.txt diff --git a/ChangeLog b/ChangeLog index 5ae6341..8ef316f 100644 --- a/ChangeLog +++ b/ChangeLog @@ -95,6 +95,9 @@ ChangeLog for Pylint string contains mixed attribute access arguments and manual fields. Closes issue #322. + * Extend the cases where 'undefined-variable' and 'used-before-assignment' + can be detected. Closes issue #291. + 2014-07-26 -- 1.3.0 diff --git a/checkers/variables.py b/checkers/variables.py index 6a26f5a..12a26ad 100644 --- a/checkers/variables.py +++ b/checkers/variables.py @@ -37,6 +37,7 @@ import six SPECIAL_OBJ = re.compile("^_{2}[a-z]+_{2}$") +PY3K = sys.version_info >= (3, 0) def in_for_else_branch(parent, stmt): """Returns True if stmt in inside the else branch for a parent For stmt.""" @@ -731,9 +732,8 @@ builtins. Remember that you should avoid to define new builtins when possible.' # class A: # b = 1 # c = lambda b=b: b * b - class_assignment = (isinstance(frame, astroid.Class) and - name in frame.locals) - if not class_assignment: + if not (isinstance(frame, astroid.Class) and + name in frame.locals): continue # the name has already been consumed, only check it's not a loop # variable used outside the loop @@ -773,10 +773,35 @@ builtins. Remember that you should avoid to define new builtins when possible.' maybee0601 = not any(isinstance(child, astroid.Nonlocal) and name in child.names for child in defframe.get_children()) + + # Handle a couple of class scoping issues. + annotation_return = False + # The class reuses itself in the class scope. + recursive_klass = (frame is defframe and + defframe.parent_of(node) and + isinstance(defframe, astroid.Class) and + node.name == defframe.name) if (self._to_consume[-1][-1] == 'lambda' and isinstance(frame, astroid.Class) and name in frame.locals): maybee0601 = True + elif (isinstance(defframe, astroid.Class) and + isinstance(frame, astroid.Function)): + # Special rule for function return annotations, + # which uses the same name as the class where + # the function lives. + if (PY3K and node is frame.returns and + defframe.parent_of(frame.returns)): + maybee0601 = annotation_return = True + + if (maybee0601 and defframe.name in defframe.locals and + defframe.locals[name][0].lineno < frame.lineno): + # Detect class assignments with the same + # name as the class. In this case, no warning + # should be raised. + maybee0601 = False + elif recursive_klass: + maybee0601 = True else: maybee0601 = maybee0601 and stmt.fromlineno <= defstmt.fromlineno @@ -785,8 +810,11 @@ builtins. Remember that you should avoid to define new builtins when possible.' and not are_exclusive(stmt, defstmt, ('NameError', 'Exception', 'BaseException'))): - if defstmt is stmt and isinstance(node, (astroid.DelName, - astroid.AssName)): + if recursive_klass or (defstmt is stmt and + isinstance(node, (astroid.DelName, + astroid.AssName))): + self.add_message('undefined-variable', args=name, node=node) + elif annotation_return: self.add_message('undefined-variable', args=name, node=node) elif self._to_consume[-1][-1] != 'lambda': # E0601 may *not* occurs in lambda scope. diff --git a/test/functional/undefined_variable.py b/test/functional/undefined_variable.py index 003708c..0b5f7aa 100644 --- a/test/functional/undefined_variable.py +++ b/test/functional/undefined_variable.py @@ -109,6 +109,18 @@ class TestClass(Ancestor): # [used-before-assignment] """ no op """ return MissingAncestor1 +class Self(object): + """ Detect when using the same name inside the class scope. """ + obj = Self # [undefined-variable] + +class Self1(object): + """ No error should be raised here. """ + + def test(self): + """ empty """ + return Self1 + + class Ancestor(object): """ No op """ diff --git a/test/functional/undefined_variable.txt b/test/functional/undefined_variable.txt index 5695be1..497f320 100644 --- a/test/functional/undefined_variable.txt +++ b/test/functional/undefined_variable.txt @@ -15,3 +15,4 @@ used-before-assignment:86:test_arguments:Using variable 'TestClass' before assig used-before-assignment:90:TestClass:Using variable 'Ancestor' before assignment used-before-assignment:93:TestClass.MissingAncestor:Using variable 'Ancestor1' before assignment used-before-assignment:100:TestClass.test1.UsingBeforeDefinition:Using variable 'Empty' before assignment +undefined-variable:114:Self:Undefined variable 'Self' \ No newline at end of file diff --git a/test/functional/undefined_variable_py30.py b/test/functional/undefined_variable_py30.py new file mode 100644 index 0000000..74e65a6 --- /dev/null +++ b/test/functional/undefined_variable_py30.py @@ -0,0 +1,37 @@ +"""Test warnings about access to undefined variables +for various Python 3 constructs. """ +# pylint: disable=too-few-public-methods, no-init, no-self-use + +class Undefined: + """ test various annotation problems. """ + + def test(self)->Undefined: # [undefined-variable] + """ used Undefined, which is Undefined in this scope. """ + + Undefined = True + + def test1(self)->Undefined: + """ This Undefined exists at local scope. """ + + def test2(self): + """ This should not emit. """ + def func()->Undefined: + """ empty """ + return + return func + + +class Undefined1: + """ Other annotation problems. """ + + Undef = 42 + ABC = 42 + + class InnerScope: + """ Test inner scope definition. """ + + def test_undefined(self)->Undef: # [undefined-variable] + """ Looking at a higher scope is impossible. """ + + def test1(self)->ABC: # [undefined-variable] + """ Triggers undefined-variable. """ diff --git a/test/functional/undefined_variable_py30.txt b/test/functional/undefined_variable_py30.txt new file mode 100644 index 0000000..30004bd --- /dev/null +++ b/test/functional/undefined_variable_py30.txt @@ -0,0 +1,3 @@ +undefined-variable:8:Undefined.test:Undefined variable 'Undefined' +undefined-variable:33:Undefined1.InnerScope.test_undefined:Undefined variable 'Undef' +undefined-variable:36:Undefined1.InnerScope.test1:Undefined variable 'ABC' \ No newline at end of file -- cgit v1.2.1