From d7a28dd78631fb962da95fb2d2de0e18ca6754a4 Mon Sep 17 00:00:00 2001 From: WMOkiishi Date: Sat, 18 Mar 2023 15:04:13 -0600 Subject: [PATCH] Enforce a blank line after a nested class in stubs (#3564) --- CHANGES.md | 2 ++ src/black/lines.py | 12 +++++++++--- src/black/mode.py | 1 + tests/data/miscellaneous/nested_class_stub.pyi | 16 ++++++++++++++++ tests/test_format.py | 6 ++++++ 5 files changed, 34 insertions(+), 3 deletions(-) create mode 100644 tests/data/miscellaneous/nested_class_stub.pyi diff --git a/CHANGES.md b/CHANGES.md index e2f21cf..029d0bc 100644 --- a/CHANGES.md +++ b/CHANGES.md @@ -23,6 +23,8 @@ compared to their non-async version. (#3609) - `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 diff --git a/src/black/lines.py b/src/black/lines.py index 66bba14..b2bdcc4 100644 --- a/src/black/lines.py +++ b/src/black/lines.py @@ -521,7 +521,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: @@ -577,12 +577,18 @@ class EmptyLineTracker: 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: @@ -637,7 +643,7 @@ class EmptyLineTracker: 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 diff --git a/src/black/mode.py b/src/black/mode.py index 6af0417..0511676 100644 --- a/src/black/mode.py +++ b/src/black/mode.py @@ -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() improved_async_statements_handling = auto() multiline_string_handling = auto() diff --git a/tests/data/miscellaneous/nested_class_stub.pyi b/tests/data/miscellaneous/nested_class_stub.pyi new file mode 100644 index 0000000..daf281b --- /dev/null +++ b/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 diff --git a/tests/test_format.py b/tests/test_format.py index ab849aa..3a6cbc9 100644 --- a/tests/test_format.py +++ b/tests/test_format.py @@ -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") -- 2.39.5