From 9fd48da132c33cd998b8d0868128dc677c9fa3c9 Mon Sep 17 00:00:00 2001 From: epenet <6771947+epenet@users.noreply.github.com> Date: Tue, 21 Jun 2022 18:53:31 +0200 Subject: [PATCH] Add lock checks to pylint type-hint plugin (#73521) * Add ability to check kwargs type annotation * Add checks for lock * Add tests * Fix components * Fix spelling * Revert "Fix components" This reverts commit 121ff6dc511d28c17b4fc13185155a2402193405. * Adjust comment * Add comments to TypeHintMatch --- pylint/plugins/hass_enforce_type_hints.py | 53 ++++++++++++++++++++--- tests/pylint/test_enforce_type_hints.py | 32 +++++++++++++- 2 files changed, 79 insertions(+), 6 deletions(-) diff --git a/pylint/plugins/hass_enforce_type_hints.py b/pylint/plugins/hass_enforce_type_hints.py index 62dc6feffc6..a1bf260e968 100644 --- a/pylint/plugins/hass_enforce_type_hints.py +++ b/pylint/plugins/hass_enforce_type_hints.py @@ -21,7 +21,10 @@ class TypeHintMatch: function_name: str return_type: list[str] | str | None | object + # arg_types is for positional arguments arg_types: dict[int, str] | None = None + # kwarg_types is for the special case `**kwargs` + kwargs_type: str | None = None check_return_type_inheritance: bool = False @@ -442,9 +445,9 @@ _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]] = { +# Overriding properties and functions are normally checked by mypy, and will only +# be checked by pylint when --ignore-missing-annotations is False +_INHERITANCE_MATCH: dict[str, list[ClassTypeHintMatch]] = { "lock": [ ClassTypeHintMatch( base_class="LockEntity", @@ -473,6 +476,36 @@ _PROPERTY_MATCH: dict[str, list[ClassTypeHintMatch]] = { function_name="is_jammed", return_type=["bool", None], ), + TypeHintMatch( + function_name="lock", + kwargs_type="Any", + return_type=None, + ), + TypeHintMatch( + function_name="async_lock", + kwargs_type="Any", + return_type=None, + ), + TypeHintMatch( + function_name="unlock", + kwargs_type="Any", + return_type=None, + ), + TypeHintMatch( + function_name="async_unlock", + kwargs_type="Any", + return_type=None, + ), + TypeHintMatch( + function_name="open", + kwargs_type="Any", + return_type=None, + ), + TypeHintMatch( + function_name="async_open", + kwargs_type="Any", + return_type=None, + ), ], ), ], @@ -613,7 +646,7 @@ class HassTypeHintChecker(BaseChecker): # type: ignore[misc] priority = -1 msgs = { "W7431": ( - "Argument %d should be of type %s", + "Argument %s should be of type %s", "hass-argument-type", "Used when method argument type is incorrect", ), @@ -659,7 +692,7 @@ class HassTypeHintChecker(BaseChecker): # type: ignore[misc] self._class_matchers.extend(class_matches) if not self.linter.config.ignore_missing_annotations and ( - property_matches := _PROPERTY_MATCH.get(module_platform) + property_matches := _INHERITANCE_MATCH.get(module_platform) ): self._class_matchers.extend(property_matches) @@ -709,6 +742,16 @@ class HassTypeHintChecker(BaseChecker): # type: ignore[misc] args=(key + 1, expected_type), ) + # Check that kwargs is correctly annotated. + if match.kwargs_type and not _is_valid_type( + match.kwargs_type, node.args.kwargannotation + ): + self.add_message( + "hass-argument-type", + node=node, + args=(node.args.kwarg, match.kwargs_type), + ) + # Check the return type. if not _is_valid_return_type(match, node.returns): self.add_message( diff --git a/tests/pylint/test_enforce_type_hints.py b/tests/pylint/test_enforce_type_hints.py index 1f601a881a6..262ff93afa8 100644 --- a/tests/pylint/test_enforce_type_hints.py +++ b/tests/pylint/test_enforce_type_hints.py @@ -478,7 +478,7 @@ def test_invalid_entity_properties( # Set bypass option type_hint_checker.config.ignore_missing_annotations = False - class_node, prop_node = astroid.extract_node( + class_node, prop_node, func_node = astroid.extract_node( """ class LockEntity(): pass @@ -491,6 +491,12 @@ def test_invalid_entity_properties( self ): pass + + async def async_lock( #@ + self, + **kwargs + ) -> bool: + pass """, "homeassistant.components.pylint_test.lock", ) @@ -507,6 +513,24 @@ def test_invalid_entity_properties( end_line=9, end_col_offset=18, ), + pylint.testutils.MessageTest( + msg_id="hass-argument-type", + node=func_node, + args=("kwargs", "Any"), + line=14, + col_offset=4, + end_line=14, + end_col_offset=24, + ), + pylint.testutils.MessageTest( + msg_id="hass-return-type", + node=func_node, + args="None", + line=14, + col_offset=4, + end_line=14, + end_col_offset=24, + ), ): type_hint_checker.visit_classdef(class_node) @@ -528,6 +552,12 @@ def test_ignore_invalid_entity_properties( self ): pass + + async def async_lock( + self, + **kwargs + ) -> bool: + pass """, "homeassistant.components.pylint_test.lock", )