summaryrefslogtreecommitdiff
diff options
context:
space:
mode:
authorcpopa <devnull@localhost>2014-09-03 16:21:07 +0300
committercpopa <devnull@localhost>2014-09-03 16:21:07 +0300
commit2da14d2ceee5d3d1727dc8abdf47af96e68bc63c (patch)
treec226617ff17d333a6dbd1e45fe3dbdc4aa5fa532
parentb4e426cb7443d465bfb0b641b1e9e69f66b330e6 (diff)
downloadpylint-2da14d2ceee5d3d1727dc8abdf47af96e68bc63c.tar.gz
Extend the cases where 'undefined-variable' and 'used-before-assignment' can be detected. Closes issue #291.
-rw-r--r--ChangeLog3
-rw-r--r--checkers/variables.py38
-rw-r--r--test/functional/undefined_variable.py12
-rw-r--r--test/functional/undefined_variable.txt1
-rw-r--r--test/functional/undefined_variable_py30.py37
-rw-r--r--test/functional/undefined_variable_py30.txt3
6 files changed, 89 insertions, 5 deletions
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