def visit_simple_stmt(self, node: Node) -> Iterator[Line]: """Visit a statement without nested statements.""" prev_type: Optional[int] = None for child in node.children: if (prev_type is None or prev_type == token.SEMI) and is_arith_like(child): wrap_in_parentheses(node, child, visible=False) prev_type = child.type is_suite_like = node.parent and node.parent.type in STATEMENT if is_suite_like: if self.mode.is_pyi and is_stub_body(node): yield from self.visit_default(node) else: yield from self.line(+1) yield from self.visit_default(node) yield from self.line(-1) else: if ( not self.mode.is_pyi or not node.parent or not is_stub_suite(node.parent) ): yield from self.line() yield from self.visit_default(node)
def remove_await_parens(node: Node) -> None: if node.children[0].type == token.AWAIT and len(node.children) > 1: if ( node.children[1].type == syms.atom and node.children[1].children[0].type == token.LPAR ): if maybe_make_parens_invisible_in_atom( node.children[1], parent=node, remove_brackets_around_comma=True, ): wrap_in_parentheses(node, node.children[1], visible=False) # Since await is an expression we shouldn't remove # brackets in cases where this would change # the AST due to operator precedence. # Therefore we only aim to remove brackets around # power nodes that aren't also await expressions themselves. # https://peps.python.org/pep-0492/#updated-operator-precedence-table # N.B. We've still removed any redundant nested brackets though :) opening_bracket = cast(Leaf, node.children[1].children[0]) closing_bracket = cast(Leaf, node.children[1].children[-1]) bracket_contents = cast(Node, node.children[1].children[1]) if bracket_contents.type != syms.power: ensure_visible(opening_bracket) ensure_visible(closing_bracket) elif ( bracket_contents.type == syms.power and bracket_contents.children[0].type == token.AWAIT ): ensure_visible(opening_bracket) ensure_visible(closing_bracket) # If we are in a nested await then recurse down. remove_await_parens(bracket_contents)
def visit_funcdef(self, node: Node) -> Iterator[Line]: """Visit function definition.""" if Preview.annotation_parens not in self.mode: yield from self.visit_stmt(node, keywords={"def"}, parens=set()) else: yield from self.line() # Remove redundant brackets around return type annotation. is_return_annotation = False for child in node.children: if child.type == token.RARROW: is_return_annotation = True elif is_return_annotation: if child.type == syms.atom and child.children[0].type == token.LPAR: if maybe_make_parens_invisible_in_atom( child, parent=node, remove_brackets_around_comma=False, ): wrap_in_parentheses(node, child, visible=False) else: wrap_in_parentheses(node, child, visible=False) is_return_annotation = False for child in node.children: yield from self.visit(child)
def normalize_invisible_parens(node: Node, parens_after: Set[str]) -> 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): 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) # 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 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) pass elif node.type == syms.import_from: # "import from" nodes store parentheses directly as part of # the statement if child.type == token.LPAR: # make parentheses invisible child.value = "" # type: ignore node.children[-1].value = "" # type: ignore elif child.type != token.STAR: # insert invisible parentheses node.insert_child(index, Leaf(token.LPAR, "")) node.append_child(Leaf(token.RPAR, "")) break elif not (isinstance(child, Leaf) and is_multiline_string(child)): wrap_in_parentheses(node, child, visible=False) check_lpar = isinstance(child, Leaf) and child.value in parens_after
def visit_simple_stmt(self, node: Node) -> Iterator[Line]: """Visit a statement without nested statements.""" if first_child_is_arith(node): wrap_in_parentheses(node, node.children[0], visible=False) is_suite_like = node.parent and node.parent.type in STATEMENT if is_suite_like: if self.mode.is_pyi and is_stub_body(node): yield from self.visit_default(node) else: yield from self.line(+1) yield from self.visit_default(node) yield from self.line(-1) else: if (not self.mode.is_pyi or not node.parent or not is_stub_suite(node.parent)): yield from self.line() yield from self.visit_default(node)
def visit_power(self, node: Node) -> Iterator[Line]: for idx, leaf in enumerate(node.children[:-1]): next_leaf = node.children[idx + 1] if not isinstance(leaf, Leaf): continue value = leaf.value.lower() if (leaf.type == token.NUMBER and next_leaf.type == syms.trailer # Ensure that we are in an attribute trailer and next_leaf.children[0].type == token.DOT # It shouldn't wrap hexadecimal, binary and octal literals and not value.startswith(("0x", "0b", "0o")) # It shouldn't wrap complex literals and "j" not in value): wrap_in_parentheses(node, leaf) yield from self.visit_default(node)
def remove_with_parens(node: Node, parent: Node) -> None: """Recursively hide optional parens in `with` statements.""" # Removing all unnecessary parentheses in with statements in one pass is a tad # complex as different variations of bracketed statements result in pretty # different parse trees: # # with (open("file")) as f: # this is an asexpr_test # ... # # with (open("file") as f): # this is an atom containing an # ... # asexpr_test # # with (open("file")) as f, (open("file")) as f: # this is asexpr_test, COMMA, # ... # asexpr_test # # with (open("file") as f, open("file") as f): # an atom containing a # ... # testlist_gexp which then # # contains multiple asexpr_test(s) if node.type == syms.atom: if maybe_make_parens_invisible_in_atom( node, parent=parent, remove_brackets_around_comma=True, ): wrap_in_parentheses(parent, node, visible=False) if isinstance(node.children[1], Node): remove_with_parens(node.children[1], node) elif node.type == syms.testlist_gexp: for child in node.children: if isinstance(child, Node): remove_with_parens(child, node) elif node.type == syms.asexpr_test and not any( leaf.type == token.COLONEQUAL for leaf in node.leaves() ): if maybe_make_parens_invisible_in_atom( node.children[0], parent=node, remove_brackets_around_comma=True, ): wrap_in_parentheses(node, node.children[0], visible=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 )