diff options
author | cpopa <devnull@localhost> | 2014-09-03 16:21:07 +0300 |
---|---|---|
committer | cpopa <devnull@localhost> | 2014-09-03 16:21:07 +0300 |
commit | 2da14d2ceee5d3d1727dc8abdf47af96e68bc63c (patch) | |
tree | c226617ff17d333a6dbd1e45fe3dbdc4aa5fa532 | |
parent | b4e426cb7443d465bfb0b641b1e9e69f66b330e6 (diff) | |
download | pylint-2da14d2ceee5d3d1727dc8abdf47af96e68bc63c.tar.gz |
Extend the cases where 'undefined-variable' and 'used-before-assignment' can be detected. Closes issue #291.
-rw-r--r-- | ChangeLog | 3 | ||||
-rw-r--r-- | checkers/variables.py | 38 | ||||
-rw-r--r-- | test/functional/undefined_variable.py | 12 | ||||
-rw-r--r-- | test/functional/undefined_variable.txt | 1 | ||||
-rw-r--r-- | test/functional/undefined_variable_py30.py | 37 | ||||
-rw-r--r-- | test/functional/undefined_variable_py30.txt | 3 |
6 files changed, 89 insertions, 5 deletions
@@ -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 |