Iterator,
List,
Optional,
+ Pattern,
Set,
Tuple,
Type,
from blib2to3.pgen2 import driver, token
from blib2to3.pgen2.parse import ParseError
-__version__ = "18.4a1"
+__version__ = "18.4a2"
DEFAULT_LINE_LENGTH = 88
# types
syms = pygram.python_symbols
token.DOUBLESTAR,
token.DOUBLESLASH,
}
-VARARGS = {token.STAR, token.DOUBLESTAR}
+STARS = {token.STAR, token.DOUBLESTAR}
+VARARGS_PARENTS = {
+ syms.arglist,
+ syms.argument, # double star in arglist
+ syms.trailer, # single argument to call
+ syms.typedargslist,
+ syms.varargslist, # lambdas
+}
+UNPACKING_PARENTS = {
+ syms.atom, # single element of a list or set literal
+ syms.dictsetmaker,
+ syms.listmaker,
+ syms.testlist_gexp,
+}
COMPREHENSION_PRIORITY = 20
COMMA_PRIORITY = 10
LOGIC_PRIORITY = 5
# that, too.
return prevp.prefix
- elif prevp.type == token.DOUBLESTAR:
- if (
- prevp.parent
- and prevp.parent.type in {
- syms.arglist,
- syms.argument,
- syms.dictsetmaker,
- syms.parameters,
- syms.typedargslist,
- syms.varargslist,
- }
- ):
+ elif prevp.type in STARS:
+ if is_vararg(prevp, within=VARARGS_PARENTS | UNPACKING_PARENTS):
return NO
elif prevp.type == token.COLON:
elif (
prevp.parent
- and prevp.parent.type in {syms.factor, syms.star_expr}
+ and prevp.parent.type == syms.factor
and prevp.type in MATH_OPERATORS
):
return NO
if not prevp or prevp.type == token.LPAR:
return NO
- elif prev.type == token.EQUAL or prev.type == token.DOUBLESTAR:
+ elif prev.type in {token.EQUAL} | STARS:
return NO
elif p.type == syms.decorator:
Higher numbers are higher priority.
"""
- if (
- leaf.type in VARARGS
- and leaf.parent
- and leaf.parent.type in {syms.argument, syms.typedargslist, syms.dictsetmaker}
- ):
+ if is_vararg(leaf, within=VARARGS_PARENTS | UNPACKING_PARENTS):
# * and ** might also be MATH_OPERATORS but in this case they are not.
# Don't treat them as a delimiter.
return 0
lowest_depth = min(lowest_depth, leaf.bracket_depth)
if (
leaf.bracket_depth == lowest_depth
- and leaf.type == token.STAR
- or leaf.type == token.DOUBLESTAR
+ and is_vararg(leaf, within=VARARGS_PARENTS)
):
trailing_comma_safe = trailing_comma_safe and py36
leaf_priority = delimiters.get(id(leaf))
return # There's an internal error
prefix = leaf.value[:first_quote_pos]
- body = leaf.value[first_quote_pos + len(orig_quote):-len(orig_quote)]
unescaped_new_quote = re.compile(rf"(([^\\]|^)(\\\\)*){new_quote}")
- escaped_orig_quote = re.compile(rf"\\(\\\\)*{orig_quote}")
+ escaped_new_quote = re.compile(rf"([^\\]|^)\\(\\\\)*{new_quote}")
+ escaped_orig_quote = re.compile(rf"([^\\]|^)\\(\\\\)*{orig_quote}")
+ body = leaf.value[first_quote_pos + len(orig_quote):-len(orig_quote)]
if "r" in prefix.casefold():
if unescaped_new_quote.search(body):
# There's at least one unescaped new_quote in this raw string
# Do not introduce or remove backslashes in raw strings
new_body = body
else:
- new_body = escaped_orig_quote.sub(rf"\1{orig_quote}", body)
- new_body = unescaped_new_quote.sub(rf"\1\\{new_quote}", new_body)
- # Add escapes again for consecutive occurences of new_quote (sub
- # doesn't match overlapping substrings).
- new_body = unescaped_new_quote.sub(rf"\1\\{new_quote}", new_body)
+ # remove unnecessary quotes
+ new_body = sub_twice(escaped_new_quote, rf"\1\2{new_quote}", body)
+ if body != new_body:
+ # Consider the string without unnecessary quotes as the original
+ body = new_body
+ leaf.value = f"{prefix}{orig_quote}{body}{orig_quote}"
+ new_body = sub_twice(escaped_orig_quote, rf"\1\2{orig_quote}", new_body)
+ new_body = sub_twice(unescaped_new_quote, rf"\1\\{new_quote}", new_body)
if new_quote == '"""' and new_body[-1] == '"':
# edge case:
new_body = new_body[:-1] + '\\"'
)
+def is_vararg(leaf: Leaf, within: Set[NodeType]) -> bool:
+ """Return True if `leaf` is a star or double star in a vararg or kwarg.
+
+ If `within` includes VARARGS_PARENTS, this applies to function signatures.
+ If `within` includes COLLECTION_LIBERALS_PARENTS, it applies to right
+ hand-side extended iterable unpacking (PEP 3132) and additional unpacking
+ generalizations (PEP 448).
+ """
+ if leaf.type not in STARS or not leaf.parent:
+ return False
+
+ p = leaf.parent
+ if p.type == syms.star_expr:
+ # Star expressions are also used as assignment targets in extended
+ # iterable unpacking (PEP 3132). See what its parent is instead.
+ if not p.parent:
+ return False
+
+ p = p.parent
+
+ return p.type in within
+
+
def max_delimiter_priority_in_atom(node: LN) -> int:
if node.type != syms.atom:
return 0
and n.children[-1].type == token.COMMA
):
for ch in n.children:
- if ch.type == token.STAR or ch.type == token.DOUBLESTAR:
+ if ch.type in STARS:
return True
return False
import tempfile
with tempfile.NamedTemporaryFile(
- mode="w", prefix="blk_", suffix=".log", delete=False
+ mode="w", prefix="blk_", suffix=".log", delete=False, encoding="utf8"
) as f:
for lines in output:
f.write(lines)
loop.close()
+def sub_twice(regex: Pattern[str], replacement: str, original: str) -> str:
+ """Replace `regex` with `replacement` twice on `original`.
+
+ This is used by string normalization to perform replaces on
+ overlapping matches.
+ """
+ return regex.sub(replacement, regex.sub(replacement, original))
+
+
if __name__ == "__main__":
main()