diff --git a/.pre-commit-config.yaml b/.pre-commit-config.yaml index 18314f6c..d5e13de2 100644 --- a/.pre-commit-config.yaml +++ b/.pre-commit-config.yaml @@ -22,18 +22,13 @@ repos: - id: requirements-txt-fixer - id: trailing-whitespace - - repo: https://github.com/adamchainz/blacken-docs - rev: "1.20.0" - hooks: - - id: blacken-docs - additional_dependencies: [black==24.*] - - repo: https://github.com/astral-sh/ruff-pre-commit - rev: "v0.15.22" + rev: "v0.16.0" hooks: - id: ruff-check args: ["--fix"] - id: ruff-format + types_or: [python, pyi, jupyter, markdown, pyproject] - repo: https://github.com/pre-commit/pygrep-hooks rev: "v1.10.0" diff --git a/README.md b/README.md index aabd8488..1ddc6a63 100644 --- a/README.md +++ b/README.md @@ -388,7 +388,7 @@ Will not show up if using lefthook instead of pre-commit/prek. - [`PC100`](https://learn.scientific-python.org/development/guides/style#PC100): Has pre-commit-hooks - [`PC110`](https://learn.scientific-python.org/development/guides/style#PC110): Uses black or ruff-format -- [`PC111`](https://learn.scientific-python.org/development/guides/style#PC111): Uses blacken-docs +- [`PC111`](https://learn.scientific-python.org/development/guides/style#PC111): Formats code in docs (ruff-format or blacken-docs) - [`PC140`](https://learn.scientific-python.org/development/guides/style#PC140): Uses a type checker - [`PC160`](https://learn.scientific-python.org/development/guides/style#PC160): Uses a spell checker - [`PC170`](https://learn.scientific-python.org/development/guides/style#PC170): Uses PyGrep hooks (only needed if rST present) diff --git a/docs/guides/style.md b/docs/guides/style.md index 8236d808..fe3da29c 100644 --- a/docs/guides/style.md +++ b/docs/guides/style.md @@ -148,7 +148,7 @@ Here is the snippet to add the formatter to your `.pre-commit-config.yml` ```yaml - repo: https://github.com/astral-sh/ruff-pre-commit - rev: "v0.15.22" + rev: "v0.16.0" hooks: # id: ruff-check would go here if using both - id: ruff-format @@ -205,11 +205,31 @@ option! Most of the time, you can find a way to make the Blacked code look better by rewriting your code; factor out long unreadable portions into a variable, avoid writing matrices as 1D lists, etc. -:::{dropdown} Documentation / README snippets support -{rr}`PC111` If you want Black used in your documentation, you can use -blacken-docs. This can even catch syntax errors in code snippets! It supports -markdown and restructured text. Note that because black is in -`additional_dependencies`, you'll have to keep it up to date manually. +::::::{dropdown} Documentation / README snippets support +{rr}`PC111` A formatter can keep the Python snippets in your docs tidy, and +even catch syntax errors in them. + +:::::{tab-set} +::::{tab-item} Ruff-format +Ruff (0.16+) formats Python code blocks in Markdown, so a single tool covers +your code and your docs. Add `markdown` to the `ruff-format` hook's `types_or`: + +```yaml +- repo: https://github.com/astral-sh/ruff-pre-commit + rev: "v0.16.0" + hooks: + - id: ruff-format + types_or: [python, pyi, jupyter, markdown, pyproject] +``` + +The hook only sees the file types you list, so `markdown` is needed until it is +part of the hook's default. + +:::: +::::{tab-item} blacken-docs +Use blacken-docs if you use Black, or want reStructuredText snippets formatted +too. Note that because black is in `additional_dependencies`, you'll have to +keep it up to date manually. ```yaml - repo: https://github.com/adamchainz/blacken-docs @@ -219,7 +239,9 @@ markdown and restructured text. Note that because black is in additional_dependencies: [black==24.*] ``` -::: +:::: +::::: +:::::: ## Ruff @@ -234,7 +256,7 @@ pre-commit hook. ```yaml - repo: https://github.com/astral-sh/ruff-pre-commit - rev: "v0.15.22" + rev: "v0.16.0" hooks: - id: ruff-check args: ["--fix", "--show-fixes"] diff --git a/docs/principles/design.md b/docs/principles/design.md index 5ac88779..0a0a7eef 100644 --- a/docs/principles/design.md +++ b/docs/principles/design.md @@ -213,7 +213,7 @@ def get_image( *, normalize: bool = True, beginning: int = 0, - end: int | None = None + end: int | None = None, ) -> np.ndarray: ... ``` diff --git a/pyproject.toml b/pyproject.toml index 99c9ec74..4a708844 100644 --- a/pyproject.toml +++ b/pyproject.toml @@ -180,6 +180,7 @@ ignore = [ "A002", # Okay for arguments to shadow builtins "A004", # Okay for imports to shadow builtins (like print) "COM812", # Trailing commas teach the formatter + "CPY001", # No copyright notices "D", # Docstrings "E501", # Line too long "FBT", # Boolean args fine diff --git a/src/sp_repo_review/checks/precommit.py b/src/sp_repo_review/checks/precommit.py index 79c70ecc..fb0b14b6 100644 --- a/src/sp_repo_review/checks/precommit.py +++ b/src/sp_repo_review/checks/precommit.py @@ -24,6 +24,19 @@ def precommit(root: Traversable) -> dict[str, Any]: return {} +def _formats_markdown(hook: dict[str, Any]) -> bool: + "A `ruff-format` hook that Markdown files can reach." + match hook: + case {"id": "ruff-format", "types_or": types}: + return "markdown" in types + # No `types_or` means Ruff's own default, which will include + # Markdown in a future release. + case {"id": "ruff-format"}: + return True + case _: + return False + + class PreCommit: family = "pre-commit" requires = {"PY006"} @@ -45,7 +58,7 @@ def describe(self) -> str: return "one of " + ", ".join(msgs) @classmethod - def check(cls, precommit: dict[str, Any]) -> bool | None | str: + def check(cls, precommit: dict[str, Any]) -> bool | str | None: "Must have {self.describe} in `.pre-commit-config.yaml`" assert cls.repos, f"{cls.__name__} must have a repo, invalid class definition" for repo_item in precommit.get("repos", {}): @@ -89,14 +102,40 @@ class PC110(PreCommit): class PC111(PreCommit): - "Uses blacken-docs" + "Formats code in docs (ruff-format or blacken-docs)" requires = {"PY006", "PC110"} - repos = {"https://github.com/adamchainz/blacken-docs"} + repos = { + "https://github.com/astral-sh/ruff-pre-commit", + "https://github.com/adamchainz/blacken-docs", + } renamed = { "https://github.com/asottile/blacken-docs": "https://github.com/adamchainz/blacken-docs" } + @classmethod + def check(cls, precommit: dict[str, Any]) -> bool | str | None: + """ + Add `blacken-docs`, or (Ruff 0.16+) let Markdown files reach the + `ruff-format` hook in `.pre-commit-config.yaml`. Until the hook formats + Markdown by default, that means `types_or: [python, pyi, jupyter, + markdown, pyproject]`. + """ + for repo_item in precommit.get("repos", {}): + match repo_item.get("repo", "").lower(), repo_item.get("hooks", []): + case "https://github.com/adamchainz/blacken-docs", _: + return True + case "https://github.com/astral-sh/ruff-pre-commit", hooks if any( + _formats_markdown(hook) for hook in hooks + ): + return True + case repo, _ if repo in cls.renamed: + return ( + f"Use `{cls.renamed[repo]}` instead of `{repo}` in " + "`.pre-commit-config.yaml`" + ) + return False + class PC190(PreCommit): "Uses a linter (Ruff/Flake8)" diff --git a/tests/test_precommit.py b/tests/test_precommit.py index fd3ef952..c4a8217c 100644 --- a/tests/test_precommit.py +++ b/tests/test_precommit.py @@ -66,6 +66,48 @@ def test_pc111(): assert compute_check("PC111", precommit=precommit).result +def test_pc111_ruff_markdown(): + precommit = yaml.safe_load(""" + repos: + - repo: https://github.com/astral-sh/ruff-pre-commit + hooks: + - id: ruff-format + types_or: [python, pyi, jupyter, markdown, pyproject] + """) + assert compute_check("PC111", precommit=precommit).result + + +def test_pc111_ruff_default_types(): + precommit = yaml.safe_load(""" + repos: + - repo: https://github.com/astral-sh/ruff-pre-commit + hooks: + - id: ruff-format + """) + assert compute_check("PC111", precommit=precommit).result + + +def test_pc111_ruff_markdown_excluded(): + precommit = yaml.safe_load(""" + repos: + - repo: https://github.com/astral-sh/ruff-pre-commit + hooks: + - id: ruff-format + types_or: [python, pyi, jupyter] + """) + assert not compute_check("PC111", precommit=precommit).result + + +def test_pc111_ruff_check_only(): + precommit = yaml.safe_load(""" + repos: + - repo: https://github.com/astral-sh/ruff-pre-commit + hooks: + - id: ruff-check + """) + assert not compute_check("PC111", precommit=precommit).result + + def test_pc111_rename(): precommit = yaml.safe_load(""" repos: diff --git a/{{cookiecutter.project_name}}/.pre-commit-config.yaml b/{{cookiecutter.project_name}}/.pre-commit-config.yaml index 39b62e12..c13d2831 100644 --- a/{{cookiecutter.project_name}}/.pre-commit-config.yaml +++ b/{{cookiecutter.project_name}}/.pre-commit-config.yaml @@ -6,12 +6,6 @@ ci: exclude: ^.cruft.json|.copier-answers.yml$ repos: - - repo: https://github.com/adamchainz/blacken-docs - rev: "1.20.0" - hooks: - - id: blacken-docs - additional_dependencies: [black==26.*] - - repo: https://github.com/pre-commit/pre-commit-hooks rev: "v6.0.0" hooks: @@ -43,11 +37,12 @@ repos: args: [--prose-wrap=always] - repo: https://github.com/astral-sh/ruff-pre-commit - rev: "v0.15.22" + rev: "v0.16.0" hooks: - id: ruff-check args: ["--fix"] - id: ruff-format + types_or: [python, pyi, jupyter, markdown, pyproject] {%- if cookiecutter.backend in ["pybind11", "skbuild", "mesonpy"] %}