From 9e1cc30e08cc8f4e1d990a7529c2a93d43e62854 Mon Sep 17 00:00:00 2001 From: konstin Date: Fri, 8 Sep 2023 16:49:02 +0200 Subject: [PATCH 1/2] Blank line between nested and function def in stub files. 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 https://github.com/konstin/typeshed/pull/2. --- CHANGES.md | 2 + src/black/lines.py | 11 +++++ src/black/mode.py | 1 + .../data/miscellaneous/nested_class_stub.pyi | 16 ------- tests/data/miscellaneous/nested_stub.pyi | 43 +++++++++++++++++++ tests/test_format.py | 4 +- 6 files changed, 59 insertions(+), 18 deletions(-) delete mode 100644 tests/data/miscellaneous/nested_class_stub.pyi create mode 100644 tests/data/miscellaneous/nested_stub.pyi diff --git a/CHANGES.md b/CHANGES.md index af9fc490acf..2d48796c8f5 100644 --- a/CHANGES.md +++ b/CHANGES.md @@ -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 diff --git a/src/black/lines.py b/src/black/lines.py index 0a307b45eff..f1de1f59235 100644 --- a/src/black/lines.py +++ b/src/black/lines.py @@ -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: diff --git a/src/black/mode.py b/src/black/mode.py index 282c1669da7..c0b660aa6b6 100644 --- a/src/black/mode.py +++ b/src/black/mode.py @@ -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() diff --git a/tests/data/miscellaneous/nested_class_stub.pyi b/tests/data/miscellaneous/nested_class_stub.pyi deleted file mode 100644 index daf281b517b..00000000000 --- a/tests/data/miscellaneous/nested_class_stub.pyi +++ /dev/null @@ -1,16 +0,0 @@ -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/data/miscellaneous/nested_stub.pyi b/tests/data/miscellaneous/nested_stub.pyi new file mode 100644 index 00000000000..15e69d854db --- /dev/null +++ b/tests/data/miscellaneous/nested_stub.pyi @@ -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: ... \ No newline at end of file diff --git a/tests/test_format.py b/tests/test_format.py index fb4d8eb4346..ee658cdf3e5 100644 --- a/tests/test_format.py +++ b/tests/test_format.py @@ -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) From 870f12c027d113f0a708379f910956c8efc98af4 Mon Sep 17 00:00:00 2001 From: Jelle Zijlstra Date: Fri, 8 Sep 2023 08:17:23 -0700 Subject: [PATCH 2/2] Update src/black/lines.py --- src/black/lines.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/black/lines.py b/src/black/lines.py index f1de1f59235..01208c5fa7d 100644 --- a/src/black/lines.py +++ b/src/black/lines.py @@ -716,7 +716,7 @@ def _maybe_empty_lines_for_class_or_def( # noqa: C901 # 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 + Preview.blank_line_between_nested_and_def_stub_file in current_line.mode and self.previous_line.depth > current_line.depth ): newlines = 1