From dff0e2cc9f370c135929364eac3173a2b1a8c683 Mon Sep 17 00:00:00 2001 From: epenet <6771947+epenet@users.noreply.github.com> Date: Wed, 25 Sep 2024 09:41:23 +0200 Subject: [PATCH] Move pylint decorator plugin and add tests (#126719) --- pylint/plugins/hass_decorator.py | 33 ++++++++++++ pylint/plugins/hass_enforce_type_hints.py | 15 +----- pyproject.toml | 1 + tests/pylint/conftest.py | 17 ++++++ tests/pylint/test_decorator.py | 64 +++++++++++++++++++++++ 5 files changed, 117 insertions(+), 13 deletions(-) create mode 100644 pylint/plugins/hass_decorator.py create mode 100644 tests/pylint/test_decorator.py diff --git a/pylint/plugins/hass_decorator.py b/pylint/plugins/hass_decorator.py new file mode 100644 index 00000000000..51bdd99cd2b --- /dev/null +++ b/pylint/plugins/hass_decorator.py @@ -0,0 +1,33 @@ +"""Plugin to check decorators.""" + +from __future__ import annotations + +from astroid import nodes +from pylint.checkers import BaseChecker +from pylint.lint import PyLinter + + +class HassDecoratorChecker(BaseChecker): + """Checker for decorators.""" + + name = "hass_decorator" + priority = -1 + msgs = { + "W7471": ( + "A coroutine function should not be decorated with @callback", + "hass-async-callback-decorator", + "Used when a coroutine function has an invalid @callback decorator", + ), + } + + def visit_asyncfunctiondef(self, node: nodes.AsyncFunctionDef) -> None: + """Apply checks on an AsyncFunctionDef node.""" + if ( + decoratornames := node.decoratornames() + ) and "homeassistant.core.callback" in decoratornames: + self.add_message("hass-async-callback-decorator", node=node) + + +def register(linter: PyLinter) -> None: + """Register the checker.""" + linter.register_checker(HassDecoratorChecker(linter)) diff --git a/pylint/plugins/hass_enforce_type_hints.py b/pylint/plugins/hass_enforce_type_hints.py index 65931a152e9..a837650f3b5 100644 --- a/pylint/plugins/hass_enforce_type_hints.py +++ b/pylint/plugins/hass_enforce_type_hints.py @@ -3093,11 +3093,6 @@ class HassTypeHintChecker(BaseChecker): "hass-consider-usefixtures-decorator", "Used when an argument type is None and could be a fixture", ), - "W7434": ( - "A coroutine function should not be decorated with @callback", - "hass-async-callback-decorator", - "Used when a coroutine function has an invalid @callback decorator", - ), } options = ( ( @@ -3200,14 +3195,6 @@ class HassTypeHintChecker(BaseChecker): self._check_function(function_node, match, annotations) checked_class_methods.add(function_node.name) - def visit_asyncfunctiondef(self, node: nodes.AsyncFunctionDef) -> None: - """Apply checks on an AsyncFunctionDef node.""" - if ( - decoratornames := node.decoratornames() - ) and "homeassistant.core.callback" in decoratornames: - self.add_message("hass-async-callback-decorator", node=node) - self.visit_functiondef(node) - def visit_functiondef(self, node: nodes.FunctionDef) -> None: """Apply relevant type hint checks on a FunctionDef node.""" annotations = _get_all_annotations(node) @@ -3247,6 +3234,8 @@ class HassTypeHintChecker(BaseChecker): continue self._check_function(node, match, annotations) + visit_asyncfunctiondef = visit_functiondef + def _check_function( self, node: nodes.FunctionDef, diff --git a/pyproject.toml b/pyproject.toml index f2eb220cedf..116fc5b74ed 100644 --- a/pyproject.toml +++ b/pyproject.toml @@ -112,6 +112,7 @@ init-hook = """\ load-plugins = [ "pylint.extensions.code_style", "pylint.extensions.typing", + "hass_decorator", "hass_enforce_class_module", "hass_enforce_sorted_platforms", "hass_enforce_super_call", diff --git a/tests/pylint/conftest.py b/tests/pylint/conftest.py index 5e8ed28da6b..8ae291ac0b7 100644 --- a/tests/pylint/conftest.py +++ b/tests/pylint/conftest.py @@ -121,3 +121,20 @@ def enforce_class_module_fixture(hass_enforce_class_module, linter) -> BaseCheck ) enforce_class_module_checker.module = "homeassistant.components.pylint_test" return enforce_class_module_checker + + +@pytest.fixture(name="hass_decorator", scope="package") +def hass_decorator_fixture() -> ModuleType: + """Fixture to provide a pylint plugin.""" + return _load_plugin_from_file( + "hass_imports", + "pylint/plugins/hass_decorator.py", + ) + + +@pytest.fixture(name="decorator_checker") +def decorator_checker_fixture(hass_decorator, linter) -> BaseChecker: + """Fixture to provide a pylint checker.""" + type_hint_checker = hass_decorator.HassDecoratorChecker(linter) + type_hint_checker.module = "homeassistant.components.pylint_test" + return type_hint_checker diff --git a/tests/pylint/test_decorator.py b/tests/pylint/test_decorator.py new file mode 100644 index 00000000000..05a443c1456 --- /dev/null +++ b/tests/pylint/test_decorator.py @@ -0,0 +1,64 @@ +"""Tests for pylint hass_enforce_type_hints plugin.""" + +from __future__ import annotations + +import astroid +from pylint.checkers import BaseChecker +from pylint.interfaces import UNDEFINED +from pylint.testutils import MessageTest +from pylint.testutils.unittest_linter import UnittestLinter +from pylint.utils.ast_walker import ASTWalker + +from . import assert_adds_messages, assert_no_messages + + +def test_good_callback(linter: UnittestLinter, decorator_checker: BaseChecker) -> None: + """Test good `@callback` decorator.""" + code = """ + from homeassistant.core import callback + + @callback + def setup( + arg1, arg2 + ): + pass + """ + + root_node = astroid.parse(code) + walker = ASTWalker(linter) + walker.add_checker(decorator_checker) + + with assert_no_messages(linter): + walker.walk(root_node) + + +def test_bad_callback(linter: UnittestLinter, decorator_checker: BaseChecker) -> None: + """Test bad `@callback` decorator.""" + code = """ + from homeassistant.core import callback + + @callback + async def setup( + arg1, arg2 + ): + pass + """ + + root_node = astroid.parse(code) + walker = ASTWalker(linter) + walker.add_checker(decorator_checker) + + with assert_adds_messages( + linter, + MessageTest( + msg_id="hass-async-callback-decorator", + line=5, + node=root_node.body[1], + args=None, + confidence=UNDEFINED, + col_offset=0, + end_line=5, + end_col_offset=15, + ), + ): + walker.walk(root_node)