Format PYI examples in docs as .pyi-file snippets (#13116)

This commit is contained in:
Calum Young 2024-08-28 13:20:40 +01:00 committed by GitHub
parent cfafaa7637
commit 2e75cfbfe7
No known key found for this signature in database
GPG key ID: B5690EEEBB952194
37 changed files with 125 additions and 148 deletions

View file

@ -27,14 +27,14 @@ use crate::checkers::ast::Checker;
///
/// ## Example
///
/// ```python
/// ```pyi
/// class Foo:
/// def __eq__(self, obj: typing.Any) -> bool: ...
/// ```
///
/// Use instead:
///
/// ```python
/// ```pyi
/// class Foo:
/// def __eq__(self, obj: object) -> bool: ...
/// ```

View file

@ -34,19 +34,17 @@ use crate::registry::Rule;
/// ```
///
/// ## Example
/// ```python
/// ```pyi
/// import sys
///
/// if sys.version_info > (3, 8):
/// ...
/// if sys.version_info > (3, 8): ...
/// ```
///
/// Use instead:
/// ```python
/// ```pyi
/// import sys
///
/// if sys.version_info >= (3, 9):
/// ...
/// if sys.version_info >= (3, 9): ...
/// ```
#[violation]
pub struct BadVersionInfoComparison;
@ -70,27 +68,23 @@ impl Violation for BadVersionInfoComparison {
///
/// ## Example
///
/// ```python
/// ```pyi
/// import sys
///
/// if sys.version_info < (3, 10):
///
/// def read_data(x, *, preserve_order=True): ...
///
/// else:
///
/// def read_data(x): ...
/// ```
///
/// Use instead:
///
/// ```python
/// ```pyi
/// if sys.version_info >= (3, 10):
///
/// def read_data(x): ...
///
/// else:
///
/// def read_data(x, *, preserve_order=True): ...
/// ```
#[violation]

View file

@ -21,17 +21,16 @@ use crate::checkers::ast::Checker;
/// precisely.
///
/// ## Example
/// ```python
/// ```pyi
/// from collections import namedtuple
///
/// person = namedtuple("Person", ["name", "age"])
/// ```
///
/// Use instead:
/// ```python
/// ```pyi
/// from typing import NamedTuple
///
///
/// class Person(NamedTuple):
/// name: str
/// age: int

View file

@ -20,27 +20,24 @@ use crate::checkers::ast::Checker;
///
/// ## Example
///
/// ```python
/// ```pyi
/// from typing import TypeAlias
///
/// a = b = int
///
///
/// class Klass: ...
///
///
/// Klass.X: TypeAlias = int
/// ```
///
/// Use instead:
///
/// ```python
/// ```pyi
/// from typing import TypeAlias
///
/// a: TypeAlias = int
/// b: TypeAlias = int
///
///
/// class Klass:
/// X: TypeAlias = int
/// ```

View file

@ -16,19 +16,17 @@ use crate::checkers::ast::Checker;
/// analyze your code.
///
/// ## Example
/// ```python
/// ```pyi
/// import sys
///
/// if (3, 10) <= sys.version_info < (3, 12):
/// ...
/// if (3, 10) <= sys.version_info < (3, 12): ...
/// ```
///
/// Use instead:
/// ```python
/// ```pyi
/// import sys
///
/// if sys.version_info >= (3, 10) and sys.version_info < (3, 12):
/// ...
/// if sys.version_info >= (3, 10) and sys.version_info < (3, 12): ...
/// ```
///
/// ## References

View file

@ -25,27 +25,22 @@ use crate::checkers::ast::Checker;
///
/// ## Example
///
/// ```python
/// ```pyi
/// class Foo:
/// def __new__(cls: type[_S], *args: str, **kwargs: int) -> _S: ...
///
/// def foo(self: _S, arg: bytes) -> _S: ...
///
/// @classmethod
/// def bar(cls: type[_S], arg: int) -> _S: ...
/// ```
///
/// Use instead:
///
/// ```python
/// ```pyi
/// from typing import Self
///
///
/// class Foo:
/// def __new__(cls, *args: str, **kwargs: int) -> Self: ...
///
/// def foo(self, arg: bytes) -> Self: ...
///
/// @classmethod
/// def bar(cls, arg: int) -> Self: ...
/// ```

View file

@ -15,7 +15,7 @@ use crate::checkers::ast::Checker;
///
/// ## Example
///
/// ```python
/// ```pyi
/// def func(param: int) -> str:
/// """This is a docstring."""
/// ...
@ -23,7 +23,7 @@ use crate::checkers::ast::Checker;
///
/// Use instead:
///
/// ```python
/// ```pyi
/// def func(param: int) -> str: ...
/// ```
#[violation]

View file

@ -15,14 +15,14 @@ use crate::fix;
/// is redundant.
///
/// ## Example
/// ```python
/// ```pyi
/// class Foo:
/// ...
/// value: int
/// ```
///
/// Use instead:
/// ```python
/// ```pyi
/// class Foo:
/// value: int
/// ```

View file

@ -24,10 +24,9 @@ use crate::checkers::ast::Checker;
///
/// ## Example
///
/// ```python
/// ```pyi
/// from types import TracebackType
///
///
/// class Foo:
/// def __exit__(
/// self, typ: BaseException, exc: BaseException, tb: TracebackType
@ -36,10 +35,9 @@ use crate::checkers::ast::Checker;
///
/// Use instead:
///
/// ```python
/// ```pyi
/// from types import TracebackType
///
///
/// class Foo:
/// def __exit__(
/// self,

View file

@ -22,14 +22,14 @@ use crate::settings::types::PythonVersion::Py311;
/// members).
///
/// ## Example
/// ```python
/// ```pyi
/// from typing import NoReturn
///
/// def foo(x: NoReturn): ...
/// ```
///
/// Use instead:
/// ```python
/// ```pyi
/// from typing import Never
///
/// def foo(x: Never): ...

View file

@ -15,13 +15,13 @@ use crate::checkers::ast::Checker;
/// for this purpose.
///
/// ## Example
/// ```python
/// ```pyi
/// def double(x: int) -> int:
/// return x * 2
/// ```
///
/// Use instead:
/// ```python
/// ```pyi
/// def double(x: int) -> int: ...
/// ```
///

View file

@ -51,30 +51,23 @@ use crate::checkers::ast::Checker;
///
/// ## Example
///
/// ```python
/// ```pyi
/// class Foo:
/// def __new__(cls, *args: Any, **kwargs: Any) -> Foo: ...
///
/// def __enter__(self) -> Foo: ...
///
/// async def __aenter__(self) -> Foo: ...
///
/// def __iadd__(self, other: Foo) -> Foo: ...
/// ```
///
/// Use instead:
///
/// ```python
/// ```pyi
/// from typing_extensions import Self
///
///
/// class Foo:
/// def __new__(cls, *args: Any, **kwargs: Any) -> Self: ...
///
/// def __enter__(self) -> Self: ...
///
/// async def __aenter__(self) -> Self: ...
///
/// def __iadd__(self, other: Foo) -> Self: ...
/// ```
/// ## References

View file

@ -20,13 +20,13 @@ use crate::checkers::ast::Checker;
///
/// ## Example
///
/// ```python
/// ```pyi
/// def foo(arg: int = 693568516352839939918568862861217771399698285293568) -> None: ...
/// ```
///
/// Use instead:
///
/// ```python
/// ```pyi
/// def foo(arg: int = ...) -> None: ...
/// ```
#[violation]

View file

@ -15,14 +15,14 @@ use crate::fix;
/// stubs.
///
/// ## Example
/// ```python
/// ```pyi
/// class MyClass:
/// x: int
/// pass
/// ```
///
/// Use instead:
/// ```python
/// ```pyi
/// class MyClass:
/// x: int
/// ```

View file

@ -13,12 +13,12 @@ use crate::checkers::ast::Checker;
/// in stub files.
///
/// ## Example
/// ```python
/// ```pyi
/// def foo(bar: int) -> list[int]: pass
/// ```
///
/// Use instead:
/// ```python
/// ```pyi
/// def foo(bar: int) -> list[int]: ...
/// ```
///

View file

@ -17,13 +17,13 @@ use crate::settings::types::PythonVersion;
///
/// ## Example
///
/// ```python
/// ```pyi
/// def foo(__x: int) -> None: ...
/// ```
///
/// Use instead:
///
/// ```python
/// ```pyi
/// def foo(x: int, /) -> None: ...
/// ```
///

View file

@ -33,14 +33,14 @@ impl fmt::Display for VarKind {
/// internal to the stub.
///
/// ## Example
/// ```python
/// ```pyi
/// from typing import TypeVar
///
/// T = TypeVar("T")
/// ```
///
/// Use instead:
/// ```python
/// ```pyi
/// from typing import TypeVar
///
/// _T = TypeVar("_T")

View file

@ -16,13 +16,13 @@ use crate::checkers::ast::Checker;
///
/// ## Example
///
/// ```python
/// ```pyi
/// def function() -> "int": ...
/// ```
///
/// Use instead:
///
/// ```python
/// ```pyi
/// def function() -> int: ...
/// ```
///

View file

@ -16,13 +16,13 @@ use crate::fix::snippet::SourceCodeSnippet;
///
/// ## Example
///
/// ```python
/// ```pyi
/// x: Final[Literal[42]]
/// y: Final[Literal[42]] = 42
/// ```
///
/// Use instead:
/// ```python
/// ```pyi
/// x: Final = 42
/// y: Final = 42
/// ```

View file

@ -24,14 +24,14 @@ use crate::fix::snippet::SourceCodeSnippet;
/// supertypes of `"A"` and `1` respectively.
///
/// ## Example
/// ```python
/// ```pyi
/// from typing import Literal
///
/// x: Literal["A", b"B"] | str
/// ```
///
/// Use instead:
/// ```python
/// ```pyi
/// from typing import Literal
///
/// x: Literal[b"B"] | str

View file

@ -27,13 +27,13 @@ use crate::checkers::ast::Checker;
///
/// ## Example
///
/// ```python
/// ```pyi
/// def foo(x: float | int | str) -> None: ...
/// ```
///
/// Use instead:
///
/// ```python
/// ```pyi
/// def foo(x: float | str) -> None: ...
/// ```
///

View file

@ -31,13 +31,13 @@ use crate::settings::types::PythonVersion;
///
/// ## Example
///
/// ```python
/// ```pyi
/// def foo(arg: list[int] = list(range(10_000))) -> None: ...
/// ```
///
/// Use instead:
///
/// ```python
/// ```pyi
/// def foo(arg: list[int] = ...) -> None: ...
/// ```
///
@ -77,13 +77,13 @@ impl AlwaysFixableViolation for TypedArgumentDefaultInStub {
///
/// ## Example
///
/// ```python
/// ```pyi
/// def foo(arg=[]) -> None: ...
/// ```
///
/// Use instead:
///
/// ```python
/// ```pyi
/// def foo(arg=...) -> None: ...
/// ```
///
@ -122,12 +122,12 @@ impl AlwaysFixableViolation for ArgumentDefaultInStub {
/// or varies according to the current platform or Python version.
///
/// ## Example
/// ```python
/// ```pyi
/// foo: str = "..."
/// ```
///
/// Use instead:
/// ```python
/// ```pyi
/// foo: str = ...
/// ```
///
@ -176,12 +176,12 @@ impl Violation for UnannotatedAssignmentInStub {
/// runtime counterparts.
///
/// ## Example
/// ```python
/// ```pyi
/// __all__: list[str]
/// ```
///
/// Use instead:
/// ```python
/// ```pyi
/// __all__: list[str] = ["foo", "bar"]
/// ```
#[violation]
@ -210,12 +210,12 @@ impl Violation for UnassignedSpecialVariableInStub {
/// to a normal variable assignment.
///
/// ## Example
/// ```python
/// ```pyi
/// Vector = list[float]
/// ```
///
/// Use instead:
/// ```python
/// ```pyi
/// from typing import TypeAlias
///
/// Vector: TypeAlias = list[float]

View file

@ -19,7 +19,7 @@ use crate::fix::edits::delete_stmt;
///
/// ## Example
///
/// ```python
/// ```pyi
/// class Foo:
/// def __repr__(self) -> str: ...
/// ```

View file

@ -23,13 +23,13 @@ use crate::checkers::ast::Checker;
///
/// ## Example
///
/// ```python
/// ```pyi
/// def foo(arg: str = "51 character stringgggggggggggggggggggggggggggggggg") -> None: ...
/// ```
///
/// Use instead:
///
/// ```python
/// ```pyi
/// def foo(arg: str = ...) -> None: ...
/// ```
#[violation]

View file

@ -16,7 +16,7 @@ use crate::checkers::ast::Checker;
///
/// ## Example
///
/// ```python
/// ```pyi
/// def function():
/// x = 1
/// y = 2
@ -25,7 +25,7 @@ use crate::checkers::ast::Checker;
///
/// Use instead:
///
/// ```python
/// ```pyi
/// def function(): ...
/// ```
#[violation]

View file

@ -13,12 +13,12 @@ use crate::checkers::ast::Checker;
/// to distinguish them from other variables.
///
/// ## Example
/// ```python
/// ```pyi
/// type_alias_name: TypeAlias = int
/// ```
///
/// Use instead:
/// ```python
/// ```pyi
/// TypeAliasName: TypeAlias = int
/// ```
#[violation]
@ -45,14 +45,14 @@ impl Violation for SnakeCaseTypeAlias {
/// be avoided.
///
/// ## Example
/// ```python
/// ```pyi
/// from typing import TypeAlias
///
/// _MyTypeT: TypeAlias = int
/// ```
///
/// Use instead:
/// ```python
/// ```pyi
/// from typing import TypeAlias
///
/// _MyType: TypeAlias = int

View file

@ -16,12 +16,12 @@ use ruff_macros::{derive_message_formats, violation};
/// stub files are not executed at runtime. The one exception is `# type: ignore`.
///
/// ## Example
/// ```python
/// ```pyi
/// x = 1 # type: int
/// ```
///
/// Use instead:
/// ```python
/// ```pyi
/// x: int = 1
/// ```
#[violation]

View file

@ -21,12 +21,12 @@ use crate::renamer::Renamer;
/// `set` builtin.
///
/// ## Example
/// ```python
/// ```pyi
/// from collections.abc import Set
/// ```
///
/// Use instead:
/// ```python
/// ```pyi
/// from collections.abc import Set as AbstractSet
/// ```
///

View file

@ -15,14 +15,14 @@ use crate::checkers::ast::Checker;
/// `Literal["foo"] | Literal[42]`, but is clearer and more concise.
///
/// ## Example
/// ```python
/// ```pyi
/// from typing import Literal
///
/// field: Literal[1] | Literal[2] | str
/// ```
///
/// Use instead:
/// ```python
/// ```pyi
/// from typing import Literal
///
/// field: Literal[1, 2] | str

View file

@ -17,12 +17,12 @@ use crate::checkers::ast::Checker;
/// annotation, but is cleaner and more concise.
///
/// ## Example
/// ```python
/// ```pyi
/// field: type[int] | type[float] | str
/// ```
///
/// Use instead:
/// ```python
/// ```pyi
/// field: type[int | float] | str
/// ```
#[violation]

View file

@ -20,7 +20,7 @@ use crate::registry::Rule;
/// `if sys.platform == "linux"`.
///
/// ## Example
/// ```python
/// ```pyi
/// if sys.platform.startswith("linux"):
/// # Linux specific definitions
/// ...
@ -30,7 +30,7 @@ use crate::registry::Rule;
/// ```
///
/// Instead, use a simple string comparison, such as `==` or `!=`:
/// ```python
/// ```pyi
/// if sys.platform == "linux":
/// # Linux specific definitions
/// ...
@ -64,15 +64,13 @@ impl Violation for UnrecognizedPlatformCheck {
/// The list of known platforms is: "linux", "win32", "cygwin", "darwin".
///
/// ## Example
/// ```python
/// if sys.platform == "linus":
/// ...
/// ```pyi
/// if sys.platform == "linus": ...
/// ```
///
/// Use instead:
/// ```python
/// if sys.platform == "linux":
/// ...
/// ```pyi
/// if sys.platform == "linux": ...
/// ```
///
/// ## References

View file

@ -17,19 +17,17 @@ use crate::registry::Rule;
/// For example, comparing against a string can lead to unexpected behavior.
///
/// ## Example
/// ```python
/// ```pyi
/// import sys
///
/// if sys.version_info[0] == "2":
/// ...
/// if sys.version_info[0] == "2": ...
/// ```
///
/// Use instead:
/// ```python
/// ```pyi
/// import sys
///
/// if sys.version_info[0] == 2:
/// ...
/// if sys.version_info[0] == 2: ...
/// ```
///
/// ## References
@ -58,19 +56,17 @@ impl Violation for UnrecognizedVersionInfoCheck {
/// and minor versions.
///
/// ## Example
/// ```python
/// ```pyi
/// import sys
///
/// if sys.version_info >= (3, 4, 3):
/// ...
/// if sys.version_info >= (3, 4, 3): ...
/// ```
///
/// Use instead:
/// ```python
/// ```pyi
/// import sys
///
/// if sys.version_info >= (3, 4):
/// ...
/// if sys.version_info >= (3, 4): ...
/// ```
///
/// ## References
@ -96,19 +92,17 @@ impl Violation for PatchVersionComparison {
/// behavior.
///
/// ## Example
/// ```python
/// ```pyi
/// import sys
///
/// if sys.version_info[:2] == (3,):
/// ...
/// if sys.version_info[:2] == (3,): ...
/// ```
///
/// Use instead:
/// ```python
/// ```pyi
/// import sys
///
/// if sys.version_info[0] == 3:
/// ...
/// if sys.version_info[0] == 3: ...
/// ```
///
/// ## References

View file

@ -16,7 +16,7 @@ use crate::checkers::ast::Checker;
/// `__all__`, which is known to be supported by all major type checkers.
///
/// ## Example
/// ```python
/// ```pyi
/// import sys
///
/// __all__ = ["A", "B"]
@ -29,7 +29,7 @@ use crate::checkers::ast::Checker;
/// ```
///
/// Use instead:
/// ```python
/// ```pyi
/// import sys
///
/// __all__ = ["A"]

View file

@ -16,7 +16,7 @@ use crate::checkers::ast::Checker;
/// should either be used, made public, or removed to avoid confusion.
///
/// ## Example
/// ```python
/// ```pyi
/// import typing
/// import typing_extensions
///
@ -50,24 +50,21 @@ impl Violation for UnusedPrivateTypeVar {
///
/// ## Example
///
/// ```python
/// ```pyi
/// import typing
///
///
/// class _PrivateProtocol(typing.Protocol):
/// foo: int
/// ```
///
/// Use instead:
///
/// ```python
/// ```pyi
/// import typing
///
///
/// class _PrivateProtocol(typing.Protocol):
/// foo: int
///
///
/// def func(arg: _PrivateProtocol) -> None: ...
/// ```
#[violation]
@ -93,7 +90,7 @@ impl Violation for UnusedPrivateProtocol {
///
/// ## Example
///
/// ```python
/// ```pyi
/// import typing
///
/// _UnusedTypeAlias: typing.TypeAlias = int
@ -101,12 +98,11 @@ impl Violation for UnusedPrivateProtocol {
///
/// Use instead:
///
/// ```python
/// ```pyi
/// import typing
///
/// _UsedTypeAlias: typing.TypeAlias = int
///
///
/// def func(arg: _UsedTypeAlias) -> _UsedTypeAlias: ...
/// ```
#[violation]
@ -132,24 +128,21 @@ impl Violation for UnusedPrivateTypeAlias {
///
/// ## Example
///
/// ```python
/// ```pyi
/// import typing
///
///
/// class _UnusedPrivateTypedDict(typing.TypedDict):
/// foo: list[int]
/// ```
///
/// Use instead:
///
/// ```python
/// ```pyi
/// import typing
///
///
/// class _UsedPrivateTypedDict(typing.TypedDict):
/// foo: set[str]
///
///
/// def func(arg: _UsedPrivateTypedDict) -> _UsedPrivateTypedDict: ...
/// ```
#[violation]

View file

@ -6,3 +6,6 @@ mkdocs-redirects==1.2.1
mdformat==0.7.17
mdformat-mkdocs==3.0.0
mdformat-admon==2.0.6
# Using a commit from pygments main branch so we get
# https://github.com/pygments/pygments/pull/2773 before it's been released
pygments @ git+https://github.com/pygments/pygments.git@67b460fdde6d9a00342b5102b37b3a8399f0e8ef

View file

@ -6,3 +6,6 @@ mkdocs-redirects==1.2.1
mdformat==0.7.17
mdformat-mkdocs==3.0.0
mdformat-admon==2.0.6
# Using a commit from pygments main branch so we get
# https://github.com/pygments/pygments/pull/2773 before it's been released
pygments @ git+https://github.com/pygments/pygments.git@67b460fdde6d9a00342b5102b37b3a8399f0e8ef

View file

@ -10,13 +10,13 @@ import subprocess
import textwrap
from pathlib import Path
from re import Match
from typing import TYPE_CHECKING
from typing import TYPE_CHECKING, Literal
if TYPE_CHECKING:
from collections.abc import Sequence
SNIPPED_RE = re.compile(
r"(?P<before>^(?P<indent> *)```\s*python\n)"
r"(?P<before>^(?P<indent> *)```(?:\s*(?P<language>\w+))?\n)"
r"(?P<code>.*?)"
r"(?P<after>^(?P=indent)```\s*$)",
re.DOTALL | re.MULTILINE,
@ -120,12 +120,12 @@ class InvalidInput(ValueError):
"""Raised when ruff fails to parse file."""
def format_str(code: str) -> str:
def format_str(code: str, extension: Literal["py", "pyi"]) -> 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", "-"],
["ruff", "format", "--stdin-filename", f"file.{extension}", "-"],
check=True,
capture_output=True,
text=True,
@ -149,9 +149,21 @@ def format_contents(src: str) -> tuple[str, Sequence[CodeBlockError]]:
errors: list[CodeBlockError] = []
def _snipped_match(match: Match[str]) -> str:
language = match["language"]
extension: Literal["py", "pyi"]
match language:
case "python":
extension = "py"
case "pyi":
extension = "pyi"
case _:
# We are only interested in checking the formatting of py or pyi code
# blocks so we can return early if the language is not one of these.
return f'{match["before"]}{match["code"]}{match["after"]}'
code = textwrap.dedent(match["code"])
try:
code = format_str(code)
code = format_str(code, extension)
except InvalidInput as e:
errors.append(CodeBlockError(e))
except NotImplementedError as e: