2021-05-22 08:15:30 +00:00
|
|
|
"""Plugin for checking imports."""
|
|
|
|
from __future__ import annotations
|
|
|
|
|
2022-04-04 09:45:53 +00:00
|
|
|
from dataclasses import dataclass
|
|
|
|
import re
|
|
|
|
|
2021-05-22 08:15:30 +00:00
|
|
|
from astroid import Import, ImportFrom, Module
|
|
|
|
from pylint.checkers import BaseChecker
|
|
|
|
from pylint.interfaces import IAstroidChecker
|
|
|
|
from pylint.lint import PyLinter
|
|
|
|
|
|
|
|
|
2022-04-04 09:45:53 +00:00
|
|
|
@dataclass
|
|
|
|
class ObsoleteImportMatch:
|
|
|
|
"""Class for pattern matching."""
|
|
|
|
|
|
|
|
constant: re.Pattern
|
|
|
|
reason: str
|
|
|
|
|
|
|
|
|
|
|
|
_OBSOLETE_IMPORT: dict[str, list[ObsoleteImportMatch]] = {
|
2022-04-04 17:48:14 +00:00
|
|
|
"homeassistant.components.binarysensor": [
|
|
|
|
ObsoleteImportMatch(
|
|
|
|
reason="replaced by BinarySensorDeviceClass enum",
|
|
|
|
constant=re.compile(r"^DEVICE_CLASS_(\w*)$"),
|
|
|
|
),
|
|
|
|
],
|
|
|
|
"homeassistant.components.cover": [
|
|
|
|
ObsoleteImportMatch(
|
|
|
|
reason="replaced by CoverDeviceClass enum",
|
|
|
|
constant=re.compile(r"^DEVICE_CLASS_(\w*)$"),
|
|
|
|
),
|
|
|
|
],
|
|
|
|
"homeassistant.components.humidifier": [
|
|
|
|
ObsoleteImportMatch(
|
|
|
|
reason="replaced by HumidifierDeviceClass enum",
|
|
|
|
constant=re.compile(r"^DEVICE_CLASS_(\w*)$"),
|
|
|
|
),
|
|
|
|
],
|
|
|
|
"homeassistant.components.humidifier.const": [
|
|
|
|
ObsoleteImportMatch(
|
|
|
|
reason="replaced by HumidifierDeviceClass enum",
|
|
|
|
constant=re.compile(r"^DEVICE_CLASS_(\w*)$"),
|
|
|
|
),
|
|
|
|
],
|
|
|
|
"homeassistant.components.media_player": [
|
|
|
|
ObsoleteImportMatch(
|
|
|
|
reason="replaced by MediaPlayerDeviceClass enum",
|
|
|
|
constant=re.compile(r"^DEVICE_CLASS_(\w*)$"),
|
|
|
|
),
|
|
|
|
],
|
2022-04-04 09:45:53 +00:00
|
|
|
"homeassistant.components.sensor": [
|
2022-04-04 22:03:28 +00:00
|
|
|
ObsoleteImportMatch(
|
|
|
|
reason="replaced by SensorDeviceClass enum",
|
|
|
|
constant=re.compile(r"^DEVICE_CLASS_(\w*)$"),
|
|
|
|
),
|
2022-04-04 09:45:53 +00:00
|
|
|
ObsoleteImportMatch(
|
|
|
|
reason="replaced by SensorStateClass enum",
|
|
|
|
constant=re.compile(r"^STATE_CLASS_(\w*)$"),
|
|
|
|
),
|
|
|
|
],
|
2022-04-04 17:48:14 +00:00
|
|
|
"homeassistant.components.switch": [
|
|
|
|
ObsoleteImportMatch(
|
|
|
|
reason="replaced by SwitchDeviceClass enum",
|
|
|
|
constant=re.compile(r"^DEVICE_CLASS_(\w*)$"),
|
|
|
|
),
|
|
|
|
],
|
|
|
|
"homeassistant.config_entries": [
|
|
|
|
ObsoleteImportMatch(
|
|
|
|
reason="replaced by ConfigEntryDisabler enum",
|
|
|
|
constant=re.compile(r"^DISABLED_(\w*)$"),
|
|
|
|
),
|
|
|
|
],
|
|
|
|
"homeassistant.const": [
|
2022-04-04 22:03:28 +00:00
|
|
|
ObsoleteImportMatch(
|
|
|
|
reason="replaced by SensorDeviceClass enum",
|
|
|
|
constant=re.compile(r"^DEVICE_CLASS_(\w*)$"),
|
|
|
|
),
|
2022-04-04 17:48:14 +00:00
|
|
|
ObsoleteImportMatch(
|
|
|
|
reason="replaced by EntityCategory enum",
|
|
|
|
constant=re.compile(r"^(ENTITY_CATEGORY_(\w*))|(ENTITY_CATEGORIES)$"),
|
|
|
|
),
|
|
|
|
],
|
|
|
|
"homeassistant.core": [
|
|
|
|
ObsoleteImportMatch(
|
|
|
|
reason="replaced by ConfigSource enum",
|
|
|
|
constant=re.compile(r"^SOURCE_(\w*)$"),
|
|
|
|
),
|
|
|
|
],
|
|
|
|
"homeassistant.helpers.device_registry": [
|
|
|
|
ObsoleteImportMatch(
|
|
|
|
reason="replaced by DeviceEntryDisabler enum",
|
|
|
|
constant=re.compile(r"^DISABLED_(\w*)$"),
|
|
|
|
),
|
|
|
|
],
|
2022-04-04 09:45:53 +00:00
|
|
|
}
|
|
|
|
|
|
|
|
|
2021-05-22 08:15:30 +00:00
|
|
|
class HassImportsFormatChecker(BaseChecker): # type: ignore[misc]
|
|
|
|
"""Checker for imports."""
|
|
|
|
|
|
|
|
__implements__ = IAstroidChecker
|
|
|
|
|
|
|
|
name = "hass_imports"
|
|
|
|
priority = -1
|
|
|
|
msgs = {
|
|
|
|
"W0011": (
|
|
|
|
"Relative import should be used",
|
|
|
|
"hass-relative-import",
|
|
|
|
"Used when absolute import should be replaced with relative import",
|
|
|
|
),
|
2022-04-04 09:45:53 +00:00
|
|
|
"W0012": (
|
|
|
|
"%s is deprecated, %s",
|
|
|
|
"hass-deprecated-import",
|
|
|
|
"Used when import is deprecated",
|
|
|
|
),
|
2021-05-22 08:15:30 +00:00
|
|
|
}
|
|
|
|
options = ()
|
|
|
|
|
|
|
|
def __init__(self, linter: PyLinter | None = None) -> None:
|
|
|
|
super().__init__(linter)
|
2021-12-23 19:14:47 +00:00
|
|
|
self.current_package: str | None = None
|
2021-05-22 08:15:30 +00:00
|
|
|
|
|
|
|
def visit_module(self, node: Module) -> None:
|
2021-12-23 19:14:47 +00:00
|
|
|
"""Called when a Module node is visited."""
|
|
|
|
if node.package:
|
|
|
|
self.current_package = node.name
|
|
|
|
else:
|
|
|
|
# Strip name of the current module
|
|
|
|
self.current_package = node.name[: node.name.rfind(".")]
|
2021-05-22 08:15:30 +00:00
|
|
|
|
|
|
|
def visit_import(self, node: Import) -> None:
|
|
|
|
"""Called when a Import node is visited."""
|
|
|
|
for module, _alias in node.names:
|
2021-12-23 19:14:47 +00:00
|
|
|
if module.startswith(f"{self.current_package}."):
|
2021-05-22 08:15:30 +00:00
|
|
|
self.add_message("hass-relative-import", node=node)
|
|
|
|
|
|
|
|
def visit_importfrom(self, node: ImportFrom) -> None:
|
|
|
|
"""Called when a ImportFrom node is visited."""
|
|
|
|
if node.level is not None:
|
|
|
|
return
|
2021-12-23 19:14:47 +00:00
|
|
|
if node.modname == self.current_package or node.modname.startswith(
|
|
|
|
f"{self.current_package}."
|
2021-05-22 08:15:30 +00:00
|
|
|
):
|
|
|
|
self.add_message("hass-relative-import", node=node)
|
2022-04-04 09:45:53 +00:00
|
|
|
elif obsolete_imports := _OBSOLETE_IMPORT.get(node.modname):
|
|
|
|
for name_tuple in node.names:
|
|
|
|
for obsolete_import in obsolete_imports:
|
|
|
|
if import_match := obsolete_import.constant.match(name_tuple[0]):
|
|
|
|
self.add_message(
|
|
|
|
"hass-deprecated-import",
|
|
|
|
node=node,
|
|
|
|
args=(import_match.string, obsolete_import.reason),
|
|
|
|
)
|
2021-05-22 08:15:30 +00:00
|
|
|
|
|
|
|
|
|
|
|
def register(linter: PyLinter) -> None:
|
|
|
|
"""Register the checker."""
|
|
|
|
linter.register_checker(HassImportsFormatChecker(linter))
|