Move pylint decorator plugin and add tests (#126719)
This commit is contained in:
parent
65abe1c875
commit
dff0e2cc9f
5 changed files with 117 additions and 13 deletions
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",
|
||||
"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,
|
||||
|
|
|
@ -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",
|
||||
|
|
|
@ -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
|
||||
|
|
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…
Add table
Reference in a new issue