Ensure hass is typed (#87068)
* Ensure hass is typed * Adjust pilight * Adjust homeassistant scene * Adjust hassio * Adjust gree * Adjust google_maps * Adjust energyzero * Adjust harmony * Adjust mobile_app
This commit is contained in:
@@ -18,6 +18,11 @@ if TYPE_CHECKING:
|
||||
# pre-commit should still work on out of date environments
|
||||
from astroid.typing import InferenceResult
|
||||
|
||||
_COMMON_ARGUMENTS: dict[str, list[str]] = {
|
||||
"hass": ["HomeAssistant", "HomeAssistant | None"]
|
||||
}
|
||||
_PLATFORMS: set[str] = {platform.value for platform in Platform}
|
||||
|
||||
|
||||
class _Special(Enum):
|
||||
"""Sentinel values."""
|
||||
@@ -25,9 +30,6 @@ class _Special(Enum):
|
||||
UNDEFINED = 1
|
||||
|
||||
|
||||
_PLATFORMS: set[str] = {platform.value for platform in Platform}
|
||||
|
||||
|
||||
@dataclass
|
||||
class TypeHintMatch:
|
||||
"""Class for pattern matching."""
|
||||
@@ -2911,6 +2913,16 @@ class HassTypeHintChecker(BaseChecker): # type: ignore[misc]
|
||||
|
||||
self._class_matchers.reverse()
|
||||
|
||||
def _ignore_function(
|
||||
self, node: nodes.FunctionDef, annotations: list[nodes.NodeNG | None]
|
||||
) -> bool:
|
||||
"""Check if we can skip the function validation."""
|
||||
return (
|
||||
self.linter.config.ignore_missing_annotations
|
||||
and node.returns is None
|
||||
and not _has_valid_annotations(annotations)
|
||||
)
|
||||
|
||||
def visit_classdef(self, node: nodes.ClassDef) -> None:
|
||||
"""Apply relevant type hint checks on a ClassDef node."""
|
||||
ancestor: nodes.ClassDef
|
||||
@@ -2932,34 +2944,55 @@ class HassTypeHintChecker(BaseChecker): # type: ignore[misc]
|
||||
cached_methods: list[nodes.FunctionDef] = list(node.mymethods())
|
||||
for match in matches:
|
||||
for function_node in cached_methods:
|
||||
if function_node.name in checked_class_methods:
|
||||
if (
|
||||
function_node.name in checked_class_methods
|
||||
or not match.need_to_check_function(function_node)
|
||||
):
|
||||
continue
|
||||
if match.need_to_check_function(function_node):
|
||||
self._check_function(function_node, match)
|
||||
checked_class_methods.add(function_node.name)
|
||||
|
||||
annotations = _get_all_annotations(function_node)
|
||||
if self._ignore_function(function_node, annotations):
|
||||
continue
|
||||
|
||||
self._check_function(function_node, match, annotations)
|
||||
checked_class_methods.add(function_node.name)
|
||||
|
||||
def visit_functiondef(self, node: nodes.FunctionDef) -> None:
|
||||
"""Apply relevant type hint checks on a FunctionDef node."""
|
||||
annotations = _get_all_annotations(node)
|
||||
if self._ignore_function(node, annotations):
|
||||
return
|
||||
|
||||
# Check that common arguments are correctly typed.
|
||||
for arg_name, expected_type in _COMMON_ARGUMENTS.items():
|
||||
arg_node, annotation = _get_named_annotation(node, arg_name)
|
||||
if arg_node and not _is_valid_type(expected_type, annotation):
|
||||
self.add_message(
|
||||
"hass-argument-type",
|
||||
node=arg_node,
|
||||
args=(arg_name, expected_type, node.name),
|
||||
)
|
||||
|
||||
# Check function matchers.
|
||||
for match in self._function_matchers:
|
||||
if not match.need_to_check_function(node) or node.is_method():
|
||||
continue
|
||||
self._check_function(node, match)
|
||||
self._check_function(node, match, annotations)
|
||||
|
||||
visit_asyncfunctiondef = visit_functiondef
|
||||
|
||||
def _check_function(self, node: nodes.FunctionDef, match: TypeHintMatch) -> None:
|
||||
# Check that at least one argument is annotated.
|
||||
annotations = _get_all_annotations(node)
|
||||
if (
|
||||
self.linter.config.ignore_missing_annotations
|
||||
and node.returns is None
|
||||
and not _has_valid_annotations(annotations)
|
||||
):
|
||||
return
|
||||
|
||||
def _check_function(
|
||||
self,
|
||||
node: nodes.FunctionDef,
|
||||
match: TypeHintMatch,
|
||||
annotations: list[nodes.NodeNG | None],
|
||||
) -> None:
|
||||
# Check that all positional arguments are correctly annotated.
|
||||
if match.arg_types:
|
||||
for key, expected_type in match.arg_types.items():
|
||||
if node.args.args[key].name in _COMMON_ARGUMENTS:
|
||||
# It has already been checked, avoid double-message
|
||||
continue
|
||||
if not _is_valid_type(expected_type, annotations[key]):
|
||||
self.add_message(
|
||||
"hass-argument-type",
|
||||
@@ -2970,6 +3003,9 @@ class HassTypeHintChecker(BaseChecker): # type: ignore[misc]
|
||||
# Check that all keyword arguments are correctly annotated.
|
||||
if match.named_arg_types is not None:
|
||||
for arg_name, expected_type in match.named_arg_types.items():
|
||||
if arg_name in _COMMON_ARGUMENTS:
|
||||
# It has already been checked, avoid double-message
|
||||
continue
|
||||
arg_node, annotation = _get_named_annotation(node, arg_name)
|
||||
if arg_node and not _is_valid_type(expected_type, annotation):
|
||||
self.add_message(
|
||||
|
||||
Reference in New Issue
Block a user