Skip to content

Commit

Permalink
Format docs with ruff formatter (#13087)
Browse files Browse the repository at this point in the history
## 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.
  • Loading branch information
calumy authored Aug 26, 2024
1 parent a822fd6 commit ab3648c
Show file tree
Hide file tree
Showing 3 changed files with 45 additions and 27 deletions.
Original file line number Diff line number Diff line change
Expand Up @@ -57,7 +57,7 @@ impl AlwaysFixableViolation for InvalidCharacterBackspace {
///
/// Use instead:
/// ```python
/// x = "\x1A"
/// x = "\x1a"
/// ```
#[violation]
pub struct InvalidCharacterSub;
Expand Down Expand Up @@ -90,7 +90,7 @@ impl AlwaysFixableViolation for InvalidCharacterSub {
///
/// Use instead:
/// ```python
/// x = "\x1B"
/// x = "\x1b"
/// ```
#[violation]
pub struct InvalidCharacterEsc;
Expand Down Expand Up @@ -155,7 +155,7 @@ impl AlwaysFixableViolation for InvalidCharacterNul {
///
/// Use instead:
/// ```python
/// x = "Dear Sir\u200B/\u200BMadam" # zero width space
/// x = "Dear Sir\u200b/\u200bMadam" # zero width space
/// ```
#[violation]
pub struct InvalidCharacterZeroWidthSpace;
Expand Down
2 changes: 1 addition & 1 deletion docs/requirements.txt
Original file line number Diff line number Diff line change
@@ -1,5 +1,5 @@
PyYAML==6.0.2
black==24.8.0
ruff==0.6.2
mkdocs==1.6.0
mkdocs-material==9.1.18
mkdocs-redirects==1.2.1
Expand Down
64 changes: 41 additions & 23 deletions scripts/check_docs_formatted.py
Original file line number Diff line number Diff line change
Expand Up @@ -6,19 +6,15 @@
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>.*?)"
Expand Down Expand Up @@ -52,6 +48,7 @@
"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",
Expand Down Expand Up @@ -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"]}'
Expand All @@ -140,12 +164,7 @@ def _snipped_match(match: Match[str]) -> 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.
Expand All @@ -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:
Expand Down Expand Up @@ -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

Expand All @@ -264,14 +279,15 @@ 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:
continue

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:
Expand All @@ -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


Expand Down

0 comments on commit ab3648c

Please sign in to comment.