Skip to content

Commit

Permalink
Blank line between nested and function def in stub files.
Browse files Browse the repository at this point in the history
The idea behind this change is that we stop looking into previous body to determine if there should be a blank before a function or class definition.

Input:

```python
import sys

if sys.version_info > (3, 7):
    class Nested1:
        assignment = 1
        def function_definition(self): ...
    def f1(self) -> str: ...
    class Nested2:
        def function_definition(self): ...
        assignment = 1
    def f2(self) -> str: ...

if sys.version_info > (3, 7):
    def nested1():
        assignment = 1
        def function_definition(self): ...
    def f1(self) -> str: ...
    def nested2():
        def function_definition(self): ...
        assignment = 1
    def f2(self) -> str: ...
```

Stable style
```python
import sys

if sys.version_info > (3, 7):
    class Nested1:
        assignment = 1
        def function_definition(self): ...

    def f1(self) -> str: ...

    class Nested2:
        def function_definition(self): ...
        assignment = 1
    def f2(self) -> str: ...

if sys.version_info > (3, 7):
    def nested1():
        assignment = 1
        def function_definition(self): ...

    def f1(self) -> str: ...
    def nested2():
        def function_definition(self): ...
        assignment = 1
    def f2(self) -> str: ...
```

In the stable formatting, we have a blank line sometimes, not depending on the previous statement on the same level, but on the last (potentially nested) statement in the previous body.

#2783/#3564 fixes this for classes in preview style:

```python
import sys

if sys.version_info > (3, 7):
    class Nested1:
        assignment = 1
        def function_definition(self): ...

    def f1(self) -> str: ...

    class Nested2:
        def function_definition(self): ...
        assignment = 1

    def f2(self) -> str: ...

if sys.version_info > (3, 7):
    def nested1():
        assignment = 1
        def function_definition(self): ...

    def f1(self) -> str: ...
    def nested2():
        def function_definition(self): ...
        assignment = 1
    def f2(self) -> str: ...
```

This PR additionally fixes this for function definitions:

```python
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: ...

if sys.version_info > (3, 8):
    if sys.platform == "win32":
        assignment = 1
        def function_definition(self): ...

    class F1: ...
    if sys.platform != "win32":
        def function_definition(self): ...
        assignment = 1

    class F2: ...
```

You can see the effect of this change on typeshed in https://github.com/konstin/typeshed/pull/1/files. As baseline, the preview mode changes without this PR are at konstin/typeshed#2.
  • Loading branch information
konstin committed Sep 8, 2023
1 parent 8daa64a commit 9e1cc30
Show file tree
Hide file tree
Showing 6 changed files with 59 additions and 18 deletions.
2 changes: 2 additions & 0 deletions CHANGES.md
Original file line number Diff line number Diff line change
Expand Up @@ -170,6 +170,8 @@ expected to become part of Black's stable style in January 2024.
- For stubs, enforce one blank line after a nested class with a body other than just
`...` (#3564)
- Improve handling of multiline strings by changing line split behavior (#1879)
- In stub files, add a blank line between a statement with a body (e.g an
`if sys.version_info > (3, x):`) and a function definition on the same level. (#3862)

### Parser

Expand Down
11 changes: 11 additions & 0 deletions src/black/lines.py
Original file line number Diff line number Diff line change
Expand Up @@ -709,6 +709,17 @@ def _maybe_empty_lines_for_class_or_def( # noqa: C901
newlines = 0
else:
newlines = 1
# Remove case `self.previous_line.depth > current_line.depth` below when
# this becomes stable.
#
# Don't inspect the previous line if it's part of the body of the previous
# statement in the same level, we always want a blank line if there's
# something with a body preceding.
elif (
Preview.blank_line_between_nested_and_def_stub_file
and self.previous_line.depth > current_line.depth
):
newlines = 1
elif (
current_line.is_def or current_line.is_decorator
) and not self.previous_line.is_def:
Expand Down
1 change: 1 addition & 0 deletions src/black/mode.py
Original file line number Diff line number Diff line change
Expand Up @@ -170,6 +170,7 @@ class Preview(Enum):

add_trailing_comma_consistently = auto()
blank_line_after_nested_stub_class = auto()
blank_line_between_nested_and_def_stub_file = auto()
hex_codes_in_unicode_sequences = auto()
improved_async_statements_handling = auto()
multiline_string_handling = auto()
Expand Down
16 changes: 0 additions & 16 deletions tests/data/miscellaneous/nested_class_stub.pyi

This file was deleted.

43 changes: 43 additions & 0 deletions tests/data/miscellaneous/nested_stub.pyi
Original file line number Diff line number Diff line change
@@ -0,0 +1,43 @@
import sys

class Outer:
class InnerStub: ...
outer_attr_after_inner_stub: int
class Inner:
inner_attr: int
outer_attr: int

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

import sys

class Outer:
class InnerStub: ...
outer_attr_after_inner_stub: int

class Inner:
inner_attr: int

outer_attr: int

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: ...
4 changes: 2 additions & 2 deletions tests/test_format.py
Original file line number Diff line number Diff line change
Expand Up @@ -194,9 +194,9 @@ def test_stub() -> None:
assert_format(source, expected, mode)


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


Expand Down

0 comments on commit 9e1cc30

Please sign in to comment.