X-Git-Url: https://git.madduck.net/etc/vim.git/blobdiff_plain/8b64e916f65f2c1023f9abd6243a904bfbcd8fe9..3ad0f5855c46410652b27b5e09c6f22314241757:/black.py?ds=sidebyside diff --git a/black.py b/black.py index a21b51a..298597b 100644 --- a/black.py +++ b/black.py @@ -239,11 +239,8 @@ def reformat_one( src = src.resolve() if src in cache and cache[src] == get_cache_info(src): changed = Changed.CACHED - if ( - changed is not Changed.CACHED - and format_file_in_place( - src, line_length=line_length, fast=fast, write_back=write_back - ) + if changed is not Changed.CACHED and format_file_in_place( + src, line_length=line_length, fast=fast, write_back=write_back ): changed = Changed.YES if write_back == WriteBack.YES and changed is not Changed.NO: @@ -285,32 +282,29 @@ async def schedule_formatting( manager = Manager() lock = manager.Lock() tasks = { - src: loop.run_in_executor( + loop.run_in_executor( executor, format_file_in_place, src, line_length, fast, write_back, lock - ) - for src in sources + ): src + for src in sorted(sources) } - _task_values = list(tasks.values()) + pending: Iterable[asyncio.Task] = tasks.keys() try: - loop.add_signal_handler(signal.SIGINT, cancel, _task_values) - loop.add_signal_handler(signal.SIGTERM, cancel, _task_values) + loop.add_signal_handler(signal.SIGINT, cancel, pending) + loop.add_signal_handler(signal.SIGTERM, cancel, pending) except NotImplementedError: # There are no good alternatives for these on Windows pass - await asyncio.wait(_task_values) - for src, task in tasks.items(): - if not task.done(): - report.failed(src, "timed out, cancelling") - task.cancel() - cancelled.append(task) - elif task.cancelled(): - cancelled.append(task) - elif task.exception(): - report.failed(src, str(task.exception())) - else: - formatted.append(src) - report.done(src, Changed.YES if task.result() else Changed.NO) - + while pending: + done, _ = await asyncio.wait(pending, return_when=asyncio.FIRST_COMPLETED) + for task in done: + src = tasks.pop(task) + if task.cancelled(): + cancelled.append(task) + elif task.exception(): + report.failed(src, str(task.exception())) + else: + formatted.append(src) + report.done(src, Changed.YES if task.result() else Changed.NO) if cancelled: await asyncio.gather(*cancelled, loop=loop, return_exceptions=True) if write_back == WriteBack.YES and formatted: @@ -632,21 +626,22 @@ 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, + token.VBAR: 9, + token.CIRCUMFLEX: 8, + token.AMPER: 7, + token.LEFTSHIFT: 6, + token.RIGHTSHIFT: 6, + token.PLUS: 5, + token.MINUS: 5, + token.STAR: 4, + token.SLASH: 4, + token.DOUBLESLASH: 4, + token.PERCENT: 4, + token.AT: 4, + token.TILDE: 3, + token.DOUBLESTAR: 2, } +DOT_PRIORITY = 1 @dataclass @@ -712,6 +707,17 @@ class BracketTracker: """ return max(v for k, v in self.delimiters.items() if k not in exclude) + def delimiter_count_with_priority(self, priority: int = 0) -> int: + """Return the number of delimiters with the given `priority`. + + If no `priority` is passed, defaults to max priority on the line. + """ + if not self.delimiters: + return 0 + + priority = priority or self.max_delimiter_priority() + return sum(1 for p in self.delimiters.values() if p == priority) + def maybe_increment_for_loop_variable(self, leaf: Leaf) -> bool: """In a for loop, or comprehension, the variables are often unpacks. @@ -843,10 +849,9 @@ class Line: @property def is_stub_class(self) -> bool: """Is this line a class definition with a body consisting only of "..."?""" - return ( - self.is_class - and self.leaves[-3:] == [Leaf(token.DOT, ".") for _ in range(3)] - ) + return self.is_class and self.leaves[-3:] == [ + Leaf(token.DOT, ".") for _ in range(3) + ] @property def is_def(self) -> bool: @@ -860,14 +865,11 @@ class Line: second_leaf: Optional[Leaf] = self.leaves[1] except IndexError: second_leaf = None - return ( - (first_leaf.type == token.NAME and first_leaf.value == "def") - or ( - first_leaf.type == token.ASYNC - and second_leaf is not None - and second_leaf.type == token.NAME - and second_leaf.value == "def" - ) + return (first_leaf.type == token.NAME and first_leaf.value == "def") or ( + first_leaf.type == token.ASYNC + and second_leaf is not None + and second_leaf.type == token.NAME + and second_leaf.value == "def" ) @property @@ -1032,9 +1034,8 @@ class Line: 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()) + return subscript_start is not None and any( + n.type in TEST_DESCENDANTS for n in subscript_start.pre_order() ) def __str__(self) -> str: @@ -1314,7 +1315,7 @@ class LineGenerator(Visitor[Line]): The relevant Python language `keywords` for a given statement will be NAME leaves within it. This methods puts those on a separate line. - `parens` holds a set of string leaf values immeditely after which + `parens` holds a set of string leaf values immediately after which invisible parens should be put. """ normalize_invisible_parens(node, parens_after=parens) @@ -1471,10 +1472,9 @@ def whitespace(leaf: Leaf, *, complex_subscript: bool) -> 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, syms.sliceop} - ): + if t == token.COLON and p.type not in { + syms.subscript, syms.subscriptlist, syms.sliceop + }: return NO prev = leaf.prev_sibling @@ -1648,10 +1648,9 @@ def whitespace(leaf: Leaf, *, complex_subscript: bool) -> str: # noqa C901 prevp_parent = prevp.parent assert prevp_parent is not None - if ( - prevp.type == token.COLON - and prevp_parent.type in {syms.subscript, syms.sliceop} - ): + if prevp.type == token.COLON and prevp_parent.type in { + syms.subscript, syms.sliceop + }: return NO elif prevp.type == token.EQUAL and prevp_parent.type == syms.argument: @@ -1731,6 +1730,14 @@ def is_split_before_delimiter(leaf: Leaf, previous: Leaf = None) -> int: # Don't treat them as a delimiter. return 0 + if ( + leaf.type == token.DOT + and leaf.parent + and leaf.parent.type not in {syms.import_from, syms.dotted_name} + and (previous is None or previous.type != token.NAME) + ): + return DOT_PRIORITY + if ( leaf.type in MATH_OPERATORS and leaf.parent @@ -1748,31 +1755,54 @@ def is_split_before_delimiter(leaf: Leaf, previous: Leaf = None) -> int: ): return STRING_PRIORITY + if leaf.type != token.NAME: + return 0 + if ( - leaf.type == token.NAME - and leaf.value == "for" + leaf.value == "for" and leaf.parent and leaf.parent.type in {syms.comp_for, syms.old_comp_for} ): return COMPREHENSION_PRIORITY if ( - leaf.type == token.NAME - and leaf.value == "if" + leaf.value == "if" and leaf.parent and leaf.parent.type in {syms.comp_if, syms.old_comp_if} ): return COMPREHENSION_PRIORITY + if leaf.value in {"if", "else"} and leaf.parent and leaf.parent.type == syms.test: + return TERNARY_PRIORITY + + if leaf.value == "is": + return COMPARATOR_PRIORITY + if ( - leaf.type == token.NAME - and leaf.value in {"if", "else"} + leaf.value == "in" and leaf.parent - and leaf.parent.type == syms.test + and leaf.parent.type in {syms.comp_op, syms.comparison} + and not ( + previous is not None + and previous.type == token.NAME + and previous.value == "not" + ) ): - return TERNARY_PRIORITY + return COMPARATOR_PRIORITY - if leaf.type == token.NAME and leaf.value in LOGIC_OPERATORS and leaf.parent: + if ( + leaf.value == "not" + and leaf.parent + and leaf.parent.type == syms.comp_op + and not ( + previous is not None + and previous.type == token.NAME + and previous.value == "is" + ) + ): + return COMPARATOR_PRIORITY + + if leaf.value in LOGIC_OPERATORS and leaf.parent: return LOGIC_PRIORITY return 0 @@ -1999,8 +2029,9 @@ def right_hand_split( # Since body is a new indent level, remove spurious leading whitespace. if body_leaves: normalize_prefix(body_leaves[0], inside_brackets=True) - elif not head_leaves: - # No `head` and no `body` means the split failed. `tail` has all content. + if not head_leaves: + # No `head` means the split failed. Either `tail` has all content or + # the matching `opening_bracket` wasn't available on `line` anymore. raise CannotSplit("No brackets found") # Build the new lines. @@ -2018,19 +2049,27 @@ def right_hand_split( # 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 + # there are no standalone comments in the body 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 ): omit = {id(closing_bracket), *omit} - try: - yield from right_hand_split(line, py36=py36, omit=omit) - return - except CannotSplit: - pass + delimiter_count = body.bracket_tracker.delimiter_count_with_priority() + if ( + delimiter_count == 0 + or delimiter_count == 1 + and ( + body.leaves[0].type in OPENING_BRACKETS + or body.leaves[-1].type in CLOSING_BRACKETS + ) + ): + try: + yield from right_hand_split(line, py36=py36, omit=omit) + return + except CannotSplit: + pass ensure_visible(opening_bracket) ensure_visible(closing_bracket) @@ -2092,14 +2131,16 @@ def delimiter_split(line: Line, py36: bool = False) -> Iterator[Line]: except IndexError: raise CannotSplit("Line empty") - delimiters = line.bracket_tracker.delimiters + bt = line.bracket_tracker try: - delimiter_priority = line.bracket_tracker.max_delimiter_priority( - exclude={id(last_leaf)} - ) + delimiter_priority = bt.max_delimiter_priority(exclude={id(last_leaf)}) except ValueError: raise CannotSplit("No delimiters found") + if delimiter_priority == DOT_PRIORITY: + if bt.delimiter_count_with_priority(delimiter_priority) == 1: + raise CannotSplit("Splitting a single attribute from its owner looks wrong") + current_line = Line(depth=line.depth, inside_brackets=line.inside_brackets) lowest_depth = sys.maxsize trailing_comma_safe = True @@ -2122,12 +2163,11 @@ def delimiter_split(line: Line, py36: bool = False) -> Iterator[Line]: yield from append_to_line(comment_after) lowest_depth = min(lowest_depth, leaf.bracket_depth) - if ( - leaf.bracket_depth == lowest_depth - and is_vararg(leaf, within=VARARGS_PARENTS) + if leaf.bracket_depth == lowest_depth and is_vararg( + leaf, within=VARARGS_PARENTS ): trailing_comma_safe = trailing_comma_safe and py36 - leaf_priority = delimiters.get(id(leaf)) + leaf_priority = bt.delimiters.get(id(leaf)) if leaf_priority == delimiter_priority: yield current_line @@ -2321,7 +2361,7 @@ def normalize_invisible_parens(node: Node, parens_after: Set[str]) -> None: rpar = Leaf(token.RPAR, ")") index = child.remove() or 0 node.insert_child(index, Node(syms.atom, [lpar, child, rpar])) - else: + elif not (isinstance(child, Leaf) and is_multiline_string(child)): # wrap child in invisible parentheses lpar = Leaf(token.LPAR, "") rpar = Leaf(token.RPAR, "") @@ -2432,6 +2472,12 @@ def is_vararg(leaf: Leaf, within: Set[NodeType]) -> bool: return p.type in within +def is_multiline_string(leaf: Leaf) -> bool: + """Return True if `leaf` is a multiline string that actually spans many lines.""" + value = leaf.value.lstrip("furbFURB") + return value[:3] in {'"""', "'''"} and "\n" in value + + def is_stub_suite(node: Node) -> bool: """Return True if `node` is a suite with a stub body.""" if ( @@ -2831,7 +2877,7 @@ def diff(a: str, b: str, a_name: str, b_name: str) -> str: ) -def cancel(tasks: List[asyncio.Task]) -> None: +def cancel(tasks: Iterable[asyncio.Task]) -> None: """asyncio signal handler that cancels all `tasks` and reports to stderr.""" err("Aborted!") for task in tasks: