-
Notifications
You must be signed in to change notification settings - Fork 2.5k
New issue
Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.
By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.
Already on GitHub? Sign in to your account
Format subscriptions in a PEP-8 compliant way #178
Changes from 3 commits
File filter
Filter by extension
Conversations
Jump to
Diff view
Diff view
There are no files selected for viewing
Original file line number | Diff line number | Diff line change |
---|---|---|
|
@@ -89,11 +89,11 @@ def __init__(self, consumed: int) -> None: | |
self.consumed = consumed | ||
|
||
def trim_prefix(self, leaf: Leaf) -> None: | ||
leaf.prefix = leaf.prefix[self.consumed:] | ||
leaf.prefix = leaf.prefix[self.consumed :] | ||
|
||
def leaf_from_consumed(self, leaf: Leaf) -> Leaf: | ||
"""Returns a new Leaf from the consumed part of the prefix.""" | ||
unformatted_prefix = leaf.prefix[:self.consumed] | ||
unformatted_prefix = leaf.prefix[: self.consumed] | ||
return Leaf(token.NEWLINE, unformatted_prefix) | ||
|
||
|
||
|
@@ -582,6 +582,23 @@ def show(cls, code: str) -> None: | |
syms.listmaker, | ||
syms.testlist_gexp, | ||
} | ||
TEST_DESCENDANTS = { | ||
syms.test, | ||
syms.lambdef, | ||
syms.or_test, | ||
syms.and_test, | ||
syms.not_test, | ||
syms.comparison, | ||
syms.star_expr, | ||
syms.expr, | ||
syms.xor_expr, | ||
syms.and_expr, | ||
syms.shift_expr, | ||
syms.arith_expr, | ||
syms.trailer, | ||
syms.term, | ||
syms.power, | ||
} | ||
COMPREHENSION_PRIORITY = 20 | ||
COMMA_PRIORITY = 10 | ||
TERNARY_PRIORITY = 7 | ||
|
@@ -698,6 +715,13 @@ def maybe_decrement_after_lambda_arguments(self, leaf: Leaf) -> bool: | |
|
||
return False | ||
|
||
def get_open_lsqb(self, leaf: Leaf) -> Optional[Leaf]: | ||
"""Returns the most recent opening square bracket at `leaf` (if any).""" | ||
if leaf.type == token.LSQB: | ||
return leaf | ||
|
||
return self.bracket_match.get((self.depth - 1, token.RSQB)) | ||
|
||
|
||
@dataclass | ||
class Line: | ||
|
@@ -726,7 +750,9 @@ def append(self, leaf: Leaf, preformatted: bool = False) -> None: | |
if self.leaves and not preformatted: | ||
# Note: at this point leaf.prefix should be empty except for | ||
# imports, for which we only preserve newlines. | ||
leaf.prefix += whitespace(leaf) | ||
leaf.prefix += whitespace( | ||
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. Yeah, much better. |
||
leaf, complex_subscript=self.is_complex_subscript(leaf) | ||
) | ||
if self.inside_brackets or not preformatted: | ||
self.bracket_tracker.mark(leaf) | ||
self.maybe_remove_trailing_comma(leaf) | ||
|
@@ -859,7 +885,7 @@ def maybe_remove_trailing_comma(self, closing: Leaf) -> bool: | |
else: | ||
return False | ||
|
||
for leaf in self.leaves[_opening_index + 1:]: | ||
for leaf in self.leaves[_opening_index + 1 :]: | ||
if leaf is closing: | ||
break | ||
|
||
|
@@ -920,6 +946,22 @@ def remove_trailing_comma(self) -> None: | |
self.comments[i] = (comma_index - 1, comment) | ||
self.leaves.pop() | ||
|
||
def is_complex_subscript(self, leaf: Leaf) -> bool: | ||
"""Returns True iff `leaf` is part of a slice with non-trivial exprs.""" | ||
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. Ditto about imperative. |
||
open_lsqb = self.bracket_tracker.get_open_lsqb(leaf) | ||
if open_lsqb is None: | ||
return False | ||
|
||
subscript_start = open_lsqb.next_sibling | ||
if ( | ||
isinstance(subscript_start, Node) | ||
and subscript_start.type == syms.subscriptlist | ||
): | ||
subscript_start = child_towards(subscript_start, leaf) | ||
return subscript_start is not None and any( | ||
n.type in TEST_DESCENDANTS for n in subscript_start.pre_order() | ||
) | ||
|
||
def __str__(self) -> str: | ||
"""Render the line.""" | ||
if not self: | ||
|
@@ -1303,8 +1345,12 @@ def __attrs_post_init__(self) -> None: | |
ALWAYS_NO_SPACE = CLOSING_BRACKETS | {token.COMMA, STANDALONE_COMMENT} | ||
|
||
|
||
def whitespace(leaf: Leaf) -> str: # noqa C901 | ||
"""Return whitespace prefix if needed for the given `leaf`.""" | ||
def whitespace(leaf: Leaf, *, complex_subscript: bool) -> str: # noqa C901 | ||
"""Return whitespace prefix if needed for the given `leaf`. | ||
|
||
`complex_subscript` signals whether the given leaf is part of a subscription | ||
which has non-trivial arguments, like arithmetic expressions or function calls. | ||
""" | ||
NO = "" | ||
SPACE = " " | ||
DOUBLESPACE = " " | ||
|
@@ -1318,7 +1364,10 @@ def whitespace(leaf: Leaf) -> str: # noqa C901 | |
return DOUBLESPACE | ||
|
||
assert p is not None, f"INTERNAL ERROR: hand-made leaf without parent: {leaf!r}" | ||
if t == token.COLON and p.type not in {syms.subscript, syms.subscriptlist}: | ||
if ( | ||
t == token.COLON | ||
and p.type not in {syms.subscript, syms.subscriptlist, syms.sliceop} | ||
): | ||
return NO | ||
|
||
prev = leaf.prev_sibling | ||
|
@@ -1328,7 +1377,14 @@ def whitespace(leaf: Leaf) -> str: # noqa C901 | |
return NO | ||
|
||
if t == token.COLON: | ||
return SPACE if prevp.type == token.COMMA else NO | ||
if prevp.type == token.COLON: | ||
return NO | ||
|
||
elif prevp.type != token.COMMA and not complex_subscript: | ||
return NO | ||
|
||
else: | ||
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. Much better! nit: |
||
return SPACE | ||
|
||
if prevp.type == token.EQUAL: | ||
if prevp.parent: | ||
|
@@ -1349,7 +1405,7 @@ def whitespace(leaf: Leaf) -> str: # noqa C901 | |
|
||
elif prevp.type == token.COLON: | ||
if prevp.parent and prevp.parent.type in {syms.subscript, syms.sliceop}: | ||
return NO | ||
return SPACE if complex_subscript else NO | ||
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. The default in this function is SPACE. We should avoid returning it from sub-branches. Instead, just add another check to the There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. This needs to return Unless you prefer to handle this case down there too |
||
|
||
elif ( | ||
prevp.parent | ||
|
@@ -1455,7 +1511,7 @@ def whitespace(leaf: Leaf) -> str: # noqa C901 | |
if prev and prev.type == token.LPAR: | ||
return NO | ||
|
||
elif p.type == syms.subscript: | ||
elif p.type in {syms.subscript, syms.sliceop}: | ||
# indexing | ||
if not prev: | ||
assert p.parent is not None, "subscripts are always parented" | ||
|
@@ -1464,7 +1520,7 @@ def whitespace(leaf: Leaf) -> str: # noqa C901 | |
|
||
return NO | ||
|
||
else: | ||
elif not complex_subscript: | ||
return NO | ||
|
||
elif p.type == syms.atom: | ||
|
@@ -1534,6 +1590,14 @@ def preceding_leaf(node: Optional[LN]) -> Optional[Leaf]: | |
return None | ||
|
||
|
||
def child_towards(ancestor: Node, descendant: LN) -> Optional[LN]: | ||
"""Return the child of `ancestor` that contains `descendant`.""" | ||
node: Optional[LN] = descendant | ||
while node and node.parent != ancestor: | ||
node = node.parent | ||
return node | ||
|
||
|
||
def is_split_after_delimiter(leaf: Leaf, previous: Leaf = None) -> int: | ||
"""Return the priority of the `leaf` delimiter, given a line break after it. | ||
|
||
|
@@ -1994,6 +2058,7 @@ def explode_split( | |
|
||
try: | ||
yield from delimiter_split(new_lines[1], py36) | ||
|
||
except CannotSplit: | ||
yield new_lines[1] | ||
|
||
|
@@ -2061,7 +2126,7 @@ def normalize_string_quotes(leaf: Leaf) -> None: | |
unescaped_new_quote = re.compile(rf"(([^\\]|^)(\\\\)*){new_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)] | ||
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 | ||
|
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,31 @@ | ||
slice[a.b : c.d] | ||
slice[d :: d + 1] | ||
slice[d + 1 :: d] | ||
slice[d::d] | ||
slice[0] | ||
slice[-1] | ||
slice[:-1] | ||
slice[::-1] | ||
slice[:c, c - 1] | ||
slice[c, c + 1, d::] | ||
slice[ham[c::d] :: 1] | ||
slice[ham[cheese ** 2 : -1] : 1 : 1, ham[1:2]] | ||
slice[:-1:] | ||
slice[lambda: None : lambda: None] | ||
slice[lambda x, y, *args, really=2, **kwargs: None :, None::] | ||
slice[1 or 2 : True and False] | ||
slice[not so_simple : 1 < val <= 10] | ||
slice[(1 for i in range(42)) : x] | ||
slice[:: [i for i in range(42)]] | ||
|
||
|
||
async def f(): | ||
slice[await x : [i async for i in arange(42)] : 42] | ||
|
||
|
||
# These are from PEP-8: | ||
ham[1:9], ham[1:9:3], ham[:9:3], ham[1::3], ham[1:9:] | ||
ham[lower:upper], ham[lower:upper:], ham[lower::step] | ||
# ham[lower+offset : upper+offset] | ||
ham[: upper_fn(x) : step_fn(x)], ham[:: step_fn(x)] | ||
ham[lower + offset : upper + offset] |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Adding a
leaf
argument toget_open_lsqb()
creates a rather bizarre signature. It suggests thatleaf
's position on the line has something to do with the result but it doesn't. It's simply a special case ifleaf
itself is a LSQB and is not otherwise used. I think that path should be handled directly inis_complex_subscript()
.nit: docstrings should be in imperative: "Return" rather than "Returns".