summaryrefslogtreecommitdiff
path: root/pylint/extensions
diff options
context:
space:
mode:
authoryushao2 <36848472+yushao2@users.noreply.github.com>2023-01-28 01:08:08 +0800
committerGitHub <noreply@github.com>2023-01-27 17:08:08 +0000
commiteb950615d77a6b979af6e0d9954fdb4197f4a722 (patch)
tree8f879829b8aca67acab083c806a4eed1cb7b7cbf /pylint/extensions
parentae3f36d7c7cf73a76c26eee2a81393fafec34c4b (diff)
downloadpylint-git-eb950615d77a6b979af6e0d9954fdb4197f4a722.tar.gz
Add `consider-refactoring-into-while-condition` checker (#8021)
Co-authored-by: Pierre Sassoulas <pierre.sassoulas@gmail.com> Co-authored-by: Mark Byrne <31762852+mbyrnepr2@users.noreply.github.com> Co-authored-by: Jacob Walls <jacobtylerwalls@gmail.com>
Diffstat (limited to 'pylint/extensions')
-rw-r--r--pylint/extensions/consider_refactoring_into_while_condition.py93
1 files changed, 93 insertions, 0 deletions
diff --git a/pylint/extensions/consider_refactoring_into_while_condition.py b/pylint/extensions/consider_refactoring_into_while_condition.py
new file mode 100644
index 000000000..b4b53d8fa
--- /dev/null
+++ b/pylint/extensions/consider_refactoring_into_while_condition.py
@@ -0,0 +1,93 @@
+# Licensed under the GPL: https://www.gnu.org/licenses/old-licenses/gpl-2.0.html
+# For details: https://github.com/PyCQA/pylint/blob/main/LICENSE
+# Copyright (c) https://github.com/PyCQA/pylint/blob/main/CONTRIBUTORS.txt
+
+"""Looks for try/except statements with too much code in the try clause."""
+
+from __future__ import annotations
+
+from typing import TYPE_CHECKING
+
+from astroid import nodes
+
+from pylint import checkers
+from pylint.checkers import utils
+from pylint.interfaces import HIGH
+
+if TYPE_CHECKING:
+ from pylint.lint import PyLinter
+
+
+class ConsiderRefactorIntoWhileConditionChecker(checkers.BaseChecker):
+ """Checks for instances where while loops are implemented with a constant condition
+ which.
+
+ always evaluates to truthy and the first statement(s) is/are if statements which, when
+ evaluated.
+
+ to True, breaks out of the loop.
+
+ The if statement(s) can be refactored into the while loop.
+ """
+
+ name = "consider_refactoring_into_while"
+ msgs = {
+ "R3501": (
+ "Consider using 'while %s' instead of 'while %s:' an 'if', and a 'break'",
+ "consider-refactoring-into-while-condition",
+ "Emitted when `while True:` loop is used and the first statement is a break condition. "
+ "The ``if / break`` construct can be removed if the check is inverted and moved to "
+ "the ``while`` statement.",
+ ),
+ }
+
+ @utils.only_required_for_messages("consider-refactoring-into-while-condition")
+ def visit_while(self, node: nodes.While) -> None:
+ self._check_breaking_after_while_true(node)
+
+ def _check_breaking_after_while_true(self, node: nodes.While) -> None:
+ """Check that any loop with an ``if`` clause has a break statement."""
+ if not isinstance(node.test, nodes.Const) or not node.test.bool_value():
+ return
+ pri_candidates: list[nodes.If] = []
+ for n in node.body:
+ if not isinstance(n, nodes.If):
+ break
+ pri_candidates.append(n)
+ candidates = []
+ tainted = False
+ for c in pri_candidates:
+ if tainted or not isinstance(c.body[0], nodes.Break):
+ break
+ candidates.append(c)
+ orelse = c.orelse
+ while orelse:
+ orelse_node = orelse[0]
+ if not isinstance(orelse_node, nodes.If):
+ tainted = True
+ else:
+ candidates.append(orelse_node)
+ if not isinstance(orelse_node, nodes.If):
+ break
+ orelse = orelse_node.orelse
+
+ candidates = [n for n in candidates if isinstance(n.body[0], nodes.Break)]
+ msg = " and ".join(
+ [f"({utils.not_condition_as_string(c.test)})" for c in candidates]
+ )
+ if len(candidates) == 1:
+ msg = utils.not_condition_as_string(candidates[0].test)
+ if not msg:
+ return
+
+ self.add_message(
+ "consider-refactoring-into-while-condition",
+ node=node,
+ line=node.lineno,
+ args=(msg, node.test.as_string()),
+ confidence=HIGH,
+ )
+
+
+def register(linter: PyLinter) -> None:
+ linter.register_checker(ConsiderRefactorIntoWhileConditionChecker(linter))