Add pylint plugin to check for sorted platforms list (#108115)

This commit is contained in:
Jan-Philipp Benecke 2024-01-16 09:47:53 +01:00 committed by GitHub
parent fb24e086b2
commit 28281523ec
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23
49 changed files with 194 additions and 62 deletions

View File

@ -63,12 +63,12 @@ AUTOMATION_SCHEMA = vol.Schema({ATTR_ENTITY_ID: cv.entity_ids})
PLATFORMS = [
Platform.ALARM_CONTROL_PANEL,
Platform.BINARY_SENSOR,
Platform.LOCK,
Platform.SWITCH,
Platform.COVER,
Platform.CAMERA,
Platform.COVER,
Platform.LIGHT,
Platform.LOCK,
Platform.SENSOR,
Platform.SWITCH,
]

View File

@ -19,11 +19,11 @@ PLATFORMS = [
Platform.BINARY_SENSOR,
Platform.CLIMATE,
Platform.COVER,
Platform.LIGHT,
Platform.SELECT,
Platform.SENSOR,
Platform.SWITCH,
Platform.UPDATE,
Platform.LIGHT,
]
_LOGGER = logging.getLogger(__name__)

View File

@ -39,8 +39,8 @@ _LOGGER = logging.getLogger(__name__)
PLATFORMS = [
Platform.ALARM_CONTROL_PANEL,
Platform.SENSOR,
Platform.BINARY_SENSOR,
Platform.SENSOR,
]

View File

@ -11,4 +11,4 @@ CONF_SITE_NMI = "site_nmi"
ATTRIBUTION = "Data provided by Amber Electric"
LOGGER = logging.getLogger(__package__)
PLATFORMS = [Platform.SENSOR, Platform.BINARY_SENSOR]
PLATFORMS = [Platform.BINARY_SENSOR, Platform.SENSOR]

View File

@ -19,7 +19,7 @@ from homeassistant.helpers.update_coordinator import (
_LOGGER = logging.getLogger(__name__)
DOMAIN = "atag"
PLATFORMS = [Platform.CLIMATE, Platform.WATER_HEATER, Platform.SENSOR]
PLATFORMS = [Platform.CLIMATE, Platform.SENSOR, Platform.WATER_HEATER]
async def async_setup_entry(hass: HomeAssistant, entry: ConfigEntry) -> bool:

View File

@ -50,9 +50,9 @@ LOGIN_METHODS = ["phone", "email"]
DEFAULT_LOGIN_METHOD = "email"
PLATFORMS = [
Platform.BINARY_SENSOR,
Platform.BUTTON,
Platform.CAMERA,
Platform.BINARY_SENSOR,
Platform.LOCK,
Platform.SENSOR,
]

View File

@ -16,7 +16,7 @@ from homeassistant.util.unit_system import METRIC_SYSTEM
_LOGGER = logging.getLogger(__name__)
PLATFORMS = [Platform.CAMERA, Platform.BINARY_SENSOR, Platform.SENSOR]
PLATFORMS = [Platform.BINARY_SENSOR, Platform.CAMERA, Platform.SENSOR]
DOMAIN = "bloomsky"

View File

@ -9,7 +9,7 @@ from homeassistant.exceptions import ConfigEntryNotReady
from .const import CONF_SWING_SUPPORT, DATA_COORDINATOR, DATA_INFO, DOMAIN
from .coordinator import CoolmasterDataUpdateCoordinator
PLATFORMS = [Platform.CLIMATE, Platform.BINARY_SENSOR, Platform.BUTTON, Platform.SENSOR]
PLATFORMS = [Platform.BINARY_SENSOR, Platform.BUTTON, Platform.CLIMATE, Platform.SENSOR]
async def async_setup_entry(hass: HomeAssistant, entry: ConfigEntry) -> bool:

View File

@ -15,7 +15,7 @@ from homeassistant.util import Throttle
_LOGGER = logging.getLogger(__name__)
PLATFORMS = [Platform.SENSOR, Platform.BINARY_SENSOR, Platform.SWITCH]
PLATFORMS = [Platform.BINARY_SENSOR, Platform.SENSOR, Platform.SWITCH]
DOMAIN = "danfoss_air"
MIN_TIME_BETWEEN_UPDATES = timedelta(seconds=60)

View File

@ -6,7 +6,7 @@ from homeassistant.const import CONF_ROOM, Platform
LOGGER = logging.getLogger(__package__)
DOMAIN = "dynalite"
PLATFORMS = [Platform.LIGHT, Platform.SWITCH, Platform.COVER]
PLATFORMS = [Platform.COVER, Platform.LIGHT, Platform.SWITCH]
CONF_ACTIVE = "active"

View File

@ -27,8 +27,8 @@ from .const import API_CLIENT, DOMAIN, EQUIPMENT
_LOGGER = logging.getLogger(__name__)
PLATFORMS = [
Platform.CLIMATE,
Platform.BINARY_SENSOR,
Platform.CLIMATE,
Platform.SENSOR,
Platform.WATER_HEATER,
]

View File

@ -15,8 +15,8 @@ SIGNAL_SEND_MESSAGE = "enocean.send_message"
LOGGER = logging.getLogger(__package__)
PLATFORMS = [
Platform.LIGHT,
Platform.BINARY_SENSOR,
Platform.LIGHT,
Platform.SENSOR,
Platform.SWITCH,
]

View File

@ -42,12 +42,12 @@ PLATFORMS = [
Platform.BINARY_SENSOR,
Platform.CLIMATE,
Platform.COVER,
Platform.EVENT,
Platform.LIGHT,
Platform.LOCK,
Platform.SCENE,
Platform.SENSOR,
Platform.LOCK,
Platform.SWITCH,
Platform.EVENT,
]
FIBARO_TYPEMAP = {

View File

@ -25,7 +25,7 @@ MIN_TIME_BETWEEN_UPDATES = timedelta(seconds=60)
_LOGGER = logging.getLogger(__name__)
PLATFORMS = [Platform.SENSOR, Platform.BINARY_SENSOR, Platform.SWITCH]
PLATFORMS = [Platform.BINARY_SENSOR, Platform.SENSOR, Platform.SWITCH]
async def async_setup_entry(hass: HomeAssistant, entry: ConfigEntry) -> bool:

View File

@ -19,12 +19,12 @@ API_VERSION = "v6"
PLATFORMS = [
Platform.ALARM_CONTROL_PANEL,
Platform.BINARY_SENSOR,
Platform.BUTTON,
Platform.CAMERA,
Platform.DEVICE_TRACKER,
Platform.SENSOR,
Platform.BINARY_SENSOR,
Platform.SWITCH,
Platform.CAMERA,
]
DEFAULT_DEVICE_NAME = "Unknown device"

View File

@ -28,8 +28,8 @@ class MeshRoles(StrEnum):
DOMAIN = "fritz"
PLATFORMS = [
Platform.BUTTON,
Platform.BINARY_SENSOR,
Platform.BUTTON,
Platform.DEVICE_TRACKER,
Platform.IMAGE,
Platform.SENSOR,

View File

@ -7,7 +7,7 @@ from homeassistant.const import Platform
DOMAIN = "gdacs"
PLATFORMS = [Platform.SENSOR, Platform.GEO_LOCATION]
PLATFORMS = [Platform.GEO_LOCATION, Platform.SENSOR]
FEED = "feed"

View File

@ -5,7 +5,7 @@ from homeassistant.const import Platform
DOMAIN = "geonetnz_quakes"
PLATFORMS = [Platform.SENSOR, Platform.GEO_LOCATION]
PLATFORMS = [Platform.GEO_LOCATION, Platform.SENSOR]
CONF_MINIMUM_MAGNITUDE = "minimum_magnitude"
CONF_MMI = "mmi"

View File

@ -127,9 +127,9 @@ PLATFORMS = [
Platform.BINARY_SENSOR,
Platform.BUTTON,
Platform.DEVICE_TRACKER,
Platform.SELECT,
Platform.SENSOR,
Platform.SWITCH,
Platform.SELECT,
]

View File

@ -32,7 +32,7 @@ from .const import (
SIGNAL_INSTANCE_REMOVE,
)
PLATFORMS = [Platform.LIGHT, Platform.SWITCH, Platform.CAMERA]
PLATFORMS = [Platform.CAMERA, Platform.LIGHT, Platform.SWITCH]
_LOGGER = logging.getLogger(__name__)

View File

@ -17,7 +17,7 @@ from .const import DATA_API, DATA_LOCATION, DOMAIN
DEFAULT_NAME = "ipma"
PLATFORMS = [Platform.WEATHER, Platform.SENSOR]
PLATFORMS = [Platform.SENSOR, Platform.WEATHER]
_LOGGER = logging.getLogger(__name__)

View File

@ -20,7 +20,7 @@ from .device import JuiceNetApi
_LOGGER = logging.getLogger(__name__)
PLATFORMS = [Platform.SENSOR, Platform.SWITCH, Platform.NUMBER]
PLATFORMS = [Platform.NUMBER, Platform.SENSOR, Platform.SWITCH]
CONFIG_SCHEMA = vol.Schema(
vol.All(

View File

@ -72,4 +72,4 @@ DEFAULT_AQI_STANDARD = "us"
DEFAULT_PREFERRED_UNIT: list[str] = []
DEFAULT_SCAN_INTERVAL = timedelta(seconds=30)
PLATFORMS = [Platform.SENSOR, Platform.AIR_QUALITY]
PLATFORMS = [Platform.AIR_QUALITY, Platform.SENSOR]

View File

@ -91,12 +91,12 @@ CONFIG_SCHEMA = vol.Schema(
PLATFORMS = [
Platform.BINARY_SENSOR,
Platform.BUTTON,
Platform.COVER,
Platform.FAN,
Platform.LIGHT,
Platform.SCENE,
Platform.SWITCH,
Platform.BUTTON,
]

View File

@ -39,7 +39,7 @@ from .http_api import RegistrationsView
from .util import async_create_cloud_hook
from .webhook import handle_webhook
PLATFORMS = [Platform.SENSOR, Platform.BINARY_SENSOR, Platform.DEVICE_TRACKER]
PLATFORMS = [Platform.BINARY_SENSOR, Platform.DEVICE_TRACKER, Platform.SENSOR]
CONFIG_SCHEMA = cv.empty_config_schema(DOMAIN)

View File

@ -34,8 +34,8 @@ _P = ParamSpec("_P")
SCAN_INTERVAL = timedelta(seconds=5)
PLATFORMS = [
Platform.BINARY_SENSOR,
Platform.LIGHT,
Platform.FAN,
Platform.LIGHT,
Platform.SENSOR,
Platform.SWITCH,
]

View File

@ -17,7 +17,7 @@ from .const import DOMAIN
_LOGGER = logging.getLogger(__name__)
PLATFORMS = [Platform.BUTTON, Platform.CLIMATE, Platform.SENSOR, Platform.BINARY_SENSOR]
PLATFORMS = [Platform.BINARY_SENSOR, Platform.BUTTON, Platform.CLIMATE, Platform.SENSOR]
UPDATE_INTERVAL = timedelta(seconds=60)

View File

@ -142,9 +142,9 @@ PLATFORMS = [
Platform.BUTTON,
Platform.CAMERA,
Platform.CLIMATE,
Platform.COVER,
Platform.DEVICE_TRACKER,
Platform.EVENT,
Platform.COVER,
Platform.FAN,
Platform.HUMIDIFIER,
Platform.IMAGE,
@ -152,8 +152,8 @@ PLATFORMS = [
Platform.LIGHT,
Platform.LOCK,
Platform.NUMBER,
Platform.SELECT,
Platform.SCENE,
Platform.SELECT,
Platform.SENSOR,
Platform.SIREN,
Platform.SWITCH,

View File

@ -41,11 +41,11 @@ CONFIG_SCHEMA = vol.Schema(
)
PLATFORMS = [
Platform.CAMERA,
Platform.VACUUM,
Platform.SWITCH,
Platform.SENSOR,
Platform.BUTTON,
Platform.CAMERA,
Platform.SENSOR,
Platform.SWITCH,
Platform.VACUUM,
]

View File

@ -95,7 +95,7 @@ CONFIG_SCHEMA = vol.Schema(
)
# Platforms for SDM API
PLATFORMS = [Platform.SENSOR, Platform.CAMERA, Platform.CLIMATE]
PLATFORMS = [Platform.CAMERA, Platform.CLIMATE, Platform.SENSOR]
# Fetch media events with a disk backed cache, with a limit for each camera
# device. The largest media items are mp4 clips at ~120kb each, and we target

View File

@ -87,7 +87,7 @@ CONFIG_SCHEMA = vol.Schema(
extra=vol.ALLOW_EXTRA,
)
PLATFORMS = [Platform.SENSOR, Platform.SWITCH, Platform.BINARY_SENSOR, Platform.BUTTON]
PLATFORMS = [Platform.BINARY_SENSOR, Platform.BUTTON, Platform.SENSOR, Platform.SWITCH]
SIGNAL_UPDATE_LEAF = "nissan_leaf_update"

View File

@ -32,11 +32,11 @@ from homeassistant.helpers.update_coordinator import DataUpdateCoordinator, Upda
from .const import CONF_ALLOW_NOTIFY, CONF_SYSTEM, DOMAIN
PLATFORMS = [
Platform.MEDIA_PLAYER,
Platform.BINARY_SENSOR,
Platform.LIGHT,
Platform.MEDIA_PLAYER,
Platform.REMOTE,
Platform.SWITCH,
Platform.BINARY_SENSOR,
]
LOGGER = logging.getLogger(__name__)

View File

@ -17,7 +17,7 @@ PLACEHOLDER_DOCS_URL = "docs_url"
PLACEHOLDER_DEVICE_TYPE = "device_type"
PLACEHOLDER_DEVICE_NAME = "device_name"
DOCS_URL = "https://www.home-assistant.io/integrations/plaato/"
PLATFORMS = [Platform.SENSOR, Platform.BINARY_SENSOR]
PLATFORMS = [Platform.BINARY_SENSOR, Platform.SENSOR]
SENSOR_DATA = "sensor_data"
COORDINATOR = "coordinator"
DEVICE = "device"

View File

@ -10,7 +10,7 @@ from homeassistant.core import HomeAssistant
from .const import DOMAIN
PLATFORMS = [Platform.SWITCH, Platform.BINARY_SENSOR]
PLATFORMS = [Platform.BINARY_SENSOR, Platform.SWITCH]
async def async_setup_entry(hass: HomeAssistant, entry: ConfigEntry) -> bool:

View File

@ -22,7 +22,7 @@ from .webhooks import (
_LOGGER = logging.getLogger(__name__)
PLATFORMS = [Platform.SWITCH, Platform.BINARY_SENSOR]
PLATFORMS = [Platform.BINARY_SENSOR, Platform.SWITCH]
CONFIG_SCHEMA = cv.removed(DOMAIN, raise_if_present=False)

View File

@ -20,11 +20,11 @@ from .coordinator import RainbirdData
_LOGGER = logging.getLogger(__name__)
PLATFORMS = [
Platform.SWITCH,
Platform.SENSOR,
Platform.BINARY_SENSOR,
Platform.NUMBER,
Platform.CALENDAR,
Platform.NUMBER,
Platform.SENSOR,
Platform.SWITCH,
]

View File

@ -76,12 +76,12 @@ def _bytearray_string(data: Any) -> bytearray:
SERVICE_SEND_SCHEMA = vol.Schema({ATTR_EVENT: _bytearray_string})
PLATFORMS = [
Platform.SWITCH,
Platform.SENSOR,
Platform.LIGHT,
Platform.BINARY_SENSOR,
Platform.COVER,
Platform.LIGHT,
Platform.SENSOR,
Platform.SIREN,
Platform.SWITCH,
]

View File

@ -15,11 +15,11 @@ DEFAULT_ENTITY_NAMESPACE = "ring"
PLATFORMS = [
Platform.BINARY_SENSOR,
Platform.CAMERA,
Platform.LIGHT,
Platform.SENSOR,
Platform.SWITCH,
Platform.CAMERA,
Platform.SIREN,
Platform.SWITCH,
]

View File

@ -9,8 +9,8 @@ CONF_BASE_URL = "base_url"
CONF_USER_DATA = "user_data"
PLATFORMS = [
Platform.BUTTON,
Platform.BINARY_SENSOR,
Platform.BUTTON,
Platform.IMAGE,
Platform.NUMBER,
Platform.SELECT,

View File

@ -2,7 +2,7 @@
from homeassistant.const import Platform
DOMAIN = "roomba"
PLATFORMS = [Platform.SENSOR, Platform.BINARY_SENSOR, Platform.VACUUM]
PLATFORMS = [Platform.BINARY_SENSOR, Platform.SENSOR, Platform.VACUUM]
CONF_CERT = "certificate"
CONF_CONTINUOUS = "continuous"
CONF_BLID = "blid"

View File

@ -34,15 +34,15 @@ STORAGE_VERSION = 1
# Ordered 'specific to least-specific platform' in order for capabilities
# to be drawn-down and represented by the most appropriate platform.
PLATFORMS = [
Platform.BINARY_SENSOR,
Platform.CLIMATE,
Platform.COVER,
Platform.FAN,
Platform.LIGHT,
Platform.LOCK,
Platform.COVER,
Platform.SWITCH,
Platform.BINARY_SENSOR,
Platform.SENSOR,
Platform.SCENE,
Platform.SENSOR,
Platform.SWITCH,
]
IGNORED_CAPABILITIES = [

View File

@ -4,4 +4,4 @@ from homeassistant.const import Platform
DOMAIN = "spider"
DEFAULT_SCAN_INTERVAL = 300
PLATFORMS = [Platform.CLIMATE, Platform.SWITCH, Platform.SENSOR]
PLATFORMS = [Platform.CLIMATE, Platform.SENSOR, Platform.SWITCH]

View File

@ -9,7 +9,7 @@ from homeassistant.helpers import device_registry as dr
from .const import DOMAIN
from .coordinator import TailwindDataUpdateCoordinator
PLATFORMS = [Platform.BINARY_SENSOR, Platform.COVER, Platform.BUTTON, Platform.NUMBER]
PLATFORMS = [Platform.BINARY_SENSOR, Platform.BUTTON, Platform.COVER, Platform.NUMBER]
async def async_setup_entry(hass: HomeAssistant, entry: ConfigEntry) -> bool:

View File

@ -7,7 +7,7 @@ from homeassistant.core import HomeAssistant
from .const import DOMAIN
from .coordinator import VodafoneStationRouter
PLATFORMS = [Platform.DEVICE_TRACKER, Platform.SENSOR, Platform.BUTTON]
PLATFORMS = [Platform.BUTTON, Platform.DEVICE_TRACKER, Platform.SENSOR]
async def async_setup_entry(hass: HomeAssistant, entry: ConfigEntry) -> bool:

View File

@ -11,7 +11,7 @@ from homeassistant.exceptions import ConfigEntryAuthFailed
from .const import CONF_STATION, DOMAIN, UPDATE_INTERVAL
from .coordinator import InvalidAuth, WallboxCoordinator
PLATFORMS = [Platform.SENSOR, Platform.NUMBER, Platform.LOCK, Platform.SWITCH]
PLATFORMS = [Platform.LOCK, Platform.NUMBER, Platform.SENSOR, Platform.SWITCH]
async def async_setup_entry(hass: HomeAssistant, entry: ConfigEntry) -> bool:

View File

@ -0,0 +1,39 @@
"""Plugin for checking sorted platforms list."""
from __future__ import annotations
from astroid import nodes
from pylint.checkers import BaseChecker
from pylint.lint import PyLinter
class HassEnforceSortedPlatformsChecker(BaseChecker):
"""Checker for sorted platforms list."""
name = "hass_enforce_sorted_platforms"
priority = -1
msgs = {
"W7451": (
"Platforms must be sorted alphabetically",
"hass-enforce-sorted-platforms",
"Used when PLATFORMS should be sorted alphabetically.",
),
}
options = ()
def visit_assign(self, node: nodes.Assign) -> None:
"""Check for sorted PLATFORMS const."""
for target in node.targets:
if (
isinstance(target, nodes.AssignName)
and target.name == "PLATFORMS"
and isinstance(node.value, nodes.List)
):
platforms = [v.as_string() for v in node.value.elts]
sorted_platforms = sorted(platforms)
if platforms != sorted_platforms:
self.add_message("hass-enforce-sorted-platforms", node=node)
def register(linter: PyLinter) -> None:
"""Register the checker."""
linter.register_checker(HassEnforceSortedPlatformsChecker(linter))

View File

@ -103,6 +103,7 @@ init-hook = """\
load-plugins = [
"pylint.extensions.code_style",
"pylint.extensions.typing",
"hass_enforce_sorted_platforms",
"hass_enforce_super_call",
"hass_enforce_type_hints",
"hass_inheritance",

View File

@ -80,3 +80,24 @@ def super_call_checker_fixture(hass_enforce_super_call, linter) -> BaseChecker:
super_call_checker = hass_enforce_super_call.HassEnforceSuperCallChecker(linter)
super_call_checker.module = "homeassistant.components.pylint_test"
return super_call_checker
@pytest.fixture(name="hass_enforce_sorted_platforms", scope="session")
def hass_enforce_sorted_platforms_fixture() -> ModuleType:
"""Fixture to the content for the hass_enforce_sorted_platforms check."""
return _load_plugin_from_file(
"hass_enforce_sorted_platforms",
"pylint/plugins/hass_enforce_sorted_platforms.py",
)
@pytest.fixture(name="enforce_sorted_platforms_checker")
def enforce_sorted_platforms_checker_fixture(
hass_enforce_sorted_platforms, linter
) -> BaseChecker:
"""Fixture to provide a hass_enforce_sorted_platforms checker."""
enforce_sorted_platforms_checker = (
hass_enforce_sorted_platforms.HassEnforceSortedPlatformsChecker(linter)
)
enforce_sorted_platforms_checker.module = "homeassistant.components.pylint_test"
return enforce_sorted_platforms_checker

View File

@ -0,0 +1,71 @@
"""Tests for pylint hass_enforce_sorted_platforms 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
import pytest
from . import assert_adds_messages, assert_no_messages
@pytest.mark.parametrize(
"code",
[
pytest.param(
"""
PLATFORMS = [Platform.SENSOR]
""",
id="one_platform",
),
pytest.param(
"""
PLATFORMS = [Platform.BINARY_SENSOR, Platform.BUTTON, Platform.SENSOR]
""",
id="multiple_platforms",
),
],
)
def test_enforce_sorted_platforms(
linter: UnittestLinter,
enforce_sorted_platforms_checker: BaseChecker,
code: str,
) -> None:
"""Good test cases."""
root_node = astroid.parse(code, "homeassistant.components.pylint_test")
walker = ASTWalker(linter)
walker.add_checker(enforce_sorted_platforms_checker)
with assert_no_messages(linter):
walker.walk(root_node)
def test_enforce_sorted_platforms_bad(
linter: UnittestLinter,
enforce_sorted_platforms_checker: BaseChecker,
) -> None:
"""Bad test case."""
assign_node = astroid.extract_node(
"""
PLATFORMS = [Platform.SENSOR, Platform.BINARY_SENSOR, Platform.BUTTON]
""",
"homeassistant.components.pylint_test",
)
with assert_adds_messages(
linter,
MessageTest(
msg_id="hass-enforce-sorted-platforms",
line=2,
node=assign_node,
args=None,
confidence=UNDEFINED,
col_offset=0,
end_line=2,
end_col_offset=70,
),
):
enforce_sorted_platforms_checker.visit_assign(assign_node)