diff options
| author | Marc Mueller <30130371+cdce8p@users.noreply.github.com> | 2021-06-10 13:04:17 +0200 |
|---|---|---|
| committer | Pierre Sassoulas <pierre.sassoulas@gmail.com> | 2021-06-12 08:38:34 +0200 |
| commit | 2cae129a9cd425c8990274e3f7deb0e8cc5a6ed9 (patch) | |
| tree | aed4b72e26178f6d34cc0574769981a22aa569d9 | |
| parent | 49a6206c7756307844c1c32c256afdf9836d7bce (diff) | |
| download | pylint-git-2cae129a9cd425c8990274e3f7deb0e8cc5a6ed9.tar.gz | |
Move consider-using-namedtuple-or-dataclass to CodeStyle extension
| -rw-r--r-- | ChangeLog | 3 | ||||
| -rw-r--r-- | doc/whatsnew/2.9.rst | 3 | ||||
| -rw-r--r-- | pylint/checkers/refactoring/refactoring_checker.py | 83 | ||||
| -rw-r--r-- | pylint/extensions/code_style.py | 85 | ||||
| -rw-r--r-- | tests/functional/ext/code_style/code_style_consider_using_namedtuple_or_dataclass.py (renamed from tests/functional/c/consider/consider_using_namedtuple_or_dataclass.py) | 0 | ||||
| -rw-r--r-- | tests/functional/ext/code_style/code_style_consider_using_namedtuple_or_dataclass.rc | 2 | ||||
| -rw-r--r-- | tests/functional/ext/code_style/code_style_consider_using_namedtuple_or_dataclass.txt (renamed from tests/functional/c/consider/consider_using_namedtuple_or_dataclass.txt) | 0 |
7 files changed, 86 insertions, 90 deletions
@@ -124,9 +124,6 @@ modules are added. Closes #4509 Closes PyCQA/astroid#999 -* New checker ``consider-using-namedtuple-or-dataclass``. Emitted when dictionary values - can be replaced by namedtuples or dataclass instances. - * New checker ``invalid-all-format``. Emitted when ``__all__`` has an invalid format, i.e. isn't a ``tuple`` or ``list``. diff --git a/doc/whatsnew/2.9.rst b/doc/whatsnew/2.9.rst index d267d8ba3..212a58bb5 100644 --- a/doc/whatsnew/2.9.rst +++ b/doc/whatsnew/2.9.rst @@ -31,9 +31,6 @@ New checkers * New checker ``invalid-class-object``: Emitted when a non-class is assigned to a ``__class__`` attribute. -* ``consider-using-namedtuple-or-dataclass``: Emitted when dictionary values - can be replaced by namedtuples or dataclass instances. - * ``invalid-all-format``: Emitted when ``__all__`` has an invalid format, i.e. isn't a ``tuple`` or ``list``. diff --git a/pylint/checkers/refactoring/refactoring_checker.py b/pylint/checkers/refactoring/refactoring_checker.py index 7a57584ab..5fb0f3172 100644 --- a/pylint/checkers/refactoring/refactoring_checker.py +++ b/pylint/checkers/refactoring/refactoring_checker.py @@ -6,15 +6,14 @@ import copy import itertools import tokenize from functools import reduce -from typing import List, Optional, Set, Tuple, Type, Union, cast +from typing import List, Optional, Tuple, Union, cast import astroid -from astroid.node_classes import NodeNG from pylint import checkers, interfaces from pylint import utils as lint_utils from pylint.checkers import utils -from pylint.checkers.utils import node_frame_class, safe_infer +from pylint.checkers.utils import node_frame_class KNOWN_INFINITE_ITERATORS = {"itertools.count"} BUILTIN_EXIT_FUNCS = frozenset(("quit", "exit")) @@ -355,11 +354,6 @@ class RefactoringChecker(checkers.BaseTokenChecker): "value by index lookup. " "The value can be accessed directly instead.", ), - "R1734": ( - "Consider using namedtuple or dataclass for dictionary values", - "consider-using-namedtuple-or-dataclass", - "Emitted when dictionary values can be replaced by namedtuples or dataclass instances.", - ), } options = ( ( @@ -1762,76 +1756,3 @@ class RefactoringChecker(checkers.BaseTokenChecker): node=subscript, args=("1".join(value.as_string().rsplit("0", maxsplit=1)),), ) - - @utils.check_messages("consider-using-namedtuple-or-dataclass") - def visit_dict(self, node: astroid.Dict) -> None: - self._check_dict_consider_namedtuple(node) - - def _check_dict_consider_namedtuple(self, node: astroid.Dict) -> None: - """Check if dictionary values can be replaced by Namedtuple.""" - if not ( - isinstance(node.parent, (astroid.Assign, astroid.AnnAssign)) - and isinstance(node.parent.parent, astroid.Module) - or isinstance(node.parent, astroid.AnnAssign) - and utils.is_assign_name_annotated_with(node.parent.target, "Final") - ): - # If dict is not part of an 'Assign' or 'AnnAssign' node in - # a module context OR 'AnnAssign' with 'Final' annotation, skip check. - return - - # All dict_values are itself dict nodes - if len(node.items) > 1 and all( - isinstance(dict_value, astroid.Dict) for _, dict_value in node.items - ): - KeyTupleT = Tuple[Type[NodeNG], str] - - # Makes sure all keys are 'Const' string nodes - keys_checked: Set[KeyTupleT] = set() - for _, dict_value in node.items: - for key, _ in dict_value.items: - key_tuple = (type(key), key.as_string()) - if key_tuple in keys_checked: - continue - inferred = safe_infer(key) - if not ( - isinstance(inferred, astroid.Const) - and inferred.pytype() == "builtins.str" - ): - return - keys_checked.add(key_tuple) - - # Makes sure all subdicts have at least 1 common key - key_tuples: List[Tuple[KeyTupleT, ...]] = [] - for _, dict_value in node.items: - key_tuples.append( - tuple((type(key), key.as_string()) for key, _ in dict_value.items) - ) - keys_intersection: Set[KeyTupleT] = set(key_tuples[0]) - for sub_key_tuples in key_tuples[1:]: - keys_intersection.intersection_update(sub_key_tuples) - if not keys_intersection: - return - - self.add_message("consider-using-namedtuple-or-dataclass", node=node) - return - - # All dict_values are itself either list or tuple nodes - if len(node.items) > 1 and all( - isinstance(dict_value, (astroid.List, astroid.Tuple)) - for _, dict_value in node.items - ): - # Make sure all sublists have the same length > 0 - list_length = len(node.items[0][1].elts) - if list_length == 0: - return - for _, dict_value in node.items[1:]: - if len(dict_value.elts) != list_length: - return - - # Make sure at least one list entry isn't a dict - for _, dict_value in node.items: - if all(isinstance(entry, astroid.Dict) for entry in dict_value.elts): - return - - self.add_message("consider-using-namedtuple-or-dataclass", node=node) - return diff --git a/pylint/extensions/code_style.py b/pylint/extensions/code_style.py index e670b41ed..72bf494b5 100644 --- a/pylint/extensions/code_style.py +++ b/pylint/extensions/code_style.py @@ -1,9 +1,10 @@ -from typing import Union +from typing import List, Set, Tuple, Type, Union import astroid +from astroid.node_classes import NodeNG -from pylint.checkers import BaseChecker -from pylint.checkers.utils import check_messages +from pylint.checkers import BaseChecker, utils +from pylint.checkers.utils import check_messages, safe_infer from pylint.interfaces import IAstroidChecker from pylint.lint import PyLinter @@ -20,6 +21,11 @@ class CodeStyleChecker(BaseChecker): name = "code_style" priority = -1 msgs = { + "R6101": ( + "Consider using namedtuple or dataclass for dictionary values", + "consider-using-namedtuple-or-dataclass", + "Emitted when dictionary values can be replaced by namedtuples or dataclass instances.", + ), "R6102": ( "Consider using an in-place tuple%s", "consider-using-tuple", @@ -32,6 +38,10 @@ class CodeStyleChecker(BaseChecker): """Initialize checker instance.""" super().__init__(linter=linter) + @check_messages("consider-using-namedtuple-or-dataclass") + def visit_dict(self, node: astroid.Dict) -> None: + self._check_dict_consider_namedtuple_dataclass(node) + @check_messages("consider-using-tuple") def visit_for(self, node: astroid.For) -> None: self._check_inplace_defined_list_set(node) @@ -40,6 +50,75 @@ class CodeStyleChecker(BaseChecker): def visit_comprehension(self, node: astroid.Comprehension) -> None: self._check_inplace_defined_list_set(node) + def _check_dict_consider_namedtuple_dataclass(self, node: astroid.Dict) -> None: + """Check if dictionary values can be replaced by Namedtuple or Dataclass.""" + if not ( + isinstance(node.parent, (astroid.Assign, astroid.AnnAssign)) + and isinstance(node.parent.parent, astroid.Module) + or isinstance(node.parent, astroid.AnnAssign) + and utils.is_assign_name_annotated_with(node.parent.target, "Final") + ): + # If dict is not part of an 'Assign' or 'AnnAssign' node in + # a module context OR 'AnnAssign' with 'Final' annotation, skip check. + return + + # All dict_values are itself dict nodes + if len(node.items) > 1 and all( + isinstance(dict_value, astroid.Dict) for _, dict_value in node.items + ): + KeyTupleT = Tuple[Type[NodeNG], str] + + # Makes sure all keys are 'Const' string nodes + keys_checked: Set[KeyTupleT] = set() + for _, dict_value in node.items: + for key, _ in dict_value.items: + key_tuple = (type(key), key.as_string()) + if key_tuple in keys_checked: + continue + inferred = safe_infer(key) + if not ( + isinstance(inferred, astroid.Const) + and inferred.pytype() == "builtins.str" + ): + return + keys_checked.add(key_tuple) + + # Makes sure all subdicts have at least 1 common key + key_tuples: List[Tuple[KeyTupleT, ...]] = [] + for _, dict_value in node.items: + key_tuples.append( + tuple((type(key), key.as_string()) for key, _ in dict_value.items) + ) + keys_intersection: Set[KeyTupleT] = set(key_tuples[0]) + for sub_key_tuples in key_tuples[1:]: + keys_intersection.intersection_update(sub_key_tuples) + if not keys_intersection: + return + + self.add_message("consider-using-namedtuple-or-dataclass", node=node) + return + + # All dict_values are itself either list or tuple nodes + if len(node.items) > 1 and all( + isinstance(dict_value, (astroid.List, astroid.Tuple)) + for _, dict_value in node.items + ): + # Make sure all sublists have the same length > 0 + list_length = len(node.items[0][1].elts) + if list_length == 0: + return + for _, dict_value in node.items[1:]: + if len(dict_value.elts) != list_length: + return + + # Make sure at least one list entry isn't a dict + for _, dict_value in node.items: + if all(isinstance(entry, astroid.Dict) for entry in dict_value.elts): + return + + self.add_message("consider-using-namedtuple-or-dataclass", node=node) + return + def _check_inplace_defined_list_set( self, node: Union[astroid.For, astroid.Comprehension] ) -> None: diff --git a/tests/functional/c/consider/consider_using_namedtuple_or_dataclass.py b/tests/functional/ext/code_style/code_style_consider_using_namedtuple_or_dataclass.py index 627de7684..627de7684 100644 --- a/tests/functional/c/consider/consider_using_namedtuple_or_dataclass.py +++ b/tests/functional/ext/code_style/code_style_consider_using_namedtuple_or_dataclass.py diff --git a/tests/functional/ext/code_style/code_style_consider_using_namedtuple_or_dataclass.rc b/tests/functional/ext/code_style/code_style_consider_using_namedtuple_or_dataclass.rc new file mode 100644 index 000000000..47767a206 --- /dev/null +++ b/tests/functional/ext/code_style/code_style_consider_using_namedtuple_or_dataclass.rc @@ -0,0 +1,2 @@ +[MASTER] +load-plugins=pylint.extensions.code_style diff --git a/tests/functional/c/consider/consider_using_namedtuple_or_dataclass.txt b/tests/functional/ext/code_style/code_style_consider_using_namedtuple_or_dataclass.txt index 6c471db96..6c471db96 100644 --- a/tests/functional/c/consider/consider_using_namedtuple_or_dataclass.txt +++ b/tests/functional/ext/code_style/code_style_consider_using_namedtuple_or_dataclass.txt |
