| 123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129 |
- # Licensed under the GPL: https://www.gnu.org/licenses/old-licenses/gpl-2.0.html
- # For details: https://github.com/pylint-dev/pylint/blob/main/LICENSE
- # Copyright (c) https://github.com/pylint-dev/pylint/blob/main/CONTRIBUTORS.txt
- """Dataclass checkers for Python code."""
- from __future__ import annotations
- from typing import TYPE_CHECKING
- from astroid import nodes
- from astroid.brain.brain_dataclasses import DATACLASS_MODULES
- from pylint.checkers import BaseChecker, utils
- from pylint.interfaces import INFERENCE
- if TYPE_CHECKING:
- from pylint.lint import PyLinter
- def _is_dataclasses_module(node: nodes.Module) -> bool:
- """Utility function to check if node is from dataclasses_module."""
- return node.name in DATACLASS_MODULES
- def _check_name_or_attrname_eq_to(
- node: nodes.Name | nodes.Attribute, check_with: str
- ) -> bool:
- """Utility function to check either a Name/Attribute node's name/attrname with a
- given string.
- """
- if isinstance(node, nodes.Name):
- return str(node.name) == check_with
- return str(node.attrname) == check_with
- class DataclassChecker(BaseChecker):
- """Checker that detects invalid or problematic usage in dataclasses.
- Checks for
- * invalid-field-call
- """
- name = "dataclass"
- msgs = {
- "E3701": (
- "Invalid usage of field(), %s",
- "invalid-field-call",
- "The dataclasses.field() specifier should only be used as the value of "
- "an assignment within a dataclass, or within the make_dataclass() function.",
- ),
- }
- @utils.only_required_for_messages("invalid-field-call")
- def visit_call(self, node: nodes.Call) -> None:
- self._check_invalid_field_call(node)
- def _check_invalid_field_call(self, node: nodes.Call) -> None:
- """Checks for correct usage of the dataclasses.field() specifier in
- dataclasses or within the make_dataclass() function.
- Emits message
- when field() is detected to be used outside a class decorated with
- @dataclass decorator and outside make_dataclass() function, or when it
- is used improperly within a dataclass.
- """
- if not isinstance(node.func, (nodes.Name, nodes.Attribute)):
- return
- if not _check_name_or_attrname_eq_to(node.func, "field"):
- return
- inferred_func = utils.safe_infer(node.func)
- if not (
- isinstance(inferred_func, nodes.FunctionDef)
- and _is_dataclasses_module(inferred_func.root())
- ):
- return
- scope_node = node.parent
- while scope_node and not isinstance(scope_node, (nodes.ClassDef, nodes.Call)):
- scope_node = scope_node.parent
- if isinstance(scope_node, nodes.Call):
- self._check_invalid_field_call_within_call(node, scope_node)
- return
- if not (scope_node and scope_node.is_dataclass):
- self.add_message(
- "invalid-field-call",
- node=node,
- args=(
- "it should be used within a dataclass or the make_dataclass() function.",
- ),
- confidence=INFERENCE,
- )
- return
- if not (isinstance(node.parent, nodes.AnnAssign) and node == node.parent.value):
- self.add_message(
- "invalid-field-call",
- node=node,
- args=("it should be the value of an assignment within a dataclass.",),
- confidence=INFERENCE,
- )
- def _check_invalid_field_call_within_call(
- self, node: nodes.Call, scope_node: nodes.Call
- ) -> None:
- """Checks for special case where calling field is valid as an argument of the
- make_dataclass() function.
- """
- inferred_func = utils.safe_infer(scope_node.func)
- if (
- isinstance(scope_node.func, (nodes.Name, nodes.AssignName))
- and scope_node.func.name == "make_dataclass"
- and isinstance(inferred_func, nodes.FunctionDef)
- and _is_dataclasses_module(inferred_func.root())
- ):
- return
- self.add_message(
- "invalid-field-call",
- node=node,
- args=(
- "it should be used within a dataclass or the make_dataclass() function.",
- ),
- confidence=INFERENCE,
- )
- def register(linter: PyLinter) -> None:
- linter.register_checker(DataclassChecker(linter))
|