Format docs with ruff formatter (#13087)

## Summary

Now that Ruff provides a formatter, there is no need to rely on Black to
check that the docs are formatted correctly in
`check_docs_formatted.py`. This PR swaps out Black for the Ruff
formatter and updates inconsistencies between the two.

This PR will be a precursor to another PR
([branch](https://github.com/calumy/ruff/tree/format-pyi-in-docs)),
updating the `check_docs_formatted.py` script to check for pyi files,
fixing #11568.

## Test Plan

- CI to check that the docs are formatted correctly using the updated
script.
This commit is contained in:
Calum Young 2024-08-26 16:55:10 +01:00 committed by GitHub
parent a822fd6642
commit ab3648c4c5
No known key found for this signature in database
GPG key ID: B5690EEEBB952194
3 changed files with 45 additions and 27 deletions

View file

@ -6,19 +6,15 @@ from __future__ import annotations
import argparse
import os
import re
import subprocess
import textwrap
from pathlib import Path
from re import Match
from typing import TYPE_CHECKING
import black
from black.mode import Mode, TargetVersion
from black.parsing import InvalidInput
if TYPE_CHECKING:
from collections.abc import Sequence
TARGET_VERSIONS = ["py37", "py38", "py39", "py310", "py311"]
SNIPPED_RE = re.compile(
r"(?P<before>^(?P<indent> *)```\s*python\n)"
r"(?P<code>.*?)"
@ -52,6 +48,7 @@ KNOWN_FORMATTING_VIOLATIONS = [
"missing-whitespace-around-modulo-operator",
"missing-whitespace-around-operator",
"missing-whitespace-around-parameter-equals",
"module-import-not-at-top-of-file",
"multi-line-implicit-string-concatenation",
"multiple-leading-hashes-for-block-comment",
"multiple-spaces-after-comma",
@ -119,19 +116,46 @@ class CodeBlockError(Exception):
"""A code block parse error."""
def format_str(
src: str,
black_mode: black.FileMode,
) -> tuple[str, Sequence[CodeBlockError]]:
"""Format a single docs file string."""
class InvalidInput(ValueError):
"""Raised when ruff fails to parse file."""
def format_str(code: str) -> str:
"""Format a code block with ruff by writing to a temporary file."""
# Run ruff to format the tmp file
try:
completed_process = subprocess.run(
["ruff", "format", "-"],
check=True,
capture_output=True,
text=True,
input=code,
)
except subprocess.CalledProcessError as e:
err = e.stderr
if "error: Failed to parse" in err:
raise InvalidInput(err) from e
raise NotImplementedError(
"This error has not been handled correctly, please update "
f"`check_docs_formatted.py\n\nError:\n\n{err}",
) from e
return completed_process.stdout
def format_contents(src: str) -> tuple[str, Sequence[CodeBlockError]]:
"""Format a single docs content."""
errors: list[CodeBlockError] = []
def _snipped_match(match: Match[str]) -> str:
code = textwrap.dedent(match["code"])
try:
code = black.format_str(code, mode=black_mode)
code = format_str(code)
except InvalidInput as e:
errors.append(CodeBlockError(e))
except NotImplementedError as e:
raise e
code = textwrap.indent(code, match["indent"])
return f'{match["before"]}{code}{match["after"]}'
@ -140,12 +164,7 @@ def format_str(
return src, errors
def format_file(
file: Path,
black_mode: black.FileMode,
error_known: bool,
args: argparse.Namespace,
) -> int:
def format_file(file: Path, error_known: bool, args: argparse.Namespace) -> int:
"""Check the formatting of a single docs file.
Returns the exit code for the script.
@ -170,7 +189,7 @@ def format_file(
# Remove everything after the last example
contents = contents[: contents.rfind("```")] + "```"
new_contents, errors = format_str(contents, black_mode)
new_contents, errors = format_contents(contents)
if errors and not args.skip_errors and not error_known:
for error in errors:
@ -237,10 +256,6 @@ def main(argv: Sequence[str] | None = None) -> int:
print("Please generate rules first.")
return 1
black_mode = Mode(
target_versions={TargetVersion[val.upper()] for val in TARGET_VERSIONS},
)
# Check known formatting violations and parse errors are sorted alphabetically and
# have no duplicates. This will reduce the diff when adding new violations
@ -264,6 +279,7 @@ def main(argv: Sequence[str] | None = None) -> int:
violations = 0
errors = 0
print("Checking docs formatting...")
for file in [*static_docs, *generated_docs]:
rule_name = file.name.split(".")[0]
if rule_name in KNOWN_FORMATTING_VIOLATIONS:
@ -271,7 +287,7 @@ def main(argv: Sequence[str] | None = None) -> int:
error_known = rule_name in KNOWN_PARSE_ERRORS
result = format_file(file, black_mode, error_known, args)
result = format_file(file, error_known, args)
if result == 1:
violations += 1
elif result == 2 and not error_known:
@ -286,6 +302,8 @@ def main(argv: Sequence[str] | None = None) -> int:
if violations > 0 or errors > 0:
return 1
print("All docs are formatted correctly.")
return 0