def visit_STRING(self, leaf: Leaf) -> Iterator[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. docstring = normalize_string_prefix(leaf.value) 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) # but that breaks on """""x""" (which is '""x'). # So we actually need to remove the first character and the next two # 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 docstring = fix_docstring(docstring, indent) else: docstring = docstring.strip() if docstring: # Add some padding if the docstring starts / ends with a quote mark. if docstring[0] == quote_char: docstring = " " + docstring if docstring[-1] == quote_char: docstring += " " if docstring[-1] == "\\": backslash_count = len(docstring) - len(docstring.rstrip("\\")) if backslash_count % 2: # Odd number of tailing backslashes, add some padding to # avoid escaping the closing string quote. docstring += " " elif not docstring_started_empty: docstring = " " # We could enforce triple quotes at this point. quote = quote_char * quote_len leaf.value = prefix + quote + docstring + quote yield from self.visit_default(leaf)
def contains_multiline_strings(self) -> bool: return any(is_multiline_string(leaf) for leaf in self.leaves)
def can_omit_invisible_parens( line: Line, line_length: int, ) -> bool: """Does `line` have a shape safe to reformat without optional parens around it? Returns True for only a subset of potentially nice looking formattings but the point is to not return false positives that end up producing lines that are too long. """ bt = line.bracket_tracker if not bt.delimiters: # Without delimiters the optional parentheses are useless. return True max_priority = bt.max_delimiter_priority() if bt.delimiter_count_with_priority(max_priority) > 1: # With more than one delimiter of a kind the optional parentheses read better. return False if max_priority == DOT_PRIORITY: # A single stranded method call doesn't require optional parentheses. return True assert len(line.leaves) >= 2, "Stranded delimiter" # With a single delimiter, omit if the expression starts or ends with # a bracket. first = line.leaves[0] second = line.leaves[1] if first.type in OPENING_BRACKETS and second.type not in CLOSING_BRACKETS: if _can_omit_opening_paren(line, first=first, line_length=line_length): return True # Note: we are not returning False here because a line might have *both* # a leading opening bracket and a trailing closing bracket. If the # opening bracket doesn't match our rule, maybe the closing will. penultimate = line.leaves[-2] last = line.leaves[-1] if ( last.type == token.RPAR or last.type == token.RBRACE or ( # don't use indexing for omitting optional parentheses; # it looks weird last.type == token.RSQB and last.parent and last.parent.type != syms.trailer ) ): if penultimate.type in OPENING_BRACKETS: # Empty brackets don't help. return False if is_multiline_string(first): # Additional wrapping of a multiline string in this situation is # unnecessary. return True if _can_omit_closing_paren(line, last=last, line_length=line_length): return True return False
def normalize_invisible_parens( node: Node, parens_after: Set[str], *, preview: bool ) -> None: """Make existing optional parentheses invisible or create new ones. `parens_after` is a set of string leaf values immediately after which parens should be put. Standardizes on visible parentheses for single-element tuples, and keeps existing visible parentheses for other tuples and generator expressions. """ for pc in list_comments(node.prefix, is_endmarker=False, preview=preview): if pc.value in FMT_OFF: # This `node` has a prefix with `# fmt: off`, don't mess with parens. return check_lpar = False for index, child in enumerate(list(node.children)): # Fixes a bug where invisible parens are not properly stripped from # assignment statements that contain type annotations. if isinstance(child, Node) and child.type == syms.annassign: normalize_invisible_parens( child, parens_after=parens_after, preview=preview ) # Add parentheses around long tuple unpacking in assignments. if ( index == 0 and isinstance(child, Node) and child.type == syms.testlist_star_expr ): check_lpar = True if check_lpar: if ( preview and child.type == syms.atom and node.type == syms.for_stmt and isinstance(child.prev_sibling, Leaf) and child.prev_sibling.type == token.NAME and child.prev_sibling.value == "for" ): if maybe_make_parens_invisible_in_atom( child, parent=node, remove_brackets_around_comma=True, ): wrap_in_parentheses(node, child, visible=False) elif preview and isinstance(child, Node) and node.type == syms.with_stmt: remove_with_parens(child, node) elif child.type == syms.atom: if maybe_make_parens_invisible_in_atom( child, parent=node, ): wrap_in_parentheses(node, child, visible=False) elif is_one_tuple(child): wrap_in_parentheses(node, child, visible=True) elif node.type == syms.import_from: # "import from" nodes store parentheses directly as part of # the statement if is_lpar_token(child): assert is_rpar_token(node.children[-1]) # make parentheses invisible child.value = "" node.children[-1].value = "" elif child.type != token.STAR: # insert invisible parentheses 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) comma_check = child.type == token.COMMA if preview else False check_lpar = isinstance(child, Leaf) and ( child.value in parens_after or comma_check )