mirror of https://github.com/astral-sh/ruff
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:
parent
a822fd6642
commit
ab3648c4c5
|
|
@ -57,7 +57,7 @@ impl AlwaysFixableViolation for InvalidCharacterBackspace {
|
||||||
///
|
///
|
||||||
/// Use instead:
|
/// Use instead:
|
||||||
/// ```python
|
/// ```python
|
||||||
/// x = "\x1A"
|
/// x = "\x1a"
|
||||||
/// ```
|
/// ```
|
||||||
#[violation]
|
#[violation]
|
||||||
pub struct InvalidCharacterSub;
|
pub struct InvalidCharacterSub;
|
||||||
|
|
@ -90,7 +90,7 @@ impl AlwaysFixableViolation for InvalidCharacterSub {
|
||||||
///
|
///
|
||||||
/// Use instead:
|
/// Use instead:
|
||||||
/// ```python
|
/// ```python
|
||||||
/// x = "\x1B"
|
/// x = "\x1b"
|
||||||
/// ```
|
/// ```
|
||||||
#[violation]
|
#[violation]
|
||||||
pub struct InvalidCharacterEsc;
|
pub struct InvalidCharacterEsc;
|
||||||
|
|
@ -155,7 +155,7 @@ impl AlwaysFixableViolation for InvalidCharacterNul {
|
||||||
///
|
///
|
||||||
/// Use instead:
|
/// Use instead:
|
||||||
/// ```python
|
/// ```python
|
||||||
/// x = "Dear Sir\u200B/\u200BMadam" # zero width space
|
/// x = "Dear Sir\u200b/\u200bMadam" # zero width space
|
||||||
/// ```
|
/// ```
|
||||||
#[violation]
|
#[violation]
|
||||||
pub struct InvalidCharacterZeroWidthSpace;
|
pub struct InvalidCharacterZeroWidthSpace;
|
||||||
|
|
|
||||||
|
|
@ -1,5 +1,5 @@
|
||||||
PyYAML==6.0.2
|
PyYAML==6.0.2
|
||||||
black==24.8.0
|
ruff==0.6.2
|
||||||
mkdocs==1.6.0
|
mkdocs==1.6.0
|
||||||
mkdocs-material==9.1.18
|
mkdocs-material==9.1.18
|
||||||
mkdocs-redirects==1.2.1
|
mkdocs-redirects==1.2.1
|
||||||
|
|
|
||||||
|
|
@ -6,19 +6,15 @@ from __future__ import annotations
|
||||||
import argparse
|
import argparse
|
||||||
import os
|
import os
|
||||||
import re
|
import re
|
||||||
|
import subprocess
|
||||||
import textwrap
|
import textwrap
|
||||||
from pathlib import Path
|
from pathlib import Path
|
||||||
from re import Match
|
from re import Match
|
||||||
from typing import TYPE_CHECKING
|
from typing import TYPE_CHECKING
|
||||||
|
|
||||||
import black
|
|
||||||
from black.mode import Mode, TargetVersion
|
|
||||||
from black.parsing import InvalidInput
|
|
||||||
|
|
||||||
if TYPE_CHECKING:
|
if TYPE_CHECKING:
|
||||||
from collections.abc import Sequence
|
from collections.abc import Sequence
|
||||||
|
|
||||||
TARGET_VERSIONS = ["py37", "py38", "py39", "py310", "py311"]
|
|
||||||
SNIPPED_RE = re.compile(
|
SNIPPED_RE = re.compile(
|
||||||
r"(?P<before>^(?P<indent> *)```\s*python\n)"
|
r"(?P<before>^(?P<indent> *)```\s*python\n)"
|
||||||
r"(?P<code>.*?)"
|
r"(?P<code>.*?)"
|
||||||
|
|
@ -52,6 +48,7 @@ KNOWN_FORMATTING_VIOLATIONS = [
|
||||||
"missing-whitespace-around-modulo-operator",
|
"missing-whitespace-around-modulo-operator",
|
||||||
"missing-whitespace-around-operator",
|
"missing-whitespace-around-operator",
|
||||||
"missing-whitespace-around-parameter-equals",
|
"missing-whitespace-around-parameter-equals",
|
||||||
|
"module-import-not-at-top-of-file",
|
||||||
"multi-line-implicit-string-concatenation",
|
"multi-line-implicit-string-concatenation",
|
||||||
"multiple-leading-hashes-for-block-comment",
|
"multiple-leading-hashes-for-block-comment",
|
||||||
"multiple-spaces-after-comma",
|
"multiple-spaces-after-comma",
|
||||||
|
|
@ -119,19 +116,46 @@ class CodeBlockError(Exception):
|
||||||
"""A code block parse error."""
|
"""A code block parse error."""
|
||||||
|
|
||||||
|
|
||||||
def format_str(
|
class InvalidInput(ValueError):
|
||||||
src: str,
|
"""Raised when ruff fails to parse file."""
|
||||||
black_mode: black.FileMode,
|
|
||||||
) -> tuple[str, Sequence[CodeBlockError]]:
|
|
||||||
"""Format a single docs file string."""
|
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] = []
|
errors: list[CodeBlockError] = []
|
||||||
|
|
||||||
def _snipped_match(match: Match[str]) -> str:
|
def _snipped_match(match: Match[str]) -> str:
|
||||||
code = textwrap.dedent(match["code"])
|
code = textwrap.dedent(match["code"])
|
||||||
try:
|
try:
|
||||||
code = black.format_str(code, mode=black_mode)
|
code = format_str(code)
|
||||||
except InvalidInput as e:
|
except InvalidInput as e:
|
||||||
errors.append(CodeBlockError(e))
|
errors.append(CodeBlockError(e))
|
||||||
|
except NotImplementedError as e:
|
||||||
|
raise e
|
||||||
|
|
||||||
code = textwrap.indent(code, match["indent"])
|
code = textwrap.indent(code, match["indent"])
|
||||||
return f'{match["before"]}{code}{match["after"]}'
|
return f'{match["before"]}{code}{match["after"]}'
|
||||||
|
|
@ -140,12 +164,7 @@ def format_str(
|
||||||
return src, errors
|
return src, errors
|
||||||
|
|
||||||
|
|
||||||
def format_file(
|
def format_file(file: Path, error_known: bool, args: argparse.Namespace) -> int:
|
||||||
file: Path,
|
|
||||||
black_mode: black.FileMode,
|
|
||||||
error_known: bool,
|
|
||||||
args: argparse.Namespace,
|
|
||||||
) -> int:
|
|
||||||
"""Check the formatting of a single docs file.
|
"""Check the formatting of a single docs file.
|
||||||
|
|
||||||
Returns the exit code for the script.
|
Returns the exit code for the script.
|
||||||
|
|
@ -170,7 +189,7 @@ def format_file(
|
||||||
# Remove everything after the last example
|
# Remove everything after the last example
|
||||||
contents = contents[: contents.rfind("```")] + "```"
|
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:
|
if errors and not args.skip_errors and not error_known:
|
||||||
for error in errors:
|
for error in errors:
|
||||||
|
|
@ -237,10 +256,6 @@ def main(argv: Sequence[str] | None = None) -> int:
|
||||||
print("Please generate rules first.")
|
print("Please generate rules first.")
|
||||||
return 1
|
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
|
# Check known formatting violations and parse errors are sorted alphabetically and
|
||||||
# have no duplicates. This will reduce the diff when adding new violations
|
# 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
|
violations = 0
|
||||||
errors = 0
|
errors = 0
|
||||||
|
print("Checking docs formatting...")
|
||||||
for file in [*static_docs, *generated_docs]:
|
for file in [*static_docs, *generated_docs]:
|
||||||
rule_name = file.name.split(".")[0]
|
rule_name = file.name.split(".")[0]
|
||||||
if rule_name in KNOWN_FORMATTING_VIOLATIONS:
|
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
|
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:
|
if result == 1:
|
||||||
violations += 1
|
violations += 1
|
||||||
elif result == 2 and not error_known:
|
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:
|
if violations > 0 or errors > 0:
|
||||||
return 1
|
return 1
|
||||||
|
|
||||||
|
print("All docs are formatted correctly.")
|
||||||
|
|
||||||
return 0
|
return 0
|
||||||
|
|
||||||
|
|
||||||
|
|
|
||||||
Loading…
Reference in New Issue