Skip to content

Commit

Permalink
Merge 1b1e1c8 into 8ed3e3d
Browse files Browse the repository at this point in the history
  • Loading branch information
jpy-git authored Apr 27, 2022
2 parents 8ed3e3d + 1b1e1c8 commit 32d9e38
Show file tree
Hide file tree
Showing 9 changed files with 172 additions and 49 deletions.
1 change: 1 addition & 0 deletions CHANGES.md
Original file line number Diff line number Diff line change
Expand Up @@ -20,6 +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)

### _Blackd_

Expand Down
1 change: 0 additions & 1 deletion fuzz.py
Original file line number Diff line number Diff line change
Expand Up @@ -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
Expand Down
4 changes: 1 addition & 3 deletions src/black/__init__.py
Original file line number Diff line number Diff line change
Expand Up @@ -1177,15 +1177,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
Expand Down
117 changes: 73 additions & 44 deletions src/black/lines.py
Original file line number Diff line number Diff line change
@@ -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,
Expand Down Expand Up @@ -419,37 +421,50 @@ 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
are consumed by `maybe_empty_lines()` and included in the computation.
"""

is_pyi: bool = False
previous_line: Optional[Line] = None
previous_after: int = 0
previous_defs: List[int] = field(default_factory=list)

def maybe_empty_lines(self, current_line: Line) -> Tuple[int, int]:
# 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`.
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_window
else before
)
self.previous_after = after
self.previous_line = current_line
return before, after
if (
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
):
before = 1

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) -> 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
Expand All @@ -464,8 +479,12 @@ 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_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:
Expand Down Expand Up @@ -499,64 +518,74 @@ 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_window
and self.previous_lines_window[-1].is_import
and not current_line.is_import
and depth == self.previous_line.depth
and depth == self.previous_lines_window[-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_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 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_window:
# 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_window[-1].is_decorator:
return 0

return 0, 0
if (
self.is_pyi
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_line.depth < current_line.depth and (
self.previous_line.is_class or self.previous_line.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, 0
return 0

if (
self.previous_line.is_comment
and self.previous_line.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, 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_window[-1].is_class:
if self.previous_lines_window[-1].depth < current_line.depth:
newlines = 0
elif self.previous_line.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_line.is_stub_class:
elif (
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
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_window[-1].is_def:
if current_line.depth:
# In classes empty lines between attributes and methods should
# be preserved.
Expand All @@ -565,13 +594,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_window[-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]]:
Expand Down
1 change: 1 addition & 0 deletions src/black/mode.py
Original file line number Diff line number Diff line change
Expand Up @@ -147,6 +147,7 @@ class Preview(Enum):
remove_redundant_parens = auto()
one_element_subscript = auto()
annotation_parens = auto()
module_docstring_newlines = auto()


class Deprecated(UserWarning):
Expand Down
25 changes: 25 additions & 0 deletions tests/data/module_docstring_1.py
Original file line number Diff line number Diff line change
@@ -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
67 changes: 67 additions & 0 deletions tests/data/module_docstring_2.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,67 @@
"""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
3 changes: 2 additions & 1 deletion tests/data/string_quotes.py
Original file line number Diff line number Diff line change
@@ -1,4 +1,5 @@
''''''

'\''
'"'
"'"
Expand Down Expand Up @@ -57,8 +58,8 @@
f"\"{a}\"{'hello' * b}\"{c}\""

# output

""""""

"'"
'"'
"'"
Expand Down
2 changes: 2 additions & 0 deletions tests/test_format.py
Original file line number Diff line number Diff line change
Expand Up @@ -91,6 +91,8 @@
"one_element_subscript",
"remove_await_parens",
"return_annotation_brackets",
"module_docstring_1",
"module_docstring_2",
]

SOURCES: List[str] = [
Expand Down

0 comments on commit 32d9e38

Please sign in to comment.