From a6b8d300ed6028cd9b24e033e96eb52f6b1e19d3 Mon Sep 17 00:00:00 2001 From: jpy-git Date: Tue, 5 Apr 2022 21:26:11 +0100 Subject: [PATCH 01/12] Refactor only --- src/black/__init__.py | 4 +- src/black/lines.py | 92 +++++++++++++++++++++++-------------------- 2 files changed, 50 insertions(+), 46 deletions(-) diff --git a/src/black/__init__.py b/src/black/__init__.py index bdeb73273bc..ab52ccf5220 100644 --- a/src/black/__init__.py +++ b/src/black/__init__.py @@ -1173,15 +1173,13 @@ def _format_str_once(src_contents: str, *, mode: Mode) -> str: lines = LineGenerator(mode=mode) elt = EmptyLineTracker(is_pyi=mode.is_pyi) empty_line = Line(mode=mode) - after = 0 split_line_features = { feature for feature in {Feature.TRAILING_COMMA_IN_CALL, Feature.TRAILING_COMMA_IN_DEF} if supports_feature(versions, feature) } for current_line in lines.visit(src_node): - dst_contents.append(str(empty_line) * after) - before, after = elt.maybe_empty_lines(current_line) + before = elt.maybe_empty_lines(current_line) dst_contents.append(str(empty_line) * before) for line in transform_line( current_line, mode=mode, features=split_line_features diff --git a/src/black/lines.py b/src/black/lines.py index e455a507539..5efa239827f 100644 --- a/src/black/lines.py +++ b/src/black/lines.py @@ -419,7 +419,7 @@ def __bool__(self) -> bool: @dataclass class EmptyLineTracker: """Provides a stateful method that returns the number of potential extra - empty lines needed before and after the currently processed line. + empty lines needed before the currently processed line. Note: this tracker works on lines that haven't been split yet. It assumes the prefix of the first leaf consists of optional newlines. Those newlines @@ -427,29 +427,27 @@ class EmptyLineTracker: """ is_pyi: bool = False - previous_line: Optional[Line] = None - previous_after: int = 0 + previous_lines: List[Line] = field(default_factory=list) previous_defs: List[int] = field(default_factory=list) - def maybe_empty_lines(self, current_line: Line) -> Tuple[int, int]: + def maybe_empty_lines(self, current_line: Line) -> int: """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) + before = self._maybe_empty_lines(current_line) 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 + if not self.previous_lines + else before ) - self.previous_after = after - self.previous_line = current_line - return before, after + self.previous_lines.append(current_line) + return before - def _maybe_empty_lines(self, current_line: Line) -> Tuple[int, int]: + def _maybe_empty_lines(self, current_line: Line) -> int: max_allowed = 1 if current_line.depth == 0: max_allowed = 1 if self.is_pyi else 2 @@ -464,8 +462,8 @@ def _maybe_empty_lines(self, current_line: Line) -> Tuple[int, int]: depth = current_line.depth while self.previous_defs and self.previous_defs[-1] >= depth: if self.is_pyi: - assert self.previous_line is not None - if depth and not current_line.is_def and self.previous_line.is_def: + assert self.previous_lines + if depth and not current_line.is_def and self.previous_lines[-1].is_def: # Empty lines between attributes and methods should be preserved. before = min(1, before) elif depth: @@ -499,64 +497,72 @@ def _maybe_empty_lines(self, current_line: Line) -> Tuple[int, int]: return self._maybe_empty_lines_for_class_or_def(current_line, before) if ( - self.previous_line - and self.previous_line.is_import + self.previous_lines + and self.previous_lines[-1].is_import and not current_line.is_import - and depth == self.previous_line.depth + and depth == self.previous_lines[-1].depth ): - return (before or 1), 0 + return before or 1 if ( - self.previous_line - and self.previous_line.is_class - and current_line.is_triple_quoted_string + len(self.previous_lines) > 1 + and self.previous_lines[-2].is_class + and self.previous_lines[-1].is_triple_quoted_string + and current_line.depth == self.previous_lines[-1].depth ): - return before, 1 + return 1 - return before, 0 + return before def _maybe_empty_lines_for_class_or_def( self, current_line: Line, before: int - ) -> Tuple[int, int]: + ) -> int: if not current_line.is_decorator: self.previous_defs.append(current_line.depth) - if self.previous_line is None: + if not self.previous_lines: # Don't insert empty lines before the first line in the file. - return 0, 0 + return 0 - if self.previous_line.is_decorator: - if self.is_pyi and current_line.is_stub_class: - # Insert an empty line after a decorated stub class - return 0, 1 + if self.previous_lines[-1].is_decorator: + return 0 - return 0, 0 + if ( + self.is_pyi + and len(self.previous_lines) > 1 + and self.previous_lines[-1].is_stub_class + and self.previous_lines[-2].is_decorator + ): + # Insert an empty line after a decorated stub class + return 1 - if self.previous_line.depth < current_line.depth and ( - self.previous_line.is_class or self.previous_line.is_def + if self.previous_lines[-1].depth < current_line.depth and ( + self.previous_lines[-1].is_class or self.previous_lines[-1].is_def ): - return 0, 0 + return 0 if ( - self.previous_line.is_comment - and self.previous_line.depth == current_line.depth + self.previous_lines[-1].is_comment + and self.previous_lines[-1].depth == current_line.depth and before == 0 ): - return 0, 0 + return 0 if self.is_pyi: - if current_line.is_class or self.previous_line.is_class: - if self.previous_line.depth < current_line.depth: + if current_line.is_class or self.previous_lines[-1].is_class: + if self.previous_lines[-1].depth < current_line.depth: newlines = 0 - elif self.previous_line.depth > current_line.depth: + elif self.previous_lines[-1].depth > current_line.depth: newlines = 1 - elif current_line.is_stub_class and self.previous_line.is_stub_class: + elif ( + current_line.is_stub_class and self.previous_lines[-1].is_stub_class + ): # No blank line between classes with an empty body newlines = 0 else: newlines = 1 elif ( current_line.is_def or current_line.is_decorator - ) and not self.previous_line.is_def: + ) and not self.previous_lines[-1].is_def: if current_line.depth: # In classes empty lines between attributes and methods should # be preserved. @@ -565,13 +571,13 @@ def _maybe_empty_lines_for_class_or_def( # Blank line between a block of functions (maybe with preceding # decorators) and a block of non-functions newlines = 1 - elif self.previous_line.depth > current_line.depth: + elif self.previous_lines[-1].depth > current_line.depth: newlines = 1 else: newlines = 0 else: newlines = 1 if current_line.depth else 2 - return newlines, 0 + return newlines def enumerate_reversed(sequence: Sequence[T]) -> Iterator[Tuple[Index, T]]: From 37d34539baf1655db2899cb0ac0817bc3a5715fa Mon Sep 17 00:00:00 2001 From: jpy-git Date: Tue, 5 Apr 2022 22:05:13 +0100 Subject: [PATCH 02/12] Standardise newlines after module-level docstrings --- CHANGES.md | 1 + src/black/__init__.py | 2 +- src/black/lines.py | 12 ++++++ src/black/mode.py | 1 - src/black/nodes.py | 1 - src/black/output.py | 1 - src/black/strings.py | 1 - tests/data/module_docstring_1.py | 25 ++++++++++++ tests/data/module_docstring_2.py | 66 ++++++++++++++++++++++++++++++++ tests/optional.py | 1 - tests/test_format.py | 2 + 11 files changed, 107 insertions(+), 6 deletions(-) create mode 100644 tests/data/module_docstring_1.py create mode 100644 tests/data/module_docstring_2.py diff --git a/CHANGES.md b/CHANGES.md index 30c00566b3c..244c0182b88 100644 --- a/CHANGES.md +++ b/CHANGES.md @@ -15,6 +15,7 @@ - Remove unnecessary parentheses from `with` statements (#2926) +- Standardise newlines after module-level docstrings (#2996) ### _Blackd_ diff --git a/src/black/__init__.py b/src/black/__init__.py index ab52ccf5220..05c8e20c334 100644 --- a/src/black/__init__.py +++ b/src/black/__init__.py @@ -1171,7 +1171,7 @@ 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) + elt = EmptyLineTracker(is_pyi=mode.is_pyi, preview=mode.preview) empty_line = Line(mode=mode) split_line_features = { feature diff --git a/src/black/lines.py b/src/black/lines.py index 5efa239827f..f43b8281000 100644 --- a/src/black/lines.py +++ b/src/black/lines.py @@ -429,6 +429,7 @@ class EmptyLineTracker: is_pyi: bool = False previous_lines: List[Line] = field(default_factory=list) previous_defs: List[int] = field(default_factory=list) + preview: bool = False def maybe_empty_lines(self, current_line: Line) -> int: """Return the number of extra empty lines before and after the `current_line`. @@ -444,6 +445,17 @@ def maybe_empty_lines(self, current_line: Line) -> int: if not self.previous_lines else before ) + if ( + self.preview + and len(self.previous_lines) == 1 + and self.previous_lines[-1].is_triple_quoted_string + ): + # Newlines after multi-line module level docstring. + if str(self.previous_lines[-1].leaves[0]).count("\n") >= 1: + before = 0 + else: + before = 1 + self.previous_lines.append(current_line) return before diff --git a/src/black/mode.py b/src/black/mode.py index 6b74c14b6de..7b48024c3d4 100644 --- a/src/black/mode.py +++ b/src/black/mode.py @@ -3,7 +3,6 @@ Mostly around Python language feature support per version and Black configuration chosen by the user. """ - from hashlib import sha256 import sys diff --git a/src/black/nodes.py b/src/black/nodes.py index d18d4bde872..3a0e2e328c9 100644 --- a/src/black/nodes.py +++ b/src/black/nodes.py @@ -1,7 +1,6 @@ """ blib2to3 Node/Leaf transformation-related utility functions. """ - import sys from typing import ( Generic, diff --git a/src/black/output.py b/src/black/output.py index 9561d4b57d2..269d96973b9 100644 --- a/src/black/output.py +++ b/src/black/output.py @@ -2,7 +2,6 @@ The double calls are for patching purposes in tests. """ - import json from typing import Any, Optional from mypy_extensions import mypyc_attr diff --git a/src/black/strings.py b/src/black/strings.py index 9d0e2eb8430..8fb84758896 100644 --- a/src/black/strings.py +++ b/src/black/strings.py @@ -1,7 +1,6 @@ """ Simple formatting on strings. Further string formatting code is in trans.py. """ - import re import sys from functools import lru_cache diff --git a/tests/data/module_docstring_1.py b/tests/data/module_docstring_1.py new file mode 100644 index 00000000000..5751154f7f0 --- /dev/null +++ b/tests/data/module_docstring_1.py @@ -0,0 +1,25 @@ +"""Single line module-level docstring should be followed by single newline.""" + + + + +a = 1 + + +"""I'm just a string so should be followed by 2 newlines.""" + + + + +b = 2 + +# output +"""Single line module-level docstring should be followed by single newline.""" + +a = 1 + + +"""I'm just a string so should be followed by 2 newlines.""" + + +b = 2 diff --git a/tests/data/module_docstring_2.py b/tests/data/module_docstring_2.py new file mode 100644 index 00000000000..3e250fac54c --- /dev/null +++ b/tests/data/module_docstring_2.py @@ -0,0 +1,66 @@ +"""I am a very helpful module docstring. + +Lorem ipsum dolor sit amet, consectetur adipiscing elit, +sed do eiusmod tempor incididunt ut labore et dolore magna aliqua. +Ut enim ad minim veniam, +quis nostrud exercitation ullamco laboris +nisi ut aliquip ex ea commodo consequat. +Duis aute irure dolor in reprehenderit in voluptate +velit esse cillum dolore eu fugiat nulla pariatur. +Excepteur sint occaecat cupidatat non proident, +sunt in culpa qui officia deserunt mollit anim id est laborum. +""" + + + + +a = 1 + + +"""Look at me I'm a docstring... + +............................................................ +............................................................ +............................................................ +............................................................ +............................................................ +............................................................ +............................................................ +........................................................NOT! +""" + + + + +b = 2 + +# output +"""I am a very helpful module docstring. + +Lorem ipsum dolor sit amet, consectetur adipiscing elit, +sed do eiusmod tempor incididunt ut labore et dolore magna aliqua. +Ut enim ad minim veniam, +quis nostrud exercitation ullamco laboris +nisi ut aliquip ex ea commodo consequat. +Duis aute irure dolor in reprehenderit in voluptate +velit esse cillum dolore eu fugiat nulla pariatur. +Excepteur sint occaecat cupidatat non proident, +sunt in culpa qui officia deserunt mollit anim id est laborum. +""" +a = 1 + + +"""Look at me I'm a docstring... + +............................................................ +............................................................ +............................................................ +............................................................ +............................................................ +............................................................ +............................................................ +........................................................NOT! +""" + + +b = 2 diff --git a/tests/optional.py b/tests/optional.py index a4e9441ef1c..3d10fe1d052 100644 --- a/tests/optional.py +++ b/tests/optional.py @@ -13,7 +13,6 @@ Adapted from https://pypi.org/project/pytest-optional-tests/, (c) 2019 Reece Hart """ - from functools import lru_cache import itertools import logging diff --git a/tests/test_format.py b/tests/test_format.py index d80eaa730cd..422e94aa62d 100644 --- a/tests/test_format.py +++ b/tests/test_format.py @@ -83,6 +83,8 @@ "remove_except_parens", "remove_for_brackets", "one_element_subscript", + "module_docstring_1", + "module_docstring_2", ] SOURCES: List[str] = [ From f64cd776f1828f62e0105922ee829242fd1b707a Mon Sep 17 00:00:00 2001 From: jpy-git Date: Tue, 5 Apr 2022 22:42:22 +0100 Subject: [PATCH 03/12] Update test --- tests/data/string_quotes.py | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/tests/data/string_quotes.py b/tests/data/string_quotes.py index 3384241f4ad..49fb37138fd 100644 --- a/tests/data/string_quotes.py +++ b/tests/data/string_quotes.py @@ -1,4 +1,5 @@ '''''' + '\'' '"' "'" @@ -57,8 +58,8 @@ f"\"{a}\"{'hello' * b}\"{c}\"" # output - """""" + "'" '"' "'" From 49c16c9653ee50259fe66135d9dec9107471a84b Mon Sep 17 00:00:00 2001 From: jpy-git Date: Tue, 5 Apr 2022 23:39:14 +0100 Subject: [PATCH 04/12] Format misc files as well --- fuzz.py | 1 - scripts/check_pre_commit_rev_in_example.py | 1 - scripts/check_version_in_basics_example.py | 1 - scripts/diff_shades_gha_helper.py | 1 - 4 files changed, 4 deletions(-) diff --git a/fuzz.py b/fuzz.py index f5f655ea279..2fcde30cd94 100644 --- a/fuzz.py +++ b/fuzz.py @@ -4,7 +4,6 @@ generation. You can run this file with `python`, `pytest`, or (soon) a coverage-guided fuzzer I'm working on. """ - import re import hypothesmith diff --git a/scripts/check_pre_commit_rev_in_example.py b/scripts/check_pre_commit_rev_in_example.py index 9560b3b8401..132384c84b6 100644 --- a/scripts/check_pre_commit_rev_in_example.py +++ b/scripts/check_pre_commit_rev_in_example.py @@ -8,7 +8,6 @@ technical and some pragmatic). Encouraging bad practice is also just not ideal. xref: https://github.com/psf/black/issues/420 """ - import os import sys diff --git a/scripts/check_version_in_basics_example.py b/scripts/check_version_in_basics_example.py index c62780d97ab..733bfcce9a4 100644 --- a/scripts/check_version_in_basics_example.py +++ b/scripts/check_version_in_basics_example.py @@ -3,7 +3,6 @@ the latest version of Black. This saves us from forgetting to update that during the release process. """ - import os import sys diff --git a/scripts/diff_shades_gha_helper.py b/scripts/diff_shades_gha_helper.py index b5fea5a817d..4b8ef27418e 100644 --- a/scripts/diff_shades_gha_helper.py +++ b/scripts/diff_shades_gha_helper.py @@ -13,7 +13,6 @@ https://black.readthedocs.io/en/latest/contributing/gauging_changes.html#diff-shades """ - import json import os import platform From d6afd674366b5a9b802bfdabbd6be92a83190cfd Mon Sep 17 00:00:00 2001 From: jpy-git Date: Wed, 6 Apr 2022 21:25:48 +0100 Subject: [PATCH 05/12] Use window of previously visited lines to keep memory usage constant --- src/black/lines.py | 76 ++++++++++++++++++++++++++++------------------ 1 file changed, 46 insertions(+), 30 deletions(-) diff --git a/src/black/lines.py b/src/black/lines.py index f43b8281000..bdd405301b4 100644 --- a/src/black/lines.py +++ b/src/black/lines.py @@ -1,8 +1,10 @@ +from collections import deque from dataclasses import dataclass, field import itertools import sys from typing import ( Callable, + Deque, Dict, Iterator, List, @@ -427,9 +429,14 @@ class EmptyLineTracker: """ is_pyi: bool = False - previous_lines: List[Line] = field(default_factory=list) previous_defs: List[int] = field(default_factory=list) preview: bool = False + # Since we shouldn't need to look back + # more than a couple lines we limit + # this window to the last 3 visited lines + # to keep memory constant when formatting large files. + previous_lines_window: Deque[Line] = field(default_factory=deque) + previous_lines_window_size = 3 def maybe_empty_lines(self, current_line: Line) -> int: """Return the number of extra empty lines before and after the `current_line`. @@ -442,21 +449,24 @@ def maybe_empty_lines(self, current_line: Line) -> int: # Black should not insert empty lines at the beginning # of the file 0 - if not self.previous_lines + if not self.previous_lines_window else before ) if ( self.preview - and len(self.previous_lines) == 1 - and self.previous_lines[-1].is_triple_quoted_string + and len(self.previous_lines_window) == 1 + and self.previous_lines_window[-1].is_triple_quoted_string ): # Newlines after multi-line module level docstring. - if str(self.previous_lines[-1].leaves[0]).count("\n") >= 1: + if str(self.previous_lines_window[-1].leaves[0]).count("\n") >= 1: before = 0 else: before = 1 - self.previous_lines.append(current_line) + self.previous_lines_window.append(current_line) + if len(self.previous_lines_window) > self.previous_lines_window_size: + self.previous_lines_window.popleft() + return before def _maybe_empty_lines(self, current_line: Line) -> int: @@ -474,8 +484,12 @@ def _maybe_empty_lines(self, current_line: Line) -> int: depth = current_line.depth while self.previous_defs and self.previous_defs[-1] >= depth: if self.is_pyi: - assert self.previous_lines - if depth and not current_line.is_def and self.previous_lines[-1].is_def: + assert self.previous_lines_window + if ( + depth + and not current_line.is_def + and self.previous_lines_window[-1].is_def + ): # Empty lines between attributes and methods should be preserved. before = min(1, before) elif depth: @@ -509,18 +523,18 @@ def _maybe_empty_lines(self, current_line: Line) -> int: return self._maybe_empty_lines_for_class_or_def(current_line, before) if ( - self.previous_lines - and self.previous_lines[-1].is_import + self.previous_lines_window + and self.previous_lines_window[-1].is_import and not current_line.is_import - and depth == self.previous_lines[-1].depth + and depth == self.previous_lines_window[-1].depth ): return before or 1 if ( - len(self.previous_lines) > 1 - and self.previous_lines[-2].is_class - and self.previous_lines[-1].is_triple_quoted_string - and current_line.depth == self.previous_lines[-1].depth + len(self.previous_lines_window) > 1 + and self.previous_lines_window[-2].is_class + and self.previous_lines_window[-1].is_triple_quoted_string + and current_line.depth == self.previous_lines_window[-1].depth ): return 1 @@ -531,42 +545,44 @@ def _maybe_empty_lines_for_class_or_def( ) -> int: if not current_line.is_decorator: self.previous_defs.append(current_line.depth) - if not self.previous_lines: + if not self.previous_lines_window: # Don't insert empty lines before the first line in the file. return 0 - if self.previous_lines[-1].is_decorator: + if self.previous_lines_window[-1].is_decorator: return 0 if ( self.is_pyi - and len(self.previous_lines) > 1 - and self.previous_lines[-1].is_stub_class - and self.previous_lines[-2].is_decorator + and len(self.previous_lines_window) > 1 + and self.previous_lines_window[-1].is_stub_class + and self.previous_lines_window[-2].is_decorator ): # Insert an empty line after a decorated stub class return 1 - if self.previous_lines[-1].depth < current_line.depth and ( - self.previous_lines[-1].is_class or self.previous_lines[-1].is_def + if self.previous_lines_window[-1].depth < current_line.depth and ( + self.previous_lines_window[-1].is_class + or self.previous_lines_window[-1].is_def ): return 0 if ( - self.previous_lines[-1].is_comment - and self.previous_lines[-1].depth == current_line.depth + self.previous_lines_window[-1].is_comment + and self.previous_lines_window[-1].depth == current_line.depth and before == 0 ): return 0 if self.is_pyi: - if current_line.is_class or self.previous_lines[-1].is_class: - if self.previous_lines[-1].depth < current_line.depth: + if current_line.is_class or self.previous_lines_window[-1].is_class: + if self.previous_lines_window[-1].depth < current_line.depth: newlines = 0 - elif self.previous_lines[-1].depth > current_line.depth: + elif self.previous_lines_window[-1].depth > current_line.depth: newlines = 1 elif ( - current_line.is_stub_class and self.previous_lines[-1].is_stub_class + current_line.is_stub_class + and self.previous_lines_window[-1].is_stub_class ): # No blank line between classes with an empty body newlines = 0 @@ -574,7 +590,7 @@ def _maybe_empty_lines_for_class_or_def( newlines = 1 elif ( current_line.is_def or current_line.is_decorator - ) and not self.previous_lines[-1].is_def: + ) and not self.previous_lines_window[-1].is_def: if current_line.depth: # In classes empty lines between attributes and methods should # be preserved. @@ -583,7 +599,7 @@ def _maybe_empty_lines_for_class_or_def( # Blank line between a block of functions (maybe with preceding # decorators) and a block of non-functions newlines = 1 - elif self.previous_lines[-1].depth > current_line.depth: + elif self.previous_lines_window[-1].depth > current_line.depth: newlines = 1 else: newlines = 0 From 581e7c866154f4095fa5ac328cb993d320093db1 Mon Sep 17 00:00:00 2001 From: jpy-git Date: Wed, 27 Apr 2022 11:39:12 +0100 Subject: [PATCH 06/12] Newline after multiline docstrings --- scripts/check_pre_commit_rev_in_example.py | 1 + scripts/check_version_in_basics_example.py | 1 + scripts/diff_shades_gha_helper.py | 1 + src/black/lines.py | 6 +----- src/black/mode.py | 1 + src/black/nodes.py | 1 + src/black/output.py | 1 + src/black/strings.py | 1 + tests/data/module_docstring_2.py | 1 + tests/optional.py | 1 + 10 files changed, 10 insertions(+), 5 deletions(-) diff --git a/scripts/check_pre_commit_rev_in_example.py b/scripts/check_pre_commit_rev_in_example.py index 132384c84b6..9560b3b8401 100644 --- a/scripts/check_pre_commit_rev_in_example.py +++ b/scripts/check_pre_commit_rev_in_example.py @@ -8,6 +8,7 @@ technical and some pragmatic). Encouraging bad practice is also just not ideal. xref: https://github.com/psf/black/issues/420 """ + import os import sys diff --git a/scripts/check_version_in_basics_example.py b/scripts/check_version_in_basics_example.py index 733bfcce9a4..c62780d97ab 100644 --- a/scripts/check_version_in_basics_example.py +++ b/scripts/check_version_in_basics_example.py @@ -3,6 +3,7 @@ the latest version of Black. This saves us from forgetting to update that during the release process. """ + import os import sys diff --git a/scripts/diff_shades_gha_helper.py b/scripts/diff_shades_gha_helper.py index 4b8ef27418e..b5fea5a817d 100644 --- a/scripts/diff_shades_gha_helper.py +++ b/scripts/diff_shades_gha_helper.py @@ -13,6 +13,7 @@ https://black.readthedocs.io/en/latest/contributing/gauging_changes.html#diff-shades """ + import json import os import platform diff --git a/src/black/lines.py b/src/black/lines.py index bdd405301b4..9ba979f517c 100644 --- a/src/black/lines.py +++ b/src/black/lines.py @@ -457,11 +457,7 @@ def maybe_empty_lines(self, current_line: Line) -> int: and len(self.previous_lines_window) == 1 and self.previous_lines_window[-1].is_triple_quoted_string ): - # Newlines after multi-line module level docstring. - if str(self.previous_lines_window[-1].leaves[0]).count("\n") >= 1: - before = 0 - else: - before = 1 + before = 1 self.previous_lines_window.append(current_line) if len(self.previous_lines_window) > self.previous_lines_window_size: diff --git a/src/black/mode.py b/src/black/mode.py index 7b48024c3d4..6b74c14b6de 100644 --- a/src/black/mode.py +++ b/src/black/mode.py @@ -3,6 +3,7 @@ Mostly around Python language feature support per version and Black configuration chosen by the user. """ + from hashlib import sha256 import sys diff --git a/src/black/nodes.py b/src/black/nodes.py index 3a0e2e328c9..d18d4bde872 100644 --- a/src/black/nodes.py +++ b/src/black/nodes.py @@ -1,6 +1,7 @@ """ blib2to3 Node/Leaf transformation-related utility functions. """ + import sys from typing import ( Generic, diff --git a/src/black/output.py b/src/black/output.py index 269d96973b9..9561d4b57d2 100644 --- a/src/black/output.py +++ b/src/black/output.py @@ -2,6 +2,7 @@ The double calls are for patching purposes in tests. """ + import json from typing import Any, Optional from mypy_extensions import mypyc_attr diff --git a/src/black/strings.py b/src/black/strings.py index 8fb84758896..9d0e2eb8430 100644 --- a/src/black/strings.py +++ b/src/black/strings.py @@ -1,6 +1,7 @@ """ Simple formatting on strings. Further string formatting code is in trans.py. """ + import re import sys from functools import lru_cache diff --git a/tests/data/module_docstring_2.py b/tests/data/module_docstring_2.py index 3e250fac54c..368e5ef90ad 100644 --- a/tests/data/module_docstring_2.py +++ b/tests/data/module_docstring_2.py @@ -47,6 +47,7 @@ Excepteur sint occaecat cupidatat non proident, sunt in culpa qui officia deserunt mollit anim id est laborum. """ + a = 1 diff --git a/tests/optional.py b/tests/optional.py index 3d10fe1d052..a4e9441ef1c 100644 --- a/tests/optional.py +++ b/tests/optional.py @@ -13,6 +13,7 @@ Adapted from https://pypi.org/project/pytest-optional-tests/, (c) 2019 Reece Hart """ + from functools import lru_cache import itertools import logging From 8b2ebaca03ce14c6fd8b84fc1e55218ec64a801f Mon Sep 17 00:00:00 2001 From: jpy-git Date: Wed, 27 Apr 2022 11:49:48 +0100 Subject: [PATCH 07/12] Use named preview mode --- src/black/__init__.py | 2 +- src/black/lines.py | 3 +-- src/black/mode.py | 1 + 3 files changed, 3 insertions(+), 3 deletions(-) diff --git a/src/black/__init__.py b/src/black/__init__.py index 05c8e20c334..ab52ccf5220 100644 --- a/src/black/__init__.py +++ b/src/black/__init__.py @@ -1171,7 +1171,7 @@ 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, preview=mode.preview) + elt = EmptyLineTracker(is_pyi=mode.is_pyi) empty_line = Line(mode=mode) split_line_features = { feature diff --git a/src/black/lines.py b/src/black/lines.py index 9ba979f517c..a3a7f622460 100644 --- a/src/black/lines.py +++ b/src/black/lines.py @@ -430,7 +430,6 @@ class EmptyLineTracker: is_pyi: bool = False previous_defs: List[int] = field(default_factory=list) - preview: bool = False # Since we shouldn't need to look back # more than a couple lines we limit # this window to the last 3 visited lines @@ -453,7 +452,7 @@ def maybe_empty_lines(self, current_line: Line) -> int: else before ) if ( - self.preview + Preview.module_docstring_newlines in current_line.mode and len(self.previous_lines_window) == 1 and self.previous_lines_window[-1].is_triple_quoted_string ): diff --git a/src/black/mode.py b/src/black/mode.py index 6b74c14b6de..f580e7685f5 100644 --- a/src/black/mode.py +++ b/src/black/mode.py @@ -129,6 +129,7 @@ class Preview(Enum): string_processing = auto() remove_redundant_parens = auto() one_element_subscript = auto() + module_docstring_newlines = auto() class Deprecated(UserWarning): From 9727c3cb71295cc55ebcb19db271068e254d5e10 Mon Sep 17 00:00:00 2001 From: jpy-git Date: Wed, 27 Apr 2022 11:58:10 +0100 Subject: [PATCH 08/12] Format ourselves --- fuzz.py | 1 + src/black/linegen.py | 1 + src/black/numerics.py | 1 + src/black/parsing.py | 1 + src/black/report.py | 1 + src/black/rusty.py | 1 + src/black/trans.py | 1 + 7 files changed, 7 insertions(+) diff --git a/fuzz.py b/fuzz.py index 2fcde30cd94..f5f655ea279 100644 --- a/fuzz.py +++ b/fuzz.py @@ -4,6 +4,7 @@ generation. You can run this file with `python`, `pytest`, or (soon) a coverage-guided fuzzer I'm working on. """ + import re import hypothesmith diff --git a/src/black/linegen.py b/src/black/linegen.py index 2cf9cf3130a..a6970677b68 100644 --- a/src/black/linegen.py +++ b/src/black/linegen.py @@ -1,6 +1,7 @@ """ Generating lines of code. """ + from functools import partial, wraps import sys from typing import Collection, Iterator, List, Optional, Set, Union diff --git a/src/black/numerics.py b/src/black/numerics.py index 879e5b2cf36..67ac8595fcc 100644 --- a/src/black/numerics.py +++ b/src/black/numerics.py @@ -1,6 +1,7 @@ """ Formatting numeric literals. """ + from blib2to3.pytree import Leaf diff --git a/src/black/parsing.py b/src/black/parsing.py index 12726567948..e04cccf1d57 100644 --- a/src/black/parsing.py +++ b/src/black/parsing.py @@ -1,6 +1,7 @@ """ Parse Python code and perform AST validation. """ + import ast import platform import sys diff --git a/src/black/report.py b/src/black/report.py index 43b942c9e3c..6b226ee0810 100644 --- a/src/black/report.py +++ b/src/black/report.py @@ -1,6 +1,7 @@ """ Summarize Black runs to users. """ + from dataclasses import dataclass from enum import Enum from pathlib import Path diff --git a/src/black/rusty.py b/src/black/rusty.py index 822e3d7858a..56544d85bb7 100644 --- a/src/black/rusty.py +++ b/src/black/rusty.py @@ -2,6 +2,7 @@ See https://doc.rust-lang.org/book/ch09-00-error-handling.html. """ + from typing import Generic, TypeVar, Union diff --git a/src/black/trans.py b/src/black/trans.py index 01aa80eaaf8..9d527d4dd42 100644 --- a/src/black/trans.py +++ b/src/black/trans.py @@ -1,6 +1,7 @@ """ String transformers that can split and merge strings. """ + from abc import ABC, abstractmethod from collections import defaultdict from dataclasses import dataclass From abd98ddfc0f914a42bd1a6e34773c67ffd626a45 Mon Sep 17 00:00:00 2001 From: Joe Young <80432516+jpy-git@users.noreply.github.com> Date: Tue, 3 May 2022 10:25:49 +0100 Subject: [PATCH 09/12] Update CHANGES.md MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Co-authored-by: Felix Hildén --- CHANGES.md | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/CHANGES.md b/CHANGES.md index e70ac48492f..0a94cfa5688 100644 --- a/CHANGES.md +++ b/CHANGES.md @@ -20,7 +20,7 @@ - Remove redundant parentheses around awaited objects (#2991) - Parentheses around return annotations are now managed (#2990) - Remove unnecessary parentheses from `with` statements (#2926) -- Standardise newlines after module-level docstrings (#2996) +- Require one empty line after module-level docstrings (#2996) ### _Blackd_ From 675cfd1d204449bc22d09c5f9519aa62443be4ab Mon Sep 17 00:00:00 2001 From: Richard Si <63936253+ichard26@users.noreply.github.com> Date: Sun, 25 Sep 2022 19:09:48 -0400 Subject: [PATCH 10/12] I messed up the merge, whoops --- 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 40d4692601f..cf613fa2a31 100644 --- a/src/black/lines.py +++ b/src/black/lines.py @@ -567,7 +567,7 @@ def _maybe_empty_lines(self, current_line: Line) -> int: if ( Preview.remove_block_trailing_newline in current_line.mode - and self.previous_lines_window[-1] + and self.previous_lines_window and self.previous_lines_window[-1].opens_block ): return 0 From 1909c8a75314e35c07481236b3d6b543e6305c3b Mon Sep 17 00:00:00 2001 From: Richard Si <63936253+ichard26@users.noreply.github.com> Date: Sun, 25 Sep 2022 21:31:33 -0400 Subject: [PATCH 11/12] Add an entry in future style docs --- docs/the_black_code_style/future_style.md | 35 +++++++++++++++++++++++ 1 file changed, 35 insertions(+) diff --git a/docs/the_black_code_style/future_style.md b/docs/the_black_code_style/future_style.md index a028a2888ed..4a0dfb08959 100644 --- a/docs/the_black_code_style/future_style.md +++ b/docs/the_black_code_style/future_style.md @@ -131,3 +131,38 @@ with open("bla.txt") as f, open("x"): async def main(): await asyncio.sleep(1) ``` + +### Enforced newline after module docstrings + +A single blank line after module docstrings will be enforced, this applies to single and +multi-line docstrings. + +```python +"""Utility functions and constants.""" +import functools +``` + +```python +""" +Utility functions and constants. +""" + + +import functools +``` + +will be changed to: + +```python +"""Utility functions and constants.""" + +import functools +``` + +```python +""" +Utility functions and constants. +""" + +import functools +``` From af301182f694112c6059a05b5e8e59229a5f13da Mon Sep 17 00:00:00 2001 From: Richard Si <63936253+ichard26@users.noreply.github.com> Date: Sun, 25 Sep 2022 21:33:11 -0400 Subject: [PATCH 12/12] Update changelog entry for new PR --- CHANGES.md | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/CHANGES.md b/CHANGES.md index ec8d88e3249..4e9285905fd 100644 --- a/CHANGES.md +++ b/CHANGES.md @@ -20,9 +20,9 @@ -- Require one empty line after module-level docstrings (#2996) - Fix a crash when formatting some dicts with parenthesis-wrapped long string keys (#3262) +- Require one empty line after module-level docstrings (#3287) ### Configuration