Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Enforce a blank line after a nested class in stubs #3564

Merged
merged 4 commits into from Mar 18, 2023
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Jump to
Jump to file
Failed to load files.
Diff view
Diff view
2 changes: 2 additions & 0 deletions CHANGES.md
Expand Up @@ -18,6 +18,8 @@
entry (#3393)
- `with` statements that contain two context managers will be consistently wrapped in
parentheses (#3589)
- For stubs, enforce one blank line after a nested class with a body other than just
`...` (#3564)

### Configuration

Expand Down
12 changes: 9 additions & 3 deletions src/black/lines.py
Expand Up @@ -501,7 +501,7 @@ class EmptyLineTracker:
mode: Mode
previous_line: Optional[Line] = None
previous_block: Optional[LinesBlock] = None
previous_defs: List[int] = field(default_factory=list)
previous_defs: List[Line] = field(default_factory=list)
semantic_leading_comment: Optional[LinesBlock] = None

def maybe_empty_lines(self, current_line: Line) -> LinesBlock:
Expand Down Expand Up @@ -557,12 +557,18 @@ def _maybe_empty_lines(self, current_line: Line) -> Tuple[int, int]:
else:
before = 0
depth = current_line.depth
while self.previous_defs and self.previous_defs[-1] >= depth:
while self.previous_defs and self.previous_defs[-1].depth >= depth:
if self.mode.is_pyi:
assert self.previous_line is not None
if depth and not current_line.is_def and self.previous_line.is_def:
# Empty lines between attributes and methods should be preserved.
before = min(1, before)
elif (
Preview.blank_line_after_nested_stub_class in self.mode
and self.previous_defs[-1].is_class
and not self.previous_defs[-1].is_stub_class
):
before = 1
elif depth:
before = 0
else:
Expand Down Expand Up @@ -616,7 +622,7 @@ def _maybe_empty_lines_for_class_or_def(
self, current_line: Line, before: int
) -> Tuple[int, int]:
if not current_line.is_decorator:
self.previous_defs.append(current_line.depth)
self.previous_defs.append(current_line)
if self.previous_line is None:
# Don't insert empty lines before the first line in the file.
return 0, 0
Expand Down
1 change: 1 addition & 0 deletions src/black/mode.py
Expand Up @@ -154,6 +154,7 @@ class Preview(Enum):
"""Individual preview style features."""

add_trailing_comma_consistently = auto()
blank_line_after_nested_stub_class = auto()
hex_codes_in_unicode_sequences = auto()
multiline_string_handling = auto()
prefer_splitting_right_hand_side_of_assignments = auto()
Expand Down
16 changes: 16 additions & 0 deletions tests/data/miscellaneous/nested_class_stub.pyi
@@ -0,0 +1,16 @@
class Outer:
class InnerStub: ...
outer_attr_after_inner_stub: int
class Inner:
inner_attr: int
outer_attr: int

# output
class Outer:
class InnerStub: ...
outer_attr_after_inner_stub: int

class Inner:
inner_attr: int

outer_attr: int
6 changes: 6 additions & 0 deletions tests/test_format.py
Expand Up @@ -186,6 +186,12 @@ def test_stub() -> None:
assert_format(source, expected, mode)


def test_nested_class_stub() -> None:
mode = replace(DEFAULT_MODE, is_pyi=True, preview=True)
source, expected = read_data("miscellaneous", "nested_class_stub.pyi")
assert_format(source, expected, mode)


def test_power_op_newline() -> None:
# requires line_length=0
source, expected = read_data("miscellaneous", "power_op_newline")
Expand Down