summaryrefslogtreecommitdiff
diff options
context:
space:
mode:
authorDaniƫl van Noord <13665637+DanielNoord@users.noreply.github.com>2021-09-11 20:36:10 +0200
committerGitHub <noreply@github.com>2021-09-11 20:36:10 +0200
commitdcd488756677fdbac3a8d56c3b1f6c09ddd0d300 (patch)
treefbe30feff639069d45bfa5dc6b5baac7ff9aaba0
parent7390b6fd0b2f80a1d3dc09327f959bffff5a94b0 (diff)
downloadpylint-git-dcd488756677fdbac3a8d56c3b1f6c09ddd0d300.tar.gz
Make ``global-variable-not-assigned`` check local scope (#4990)
* Make ``global-variable-not-assigned`` check local scope This checker now checks whether the names after the global keyword are reassigned in the local scope. This closes #1375 * Make ``global-variable-not-assigned`` check functions This checker now also checks function defintions in the module and local scope This closes #330 Co-authored-by: Pierre Sassoulas <pierre.sassoulas@gmail.com>
-rw-r--r--ChangeLog6
-rw-r--r--doc/whatsnew/2.11.rst6
-rw-r--r--pylint/checkers/utils.py8
-rw-r--r--pylint/checkers/variables.py8
-rw-r--r--tests/functional/g/globals.py37
-rw-r--r--tests/functional/g/globals.txt18
-rw-r--r--tests/functional/u/unused/unused_variable.py4
-rw-r--r--tests/functional/u/unused/unused_variable.txt3
8 files changed, 78 insertions, 12 deletions
diff --git a/ChangeLog b/ChangeLog
index 0fffa5a0f..61821be25 100644
--- a/ChangeLog
+++ b/ChangeLog
@@ -77,6 +77,12 @@ Release date: TBA
Closes #4900
+* The ``global-variable-not-assigned`` checker now catches global variables that are never reassigned in a
+ local scope and catches (reassigned) functions
+
+ Closes #1375
+ Closes #330
+
* Fix ``no-self-use`` and ``docparams extension`` for async functions and methods.
diff --git a/doc/whatsnew/2.11.rst b/doc/whatsnew/2.11.rst
index 8dfcbf691..6ace2f633 100644
--- a/doc/whatsnew/2.11.rst
+++ b/doc/whatsnew/2.11.rst
@@ -79,3 +79,9 @@ Other Changes
* Fix a bug where pylint complained if the cache's parent directory does not exist
Closes #4900
+
+* The ``global-variable-not-assigned`` checker now catches global variables that are never reassigned in a
+ local scope and catches (reassigned) functions
+
+ Closes #1375
+ Closes #330
diff --git a/pylint/checkers/utils.py b/pylint/checkers/utils.py
index 6c08a48b4..6ccbecb4f 100644
--- a/pylint/checkers/utils.py
+++ b/pylint/checkers/utils.py
@@ -1558,3 +1558,11 @@ def is_node_in_guarded_import_block(node: nodes.NodeNG) -> bool:
return isinstance(node.parent, nodes.If) and (
node.parent.is_sys_guard() or node.parent.is_typing_guard()
)
+
+
+def is_reassigned_after_current(node: nodes.NodeNG, varname: str) -> bool:
+ """Check if the given variable name is reassigned in the same scope after the current node"""
+ return any(
+ a.name == varname and a.lineno > node.lineno
+ for a in node.scope().nodes_of_class((nodes.AssignName, nodes.FunctionDef))
+ )
diff --git a/pylint/checkers/variables.py b/pylint/checkers/variables.py
index aa5ac14a1..411e016f5 100644
--- a/pylint/checkers/variables.py
+++ b/pylint/checkers/variables.py
@@ -931,7 +931,10 @@ class VariablesChecker(BaseChecker):
not_defined_locally_by_import = not any(
isinstance(local, nodes.Import) for local in locals_.get(name, ())
)
- if not assign_nodes and not_defined_locally_by_import:
+ if (
+ not utils.is_reassigned_after_current(node, name)
+ and not_defined_locally_by_import
+ ):
self.add_message("global-variable-not-assigned", args=name, node=node)
default_message = False
continue
@@ -946,6 +949,9 @@ class VariablesChecker(BaseChecker):
if anode.frame() is module:
# module level assignment
break
+ if isinstance(anode, nodes.FunctionDef) and anode.parent is module:
+ # module level function assignment
+ break
else:
if not_defined_locally_by_import:
# global undefined at the module scope
diff --git a/tests/functional/g/globals.py b/tests/functional/g/globals.py
index e7f3cc85b..6440e62fc 100644
--- a/tests/functional/g/globals.py
+++ b/tests/functional/g/globals.py
@@ -1,10 +1,13 @@
"""Warnings about global statements and usage of global variables."""
+# pylint: disable=invalid-name, redefined-outer-name, missing-function-docstring
from __future__ import print_function
global CSTE # [global-at-module-level]
print(CSTE) # [undefined-variable]
CONSTANT = 1
+def FUNC():
+ pass
def fix_contant(value):
"""all this is ok, but try not using global ;)"""
@@ -25,8 +28,40 @@ def define_constant():
SOMEVAR = 2
-# pylint: disable=invalid-name
def global_with_import():
"""should only warn for global-statement"""
global sys # [global-statement]
import sys # pylint: disable=import-outside-toplevel
+
+
+def global_no_assign():
+ """Not assigning anything to the global makes 'global' superfluous"""
+ global CONSTANT # [global-variable-not-assigned]
+ print(CONSTANT)
+
+
+def global_operator_assign():
+ """Operator assigns should only throw a global statement error"""
+ global CONSTANT # [global-statement]
+ print(CONSTANT)
+ CONSTANT += 1
+
+
+def global_function_assign():
+ """Function assigns should only throw a global statement error"""
+ global CONSTANT # [global-statement]
+
+ def CONSTANT():
+ pass
+
+ CONSTANT()
+
+
+def override_func():
+ """Overriding a function should only throw a global statement error"""
+ global FUNC # [global-statement]
+
+ def FUNC():
+ pass
+
+ FUNC()
diff --git a/tests/functional/g/globals.txt b/tests/functional/g/globals.txt
index 399fea0dd..1f07e0e75 100644
--- a/tests/functional/g/globals.txt
+++ b/tests/functional/g/globals.txt
@@ -1,7 +1,11 @@
-global-at-module-level:4:0::Using the global statement at the module level
-undefined-variable:5:6::Undefined variable 'CSTE'
-global-statement:11:4:fix_contant:Using the global statement
-global-variable-not-assigned:18:4:other:Using global for 'HOP' but no assignment is done
-undefined-variable:19:10:other:Undefined variable 'HOP'
-global-variable-undefined:24:4:define_constant:Global variable 'SOMEVAR' undefined at the module level
-global-statement:31:4:global_with_import:Using the global statement
+global-at-module-level:5:0::Using the global statement at the module level:HIGH
+undefined-variable:6:6::Undefined variable 'CSTE':HIGH
+global-statement:14:4:fix_contant:Using the global statement:HIGH
+global-variable-not-assigned:21:4:other:Using global for 'HOP' but no assignment is done:HIGH
+undefined-variable:22:10:other:Undefined variable 'HOP':HIGH
+global-variable-undefined:27:4:define_constant:Global variable 'SOMEVAR' undefined at the module level:HIGH
+global-statement:33:4:global_with_import:Using the global statement:HIGH
+global-variable-not-assigned:39:4:global_no_assign:Using global for 'CONSTANT' but no assignment is done:HIGH
+global-statement:45:4:global_operator_assign:Using the global statement:HIGH
+global-statement:52:4:global_function_assign:Using the global statement:HIGH
+global-statement:62:4:override_func:Using the global statement:HIGH
diff --git a/tests/functional/u/unused/unused_variable.py b/tests/functional/u/unused/unused_variable.py
index 84ca8c50f..d5afd8f2b 100644
--- a/tests/functional/u/unused/unused_variable.py
+++ b/tests/functional/u/unused/unused_variable.py
@@ -1,4 +1,4 @@
-# pylint: disable=missing-docstring, invalid-name, too-few-public-methods, no-self-use, useless-object-inheritance,import-outside-toplevel, fixme
+# pylint: disable=missing-docstring, invalid-name, too-few-public-methods, no-self-use, useless-object-inheritance,import-outside-toplevel, fixme, line-too-long
def test_regression_737():
import xml # [unused-import]
@@ -94,7 +94,7 @@ def test_global():
""" Test various assignments of global
variables through imports.
"""
- global PATH, OS, collections, deque # [global-statement]
+ global PATH, OS, collections, deque # [global-variable-not-assigned, global-variable-not-assigned]
from os import path as PATH
import os as OS
import collections
diff --git a/tests/functional/u/unused/unused_variable.txt b/tests/functional/u/unused/unused_variable.txt
index 2687fcb5a..bfe5dc91b 100644
--- a/tests/functional/u/unused/unused_variable.txt
+++ b/tests/functional/u/unused/unused_variable.txt
@@ -13,7 +13,8 @@ unused-import:55:4:unused_import_from:Unused namedtuple imported from collection
unused-import:59:4:unused_import_in_function:Unused hexdigits imported from string
unused-variable:64:4:hello:Unused variable 'my_var'
unused-variable:76:4:function:Unused variable 'aaaa'
-global-statement:97:4:test_global:Using the global statement
+global-variable-not-assigned:97:4:test_global:Using global for 'PATH' but no assignment is done:HIGH
+global-variable-not-assigned:97:4:test_global:Using global for 'deque' but no assignment is done:HIGH
unused-import:103:4:test_global:Unused platform imported from sys
unused-import:104:4:test_global:Unused version imported from sys as VERSION
unused-import:105:4:test_global:Unused import this