summaryrefslogtreecommitdiff
path: root/pylint
diff options
context:
space:
mode:
authorZen Lee <53538590+zenlyj@users.noreply.github.com>2023-02-27 04:21:34 +0800
committerGitHub <noreply@github.com>2023-02-26 21:21:34 +0100
commit52a2a04a59526ce8344d4d2b7f86bb978177e047 (patch)
treed8e9053d8eca4c9eb1cce97173ef5d3a065cb750 /pylint
parent27a3984832faf36be4fab07fef84086d25283846 (diff)
downloadpylint-git-52a2a04a59526ce8344d4d2b7f86bb978177e047.tar.gz
Add new checker `bad-chained-comparison` (#7990)
Co-authored-by: Pierre Sassoulas <pierre.sassoulas@gmail.com>
Diffstat (limited to 'pylint')
-rw-r--r--pylint/checkers/bad_chained_comparison.py61
1 files changed, 61 insertions, 0 deletions
diff --git a/pylint/checkers/bad_chained_comparison.py b/pylint/checkers/bad_chained_comparison.py
new file mode 100644
index 000000000..fa75c52b2
--- /dev/null
+++ b/pylint/checkers/bad_chained_comparison.py
@@ -0,0 +1,61 @@
+# 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
+
+from __future__ import annotations
+
+from typing import TYPE_CHECKING
+
+from astroid import nodes
+
+from pylint.checkers import BaseChecker
+from pylint.interfaces import HIGH
+
+if TYPE_CHECKING:
+ from pylint.lint import PyLinter
+
+COMPARISON_OP = frozenset(("<", "<=", ">", ">=", "!=", "=="))
+IDENTITY_OP = frozenset(("is", "is not"))
+MEMBERSHIP_OP = frozenset(("in", "not in"))
+
+
+class BadChainedComparisonChecker(BaseChecker):
+ """Checks for unintentional usage of chained comparison."""
+
+ name = "bad-chained-comparison"
+ msgs = {
+ "W3501": (
+ "Suspicious %s-part chained comparison using semantically incompatible operators (%s)",
+ "bad-chained-comparison",
+ "Used when there is a chained comparison where one expression is part "
+ "of two comparisons that belong to different semantic groups "
+ '("<" does not mean the same thing as "is", chaining them in '
+ '"0 < x is None" is probably a mistake).',
+ )
+ }
+
+ def _has_diff_semantic_groups(self, operators: list[str]) -> bool:
+
+ # Check if comparison operators are in the same semantic group
+ for semantic_group in (COMPARISON_OP, IDENTITY_OP, MEMBERSHIP_OP):
+ if operators[0] in semantic_group:
+ group = semantic_group
+ return not all(o in group for o in operators)
+
+ def visit_compare(self, node: nodes.Compare) -> None:
+ operators = sorted({op[0] for op in node.ops})
+ if self._has_diff_semantic_groups(operators):
+ num_parts = f"{len(node.ops)}"
+ incompatibles = (
+ ", ".join(f"'{o}'" for o in operators[:-1]) + f" and '{operators[-1]}'"
+ )
+ self.add_message(
+ "bad-chained-comparison",
+ node=node,
+ args=(num_parts, incompatibles),
+ confidence=HIGH,
+ )
+
+
+def register(linter: PyLinter) -> None:
+ linter.register_checker(BadChainedComparisonChecker(linter))