mirror of
https://github.com/astral-sh/ruff.git
synced 2025-11-01 20:30:49 +00:00
Format empty lines in stub files like black's preview style (#7206)
## Summary Fix all but one empty line differences with the black preview style in typeshed. The remaining differences are breaking with type comments and trailing commas in function definitions. I compared the empty line differences with the preview mode of black since stable has some oddities that would have been hard to replicate (https://github.com/psf/black/issues/3861). Additionally, it assumes the style proposed in https://github.com/psf/black/issues/3862. An edge case that also surfaced with typeshed are newline before trailing module comments. **main** | project | similarity index | total files | changed files | |--------------|------------------:|------------------:|------------------:| | cpython | 0.76083 | 1789 | 1632 | | django | 0.99966 | 2760 | 58 | | transformers | 0.99930 | 2587 | 447 | | twine | 1.00000 | 33 | 0 | | **typeshed** | 0.99978 | 3496 | **2173** | | warehouse | 0.99825 | 648 | 22 | | zulip | 0.99950 | 1437 | 27 | **PR** | project | similarity index | total files | changed files | |--------------|------------------:|------------------:|------------------:| | cpython | 0.76083 | 1789 | 1632 | | django | 0.99966 | 2760 | 58 | | transformers | 0.99930 | 2587 | 447 | | twine | 1.00000 | 33 | 0 | | **typeshed** | 0.99983 | 3496 | **18** | | warehouse | 0.99825 | 648 | 22 | | zulip | 0.99950 | 1437 | 27 | Closes #6723 ## Test Plan The main driver was the typeshed diff. I added new test cases for all kinds of possible empty line combinations in stub files, test cases for newlines before trailing module comments. --------- Co-authored-by: Micha Reiser <micha@reiser.io>
This commit is contained in:
parent
7440e54ec6
commit
3a2c3a7398
18 changed files with 858 additions and 77 deletions
|
|
@ -0,0 +1,20 @@
|
|||
---
|
||||
source: crates/ruff_python_formatter/tests/fixtures.rs
|
||||
input_file: crates/ruff_python_formatter/resources/test/fixtures/ruff/module_dangling_comment1.py
|
||||
---
|
||||
## Input
|
||||
```py
|
||||
__all__ = ["X", "XK", "Xatom", "Xcursorfont", "Xutil", "display", "error", "rdb"]
|
||||
|
||||
# Shared types throughout the stub
|
||||
```
|
||||
|
||||
## Output
|
||||
```py
|
||||
__all__ = ["X", "XK", "Xatom", "Xcursorfont", "Xutil", "display", "error", "rdb"]
|
||||
|
||||
# Shared types throughout the stub
|
||||
```
|
||||
|
||||
|
||||
|
||||
|
|
@ -0,0 +1,18 @@
|
|||
---
|
||||
source: crates/ruff_python_formatter/tests/fixtures.rs
|
||||
input_file: crates/ruff_python_formatter/resources/test/fixtures/ruff/module_dangling_comment2.py
|
||||
---
|
||||
## Input
|
||||
```py
|
||||
__all__ = ["X", "XK", "Xatom", "Xcursorfont", "Xutil", "display", "error", "rdb"]
|
||||
# Shared types throughout the stub
|
||||
```
|
||||
|
||||
## Output
|
||||
```py
|
||||
__all__ = ["X", "XK", "Xatom", "Xcursorfont", "Xutil", "display", "error", "rdb"]
|
||||
# Shared types throughout the stub
|
||||
```
|
||||
|
||||
|
||||
|
||||
|
|
@ -0,0 +1,28 @@
|
|||
---
|
||||
source: crates/ruff_python_formatter/tests/fixtures.rs
|
||||
input_file: crates/ruff_python_formatter/resources/test/fixtures/ruff/stub_files/comments.pyi
|
||||
---
|
||||
## Input
|
||||
```py
|
||||
class SupportsAnext:
|
||||
def __anext__(self): ...
|
||||
|
||||
# Comparison protocols
|
||||
|
||||
class SupportsDunderLT:
|
||||
def __init__(self): ...
|
||||
```
|
||||
|
||||
## Output
|
||||
```py
|
||||
class SupportsAnext:
|
||||
def __anext__(self): ...
|
||||
|
||||
# Comparison protocols
|
||||
|
||||
class SupportsDunderLT:
|
||||
def __init__(self): ...
|
||||
```
|
||||
|
||||
|
||||
|
||||
|
|
@ -0,0 +1,75 @@
|
|||
---
|
||||
source: crates/ruff_python_formatter/tests/fixtures.rs
|
||||
input_file: crates/ruff_python_formatter/resources/test/fixtures/ruff/stub_files/nesting.pyi
|
||||
---
|
||||
## Input
|
||||
```py
|
||||
"""Tests specifically for https://github.com/psf/black/issues/3861"""
|
||||
|
||||
import sys
|
||||
|
||||
|
||||
class OuterClassOrOtherSuite:
|
||||
class Nested11:
|
||||
class Nested12:
|
||||
assignment = 1
|
||||
def function_definition(self): ...
|
||||
|
||||
def f1(self) -> str: ...
|
||||
|
||||
class Nested21:
|
||||
class Nested22:
|
||||
def function_definition(self): ...
|
||||
assignment = 1
|
||||
|
||||
def f2(self) -> str: ...
|
||||
|
||||
if sys.version_info > (3, 7):
|
||||
if sys.platform == "win32":
|
||||
assignment = 1
|
||||
def function_definition(self): ...
|
||||
|
||||
def f1(self) -> str: ...
|
||||
if sys.platform != "win32":
|
||||
def function_definition(self): ...
|
||||
assignment = 1
|
||||
|
||||
def f2(self) -> str: ...
|
||||
```
|
||||
|
||||
## Output
|
||||
```py
|
||||
"""Tests specifically for https://github.com/psf/black/issues/3861"""
|
||||
|
||||
import sys
|
||||
|
||||
class OuterClassOrOtherSuite:
|
||||
class Nested11:
|
||||
class Nested12:
|
||||
assignment = 1
|
||||
def function_definition(self): ...
|
||||
|
||||
def f1(self) -> str: ...
|
||||
|
||||
class Nested21:
|
||||
class Nested22:
|
||||
def function_definition(self): ...
|
||||
assignment = 1
|
||||
|
||||
def f2(self) -> str: ...
|
||||
|
||||
if sys.version_info > (3, 7):
|
||||
if sys.platform == "win32":
|
||||
assignment = 1
|
||||
def function_definition(self): ...
|
||||
|
||||
def f1(self) -> str: ...
|
||||
if sys.platform != "win32":
|
||||
def function_definition(self): ...
|
||||
assignment = 1
|
||||
|
||||
def f2(self) -> str: ...
|
||||
```
|
||||
|
||||
|
||||
|
||||
|
|
@ -0,0 +1,312 @@
|
|||
---
|
||||
source: crates/ruff_python_formatter/tests/fixtures.rs
|
||||
input_file: crates/ruff_python_formatter/resources/test/fixtures/ruff/stub_files/suite.pyi
|
||||
---
|
||||
## Input
|
||||
```py
|
||||
"""Tests for empty line rules in stub files, mostly inspired by typeshed.
|
||||
The rules are a list of nested exceptions. See also
|
||||
https://github.com/psf/black/blob/c160e4b7ce30c661ac4f2dfa5038becf1b8c5c33/src/black/lines.py#L576-L744
|
||||
"""
|
||||
|
||||
import sys
|
||||
from typing import Self, TypeAlias, final
|
||||
|
||||
if sys.version_info >= (3, 8):
|
||||
class InnerClass1: ...
|
||||
|
||||
class InnerClass2:
|
||||
def a(self): ...
|
||||
|
||||
class InnerClass3:
|
||||
def a(self): ...
|
||||
|
||||
class InnerClass4: ...
|
||||
details: int
|
||||
def f1(self, hresult: int, text: str | None, detail: int) -> None: ...
|
||||
details: int
|
||||
def f2(self, hresult: int, text: str | None, detail: int) -> None: ...
|
||||
@final
|
||||
class DecoratorInsteadOfEmptyLine: ...
|
||||
|
||||
def open(device: str) -> None: ...
|
||||
|
||||
# oss_mixer_device return type
|
||||
def openmixer(device: str = ...) -> None: ...
|
||||
def open2(device: str) -> None: ...
|
||||
# oss_mixer_device2 return type
|
||||
def openmixer2(device: str = ...) -> None: ...
|
||||
|
||||
else:
|
||||
class Slice1: ...
|
||||
_Slice1: TypeAlias = Slice1
|
||||
|
||||
class Slice2: ...
|
||||
_Slice2: TypeAlias = Slice2
|
||||
|
||||
class NoEmptyLinesBetweenFunctions:
|
||||
def multi_line_but_only_ellipsis(
|
||||
self,
|
||||
mandatory_release: float | None,
|
||||
) -> None: ...
|
||||
def only_ellipsis1(self) -> float: ...
|
||||
def only_ellipsis2(self) -> float | None: ...
|
||||
def has_impl1(self):
|
||||
print(self)
|
||||
return 1
|
||||
|
||||
def has_impl2(self):
|
||||
print(self)
|
||||
return 2
|
||||
|
||||
def no_impl4(self): ...
|
||||
|
||||
class NoEmptyLinesBetweenField:
|
||||
field1: int
|
||||
field2: (
|
||||
# type
|
||||
int
|
||||
)
|
||||
field3 = 3
|
||||
field4 = (
|
||||
1,
|
||||
2,
|
||||
)
|
||||
field5 = 5
|
||||
|
||||
class FieldAndFunctionsWithOptionalEmptyLines:
|
||||
details1: int
|
||||
def f1(self, hresult: int, text: str | None, detail: int) -> None: ...
|
||||
details2: int
|
||||
def f2(self, hresult: int, text: str | None, detail: int) -> None: ...
|
||||
details3: int
|
||||
|
||||
class NewlinesBetweenStubInnerClasses:
|
||||
def f1(self): ...
|
||||
|
||||
class InnerClass1: ...
|
||||
class InnerClass2: ...
|
||||
|
||||
def f2(self): ...
|
||||
|
||||
class InnerClass3: ...
|
||||
class InnerClass4: ...
|
||||
field = 1
|
||||
|
||||
class InnerClass3: ...
|
||||
class InnerClass4: ...
|
||||
|
||||
def f3(self): ...
|
||||
@final
|
||||
class DecoratorInsteadOfEmptyLine: ...
|
||||
|
||||
@final
|
||||
class DecoratorStillEmptyLine: ...
|
||||
|
||||
class NewlinesBetweenInnerClasses:
|
||||
class InnerClass1: ...
|
||||
|
||||
class InnerClass2:
|
||||
def a(self): ...
|
||||
|
||||
class InnerClass3:
|
||||
def a(self): ...
|
||||
|
||||
class InnerClass4: ...
|
||||
|
||||
class InnerClass5:
|
||||
def a(self): ...
|
||||
field1 = 1
|
||||
|
||||
class InnerClass6:
|
||||
def a(self): ...
|
||||
|
||||
def f1(self): ...
|
||||
|
||||
class InnerClass7:
|
||||
def a(self): ...
|
||||
print("hi")
|
||||
|
||||
class InnerClass8:
|
||||
def a(self): ...
|
||||
|
||||
class ComplexStatements:
|
||||
# didn't match the name in the C implementation,
|
||||
# meaning it is only *safe* to pass it as a keyword argument on 3.12+
|
||||
if sys.version_info >= (3, 12):
|
||||
@classmethod
|
||||
def fromtimestamp(cls, timestamp: float, tz: float | None = ...) -> Self: ...
|
||||
else:
|
||||
@classmethod
|
||||
def fromtimestamp(cls, __timestamp: float, tz: float | None = ...) -> Self: ...
|
||||
|
||||
@classmethod
|
||||
def utcfromtimestamp(cls, __t: float) -> Self: ...
|
||||
if sys.version_info >= (3, 8):
|
||||
@classmethod
|
||||
def now(cls, tz: float | None = None) -> Self: ...
|
||||
else:
|
||||
@classmethod
|
||||
def now(cls, tz: None = None) -> Self: ...
|
||||
@classmethod
|
||||
def now2(cls, tz: float) -> Self: ...
|
||||
|
||||
@classmethod
|
||||
def utcnow(cls) -> Self: ...
|
||||
```
|
||||
|
||||
## Output
|
||||
```py
|
||||
"""Tests for empty line rules in stub files, mostly inspired by typeshed.
|
||||
The rules are a list of nested exceptions. See also
|
||||
https://github.com/psf/black/blob/c160e4b7ce30c661ac4f2dfa5038becf1b8c5c33/src/black/lines.py#L576-L744
|
||||
"""
|
||||
|
||||
import sys
|
||||
from typing import Self, TypeAlias, final
|
||||
|
||||
if sys.version_info >= (3, 8):
|
||||
class InnerClass1: ...
|
||||
|
||||
class InnerClass2:
|
||||
def a(self): ...
|
||||
|
||||
class InnerClass3:
|
||||
def a(self): ...
|
||||
|
||||
class InnerClass4: ...
|
||||
details: int
|
||||
def f1(self, hresult: int, text: str | None, detail: int) -> None: ...
|
||||
details: int
|
||||
def f2(self, hresult: int, text: str | None, detail: int) -> None: ...
|
||||
@final
|
||||
class DecoratorInsteadOfEmptyLine: ...
|
||||
|
||||
def open(device: str) -> None: ...
|
||||
|
||||
# oss_mixer_device return type
|
||||
def openmixer(device: str = ...) -> None: ...
|
||||
def open2(device: str) -> None: ...
|
||||
# oss_mixer_device2 return type
|
||||
def openmixer2(device: str = ...) -> None: ...
|
||||
|
||||
else:
|
||||
class Slice1: ...
|
||||
_Slice1: TypeAlias = Slice1
|
||||
|
||||
class Slice2: ...
|
||||
_Slice2: TypeAlias = Slice2
|
||||
|
||||
class NoEmptyLinesBetweenFunctions:
|
||||
def multi_line_but_only_ellipsis(
|
||||
self,
|
||||
mandatory_release: float | None,
|
||||
) -> None: ...
|
||||
def only_ellipsis1(self) -> float: ...
|
||||
def only_ellipsis2(self) -> float | None: ...
|
||||
def has_impl1(self):
|
||||
print(self)
|
||||
return 1
|
||||
|
||||
def has_impl2(self):
|
||||
print(self)
|
||||
return 2
|
||||
|
||||
def no_impl4(self): ...
|
||||
|
||||
class NoEmptyLinesBetweenField:
|
||||
field1: int
|
||||
field2: (
|
||||
# type
|
||||
int
|
||||
)
|
||||
field3 = 3
|
||||
field4 = (
|
||||
1,
|
||||
2,
|
||||
)
|
||||
field5 = 5
|
||||
|
||||
class FieldAndFunctionsWithOptionalEmptyLines:
|
||||
details1: int
|
||||
def f1(self, hresult: int, text: str | None, detail: int) -> None: ...
|
||||
details2: int
|
||||
def f2(self, hresult: int, text: str | None, detail: int) -> None: ...
|
||||
details3: int
|
||||
|
||||
class NewlinesBetweenStubInnerClasses:
|
||||
def f1(self): ...
|
||||
|
||||
class InnerClass1: ...
|
||||
class InnerClass2: ...
|
||||
|
||||
def f2(self): ...
|
||||
|
||||
class InnerClass3: ...
|
||||
class InnerClass4: ...
|
||||
field = 1
|
||||
|
||||
class InnerClass3: ...
|
||||
class InnerClass4: ...
|
||||
|
||||
def f3(self): ...
|
||||
@final
|
||||
class DecoratorInsteadOfEmptyLine: ...
|
||||
|
||||
@final
|
||||
class DecoratorStillEmptyLine: ...
|
||||
|
||||
class NewlinesBetweenInnerClasses:
|
||||
class InnerClass1: ...
|
||||
|
||||
class InnerClass2:
|
||||
def a(self): ...
|
||||
|
||||
class InnerClass3:
|
||||
def a(self): ...
|
||||
|
||||
class InnerClass4: ...
|
||||
|
||||
class InnerClass5:
|
||||
def a(self): ...
|
||||
field1 = 1
|
||||
|
||||
class InnerClass6:
|
||||
def a(self): ...
|
||||
|
||||
def f1(self): ...
|
||||
|
||||
class InnerClass7:
|
||||
def a(self): ...
|
||||
print("hi")
|
||||
|
||||
class InnerClass8:
|
||||
def a(self): ...
|
||||
|
||||
class ComplexStatements:
|
||||
# didn't match the name in the C implementation,
|
||||
# meaning it is only *safe* to pass it as a keyword argument on 3.12+
|
||||
if sys.version_info >= (3, 12):
|
||||
@classmethod
|
||||
def fromtimestamp(cls, timestamp: float, tz: float | None = ...) -> Self: ...
|
||||
else:
|
||||
@classmethod
|
||||
def fromtimestamp(cls, __timestamp: float, tz: float | None = ...) -> Self: ...
|
||||
|
||||
@classmethod
|
||||
def utcfromtimestamp(cls, __t: float) -> Self: ...
|
||||
if sys.version_info >= (3, 8):
|
||||
@classmethod
|
||||
def now(cls, tz: float | None = None) -> Self: ...
|
||||
else:
|
||||
@classmethod
|
||||
def now(cls, tz: None = None) -> Self: ...
|
||||
@classmethod
|
||||
def now2(cls, tz: float) -> Self: ...
|
||||
|
||||
@classmethod
|
||||
def utcnow(cls) -> Self: ...
|
||||
```
|
||||
|
||||
|
||||
|
||||
|
|
@ -0,0 +1,47 @@
|
|||
---
|
||||
source: crates/ruff_python_formatter/tests/fixtures.rs
|
||||
input_file: crates/ruff_python_formatter/resources/test/fixtures/ruff/stub_files/top_level.pyi
|
||||
---
|
||||
## Input
|
||||
```py
|
||||
from typing import final
|
||||
|
||||
|
||||
def count1(): ...
|
||||
def count2(): ...
|
||||
@final
|
||||
def count3(): ...
|
||||
@final
|
||||
class LockType1: ...
|
||||
|
||||
def count4(): ...
|
||||
|
||||
class LockType2: ...
|
||||
class LockType3: ...
|
||||
|
||||
@final
|
||||
class LockType4: ...
|
||||
```
|
||||
|
||||
## Output
|
||||
```py
|
||||
from typing import final
|
||||
|
||||
def count1(): ...
|
||||
def count2(): ...
|
||||
@final
|
||||
def count3(): ...
|
||||
@final
|
||||
class LockType1: ...
|
||||
|
||||
def count4(): ...
|
||||
|
||||
class LockType2: ...
|
||||
class LockType3: ...
|
||||
|
||||
@final
|
||||
class LockType4: ...
|
||||
```
|
||||
|
||||
|
||||
|
||||
Loading…
Add table
Add a link
Reference in a new issue