mirror of
https://github.com/home-assistant/core.git
synced 2025-07-22 20:57:21 +00:00
Move pylint decorator plugin and add tests (#126719)
This commit is contained in:
parent
65abe1c875
commit
dff0e2cc9f
33
pylint/plugins/hass_decorator.py
Normal file
33
pylint/plugins/hass_decorator.py
Normal file
@ -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))
|
@ -3093,11 +3093,6 @@ class HassTypeHintChecker(BaseChecker):
|
|||||||
"hass-consider-usefixtures-decorator",
|
"hass-consider-usefixtures-decorator",
|
||||||
"Used when an argument type is None and could be a fixture",
|
"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 = (
|
options = (
|
||||||
(
|
(
|
||||||
@ -3200,14 +3195,6 @@ class HassTypeHintChecker(BaseChecker):
|
|||||||
self._check_function(function_node, match, annotations)
|
self._check_function(function_node, match, annotations)
|
||||||
checked_class_methods.add(function_node.name)
|
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:
|
def visit_functiondef(self, node: nodes.FunctionDef) -> None:
|
||||||
"""Apply relevant type hint checks on a FunctionDef node."""
|
"""Apply relevant type hint checks on a FunctionDef node."""
|
||||||
annotations = _get_all_annotations(node)
|
annotations = _get_all_annotations(node)
|
||||||
@ -3247,6 +3234,8 @@ class HassTypeHintChecker(BaseChecker):
|
|||||||
continue
|
continue
|
||||||
self._check_function(node, match, annotations)
|
self._check_function(node, match, annotations)
|
||||||
|
|
||||||
|
visit_asyncfunctiondef = visit_functiondef
|
||||||
|
|
||||||
def _check_function(
|
def _check_function(
|
||||||
self,
|
self,
|
||||||
node: nodes.FunctionDef,
|
node: nodes.FunctionDef,
|
||||||
|
@ -112,6 +112,7 @@ init-hook = """\
|
|||||||
load-plugins = [
|
load-plugins = [
|
||||||
"pylint.extensions.code_style",
|
"pylint.extensions.code_style",
|
||||||
"pylint.extensions.typing",
|
"pylint.extensions.typing",
|
||||||
|
"hass_decorator",
|
||||||
"hass_enforce_class_module",
|
"hass_enforce_class_module",
|
||||||
"hass_enforce_sorted_platforms",
|
"hass_enforce_sorted_platforms",
|
||||||
"hass_enforce_super_call",
|
"hass_enforce_super_call",
|
||||||
|
@ -121,3 +121,20 @@ def enforce_class_module_fixture(hass_enforce_class_module, linter) -> BaseCheck
|
|||||||
)
|
)
|
||||||
enforce_class_module_checker.module = "homeassistant.components.pylint_test"
|
enforce_class_module_checker.module = "homeassistant.components.pylint_test"
|
||||||
return enforce_class_module_checker
|
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
|
||||||
|
64
tests/pylint/test_decorator.py
Normal file
64
tests/pylint/test_decorator.py
Normal file
@ -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)
|
Loading…
x
Reference in New Issue
Block a user