diff options
-rw-r--r-- | pylint/checkers/base.py | 27 | ||||
-rw-r--r-- | pylint/test/functional/nonlocal_and_global.py | 2 | ||||
-rw-r--r-- | pylint/test/functional/nonlocal_without_binding.py | 20 | ||||
-rw-r--r-- | pylint/test/functional/nonlocal_without_binding.rc | 2 | ||||
-rw-r--r-- | pylint/test/functional/nonlocal_without_binding.txt | 1 |
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 |