from dataclasses import dataclass, field
-from black.nodes import WHITESPACE, STATEMENT, STANDALONE_COMMENT
+from black.nodes import WHITESPACE, RARROW, STATEMENT, STANDALONE_COMMENT
from black.nodes import ASSIGNMENTS, OPENING_BRACKETS, CLOSING_BRACKETS
from black.nodes import Visitor, syms, first_child_is_arith, ensure_visible
from black.nodes import is_docstring, is_empty_tuple, is_one_tuple, is_one_tuple_between
"""Visit a statement.
This implementation is shared for `if`, `while`, `for`, `try`, `except`,
- `def`, `with`, `class`, `assert` and assignments.
+ `def`, `with`, `class`, `assert`, `match`, `case` and assignments.
The relevant Python language `keywords` for a given statement will be
NAME leaves within it. This methods puts those on a separate line.
if is_docstring(leaf) and "\\\n" not in leaf.value:
# We're ignoring docstrings with backslash newline escapes because changing
# indentation of those changes the AST representation of the code.
- prefix = get_string_prefix(leaf.value)
- docstring = leaf.value[len(prefix) :] # Remove the prefix
+ docstring = normalize_string_prefix(leaf.value, self.remove_u_prefix)
+ prefix = get_string_prefix(docstring)
+ docstring = docstring[len(prefix) :] # Remove the prefix
quote_char = docstring[0]
# A natural way to remove the outer quotes is to do:
# docstring = docstring.strip(quote_char)
# characters but only if they are the same as the first.
quote_len = 1 if docstring[1] != quote_char else 3
docstring = docstring[quote_len:-quote_len]
+ docstring_started_empty = not docstring
if is_multiline_string(leaf):
indent = " " * 4 * self.current_line.depth
# Odd number of tailing backslashes, add some padding to
# avoid escaping the closing string quote.
docstring += " "
- else:
- # Add some padding if the docstring is empty.
+ elif not docstring_started_empty:
docstring = " "
# We could enforce triple quotes at this point.
self.visit_async_funcdef = self.visit_async_stmt
self.visit_decorated = self.visit_decorators
+ # PEP 634
+ self.visit_match_stmt = partial(v, keywords={"match"}, parens=Ø)
+ self.visit_case_block = partial(v, keywords={"case"}, parens=Ø)
+
def transform_line(
line: Line, mode: Mode, features: Collection[Feature] = ()
yield from right_hand_split(line, line_length, features=features, omit=omit)
return
- except CannotSplit:
+ except CannotSplit as e:
if not (
can_be_split(body)
or is_line_short_enough(body, line_length=line_length)
):
raise CannotSplit(
"Splitting failed, body is still too long and can't be split."
- )
+ ) from e
elif head.contains_multiline_strings() or tail.contains_multiline_strings():
raise CannotSplit(
" satisfy the splitting algorithm because the head or the tail"
" contains multiline strings which by definition never fit one"
" line."
- )
+ ) from e
ensure_visible(opening_bracket)
ensure_visible(closing_bracket)
original.is_def
and opening_bracket.value == "("
and not any(leaf.type == token.COMMA for leaf in leaves)
+ # In particular, don't add one within a parenthesized return annotation.
+ # Unfortunately the indicator we're in a return annotation (RARROW) may
+ # be defined directly in the parent node, the parent of the parent ...
+ # and so on depending on how complex the return annotation is.
+ # This isn't perfect and there's some false negatives but they are in
+ # contexts were a comma is actually fine.
+ and not any(
+ node.prev_sibling.type == RARROW
+ for node in (
+ leaves[0].parent,
+ getattr(leaves[0].parent, "parent", None),
+ )
+ if isinstance(node, Node) and isinstance(node.prev_sibling, Leaf)
+ )
)
if original.is_import or no_commas:
try:
last_leaf = line.leaves[-1]
except IndexError:
- raise CannotSplit("Line empty")
+ raise CannotSplit("Line empty") from None
bt = line.bracket_tracker
try:
delimiter_priority = bt.max_delimiter_priority(exclude={id(last_leaf)})
except ValueError:
- raise CannotSplit("No delimiters found")
+ raise CannotSplit("No delimiters found") from None
if delimiter_priority == DOT_PRIORITY:
if bt.delimiter_count_with_priority(delimiter_priority) == 1:
result.extend(transform_line(transformed_line, mode=mode, features=features))
- if not (
- transform.__name__ == "rhs"
- and line.bracket_tracker.invisible
- and not any(bracket.value for bracket in line.bracket_tracker.invisible)
- and not line.contains_multiline_strings()
- and not result[0].contains_uncollapsable_type_comments()
- and not result[0].contains_unsplittable_type_ignore()
- and not is_line_short_enough(result[0], line_length=mode.line_length)
+ if (
+ transform.__name__ != "rhs"
+ or not line.bracket_tracker.invisible
+ or any(bracket.value for bracket in line.bracket_tracker.invisible)
+ or line.contains_multiline_strings()
+ or result[0].contains_uncollapsable_type_comments()
+ or result[0].contains_unsplittable_type_ignore()
+ or is_line_short_enough(result[0], line_length=mode.line_length)
+ # If any leaves have no parents (which _can_ occur since
+ # `transform(line)` potentially destroys the line's underlying node
+ # structure), then we can't proceed. Doing so would cause the below
+ # call to `append_leaves()` to fail.
+ or any(leaf.parent is None for leaf in line.leaves)
):
return result