diff options
author | Marc Mueller <30130371+cdce8p@users.noreply.github.com> | 2021-04-24 20:43:29 +0200 |
---|---|---|
committer | GitHub <noreply@github.com> | 2021-04-24 20:43:29 +0200 |
commit | fce898e283107ff5a4f3dbf11c8927bef1a7333a (patch) | |
tree | 6619901970d4939589b694da7a4449c7d7736ca0 | |
parent | c91f7f9fc4247b1071c841131caed9a7b4d9c6d4 (diff) | |
download | pylint-git-fce898e283107ff5a4f3dbf11c8927bef1a7333a.tar.gz |
Add new extension TypingChecker (#4382)
23 files changed, 804 insertions, 1 deletions
@@ -57,6 +57,11 @@ Release date: Undefined * Update ``astroid`` to 2.5.4 +* Add new extension ``TypingChecker``. This optional checker can detect the use of deprecated typing aliases + and can suggest the use of the alternative union syntax where possible. + (For example, 'typing.Dict' can be replaced by 'dict', and 'typing.Unions' by '|', etc.) + Make sure to check the config options if you plan on using it! + What's New in Pylint 2.7.5? =========================== diff --git a/doc/whatsnew/2.8.rst b/doc/whatsnew/2.8.rst index 277bc8226..240cb1aaa 100644 --- a/doc/whatsnew/2.8.rst +++ b/doc/whatsnew/2.8.rst @@ -23,6 +23,12 @@ New checkers * Add ``consider-using-min-max-builtin`` check for if statement which could be replaced by Python builtin min or max. +* Add new extension ``TypingChecker``. This optional checker can detect the use of deprecated typing aliases + and can suggest the use of the alternative union syntax where possible. + (For example, 'typing.Dict' can be replaced by 'dict', and 'typing.Unions' by '|', etc.) + Make sure to check the config options if you plan on using it! + + Other Changes ============= diff --git a/pylint/config/option.py b/pylint/config/option.py index 034c2094d..5994333ec 100644 --- a/pylint/config/option.py +++ b/pylint/config/option.py @@ -63,6 +63,15 @@ def _multiple_choices_validating_option(opt, name, value): return _multiple_choice_validator(opt.choices, name, value) +def _py_version_validator(_, name, value): + if not isinstance(value, tuple): + try: + value = tuple(int(val) for val in value.split(".")) + except (ValueError, AttributeError): + raise optparse.OptionValueError(f"Invalid format for {name}") from None + return value + + VALIDATORS = { "string": utils._unquote, "int": int, @@ -76,6 +85,7 @@ VALIDATORS = { opt["choices"], name, value ), "non_empty_string": _non_empty_string_validator, + "py_version": _py_version_validator, } @@ -114,6 +124,7 @@ class Option(optparse.Option): "yn", "multiple_choice", "non_empty_string", + "py_version", ) ATTRS = optparse.Option.ATTRS + ["hide", "level"] TYPE_CHECKER = copy.copy(optparse.Option.TYPE_CHECKER) @@ -123,6 +134,7 @@ class Option(optparse.Option): TYPE_CHECKER["yn"] = _yn_validator TYPE_CHECKER["multiple_choice"] = _multiple_choices_validating_option TYPE_CHECKER["non_empty_string"] = _non_empty_string_validator + TYPE_CHECKER["py_version"] = _py_version_validator def __init__(self, *opts, **attrs): optparse.Option.__init__(self, *opts, **attrs) diff --git a/pylint/extensions/typing.py b/pylint/extensions/typing.py new file mode 100644 index 000000000..e4b72484b --- /dev/null +++ b/pylint/extensions/typing.py @@ -0,0 +1,315 @@ +from functools import lru_cache +from typing import Dict, List, NamedTuple, Set, Union + +import astroid +import astroid.bases +import astroid.node_classes + +from pylint.checkers import BaseChecker +from pylint.checkers.utils import ( + check_messages, + is_node_in_type_annotation_context, + safe_infer, +) +from pylint.interfaces import IAstroidChecker +from pylint.lint import PyLinter + + +class TypingAlias(NamedTuple): + name: str + name_collision: bool + + +DEPRECATED_TYPING_ALIASES: Dict[str, TypingAlias] = { + "typing.Tuple": TypingAlias("tuple", False), + "typing.List": TypingAlias("list", False), + "typing.Dict": TypingAlias("dict", False), + "typing.Set": TypingAlias("set", False), + "typing.FrozenSet": TypingAlias("frozenset", False), + "typing.Type": TypingAlias("type", False), + "typing.Deque": TypingAlias("collections.deque", True), + "typing.DefaultDict": TypingAlias("collections.defaultdict", True), + "typing.OrderedDict": TypingAlias("collections.OrderedDict", True), + "typing.Counter": TypingAlias("collections.Counter", True), + "typing.ChainMap": TypingAlias("collections.ChainMap", True), + "typing.Awaitable": TypingAlias("collections.abc.Awaitable", True), + "typing.Coroutine": TypingAlias("collections.abc.Coroutine", True), + "typing.AsyncIterable": TypingAlias("collections.abc.AsyncIterable", True), + "typing.AsyncIterator": TypingAlias("collections.abc.AsyncIterator", True), + "typing.AsyncGenerator": TypingAlias("collections.abc.AsyncGenerator", True), + "typing.Iterable": TypingAlias("collections.abc.Iterable", True), + "typing.Iterator": TypingAlias("collections.abc.Iterator", True), + "typing.Generator": TypingAlias("collections.abc.Generator", True), + "typing.Reversible": TypingAlias("collections.abc.Reversible", True), + "typing.Container": TypingAlias("collections.abc.Container", True), + "typing.Collection": TypingAlias("collections.abc.Collection", True), + "typing.Callable": TypingAlias("collections.abc.Callable", True), + "typing.AbstractSet": TypingAlias("collections.abc.Set", False), + "typing.MutableSet": TypingAlias("collections.abc.MutableSet", True), + "typing.Mapping": TypingAlias("collections.abc.Mapping", True), + "typing.MutableMapping": TypingAlias("collections.abc.MutableMapping", True), + "typing.Sequence": TypingAlias("collections.abc.Sequence", True), + "typing.MutableSequence": TypingAlias("collections.abc.MutableSequence", True), + "typing.ByteString": TypingAlias("collections.abc.ByteString", True), + "typing.MappingView": TypingAlias("collections.abc.MappingView", True), + "typing.KeysView": TypingAlias("collections.abc.KeysView", True), + "typing.ItemsView": TypingAlias("collections.abc.ItemsView", True), + "typing.ValuesView": TypingAlias("collections.abc.ValuesView", True), + "typing.ContextManager": TypingAlias("contextlib.AbstractContextManager", False), + "typing.AsyncContextManager": TypingAlias( + "contextlib.AbstractAsyncContextManager", False + ), + "typing.Pattern": TypingAlias("re.Pattern", True), + "typing.Match": TypingAlias("re.Match", True), + "typing.Hashable": TypingAlias("collections.abc.Hashable", True), + "typing.Sized": TypingAlias("collections.abc.Sized", True), +} + +ALIAS_NAMES = frozenset(key.split(".")[1] for key in DEPRECATED_TYPING_ALIASES) +UNION_NAMES = ("Optional", "Union") + + +class DeprecatedTypingAliasMsg(NamedTuple): + node: Union[astroid.Name, astroid.Attribute] + qname: str + alias: str + parent_subscript: bool + + +class TypingChecker(BaseChecker): + """Find issue specifically related to type annotations.""" + + __implements__ = (IAstroidChecker,) + + name = "typing" + priority = -1 + msgs = { + "W6001": ( + "'%s' is deprecated, use '%s' instead", + "deprecated-typing-alias", + "Emitted when a deprecated typing alias is used.", + ), + "R6002": ( + "'%s' will be deprecated with PY39, consider using '%s' instead%s", + "consider-using-alias", + "Only emitted if 'runtime-typing=no' and a deprecated " + "typing alias is used in a type annotation context in " + "Python 3.7 or 3.8.", + ), + "R6003": ( + "Consider using alternative Union syntax instead of '%s'%s", + "consider-alternative-union-syntax", + "Emitted when 'typing.Union' or 'typing.Optional' is used " + "instead of the alternative Union syntax 'int | None'.", + ), + } + options = ( + ( + "py-version", + { + "default": (3, 7), + "type": "py_version", + "metavar": "<py_version>", + "help": ( + "Min Python version to use for typing related checks, " + "e.g. ``3.7``. This should be equal to the min supported Python " + "version of the project." + ), + }, + ), + ( + "runtime-typing", + { + "default": True, + "type": "yn", + "metavar": "<y_or_n>", + "help": ( + "Set to ``no`` if the app / libary does NOT need to " + "support runtime introspection of type " + "annotations. Only applies to Python version " + "3.7 - 3.9" + ), + }, + ), + ) + + def __init__(self, linter: PyLinter) -> None: + """Initialize checker instance.""" + super().__init__(linter=linter) + self._alias_name_collisions: Set[str] = set() + self._consider_using_alias_msgs: List[DeprecatedTypingAliasMsg] = [] + + @lru_cache() + def _py37_plus(self) -> bool: + return self.config.py_version >= (3, 7) + + @lru_cache() + def _py39_plus(self) -> bool: + return self.config.py_version >= (3, 9) + + @lru_cache() + def _py310_plus(self) -> bool: + return self.config.py_version >= (3, 10) + + @lru_cache() + def _should_check_typing_alias(self) -> bool: + """The use of type aliases (PEP 585) requires Python 3.9 + or Python 3.7+ with postponed evaluation. + """ + return ( + self._py39_plus() + or self._py37_plus() + and self.config.runtime_typing is False + ) + + @lru_cache() + def _should_check_alternative_union_syntax(self) -> bool: + """The use of alternative union syntax (PEP 604) requires Python 3.10 + or Python 3.7+ with postponed evaluation. + """ + return ( + self._py310_plus() + or self._py37_plus() + and self.config.runtime_typing is False + ) + + def _msg_postponed_eval_hint(self, node) -> str: + """Message hint if postponed evaluation isn't enabled.""" + if self._py310_plus() or "annotations" in node.root().future_imports: + return "" + return ". Add 'from __future__ import annotations' as well" + + @check_messages( + "deprecated-typing-alias", + "consider-using-alias", + "consider-alternative-union-syntax", + ) + def visit_name(self, node: astroid.Name) -> None: + if self._should_check_typing_alias() and node.name in ALIAS_NAMES: + self._check_for_typing_alias(node) + if self._should_check_alternative_union_syntax() and node.name in UNION_NAMES: + self._check_for_alternative_union_syntax(node, node.name) + + @check_messages( + "deprecated-typing-alias", + "consider-using-alias", + "consider-alternative-union-syntax", + ) + def visit_attribute(self, node: astroid.Attribute): + if self._should_check_typing_alias() and node.attrname in ALIAS_NAMES: + self._check_for_typing_alias(node) + if ( + self._should_check_alternative_union_syntax() + and node.attrname in UNION_NAMES + ): + self._check_for_alternative_union_syntax(node, node.attrname) + + def _check_for_alternative_union_syntax( + self, + node: Union[astroid.Name, astroid.Attribute], + name: str, + ) -> None: + """Check if alternative union syntax could be used. + + Requires + - Python 3.10 + - OR: Python 3.7+ with postponed evaluation in + a type annotation context + """ + inferred = safe_infer(node) + if not ( + isinstance(inferred, astroid.FunctionDef) + and inferred.qname() + in ( + "typing.Optional", + "typing.Union", + ) + or isinstance(inferred, astroid.bases.Instance) + and inferred.qname() == "typing._SpecialForm" + ): + return + if not (self._py310_plus() or is_node_in_type_annotation_context(node)): + return + self.add_message( + "consider-alternative-union-syntax", + node=node, + args=(name, self._msg_postponed_eval_hint(node)), + ) + + def _check_for_typing_alias( + self, + node: Union[astroid.Name, astroid.Attribute], + ) -> None: + """Check if typing alias is depecated or could be replaced. + + Requires + - Python 3.9 + - OR: Python 3.7+ with postponed evaluation in + a type annotation context + + For Python 3.7+: Only emitt message if change doesn't create + any name collisions, only ever used in a type annotation + context, and can safely be replaced. + """ + inferred = safe_infer(node) + if not isinstance(inferred, astroid.ClassDef): + return + alias = DEPRECATED_TYPING_ALIASES.get(inferred.qname(), None) + if alias is None: + return + + if self._py39_plus(): + self.add_message( + "deprecated-typing-alias", + node=node, + args=(inferred.qname(), alias.name), + ) + return + + # For PY37+, check for type annotation context first + if not is_node_in_type_annotation_context(node) and isinstance( + node.parent, astroid.Subscript + ): + if alias.name_collision is True: + self._alias_name_collisions.add(inferred.qname()) + return + self._consider_using_alias_msgs.append( + DeprecatedTypingAliasMsg( + node, + inferred.qname(), + alias.name, + isinstance(node.parent, astroid.Subscript), + ) + ) + + @check_messages("consider-using-alias") + def leave_module(self, node: astroid.Module) -> None: + """After parsing of module is complete, add messages for + 'consider-using-alias' check. Make sure results are safe + to recommend / collision free. + """ + if self._py37_plus() and not self._py39_plus(): + msg_future_import = self._msg_postponed_eval_hint(node) + while True: + try: + msg = self._consider_using_alias_msgs.pop(0) + except IndexError: + break + if msg.qname in self._alias_name_collisions: + continue + self.add_message( + "consider-using-alias", + node=msg.node, + args=( + msg.qname, + msg.alias, + msg_future_import if msg.parent_subscript else "", + ), + ) + # Clear all module cache variables + self._alias_name_collisions.clear() + self._consider_using_alias_msgs.clear() + + +def register(linter: PyLinter) -> None: + linter.register_checker(TypingChecker(linter)) diff --git a/pylint/utils/utils.py b/pylint/utils/utils.py index 4d4aecc1d..709ef4afc 100644 --- a/pylint/utils/utils.py +++ b/pylint/utils/utils.py @@ -245,7 +245,9 @@ def _comment(string): def _format_option_value(optdict, value): """return the user input's value from a 'compiled' value""" - if isinstance(value, (list, tuple)): + if optdict.get("type", None) == "py_version": + value = ".".join(str(item) for item in value) + elif isinstance(value, (list, tuple)): value = ",".join(_format_option_value(optdict, item) for item in value) elif isinstance(value, dict): value = ",".join(f"{k}:{v}" for k, v in value.items()) diff --git a/tests/functional/t/typing/typing_consider_using_alias.py b/tests/functional/t/typing/typing_consider_using_alias.py new file mode 100644 index 000000000..7549dd2e4 --- /dev/null +++ b/tests/functional/t/typing/typing_consider_using_alias.py @@ -0,0 +1,61 @@ +"""Test pylint.extension.typing - consider-using-alias + +'py-version' needs to be set to '3.7' or '3.8' and 'runtime-typing=no'. +With 'from __future__ import annotations' present. +""" +# pylint: disable=missing-docstring,invalid-name,unused-argument,line-too-long +from __future__ import annotations + +import collections +import collections.abc +import typing +from collections.abc import Awaitable +from dataclasses import dataclass +from typing import Dict, List, Set, Union, TypedDict + +var1: typing.Dict[str, int] # [consider-using-alias] +var2: List[int] # [consider-using-alias] +var3: collections.abc.Iterable[int] +var4: typing.OrderedDict[str, int] # [consider-using-alias] +var5: typing.Awaitable[None] # [consider-using-alias] +var6: typing.Iterable[int] # [consider-using-alias] +var7: typing.Hashable # [consider-using-alias] +var8: typing.ContextManager[str] # [consider-using-alias] +var9: typing.Pattern[str] # [consider-using-alias] +var10: typing.re.Match[str] # [consider-using-alias] +var11: list[int] +var12: collections.abc +var13: Awaitable[None] +var14: collections.defaultdict[str, str] + +Alias1 = Set[int] +Alias2 = Dict[int, List[int]] +Alias3 = Union[int, typing.List[str]] +Alias4 = List # [consider-using-alias] + +def func1(arg1: List[int], /, *args: List[int], arg2: set[int], **kwargs: Dict[str, int]) -> typing.Tuple[int]: + # -1:[consider-using-alias,consider-using-alias,consider-using-alias,consider-using-alias] + pass + +def func2(arg1: list[int]) -> tuple[int, int]: + pass + +class CustomIntList(typing.List[int]): + pass + +cast_variable = [1, 2, 3] +cast_variable = typing.cast(List[int], cast_variable) + +(lambda x: 2)(List[int]) + +class CustomNamedTuple(typing.NamedTuple): + my_var: List[int] # [consider-using-alias] + +CustomTypedDict1 = TypedDict("CustomTypedDict1", my_var=List[int]) + +class CustomTypedDict2(TypedDict): + my_var: List[int] # [consider-using-alias] + +@dataclass +class CustomDataClass: + my_var: List[int] # [consider-using-alias] diff --git a/tests/functional/t/typing/typing_consider_using_alias.rc b/tests/functional/t/typing/typing_consider_using_alias.rc new file mode 100644 index 000000000..e1f43ca61 --- /dev/null +++ b/tests/functional/t/typing/typing_consider_using_alias.rc @@ -0,0 +1,9 @@ +[master] +load-plugins=pylint.extensions.typing + +[testoptions] +min_pyver=3.8 + +[typing] +py-version=3.7 +runtime-typing=no diff --git a/tests/functional/t/typing/typing_consider_using_alias.txt b/tests/functional/t/typing/typing_consider_using_alias.txt new file mode 100644 index 000000000..2ae1378f7 --- /dev/null +++ b/tests/functional/t/typing/typing_consider_using_alias.txt @@ -0,0 +1,17 @@ +consider-using-alias:16:6::'typing.Dict' will be deprecated with PY39, consider using 'dict' instead +consider-using-alias:17:6::'typing.List' will be deprecated with PY39, consider using 'list' instead +consider-using-alias:19:6::'typing.OrderedDict' will be deprecated with PY39, consider using 'collections.OrderedDict' instead +consider-using-alias:20:6::'typing.Awaitable' will be deprecated with PY39, consider using 'collections.abc.Awaitable' instead +consider-using-alias:21:6::'typing.Iterable' will be deprecated with PY39, consider using 'collections.abc.Iterable' instead +consider-using-alias:22:6::'typing.Hashable' will be deprecated with PY39, consider using 'collections.abc.Hashable' instead +consider-using-alias:23:6::'typing.ContextManager' will be deprecated with PY39, consider using 'contextlib.AbstractContextManager' instead +consider-using-alias:24:6::'typing.Pattern' will be deprecated with PY39, consider using 're.Pattern' instead +consider-using-alias:25:7::'typing.Match' will be deprecated with PY39, consider using 're.Match' instead +consider-using-alias:34:9::'typing.List' will be deprecated with PY39, consider using 'list' instead +consider-using-alias:36:74:func1:'typing.Dict' will be deprecated with PY39, consider using 'dict' instead +consider-using-alias:36:16:func1:'typing.List' will be deprecated with PY39, consider using 'list' instead +consider-using-alias:36:37:func1:'typing.List' will be deprecated with PY39, consider using 'list' instead +consider-using-alias:36:93:func1:'typing.Tuple' will be deprecated with PY39, consider using 'tuple' instead +consider-using-alias:52:12:CustomNamedTuple:'typing.List' will be deprecated with PY39, consider using 'list' instead +consider-using-alias:57:12:CustomTypedDict2:'typing.List' will be deprecated with PY39, consider using 'list' instead +consider-using-alias:61:12:CustomDataClass:'typing.List' will be deprecated with PY39, consider using 'list' instead diff --git a/tests/functional/t/typing/typing_consider_using_alias_without_future.py b/tests/functional/t/typing/typing_consider_using_alias_without_future.py new file mode 100644 index 000000000..44f55b8b4 --- /dev/null +++ b/tests/functional/t/typing/typing_consider_using_alias_without_future.py @@ -0,0 +1,58 @@ +"""Test pylint.extension.typing - consider-using-alias + +'py-version' needs to be set to '3.7' or '3.8' and 'runtime-typing=no'. +""" +# pylint: disable=missing-docstring,invalid-name,unused-argument,line-too-long,unsubscriptable-object +import collections +import collections.abc +import typing +from collections.abc import Awaitable +from dataclasses import dataclass +from typing import Dict, List, Set, Union, TypedDict + +var1: typing.Dict[str, int] # [consider-using-alias] +var2: List[int] # [consider-using-alias] +var3: collections.abc.Iterable[int] +var4: typing.OrderedDict[str, int] # [consider-using-alias] +var5: typing.Awaitable[None] # [consider-using-alias] +var6: typing.Iterable[int] # [consider-using-alias] +var7: typing.Hashable # [consider-using-alias] +var8: typing.ContextManager[str] # [consider-using-alias] +var9: typing.Pattern[str] # [consider-using-alias] +var10: typing.re.Match[str] # [consider-using-alias] +var11: list[int] +var12: collections.abc +var13: Awaitable[None] +var14: collections.defaultdict[str, str] + +Alias1 = Set[int] +Alias2 = Dict[int, List[int]] +Alias3 = Union[int, typing.List[str]] +Alias4 = List # [consider-using-alias] + +def func1(arg1: List[int], /, *args: List[int], arg2: set[int], **kwargs: Dict[str, int]) -> typing.Tuple[int]: + # -1:[consider-using-alias,consider-using-alias,consider-using-alias,consider-using-alias] + pass + +def func2(arg1: list[int]) -> tuple[int, int]: + pass + +class CustomIntList(typing.List[int]): + pass + +cast_variable = [1, 2, 3] +cast_variable = typing.cast(List[int], cast_variable) + +(lambda x: 2)(List[int]) + +class CustomNamedTuple(typing.NamedTuple): + my_var: List[int] # [consider-using-alias] + +CustomTypedDict1 = TypedDict("CustomTypedDict1", my_var=List[int]) + +class CustomTypedDict2(TypedDict): + my_var: List[int] # [consider-using-alias] + +@dataclass +class CustomDataClass: + my_var: List[int] # [consider-using-alias] diff --git a/tests/functional/t/typing/typing_consider_using_alias_without_future.rc b/tests/functional/t/typing/typing_consider_using_alias_without_future.rc new file mode 100644 index 000000000..e1f43ca61 --- /dev/null +++ b/tests/functional/t/typing/typing_consider_using_alias_without_future.rc @@ -0,0 +1,9 @@ +[master] +load-plugins=pylint.extensions.typing + +[testoptions] +min_pyver=3.8 + +[typing] +py-version=3.7 +runtime-typing=no diff --git a/tests/functional/t/typing/typing_consider_using_alias_without_future.txt b/tests/functional/t/typing/typing_consider_using_alias_without_future.txt new file mode 100644 index 000000000..c7105b965 --- /dev/null +++ b/tests/functional/t/typing/typing_consider_using_alias_without_future.txt @@ -0,0 +1,17 @@ +consider-using-alias:13:6::'typing.Dict' will be deprecated with PY39, consider using 'dict' instead. Add 'from __future__ import annotations' as well +consider-using-alias:14:6::'typing.List' will be deprecated with PY39, consider using 'list' instead. Add 'from __future__ import annotations' as well +consider-using-alias:16:6::'typing.OrderedDict' will be deprecated with PY39, consider using 'collections.OrderedDict' instead. Add 'from __future__ import annotations' as well +consider-using-alias:17:6::'typing.Awaitable' will be deprecated with PY39, consider using 'collections.abc.Awaitable' instead. Add 'from __future__ import annotations' as well +consider-using-alias:18:6::'typing.Iterable' will be deprecated with PY39, consider using 'collections.abc.Iterable' instead. Add 'from __future__ import annotations' as well +consider-using-alias:19:6::'typing.Hashable' will be deprecated with PY39, consider using 'collections.abc.Hashable' instead +consider-using-alias:20:6::'typing.ContextManager' will be deprecated with PY39, consider using 'contextlib.AbstractContextManager' instead. Add 'from __future__ import annotations' as well +consider-using-alias:21:6::'typing.Pattern' will be deprecated with PY39, consider using 're.Pattern' instead. Add 'from __future__ import annotations' as well +consider-using-alias:22:7::'typing.Match' will be deprecated with PY39, consider using 're.Match' instead. Add 'from __future__ import annotations' as well +consider-using-alias:31:9::'typing.List' will be deprecated with PY39, consider using 'list' instead +consider-using-alias:33:74:func1:'typing.Dict' will be deprecated with PY39, consider using 'dict' instead. Add 'from __future__ import annotations' as well +consider-using-alias:33:16:func1:'typing.List' will be deprecated with PY39, consider using 'list' instead. Add 'from __future__ import annotations' as well +consider-using-alias:33:37:func1:'typing.List' will be deprecated with PY39, consider using 'list' instead. Add 'from __future__ import annotations' as well +consider-using-alias:33:93:func1:'typing.Tuple' will be deprecated with PY39, consider using 'tuple' instead. Add 'from __future__ import annotations' as well +consider-using-alias:49:12:CustomNamedTuple:'typing.List' will be deprecated with PY39, consider using 'list' instead. Add 'from __future__ import annotations' as well +consider-using-alias:54:12:CustomTypedDict2:'typing.List' will be deprecated with PY39, consider using 'list' instead. Add 'from __future__ import annotations' as well +consider-using-alias:58:12:CustomDataClass:'typing.List' will be deprecated with PY39, consider using 'list' instead. Add 'from __future__ import annotations' as well diff --git a/tests/functional/t/typing/typing_consider_using_union.py b/tests/functional/t/typing/typing_consider_using_union.py new file mode 100644 index 000000000..2f27b40da --- /dev/null +++ b/tests/functional/t/typing/typing_consider_using_union.py @@ -0,0 +1,47 @@ +"""Test pylint.extension.typing - consider-alternative-union-syntax + +'py-version' needs to be set to >= '3.7' and 'runtime-typing=no'. +With 'from __future__ import annotations' present. +""" +# pylint: disable=missing-docstring,invalid-name,unused-argument,line-too-long +# pylint: disable=consider-using-alias +from __future__ import annotations +from dataclasses import dataclass +import typing +from typing import Dict, List, Optional, Union, TypedDict + +var1: Union[int, str] # [consider-alternative-union-syntax] +var2: List[Union[int, None]] # [consider-alternative-union-syntax] +var3: Dict[str, typing.Union[int, str]] # [consider-alternative-union-syntax] +var4: Optional[int] # [consider-alternative-union-syntax] + +Alias1 = Union[int, str] +Alias2 = List[Union[int, None]] +Alias3 = Dict[str, typing.Union[int, str]] +Alias4 = Optional[int] + +def func1( + arg1: Optional[int], # [consider-alternative-union-syntax] + **kwargs: Dict[str, Union[int, str]] # [consider-alternative-union-syntax] +) -> Union[str, None]: # [consider-alternative-union-syntax] + pass + +class Custom1(List[Union[str, int]]): + pass + +cast_variable = [1, 2, 3] +cast_variable = typing.cast(Union[List[int], None], cast_variable) + +(lambda x: 2)(Optional[int]) + +class CustomNamedTuple(typing.NamedTuple): + my_var: Union[int, str] # [consider-alternative-union-syntax] + +CustomTypedDict1 = TypedDict("CustomTypedDict1", my_var=Optional[int]) + +class CustomTypedDict2(TypedDict): + my_var: Dict[str, List[Union[str, int]]] # [consider-alternative-union-syntax] + +@dataclass +class CustomDataClass: + my_var: Optional[int] # [consider-alternative-union-syntax] diff --git a/tests/functional/t/typing/typing_consider_using_union.rc b/tests/functional/t/typing/typing_consider_using_union.rc new file mode 100644 index 000000000..e1f43ca61 --- /dev/null +++ b/tests/functional/t/typing/typing_consider_using_union.rc @@ -0,0 +1,9 @@ +[master] +load-plugins=pylint.extensions.typing + +[testoptions] +min_pyver=3.8 + +[typing] +py-version=3.7 +runtime-typing=no diff --git a/tests/functional/t/typing/typing_consider_using_union.txt b/tests/functional/t/typing/typing_consider_using_union.txt new file mode 100644 index 000000000..330a7165f --- /dev/null +++ b/tests/functional/t/typing/typing_consider_using_union.txt @@ -0,0 +1,10 @@ +consider-alternative-union-syntax:13:6::Consider using alternative Union syntax instead of 'Union' +consider-alternative-union-syntax:14:11::Consider using alternative Union syntax instead of 'Union' +consider-alternative-union-syntax:15:16::Consider using alternative Union syntax instead of 'Union' +consider-alternative-union-syntax:16:6::Consider using alternative Union syntax instead of 'Optional' +consider-alternative-union-syntax:24:10:func1:Consider using alternative Union syntax instead of 'Optional' +consider-alternative-union-syntax:25:24:func1:Consider using alternative Union syntax instead of 'Union' +consider-alternative-union-syntax:26:5:func1:Consider using alternative Union syntax instead of 'Union' +consider-alternative-union-syntax:38:12:CustomNamedTuple:Consider using alternative Union syntax instead of 'Union' +consider-alternative-union-syntax:43:27:CustomTypedDict2:Consider using alternative Union syntax instead of 'Union' +consider-alternative-union-syntax:47:12:CustomDataClass:Consider using alternative Union syntax instead of 'Optional' diff --git a/tests/functional/t/typing/typing_consider_using_union_py310.py b/tests/functional/t/typing/typing_consider_using_union_py310.py new file mode 100644 index 000000000..1018c3670 --- /dev/null +++ b/tests/functional/t/typing/typing_consider_using_union_py310.py @@ -0,0 +1,45 @@ +"""Test pylint.extension.typing - consider-alternative-union-syntax + +'py-version' needs to be set to >= '3.10'. +""" +# pylint: disable=missing-docstring,invalid-name,unused-argument,line-too-long +# pylint: disable=deprecated-typing-alias +from dataclasses import dataclass +import typing +from typing import Dict, List, Optional, Union, TypedDict + +var1: Union[int, str] # [consider-alternative-union-syntax] +var2: List[Union[int, None]] # [consider-alternative-union-syntax] +var3: Dict[str, typing.Union[int, str]] # [consider-alternative-union-syntax] +var4: Optional[int] # [consider-alternative-union-syntax] + +Alias1 = Union[int, str] # [consider-alternative-union-syntax] +Alias2 = List[Union[int, None]] # [consider-alternative-union-syntax] +Alias3 = Dict[str, typing.Union[int, str]] # [consider-alternative-union-syntax] +Alias4 = Optional[int] # [consider-alternative-union-syntax] + +def func1( + arg1: Optional[int], # [consider-alternative-union-syntax] + **kwargs: Dict[str, Union[int, str]] # [consider-alternative-union-syntax] +) -> Union[str, None]: # [consider-alternative-union-syntax] + pass + +class Custom1(List[Union[str, int]]): # [consider-alternative-union-syntax] + pass + +cast_variable = [1, 2, 3] +cast_variable = typing.cast(Union[List[int], None], cast_variable) # [consider-alternative-union-syntax] + +(lambda x: 2)(Optional[int]) # [consider-alternative-union-syntax] + +class CustomNamedTuple(typing.NamedTuple): + my_var: Union[int, str] # [consider-alternative-union-syntax] + +CustomTypedDict1 = TypedDict("CustomTypedDict1", my_var=Optional[int]) # [consider-alternative-union-syntax] + +class CustomTypedDict2(TypedDict): + my_var: Dict[str, List[Union[str, int]]] # [consider-alternative-union-syntax] + +@dataclass +class CustomDataClass: + my_var: Optional[int] # [consider-alternative-union-syntax] diff --git a/tests/functional/t/typing/typing_consider_using_union_py310.rc b/tests/functional/t/typing/typing_consider_using_union_py310.rc new file mode 100644 index 000000000..7400f16d6 --- /dev/null +++ b/tests/functional/t/typing/typing_consider_using_union_py310.rc @@ -0,0 +1,8 @@ +[master] +load-plugins=pylint.extensions.typing + +[testoptions] +min_pyver=3.8 + +[typing] +py-version=3.10 diff --git a/tests/functional/t/typing/typing_consider_using_union_py310.txt b/tests/functional/t/typing/typing_consider_using_union_py310.txt new file mode 100644 index 000000000..4402eb218 --- /dev/null +++ b/tests/functional/t/typing/typing_consider_using_union_py310.txt @@ -0,0 +1,18 @@ +consider-alternative-union-syntax:11:6::Consider using alternative Union syntax instead of 'Union' +consider-alternative-union-syntax:12:11::Consider using alternative Union syntax instead of 'Union' +consider-alternative-union-syntax:13:16::Consider using alternative Union syntax instead of 'Union' +consider-alternative-union-syntax:14:6::Consider using alternative Union syntax instead of 'Optional' +consider-alternative-union-syntax:16:9::Consider using alternative Union syntax instead of 'Union' +consider-alternative-union-syntax:17:14::Consider using alternative Union syntax instead of 'Union' +consider-alternative-union-syntax:18:19::Consider using alternative Union syntax instead of 'Union' +consider-alternative-union-syntax:19:9::Consider using alternative Union syntax instead of 'Optional' +consider-alternative-union-syntax:22:10:func1:Consider using alternative Union syntax instead of 'Optional' +consider-alternative-union-syntax:23:24:func1:Consider using alternative Union syntax instead of 'Union' +consider-alternative-union-syntax:24:5:func1:Consider using alternative Union syntax instead of 'Union' +consider-alternative-union-syntax:27:19:Custom1:Consider using alternative Union syntax instead of 'Union' +consider-alternative-union-syntax:31:28::Consider using alternative Union syntax instead of 'Union' +consider-alternative-union-syntax:33:14::Consider using alternative Union syntax instead of 'Optional' +consider-alternative-union-syntax:36:12:CustomNamedTuple:Consider using alternative Union syntax instead of 'Union' +consider-alternative-union-syntax:38:56::Consider using alternative Union syntax instead of 'Optional' +consider-alternative-union-syntax:41:27:CustomTypedDict2:Consider using alternative Union syntax instead of 'Union' +consider-alternative-union-syntax:45:12:CustomDataClass:Consider using alternative Union syntax instead of 'Optional' diff --git a/tests/functional/t/typing/typing_consider_using_union_without_future.py b/tests/functional/t/typing/typing_consider_using_union_without_future.py new file mode 100644 index 000000000..f459d047b --- /dev/null +++ b/tests/functional/t/typing/typing_consider_using_union_without_future.py @@ -0,0 +1,45 @@ +"""Test pylint.extension.typing - consider-alternative-union-syntax + +'py-version' needs to be set to >= '3.7' and 'runtime-typing=no'. +""" +# pylint: disable=missing-docstring,invalid-name,unused-argument,line-too-long +# pylint: disable=consider-using-alias +from dataclasses import dataclass +import typing +from typing import Dict, List, Optional, Union, TypedDict + +var1: Union[int, str] # [consider-alternative-union-syntax] +var2: List[Union[int, None]] # [consider-alternative-union-syntax] +var3: Dict[str, typing.Union[int, str]] # [consider-alternative-union-syntax] +var4: Optional[int] # [consider-alternative-union-syntax] + +Alias1 = Union[int, str] +Alias2 = List[Union[int, None]] +Alias3 = Dict[str, typing.Union[int, str]] +Alias4 = Optional[int] + +def func1( + arg1: Optional[int], # [consider-alternative-union-syntax] + **kwargs: Dict[str, Union[int, str]] # [consider-alternative-union-syntax] +) -> Union[str, None]: # [consider-alternative-union-syntax] + pass + +class Custom1(List[Union[str, int]]): + pass + +cast_variable = [1, 2, 3] +cast_variable = typing.cast(Union[List[int], None], cast_variable) + +(lambda x: 2)(Optional[int]) + +class CustomNamedTuple(typing.NamedTuple): + my_var: Union[int, str] # [consider-alternative-union-syntax] + +CustomTypedDict1 = TypedDict("CustomTypedDict1", my_var=Optional[int]) + +class CustomTypedDict2(TypedDict): + my_var: Dict[str, List[Union[str, int]]] # [consider-alternative-union-syntax] + +@dataclass +class CustomDataClass: + my_var: Optional[int] # [consider-alternative-union-syntax] diff --git a/tests/functional/t/typing/typing_consider_using_union_without_future.rc b/tests/functional/t/typing/typing_consider_using_union_without_future.rc new file mode 100644 index 000000000..e1f43ca61 --- /dev/null +++ b/tests/functional/t/typing/typing_consider_using_union_without_future.rc @@ -0,0 +1,9 @@ +[master] +load-plugins=pylint.extensions.typing + +[testoptions] +min_pyver=3.8 + +[typing] +py-version=3.7 +runtime-typing=no diff --git a/tests/functional/t/typing/typing_consider_using_union_without_future.txt b/tests/functional/t/typing/typing_consider_using_union_without_future.txt new file mode 100644 index 000000000..c43cbc3ce --- /dev/null +++ b/tests/functional/t/typing/typing_consider_using_union_without_future.txt @@ -0,0 +1,10 @@ +consider-alternative-union-syntax:11:6::Consider using alternative Union syntax instead of 'Union'. Add 'from __future__ import annotations' as well +consider-alternative-union-syntax:12:11::Consider using alternative Union syntax instead of 'Union'. Add 'from __future__ import annotations' as well +consider-alternative-union-syntax:13:16::Consider using alternative Union syntax instead of 'Union'. Add 'from __future__ import annotations' as well +consider-alternative-union-syntax:14:6::Consider using alternative Union syntax instead of 'Optional'. Add 'from __future__ import annotations' as well +consider-alternative-union-syntax:22:10:func1:Consider using alternative Union syntax instead of 'Optional'. Add 'from __future__ import annotations' as well +consider-alternative-union-syntax:23:24:func1:Consider using alternative Union syntax instead of 'Union'. Add 'from __future__ import annotations' as well +consider-alternative-union-syntax:24:5:func1:Consider using alternative Union syntax instead of 'Union'. Add 'from __future__ import annotations' as well +consider-alternative-union-syntax:36:12:CustomNamedTuple:Consider using alternative Union syntax instead of 'Union'. Add 'from __future__ import annotations' as well +consider-alternative-union-syntax:41:27:CustomTypedDict2:Consider using alternative Union syntax instead of 'Union'. Add 'from __future__ import annotations' as well +consider-alternative-union-syntax:45:12:CustomDataClass:Consider using alternative Union syntax instead of 'Optional'. Add 'from __future__ import annotations' as well diff --git a/tests/functional/t/typing/typing_deprecated_alias.py b/tests/functional/t/typing/typing_deprecated_alias.py new file mode 100644 index 000000000..3cd139cdb --- /dev/null +++ b/tests/functional/t/typing/typing_deprecated_alias.py @@ -0,0 +1,58 @@ +"""Test pylint.extension.typing - deprecated-typing-alias + +'py-version' needs to be set to >= '3.9'. +""" +# pylint: disable=missing-docstring,invalid-name,unused-argument,line-too-long,unsubscriptable-object +import collections +import collections.abc +import typing +from collections.abc import Awaitable +from dataclasses import dataclass +from typing import Dict, List, Set, Union, TypedDict + +var1: typing.Dict[str, int] # [deprecated-typing-alias] +var2: List[int] # [deprecated-typing-alias] +var3: collections.abc.Iterable[int] +var4: typing.OrderedDict[str, int] # [deprecated-typing-alias] +var5: typing.Awaitable[None] # [deprecated-typing-alias] +var6: typing.Iterable[int] # [deprecated-typing-alias] +var7: typing.Hashable # [deprecated-typing-alias] +var8: typing.ContextManager[str] # [deprecated-typing-alias] +var9: typing.Pattern[str] # [deprecated-typing-alias] +var10: typing.re.Match[str] # [deprecated-typing-alias] +var11: list[int] +var12: collections.abc +var13: Awaitable[None] +var14: collections.defaultdict[str, str] + +Alias1 = Set[int] # [deprecated-typing-alias] +Alias2 = Dict[int, List[int]] # [deprecated-typing-alias,deprecated-typing-alias] +Alias3 = Union[int, typing.List[str]] # [deprecated-typing-alias] +Alias4 = List # [deprecated-typing-alias] + +def func1(arg1: List[int], /, *args: List[int], arg2: set[int], **kwargs: Dict[str, int]) -> typing.Tuple[int]: + # -1:[deprecated-typing-alias,deprecated-typing-alias,deprecated-typing-alias,deprecated-typing-alias] + pass + +def func2(arg1: list[int]) -> tuple[int, int]: + pass + +class CustomIntList(typing.List[int]): # [deprecated-typing-alias] + pass + +cast_variable = [1, 2, 3] +cast_variable = typing.cast(List[int], cast_variable) # [deprecated-typing-alias] + +(lambda x: 2)(List[int]) # [deprecated-typing-alias] + +class CustomNamedTuple(typing.NamedTuple): + my_var: List[int] # [deprecated-typing-alias] + +CustomTypedDict1 = TypedDict("CustomTypedDict1", my_var=List[int]) # [deprecated-typing-alias] + +class CustomTypedDict2(TypedDict): + my_var: List[int] # [deprecated-typing-alias] + +@dataclass +class CustomDataClass: + my_var: List[int] # [deprecated-typing-alias] diff --git a/tests/functional/t/typing/typing_deprecated_alias.rc b/tests/functional/t/typing/typing_deprecated_alias.rc new file mode 100644 index 000000000..2d21d503c --- /dev/null +++ b/tests/functional/t/typing/typing_deprecated_alias.rc @@ -0,0 +1,8 @@ +[master] +load-plugins=pylint.extensions.typing + +[testoptions] +min_pyver=3.8 + +[typing] +py-version=3.9 diff --git a/tests/functional/t/typing/typing_deprecated_alias.txt b/tests/functional/t/typing/typing_deprecated_alias.txt new file mode 100644 index 000000000..fa74fc161 --- /dev/null +++ b/tests/functional/t/typing/typing_deprecated_alias.txt @@ -0,0 +1,25 @@ +deprecated-typing-alias:13:6::'typing.Dict' is deprecated, use 'dict' instead +deprecated-typing-alias:14:6::'typing.List' is deprecated, use 'list' instead +deprecated-typing-alias:16:6::'typing.OrderedDict' is deprecated, use 'collections.OrderedDict' instead +deprecated-typing-alias:17:6::'typing.Awaitable' is deprecated, use 'collections.abc.Awaitable' instead +deprecated-typing-alias:18:6::'typing.Iterable' is deprecated, use 'collections.abc.Iterable' instead +deprecated-typing-alias:19:6::'typing.Hashable' is deprecated, use 'collections.abc.Hashable' instead +deprecated-typing-alias:20:6::'typing.ContextManager' is deprecated, use 'contextlib.AbstractContextManager' instead +deprecated-typing-alias:21:6::'typing.Pattern' is deprecated, use 're.Pattern' instead +deprecated-typing-alias:22:7::'typing.Match' is deprecated, use 're.Match' instead +deprecated-typing-alias:28:9::'typing.Set' is deprecated, use 'set' instead +deprecated-typing-alias:29:9::'typing.Dict' is deprecated, use 'dict' instead +deprecated-typing-alias:29:19::'typing.List' is deprecated, use 'list' instead +deprecated-typing-alias:30:20::'typing.List' is deprecated, use 'list' instead +deprecated-typing-alias:31:9::'typing.List' is deprecated, use 'list' instead +deprecated-typing-alias:33:74:func1:'typing.Dict' is deprecated, use 'dict' instead +deprecated-typing-alias:33:16:func1:'typing.List' is deprecated, use 'list' instead +deprecated-typing-alias:33:37:func1:'typing.List' is deprecated, use 'list' instead +deprecated-typing-alias:33:93:func1:'typing.Tuple' is deprecated, use 'tuple' instead +deprecated-typing-alias:40:20:CustomIntList:'typing.List' is deprecated, use 'list' instead +deprecated-typing-alias:44:28::'typing.List' is deprecated, use 'list' instead +deprecated-typing-alias:46:14::'typing.List' is deprecated, use 'list' instead +deprecated-typing-alias:49:12:CustomNamedTuple:'typing.List' is deprecated, use 'list' instead +deprecated-typing-alias:51:56::'typing.List' is deprecated, use 'list' instead +deprecated-typing-alias:54:12:CustomTypedDict2:'typing.List' is deprecated, use 'list' instead +deprecated-typing-alias:58:12:CustomDataClass:'typing.List' is deprecated, use 'list' instead |