for child in children:
yield from self.visit(child)
- if child.type == token.ASYNC:
+ if child.type == token.ASYNC or child.type == STANDALONE_COMMENT:
+ # STANDALONE_COMMENT happens when `# fmt: skip` is applied on the async
+ # line.
break
internal_stmt = next(children)
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.
- docstring = normalize_string_prefix(leaf.value)
+ if Preview.normalize_docstring_quotes_and_prefixes_properly in self.mode:
+ # There was a bug where --skip-string-normalization wouldn't stop us
+ # from normalizing docstring prefixes. To maintain stability, we can
+ # only address this buggy behaviour while the preview style is enabled.
+ if self.mode.string_normalization:
+ docstring = normalize_string_prefix(leaf.value)
+ # visit_default() does handle string normalization for us, but
+ # since this method acts differently depending on quote style (ex.
+ # see padding logic below), there's a possibility for unstable
+ # formatting as visit_default() is called *after*. To avoid a
+ # situation where this function formats a docstring differently on
+ # the second pass, normalize it early.
+ docstring = normalize_string_quotes(docstring)
+ else:
+ docstring = leaf.value
+ else:
+ # ... otherwise, we'll keep the buggy behaviour >.<
+ docstring = normalize_string_prefix(leaf.value)
prefix = get_string_prefix(docstring)
docstring = docstring[len(prefix) :] # Remove the prefix
quote_char = docstring[0]
quote_len = 1 if docstring[1] != quote_char else 3
docstring = docstring[quote_len:-quote_len]
docstring_started_empty = not docstring
+ indent = " " * 4 * self.current_line.depth
if is_multiline_string(leaf):
- indent = " " * 4 * self.current_line.depth
docstring = fix_docstring(docstring, indent)
else:
docstring = docstring.strip()
# We could enforce triple quotes at this point.
quote = quote_char * quote_len
- leaf.value = prefix + quote + docstring + quote
+
+ # It's invalid to put closing single-character quotes on a new line.
+ if Preview.long_docstring_quotes_on_newline in self.mode and quote_len == 3:
+ # We need to find the length of the last line of the docstring
+ # to find if we can add the closing quotes to the line without
+ # exceeding the maximum line length.
+ # If docstring is one line, then we need to add the length
+ # of the indent, prefix, and starting quotes. Ending quotes are
+ # handled later.
+ lines = docstring.splitlines()
+ last_line_length = len(lines[-1]) if docstring else 0
+
+ if len(lines) == 1:
+ last_line_length += len(indent) + len(prefix) + quote_len
+
+ # If adding closing quotes would cause the last line to exceed
+ # the maximum line length then put a line break before the
+ # closing quotes
+ if last_line_length + quote_len > self.mode.line_length:
+ leaf.value = prefix + quote + docstring + "\n" + indent + quote
+ else:
+ leaf.value = prefix + quote + docstring + quote
+ else:
+ leaf.value = prefix + quote + docstring + quote
yield from self.visit_default(leaf)
node.insert_child(index, Leaf(token.LPAR, ""))
node.append_child(Leaf(token.RPAR, ""))
break
+ elif (
+ index == 1
+ and child.type == token.STAR
+ and node.type == syms.except_clause
+ ):
+ # In except* (PEP 654), the star is actually part of
+ # of the keyword. So we need to skip the insertion of
+ # invisible parentheses to work more precisely.
+ continue
elif not (isinstance(child, Leaf) and is_multiline_string(child)):
wrap_in_parentheses(node, child, visible=False)