diff options
author | Zen Lee <53538590+zenlyj@users.noreply.github.com> | 2023-02-27 04:21:34 +0800 |
---|---|---|
committer | GitHub <noreply@github.com> | 2023-02-26 21:21:34 +0100 |
commit | 52a2a04a59526ce8344d4d2b7f86bb978177e047 (patch) | |
tree | d8e9053d8eca4c9eb1cce97173ef5d3a065cb750 /pylint | |
parent | 27a3984832faf36be4fab07fef84086d25283846 (diff) | |
download | pylint-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.py | 61 |
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)) |