summaryrefslogtreecommitdiff
path: root/pylint/extensions
diff options
context:
space:
mode:
authorDani Alcala <112832187+clavedeluna@users.noreply.github.com>2022-11-17 10:28:10 -0300
committerGitHub <noreply@github.com>2022-11-17 14:28:10 +0100
commit325e0d45dd3f6e721de9f08e6b3908f31278534d (patch)
tree926dd1d918515f38c0ab043edfcf50141535530c /pylint/extensions
parented99ffd02eb107f5cd289c5c13175a8480c81f67 (diff)
downloadpylint-git-325e0d45dd3f6e721de9f08e6b3908f31278534d.tar.gz
Fix false negatives for-any-all (#7707)
Diffstat (limited to 'pylint/extensions')
-rw-r--r--pylint/extensions/for_any_all.py100
1 files changed, 93 insertions, 7 deletions
diff --git a/pylint/extensions/for_any_all.py b/pylint/extensions/for_any_all.py
index e6ab41c3f..257b5d37f 100644
--- a/pylint/extensions/for_any_all.py
+++ b/pylint/extensions/for_any_all.py
@@ -11,7 +11,12 @@ from typing import TYPE_CHECKING
from astroid import nodes
from pylint.checkers import BaseChecker
-from pylint.checkers.utils import only_required_for_messages, returns_bool
+from pylint.checkers.utils import (
+ assigned_bool,
+ only_required_for_messages,
+ returns_bool,
+)
+from pylint.interfaces import HIGH
if TYPE_CHECKING:
from pylint.lint.pylinter import PyLinter
@@ -36,19 +41,100 @@ class ConsiderUsingAnyOrAllChecker(BaseChecker):
return
if_children = list(node.body[0].get_children())
- if not len(if_children) == 2: # The If node has only a comparison and return
- return
- if not returns_bool(if_children[1]):
+ if any(isinstance(child, nodes.If) for child in if_children):
+ # an if node within the if-children indicates an elif clause,
+ # suggesting complex logic.
return
- # Check for terminating boolean return right after the loop
node_after_loop = node.next_sibling()
- if returns_bool(node_after_loop):
+
+ if self._assigned_reassigned_returned(node, if_children, node_after_loop):
+ final_return_bool = node_after_loop.value.name
+ suggested_string = self._build_suggested_string(node, final_return_bool)
+ self.add_message(
+ "consider-using-any-or-all",
+ node=node,
+ args=suggested_string,
+ confidence=HIGH,
+ )
+ return
+
+ if self._if_statement_returns_bool(if_children, node_after_loop):
final_return_bool = node_after_loop.value.value
suggested_string = self._build_suggested_string(node, final_return_bool)
self.add_message(
- "consider-using-any-or-all", node=node, args=suggested_string
+ "consider-using-any-or-all",
+ node=node,
+ args=suggested_string,
+ confidence=HIGH,
)
+ return
+
+ @staticmethod
+ def _if_statement_returns_bool(
+ if_children: list[nodes.NodeNG], node_after_loop: nodes.NodeNG
+ ) -> bool:
+ """Detect for-loop, if-statement, return pattern:
+
+ Ex:
+ def any_uneven(items):
+ for item in items:
+ if not item % 2 == 0:
+ return True
+ return False
+ """
+ if not len(if_children) == 2:
+ # The If node has only a comparison and return
+ return False
+ if not returns_bool(if_children[1]):
+ return False
+
+ # Check for terminating boolean return right after the loop
+ return returns_bool(node_after_loop)
+
+ @staticmethod
+ def _assigned_reassigned_returned(
+ node: nodes.For, if_children: list[nodes.NodeNG], node_after_loop: nodes.NodeNG
+ ) -> bool:
+ """Detect boolean-assign, for-loop, re-assign, return pattern:
+
+ Ex:
+ def check_lines(lines, max_chars):
+ long_line = False
+ for line in lines:
+ if len(line) > max_chars:
+ long_line = True
+ # no elif / else statement
+ return long_line
+ """
+ node_before_loop = node.previous_sibling()
+
+ if not assigned_bool(node_before_loop):
+ # node before loop isn't assigning to boolean
+ return False
+
+ assign_children = [x for x in if_children if isinstance(x, nodes.Assign)]
+ if not assign_children:
+ # if-nodes inside loop aren't assignments
+ return False
+
+ # We only care for the first assign node of the if-children. Otherwise it breaks the pattern.
+ first_target = assign_children[0].targets[0]
+ target_before_loop = node_before_loop.targets[0]
+
+ if not (
+ isinstance(first_target, nodes.AssignName)
+ and isinstance(target_before_loop, nodes.AssignName)
+ ):
+ return False
+
+ node_before_loop_name = node_before_loop.targets[0].name
+ return (
+ first_target.name == node_before_loop_name
+ and isinstance(node_after_loop, nodes.Return)
+ and isinstance(node_after_loop.value, nodes.Name)
+ and node_after_loop.value.name == node_before_loop_name
+ )
@staticmethod
def _build_suggested_string(node: nodes.For, final_return_bool: bool) -> str: