From a63d0619065900ec3f75a365f38fe16b48576e4a Mon Sep 17 00:00:00 2001 From: Yilei Yang Date: Fri, 30 Sep 2022 11:39:02 -0700 Subject: [PATCH 01/13] Enforce empty lines before classes/functions with leading comments. --- src/black/__init__.py | 19 ++- src/black/lines.py | 78 +++++++-- src/black/mode.py | 1 + tests/data/preview/comments9.py | 192 ++++++++++++++++++++++ tests/data/preview/remove_await_parens.py | 1 + tests/data/simple_cases/comments5.py | 6 +- 6 files changed, 274 insertions(+), 23 deletions(-) create mode 100644 tests/data/preview/comments9.py diff --git a/src/black/__init__.py b/src/black/__init__.py index 5293796aea1..8c256a0248a 100644 --- a/src/black/__init__.py +++ b/src/black/__init__.py @@ -61,7 +61,7 @@ unmask_cell, ) from black.linegen import LN, LineGenerator, transform_line -from black.lines import EmptyLineTracker, Line +from black.lines import EmptyLineTracker, LinesBlock from black.mode import ( FUTURE_FLAG_TO_FEATURE, VERSION_TO_FEATURES, @@ -1075,7 +1075,7 @@ def f( def _format_str_once(src_contents: str, *, mode: Mode) -> str: src_node = lib2to3_parse(src_contents.lstrip(), mode.target_versions) - dst_contents = [] + dst_blocks: List[LinesBlock] = [] if mode.target_versions: versions = mode.target_versions else: @@ -1084,22 +1084,23 @@ def _format_str_once(src_contents: str, *, mode: Mode) -> str: normalize_fmt_off(src_node, preview=mode.preview) lines = LineGenerator(mode=mode) - elt = EmptyLineTracker(is_pyi=mode.is_pyi) - empty_line = Line(mode=mode) - after = 0 + elt = EmptyLineTracker(mode=mode) split_line_features = { feature for feature in {Feature.TRAILING_COMMA_IN_CALL, Feature.TRAILING_COMMA_IN_DEF} if supports_feature(versions, feature) } + block: Optional[LinesBlock] = None for current_line in lines.visit(src_node): - dst_contents.append(str(empty_line) * after) - before, after = elt.maybe_empty_lines(current_line) - dst_contents.append(str(empty_line) * before) + block = elt.maybe_empty_lines(current_line) + dst_blocks.append(block) for line in transform_line( current_line, mode=mode, features=split_line_features ): - dst_contents.append(str(line)) + block.content_lines.append(str(line)) + dst_contents = [] + for block in dst_blocks: + dst_contents.extend(block.all_lines()) return "".join(dst_contents) diff --git a/src/black/lines.py b/src/black/lines.py index 30622650d53..2570d1627ac 100644 --- a/src/black/lines.py +++ b/src/black/lines.py @@ -448,6 +448,27 @@ def __bool__(self) -> bool: return bool(self.leaves or self.comments) +@dataclass +class LinesBlock: + """Class that holds information about a block of formatted lines. + + This is introduced so that the EmptyLineTracker can look behind the standalone + comments and adjust their empty lines for class or def lines. + """ + + mode: Mode + previous_block: Optional["LinesBlock"] + before: int = 0 + content_lines: List[str] = field(default_factory=list) + after: int = 0 + + def all_lines(self) -> List[str]: + empty_line = str(Line(mode=self.mode)) + return ( + [empty_line * self.before] + self.content_lines + [empty_line * self.after] + ) + + @dataclass class EmptyLineTracker: """Provides a stateful method that returns the number of potential extra @@ -458,33 +479,50 @@ class EmptyLineTracker: are consumed by `maybe_empty_lines()` and included in the computation. """ - is_pyi: bool = False + mode: Mode previous_line: Optional[Line] = None - previous_after: int = 0 + previous_block: Optional[LinesBlock] = None previous_defs: List[int] = field(default_factory=list) + semantic_leading_comment: Optional[LinesBlock] = None - def maybe_empty_lines(self, current_line: Line) -> Tuple[int, int]: + def maybe_empty_lines(self, current_line: Line) -> LinesBlock: """Return the number of extra empty lines before and after the `current_line`. This is for separating `def`, `async def` and `class` with extra empty lines (two on module-level). """ before, after = self._maybe_empty_lines(current_line) + previous_after = self.previous_block.after if self.previous_block else 0 before = ( # Black should not insert empty lines at the beginning # of the file 0 if self.previous_line is None - else before - self.previous_after + else before - previous_after ) - self.previous_after = after + block = LinesBlock(mode=self.mode, previous_block=self.previous_block) + block.before = before + block.after = after + + # Maintain the semantic_leading_comment state. + if current_line.is_comment: + if ( + self.semantic_leading_comment is None + or self.previous_line is None + or (before and not self.previous_line.is_decorator) + ): + self.semantic_leading_comment = block + elif not current_line.is_decorator: + self.semantic_leading_comment = None + self.previous_line = current_line - return before, after + self.previous_block = block + return block def _maybe_empty_lines(self, current_line: Line) -> Tuple[int, int]: max_allowed = 1 if current_line.depth == 0: - max_allowed = 1 if self.is_pyi else 2 + max_allowed = 1 if self.mode.is_pyi else 2 if current_line.leaves: # Consume the first leaf's extra newlines. first_leaf = current_line.leaves[0] @@ -495,7 +533,7 @@ def _maybe_empty_lines(self, current_line: Line) -> Tuple[int, int]: before = 0 depth = current_line.depth while self.previous_defs and self.previous_defs[-1] >= depth: - if self.is_pyi: + 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. @@ -563,7 +601,7 @@ def _maybe_empty_lines_for_class_or_def( return 0, 0 if self.previous_line.is_decorator: - if self.is_pyi and current_line.is_stub_class: + if self.mode.is_pyi and current_line.is_stub_class: # Insert an empty line after a decorated stub class return 0, 1 @@ -574,14 +612,25 @@ def _maybe_empty_lines_for_class_or_def( ): return 0, 0 + comment_to_add_newlines: Optional[LinesBlock] = None if ( self.previous_line.is_comment and self.previous_line.depth == current_line.depth and before == 0 ): - return 0, 0 + slc = self.semantic_leading_comment + if ( + Preview.empty_lines_before_class_or_def_with_leading_comments + in current_line.mode + and slc is not None + and slc.previous_block is not None + and slc.before <= 1 + ): + comment_to_add_newlines = slc + else: + return 0, 0 - if self.is_pyi: + if self.mode.is_pyi: if current_line.is_class or self.previous_line.is_class: if self.previous_line.depth < current_line.depth: newlines = 0 @@ -609,6 +658,13 @@ def _maybe_empty_lines_for_class_or_def( newlines = 0 else: newlines = 1 if current_line.depth else 2 + if comment_to_add_newlines is not None: + previous_block = comment_to_add_newlines.previous_block + if previous_block is not None: + comment_to_add_newlines.before = ( + max(comment_to_add_newlines.before, newlines) - previous_block.after + ) + newlines = 0 return newlines, 0 diff --git a/src/black/mode.py b/src/black/mode.py index e3c36450ed1..1e83f2a9c6d 100644 --- a/src/black/mode.py +++ b/src/black/mode.py @@ -150,6 +150,7 @@ class Preview(Enum): """Individual preview style features.""" annotation_parens = auto() + empty_lines_before_class_or_def_with_leading_comments = auto() long_docstring_quotes_on_newline = auto() normalize_docstring_quotes_and_prefixes_properly = auto() one_element_subscript = auto() diff --git a/tests/data/preview/comments9.py b/tests/data/preview/comments9.py new file mode 100644 index 00000000000..e70b1209195 --- /dev/null +++ b/tests/data/preview/comments9.py @@ -0,0 +1,192 @@ +# Test for https://github.com/psf/black/issues/246. + +some = statement +# This comment should be split from the statement above by two lines. +def function(): + pass + + +some = statement +# This comment should be split from the statement above by two lines. +async def async_function(): + pass + + +some = statement +# This comment should be split from the statement above by two lines. +class MyClass: + pass + + +some = statement +# This should be stick to the statement above + +# This should be split from the above by two lines +class MyClassWithComplexLeadingComments: + pass + + +class ClassWithDocstring: + """A docstring.""" +# Leading comment after a class with just a docstring +class MyClassAfterAnotherClassWithDocstring: + pass + + +some = statement +# leading 1 +@deco1 +# leading 2 +@deco2(with_args=True) +# leading 3 +@deco3 +def decorated(): + pass + + +some = statement +# leading 1 +@deco1 +# leading 2 +@deco2(with_args=True) + +# leading 3 that already has an empty line +@deco3 +def decorated_with_split_leading_comments(): + pass + + +def main(): + if a: + # Leading comment before inline function + def inline(): + pass + # Another leading comment + def another_inline(): + pass + else: + # More leading comments + def inline_after_else(): + pass + + +if a: + # Leading comment before "top-level inline" function + def top_level_quote_inline(): + pass + # Another leading comment + def another_top_level_quote_inline_inline(): + pass +else: + # More leading comments + def top_level_quote_inline_after_else(): + pass + + +# output + + +# Test for https://github.com/psf/black/issues/246. + +some = statement + + +# This comment should be split from the statement above by two lines. +def function(): + pass + + +some = statement + + +# This comment should be split from the statement above by two lines. +async def async_function(): + pass + + +some = statement + + +# This comment should be split from the statement above by two lines. +class MyClass: + pass + + +some = statement +# This should be stick to the statement above + + +# This should be split from the above by two lines +class MyClassWithComplexLeadingComments: + pass + + +class ClassWithDocstring: + """A docstring.""" + + +# Leading comment after a class with just a docstring +class MyClassAfterAnotherClassWithDocstring: + pass + + +some = statement + + +# leading 1 +@deco1 +# leading 2 +@deco2(with_args=True) +# leading 3 +@deco3 +def decorated(): + pass + + +some = statement + + +# leading 1 +@deco1 +# leading 2 +@deco2(with_args=True) + +# leading 3 that already has an empty line +@deco3 +def decorated_with_split_leading_comments(): + pass + + +def main(): + if a: + + # Leading comment before inline function + def inline(): + pass + + # Another leading comment + def another_inline(): + pass + + else: + + # More leading comments + def inline_after_else(): + pass + + +if a: + + # Leading comment before "top-level inline" function + def top_level_quote_inline(): + pass + + # Another leading comment + def another_top_level_quote_inline_inline(): + pass + +else: + + # More leading comments + def top_level_quote_inline_after_else(): + pass diff --git a/tests/data/preview/remove_await_parens.py b/tests/data/preview/remove_await_parens.py index eb7dad340c3..571210a2d80 100644 --- a/tests/data/preview/remove_await_parens.py +++ b/tests/data/preview/remove_await_parens.py @@ -80,6 +80,7 @@ async def main(): # output import asyncio + # Control example async def main(): await asyncio.sleep(1) diff --git a/tests/data/simple_cases/comments5.py b/tests/data/simple_cases/comments5.py index d83b6b8ff47..c8c38813d55 100644 --- a/tests/data/simple_cases/comments5.py +++ b/tests/data/simple_cases/comments5.py @@ -58,9 +58,9 @@ def decorated1(): ... -# Note: crappy but inevitable. The current design of EmptyLineTracker doesn't -# allow this to work correctly. The user will have to split those lines by -# hand. +# Note: this is fixed in +# Preview.empty_lines_before_class_or_def_with_leading_comments. +# In the current style, the user will have to split those lines by hand. some_instruction # This comment should be split from `some_instruction` by two lines but isn't. def g(): From 194bb07c62137deb53e40d13b60ad40b995adaa2 Mon Sep 17 00:00:00 2001 From: Yilei Yang Date: Fri, 30 Sep 2022 11:40:52 -0700 Subject: [PATCH 02/13] Update CHANGES.md --- CHANGES.md | 2 ++ 1 file changed, 2 insertions(+) diff --git a/CHANGES.md b/CHANGES.md index 3a8cf4d6af5..ba970547ee4 100644 --- a/CHANGES.md +++ b/CHANGES.md @@ -14,6 +14,8 @@ +- Enforce empty lines before classes and functions with sticky leading comments (#3302) + ### Configuration From 4e38db730188a4341cf4fb62bb9915b2c8e481b1 Mon Sep 17 00:00:00 2001 From: Yilei Yang Date: Fri, 30 Sep 2022 11:45:01 -0700 Subject: [PATCH 03/13] Update future_style.md --- docs/the_black_code_style/future_style.md | 49 ++++++++++++++++------- 1 file changed, 35 insertions(+), 14 deletions(-) diff --git a/docs/the_black_code_style/future_style.md b/docs/the_black_code_style/future_style.md index a028a2888ed..17b7eef092f 100644 --- a/docs/the_black_code_style/future_style.md +++ b/docs/the_black_code_style/future_style.md @@ -63,26 +63,47 @@ limit. Line continuation backslashes are converted into parenthesized strings. Unnecessary parentheses are stripped. The stability and status of this feature is tracked in [this issue](https://github.com/psf/black/issues/2188). -### Removing newlines in the beginning of code blocks +### Improved empty line management -_Black_ will remove newlines in the beginning of new code blocks, i.e. when the -indentation level is increased. For example: +1. _Black_ will remove newlines in the beginning of new code blocks, i.e. when the + indentation level is increased. For example: -```python -def my_func(): + ```python + def my_func(): - print("The line above me will be deleted!") -``` + print("The line above me will be deleted!") + ``` -will be changed to: + will be changed to: + + ```python + def my_func(): + print("The line above me will be deleted!") + ``` + + This new feature will be applied to **all code blocks**: `def`, `class`, `if`, + `for`, `while`, `with`, `case` and `match`. + +2. _Black_ will enforce empty lines before classes and functions with leading comments. + For example: + + ```python + some_var = 1 + # Leading sticky comment + def my_func(): + ... + ``` + + will be changed to: + + ```python + some_var = 1 -```python -def my_func(): - print("The line above me will be deleted!") -``` -This new feature will be applied to **all code blocks**: `def`, `class`, `if`, `for`, -`while`, `with`, `case` and `match`. + # Leading sticky comment + def my_func(): + ... + ``` ### Improved parentheses management From d24b21694d7db39bf4ead667b845cb61d67207ae Mon Sep 17 00:00:00 2001 From: Yilei Yang Date: Fri, 30 Sep 2022 11:55:24 -0700 Subject: [PATCH 04/13] Update reference_classes.rst --- .../reference/reference_classes.rst | 22 ++++++++++++------- 1 file changed, 14 insertions(+), 8 deletions(-) diff --git a/docs/contributing/reference/reference_classes.rst b/docs/contributing/reference/reference_classes.rst index fa765961e69..3931e0e0072 100644 --- a/docs/contributing/reference/reference_classes.rst +++ b/docs/contributing/reference/reference_classes.rst @@ -11,23 +11,29 @@ .. autoclass:: black.brackets.BracketTracker :members: -:class:`EmptyLineTracker` +:class:`Line` +------------- + +.. autoclass:: black.lines.Line + :members: + :special-members: __str__, __bool__ + +:class:`LinesBlock` ------------------------- -.. autoclass:: black.EmptyLineTracker +.. autoclass:: black.lines.LinesBlock :members: -:class:`Line` -------------- +:class:`EmptyLineTracker` +------------------------- -.. autoclass:: black.Line +.. autoclass:: black.lines.EmptyLineTracker :members: - :special-members: __str__, __bool__ :class:`LineGenerator` ---------------------- -.. autoclass:: black.LineGenerator +.. autoclass:: black.linegen.LineGenerator :show-inheritance: :members: @@ -40,7 +46,7 @@ :class:`Report` --------------- -.. autoclass:: black.Report +.. autoclass:: black.report.Report :members: :special-members: __str__ From 4b8f7ec10e6c3a7f26aba5237890cee62bcc8515 Mon Sep 17 00:00:00 2001 From: Yilei Yang Date: Fri, 30 Sep 2022 11:56:09 -0700 Subject: [PATCH 05/13] Reformat test_blackd.py. --- tests/test_blackd.py | 1 + 1 file changed, 1 insertion(+) diff --git a/tests/test_blackd.py b/tests/test_blackd.py index 5b6461f7685..4e42315129f 100644 --- a/tests/test_blackd.py +++ b/tests/test_blackd.py @@ -23,6 +23,7 @@ try: from aiohttp.test_utils import unittest_run_loop except ImportError: + # unittest_run_loop is unnecessary and a no-op since aiohttp 3.8, and # aiohttp 4 removed it. To maintain compatibility we can make our own # no-op decorator. From 20a15e2d1a0273d4bcbab3fe77411f3f09c806e5 Mon Sep 17 00:00:00 2001 From: Yilei Yang Date: Fri, 30 Sep 2022 12:25:21 -0700 Subject: [PATCH 06/13] Fix a bug where empty line is added before eof. --- src/black/__init__.py | 2 ++ .../simple_cases/docstring_no_extra_empty_line_before_eof.py | 4 ++++ 2 files changed, 6 insertions(+) create mode 100644 tests/data/simple_cases/docstring_no_extra_empty_line_before_eof.py diff --git a/src/black/__init__.py b/src/black/__init__.py index 8c256a0248a..d9fba41ebd3 100644 --- a/src/black/__init__.py +++ b/src/black/__init__.py @@ -1098,6 +1098,8 @@ def _format_str_once(src_contents: str, *, mode: Mode) -> str: current_line, mode=mode, features=split_line_features ): block.content_lines.append(str(line)) + if dst_blocks: + dst_blocks[-1].after = 0 dst_contents = [] for block in dst_blocks: dst_contents.extend(block.all_lines()) diff --git a/tests/data/simple_cases/docstring_no_extra_empty_line_before_eof.py b/tests/data/simple_cases/docstring_no_extra_empty_line_before_eof.py new file mode 100644 index 00000000000..6fea860adf6 --- /dev/null +++ b/tests/data/simple_cases/docstring_no_extra_empty_line_before_eof.py @@ -0,0 +1,4 @@ +# Make sure when the file ends with class's docstring, +# It doesn't add extra blank lines. +class ClassWithDocstring: + """A docstring.""" From 41110a205270fcfffe0570361d40035d0dba7eef Mon Sep 17 00:00:00 2001 From: Yilei Yang Date: Tue, 4 Oct 2022 19:26:17 -0700 Subject: [PATCH 07/13] Fix a bug where two empty lines were added before class's leading comment but after decorators. --- src/black/lines.py | 7 +++---- tests/data/preview/comments9.py | 32 ++++++++++++++++++++++++++++++++ 2 files changed, 35 insertions(+), 4 deletions(-) diff --git a/src/black/lines.py b/src/black/lines.py index 2570d1627ac..2fa1998235c 100644 --- a/src/black/lines.py +++ b/src/black/lines.py @@ -506,10 +506,9 @@ def maybe_empty_lines(self, current_line: Line) -> LinesBlock: # Maintain the semantic_leading_comment state. if current_line.is_comment: - if ( - self.semantic_leading_comment is None - or self.previous_line is None - or (before and not self.previous_line.is_decorator) + if self.previous_line is None or ( + not self.previous_line.is_decorator + and (self.semantic_leading_comment is None or before) ): self.semantic_leading_comment = block elif not current_line.is_decorator: diff --git a/tests/data/preview/comments9.py b/tests/data/preview/comments9.py index e70b1209195..18966c16ff2 100644 --- a/tests/data/preview/comments9.py +++ b/tests/data/preview/comments9.py @@ -40,6 +40,7 @@ class MyClassAfterAnotherClassWithDocstring: @deco2(with_args=True) # leading 3 @deco3 +# leading 4 def decorated(): pass @@ -52,6 +53,20 @@ def decorated(): # leading 3 that already has an empty line @deco3 +# leading 4 +def decorated_with_split_leading_comments(): + pass + + +some = statement +# leading 1 +@deco1 +# leading 2 +@deco2(with_args=True) +# leading 3 +@deco3 + +# leading 4 that already has an empty line def decorated_with_split_leading_comments(): pass @@ -139,6 +154,7 @@ class MyClassAfterAnotherClassWithDocstring: @deco2(with_args=True) # leading 3 @deco3 +# leading 4 def decorated(): pass @@ -153,6 +169,22 @@ def decorated(): # leading 3 that already has an empty line @deco3 +# leading 4 +def decorated_with_split_leading_comments(): + pass + + +some = statement + + +# leading 1 +@deco1 +# leading 2 +@deco2(with_args=True) +# leading 3 +@deco3 + +# leading 4 that already has an empty line def decorated_with_split_leading_comments(): pass From 3318fd54d8d2263b374138d7faa9eba8dafc0d9f Mon Sep 17 00:00:00 2001 From: Yilei Yang Date: Tue, 4 Oct 2022 20:06:52 -0700 Subject: [PATCH 08/13] Fix an issue where an empty line was added right after a bare class def. --- src/black/lines.py | 8 +++++++- tests/data/preview/comments9.py | 14 ++++++++++++++ 2 files changed, 21 insertions(+), 1 deletion(-) diff --git a/src/black/lines.py b/src/black/lines.py index 2fa1998235c..9f0471f9413 100644 --- a/src/black/lines.py +++ b/src/black/lines.py @@ -458,6 +458,7 @@ class LinesBlock: mode: Mode previous_block: Optional["LinesBlock"] + is_class: bool # Whether the original line is a class def. before: int = 0 content_lines: List[str] = field(default_factory=list) after: int = 0 @@ -500,7 +501,11 @@ def maybe_empty_lines(self, current_line: Line) -> LinesBlock: if self.previous_line is None else before - previous_after ) - block = LinesBlock(mode=self.mode, previous_block=self.previous_block) + block = LinesBlock( + mode=self.mode, + previous_block=self.previous_block, + is_class=current_line.is_class, + ) block.before = before block.after = after @@ -623,6 +628,7 @@ def _maybe_empty_lines_for_class_or_def( in current_line.mode and slc is not None and slc.previous_block is not None + and not slc.previous_block.is_class and slc.before <= 1 ): comment_to_add_newlines = slc diff --git a/tests/data/preview/comments9.py b/tests/data/preview/comments9.py index 18966c16ff2..1919a97fe45 100644 --- a/tests/data/preview/comments9.py +++ b/tests/data/preview/comments9.py @@ -98,6 +98,13 @@ def top_level_quote_inline_after_else(): pass +class MyClass: + # First method has no empty lines between bare class def. + # More comments. + def first_method(self): + pass + + # output @@ -222,3 +229,10 @@ def another_top_level_quote_inline_inline(): # More leading comments def top_level_quote_inline_after_else(): pass + + +class MyClass: + # First method has no empty lines between bare class def. + # More comments. + def first_method(self): + pass From 524cc6bd0d86f0695844c6152f360b23294aabca Mon Sep 17 00:00:00 2001 From: Yilei Yang Date: Tue, 4 Oct 2022 20:39:24 -0700 Subject: [PATCH 09/13] Fix an issue where empty lines were added between comments inside decorators. --- src/black/lines.py | 2 ++ tests/data/preview/comments9.py | 2 ++ 2 files changed, 4 insertions(+) diff --git a/src/black/lines.py b/src/black/lines.py index 9f0471f9413..f7c8b5fec13 100644 --- a/src/black/lines.py +++ b/src/black/lines.py @@ -513,6 +513,8 @@ def maybe_empty_lines(self, current_line: Line) -> LinesBlock: if current_line.is_comment: if self.previous_line is None or ( not self.previous_line.is_decorator + # `or before` means this comment already has an empty line before + and (not self.previous_line.is_comment or before) and (self.semantic_leading_comment is None or before) ): self.semantic_leading_comment = block diff --git a/tests/data/preview/comments9.py b/tests/data/preview/comments9.py index 1919a97fe45..359d1412158 100644 --- a/tests/data/preview/comments9.py +++ b/tests/data/preview/comments9.py @@ -37,6 +37,7 @@ class MyClassAfterAnotherClassWithDocstring: # leading 1 @deco1 # leading 2 +# leading 2 extra @deco2(with_args=True) # leading 3 @deco3 @@ -158,6 +159,7 @@ class MyClassAfterAnotherClassWithDocstring: # leading 1 @deco1 # leading 2 +# leading 2 extra @deco2(with_args=True) # leading 3 @deco3 From e0e60ff13dfcc7b1d23f22c552908296a0507ee3 Mon Sep 17 00:00:00 2001 From: "Yilei \"Dolee\" Yang" Date: Thu, 6 Oct 2022 14:38:18 -0700 Subject: [PATCH 10/13] Move before/after to init Co-authored-by: Jelle Zijlstra --- src/black/lines.py | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/src/black/lines.py b/src/black/lines.py index f7c8b5fec13..3fc3859e57c 100644 --- a/src/black/lines.py +++ b/src/black/lines.py @@ -505,9 +505,9 @@ def maybe_empty_lines(self, current_line: Line) -> LinesBlock: mode=self.mode, previous_block=self.previous_block, is_class=current_line.is_class, + before=before, + after=after, ) - block.before = before - block.after = after # Maintain the semantic_leading_comment state. if current_line.is_comment: From 3ff271098f133a1c1054d1d11c9fc61c62d14404 Mon Sep 17 00:00:00 2001 From: Yilei Yang Date: Fri, 7 Oct 2022 15:07:13 -0700 Subject: [PATCH 11/13] Add a test case for multiple lines of comment block. --- tests/data/preview/comments9.py | 18 ++++++++++++++++++ 1 file changed, 18 insertions(+) diff --git a/tests/data/preview/comments9.py b/tests/data/preview/comments9.py index 359d1412158..c0eec117048 100644 --- a/tests/data/preview/comments9.py +++ b/tests/data/preview/comments9.py @@ -6,6 +6,14 @@ def function(): pass +some = statement +# This multiline comments section +# should be split from the statement +# above by two lines. +def function(): + pass + + some = statement # This comment should be split from the statement above by two lines. async def async_function(): @@ -122,6 +130,16 @@ def function(): some = statement +# This multiline comments section +# should be split from the statement +# above by two lines. +def function(): + pass + + +some = statement + + # This comment should be split from the statement above by two lines. async def async_function(): pass From 42212a36a5deb750bfd08786c642a4fcbe1706fa Mon Sep 17 00:00:00 2001 From: Yilei Yang Date: Mon, 10 Oct 2022 10:16:49 -0700 Subject: [PATCH 12/13] For now, do not add empty line after block open. --- src/black/lines.py | 7 ++++--- tests/data/preview/comments9.py | 4 ---- 2 files changed, 4 insertions(+), 7 deletions(-) diff --git a/src/black/lines.py b/src/black/lines.py index 3fc3859e57c..0d074534def 100644 --- a/src/black/lines.py +++ b/src/black/lines.py @@ -458,7 +458,7 @@ class LinesBlock: mode: Mode previous_block: Optional["LinesBlock"] - is_class: bool # Whether the original line is a class def. + original_line: Line before: int = 0 content_lines: List[str] = field(default_factory=list) after: int = 0 @@ -504,7 +504,7 @@ def maybe_empty_lines(self, current_line: Line) -> LinesBlock: block = LinesBlock( mode=self.mode, previous_block=self.previous_block, - is_class=current_line.is_class, + original_line=current_line, before=before, after=after, ) @@ -630,7 +630,8 @@ def _maybe_empty_lines_for_class_or_def( in current_line.mode and slc is not None and slc.previous_block is not None - and not slc.previous_block.is_class + and not slc.previous_block.original_line.is_class + and not slc.previous_block.original_line.opens_block and slc.before <= 1 ): comment_to_add_newlines = slc diff --git a/tests/data/preview/comments9.py b/tests/data/preview/comments9.py index c0eec117048..449612c037a 100644 --- a/tests/data/preview/comments9.py +++ b/tests/data/preview/comments9.py @@ -218,7 +218,6 @@ def decorated_with_split_leading_comments(): def main(): if a: - # Leading comment before inline function def inline(): pass @@ -228,14 +227,12 @@ def another_inline(): pass else: - # More leading comments def inline_after_else(): pass if a: - # Leading comment before "top-level inline" function def top_level_quote_inline(): pass @@ -245,7 +242,6 @@ def another_top_level_quote_inline_inline(): pass else: - # More leading comments def top_level_quote_inline_after_else(): pass From 3c71bd64806dce37fa64b2afacaefff72c9e9d29 Mon Sep 17 00:00:00 2001 From: Yilei Yang Date: Mon, 10 Oct 2022 10:20:48 -0700 Subject: [PATCH 13/13] Format code. --- tests/test_blackd.py | 1 - 1 file changed, 1 deletion(-) diff --git a/tests/test_blackd.py b/tests/test_blackd.py index 4e42315129f..5b6461f7685 100644 --- a/tests/test_blackd.py +++ b/tests/test_blackd.py @@ -23,7 +23,6 @@ try: from aiohttp.test_utils import unittest_run_loop except ImportError: - # unittest_run_loop is unnecessary and a no-op since aiohttp 3.8, and # aiohttp 4 removed it. To maintain compatibility we can make our own # no-op decorator.