]> git.madduck.net Git - etc/vim.git/blob - black.py

madduck's git repository

Every one of the projects in this repository is available at the canonical URL git://git.madduck.net/madduck/pub/<projectpath> — see each project's metadata for the exact URL.

All patches and comments are welcome. Please squash your changes to logical commits before using git-format-patch and git-send-email to patches@git.madduck.net. If you'd read over the Git project's submission guidelines and adhered to them, I'd be especially grateful.

SSH access, as well as push access can be individually arranged.

If you use my repositories frequently, consider adding the following snippet to ~/.gitconfig and using the third clone URL listed for each project:

[url "git://git.madduck.net/madduck/"]
  insteadOf = madduck:

2ee7634c80b151e0e832a883ba7b55fa5c5b0643
[etc/vim.git] / black.py
1 #!/usr/bin/env python3
2
3 import asyncio
4 from asyncio.base_events import BaseEventLoop
5 from concurrent.futures import Executor, ProcessPoolExecutor
6 from enum import Enum
7 from functools import partial, wraps
8 import keyword
9 import logging
10 from multiprocessing import Manager
11 import os
12 from pathlib import Path
13 import tokenize
14 import signal
15 import sys
16 from typing import (
17     Any,
18     Callable,
19     Dict,
20     Generic,
21     Iterable,
22     Iterator,
23     List,
24     Optional,
25     Set,
26     Tuple,
27     Type,
28     TypeVar,
29     Union,
30 )
31
32 from attr import dataclass, Factory
33 import click
34
35 # lib2to3 fork
36 from blib2to3.pytree import Node, Leaf, type_repr
37 from blib2to3 import pygram, pytree
38 from blib2to3.pgen2 import driver, token
39 from blib2to3.pgen2.parse import ParseError
40
41 __version__ = "18.4a0"
42 DEFAULT_LINE_LENGTH = 88
43 # types
44 syms = pygram.python_symbols
45 FileContent = str
46 Encoding = str
47 Depth = int
48 NodeType = int
49 LeafID = int
50 Priority = int
51 Index = int
52 LN = Union[Leaf, Node]
53 SplitFunc = Callable[["Line", bool], Iterator["Line"]]
54 out = partial(click.secho, bold=True, err=True)
55 err = partial(click.secho, fg="red", err=True)
56
57
58 class NothingChanged(UserWarning):
59     """Raised by :func:`format_file` when reformatted code is the same as source."""
60
61
62 class CannotSplit(Exception):
63     """A readable split that fits the allotted line length is impossible.
64
65     Raised by :func:`left_hand_split`, :func:`right_hand_split`, and
66     :func:`delimiter_split`.
67     """
68
69
70 class FormatError(Exception):
71     """Base exception for `# fmt: on` and `# fmt: off` handling.
72
73     It holds the number of bytes of the prefix consumed before the format
74     control comment appeared.
75     """
76
77     def __init__(self, consumed: int) -> None:
78         super().__init__(consumed)
79         self.consumed = consumed
80
81     def trim_prefix(self, leaf: Leaf) -> None:
82         leaf.prefix = leaf.prefix[self.consumed:]
83
84     def leaf_from_consumed(self, leaf: Leaf) -> Leaf:
85         """Returns a new Leaf from the consumed part of the prefix."""
86         unformatted_prefix = leaf.prefix[:self.consumed]
87         return Leaf(token.NEWLINE, unformatted_prefix)
88
89
90 class FormatOn(FormatError):
91     """Found a comment like `# fmt: on` in the file."""
92
93
94 class FormatOff(FormatError):
95     """Found a comment like `# fmt: off` in the file."""
96
97
98 class WriteBack(Enum):
99     NO = 0
100     YES = 1
101     DIFF = 2
102
103
104 @click.command()
105 @click.option(
106     "-l",
107     "--line-length",
108     type=int,
109     default=DEFAULT_LINE_LENGTH,
110     help="How many character per line to allow.",
111     show_default=True,
112 )
113 @click.option(
114     "--check",
115     is_flag=True,
116     help=(
117         "Don't write the files back, just return the status.  Return code 0 "
118         "means nothing would change.  Return code 1 means some files would be "
119         "reformatted.  Return code 123 means there was an internal error."
120     ),
121 )
122 @click.option(
123     "--diff",
124     is_flag=True,
125     help="Don't write the files back, just output a diff for each file on stdout.",
126 )
127 @click.option(
128     "--fast/--safe",
129     is_flag=True,
130     help="If --fast given, skip temporary sanity checks. [default: --safe]",
131 )
132 @click.option(
133     "-q",
134     "--quiet",
135     is_flag=True,
136     help=(
137         "Don't emit non-error messages to stderr. Errors are still emitted, "
138         "silence those with 2>/dev/null."
139     ),
140 )
141 @click.version_option(version=__version__)
142 @click.argument(
143     "src",
144     nargs=-1,
145     type=click.Path(
146         exists=True, file_okay=True, dir_okay=True, readable=True, allow_dash=True
147     ),
148 )
149 @click.pass_context
150 def main(
151     ctx: click.Context,
152     line_length: int,
153     check: bool,
154     diff: bool,
155     fast: bool,
156     quiet: bool,
157     src: List[str],
158 ) -> None:
159     """The uncompromising code formatter."""
160     sources: List[Path] = []
161     for s in src:
162         p = Path(s)
163         if p.is_dir():
164             sources.extend(gen_python_files_in_dir(p))
165         elif p.is_file():
166             # if a file was explicitly given, we don't care about its extension
167             sources.append(p)
168         elif s == "-":
169             sources.append(Path("-"))
170         else:
171             err(f"invalid path: {s}")
172     if check and diff:
173         exc = click.ClickException("Options --check and --diff are mutually exclusive")
174         exc.exit_code = 2
175         raise exc
176
177     if check:
178         write_back = WriteBack.NO
179     elif diff:
180         write_back = WriteBack.DIFF
181     else:
182         write_back = WriteBack.YES
183     if len(sources) == 0:
184         ctx.exit(0)
185     elif len(sources) == 1:
186         p = sources[0]
187         report = Report(check=check, quiet=quiet)
188         try:
189             if not p.is_file() and str(p) == "-":
190                 changed = format_stdin_to_stdout(
191                     line_length=line_length, fast=fast, write_back=write_back
192                 )
193             else:
194                 changed = format_file_in_place(
195                     p, line_length=line_length, fast=fast, write_back=write_back
196                 )
197             report.done(p, changed)
198         except Exception as exc:
199             report.failed(p, str(exc))
200         ctx.exit(report.return_code)
201     else:
202         loop = asyncio.get_event_loop()
203         executor = ProcessPoolExecutor(max_workers=os.cpu_count())
204         return_code = 1
205         try:
206             return_code = loop.run_until_complete(
207                 schedule_formatting(
208                     sources, line_length, write_back, fast, quiet, loop, executor
209                 )
210             )
211         finally:
212             shutdown(loop)
213             ctx.exit(return_code)
214
215
216 async def schedule_formatting(
217     sources: List[Path],
218     line_length: int,
219     write_back: WriteBack,
220     fast: bool,
221     quiet: bool,
222     loop: BaseEventLoop,
223     executor: Executor,
224 ) -> int:
225     """Run formatting of `sources` in parallel using the provided `executor`.
226
227     (Use ProcessPoolExecutors for actual parallelism.)
228
229     `line_length`, `write_back`, and `fast` options are passed to
230     :func:`format_file_in_place`.
231     """
232     lock = None
233     if write_back == WriteBack.DIFF:
234         # For diff output, we need locks to ensure we don't interleave output
235         # from different processes.
236         manager = Manager()
237         lock = manager.Lock()
238     tasks = {
239         src: loop.run_in_executor(
240             executor, format_file_in_place, src, line_length, fast, write_back, lock
241         )
242         for src in sources
243     }
244     _task_values = list(tasks.values())
245     loop.add_signal_handler(signal.SIGINT, cancel, _task_values)
246     loop.add_signal_handler(signal.SIGTERM, cancel, _task_values)
247     await asyncio.wait(tasks.values())
248     cancelled = []
249     report = Report(check=write_back is WriteBack.NO, quiet=quiet)
250     for src, task in tasks.items():
251         if not task.done():
252             report.failed(src, "timed out, cancelling")
253             task.cancel()
254             cancelled.append(task)
255         elif task.cancelled():
256             cancelled.append(task)
257         elif task.exception():
258             report.failed(src, str(task.exception()))
259         else:
260             report.done(src, task.result())
261     if cancelled:
262         await asyncio.gather(*cancelled, loop=loop, return_exceptions=True)
263     elif not quiet:
264         out("All done! ✨ 🍰 ✨")
265     if not quiet:
266         click.echo(str(report))
267     return report.return_code
268
269
270 def format_file_in_place(
271     src: Path,
272     line_length: int,
273     fast: bool,
274     write_back: WriteBack = WriteBack.NO,
275     lock: Any = None,  # multiprocessing.Manager().Lock() is some crazy proxy
276 ) -> bool:
277     """Format file under `src` path. Return True if changed.
278
279     If `write_back` is True, write reformatted code back to stdout.
280     `line_length` and `fast` options are passed to :func:`format_file_contents`.
281     """
282     with tokenize.open(src) as src_buffer:
283         src_contents = src_buffer.read()
284     try:
285         dst_contents = format_file_contents(
286             src_contents, line_length=line_length, fast=fast
287         )
288     except NothingChanged:
289         return False
290
291     if write_back == write_back.YES:
292         with open(src, "w", encoding=src_buffer.encoding) as f:
293             f.write(dst_contents)
294     elif write_back == write_back.DIFF:
295         src_name = f"{src.name}  (original)"
296         dst_name = f"{src.name}  (formatted)"
297         diff_contents = diff(src_contents, dst_contents, src_name, dst_name)
298         if lock:
299             lock.acquire()
300         try:
301             sys.stdout.write(diff_contents)
302         finally:
303             if lock:
304                 lock.release()
305     return True
306
307
308 def format_stdin_to_stdout(
309     line_length: int, fast: bool, write_back: WriteBack = WriteBack.NO
310 ) -> bool:
311     """Format file on stdin. Return True if changed.
312
313     If `write_back` is True, write reformatted code back to stdout.
314     `line_length` and `fast` arguments are passed to :func:`format_file_contents`.
315     """
316     src = sys.stdin.read()
317     try:
318         dst = format_file_contents(src, line_length=line_length, fast=fast)
319         return True
320
321     except NothingChanged:
322         dst = src
323         return False
324
325     finally:
326         if write_back == WriteBack.YES:
327             sys.stdout.write(dst)
328         elif write_back == WriteBack.DIFF:
329             src_name = "<stdin>  (original)"
330             dst_name = "<stdin>  (formatted)"
331             sys.stdout.write(diff(src, dst, src_name, dst_name))
332
333
334 def format_file_contents(
335     src_contents: str, line_length: int, fast: bool
336 ) -> FileContent:
337     """Reformat contents a file and return new contents.
338
339     If `fast` is False, additionally confirm that the reformatted code is
340     valid by calling :func:`assert_equivalent` and :func:`assert_stable` on it.
341     `line_length` is passed to :func:`format_str`.
342     """
343     if src_contents.strip() == "":
344         raise NothingChanged
345
346     dst_contents = format_str(src_contents, line_length=line_length)
347     if src_contents == dst_contents:
348         raise NothingChanged
349
350     if not fast:
351         assert_equivalent(src_contents, dst_contents)
352         assert_stable(src_contents, dst_contents, line_length=line_length)
353     return dst_contents
354
355
356 def format_str(src_contents: str, line_length: int) -> FileContent:
357     """Reformat a string and return new contents.
358
359     `line_length` determines how many characters per line are allowed.
360     """
361     src_node = lib2to3_parse(src_contents)
362     dst_contents = ""
363     lines = LineGenerator()
364     elt = EmptyLineTracker()
365     py36 = is_python36(src_node)
366     empty_line = Line()
367     after = 0
368     for current_line in lines.visit(src_node):
369         for _ in range(after):
370             dst_contents += str(empty_line)
371         before, after = elt.maybe_empty_lines(current_line)
372         for _ in range(before):
373             dst_contents += str(empty_line)
374         for line in split_line(current_line, line_length=line_length, py36=py36):
375             dst_contents += str(line)
376     return dst_contents
377
378
379 GRAMMARS = [
380     pygram.python_grammar_no_print_statement_no_exec_statement,
381     pygram.python_grammar_no_print_statement,
382     pygram.python_grammar_no_exec_statement,
383     pygram.python_grammar,
384 ]
385
386
387 def lib2to3_parse(src_txt: str) -> Node:
388     """Given a string with source, return the lib2to3 Node."""
389     grammar = pygram.python_grammar_no_print_statement
390     if src_txt[-1] != "\n":
391         nl = "\r\n" if "\r\n" in src_txt[:1024] else "\n"
392         src_txt += nl
393     for grammar in GRAMMARS:
394         drv = driver.Driver(grammar, pytree.convert)
395         try:
396             result = drv.parse_string(src_txt, True)
397             break
398
399         except ParseError as pe:
400             lineno, column = pe.context[1]
401             lines = src_txt.splitlines()
402             try:
403                 faulty_line = lines[lineno - 1]
404             except IndexError:
405                 faulty_line = "<line number missing in source>"
406             exc = ValueError(f"Cannot parse: {lineno}:{column}: {faulty_line}")
407     else:
408         raise exc from None
409
410     if isinstance(result, Leaf):
411         result = Node(syms.file_input, [result])
412     return result
413
414
415 def lib2to3_unparse(node: Node) -> str:
416     """Given a lib2to3 node, return its string representation."""
417     code = str(node)
418     return code
419
420
421 T = TypeVar("T")
422
423
424 class Visitor(Generic[T]):
425     """Basic lib2to3 visitor that yields things of type `T` on `visit()`."""
426
427     def visit(self, node: LN) -> Iterator[T]:
428         """Main method to visit `node` and its children.
429
430         It tries to find a `visit_*()` method for the given `node.type`, like
431         `visit_simple_stmt` for Node objects or `visit_INDENT` for Leaf objects.
432         If no dedicated `visit_*()` method is found, chooses `visit_default()`
433         instead.
434
435         Then yields objects of type `T` from the selected visitor.
436         """
437         if node.type < 256:
438             name = token.tok_name[node.type]
439         else:
440             name = type_repr(node.type)
441         yield from getattr(self, f"visit_{name}", self.visit_default)(node)
442
443     def visit_default(self, node: LN) -> Iterator[T]:
444         """Default `visit_*()` implementation. Recurses to children of `node`."""
445         if isinstance(node, Node):
446             for child in node.children:
447                 yield from self.visit(child)
448
449
450 @dataclass
451 class DebugVisitor(Visitor[T]):
452     tree_depth: int = 0
453
454     def visit_default(self, node: LN) -> Iterator[T]:
455         indent = " " * (2 * self.tree_depth)
456         if isinstance(node, Node):
457             _type = type_repr(node.type)
458             out(f"{indent}{_type}", fg="yellow")
459             self.tree_depth += 1
460             for child in node.children:
461                 yield from self.visit(child)
462
463             self.tree_depth -= 1
464             out(f"{indent}/{_type}", fg="yellow", bold=False)
465         else:
466             _type = token.tok_name.get(node.type, str(node.type))
467             out(f"{indent}{_type}", fg="blue", nl=False)
468             if node.prefix:
469                 # We don't have to handle prefixes for `Node` objects since
470                 # that delegates to the first child anyway.
471                 out(f" {node.prefix!r}", fg="green", bold=False, nl=False)
472             out(f" {node.value!r}", fg="blue", bold=False)
473
474     @classmethod
475     def show(cls, code: str) -> None:
476         """Pretty-print the lib2to3 AST of a given string of `code`.
477
478         Convenience method for debugging.
479         """
480         v: DebugVisitor[None] = DebugVisitor()
481         list(v.visit(lib2to3_parse(code)))
482
483
484 KEYWORDS = set(keyword.kwlist)
485 WHITESPACE = {token.DEDENT, token.INDENT, token.NEWLINE}
486 FLOW_CONTROL = {"return", "raise", "break", "continue"}
487 STATEMENT = {
488     syms.if_stmt,
489     syms.while_stmt,
490     syms.for_stmt,
491     syms.try_stmt,
492     syms.except_clause,
493     syms.with_stmt,
494     syms.funcdef,
495     syms.classdef,
496 }
497 STANDALONE_COMMENT = 153
498 LOGIC_OPERATORS = {"and", "or"}
499 COMPARATORS = {
500     token.LESS,
501     token.GREATER,
502     token.EQEQUAL,
503     token.NOTEQUAL,
504     token.LESSEQUAL,
505     token.GREATEREQUAL,
506 }
507 MATH_OPERATORS = {
508     token.PLUS,
509     token.MINUS,
510     token.STAR,
511     token.SLASH,
512     token.VBAR,
513     token.AMPER,
514     token.PERCENT,
515     token.CIRCUMFLEX,
516     token.TILDE,
517     token.LEFTSHIFT,
518     token.RIGHTSHIFT,
519     token.DOUBLESTAR,
520     token.DOUBLESLASH,
521 }
522 VARARGS = {token.STAR, token.DOUBLESTAR}
523 COMPREHENSION_PRIORITY = 20
524 COMMA_PRIORITY = 10
525 LOGIC_PRIORITY = 5
526 STRING_PRIORITY = 4
527 COMPARATOR_PRIORITY = 3
528 MATH_PRIORITY = 1
529
530
531 @dataclass
532 class BracketTracker:
533     """Keeps track of brackets on a line."""
534
535     depth: int = 0
536     bracket_match: Dict[Tuple[Depth, NodeType], Leaf] = Factory(dict)
537     delimiters: Dict[LeafID, Priority] = Factory(dict)
538     previous: Optional[Leaf] = None
539
540     def mark(self, leaf: Leaf) -> None:
541         """Mark `leaf` with bracket-related metadata. Keep track of delimiters.
542
543         All leaves receive an int `bracket_depth` field that stores how deep
544         within brackets a given leaf is. 0 means there are no enclosing brackets
545         that started on this line.
546
547         If a leaf is itself a closing bracket, it receives an `opening_bracket`
548         field that it forms a pair with. This is a one-directional link to
549         avoid reference cycles.
550
551         If a leaf is a delimiter (a token on which Black can split the line if
552         needed) and it's on depth 0, its `id()` is stored in the tracker's
553         `delimiters` field.
554         """
555         if leaf.type == token.COMMENT:
556             return
557
558         if leaf.type in CLOSING_BRACKETS:
559             self.depth -= 1
560             opening_bracket = self.bracket_match.pop((self.depth, leaf.type))
561             leaf.opening_bracket = opening_bracket
562         leaf.bracket_depth = self.depth
563         if self.depth == 0:
564             delim = is_split_before_delimiter(leaf, self.previous)
565             if delim and self.previous is not None:
566                 self.delimiters[id(self.previous)] = delim
567             else:
568                 delim = is_split_after_delimiter(leaf, self.previous)
569                 if delim:
570                     self.delimiters[id(leaf)] = delim
571         if leaf.type in OPENING_BRACKETS:
572             self.bracket_match[self.depth, BRACKET[leaf.type]] = leaf
573             self.depth += 1
574         self.previous = leaf
575
576     def any_open_brackets(self) -> bool:
577         """Return True if there is an yet unmatched open bracket on the line."""
578         return bool(self.bracket_match)
579
580     def max_delimiter_priority(self, exclude: Iterable[LeafID] = ()) -> int:
581         """Return the highest priority of a delimiter found on the line.
582
583         Values are consistent with what `is_delimiter()` returns.
584         """
585         return max(v for k, v in self.delimiters.items() if k not in exclude)
586
587
588 @dataclass
589 class Line:
590     """Holds leaves and comments. Can be printed with `str(line)`."""
591
592     depth: int = 0
593     leaves: List[Leaf] = Factory(list)
594     comments: List[Tuple[Index, Leaf]] = Factory(list)
595     bracket_tracker: BracketTracker = Factory(BracketTracker)
596     inside_brackets: bool = False
597     has_for: bool = False
598     _for_loop_variable: bool = False
599
600     def append(self, leaf: Leaf, preformatted: bool = False) -> None:
601         """Add a new `leaf` to the end of the line.
602
603         Unless `preformatted` is True, the `leaf` will receive a new consistent
604         whitespace prefix and metadata applied by :class:`BracketTracker`.
605         Trailing commas are maybe removed, unpacked for loop variables are
606         demoted from being delimiters.
607
608         Inline comments are put aside.
609         """
610         has_value = leaf.value.strip()
611         if not has_value:
612             return
613
614         if self.leaves and not preformatted:
615             # Note: at this point leaf.prefix should be empty except for
616             # imports, for which we only preserve newlines.
617             leaf.prefix += whitespace(leaf)
618         if self.inside_brackets or not preformatted:
619             self.maybe_decrement_after_for_loop_variable(leaf)
620             self.bracket_tracker.mark(leaf)
621             self.maybe_remove_trailing_comma(leaf)
622             self.maybe_increment_for_loop_variable(leaf)
623
624         if not self.append_comment(leaf):
625             self.leaves.append(leaf)
626
627     def append_safe(self, leaf: Leaf, preformatted: bool = False) -> None:
628         """Like :func:`append()` but disallow invalid standalone comment structure.
629
630         Raises ValueError when any `leaf` is appended after a standalone comment
631         or when a standalone comment is not the first leaf on the line.
632         """
633         if self.bracket_tracker.depth == 0:
634             if self.is_comment:
635                 raise ValueError("cannot append to standalone comments")
636
637             if self.leaves and leaf.type == STANDALONE_COMMENT:
638                 raise ValueError(
639                     "cannot append standalone comments to a populated line"
640                 )
641
642         self.append(leaf, preformatted=preformatted)
643
644     @property
645     def is_comment(self) -> bool:
646         """Is this line a standalone comment?"""
647         return len(self.leaves) == 1 and self.leaves[0].type == STANDALONE_COMMENT
648
649     @property
650     def is_decorator(self) -> bool:
651         """Is this line a decorator?"""
652         return bool(self) and self.leaves[0].type == token.AT
653
654     @property
655     def is_import(self) -> bool:
656         """Is this an import line?"""
657         return bool(self) and is_import(self.leaves[0])
658
659     @property
660     def is_class(self) -> bool:
661         """Is this line a class definition?"""
662         return (
663             bool(self)
664             and self.leaves[0].type == token.NAME
665             and self.leaves[0].value == "class"
666         )
667
668     @property
669     def is_def(self) -> bool:
670         """Is this a function definition? (Also returns True for async defs.)"""
671         try:
672             first_leaf = self.leaves[0]
673         except IndexError:
674             return False
675
676         try:
677             second_leaf: Optional[Leaf] = self.leaves[1]
678         except IndexError:
679             second_leaf = None
680         return (
681             (first_leaf.type == token.NAME and first_leaf.value == "def")
682             or (
683                 first_leaf.type == token.ASYNC
684                 and second_leaf is not None
685                 and second_leaf.type == token.NAME
686                 and second_leaf.value == "def"
687             )
688         )
689
690     @property
691     def is_flow_control(self) -> bool:
692         """Is this line a flow control statement?
693
694         Those are `return`, `raise`, `break`, and `continue`.
695         """
696         return (
697             bool(self)
698             and self.leaves[0].type == token.NAME
699             and self.leaves[0].value in FLOW_CONTROL
700         )
701
702     @property
703     def is_yield(self) -> bool:
704         """Is this line a yield statement?"""
705         return (
706             bool(self)
707             and self.leaves[0].type == token.NAME
708             and self.leaves[0].value == "yield"
709         )
710
711     @property
712     def contains_standalone_comments(self) -> bool:
713         """If so, needs to be split before emitting."""
714         for leaf in self.leaves:
715             if leaf.type == STANDALONE_COMMENT:
716                 return True
717
718         return False
719
720     def maybe_remove_trailing_comma(self, closing: Leaf) -> bool:
721         """Remove trailing comma if there is one and it's safe."""
722         if not (
723             self.leaves
724             and self.leaves[-1].type == token.COMMA
725             and closing.type in CLOSING_BRACKETS
726         ):
727             return False
728
729         if closing.type == token.RBRACE:
730             self.remove_trailing_comma()
731             return True
732
733         if closing.type == token.RSQB:
734             comma = self.leaves[-1]
735             if comma.parent and comma.parent.type == syms.listmaker:
736                 self.remove_trailing_comma()
737                 return True
738
739         # For parens let's check if it's safe to remove the comma.  If the
740         # trailing one is the only one, we might mistakenly change a tuple
741         # into a different type by removing the comma.
742         depth = closing.bracket_depth + 1
743         commas = 0
744         opening = closing.opening_bracket
745         for _opening_index, leaf in enumerate(self.leaves):
746             if leaf is opening:
747                 break
748
749         else:
750             return False
751
752         for leaf in self.leaves[_opening_index + 1:]:
753             if leaf is closing:
754                 break
755
756             bracket_depth = leaf.bracket_depth
757             if bracket_depth == depth and leaf.type == token.COMMA:
758                 commas += 1
759                 if leaf.parent and leaf.parent.type == syms.arglist:
760                     commas += 1
761                     break
762
763         if commas > 1:
764             self.remove_trailing_comma()
765             return True
766
767         return False
768
769     def maybe_increment_for_loop_variable(self, leaf: Leaf) -> bool:
770         """In a for loop, or comprehension, the variables are often unpacks.
771
772         To avoid splitting on the comma in this situation, increase the depth of
773         tokens between `for` and `in`.
774         """
775         if leaf.type == token.NAME and leaf.value == "for":
776             self.has_for = True
777             self.bracket_tracker.depth += 1
778             self._for_loop_variable = True
779             return True
780
781         return False
782
783     def maybe_decrement_after_for_loop_variable(self, leaf: Leaf) -> bool:
784         """See `maybe_increment_for_loop_variable` above for explanation."""
785         if self._for_loop_variable and leaf.type == token.NAME and leaf.value == "in":
786             self.bracket_tracker.depth -= 1
787             self._for_loop_variable = False
788             return True
789
790         return False
791
792     def append_comment(self, comment: Leaf) -> bool:
793         """Add an inline or standalone comment to the line."""
794         if (
795             comment.type == STANDALONE_COMMENT
796             and self.bracket_tracker.any_open_brackets()
797         ):
798             comment.prefix = ""
799             return False
800
801         if comment.type != token.COMMENT:
802             return False
803
804         after = len(self.leaves) - 1
805         if after == -1:
806             comment.type = STANDALONE_COMMENT
807             comment.prefix = ""
808             return False
809
810         else:
811             self.comments.append((after, comment))
812             return True
813
814     def comments_after(self, leaf: Leaf) -> Iterator[Leaf]:
815         """Generate comments that should appear directly after `leaf`."""
816         for _leaf_index, _leaf in enumerate(self.leaves):
817             if leaf is _leaf:
818                 break
819
820         else:
821             return
822
823         for index, comment_after in self.comments:
824             if _leaf_index == index:
825                 yield comment_after
826
827     def remove_trailing_comma(self) -> None:
828         """Remove the trailing comma and moves the comments attached to it."""
829         comma_index = len(self.leaves) - 1
830         for i in range(len(self.comments)):
831             comment_index, comment = self.comments[i]
832             if comment_index == comma_index:
833                 self.comments[i] = (comma_index - 1, comment)
834         self.leaves.pop()
835
836     def __str__(self) -> str:
837         """Render the line."""
838         if not self:
839             return "\n"
840
841         indent = "    " * self.depth
842         leaves = iter(self.leaves)
843         first = next(leaves)
844         res = f"{first.prefix}{indent}{first.value}"
845         for leaf in leaves:
846             res += str(leaf)
847         for _, comment in self.comments:
848             res += str(comment)
849         return res + "\n"
850
851     def __bool__(self) -> bool:
852         """Return True if the line has leaves or comments."""
853         return bool(self.leaves or self.comments)
854
855
856 class UnformattedLines(Line):
857     """Just like :class:`Line` but stores lines which aren't reformatted."""
858
859     def append(self, leaf: Leaf, preformatted: bool = True) -> None:
860         """Just add a new `leaf` to the end of the lines.
861
862         The `preformatted` argument is ignored.
863
864         Keeps track of indentation `depth`, which is useful when the user
865         says `# fmt: on`. Otherwise, doesn't do anything with the `leaf`.
866         """
867         try:
868             list(generate_comments(leaf))
869         except FormatOn as f_on:
870             self.leaves.append(f_on.leaf_from_consumed(leaf))
871             raise
872
873         self.leaves.append(leaf)
874         if leaf.type == token.INDENT:
875             self.depth += 1
876         elif leaf.type == token.DEDENT:
877             self.depth -= 1
878
879     def __str__(self) -> str:
880         """Render unformatted lines from leaves which were added with `append()`.
881
882         `depth` is not used for indentation in this case.
883         """
884         if not self:
885             return "\n"
886
887         res = ""
888         for leaf in self.leaves:
889             res += str(leaf)
890         return res
891
892     def append_comment(self, comment: Leaf) -> bool:
893         """Not implemented in this class. Raises `NotImplementedError`."""
894         raise NotImplementedError("Unformatted lines don't store comments separately.")
895
896     def maybe_remove_trailing_comma(self, closing: Leaf) -> bool:
897         """Does nothing and returns False."""
898         return False
899
900     def maybe_increment_for_loop_variable(self, leaf: Leaf) -> bool:
901         """Does nothing and returns False."""
902         return False
903
904
905 @dataclass
906 class EmptyLineTracker:
907     """Provides a stateful method that returns the number of potential extra
908     empty lines needed before and after the currently processed line.
909
910     Note: this tracker works on lines that haven't been split yet.  It assumes
911     the prefix of the first leaf consists of optional newlines.  Those newlines
912     are consumed by `maybe_empty_lines()` and included in the computation.
913     """
914     previous_line: Optional[Line] = None
915     previous_after: int = 0
916     previous_defs: List[int] = Factory(list)
917
918     def maybe_empty_lines(self, current_line: Line) -> Tuple[int, int]:
919         """Return the number of extra empty lines before and after the `current_line`.
920
921         This is for separating `def`, `async def` and `class` with extra empty
922         lines (two on module-level), as well as providing an extra empty line
923         after flow control keywords to make them more prominent.
924         """
925         if isinstance(current_line, UnformattedLines):
926             return 0, 0
927
928         before, after = self._maybe_empty_lines(current_line)
929         before -= self.previous_after
930         self.previous_after = after
931         self.previous_line = current_line
932         return before, after
933
934     def _maybe_empty_lines(self, current_line: Line) -> Tuple[int, int]:
935         max_allowed = 1
936         if current_line.depth == 0:
937             max_allowed = 2
938         if current_line.leaves:
939             # Consume the first leaf's extra newlines.
940             first_leaf = current_line.leaves[0]
941             before = first_leaf.prefix.count("\n")
942             before = min(before, max_allowed)
943             first_leaf.prefix = ""
944         else:
945             before = 0
946         depth = current_line.depth
947         while self.previous_defs and self.previous_defs[-1] >= depth:
948             self.previous_defs.pop()
949             before = 1 if depth else 2
950         is_decorator = current_line.is_decorator
951         if is_decorator or current_line.is_def or current_line.is_class:
952             if not is_decorator:
953                 self.previous_defs.append(depth)
954             if self.previous_line is None:
955                 # Don't insert empty lines before the first line in the file.
956                 return 0, 0
957
958             if self.previous_line and self.previous_line.is_decorator:
959                 # Don't insert empty lines between decorators.
960                 return 0, 0
961
962             newlines = 2
963             if current_line.depth:
964                 newlines -= 1
965             return newlines, 0
966
967         if current_line.is_flow_control:
968             return before, 1
969
970         if (
971             self.previous_line
972             and self.previous_line.is_import
973             and not current_line.is_import
974             and depth == self.previous_line.depth
975         ):
976             return (before or 1), 0
977
978         if (
979             self.previous_line
980             and self.previous_line.is_yield
981             and (not current_line.is_yield or depth != self.previous_line.depth)
982         ):
983             return (before or 1), 0
984
985         return before, 0
986
987
988 @dataclass
989 class LineGenerator(Visitor[Line]):
990     """Generates reformatted Line objects.  Empty lines are not emitted.
991
992     Note: destroys the tree it's visiting by mutating prefixes of its leaves
993     in ways that will no longer stringify to valid Python code on the tree.
994     """
995     current_line: Line = Factory(Line)
996
997     def line(self, indent: int = 0, type: Type[Line] = Line) -> Iterator[Line]:
998         """Generate a line.
999
1000         If the line is empty, only emit if it makes sense.
1001         If the line is too long, split it first and then generate.
1002
1003         If any lines were generated, set up a new current_line.
1004         """
1005         if not self.current_line:
1006             if self.current_line.__class__ == type:
1007                 self.current_line.depth += indent
1008             else:
1009                 self.current_line = type(depth=self.current_line.depth + indent)
1010             return  # Line is empty, don't emit. Creating a new one unnecessary.
1011
1012         complete_line = self.current_line
1013         self.current_line = type(depth=complete_line.depth + indent)
1014         yield complete_line
1015
1016     def visit(self, node: LN) -> Iterator[Line]:
1017         """Main method to visit `node` and its children.
1018
1019         Yields :class:`Line` objects.
1020         """
1021         if isinstance(self.current_line, UnformattedLines):
1022             # File contained `# fmt: off`
1023             yield from self.visit_unformatted(node)
1024
1025         else:
1026             yield from super().visit(node)
1027
1028     def visit_default(self, node: LN) -> Iterator[Line]:
1029         """Default `visit_*()` implementation. Recurses to children of `node`."""
1030         if isinstance(node, Leaf):
1031             any_open_brackets = self.current_line.bracket_tracker.any_open_brackets()
1032             try:
1033                 for comment in generate_comments(node):
1034                     if any_open_brackets:
1035                         # any comment within brackets is subject to splitting
1036                         self.current_line.append(comment)
1037                     elif comment.type == token.COMMENT:
1038                         # regular trailing comment
1039                         self.current_line.append(comment)
1040                         yield from self.line()
1041
1042                     else:
1043                         # regular standalone comment
1044                         yield from self.line()
1045
1046                         self.current_line.append(comment)
1047                         yield from self.line()
1048
1049             except FormatOff as f_off:
1050                 f_off.trim_prefix(node)
1051                 yield from self.line(type=UnformattedLines)
1052                 yield from self.visit(node)
1053
1054             except FormatOn as f_on:
1055                 # This only happens here if somebody says "fmt: on" multiple
1056                 # times in a row.
1057                 f_on.trim_prefix(node)
1058                 yield from self.visit_default(node)
1059
1060             else:
1061                 normalize_prefix(node, inside_brackets=any_open_brackets)
1062                 if node.type == token.STRING:
1063                     normalize_string_quotes(node)
1064                 if node.type not in WHITESPACE:
1065                     self.current_line.append(node)
1066         yield from super().visit_default(node)
1067
1068     def visit_INDENT(self, node: Node) -> Iterator[Line]:
1069         """Increase indentation level, maybe yield a line."""
1070         # In blib2to3 INDENT never holds comments.
1071         yield from self.line(+1)
1072         yield from self.visit_default(node)
1073
1074     def visit_DEDENT(self, node: Node) -> Iterator[Line]:
1075         """Decrease indentation level, maybe yield a line."""
1076         # DEDENT has no value. Additionally, in blib2to3 it never holds comments.
1077         yield from self.line(-1)
1078
1079     def visit_stmt(self, node: Node, keywords: Set[str]) -> Iterator[Line]:
1080         """Visit a statement.
1081
1082         This implementation is shared for `if`, `while`, `for`, `try`, `except`,
1083         `def`, `with`, and `class`.
1084
1085         The relevant Python language `keywords` for a given statement will be NAME
1086         leaves within it. This methods puts those on a separate line.
1087         """
1088         for child in node.children:
1089             if child.type == token.NAME and child.value in keywords:  # type: ignore
1090                 yield from self.line()
1091
1092             yield from self.visit(child)
1093
1094     def visit_simple_stmt(self, node: Node) -> Iterator[Line]:
1095         """Visit a statement without nested statements."""
1096         is_suite_like = node.parent and node.parent.type in STATEMENT
1097         if is_suite_like:
1098             yield from self.line(+1)
1099             yield from self.visit_default(node)
1100             yield from self.line(-1)
1101
1102         else:
1103             yield from self.line()
1104             yield from self.visit_default(node)
1105
1106     def visit_async_stmt(self, node: Node) -> Iterator[Line]:
1107         """Visit `async def`, `async for`, `async with`."""
1108         yield from self.line()
1109
1110         children = iter(node.children)
1111         for child in children:
1112             yield from self.visit(child)
1113
1114             if child.type == token.ASYNC:
1115                 break
1116
1117         internal_stmt = next(children)
1118         for child in internal_stmt.children:
1119             yield from self.visit(child)
1120
1121     def visit_decorators(self, node: Node) -> Iterator[Line]:
1122         """Visit decorators."""
1123         for child in node.children:
1124             yield from self.line()
1125             yield from self.visit(child)
1126
1127     def visit_SEMI(self, leaf: Leaf) -> Iterator[Line]:
1128         """Remove a semicolon and put the other statement on a separate line."""
1129         yield from self.line()
1130
1131     def visit_ENDMARKER(self, leaf: Leaf) -> Iterator[Line]:
1132         """End of file. Process outstanding comments and end with a newline."""
1133         yield from self.visit_default(leaf)
1134         yield from self.line()
1135
1136     def visit_unformatted(self, node: LN) -> Iterator[Line]:
1137         """Used when file contained a `# fmt: off`."""
1138         if isinstance(node, Node):
1139             for child in node.children:
1140                 yield from self.visit(child)
1141
1142         else:
1143             try:
1144                 self.current_line.append(node)
1145             except FormatOn as f_on:
1146                 f_on.trim_prefix(node)
1147                 yield from self.line()
1148                 yield from self.visit(node)
1149
1150             if node.type == token.ENDMARKER:
1151                 # somebody decided not to put a final `# fmt: on`
1152                 yield from self.line()
1153
1154     def __attrs_post_init__(self) -> None:
1155         """You are in a twisty little maze of passages."""
1156         v = self.visit_stmt
1157         self.visit_if_stmt = partial(v, keywords={"if", "else", "elif"})
1158         self.visit_while_stmt = partial(v, keywords={"while", "else"})
1159         self.visit_for_stmt = partial(v, keywords={"for", "else"})
1160         self.visit_try_stmt = partial(v, keywords={"try", "except", "else", "finally"})
1161         self.visit_except_clause = partial(v, keywords={"except"})
1162         self.visit_funcdef = partial(v, keywords={"def"})
1163         self.visit_with_stmt = partial(v, keywords={"with"})
1164         self.visit_classdef = partial(v, keywords={"class"})
1165         self.visit_async_funcdef = self.visit_async_stmt
1166         self.visit_decorated = self.visit_decorators
1167
1168
1169 BRACKET = {token.LPAR: token.RPAR, token.LSQB: token.RSQB, token.LBRACE: token.RBRACE}
1170 OPENING_BRACKETS = set(BRACKET.keys())
1171 CLOSING_BRACKETS = set(BRACKET.values())
1172 BRACKETS = OPENING_BRACKETS | CLOSING_BRACKETS
1173 ALWAYS_NO_SPACE = CLOSING_BRACKETS | {token.COMMA, STANDALONE_COMMENT}
1174
1175
1176 def whitespace(leaf: Leaf) -> str:  # noqa C901
1177     """Return whitespace prefix if needed for the given `leaf`."""
1178     NO = ""
1179     SPACE = " "
1180     DOUBLESPACE = "  "
1181     t = leaf.type
1182     p = leaf.parent
1183     v = leaf.value
1184     if t in ALWAYS_NO_SPACE:
1185         return NO
1186
1187     if t == token.COMMENT:
1188         return DOUBLESPACE
1189
1190     assert p is not None, f"INTERNAL ERROR: hand-made leaf without parent: {leaf!r}"
1191     if t == token.COLON and p.type not in {syms.subscript, syms.subscriptlist}:
1192         return NO
1193
1194     prev = leaf.prev_sibling
1195     if not prev:
1196         prevp = preceding_leaf(p)
1197         if not prevp or prevp.type in OPENING_BRACKETS:
1198             return NO
1199
1200         if t == token.COLON:
1201             return SPACE if prevp.type == token.COMMA else NO
1202
1203         if prevp.type == token.EQUAL:
1204             if prevp.parent:
1205                 if prevp.parent.type in {
1206                     syms.arglist, syms.argument, syms.parameters, syms.varargslist
1207                 }:
1208                     return NO
1209
1210                 elif prevp.parent.type == syms.typedargslist:
1211                     # A bit hacky: if the equal sign has whitespace, it means we
1212                     # previously found it's a typed argument.  So, we're using
1213                     # that, too.
1214                     return prevp.prefix
1215
1216         elif prevp.type == token.DOUBLESTAR:
1217             if prevp.parent and prevp.parent.type in {
1218                 syms.arglist,
1219                 syms.argument,
1220                 syms.dictsetmaker,
1221                 syms.parameters,
1222                 syms.typedargslist,
1223                 syms.varargslist,
1224             }:
1225                 return NO
1226
1227         elif prevp.type == token.COLON:
1228             if prevp.parent and prevp.parent.type in {syms.subscript, syms.sliceop}:
1229                 return NO
1230
1231         elif (
1232             prevp.parent
1233             and prevp.parent.type in {syms.factor, syms.star_expr}
1234             and prevp.type in MATH_OPERATORS
1235         ):
1236             return NO
1237
1238         elif (
1239             prevp.type == token.RIGHTSHIFT
1240             and prevp.parent
1241             and prevp.parent.type == syms.shift_expr
1242             and prevp.prev_sibling
1243             and prevp.prev_sibling.type == token.NAME
1244             and prevp.prev_sibling.value == "print"  # type: ignore
1245         ):
1246             # Python 2 print chevron
1247             return NO
1248
1249     elif prev.type in OPENING_BRACKETS:
1250         return NO
1251
1252     if p.type in {syms.parameters, syms.arglist}:
1253         # untyped function signatures or calls
1254         if t == token.RPAR:
1255             return NO
1256
1257         if not prev or prev.type != token.COMMA:
1258             return NO
1259
1260     elif p.type == syms.varargslist:
1261         # lambdas
1262         if t == token.RPAR:
1263             return NO
1264
1265         if prev and prev.type != token.COMMA:
1266             return NO
1267
1268     elif p.type == syms.typedargslist:
1269         # typed function signatures
1270         if not prev:
1271             return NO
1272
1273         if t == token.EQUAL:
1274             if prev.type != syms.tname:
1275                 return NO
1276
1277         elif prev.type == token.EQUAL:
1278             # A bit hacky: if the equal sign has whitespace, it means we
1279             # previously found it's a typed argument.  So, we're using that, too.
1280             return prev.prefix
1281
1282         elif prev.type != token.COMMA:
1283             return NO
1284
1285     elif p.type == syms.tname:
1286         # type names
1287         if not prev:
1288             prevp = preceding_leaf(p)
1289             if not prevp or prevp.type != token.COMMA:
1290                 return NO
1291
1292     elif p.type == syms.trailer:
1293         # attributes and calls
1294         if t == token.LPAR or t == token.RPAR:
1295             return NO
1296
1297         if not prev:
1298             if t == token.DOT:
1299                 prevp = preceding_leaf(p)
1300                 if not prevp or prevp.type != token.NUMBER:
1301                     return NO
1302
1303             elif t == token.LSQB:
1304                 return NO
1305
1306         elif prev.type != token.COMMA:
1307             return NO
1308
1309     elif p.type == syms.argument:
1310         # single argument
1311         if t == token.EQUAL:
1312             return NO
1313
1314         if not prev:
1315             prevp = preceding_leaf(p)
1316             if not prevp or prevp.type == token.LPAR:
1317                 return NO
1318
1319         elif prev.type == token.EQUAL or prev.type == token.DOUBLESTAR:
1320             return NO
1321
1322     elif p.type == syms.decorator:
1323         # decorators
1324         return NO
1325
1326     elif p.type == syms.dotted_name:
1327         if prev:
1328             return NO
1329
1330         prevp = preceding_leaf(p)
1331         if not prevp or prevp.type == token.AT or prevp.type == token.DOT:
1332             return NO
1333
1334     elif p.type == syms.classdef:
1335         if t == token.LPAR:
1336             return NO
1337
1338         if prev and prev.type == token.LPAR:
1339             return NO
1340
1341     elif p.type == syms.subscript:
1342         # indexing
1343         if not prev:
1344             assert p.parent is not None, "subscripts are always parented"
1345             if p.parent.type == syms.subscriptlist:
1346                 return SPACE
1347
1348             return NO
1349
1350         else:
1351             return NO
1352
1353     elif p.type == syms.atom:
1354         if prev and t == token.DOT:
1355             # dots, but not the first one.
1356             return NO
1357
1358     elif (
1359         p.type == syms.listmaker
1360         or p.type == syms.testlist_gexp
1361         or p.type == syms.subscriptlist
1362     ):
1363         # list interior, including unpacking
1364         if not prev:
1365             return NO
1366
1367     elif p.type == syms.dictsetmaker:
1368         # dict and set interior, including unpacking
1369         if not prev:
1370             return NO
1371
1372         if prev.type == token.DOUBLESTAR:
1373             return NO
1374
1375     elif p.type in {syms.factor, syms.star_expr}:
1376         # unary ops
1377         if not prev:
1378             prevp = preceding_leaf(p)
1379             if not prevp or prevp.type in OPENING_BRACKETS:
1380                 return NO
1381
1382             prevp_parent = prevp.parent
1383             assert prevp_parent is not None
1384             if prevp.type == token.COLON and prevp_parent.type in {
1385                 syms.subscript, syms.sliceop
1386             }:
1387                 return NO
1388
1389             elif prevp.type == token.EQUAL and prevp_parent.type == syms.argument:
1390                 return NO
1391
1392         elif t == token.NAME or t == token.NUMBER:
1393             return NO
1394
1395     elif p.type == syms.import_from:
1396         if t == token.DOT:
1397             if prev and prev.type == token.DOT:
1398                 return NO
1399
1400         elif t == token.NAME:
1401             if v == "import":
1402                 return SPACE
1403
1404             if prev and prev.type == token.DOT:
1405                 return NO
1406
1407     elif p.type == syms.sliceop:
1408         return NO
1409
1410     return SPACE
1411
1412
1413 def preceding_leaf(node: Optional[LN]) -> Optional[Leaf]:
1414     """Return the first leaf that precedes `node`, if any."""
1415     while node:
1416         res = node.prev_sibling
1417         if res:
1418             if isinstance(res, Leaf):
1419                 return res
1420
1421             try:
1422                 return list(res.leaves())[-1]
1423
1424             except IndexError:
1425                 return None
1426
1427         node = node.parent
1428     return None
1429
1430
1431 def is_split_after_delimiter(leaf: Leaf, previous: Leaf = None) -> int:
1432     """Return the priority of the `leaf` delimiter, given a line break after it.
1433
1434     The delimiter priorities returned here are from those delimiters that would
1435     cause a line break after themselves.
1436
1437     Higher numbers are higher priority.
1438     """
1439     if leaf.type == token.COMMA:
1440         return COMMA_PRIORITY
1441
1442     return 0
1443
1444
1445 def is_split_before_delimiter(leaf: Leaf, previous: Leaf = None) -> int:
1446     """Return the priority of the `leaf` delimiter, given a line before after it.
1447
1448     The delimiter priorities returned here are from those delimiters that would
1449     cause a line break before themselves.
1450
1451     Higher numbers are higher priority.
1452     """
1453     if (
1454         leaf.type in VARARGS
1455         and leaf.parent
1456         and leaf.parent.type in {syms.argument, syms.typedargslist}
1457     ):
1458         # * and ** might also be MATH_OPERATORS but in this case they are not.
1459         # Don't treat them as a delimiter.
1460         return 0
1461
1462     if (
1463         leaf.type in MATH_OPERATORS
1464         and leaf.parent
1465         and leaf.parent.type not in {syms.factor, syms.star_expr}
1466     ):
1467         return MATH_PRIORITY
1468
1469     if leaf.type in COMPARATORS:
1470         return COMPARATOR_PRIORITY
1471
1472     if (
1473         leaf.type == token.STRING
1474         and previous is not None
1475         and previous.type == token.STRING
1476     ):
1477         return STRING_PRIORITY
1478
1479     if (
1480         leaf.type == token.NAME
1481         and leaf.value == "for"
1482         and leaf.parent
1483         and leaf.parent.type in {syms.comp_for, syms.old_comp_for}
1484     ):
1485         return COMPREHENSION_PRIORITY
1486
1487     if (
1488         leaf.type == token.NAME
1489         and leaf.value == "if"
1490         and leaf.parent
1491         and leaf.parent.type in {syms.comp_if, syms.old_comp_if}
1492     ):
1493         return COMPREHENSION_PRIORITY
1494
1495     if leaf.type == token.NAME and leaf.value in LOGIC_OPERATORS and leaf.parent:
1496         return LOGIC_PRIORITY
1497
1498     return 0
1499
1500
1501 def is_delimiter(leaf: Leaf, previous: Leaf = None) -> int:
1502     """Return the priority of the `leaf` delimiter. Return 0 if not delimiter.
1503
1504     Higher numbers are higher priority.
1505     """
1506     return max(
1507         is_split_before_delimiter(leaf, previous),
1508         is_split_after_delimiter(leaf, previous),
1509     )
1510
1511
1512 def generate_comments(leaf: Leaf) -> Iterator[Leaf]:
1513     """Clean the prefix of the `leaf` and generate comments from it, if any.
1514
1515     Comments in lib2to3 are shoved into the whitespace prefix.  This happens
1516     in `pgen2/driver.py:Driver.parse_tokens()`.  This was a brilliant implementation
1517     move because it does away with modifying the grammar to include all the
1518     possible places in which comments can be placed.
1519
1520     The sad consequence for us though is that comments don't "belong" anywhere.
1521     This is why this function generates simple parentless Leaf objects for
1522     comments.  We simply don't know what the correct parent should be.
1523
1524     No matter though, we can live without this.  We really only need to
1525     differentiate between inline and standalone comments.  The latter don't
1526     share the line with any code.
1527
1528     Inline comments are emitted as regular token.COMMENT leaves.  Standalone
1529     are emitted with a fake STANDALONE_COMMENT token identifier.
1530     """
1531     p = leaf.prefix
1532     if not p:
1533         return
1534
1535     if "#" not in p:
1536         return
1537
1538     consumed = 0
1539     nlines = 0
1540     for index, line in enumerate(p.split("\n")):
1541         consumed += len(line) + 1  # adding the length of the split '\n'
1542         line = line.lstrip()
1543         if not line:
1544             nlines += 1
1545         if not line.startswith("#"):
1546             continue
1547
1548         if index == 0 and leaf.type != token.ENDMARKER:
1549             comment_type = token.COMMENT  # simple trailing comment
1550         else:
1551             comment_type = STANDALONE_COMMENT
1552         comment = make_comment(line)
1553         yield Leaf(comment_type, comment, prefix="\n" * nlines)
1554
1555         if comment in {"# fmt: on", "# yapf: enable"}:
1556             raise FormatOn(consumed)
1557
1558         if comment in {"# fmt: off", "# yapf: disable"}:
1559             if comment_type == STANDALONE_COMMENT:
1560                 raise FormatOff(consumed)
1561
1562             prev = preceding_leaf(leaf)
1563             if not prev or prev.type in WHITESPACE:  # standalone comment in disguise
1564                 raise FormatOff(consumed)
1565
1566         nlines = 0
1567
1568
1569 def make_comment(content: str) -> str:
1570     """Return a consistently formatted comment from the given `content` string.
1571
1572     All comments (except for "##", "#!", "#:") should have a single space between
1573     the hash sign and the content.
1574
1575     If `content` didn't start with a hash sign, one is provided.
1576     """
1577     content = content.rstrip()
1578     if not content:
1579         return "#"
1580
1581     if content[0] == "#":
1582         content = content[1:]
1583     if content and content[0] not in " !:#":
1584         content = " " + content
1585     return "#" + content
1586
1587
1588 def split_line(
1589     line: Line, line_length: int, inner: bool = False, py36: bool = False
1590 ) -> Iterator[Line]:
1591     """Split a `line` into potentially many lines.
1592
1593     They should fit in the allotted `line_length` but might not be able to.
1594     `inner` signifies that there were a pair of brackets somewhere around the
1595     current `line`, possibly transitively. This means we can fallback to splitting
1596     by delimiters if the LHS/RHS don't yield any results.
1597
1598     If `py36` is True, splitting may generate syntax that is only compatible
1599     with Python 3.6 and later.
1600     """
1601     if isinstance(line, UnformattedLines) or line.is_comment:
1602         yield line
1603         return
1604
1605     line_str = str(line).strip("\n")
1606     if (
1607         len(line_str) <= line_length
1608         and "\n" not in line_str  # multiline strings
1609         and not line.contains_standalone_comments
1610     ):
1611         yield line
1612         return
1613
1614     split_funcs: List[SplitFunc]
1615     if line.is_def:
1616         split_funcs = [left_hand_split]
1617     elif line.inside_brackets:
1618         split_funcs = [delimiter_split, standalone_comment_split, right_hand_split]
1619     else:
1620         split_funcs = [right_hand_split]
1621     for split_func in split_funcs:
1622         # We are accumulating lines in `result` because we might want to abort
1623         # mission and return the original line in the end, or attempt a different
1624         # split altogether.
1625         result: List[Line] = []
1626         try:
1627             for l in split_func(line, py36):
1628                 if str(l).strip("\n") == line_str:
1629                     raise CannotSplit("Split function returned an unchanged result")
1630
1631                 result.extend(
1632                     split_line(l, line_length=line_length, inner=True, py36=py36)
1633                 )
1634         except CannotSplit as cs:
1635             continue
1636
1637         else:
1638             yield from result
1639             break
1640
1641     else:
1642         yield line
1643
1644
1645 def left_hand_split(line: Line, py36: bool = False) -> Iterator[Line]:
1646     """Split line into many lines, starting with the first matching bracket pair.
1647
1648     Note: this usually looks weird, only use this for function definitions.
1649     Prefer RHS otherwise.
1650     """
1651     head = Line(depth=line.depth)
1652     body = Line(depth=line.depth + 1, inside_brackets=True)
1653     tail = Line(depth=line.depth)
1654     tail_leaves: List[Leaf] = []
1655     body_leaves: List[Leaf] = []
1656     head_leaves: List[Leaf] = []
1657     current_leaves = head_leaves
1658     matching_bracket = None
1659     for leaf in line.leaves:
1660         if (
1661             current_leaves is body_leaves
1662             and leaf.type in CLOSING_BRACKETS
1663             and leaf.opening_bracket is matching_bracket
1664         ):
1665             current_leaves = tail_leaves if body_leaves else head_leaves
1666         current_leaves.append(leaf)
1667         if current_leaves is head_leaves:
1668             if leaf.type in OPENING_BRACKETS:
1669                 matching_bracket = leaf
1670                 current_leaves = body_leaves
1671     # Since body is a new indent level, remove spurious leading whitespace.
1672     if body_leaves:
1673         normalize_prefix(body_leaves[0], inside_brackets=True)
1674     # Build the new lines.
1675     for result, leaves in (
1676         (head, head_leaves), (body, body_leaves), (tail, tail_leaves)
1677     ):
1678         for leaf in leaves:
1679             result.append(leaf, preformatted=True)
1680             for comment_after in line.comments_after(leaf):
1681                 result.append(comment_after, preformatted=True)
1682     bracket_split_succeeded_or_raise(head, body, tail)
1683     for result in (head, body, tail):
1684         if result:
1685             yield result
1686
1687
1688 def right_hand_split(line: Line, py36: bool = False) -> Iterator[Line]:
1689     """Split line into many lines, starting with the last matching bracket pair."""
1690     head = Line(depth=line.depth)
1691     body = Line(depth=line.depth + 1, inside_brackets=True)
1692     tail = Line(depth=line.depth)
1693     tail_leaves: List[Leaf] = []
1694     body_leaves: List[Leaf] = []
1695     head_leaves: List[Leaf] = []
1696     current_leaves = tail_leaves
1697     opening_bracket = None
1698     for leaf in reversed(line.leaves):
1699         if current_leaves is body_leaves:
1700             if leaf is opening_bracket:
1701                 current_leaves = head_leaves if body_leaves else tail_leaves
1702         current_leaves.append(leaf)
1703         if current_leaves is tail_leaves:
1704             if leaf.type in CLOSING_BRACKETS:
1705                 opening_bracket = leaf.opening_bracket
1706                 current_leaves = body_leaves
1707     tail_leaves.reverse()
1708     body_leaves.reverse()
1709     head_leaves.reverse()
1710     # Since body is a new indent level, remove spurious leading whitespace.
1711     if body_leaves:
1712         normalize_prefix(body_leaves[0], inside_brackets=True)
1713     # Build the new lines.
1714     for result, leaves in (
1715         (head, head_leaves), (body, body_leaves), (tail, tail_leaves)
1716     ):
1717         for leaf in leaves:
1718             result.append(leaf, preformatted=True)
1719             for comment_after in line.comments_after(leaf):
1720                 result.append(comment_after, preformatted=True)
1721     bracket_split_succeeded_or_raise(head, body, tail)
1722     for result in (head, body, tail):
1723         if result:
1724             yield result
1725
1726
1727 def bracket_split_succeeded_or_raise(head: Line, body: Line, tail: Line) -> None:
1728     """Raise :exc:`CannotSplit` if the last left- or right-hand split failed.
1729
1730     Do nothing otherwise.
1731
1732     A left- or right-hand split is based on a pair of brackets. Content before
1733     (and including) the opening bracket is left on one line, content inside the
1734     brackets is put on a separate line, and finally content starting with and
1735     following the closing bracket is put on a separate line.
1736
1737     Those are called `head`, `body`, and `tail`, respectively. If the split
1738     produced the same line (all content in `head`) or ended up with an empty `body`
1739     and the `tail` is just the closing bracket, then it's considered failed.
1740     """
1741     tail_len = len(str(tail).strip())
1742     if not body:
1743         if tail_len == 0:
1744             raise CannotSplit("Splitting brackets produced the same line")
1745
1746         elif tail_len < 3:
1747             raise CannotSplit(
1748                 f"Splitting brackets on an empty body to save "
1749                 f"{tail_len} characters is not worth it"
1750             )
1751
1752
1753 def dont_increase_indentation(split_func: SplitFunc) -> SplitFunc:
1754     """Normalize prefix of the first leaf in every line returned by `split_func`.
1755
1756     This is a decorator over relevant split functions.
1757     """
1758
1759     @wraps(split_func)
1760     def split_wrapper(line: Line, py36: bool = False) -> Iterator[Line]:
1761         for l in split_func(line, py36):
1762             normalize_prefix(l.leaves[0], inside_brackets=True)
1763             yield l
1764
1765     return split_wrapper
1766
1767
1768 @dont_increase_indentation
1769 def delimiter_split(line: Line, py36: bool = False) -> Iterator[Line]:
1770     """Split according to delimiters of the highest priority.
1771
1772     If `py36` is True, the split will add trailing commas also in function
1773     signatures that contain `*` and `**`.
1774     """
1775     try:
1776         last_leaf = line.leaves[-1]
1777     except IndexError:
1778         raise CannotSplit("Line empty")
1779
1780     delimiters = line.bracket_tracker.delimiters
1781     try:
1782         delimiter_priority = line.bracket_tracker.max_delimiter_priority(
1783             exclude={id(last_leaf)}
1784         )
1785     except ValueError:
1786         raise CannotSplit("No delimiters found")
1787
1788     current_line = Line(depth=line.depth, inside_brackets=line.inside_brackets)
1789     lowest_depth = sys.maxsize
1790     trailing_comma_safe = True
1791
1792     def append_to_line(leaf: Leaf) -> Iterator[Line]:
1793         """Append `leaf` to current line or to new line if appending impossible."""
1794         nonlocal current_line
1795         try:
1796             current_line.append_safe(leaf, preformatted=True)
1797         except ValueError as ve:
1798             yield current_line
1799
1800             current_line = Line(depth=line.depth, inside_brackets=line.inside_brackets)
1801             current_line.append(leaf)
1802
1803     for leaf in line.leaves:
1804         yield from append_to_line(leaf)
1805
1806         for comment_after in line.comments_after(leaf):
1807             yield from append_to_line(comment_after)
1808
1809         lowest_depth = min(lowest_depth, leaf.bracket_depth)
1810         if (
1811             leaf.bracket_depth == lowest_depth
1812             and leaf.type == token.STAR
1813             or leaf.type == token.DOUBLESTAR
1814         ):
1815             trailing_comma_safe = trailing_comma_safe and py36
1816         leaf_priority = delimiters.get(id(leaf))
1817         if leaf_priority == delimiter_priority:
1818             yield current_line
1819
1820             current_line = Line(depth=line.depth, inside_brackets=line.inside_brackets)
1821     if current_line:
1822         if (
1823             trailing_comma_safe
1824             and delimiter_priority == COMMA_PRIORITY
1825             and current_line.leaves[-1].type != token.COMMA
1826             and current_line.leaves[-1].type != STANDALONE_COMMENT
1827         ):
1828             current_line.append(Leaf(token.COMMA, ","))
1829         yield current_line
1830
1831
1832 @dont_increase_indentation
1833 def standalone_comment_split(line: Line, py36: bool = False) -> Iterator[Line]:
1834     """Split standalone comments from the rest of the line."""
1835     for leaf in line.leaves:
1836         if leaf.type == STANDALONE_COMMENT:
1837             if leaf.bracket_depth == 0:
1838                 break
1839
1840     else:
1841         raise CannotSplit("Line does not have any standalone comments")
1842
1843     current_line = Line(depth=line.depth, inside_brackets=line.inside_brackets)
1844
1845     def append_to_line(leaf: Leaf) -> Iterator[Line]:
1846         """Append `leaf` to current line or to new line if appending impossible."""
1847         nonlocal current_line
1848         try:
1849             current_line.append_safe(leaf, preformatted=True)
1850         except ValueError as ve:
1851             yield current_line
1852
1853             current_line = Line(depth=line.depth, inside_brackets=line.inside_brackets)
1854             current_line.append(leaf)
1855
1856     for leaf in line.leaves:
1857         yield from append_to_line(leaf)
1858
1859         for comment_after in line.comments_after(leaf):
1860             yield from append_to_line(comment_after)
1861
1862     if current_line:
1863         yield current_line
1864
1865
1866 def is_import(leaf: Leaf) -> bool:
1867     """Return True if the given leaf starts an import statement."""
1868     p = leaf.parent
1869     t = leaf.type
1870     v = leaf.value
1871     return bool(
1872         t == token.NAME
1873         and (
1874             (v == "import" and p and p.type == syms.import_name)
1875             or (v == "from" and p and p.type == syms.import_from)
1876         )
1877     )
1878
1879
1880 def normalize_prefix(leaf: Leaf, *, inside_brackets: bool) -> None:
1881     """Leave existing extra newlines if not `inside_brackets`. Remove everything
1882     else.
1883
1884     Note: don't use backslashes for formatting or you'll lose your voting rights.
1885     """
1886     if not inside_brackets:
1887         spl = leaf.prefix.split("#")
1888         if "\\" not in spl[0]:
1889             nl_count = spl[-1].count("\n")
1890             if len(spl) > 1:
1891                 nl_count -= 1
1892             leaf.prefix = "\n" * nl_count
1893             return
1894
1895     leaf.prefix = ""
1896
1897
1898 def normalize_string_quotes(leaf: Leaf) -> None:
1899     """Prefer double quotes but only if it doesn't cause more escaping.
1900
1901     Adds or removes backslashes as appropriate. Doesn't parse and fix
1902     strings nested in f-strings (yet).
1903
1904     Note: Mutates its argument.
1905     """
1906     value = leaf.value.lstrip("furbFURB")
1907     if value[:3] == '"""':
1908         return
1909
1910     elif value[:3] == "'''":
1911         orig_quote = "'''"
1912         new_quote = '"""'
1913     elif value[0] == '"':
1914         orig_quote = '"'
1915         new_quote = "'"
1916     else:
1917         orig_quote = "'"
1918         new_quote = '"'
1919     first_quote_pos = leaf.value.find(orig_quote)
1920     if first_quote_pos == -1:
1921         return  # There's an internal error
1922
1923     prefix = leaf.value[:first_quote_pos]
1924     body = leaf.value[first_quote_pos + len(orig_quote):-len(orig_quote)]
1925     if "r" in prefix.casefold():
1926         if body.count(new_quote) != body.count(f"\\{new_quote}"):
1927             # There's at least one unescaped new_quote in this raw string
1928             # so converting is impossible
1929             return
1930
1931         # Do not introduce or remove backslashes in raw strings
1932         new_body = body
1933     else:
1934         new_body = body.replace(f"\\{orig_quote}", orig_quote).replace(
1935             new_quote, f"\\{new_quote}"
1936         )
1937     if new_quote == '"""' and new_body[-1] == '"':
1938         # edge case:
1939         new_body = new_body[:-1] + '\\"'
1940     orig_escape_count = body.count("\\")
1941     new_escape_count = new_body.count("\\")
1942     if new_escape_count > orig_escape_count:
1943         return  # Do not introduce more escaping
1944
1945     if new_escape_count == orig_escape_count and orig_quote == '"':
1946         return  # Prefer double quotes
1947
1948     leaf.value = f"{prefix}{new_quote}{new_body}{new_quote}"
1949
1950
1951 def is_python36(node: Node) -> bool:
1952     """Return True if the current file is using Python 3.6+ features.
1953
1954     Currently looking for:
1955     - f-strings; and
1956     - trailing commas after * or ** in function signatures.
1957     """
1958     for n in node.pre_order():
1959         if n.type == token.STRING:
1960             value_head = n.value[:2]  # type: ignore
1961             if value_head in {'f"', 'F"', "f'", "F'", "rf", "fr", "RF", "FR"}:
1962                 return True
1963
1964         elif (
1965             n.type == syms.typedargslist
1966             and n.children
1967             and n.children[-1].type == token.COMMA
1968         ):
1969             for ch in n.children:
1970                 if ch.type == token.STAR or ch.type == token.DOUBLESTAR:
1971                     return True
1972
1973     return False
1974
1975
1976 PYTHON_EXTENSIONS = {".py"}
1977 BLACKLISTED_DIRECTORIES = {
1978     "build", "buck-out", "dist", "_build", ".git", ".hg", ".mypy_cache", ".tox", ".venv"
1979 }
1980
1981
1982 def gen_python_files_in_dir(path: Path) -> Iterator[Path]:
1983     """Generate all files under `path` which aren't under BLACKLISTED_DIRECTORIES
1984     and have one of the PYTHON_EXTENSIONS.
1985     """
1986     for child in path.iterdir():
1987         if child.is_dir():
1988             if child.name in BLACKLISTED_DIRECTORIES:
1989                 continue
1990
1991             yield from gen_python_files_in_dir(child)
1992
1993         elif child.suffix in PYTHON_EXTENSIONS:
1994             yield child
1995
1996
1997 @dataclass
1998 class Report:
1999     """Provides a reformatting counter. Can be rendered with `str(report)`."""
2000     check: bool = False
2001     quiet: bool = False
2002     change_count: int = 0
2003     same_count: int = 0
2004     failure_count: int = 0
2005
2006     def done(self, src: Path, changed: bool) -> None:
2007         """Increment the counter for successful reformatting. Write out a message."""
2008         if changed:
2009             reformatted = "would reformat" if self.check else "reformatted"
2010             if not self.quiet:
2011                 out(f"{reformatted} {src}")
2012             self.change_count += 1
2013         else:
2014             if not self.quiet:
2015                 out(f"{src} already well formatted, good job.", bold=False)
2016             self.same_count += 1
2017
2018     def failed(self, src: Path, message: str) -> None:
2019         """Increment the counter for failed reformatting. Write out a message."""
2020         err(f"error: cannot format {src}: {message}")
2021         self.failure_count += 1
2022
2023     @property
2024     def return_code(self) -> int:
2025         """Return the exit code that the app should use.
2026
2027         This considers the current state of changed files and failures:
2028         - if there were any failures, return 123;
2029         - if any files were changed and --check is being used, return 1;
2030         - otherwise return 0.
2031         """
2032         # According to http://tldp.org/LDP/abs/html/exitcodes.html starting with
2033         # 126 we have special returncodes reserved by the shell.
2034         if self.failure_count:
2035             return 123
2036
2037         elif self.change_count and self.check:
2038             return 1
2039
2040         return 0
2041
2042     def __str__(self) -> str:
2043         """Render a color report of the current state.
2044
2045         Use `click.unstyle` to remove colors.
2046         """
2047         if self.check:
2048             reformatted = "would be reformatted"
2049             unchanged = "would be left unchanged"
2050             failed = "would fail to reformat"
2051         else:
2052             reformatted = "reformatted"
2053             unchanged = "left unchanged"
2054             failed = "failed to reformat"
2055         report = []
2056         if self.change_count:
2057             s = "s" if self.change_count > 1 else ""
2058             report.append(
2059                 click.style(f"{self.change_count} file{s} {reformatted}", bold=True)
2060             )
2061         if self.same_count:
2062             s = "s" if self.same_count > 1 else ""
2063             report.append(f"{self.same_count} file{s} {unchanged}")
2064         if self.failure_count:
2065             s = "s" if self.failure_count > 1 else ""
2066             report.append(
2067                 click.style(f"{self.failure_count} file{s} {failed}", fg="red")
2068             )
2069         return ", ".join(report) + "."
2070
2071
2072 def assert_equivalent(src: str, dst: str) -> None:
2073     """Raise AssertionError if `src` and `dst` aren't equivalent."""
2074
2075     import ast
2076     import traceback
2077
2078     def _v(node: ast.AST, depth: int = 0) -> Iterator[str]:
2079         """Simple visitor generating strings to compare ASTs by content."""
2080         yield f"{'  ' * depth}{node.__class__.__name__}("
2081
2082         for field in sorted(node._fields):
2083             try:
2084                 value = getattr(node, field)
2085             except AttributeError:
2086                 continue
2087
2088             yield f"{'  ' * (depth+1)}{field}="
2089
2090             if isinstance(value, list):
2091                 for item in value:
2092                     if isinstance(item, ast.AST):
2093                         yield from _v(item, depth + 2)
2094
2095             elif isinstance(value, ast.AST):
2096                 yield from _v(value, depth + 2)
2097
2098             else:
2099                 yield f"{'  ' * (depth+2)}{value!r},  # {value.__class__.__name__}"
2100
2101         yield f"{'  ' * depth})  # /{node.__class__.__name__}"
2102
2103     try:
2104         src_ast = ast.parse(src)
2105     except Exception as exc:
2106         major, minor = sys.version_info[:2]
2107         raise AssertionError(
2108             f"cannot use --safe with this file; failed to parse source file "
2109             f"with Python {major}.{minor}'s builtin AST. Re-run with --fast "
2110             f"or stop using deprecated Python 2 syntax. AST error message: {exc}"
2111         )
2112
2113     try:
2114         dst_ast = ast.parse(dst)
2115     except Exception as exc:
2116         log = dump_to_file("".join(traceback.format_tb(exc.__traceback__)), dst)
2117         raise AssertionError(
2118             f"INTERNAL ERROR: Black produced invalid code: {exc}. "
2119             f"Please report a bug on https://github.com/ambv/black/issues.  "
2120             f"This invalid output might be helpful: {log}"
2121         ) from None
2122
2123     src_ast_str = "\n".join(_v(src_ast))
2124     dst_ast_str = "\n".join(_v(dst_ast))
2125     if src_ast_str != dst_ast_str:
2126         log = dump_to_file(diff(src_ast_str, dst_ast_str, "src", "dst"))
2127         raise AssertionError(
2128             f"INTERNAL ERROR: Black produced code that is not equivalent to "
2129             f"the source.  "
2130             f"Please report a bug on https://github.com/ambv/black/issues.  "
2131             f"This diff might be helpful: {log}"
2132         ) from None
2133
2134
2135 def assert_stable(src: str, dst: str, line_length: int) -> None:
2136     """Raise AssertionError if `dst` reformats differently the second time."""
2137     newdst = format_str(dst, line_length=line_length)
2138     if dst != newdst:
2139         log = dump_to_file(
2140             diff(src, dst, "source", "first pass"),
2141             diff(dst, newdst, "first pass", "second pass"),
2142         )
2143         raise AssertionError(
2144             f"INTERNAL ERROR: Black produced different code on the second pass "
2145             f"of the formatter.  "
2146             f"Please report a bug on https://github.com/ambv/black/issues.  "
2147             f"This diff might be helpful: {log}"
2148         ) from None
2149
2150
2151 def dump_to_file(*output: str) -> str:
2152     """Dump `output` to a temporary file. Return path to the file."""
2153     import tempfile
2154
2155     with tempfile.NamedTemporaryFile(
2156         mode="w", prefix="blk_", suffix=".log", delete=False
2157     ) as f:
2158         for lines in output:
2159             f.write(lines)
2160             if lines and lines[-1] != "\n":
2161                 f.write("\n")
2162     return f.name
2163
2164
2165 def diff(a: str, b: str, a_name: str, b_name: str) -> str:
2166     """Return a unified diff string between strings `a` and `b`."""
2167     import difflib
2168
2169     a_lines = [line + "\n" for line in a.split("\n")]
2170     b_lines = [line + "\n" for line in b.split("\n")]
2171     return "".join(
2172         difflib.unified_diff(a_lines, b_lines, fromfile=a_name, tofile=b_name, n=5)
2173     )
2174
2175
2176 def cancel(tasks: List[asyncio.Task]) -> None:
2177     """asyncio signal handler that cancels all `tasks` and reports to stderr."""
2178     err("Aborted!")
2179     for task in tasks:
2180         task.cancel()
2181
2182
2183 def shutdown(loop: BaseEventLoop) -> None:
2184     """Cancel all pending tasks on `loop`, wait for them, and close the loop."""
2185     try:
2186         # This part is borrowed from asyncio/runners.py in Python 3.7b2.
2187         to_cancel = [task for task in asyncio.Task.all_tasks(loop) if not task.done()]
2188         if not to_cancel:
2189             return
2190
2191         for task in to_cancel:
2192             task.cancel()
2193         loop.run_until_complete(
2194             asyncio.gather(*to_cancel, loop=loop, return_exceptions=True)
2195         )
2196     finally:
2197         # `concurrent.futures.Future` objects cannot be cancelled once they
2198         # are already running. There might be some when the `shutdown()` happened.
2199         # Silence their logger's spew about the event loop being closed.
2200         cf_logger = logging.getLogger("concurrent.futures")
2201         cf_logger.setLevel(logging.CRITICAL)
2202         loop.close()
2203
2204
2205 if __name__ == "__main__":
2206     main()