summaryrefslogtreecommitdiff
diff options
context:
space:
mode:
-rw-r--r--pylint/checkers/base.py27
-rw-r--r--pylint/test/functional/nonlocal_and_global.py2
-rw-r--r--pylint/test/functional/nonlocal_without_binding.py20
-rw-r--r--pylint/test/functional/nonlocal_without_binding.rc2
-rw-r--r--pylint/test/functional/nonlocal_without_binding.txt1
5 files changed, 51 insertions, 1 deletions
diff --git a/pylint/checkers/base.py b/pylint/checkers/base.py
index 6cff1e2..38cf0ab 100644
--- a/pylint/checkers/base.py
+++ b/pylint/checkers/base.py
@@ -328,6 +328,10 @@ class BasicErrorChecker(_BasicChecker):
'continue-in-finally',
'Emitted when the `continue` keyword is found '
'inside a finally clause, which is a SyntaxError.'),
+ 'E0117': ("nonlocal variable without binding",
+ 'nonlocal-without-binding',
+ 'Emitted when a nonlocal variable is not bound',
+ {'minversion': (3, 0)}),
}
@check_messages('function-redefined')
@@ -452,6 +456,29 @@ class BasicErrorChecker(_BasicChecker):
(node.operand.op == node.op)):
self.add_message('nonexistent-operator', node=node, args=node.op*2)
+ def _check_nonlocal_without_binding(self, node, name):
+ current_scope = node.scope()
+ while True:
+ if current_scope.parent is None:
+ break
+
+ if not isinstance(current_scope, astroid.FunctionDef):
+ self.add_message('nonlocal-without-binding', node=node)
+ break
+ else:
+ if name not in current_scope.locals:
+ current_scope = current_scope.parent.scope()
+ continue
+ else:
+ return
+
+ self.add_message('nonlocal-without-binding', node=node)
+
+ @check_messages('nonlocal-without-binding')
+ def visit_nonlocal(self, node):
+ for name in node.names:
+ self._check_nonlocal_without_binding(node, name)
+
@check_messages('abstract-class-instantiated')
def visit_call(self, node):
""" Check instantiating abstract class with
diff --git a/pylint/test/functional/nonlocal_and_global.py b/pylint/test/functional/nonlocal_and_global.py
index c601a29..9400524 100644
--- a/pylint/test/functional/nonlocal_and_global.py
+++ b/pylint/test/functional/nonlocal_and_global.py
@@ -1,5 +1,5 @@
"""Test that a name is both nonlocal and global in the same scope."""
-# pylint: disable=missing-docstring,global-variable-not-assigned,invalid-name
+# pylint: disable=missing-docstring,global-variable-not-assigned,invalid-name,nonlocal-without-binding
def bad(): # [nonlocal-and-global]
nonlocal missing
diff --git a/pylint/test/functional/nonlocal_without_binding.py b/pylint/test/functional/nonlocal_without_binding.py
new file mode 100644
index 0000000..2871ed6
--- /dev/null
+++ b/pylint/test/functional/nonlocal_without_binding.py
@@ -0,0 +1,20 @@
+""" Checks that reversed() receive proper argument """
+# pylint: disable=missing-docstring
+# pylint: disable=too-few-public-methods,no-self-use,no-absolute-import,invalid-name,unused-variable
+
+def test():
+ def parent():
+ a = 42
+ def stuff():
+ nonlocal a
+
+ def parent2():
+ a = 42
+ def stuff():
+ def other_stuff():
+ nonlocal a
+
+b = 42
+def func():
+ def other_func():
+ nonlocal b # [nonlocal-without-binding]
diff --git a/pylint/test/functional/nonlocal_without_binding.rc b/pylint/test/functional/nonlocal_without_binding.rc
new file mode 100644
index 0000000..a2ab06c
--- /dev/null
+++ b/pylint/test/functional/nonlocal_without_binding.rc
@@ -0,0 +1,2 @@
+[testoptions]
+min_pyver=3.0 \ No newline at end of file
diff --git a/pylint/test/functional/nonlocal_without_binding.txt b/pylint/test/functional/nonlocal_without_binding.txt
new file mode 100644
index 0000000..17a40ac
--- /dev/null
+++ b/pylint/test/functional/nonlocal_without_binding.txt
@@ -0,0 +1 @@
+nonlocal-without-binding:20:func.other_func:nonlocal variable without binding \ No newline at end of file