summaryrefslogtreecommitdiff
diff options
context:
space:
mode:
authorMarc Mueller <30130371+cdce8p@users.noreply.github.com>2021-06-10 13:04:17 +0200
committerPierre Sassoulas <pierre.sassoulas@gmail.com>2021-06-12 08:38:34 +0200
commit2cae129a9cd425c8990274e3f7deb0e8cc5a6ed9 (patch)
treeaed4b72e26178f6d34cc0574769981a22aa569d9
parent49a6206c7756307844c1c32c256afdf9836d7bce (diff)
downloadpylint-git-2cae129a9cd425c8990274e3f7deb0e8cc5a6ed9.tar.gz
Move consider-using-namedtuple-or-dataclass to CodeStyle extension
-rw-r--r--ChangeLog3
-rw-r--r--doc/whatsnew/2.9.rst3
-rw-r--r--pylint/checkers/refactoring/refactoring_checker.py83
-rw-r--r--pylint/extensions/code_style.py85
-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.rc2
-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
diff --git a/ChangeLog b/ChangeLog
index 5ae496476..b4da74564 100644
--- a/ChangeLog
+++ b/ChangeLog
@@ -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