diff --git a/.cspell.json b/.cspell.json index 611483e1..b29c863c 100644 --- a/.cspell.json +++ b/.cspell.json @@ -92,6 +92,7 @@ "prettierrc", "pyenv", "pyright", + "pyrightconfig", "pyupgrade", "redeboer", "repos", diff --git a/.vscode/settings.json b/.vscode/settings.json index 1dd0d7b7..82f8fb63 100644 --- a/.vscode/settings.json +++ b/.vscode/settings.json @@ -71,7 +71,9 @@ "src/compwa_policy/.github/workflows/release-drafter.yml": true, "src/compwa_policy/.template/.gitpod.yml": true, "src/compwa_policy/.template/.prettierrc": true, - "tests/**/.pre-commit-config*.yaml": true + "tests/**/.pre-commit-config*.yaml": true, + "tests/**/pyproject*.toml": true, + "tests/**/pyrightconfig*.json": true }, "telemetry.telemetryLevel": "off", "yaml.schemas": { diff --git a/src/compwa_policy/check_dev_files/__init__.py b/src/compwa_policy/check_dev_files/__init__.py index 72a6dc53..4c01264f 100644 --- a/src/compwa_policy/check_dev_files/__init__.py +++ b/src/compwa_policy/check_dev_files/__init__.py @@ -93,7 +93,7 @@ def main(argv: Sequence[str] | None = None) -> int: github_pages=args.github_pages, ) do(mypy.main) - do(pyright.main) + do(pyright.main, precommit_config) do(pytest.main) do(pyupgrade.main, precommit_config, args.no_ruff) if not args.no_ruff: diff --git a/src/compwa_policy/check_dev_files/pyright.py b/src/compwa_policy/check_dev_files/pyright.py index f3f9dc4b..95e8c184 100644 --- a/src/compwa_policy/check_dev_files/pyright.py +++ b/src/compwa_policy/check_dev_files/pyright.py @@ -4,19 +4,34 @@ import json import os +from pathlib import Path +from typing import TYPE_CHECKING -from compwa_policy.utilities.pyproject import ModifiablePyproject, complies_with_subset +from compwa_policy.utilities.precommit.struct import Hook, Repo +from compwa_policy.utilities.pyproject import ( + ModifiablePyproject, + Pyproject, + complies_with_subset, +) from compwa_policy.utilities.toml import to_toml_array +if TYPE_CHECKING: + from compwa_policy.utilities.precommit import ModifiablePrecommit -def main() -> None: + +def main(precommit: ModifiablePrecommit) -> None: with ModifiablePyproject.load() as pyproject: _merge_config_into_pyproject(pyproject) + _update_precommit(precommit, pyproject) _update_settings(pyproject) -def _merge_config_into_pyproject(pyproject: ModifiablePyproject) -> None: - old_config_path = "pyrightconfig.json" # cspell:ignore pyrightconfig +def _merge_config_into_pyproject( + pyproject: ModifiablePyproject, + path: Path = Path("pyrightconfig.json"), + remove: bool = True, +) -> None: + old_config_path = path if not os.path.exists(old_config_path): return with open(old_config_path) as stream: @@ -26,14 +41,25 @@ def _merge_config_into_pyproject(pyproject: ModifiablePyproject) -> None: existing_config[key] = to_toml_array(sorted(value)) tool_table = pyproject.get_table("tool.pyright", create=True) tool_table.update(existing_config) - os.remove(old_config_path) + if remove: + os.remove(old_config_path) msg = f"Imported pyright configuration from {old_config_path}" pyproject.append_to_changelog(msg) +def _update_precommit(precommit: ModifiablePrecommit, pyproject: Pyproject) -> None: + if not __has_pyright(pyproject): + return + repo = Repo( + repo="https://github.com/ComPWA/mirrors-pyright", + rev="", + hooks=[Hook(id="pyright")], + ) + precommit.update_single_hook_repo(repo) + + def _update_settings(pyproject: ModifiablePyproject) -> None: - table_key = "tool.pyright" - if not pyproject.has_table(table_key): + if not __has_pyright(pyproject): return pyright_settings = pyproject.get_table("tool.pyright") minimal_settings = { @@ -43,3 +69,8 @@ def _update_settings(pyproject: ModifiablePyproject) -> None: pyright_settings.update(minimal_settings) msg = "Updated pyright configuration" pyproject.append_to_changelog(msg) + + +def __has_pyright(pyproject: Pyproject) -> bool: + table_key = "tool.pyright" + return pyproject.has_table(table_key) diff --git a/tests/check_dev_files/pyright/.pre-commit-config-bad.yaml b/tests/check_dev_files/pyright/.pre-commit-config-bad.yaml new file mode 100644 index 00000000..824fae7f --- /dev/null +++ b/tests/check_dev_files/pyright/.pre-commit-config-bad.yaml @@ -0,0 +1,4 @@ +repos: + - repo: meta + hooks: + - id: check-hooks-apply diff --git a/tests/check_dev_files/pyright/.pre-commit-config-good.yaml b/tests/check_dev_files/pyright/.pre-commit-config-good.yaml new file mode 100644 index 00000000..a3910eb2 --- /dev/null +++ b/tests/check_dev_files/pyright/.pre-commit-config-good.yaml @@ -0,0 +1,9 @@ +repos: + - repo: meta + hooks: + - id: check-hooks-apply + + - repo: https://github.com/ComPWA/mirrors-pyright + rev: PLEASE-UPDATE + hooks: + - id: pyright diff --git a/tests/check_dev_files/pyright/__init__.py b/tests/check_dev_files/pyright/__init__.py new file mode 100644 index 00000000..e69de29b diff --git a/tests/check_dev_files/pyright/pyproject-bad.toml b/tests/check_dev_files/pyright/pyproject-bad.toml new file mode 100644 index 00000000..af0ff64e --- /dev/null +++ b/tests/check_dev_files/pyright/pyproject-bad.toml @@ -0,0 +1,3 @@ +[tool.pyright] +include = ["**/*.py"] +reportUnusedImport = true diff --git a/tests/check_dev_files/pyright/pyrightconfig.json b/tests/check_dev_files/pyright/pyrightconfig.json new file mode 100644 index 00000000..e2b2189f --- /dev/null +++ b/tests/check_dev_files/pyright/pyrightconfig.json @@ -0,0 +1,7 @@ +{ + "include": ["src/**/*.py"], + "exclude": ["tests/**/*.py"], + "pythonVersion": "3.9", + "reportMissingTypeStubs": false, + "reportMissingImports": true +} diff --git a/tests/check_dev_files/pyright/test_pyright.py b/tests/check_dev_files/pyright/test_pyright.py new file mode 100644 index 00000000..4665727f --- /dev/null +++ b/tests/check_dev_files/pyright/test_pyright.py @@ -0,0 +1,74 @@ +import io +import re +from pathlib import Path +from textwrap import dedent + +import pytest + +from compwa_policy.check_dev_files.pyright import ( + _merge_config_into_pyproject, + _update_precommit, + _update_settings, +) +from compwa_policy.errors import PrecommitError +from compwa_policy.utilities.precommit import ModifiablePrecommit +from compwa_policy.utilities.pyproject import ModifiablePyproject, Pyproject + + +@pytest.fixture +def this_dir() -> Path: + return Path(__file__).parent + + +def test_merge_config_into_pyproject(this_dir: Path): + input_stream = io.StringIO() + old_config_path = this_dir / "pyrightconfig.json" + with pytest.raises( + PrecommitError, + match=re.escape(f"Imported pyright configuration from {old_config_path}"), + ), ModifiablePyproject.load(input_stream) as pyproject: + _merge_config_into_pyproject(pyproject, old_config_path, remove=False) + + result = input_stream.getvalue() + expected_result = dedent(""" + [tool.pyright] + include = ["src/**/*.py"] + exclude = ["tests/**/*.py"] + pythonVersion = "3.9" + reportMissingTypeStubs = false + reportMissingImports = true + """) + assert result.strip() == expected_result.strip() + + +def test_update_precommit(this_dir: Path): + pyproject = Pyproject.load(this_dir / "pyproject-bad.toml") + with open(this_dir / ".pre-commit-config-bad.yaml") as stream: + input_stream = io.StringIO(stream.read()) + with pytest.raises(PrecommitError), ModifiablePrecommit.load( + input_stream + ) as precommit: + _update_precommit(precommit, pyproject) + + result = input_stream.getvalue() + with open(this_dir / ".pre-commit-config-good.yaml") as stream: + expected_result = stream.read() + assert result.strip() == expected_result.strip() + + +def test_update_settings(this_dir: Path): + with open(this_dir / "pyproject-bad.toml") as stream: + input_stream = io.StringIO(stream.read()) + with pytest.raises( + PrecommitError, match=r"Updated pyright configuration" + ), ModifiablePyproject.load(input_stream) as pyproject: + _update_settings(pyproject) + + result = input_stream.getvalue() + expected_result = dedent(""" + [tool.pyright] + include = ["**/*.py"] + reportUnusedImport = true + typeCheckingMode = "strict" + """) + assert result.strip() == expected_result.strip()