Refactor pylint plugins to use match statements (#168894)

This commit is contained in:
Marc Mueller
2026-04-24 12:25:16 +02:00
committed by GitHub
parent 76376d6b26
commit 28c3ca37b9
3 changed files with 39 additions and 68 deletions
@@ -124,34 +124,18 @@ def _check_subscript_or_call_ip(node: nodes.NodeNG) -> str | None:
avoiding false positives from local variables named ``host`` used in
attribute chains like ``host.api.mac_address``.
"""
# Subscript: data[CONF_HOST] or data["host"]
if isinstance(node, nodes.Subscript):
key = node.slice
if isinstance(key, nodes.Name) and key.name in _IP_HOST_NAMES:
return str(key.name)
if (
isinstance(key, nodes.Const)
and isinstance(key.value, str)
and key.value in _IP_HOST_NAMES
):
return key.value
# Call: data.get(CONF_HOST) or data.get("host")
if (
isinstance(node, nodes.Call)
and isinstance(node.func, nodes.Attribute)
and node.func.attrname == "get"
and node.args
):
first_arg = node.args[0]
if isinstance(first_arg, nodes.Name) and first_arg.name in _IP_HOST_NAMES:
return str(first_arg.name)
if (
isinstance(first_arg, nodes.Const)
and isinstance(first_arg.value, str)
and first_arg.value in _IP_HOST_NAMES
):
return first_arg.value
match node:
# Subscript: data[CONF_HOST] or data["host"]
case nodes.Subscript(
slice=nodes.Name(name=val) | nodes.Const(value=str(val))
) if val in _IP_HOST_NAMES:
return str(val)
# Call: data.get(CONF_HOST) or data.get("host")
case nodes.Call(
func=nodes.Attribute(attrname="get"),
args=[nodes.Name(name=val) | nodes.Const(value=str(val)), *_],
) if val in _IP_HOST_NAMES:
return str(val)
# Recurse into child nodes to catch embedded references (e.g. f-strings),
# but skip function call arguments -- a call like get_unique_id(host)
@@ -83,19 +83,14 @@ class HassEnforceConfigFlowNoPollingChecker(BaseChecker):
def _get_schema_field_name(node: nodes.Call) -> str | None:
"""Extract the field name from vol.Required(...) or vol.Optional(...)."""
if not isinstance(node.func, nodes.Attribute):
return None
if node.func.attrname not in {"Required", "Optional"}:
return None
if not node.args:
return None
first_arg = node.args[0]
if isinstance(first_arg, nodes.Const) and isinstance(first_arg.value, str):
return first_arg.value
if isinstance(first_arg, nodes.Name):
return str(first_arg.name)
return None
match node:
case nodes.Call(
func=nodes.Attribute(attrname="Required" | "Optional"),
args=[nodes.Name(name=val) | nodes.Const(value=str(val)), *_],
):
return str(val)
case _:
return None
def register(linter: PyLinter) -> None:
+19 -27
View File
@@ -81,39 +81,31 @@ class HassEnforceRuntimeDataChecker(BaseChecker):
return
# Don't flag deletion: del hass.data[DOMAIN] or hass.data[DOMAIN].pop(...)
parent = node.parent
if isinstance(parent, nodes.Delete):
return
if (
isinstance(parent, nodes.Attribute)
and parent.attrname == "pop"
and isinstance(parent.parent, nodes.Call)
):
return
match node.parent:
case nodes.Delete():
return
case nodes.Attribute(attrname="pop", parent=nodes.Call()):
return
self.add_message("hass-use-runtime-data", node=node)
def _is_hass_data_domain_access(node: nodes.Subscript) -> bool:
"""Return True if node is hass.data[DOMAIN] or self.hass.data[DOMAIN]."""
if not isinstance(node.value, nodes.Attribute):
return False
if node.value.attrname != "data":
return False
slice_node = node.slice
if not isinstance(slice_node, nodes.Name) or slice_node.name != "DOMAIN":
return False
expr = node.value.expr
if isinstance(expr, nodes.Name) and expr.name == "hass":
return True
return (
isinstance(expr, nodes.Attribute)
and expr.attrname == "hass"
and isinstance(expr.expr, nodes.Name)
and expr.expr.name == "self"
)
match node:
case nodes.Subscript(
value=nodes.Attribute(
expr=(
nodes.Name(name="hass")
| nodes.Attribute(expr=nodes.Name(name="self"), attrname="hass")
),
attrname="data",
),
slice=nodes.Name(name="DOMAIN"),
):
return True
case _:
return False
def _has_config_flow(integration: str, module: nodes.Module) -> bool: