# Note: at this point leaf.prefix should be empty except for
# imports, for which we only preserve newlines.
leaf.prefix += whitespace(
- leaf, complex_subscript=self.is_complex_subscript(leaf)
+ leaf,
+ complex_subscript=self.is_complex_subscript(leaf),
+ mode=self.mode,
)
if self.inside_brackets or not preformatted or track_bracket:
self.bracket_tracker.mark(leaf)
and second_leaf.value == "def"
)
+ @property
+ def is_stub_def(self) -> bool:
+ """Is this line a function definition with a body consisting only of "..."?"""
+ return self.is_def and self.leaves[-4:] == [Leaf(token.COLON, ":")] + [
+ Leaf(token.DOT, ".") for _ in range(3)
+ ]
+
@property
def is_class_paren_empty(self) -> bool:
"""Is this a class with no base classes but using parentheses?
@property
def is_triple_quoted_string(self) -> bool:
"""Is the line a triple quoted string?"""
- return (
- bool(self)
- and self.leaves[0].type == token.STRING
- and self.leaves[0].value.startswith(('"""', "'''"))
- )
+ if not self or self.leaves[0].type != token.STRING:
+ return False
+ value = self.leaves[0].value
+ if value.startswith(('"""', "'''")):
+ return True
+ if Preview.accept_raw_docstrings in self.mode and value.startswith(
+ ("r'''", 'r"""', "R'''", 'R"""')
+ ):
+ return True
+ return False
@property
def opens_block(self) -> bool:
if self.previous_line is None
else before - previous_after
)
+ if (
+ Preview.module_docstring_newlines in current_line.mode
+ and self.previous_block
+ and self.previous_block.previous_block is None
+ and len(self.previous_block.original_line.leaves) == 1
+ and self.previous_block.original_line.is_triple_quoted_string
+ ):
+ before = 1
+
block = LinesBlock(
mode=self.mode,
previous_block=self.previous_block,
first_leaf.prefix = ""
else:
before = 0
+
+ user_had_newline = bool(before)
depth = current_line.depth
previous_def = None
if self.mode.is_pyi:
if depth and not current_line.is_def and self.previous_line.is_def:
# Empty lines between attributes and methods should be preserved.
- before = min(1, before)
+ before = 1 if user_had_newline else 0
elif (
Preview.blank_line_after_nested_stub_class in self.mode
and previous_def.is_class
before = 2
if current_line.is_decorator or current_line.is_def or current_line.is_class:
- return self._maybe_empty_lines_for_class_or_def(current_line, before)
+ return self._maybe_empty_lines_for_class_or_def(
+ current_line, before, user_had_newline
+ )
if (
self.previous_line
return 0, 0
return before, 0
- def _maybe_empty_lines_for_class_or_def(
- self, current_line: Line, before: int
+ def _maybe_empty_lines_for_class_or_def( # noqa: C901
+ self, current_line: Line, before: int, user_had_newline: bool
) -> Tuple[int, int]:
if not current_line.is_decorator:
self.previous_defs.append(current_line)
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 in current_line.mode
+ 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:
newlines = 0
else:
newlines = 1 if current_line.depth else 2
+ # If a user has left no space after a dummy implementation, don't insert
+ # new lines. This is useful for instance for @overload or Protocols.
+ if (
+ Preview.dummy_implementations in self.mode
+ and self.previous_line.is_stub_def
+ and not user_had_newline
+ ):
+ newlines = 0
if comment_to_add_newlines is not None:
previous_block = comment_to_add_newlines.previous_block
if previous_block is not None: