diff options
author | orSolocate <38433858+orSolocate@users.noreply.github.com> | 2022-11-30 23:56:02 +0200 |
---|---|---|
committer | GitHub <noreply@github.com> | 2022-11-30 22:56:02 +0100 |
commit | 1a81b16f6113b10233279a0cdfde61c30decaac3 (patch) | |
tree | c94f46a10925e73160bf55a0b928df7885dc4b0e | |
parent | 8e2c9078922ca1676587a78caa95509a1f8397f5 (diff) | |
download | pylint-git-1a81b16f6113b10233279a0cdfde61c30decaac3.tar.gz |
Add ``redundant-union-assign-typehint`` checker for duplicated assign typehints (#7745)
Co-authored-by: Pierre Sassoulas <pierre.sassoulas@gmail.com>
-rw-r--r-- | doc/whatsnew/fragments/7636.new_check | 4 | ||||
-rw-r--r-- | pylint/extensions/typing.py | 80 | ||||
-rw-r--r-- | tests/functional/ext/typing/redundant_typehint_argument.py | 21 | ||||
-rw-r--r-- | tests/functional/ext/typing/redundant_typehint_argument.rc | 8 | ||||
-rw-r--r-- | tests/functional/ext/typing/redundant_typehint_argument.txt | 9 |
5 files changed, 121 insertions, 1 deletions
diff --git a/doc/whatsnew/fragments/7636.new_check b/doc/whatsnew/fragments/7636.new_check new file mode 100644 index 000000000..b00028c31 --- /dev/null +++ b/doc/whatsnew/fragments/7636.new_check @@ -0,0 +1,4 @@ +Add ``redundant-typehint-argument`` message for `typing` plugin for duplicate assign typehints. +Enable the plugin to enable the message with: ``--load-plugins=pylint.extensions.typing``. + +Closes #7636 diff --git a/pylint/extensions/typing.py b/pylint/extensions/typing.py index 5f89cd561..1cf7ec10c 100644 --- a/pylint/extensions/typing.py +++ b/pylint/extensions/typing.py @@ -18,7 +18,7 @@ from pylint.checkers.utils import ( safe_infer, ) from pylint.constants import TYPING_NORETURN -from pylint.interfaces import INFERENCE +from pylint.interfaces import HIGH, INFERENCE if TYPE_CHECKING: from pylint.lint import PyLinter @@ -124,6 +124,11 @@ class TypingChecker(BaseChecker): "Python 3.9.0 and 3.9.1. Use ``typing.Callable`` for these cases instead. " "https://bugs.python.org/issue42965", ), + "R6006": ( + "Type `%s` is used more than once in union type annotation. Remove redundant typehints.", + "redundant-typehint-argument", + "Duplicated type arguments will be skipped by `mypy` tool, therefore should be removed to avoid confusion.", + ), } options = ( ( @@ -219,6 +224,79 @@ class TypingChecker(BaseChecker): if self._should_check_callable and node.attrname == "Callable": self._check_broken_callable(node) + @only_required_for_messages("redundant-typehint-argument") + def visit_annassign(self, node: nodes.AnnAssign) -> None: + annotation = node.annotation + if self._is_deprecated_union_annotation(annotation, "Optional"): + if self._is_optional_none_annotation(annotation): + self.add_message( + "redundant-typehint-argument", + node=annotation, + args="None", + confidence=HIGH, + ) + return + if self._is_deprecated_union_annotation(annotation, "Union") and isinstance( + annotation.slice, nodes.Tuple + ): + types = annotation.slice.elts + elif self._is_binop_union_annotation(annotation): + types = self._parse_binops_typehints(annotation) + else: + return + + self._check_union_types(types, node) + + @staticmethod + def _is_deprecated_union_annotation( + annotation: nodes.NodeNG, union_name: str + ) -> bool: + return ( + isinstance(annotation, nodes.Subscript) + and isinstance(annotation.value, nodes.Name) + and annotation.value.name == union_name + ) + + def _is_binop_union_annotation(self, annotation: nodes.NodeNG) -> bool: + return self._should_check_alternative_union_syntax and isinstance( + annotation, nodes.BinOp + ) + + @staticmethod + def _is_optional_none_annotation(annotation: nodes.Subscript) -> bool: + return ( + isinstance(annotation.slice, nodes.Const) and annotation.slice.value is None + ) + + def _parse_binops_typehints( + self, binop_node: nodes.BinOp, typehints_list: list[nodes.NodeNG] | None = None + ) -> list[nodes.NodeNG]: + typehints_list = typehints_list or [] + if isinstance(binop_node.left, nodes.BinOp): + typehints_list.extend( + self._parse_binops_typehints(binop_node.left, typehints_list) + ) + else: + typehints_list.append(binop_node.left) + typehints_list.append(binop_node.right) + return typehints_list + + def _check_union_types( + self, types: list[nodes.NodeNG], annotation: nodes.NodeNG + ) -> None: + types_set = set() + for typehint in types: + typehint_str = typehint.as_string() + if typehint_str in types_set: + self.add_message( + "redundant-typehint-argument", + node=annotation, + args=(typehint_str), + confidence=HIGH, + ) + else: + types_set.add(typehint_str) + def _check_for_alternative_union_syntax( self, node: nodes.Name | nodes.Attribute, diff --git a/tests/functional/ext/typing/redundant_typehint_argument.py b/tests/functional/ext/typing/redundant_typehint_argument.py new file mode 100644 index 000000000..2b423dc7a --- /dev/null +++ b/tests/functional/ext/typing/redundant_typehint_argument.py @@ -0,0 +1,21 @@ +""""Checks for redundant Union typehints in assignments""" +# pylint: disable=deprecated-typing-alias,consider-alternative-union-syntax,consider-using-alias + +from __future__ import annotations +from typing import Union, Optional, Sequence + +# +1: [redundant-typehint-argument, redundant-typehint-argument] +ANSWER_0: Union[int, int, str, bool, float, str] = 0 +ANSWER_1: Optional[int] = 1 +ANSWER_2: Sequence[int] = [2] +ANSWER_3: Union[list[int], str, int, bool, list[int]] = 3 # [redundant-typehint-argument] +ANSWER_4: Optional[None] = None # [redundant-typehint-argument] +ANSWER_5: Optional[list[int]] = None +ANSWER_6: Union[None, None] = None # [redundant-typehint-argument] +# +1: [redundant-typehint-argument] +ANSWER_7: Union[list[int], dict[int], dict[list[int]], list[str], list[str]] = [7] +ANSWER_8: int | int = 8 # [redundant-typehint-argument] +ANSWER_9: str | int | None | int | bool = 9 # [redundant-typehint-argument] +ANSWER_10: dict | list[int] | float | str | int | bool = 10 +# +1: [redundant-typehint-argument] +ANSWER_11: list[int] | dict[int] | dict[list[int]] | list[str] | list[str] = ['string'] diff --git a/tests/functional/ext/typing/redundant_typehint_argument.rc b/tests/functional/ext/typing/redundant_typehint_argument.rc new file mode 100644 index 000000000..7ffc1704b --- /dev/null +++ b/tests/functional/ext/typing/redundant_typehint_argument.rc @@ -0,0 +1,8 @@ +[main] +load-plugins=pylint.extensions.typing + +[testoptions] +min_pyver=3.7 + +[TYPING] +runtime-typing=no diff --git a/tests/functional/ext/typing/redundant_typehint_argument.txt b/tests/functional/ext/typing/redundant_typehint_argument.txt new file mode 100644 index 000000000..e76dc562d --- /dev/null +++ b/tests/functional/ext/typing/redundant_typehint_argument.txt @@ -0,0 +1,9 @@ +redundant-typehint-argument:8:0:8:52::Type `int` is used more than once in union type annotation. Remove redundant typehints.:HIGH +redundant-typehint-argument:8:0:8:52::Type `str` is used more than once in union type annotation. Remove redundant typehints.:HIGH +redundant-typehint-argument:11:0:11:57::Type `list[int]` is used more than once in union type annotation. Remove redundant typehints.:HIGH +redundant-typehint-argument:12:10:12:24::Type `None` is used more than once in union type annotation. Remove redundant typehints.:HIGH +redundant-typehint-argument:14:0:14:34::Type `None` is used more than once in union type annotation. Remove redundant typehints.:HIGH +redundant-typehint-argument:16:0:16:82::Type `list[str]` is used more than once in union type annotation. Remove redundant typehints.:HIGH +redundant-typehint-argument:17:0:17:23::Type `int` is used more than once in union type annotation. Remove redundant typehints.:HIGH +redundant-typehint-argument:18:0:18:43::Type `int` is used more than once in union type annotation. Remove redundant typehints.:HIGH +redundant-typehint-argument:21:0:21:87::Type `list[str]` is used more than once in union type annotation. Remove redundant typehints.:HIGH |