diff --git a/.strict-typing b/.strict-typing index d45fe269638..4778893bfc2 100644 --- a/.strict-typing +++ b/.strict-typing @@ -5,14 +5,10 @@ # Strict typing is enabled by default for core files. # Add it here to add 'disallow_any_generics'. # --- Only for core file! --- -homeassistant.exceptions -homeassistant.core -homeassistant.loader -homeassistant.requirements -homeassistant.runner -homeassistant.setup homeassistant.auth.auth_store homeassistant.auth.providers.* +homeassistant.core +homeassistant.exceptions homeassistant.helpers.area_registry homeassistant.helpers.condition homeassistant.helpers.debounce @@ -29,6 +25,10 @@ homeassistant.helpers.script_variables homeassistant.helpers.singleton homeassistant.helpers.sun homeassistant.helpers.translation +homeassistant.loader +homeassistant.requirements +homeassistant.runner +homeassistant.setup homeassistant.util.async_ homeassistant.util.color homeassistant.util.decorator @@ -79,9 +79,9 @@ homeassistant.components.button.* homeassistant.components.calendar.* homeassistant.components.camera.* homeassistant.components.canary.* -homeassistant.components.cover.* homeassistant.components.clickatell.* homeassistant.components.clicksend.* +homeassistant.components.cover.* homeassistant.components.cpuspeed.* homeassistant.components.crownstone.* homeassistant.components.deconz.* @@ -215,9 +215,9 @@ homeassistant.components.prusalink.* homeassistant.components.pure_energie.* homeassistant.components.pvoutput.* homeassistant.components.qnap_qsw.* +homeassistant.components.radarr.* homeassistant.components.rainmachine.* homeassistant.components.rdw.* -homeassistant.components.radarr.* homeassistant.components.recollect_waste.* homeassistant.components.recorder.* homeassistant.components.remote.* diff --git a/mypy.ini b/mypy.ini index e30a78dab8c..326bbc5ed36 100644 --- a/mypy.ini +++ b/mypy.ini @@ -28,28 +28,16 @@ warn_unreachable = true [mypy-homeassistant.*] no_implicit_reexport = true -[mypy-homeassistant.exceptions] +[mypy-homeassistant.auth.auth_store] +disallow_any_generics = true + +[mypy-homeassistant.auth.providers.*] disallow_any_generics = true [mypy-homeassistant.core] disallow_any_generics = true -[mypy-homeassistant.loader] -disallow_any_generics = true - -[mypy-homeassistant.requirements] -disallow_any_generics = true - -[mypy-homeassistant.runner] -disallow_any_generics = true - -[mypy-homeassistant.setup] -disallow_any_generics = true - -[mypy-homeassistant.auth.auth_store] -disallow_any_generics = true - -[mypy-homeassistant.auth.providers.*] +[mypy-homeassistant.exceptions] disallow_any_generics = true [mypy-homeassistant.helpers.area_registry] @@ -100,6 +88,18 @@ disallow_any_generics = true [mypy-homeassistant.helpers.translation] disallow_any_generics = true +[mypy-homeassistant.loader] +disallow_any_generics = true + +[mypy-homeassistant.requirements] +disallow_any_generics = true + +[mypy-homeassistant.runner] +disallow_any_generics = true + +[mypy-homeassistant.setup] +disallow_any_generics = true + [mypy-homeassistant.util.async_] disallow_any_generics = true @@ -543,16 +543,6 @@ disallow_untyped_defs = true warn_return_any = true warn_unreachable = true -[mypy-homeassistant.components.cover.*] -check_untyped_defs = true -disallow_incomplete_defs = true -disallow_subclassing_any = true -disallow_untyped_calls = true -disallow_untyped_decorators = true -disallow_untyped_defs = true -warn_return_any = true -warn_unreachable = true - [mypy-homeassistant.components.clickatell.*] check_untyped_defs = true disallow_incomplete_defs = true @@ -573,6 +563,16 @@ disallow_untyped_defs = true warn_return_any = true warn_unreachable = true +[mypy-homeassistant.components.cover.*] +check_untyped_defs = true +disallow_incomplete_defs = true +disallow_subclassing_any = true +disallow_untyped_calls = true +disallow_untyped_decorators = true +disallow_untyped_defs = true +warn_return_any = true +warn_unreachable = true + [mypy-homeassistant.components.cpuspeed.*] check_untyped_defs = true disallow_incomplete_defs = true @@ -1903,6 +1903,16 @@ disallow_untyped_defs = true warn_return_any = true warn_unreachable = true +[mypy-homeassistant.components.radarr.*] +check_untyped_defs = true +disallow_incomplete_defs = true +disallow_subclassing_any = true +disallow_untyped_calls = true +disallow_untyped_decorators = true +disallow_untyped_defs = true +warn_return_any = true +warn_unreachable = true + [mypy-homeassistant.components.rainmachine.*] check_untyped_defs = true disallow_incomplete_defs = true @@ -1923,16 +1933,6 @@ disallow_untyped_defs = true warn_return_any = true warn_unreachable = true -[mypy-homeassistant.components.radarr.*] -check_untyped_defs = true -disallow_incomplete_defs = true -disallow_subclassing_any = true -disallow_untyped_calls = true -disallow_untyped_decorators = true -disallow_untyped_defs = true -warn_return_any = true -warn_unreachable = true - [mypy-homeassistant.components.recollect_waste.*] check_untyped_defs = true disallow_incomplete_defs = true diff --git a/script/hassfest/mypy_config.py b/script/hassfest/mypy_config.py index 99e07bcf118..e6886f2f79f 100644 --- a/script/hassfest/mypy_config.py +++ b/script/hassfest/mypy_config.py @@ -1,6 +1,7 @@ """Generate mypy config.""" from __future__ import annotations +from collections.abc import Iterable import configparser import io import os @@ -90,18 +91,48 @@ def _strict_module_in_ignore_list( return None -def generate_and_validate(config: Config) -> str: +def _sort_within_sections(line_iter: Iterable[str]) -> Iterable[str]: + """ + Sort lines within sections. + + Sections are defined as anything not delimited by a blank line + or an octothorpe-prefixed comment line. + """ + section: list[str] = [] + for line in line_iter: + if line.startswith("#") or not line.strip(): + yield from sorted(section) + section.clear() + yield line + continue + section.append(line) + yield from sorted(section) + + +def _get_strict_typing_path(config: Config) -> Path: + return config.root / ".strict-typing" + + +def _get_mypy_ini_path(config: Config) -> Path: + return config.root / "mypy.ini" + + +def _generate_and_validate_strict_typing(config: Config) -> str: + """Validate and generate strict_typing.""" + lines = [ + line.strip() + for line in _get_strict_typing_path(config).read_text().splitlines() + ] + return "\n".join(_sort_within_sections(lines)) + "\n" + + +def _generate_and_validate_mypy_config(config: Config) -> str: """Validate and generate mypy config.""" - config_path = config.root / ".strict-typing" - - with config_path.open() as fp: - lines = fp.readlines() - # Filter empty and commented lines. parsed_modules: list[str] = [ line.strip() - for line in lines + for line in config.cache["strict_typing"].splitlines() if line.strip() != "" and not line.startswith("#") ] @@ -210,28 +241,36 @@ def generate_and_validate(config: Config) -> str: with io.StringIO() as fp: mypy_config.write(fp) fp.seek(0) - return HEADER + fp.read().strip() + return f"{HEADER}{fp.read().strip()}\n" def validate(integrations: dict[str, Integration], config: Config) -> None: - """Validate mypy config.""" - config_path = config.root / "mypy.ini" - config.cache["mypy_config"] = content = generate_and_validate(config) + """Validate strict_typing and mypy config.""" + strict_typing_content = _generate_and_validate_strict_typing(config) + config.cache["strict_typing"] = strict_typing_content + + mypy_content = _generate_and_validate_mypy_config(config) + config.cache["mypy_config"] = mypy_content if any(err.plugin == "mypy_config" for err in config.errors): return - with open(str(config_path)) as fp: - if fp.read().strip() != content: - config.add_error( - "mypy_config", - "File mypy.ini is not up to date. Run python3 -m script.hassfest", - fixable=True, - ) + if _get_strict_typing_path(config).read_text() != strict_typing_content: + config.add_error( + "mypy_config", + "File .strict_typing is not up to date. Run python3 -m script.hassfest", + fixable=True, + ) + + if _get_mypy_ini_path(config).read_text() != mypy_content: + config.add_error( + "mypy_config", + "File mypy.ini is not up to date. Run python3 -m script.hassfest", + fixable=True, + ) def generate(integrations: dict[str, Integration], config: Config) -> None: - """Generate mypy config.""" - config_path = config.root / "mypy.ini" - with open(str(config_path), "w") as fp: - fp.write(f"{config.cache['mypy_config']}\n") + """Generate strict_typing and mypy config.""" + _get_mypy_ini_path(config).write_text(config.cache["mypy_config"]) + _get_strict_typing_path(config).write_text(config.cache["strict_typing"])