is_multiline_string,
is_one_sequence_between,
is_type_comment,
- is_with_stmt,
+ is_with_or_async_with_stmt,
replace_child,
syms,
whitespace,
)
+from black.strings import str_width
from blib2to3.pgen2 import token
from blib2to3.pytree import Leaf, Node
return bool(self) and is_import(self.leaves[0])
@property
- def is_with_stmt(self) -> bool:
+ def is_with_or_async_with_stmt(self) -> bool:
"""Is this a with_stmt line?"""
- return bool(self) and is_with_stmt(self.leaves[0])
+ return bool(self) and is_with_or_async_with_stmt(self.leaves[0])
@property
def is_class(self) -> bool:
return False
return self.leaves[-1].type == token.COLON
+ def is_fmt_pass_converted(
+ self, *, first_leaf_matches: Optional[Callable[[Leaf], bool]] = None
+ ) -> bool:
+ """Is this line converted from fmt off/skip code?
+
+ If first_leaf_matches is not None, it only returns True if the first
+ leaf of converted code matches.
+ """
+ if len(self.leaves) != 1:
+ return False
+ leaf = self.leaves[0]
+ if (
+ leaf.type != STANDALONE_COMMENT
+ or leaf.fmt_pass_converted_first_leaf is None
+ ):
+ return False
+ return first_leaf_matches is None or first_leaf_matches(
+ leaf.fmt_pass_converted_first_leaf
+ )
+
def contains_standalone_comments(self, depth_limit: int = sys.maxsize) -> bool:
"""If so, needs to be split before emitting."""
for leaf in self.leaves:
mode: Mode
previous_line: Optional[Line] = None
previous_block: Optional[LinesBlock] = None
- previous_defs: List[int] = field(default_factory=list)
+ previous_defs: List[Line] = field(default_factory=list)
semantic_leading_comment: Optional[LinesBlock] = None
def maybe_empty_lines(self, current_line: Line) -> LinesBlock:
else:
before = 0
depth = current_line.depth
- while self.previous_defs and self.previous_defs[-1] >= depth:
+ while self.previous_defs and self.previous_defs[-1].depth >= depth:
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.
before = min(1, before)
+ elif (
+ Preview.blank_line_after_nested_stub_class in self.mode
+ and self.previous_defs[-1].is_class
+ and not self.previous_defs[-1].is_stub_class
+ ):
+ before = 1
elif depth:
before = 0
else:
before = 1
elif (
not depth
- and self.previous_defs[-1]
+ and self.previous_defs[-1].depth
and current_line.leaves[-1].type == token.COLON
and (
current_line.leaves[0].value
self.previous_line
and self.previous_line.is_import
and not current_line.is_import
+ and not current_line.is_fmt_pass_converted(first_leaf_matches=is_import)
and depth == self.previous_line.depth
):
return (before or 1), 0
self, current_line: Line, before: int
) -> Tuple[int, int]:
if not current_line.is_decorator:
- self.previous_defs.append(current_line.depth)
+ self.previous_defs.append(current_line)
if self.previous_line is None:
# Don't insert empty lines before the first line in the file.
return 0, 0
if not line_str:
line_str = line_to_string(line)
+ width = str_width if mode.preview else len
+
if Preview.multiline_string_handling not in mode:
return (
- len(line_str) <= mode.line_length
+ width(line_str) <= mode.line_length
and "\n" not in line_str # multiline strings
and not line.contains_standalone_comments()
)
return False
if "\n" not in line_str:
# No multiline strings (MLS) present
- return len(line_str) <= mode.line_length
+ return width(line_str) <= mode.line_length
first, *_, last = line_str.split("\n")
- if len(first) > mode.line_length or len(last) > mode.line_length:
+ if width(first) > mode.line_length or width(last) > mode.line_length:
return False
# Traverse the AST to examine the context of the multiline string (MLS),
if (
Preview.wrap_multiple_context_managers_in_parens in line.mode
and max_priority == COMMA_PRIORITY
- and rhs.head.is_with_stmt
+ and rhs.head.is_with_or_async_with_stmt
):
# For two context manager with statements, the optional parentheses read
# better. In this case, `rhs.body` is the context managers part of