mirror of
				https://github.com/astral-sh/ruff.git
				synced 2025-10-31 12:05:57 +00:00 
			
		
		
		
	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
					
				
					 3 changed files with 45 additions and 27 deletions
				
			
		|  | @ -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…
	
	Add table
		Add a link
		
	
		Reference in a new issue
	
	 Calum Young
						Calum Young