Add pylint checker for CONNECTION_CLASS in config flows (#183937)

Co-authored-by: Claude <noreply@anthropic.com>
This commit is contained in:
Joost Lekkerkerker
2026-10-01 19:53:09 +01:00
committed by GitHub
co-authored by Claude
parent e49242244b
commit 6edc3771fd
4 changed files with 166 additions and 0 deletions
+10
View File
@@ -1042,3 +1042,13 @@ The light provides `color_mode` but no `supported_color_modes`. At runtime
("does not set supported color modes") from both `state_attributes` and
`capability_attributes` whenever `supported_color_modes` is `None`. Set
`_attr_supported_color_modes` or override the `supported_color_modes` property.
## `home_assistant_enforce_config_flow_no_connection_class` checker
Detects config flow classes that set `CONNECTION_CLASS`.
### `W7438`: `home-assistant-config-flow-connection-class`
`CONNECTION_CLASS` is no longer used by Home Assistant and should not be set
on config flows. Remove the attribute.
@@ -0,0 +1,56 @@
"""Checker for the removed CONNECTION_CLASS attribute in config flows."""
from astroid import nodes
from pylint.checkers import BaseChecker
from pylint.lint import PyLinter
from pylint_home_assistant.const import Module
from pylint_home_assistant.helpers.module_info import parse_module
_CONNECTION_CLASS = "CONNECTION_CLASS"
class HassEnforceConfigFlowNoConnectionClassChecker(BaseChecker):
"""Checker for CONNECTION_CLASS in config flows."""
name = "home_assistant_enforce_config_flow_no_connection_class"
priority = -1
msgs = {
"W7438": (
"Config flow should not set CONNECTION_CLASS",
"home-assistant-config-flow-connection-class",
"Used when a config flow class sets CONNECTION_CLASS. The attribute "
"is no longer used by Home Assistant and should not be set.",
),
}
options = ()
def visit_assign(self, node: nodes.Assign) -> None:
"""Check class-level assignments."""
if any(
isinstance(target, nodes.AssignName) and target.name == _CONNECTION_CLASS
for target in node.targets
):
self._check(node)
def visit_annassign(self, node: nodes.AnnAssign) -> None:
"""Check annotated class-level assignments."""
if (
isinstance(node.target, nodes.AssignName)
and node.target.name == _CONNECTION_CLASS
):
self._check(node)
def _check(self, node: nodes.Assign | nodes.AnnAssign) -> None:
"""Add a message if the assignment is in a config flow class body."""
parsed = parse_module(node.root().name)
if parsed is None or parsed.module != Module.CONFIG_FLOW:
return
if not isinstance(node.parent, nodes.ClassDef):
return
self.add_message("home-assistant-config-flow-connection-class", node=node)
def register(linter: PyLinter) -> None:
"""Register the checker."""
linter.register_checker(HassEnforceConfigFlowNoConnectionClassChecker(linter))
@@ -0,0 +1,86 @@
"""Tests for the config flow no CONNECTION_CLASS checker."""
from __future__ import annotations
import astroid
from pylint.checkers import BaseChecker
from pylint.testutils.unittest_linter import UnittestLinter
import pytest
from tests.pylint import assert_no_messages, walk_checker
CONFIG_FLOW_MODULE = "homeassistant.components.test.config_flow"
@pytest.mark.parametrize(
("code", "module_name"),
[
pytest.param(
"""
class TestFlow:
VERSION = 1
""",
CONFIG_FLOW_MODULE,
id="no_connection_class",
),
pytest.param(
"""
class TestFlow:
CONNECTION_CLASS = "local_polling"
""",
"homeassistant.components.test.sensor",
id="other_module",
),
pytest.param(
"""
CONNECTION_CLASS = "local_polling"
""",
CONFIG_FLOW_MODULE,
id="module_level",
),
],
)
def test_no_connection_class(
linter: UnittestLinter,
enforce_config_flow_no_connection_class_checker: BaseChecker,
code: str,
module_name: str,
) -> None:
"""Good test cases."""
root_node = astroid.parse(code, module_name)
with assert_no_messages(linter):
walk_checker(linter, enforce_config_flow_no_connection_class_checker, root_node)
@pytest.mark.parametrize(
"code",
[
pytest.param(
"""
class TestFlow:
CONNECTION_CLASS = "local_polling"
""",
id="assign",
),
pytest.param(
"""
class TestFlow:
CONNECTION_CLASS: str = "local_polling"
""",
id="annotated_assign",
),
],
)
def test_no_connection_class_bad(
linter: UnittestLinter,
enforce_config_flow_no_connection_class_checker: BaseChecker,
code: str,
) -> None:
"""Bad test cases."""
root_node = astroid.parse(code, CONFIG_FLOW_MODULE)
walk_checker(linter, enforce_config_flow_no_connection_class_checker, root_node)
messages = linter.release_messages()
assert len(messages) == 1
assert messages[0].msg_id == "home-assistant-config-flow-connection-class"
+14
View File
@@ -3,6 +3,9 @@
from pylint.checkers import BaseChecker
from pylint.testutils.unittest_linter import UnittestLinter
from pylint_home_assistant.checkers.class_module import HassEnforceClassModule
from pylint_home_assistant.checkers.config_flow.no_connection_class import (
HassEnforceConfigFlowNoConnectionClassChecker,
)
from pylint_home_assistant.checkers.config_flow.no_name import (
HassEnforceConfigFlowNoNameChecker,
)
@@ -109,6 +112,17 @@ def enforce_serial_port_selector_usb_checker_fixture(
return checker
@pytest.fixture(name="enforce_config_flow_no_connection_class_checker")
def enforce_config_flow_no_connection_class_checker_fixture(
linter: UnittestLinter,
) -> BaseChecker:
"""Fixture to provide a config_flow_no_connection_class checker."""
clear_caches()
checker = HassEnforceConfigFlowNoConnectionClassChecker(linter)
checker.module = "homeassistant.components.pylint_test"
return checker
@pytest.fixture(name="enforce_config_flow_no_name_checker")
def enforce_config_flow_no_name_checker_fixture(
linter: UnittestLinter,