from blib2to3.pgen2 import driver, token
from blib2to3.pgen2.parse import ParseError
-__version__ = "18.4a4"
+__version__ = "18.4a6"
DEFAULT_LINE_LENGTH = 88
# types
write_back = WriteBack.YES
report = Report(check=check, quiet=quiet)
if len(sources) == 0:
+ out("No paths given. Nothing to do 😴")
ctx.exit(0)
return
token.GREATEREQUAL,
}
MATH_OPERATORS = {
+ token.VBAR,
+ token.CIRCUMFLEX,
+ token.AMPER,
+ token.LEFTSHIFT,
+ token.RIGHTSHIFT,
token.PLUS,
token.MINUS,
token.STAR,
token.SLASH,
- token.VBAR,
- token.AMPER,
+ token.DOUBLESLASH,
token.PERCENT,
- token.CIRCUMFLEX,
+ token.AT,
token.TILDE,
- token.LEFTSHIFT,
- token.RIGHTSHIFT,
token.DOUBLESTAR,
- token.DOUBLESLASH,
}
STARS = {token.STAR, token.DOUBLESTAR}
VARARGS_PARENTS = {
syms.power,
}
COMPREHENSION_PRIORITY = 20
-COMMA_PRIORITY = 10
-TERNARY_PRIORITY = 7
-LOGIC_PRIORITY = 5
-STRING_PRIORITY = 4
-COMPARATOR_PRIORITY = 3
-MATH_PRIORITY = 1
+COMMA_PRIORITY = 18
+TERNARY_PRIORITY = 16
+LOGIC_PRIORITY = 14
+STRING_PRIORITY = 12
+COMPARATOR_PRIORITY = 10
+MATH_PRIORITIES = {
+ token.VBAR: 8,
+ token.CIRCUMFLEX: 7,
+ token.AMPER: 6,
+ token.LEFTSHIFT: 5,
+ token.RIGHTSHIFT: 5,
+ token.PLUS: 4,
+ token.MINUS: 4,
+ token.STAR: 3,
+ token.SLASH: 3,
+ token.DOUBLESLASH: 3,
+ token.PERCENT: 3,
+ token.AT: 3,
+ token.TILDE: 2,
+ token.DOUBLESTAR: 1,
+}
@dataclass
if not has_value:
return
+ if token.COLON == leaf.type and self.is_class_paren_empty:
+ del self.leaves[-2:]
if self.leaves and not preformatted:
# Note: at this point leaf.prefix should be empty except for
# imports, for which we only preserve newlines.
if self.inside_brackets or not preformatted:
self.bracket_tracker.mark(leaf)
self.maybe_remove_trailing_comma(leaf)
-
if not self.append_comment(leaf):
self.leaves.append(leaf)
and self.leaves[0].value == "yield"
)
+ @property
+ def is_class_paren_empty(self) -> bool:
+ """Is this a class with no base classes but using parentheses?
+
+ Those are unnecessary and should be removed.
+ """
+ return (
+ bool(self)
+ and len(self.leaves) == 4
+ and self.is_class
+ and self.leaves[2].type == token.LPAR
+ and self.leaves[2].value == "("
+ and self.leaves[3].type == token.RPAR
+ and self.leaves[3].value == ")"
+ )
+
def contains_standalone_comments(self, depth_limit: int = sys.maxsize) -> bool:
"""If so, needs to be split before emitting."""
for leaf in self.leaves:
self.remove_trailing_comma()
return True
- # For parens let's check if it's safe to remove the comma. If the
- # trailing one is the only one, we might mistakenly change a tuple
- # into a different type by removing the comma.
+ # For parens let's check if it's safe to remove the comma.
+ # Imports are always safe.
+ if self.is_import:
+ self.remove_trailing_comma()
+ return True
+
+ # Otheriwsse, if the trailing one is the only one, we might mistakenly
+ # change a tuple into a different type by removing the comma.
depth = closing.bracket_depth + 1
commas = 0
opening = closing.opening_bracket
and leaf.parent
and leaf.parent.type not in {syms.factor, syms.star_expr}
):
- return MATH_PRIORITY
+ return MATH_PRIORITIES[leaf.type]
if leaf.type in COMPARATORS:
return COMPARATOR_PRIORITY
"""Split line into many lines, starting with the first matching bracket pair.
Note: this usually looks weird, only use this for function definitions.
- Prefer RHS otherwise.
+ Prefer RHS otherwise. This is why this function is not symmetrical with
+ :func:`right_hand_split` which also handles optional parentheses.
"""
head = Line(depth=line.depth)
body = Line(depth=line.depth + 1, inside_brackets=True)
def right_hand_split(
line: Line, py36: bool = False, omit: Collection[LeafID] = ()
) -> Iterator[Line]:
- """Split line into many lines, starting with the last matching bracket pair."""
+ """Split line into many lines, starting with the last matching bracket pair.
+
+ If the split was by optional parentheses, attempt splitting without them, too.
+ """
head = Line(depth=line.depth)
body = Line(depth=line.depth + 1, inside_brackets=True)
tail = Line(depth=line.depth)
bracket_split_succeeded_or_raise(head, body, tail)
assert opening_bracket and closing_bracket
if (
+ # the opening bracket is an optional paren
opening_bracket.type == token.LPAR
and not opening_bracket.value
+ # the closing bracket is an optional paren
and closing_bracket.type == token.RPAR
and not closing_bracket.value
+ # there are no delimiters or standalone comments in the body
+ and not body.bracket_tracker.delimiters
+ and not line.contains_standalone_comments(0)
+ # and it's not an import (optional parens are the only thing we can split
+ # on in this case; attempting a split without them is a waste of time)
+ and not line.is_import
):
- # These parens were optional. If there aren't any delimiters or standalone
- # comments in the body, they were unnecessary and another split without
- # them should be attempted.
- if not (
- body.bracket_tracker.delimiters or line.contains_standalone_comments(0)
- ):
- omit = {id(closing_bracket), *omit}
+ omit = {id(closing_bracket), *omit}
+ try:
yield from right_hand_split(line, py36=py36, omit=omit)
return
+ except CannotSplit:
+ pass
ensure_visible(opening_bracket)
ensure_visible(closing_bracket)
for child in list(node.children):
if check_lpar:
if child.type == syms.atom:
- if not (
- is_empty_tuple(child)
- or is_one_tuple(child)
- or max_delimiter_priority_in_atom(child) >= COMMA_PRIORITY
- ):
- first = child.children[0]
- last = child.children[-1]
- if first.type == token.LPAR and last.type == token.RPAR:
- # make parentheses invisible
- first.value = "" # type: ignore
- last.value = "" # type: ignore
+ maybe_make_parens_invisible_in_atom(child)
elif is_one_tuple(child):
# wrap child in visible parentheses
lpar = Leaf(token.LPAR, "(")
check_lpar = isinstance(child, Leaf) and child.value in parens_after
+def maybe_make_parens_invisible_in_atom(node: LN) -> bool:
+ """If it's safe, make the parens in the atom `node` invisible, recusively."""
+ if (
+ node.type != syms.atom
+ or is_empty_tuple(node)
+ or is_one_tuple(node)
+ or max_delimiter_priority_in_atom(node) >= COMMA_PRIORITY
+ ):
+ return False
+
+ first = node.children[0]
+ last = node.children[-1]
+ if first.type == token.LPAR and last.type == token.RPAR:
+ # make parentheses invisible
+ first.value = "" # type: ignore
+ last.value = "" # type: ignore
+ if len(node.children) > 1:
+ maybe_make_parens_invisible_in_atom(node.children[1])
+ return True
+
+ return False
+
+
def is_empty_tuple(node: LN) -> bool:
"""Return True if `node` holds an empty tuple."""
return (
Currently looking for:
- f-strings; and
- - trailing commas after * or ** in function signatures.
+ - trailing commas after * or ** in function signatures and calls.
"""
for n in node.pre_order():
if n.type == token.STRING:
return True
elif (
- n.type == syms.typedargslist
+ n.type in {syms.typedargslist, syms.arglist}
and n.children
and n.children[-1].type == token.COMMA
):
if ch.type in STARS:
return True
+ if ch.type == syms.argument:
+ for argch in ch.children:
+ if argch.type in STARS:
+ return True
+
return False