Add checks for lock properties in type-hint plugin (#73729)

* Add checks for lock properties in type-hint plugin

* Adjust comment

* Simplify return-type

* Only check properties when ignore_missing_annotations is disabled

* Adjust tests

* Add comment

* Adjust docstring
This commit is contained in:
epenet 2022-06-21 14:36:22 +02:00 committed by GitHub
parent c674af3ba1
commit 1b8dd3368a
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23
2 changed files with 118 additions and 11 deletions

View File

@ -20,8 +20,8 @@ class TypeHintMatch:
"""Class for pattern matching."""
function_name: str
arg_types: dict[int, str]
return_type: list[str] | str | None | object
arg_types: dict[int, str] | None = None
check_return_type_inheritance: bool = False
@ -440,7 +440,42 @@ _CLASS_MATCH: dict[str, list[ClassTypeHintMatch]] = {
),
],
),
]
],
}
# Properties are normally checked by mypy, and will only be checked
# by pylint when --ignore-missing-annotations is False
_PROPERTY_MATCH: dict[str, list[ClassTypeHintMatch]] = {
"lock": [
ClassTypeHintMatch(
base_class="LockEntity",
matches=[
TypeHintMatch(
function_name="changed_by",
return_type=["str", None],
),
TypeHintMatch(
function_name="code_format",
return_type=["str", None],
),
TypeHintMatch(
function_name="is_locked",
return_type=["bool", None],
),
TypeHintMatch(
function_name="is_locking",
return_type=["bool", None],
),
TypeHintMatch(
function_name="is_unlocking",
return_type=["bool", None],
),
TypeHintMatch(
function_name="is_jammed",
return_type=["bool", None],
),
],
),
],
}
@ -621,7 +656,12 @@ class HassTypeHintChecker(BaseChecker): # type: ignore[misc]
self._function_matchers.extend(function_matches)
if class_matches := _CLASS_MATCH.get(module_platform):
self._class_matchers = class_matches
self._class_matchers.extend(class_matches)
if not self.linter.config.ignore_missing_annotations and (
property_matches := _PROPERTY_MATCH.get(module_platform)
):
self._class_matchers.extend(property_matches)
def visit_classdef(self, node: nodes.ClassDef) -> None:
"""Called when a ClassDef node is visited."""
@ -659,14 +699,15 @@ class HassTypeHintChecker(BaseChecker): # type: ignore[misc]
):
return
# Check that all arguments are correctly annotated.
for key, expected_type in match.arg_types.items():
if not _is_valid_type(expected_type, annotations[key]):
self.add_message(
"hass-argument-type",
node=node.args.args[key],
args=(key + 1, expected_type),
)
# Check that all positional arguments are correctly annotated.
if match.arg_types:
for key, expected_type in match.arg_types.items():
if not _is_valid_type(expected_type, annotations[key]):
self.add_message(
"hass-argument-type",
node=node.args.args[key],
args=(key + 1, expected_type),
)
# Check the return type.
if not _is_valid_return_type(match, node.returns):

View File

@ -469,3 +469,69 @@ def test_valid_config_flow_async_get_options_flow(
with assert_no_messages(linter):
type_hint_checker.visit_classdef(class_node)
def test_invalid_entity_properties(
linter: UnittestLinter, type_hint_checker: BaseChecker
) -> None:
"""Check missing entity properties when ignore_missing_annotations is False."""
# Set bypass option
type_hint_checker.config.ignore_missing_annotations = False
class_node, prop_node = astroid.extract_node(
"""
class LockEntity():
pass
class DoorLock( #@
LockEntity
):
@property
def changed_by( #@
self
):
pass
""",
"homeassistant.components.pylint_test.lock",
)
type_hint_checker.visit_module(class_node.parent)
with assert_adds_messages(
linter,
pylint.testutils.MessageTest(
msg_id="hass-return-type",
node=prop_node,
args=["str", None],
line=9,
col_offset=4,
end_line=9,
end_col_offset=18,
),
):
type_hint_checker.visit_classdef(class_node)
def test_ignore_invalid_entity_properties(
linter: UnittestLinter, type_hint_checker: BaseChecker
) -> None:
"""Check invalid entity properties are ignored by default."""
class_node = astroid.extract_node(
"""
class LockEntity():
pass
class DoorLock( #@
LockEntity
):
@property
def changed_by(
self
):
pass
""",
"homeassistant.components.pylint_test.lock",
)
type_hint_checker.visit_module(class_node.parent)
with assert_no_messages(linter):
type_hint_checker.visit_classdef(class_node)