From fae605da8858f11ff5fc9b29a8716799eb5368d1 Mon Sep 17 00:00:00 2001 From: Ramon Saraiva Date: Thu, 6 Oct 2022 01:02:51 -0300 Subject: [PATCH 1/4] Add check for name-is-parameter-and-nonlocal --- doc/user_guide/checkers/features.rst | 2 ++ doc/user_guide/messages/messages_overview.rst | 1 + doc/whatsnew/fragments/6882.new_check | 3 +++ pylint/checkers/base/basic_error_checker.py | 14 ++++++++++++++ .../n/name/name_is_parameter_and_nonlocal.py | 5 +++++ .../n/name/name_is_parameter_and_nonlocal.txt | 1 + 6 files changed, 26 insertions(+) create mode 100644 doc/whatsnew/fragments/6882.new_check create mode 100644 tests/functional/n/name/name_is_parameter_and_nonlocal.py create mode 100644 tests/functional/n/name/name_is_parameter_and_nonlocal.txt diff --git a/doc/user_guide/checkers/features.rst b/doc/user_guide/checkers/features.rst index a23c57c3eb..98be1e3499 100644 --- a/doc/user_guide/checkers/features.rst +++ b/doc/user_guide/checkers/features.rst @@ -65,6 +65,8 @@ Basic checker Messages Emitted when a name is used prior a global declaration, which results in an error since Python 3.6. This message can't be emitted when using Python < 3.6. +:name-is-parameter-and-nonlocal (E0134): *Name %r is parameter and nonlocal* + Emitted when a nonlocal variable is also a parameter. :return-outside-function (E0104): *Return outside function* Used when a "return" statement is found outside a function or method. :return-arg-in-generator (E0106): *Return with argument inside generator* diff --git a/doc/user_guide/messages/messages_overview.rst b/doc/user_guide/messages/messages_overview.rst index 558b3f1bf9..935de02457 100644 --- a/doc/user_guide/messages/messages_overview.rst +++ b/doc/user_guide/messages/messages_overview.rst @@ -120,6 +120,7 @@ All messages in the error category: error/mixed-format-string error/modified-iterating-dict error/modified-iterating-set + error/name-is-parameter-and-nolocal error/no-member error/no-method-argument error/no-name-in-module diff --git a/doc/whatsnew/fragments/6882.new_check b/doc/whatsnew/fragments/6882.new_check new file mode 100644 index 0000000000..7730a4183d --- /dev/null +++ b/doc/whatsnew/fragments/6882.new_check @@ -0,0 +1,3 @@ +Added ``name-is-parameter-and-nonlocal`` which flags when a nonlocal variable is also a paremeter. + +Closes #6682 diff --git a/pylint/checkers/base/basic_error_checker.py b/pylint/checkers/base/basic_error_checker.py index f76c5aa8b6..7726ab09e0 100644 --- a/pylint/checkers/base/basic_error_checker.py +++ b/pylint/checkers/base/basic_error_checker.py @@ -204,6 +204,11 @@ class BasicErrorChecker(_BasicChecker): "which results in an error since Python 3.6.", {"minversion": (3, 6)}, ), + "E0134": ( + "Name %r is parameter and nonlocal", + "name-is-parameter-and-nonlocal", + "Emitted when a nonlocal variable is also a parameter.", + ), } def open(self) -> None: @@ -267,6 +272,7 @@ def visit_starred(self, node: nodes.Starred) -> None: ) def visit_functiondef(self, node: nodes.FunctionDef) -> None: self._check_nonlocal_and_global(node) + self._check_nonlocal_and_parameter(node) self._check_name_used_prior_global(node) if not redefined_by_decorator( node @@ -327,6 +333,14 @@ def _check_name_used_prior_global(self, node: nodes.FunctionDef) -> None: "used-prior-global-declaration", node=node_name, args=(name,) ) + def _check_nonlocal_and_parameter(self, node: nodes.FunctionDef) -> None: + """Check that a name is both parameter and nonlocal.""" + node_arg_names = {arg.name for arg in node.args.args} + for body_node in node.nodes_of_class(nodes.Nonlocal): + for non_local_name in body_node.names: + if non_local_name in node_arg_names: + self.add_message("name-is-parameter-and-nonlocal", node=body_node) + def _check_nonlocal_and_global(self, node: nodes.FunctionDef) -> None: """Check that a name is both nonlocal and global.""" diff --git a/tests/functional/n/name/name_is_parameter_and_nonlocal.py b/tests/functional/n/name/name_is_parameter_and_nonlocal.py new file mode 100644 index 0000000000..28ec156c7b --- /dev/null +++ b/tests/functional/n/name/name_is_parameter_and_nonlocal.py @@ -0,0 +1,5 @@ +# pylint: disable=missing-docstring,invalid-name,nonlocal-without-binding + + +def tomato(is_tasty: bool = True): + nonlocal is_tasty # [name-is-parameter-and-nonlocal] diff --git a/tests/functional/n/name/name_is_parameter_and_nonlocal.txt b/tests/functional/n/name/name_is_parameter_and_nonlocal.txt new file mode 100644 index 0000000000..9892f33c3a --- /dev/null +++ b/tests/functional/n/name/name_is_parameter_and_nonlocal.txt @@ -0,0 +1 @@ +name-is-parameter-and-nonlocal:5:4:5:21:tomato:Name %r is parameter and nonlocal:UNDEFINED From c3820a9ec62bef4ef8567da4d74844d33b71ef9b Mon Sep 17 00:00:00 2001 From: Ramon Saraiva Date: Thu, 6 Oct 2022 01:09:18 -0300 Subject: [PATCH 2/4] Add message args, confidence and a test case for nonlocal that is not a parameter --- pylint/checkers/base/basic_error_checker.py | 7 ++++++- tests/functional/n/name/name_is_parameter_and_nonlocal.py | 1 + tests/functional/n/name/name_is_parameter_and_nonlocal.txt | 2 +- 3 files changed, 8 insertions(+), 2 deletions(-) diff --git a/pylint/checkers/base/basic_error_checker.py b/pylint/checkers/base/basic_error_checker.py index 7726ab09e0..5ba24ec59f 100644 --- a/pylint/checkers/base/basic_error_checker.py +++ b/pylint/checkers/base/basic_error_checker.py @@ -339,7 +339,12 @@ def _check_nonlocal_and_parameter(self, node: nodes.FunctionDef) -> None: for body_node in node.nodes_of_class(nodes.Nonlocal): for non_local_name in body_node.names: if non_local_name in node_arg_names: - self.add_message("name-is-parameter-and-nonlocal", node=body_node) + self.add_message( + "name-is-parameter-and-nonlocal", + args=(non_local_name,), + node=body_node, + confidence=HIGH, + ) def _check_nonlocal_and_global(self, node: nodes.FunctionDef) -> None: """Check that a name is both nonlocal and global.""" diff --git a/tests/functional/n/name/name_is_parameter_and_nonlocal.py b/tests/functional/n/name/name_is_parameter_and_nonlocal.py index 28ec156c7b..10406a1c4b 100644 --- a/tests/functional/n/name/name_is_parameter_and_nonlocal.py +++ b/tests/functional/n/name/name_is_parameter_and_nonlocal.py @@ -2,4 +2,5 @@ def tomato(is_tasty: bool = True): + nonlocal color nonlocal is_tasty # [name-is-parameter-and-nonlocal] diff --git a/tests/functional/n/name/name_is_parameter_and_nonlocal.txt b/tests/functional/n/name/name_is_parameter_and_nonlocal.txt index 9892f33c3a..c47e2484c2 100644 --- a/tests/functional/n/name/name_is_parameter_and_nonlocal.txt +++ b/tests/functional/n/name/name_is_parameter_and_nonlocal.txt @@ -1 +1 @@ -name-is-parameter-and-nonlocal:5:4:5:21:tomato:Name %r is parameter and nonlocal:UNDEFINED +name-is-parameter-and-nonlocal:6:4:6:21:tomato:Name 'is_tasty' is parameter and nonlocal:HIGH From 14efefd7f463675977fe0cd51d6ffea40983c8c9 Mon Sep 17 00:00:00 2001 From: Ramon Saraiva Date: Thu, 6 Oct 2022 01:14:44 -0300 Subject: [PATCH 3/4] Fix fragment closes issue id --- doc/whatsnew/fragments/6882.new_check | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/doc/whatsnew/fragments/6882.new_check b/doc/whatsnew/fragments/6882.new_check index 7730a4183d..019eb44efe 100644 --- a/doc/whatsnew/fragments/6882.new_check +++ b/doc/whatsnew/fragments/6882.new_check @@ -1,3 +1,3 @@ Added ``name-is-parameter-and-nonlocal`` which flags when a nonlocal variable is also a paremeter. -Closes #6682 +Closes #6882 From de419acb99a8a34142dd1dfb795bbe93f7347bd7 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Dani=C3=ABl=20van=20Noord?= <13665637+DanielNoord@users.noreply.github.com> Date: Sun, 13 Nov 2022 14:00:49 +0100 Subject: [PATCH 4/4] Rephrase message --- doc/user_guide/checkers/features.rst | 2 +- pylint/checkers/base/basic_error_checker.py | 5 +++-- tests/functional/n/name/name_is_parameter_and_nonlocal.txt | 2 +- 3 files changed, 5 insertions(+), 4 deletions(-) diff --git a/doc/user_guide/checkers/features.rst b/doc/user_guide/checkers/features.rst index 98be1e3499..5780968828 100644 --- a/doc/user_guide/checkers/features.rst +++ b/doc/user_guide/checkers/features.rst @@ -65,7 +65,7 @@ Basic checker Messages Emitted when a name is used prior a global declaration, which results in an error since Python 3.6. This message can't be emitted when using Python < 3.6. -:name-is-parameter-and-nonlocal (E0134): *Name %r is parameter and nonlocal* +:name-is-parameter-and-nonlocal (E0134): *Name %r is both parameter and nonlocal* Emitted when a nonlocal variable is also a parameter. :return-outside-function (E0104): *Return outside function* Used when a "return" statement is found outside a function or method. diff --git a/pylint/checkers/base/basic_error_checker.py b/pylint/checkers/base/basic_error_checker.py index 5ba24ec59f..e9c36322b4 100644 --- a/pylint/checkers/base/basic_error_checker.py +++ b/pylint/checkers/base/basic_error_checker.py @@ -205,7 +205,7 @@ class BasicErrorChecker(_BasicChecker): {"minversion": (3, 6)}, ), "E0134": ( - "Name %r is parameter and nonlocal", + "Name %r is both parameter and nonlocal", "name-is-parameter-and-nonlocal", "Emitted when a nonlocal variable is also a parameter.", ), @@ -269,6 +269,7 @@ def visit_starred(self, node: nodes.Starred) -> None: "duplicate-argument-name", "nonlocal-and-global", "used-prior-global-declaration", + "name-is-parameter-and-nonlocal", ) def visit_functiondef(self, node: nodes.FunctionDef) -> None: self._check_nonlocal_and_global(node) @@ -334,7 +335,7 @@ def _check_name_used_prior_global(self, node: nodes.FunctionDef) -> None: ) def _check_nonlocal_and_parameter(self, node: nodes.FunctionDef) -> None: - """Check that a name is both parameter and nonlocal.""" + """Check if a name is both parameter and nonlocal.""" node_arg_names = {arg.name for arg in node.args.args} for body_node in node.nodes_of_class(nodes.Nonlocal): for non_local_name in body_node.names: diff --git a/tests/functional/n/name/name_is_parameter_and_nonlocal.txt b/tests/functional/n/name/name_is_parameter_and_nonlocal.txt index c47e2484c2..c8322e45f4 100644 --- a/tests/functional/n/name/name_is_parameter_and_nonlocal.txt +++ b/tests/functional/n/name/name_is_parameter_and_nonlocal.txt @@ -1 +1 @@ -name-is-parameter-and-nonlocal:6:4:6:21:tomato:Name 'is_tasty' is parameter and nonlocal:HIGH +name-is-parameter-and-nonlocal:6:4:6:21:tomato:Name 'is_tasty' is both parameter and nonlocal:HIGH