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.
4 from asyncio.base_events import BaseEventLoop
5 from concurrent.futures import Executor, ProcessPoolExecutor
6 from functools import partial
9 from pathlib import Path
13 Dict, Generic, Iterable, Iterator, List, Optional, Set, Tuple, TypeVar, Union
16 from attr import dataclass, Factory
20 from blib2to3.pytree import Node, Leaf, type_repr
21 from blib2to3 import pygram, pytree
22 from blib2to3.pgen2 import driver, token
23 from blib2to3.pgen2.parse import ParseError
25 __version__ = "18.3a3"
26 DEFAULT_LINE_LENGTH = 88
28 syms = pygram.python_symbols
35 LN = Union[Leaf, Node]
36 out = partial(click.secho, bold=True, err=True)
37 err = partial(click.secho, fg='red', err=True)
40 class NothingChanged(UserWarning):
41 """Raised by `format_file` when the reformatted code is the same as source."""
44 class CannotSplit(Exception):
45 """A readable split that fits the allotted line length is impossible.
47 Raised by `left_hand_split()` and `right_hand_split()`.
56 default=DEFAULT_LINE_LENGTH,
57 help='How many character per line to allow.',
64 "Don't write back the files, just return the status. Return code 0 "
65 "means nothing changed. Return code 1 means some files were "
66 "reformatted. Return code 123 means there was an internal error."
72 help='If --fast given, skip temporary sanity checks. [default: --safe]',
74 @click.version_option(version=__version__)
79 exists=True, file_okay=True, dir_okay=True, readable=True, allow_dash=True
84 ctx: click.Context, line_length: int, check: bool, fast: bool, src: List[str]
86 """The uncompromising code formatter."""
87 sources: List[Path] = []
91 sources.extend(gen_python_files_in_dir(p))
93 # if a file was explicitly given, we don't care about its extension
96 sources.append(Path('-'))
98 err(f'invalid path: {s}')
101 elif len(sources) == 1:
105 if not p.is_file() and str(p) == '-':
106 changed = format_stdin_to_stdout(
107 line_length=line_length, fast=fast, write_back=not check
110 changed = format_file_in_place(
111 p, line_length=line_length, fast=fast, write_back=not check
113 report.done(p, changed)
114 except Exception as exc:
115 report.failed(p, str(exc))
116 ctx.exit(report.return_code)
118 loop = asyncio.get_event_loop()
119 executor = ProcessPoolExecutor(max_workers=os.cpu_count())
122 return_code = loop.run_until_complete(
124 sources, line_length, not check, fast, loop, executor
129 ctx.exit(return_code)
132 async def schedule_formatting(
141 src: loop.run_in_executor(
142 executor, format_file_in_place, src, line_length, fast, write_back
146 await asyncio.wait(tasks.values())
149 for src, task in tasks.items():
151 report.failed(src, 'timed out, cancelling')
153 cancelled.append(task)
154 elif task.exception():
155 report.failed(src, str(task.exception()))
157 report.done(src, task.result())
159 await asyncio.wait(cancelled, timeout=2)
160 out('All done! ✨ 🍰 ✨')
161 click.echo(str(report))
162 return report.return_code
165 def format_file_in_place(
166 src: Path, line_length: int, fast: bool, write_back: bool = False
168 """Format the file and rewrite if changed. Return True if changed."""
169 with tokenize.open(src) as src_buffer:
170 src_contents = src_buffer.read()
172 contents = format_file_contents(
173 src_contents, line_length=line_length, fast=fast
175 except NothingChanged:
179 with open(src, "w", encoding=src_buffer.encoding) as f:
184 def format_stdin_to_stdout(
185 line_length: int, fast: bool, write_back: bool = False
187 """Format file on stdin and pipe output to stdout. Return True if changed."""
188 contents = sys.stdin.read()
190 contents = format_file_contents(contents, line_length=line_length, fast=fast)
193 except NothingChanged:
198 sys.stdout.write(contents)
201 def format_file_contents(
202 src_contents: str, line_length: int, fast: bool
204 """Reformats a file and returns its contents and encoding."""
205 if src_contents.strip() == '':
208 dst_contents = format_str(src_contents, line_length=line_length)
209 if src_contents == dst_contents:
213 assert_equivalent(src_contents, dst_contents)
214 assert_stable(src_contents, dst_contents, line_length=line_length)
218 def format_str(src_contents: str, line_length: int) -> FileContent:
219 """Reformats a string and returns new contents."""
220 src_node = lib2to3_parse(src_contents)
222 lines = LineGenerator()
223 elt = EmptyLineTracker()
224 py36 = is_python36(src_node)
227 for current_line in lines.visit(src_node):
228 for _ in range(after):
229 dst_contents += str(empty_line)
230 before, after = elt.maybe_empty_lines(current_line)
231 for _ in range(before):
232 dst_contents += str(empty_line)
233 for line in split_line(current_line, line_length=line_length, py36=py36):
234 dst_contents += str(line)
238 def lib2to3_parse(src_txt: str) -> Node:
239 """Given a string with source, return the lib2to3 Node."""
240 grammar = pygram.python_grammar_no_print_statement
241 drv = driver.Driver(grammar, pytree.convert)
242 if src_txt[-1] != '\n':
243 nl = '\r\n' if '\r\n' in src_txt[:1024] else '\n'
246 result = drv.parse_string(src_txt, True)
247 except ParseError as pe:
248 lineno, column = pe.context[1]
249 lines = src_txt.splitlines()
251 faulty_line = lines[lineno - 1]
253 faulty_line = "<line number missing in source>"
254 raise ValueError(f"Cannot parse: {lineno}:{column}: {faulty_line}") from None
256 if isinstance(result, Leaf):
257 result = Node(syms.file_input, [result])
261 def lib2to3_unparse(node: Node) -> str:
262 """Given a lib2to3 node, return its string representation."""
270 class Visitor(Generic[T]):
271 """Basic lib2to3 visitor that yields things on visiting."""
273 def visit(self, node: LN) -> Iterator[T]:
275 name = token.tok_name[node.type]
277 name = type_repr(node.type)
278 yield from getattr(self, f'visit_{name}', self.visit_default)(node)
280 def visit_default(self, node: LN) -> Iterator[T]:
281 if isinstance(node, Node):
282 for child in node.children:
283 yield from self.visit(child)
287 class DebugVisitor(Visitor[T]):
290 def visit_default(self, node: LN) -> Iterator[T]:
291 indent = ' ' * (2 * self.tree_depth)
292 if isinstance(node, Node):
293 _type = type_repr(node.type)
294 out(f'{indent}{_type}', fg='yellow')
296 for child in node.children:
297 yield from self.visit(child)
300 out(f'{indent}/{_type}', fg='yellow', bold=False)
302 _type = token.tok_name.get(node.type, str(node.type))
303 out(f'{indent}{_type}', fg='blue', nl=False)
305 # We don't have to handle prefixes for `Node` objects since
306 # that delegates to the first child anyway.
307 out(f' {node.prefix!r}', fg='green', bold=False, nl=False)
308 out(f' {node.value!r}', fg='blue', bold=False)
311 KEYWORDS = set(keyword.kwlist)
312 WHITESPACE = {token.DEDENT, token.INDENT, token.NEWLINE}
313 FLOW_CONTROL = {'return', 'raise', 'break', 'continue'}
324 STANDALONE_COMMENT = 153
325 LOGIC_OPERATORS = {'and', 'or'}
349 COMPREHENSION_PRIORITY = 20
353 COMPARATOR_PRIORITY = 3
358 class BracketTracker:
360 bracket_match: Dict[Tuple[Depth, NodeType], Leaf] = Factory(dict)
361 delimiters: Dict[LeafID, Priority] = Factory(dict)
362 previous: Optional[Leaf] = None
364 def mark(self, leaf: Leaf) -> None:
365 if leaf.type == token.COMMENT:
368 if leaf.type in CLOSING_BRACKETS:
370 opening_bracket = self.bracket_match.pop((self.depth, leaf.type))
371 leaf.opening_bracket = opening_bracket
372 leaf.bracket_depth = self.depth
374 delim = is_delimiter(leaf)
376 self.delimiters[id(leaf)] = delim
377 elif self.previous is not None:
378 if leaf.type == token.STRING and self.previous.type == token.STRING:
379 self.delimiters[id(self.previous)] = STRING_PRIORITY
381 leaf.type == token.NAME
382 and leaf.value == 'for'
384 and leaf.parent.type in {syms.comp_for, syms.old_comp_for}
386 self.delimiters[id(self.previous)] = COMPREHENSION_PRIORITY
388 leaf.type == token.NAME
389 and leaf.value == 'if'
391 and leaf.parent.type in {syms.comp_if, syms.old_comp_if}
393 self.delimiters[id(self.previous)] = COMPREHENSION_PRIORITY
395 leaf.type == token.NAME
396 and leaf.value in LOGIC_OPERATORS
399 self.delimiters[id(self.previous)] = LOGIC_PRIORITY
400 if leaf.type in OPENING_BRACKETS:
401 self.bracket_match[self.depth, BRACKET[leaf.type]] = leaf
405 def any_open_brackets(self) -> bool:
406 """Returns True if there is an yet unmatched open bracket on the line."""
407 return bool(self.bracket_match)
409 def max_priority(self, exclude: Iterable[LeafID] =()) -> int:
410 """Returns the highest priority of a delimiter found on the line.
412 Values are consistent with what `is_delimiter()` returns.
414 return max(v for k, v in self.delimiters.items() if k not in exclude)
420 leaves: List[Leaf] = Factory(list)
421 comments: Dict[LeafID, Leaf] = Factory(dict)
422 bracket_tracker: BracketTracker = Factory(BracketTracker)
423 inside_brackets: bool = False
424 has_for: bool = False
425 _for_loop_variable: bool = False
427 def append(self, leaf: Leaf, preformatted: bool = False) -> None:
428 has_value = leaf.value.strip()
432 if self.leaves and not preformatted:
433 # Note: at this point leaf.prefix should be empty except for
434 # imports, for which we only preserve newlines.
435 leaf.prefix += whitespace(leaf)
436 if self.inside_brackets or not preformatted:
437 self.maybe_decrement_after_for_loop_variable(leaf)
438 self.bracket_tracker.mark(leaf)
439 self.maybe_remove_trailing_comma(leaf)
440 self.maybe_increment_for_loop_variable(leaf)
441 if self.maybe_adapt_standalone_comment(leaf):
444 if not self.append_comment(leaf):
445 self.leaves.append(leaf)
448 def is_comment(self) -> bool:
449 return bool(self) and self.leaves[0].type == STANDALONE_COMMENT
452 def is_decorator(self) -> bool:
453 return bool(self) and self.leaves[0].type == token.AT
456 def is_import(self) -> bool:
457 return bool(self) and is_import(self.leaves[0])
460 def is_class(self) -> bool:
463 and self.leaves[0].type == token.NAME
464 and self.leaves[0].value == 'class'
468 def is_def(self) -> bool:
469 """Also returns True for async defs."""
471 first_leaf = self.leaves[0]
476 second_leaf: Optional[Leaf] = self.leaves[1]
480 (first_leaf.type == token.NAME and first_leaf.value == 'def')
482 first_leaf.type == token.ASYNC
483 and second_leaf is not None
484 and second_leaf.type == token.NAME
485 and second_leaf.value == 'def'
490 def is_flow_control(self) -> bool:
493 and self.leaves[0].type == token.NAME
494 and self.leaves[0].value in FLOW_CONTROL
498 def is_yield(self) -> bool:
501 and self.leaves[0].type == token.NAME
502 and self.leaves[0].value == 'yield'
505 def maybe_remove_trailing_comma(self, closing: Leaf) -> bool:
508 and self.leaves[-1].type == token.COMMA
509 and closing.type in CLOSING_BRACKETS
513 if closing.type == token.RSQB or closing.type == token.RBRACE:
517 # For parens let's check if it's safe to remove the comma. If the
518 # trailing one is the only one, we might mistakenly change a tuple
519 # into a different type by removing the comma.
520 depth = closing.bracket_depth + 1
522 opening = closing.opening_bracket
523 for _opening_index, leaf in enumerate(self.leaves):
530 for leaf in self.leaves[_opening_index + 1:]:
534 bracket_depth = leaf.bracket_depth
535 if bracket_depth == depth and leaf.type == token.COMMA:
537 if leaf.parent and leaf.parent.type == syms.arglist:
547 def maybe_increment_for_loop_variable(self, leaf: Leaf) -> bool:
548 """In a for loop, or comprehension, the variables are often unpacks.
550 To avoid splitting on the comma in this situation, we will increase
551 the depth of tokens between `for` and `in`.
553 if leaf.type == token.NAME and leaf.value == 'for':
555 self.bracket_tracker.depth += 1
556 self._for_loop_variable = True
561 def maybe_decrement_after_for_loop_variable(self, leaf: Leaf) -> bool:
562 # See `maybe_increment_for_loop_variable` above for explanation.
563 if self._for_loop_variable and leaf.type == token.NAME and leaf.value == 'in':
564 self.bracket_tracker.depth -= 1
565 self._for_loop_variable = False
570 def maybe_adapt_standalone_comment(self, comment: Leaf) -> bool:
571 """Hack a standalone comment to act as a trailing comment for line splitting.
573 If this line has brackets and a standalone `comment`, we need to adapt
574 it to be able to still reformat the line.
576 This is not perfect, the line to which the standalone comment gets
577 appended will appear "too long" when splitting.
580 comment.type == STANDALONE_COMMENT
581 and self.bracket_tracker.any_open_brackets()
585 comment.type = token.COMMENT
586 comment.prefix = '\n' + ' ' * (self.depth + 1)
587 return self.append_comment(comment)
589 def append_comment(self, comment: Leaf) -> bool:
590 if comment.type != token.COMMENT:
594 after = id(self.last_non_delimiter())
596 comment.type = STANDALONE_COMMENT
601 if after in self.comments:
602 self.comments[after].value += str(comment)
604 self.comments[after] = comment
607 def last_non_delimiter(self) -> Leaf:
608 for i in range(len(self.leaves)):
609 last = self.leaves[-i - 1]
610 if not is_delimiter(last):
613 raise LookupError("No non-delimiters found")
615 def __str__(self) -> str:
619 indent = ' ' * self.depth
620 leaves = iter(self.leaves)
622 res = f'{first.prefix}{indent}{first.value}'
625 for comment in self.comments.values():
629 def __bool__(self) -> bool:
630 return bool(self.leaves or self.comments)
634 class EmptyLineTracker:
635 """Provides a stateful method that returns the number of potential extra
636 empty lines needed before and after the currently processed line.
638 Note: this tracker works on lines that haven't been split yet. It assumes
639 the prefix of the first leaf consists of optional newlines. Those newlines
640 are consumed by `maybe_empty_lines()` and included in the computation.
642 previous_line: Optional[Line] = None
643 previous_after: int = 0
644 previous_defs: List[int] = Factory(list)
646 def maybe_empty_lines(self, current_line: Line) -> Tuple[int, int]:
647 """Returns the number of extra empty lines before and after the `current_line`.
649 This is for separating `def`, `async def` and `class` with extra empty lines
650 (two on module-level), as well as providing an extra empty line after flow
651 control keywords to make them more prominent.
653 before, after = self._maybe_empty_lines(current_line)
654 before -= self.previous_after
655 self.previous_after = after
656 self.previous_line = current_line
659 def _maybe_empty_lines(self, current_line: Line) -> Tuple[int, int]:
661 if current_line.is_comment and current_line.depth == 0:
663 if current_line.leaves:
664 # Consume the first leaf's extra newlines.
665 first_leaf = current_line.leaves[0]
666 before = first_leaf.prefix.count('\n')
667 before = min(before, max(before, max_allowed))
668 first_leaf.prefix = ''
671 depth = current_line.depth
672 while self.previous_defs and self.previous_defs[-1] >= depth:
673 self.previous_defs.pop()
674 before = 1 if depth else 2
675 is_decorator = current_line.is_decorator
676 if is_decorator or current_line.is_def or current_line.is_class:
678 self.previous_defs.append(depth)
679 if self.previous_line is None:
680 # Don't insert empty lines before the first line in the file.
683 if self.previous_line and self.previous_line.is_decorator:
684 # Don't insert empty lines between decorators.
688 if current_line.depth:
692 if current_line.is_flow_control:
697 and self.previous_line.is_import
698 and not current_line.is_import
699 and depth == self.previous_line.depth
701 return (before or 1), 0
705 and self.previous_line.is_yield
706 and (not current_line.is_yield or depth != self.previous_line.depth)
708 return (before or 1), 0
714 class LineGenerator(Visitor[Line]):
715 """Generates reformatted Line objects. Empty lines are not emitted.
717 Note: destroys the tree it's visiting by mutating prefixes of its leaves
718 in ways that will no longer stringify to valid Python code on the tree.
720 current_line: Line = Factory(Line)
722 def line(self, indent: int = 0) -> Iterator[Line]:
725 If the line is empty, only emit if it makes sense.
726 If the line is too long, split it first and then generate.
728 If any lines were generated, set up a new current_line.
730 if not self.current_line:
731 self.current_line.depth += indent
732 return # Line is empty, don't emit. Creating a new one unnecessary.
734 complete_line = self.current_line
735 self.current_line = Line(depth=complete_line.depth + indent)
738 def visit_default(self, node: LN) -> Iterator[Line]:
739 if isinstance(node, Leaf):
740 any_open_brackets = self.current_line.bracket_tracker.any_open_brackets()
741 for comment in generate_comments(node):
742 if any_open_brackets:
743 # any comment within brackets is subject to splitting
744 self.current_line.append(comment)
745 elif comment.type == token.COMMENT:
746 # regular trailing comment
747 self.current_line.append(comment)
748 yield from self.line()
751 # regular standalone comment
752 yield from self.line()
754 self.current_line.append(comment)
755 yield from self.line()
757 normalize_prefix(node, inside_brackets=any_open_brackets)
758 if node.type not in WHITESPACE:
759 self.current_line.append(node)
760 yield from super().visit_default(node)
762 def visit_INDENT(self, node: Node) -> Iterator[Line]:
763 yield from self.line(+1)
764 yield from self.visit_default(node)
766 def visit_DEDENT(self, node: Node) -> Iterator[Line]:
767 yield from self.line(-1)
769 def visit_stmt(self, node: Node, keywords: Set[str]) -> Iterator[Line]:
770 """Visit a statement.
772 The relevant Python language keywords for this statement are NAME leaves
775 for child in node.children:
776 if child.type == token.NAME and child.value in keywords: # type: ignore
777 yield from self.line()
779 yield from self.visit(child)
781 def visit_simple_stmt(self, node: Node) -> Iterator[Line]:
782 """A statement without nested statements."""
783 is_suite_like = node.parent and node.parent.type in STATEMENT
785 yield from self.line(+1)
786 yield from self.visit_default(node)
787 yield from self.line(-1)
790 yield from self.line()
791 yield from self.visit_default(node)
793 def visit_async_stmt(self, node: Node) -> Iterator[Line]:
794 yield from self.line()
796 children = iter(node.children)
797 for child in children:
798 yield from self.visit(child)
800 if child.type == token.ASYNC:
803 internal_stmt = next(children)
804 for child in internal_stmt.children:
805 yield from self.visit(child)
807 def visit_decorators(self, node: Node) -> Iterator[Line]:
808 for child in node.children:
809 yield from self.line()
810 yield from self.visit(child)
812 def visit_SEMI(self, leaf: Leaf) -> Iterator[Line]:
813 yield from self.line()
815 def visit_ENDMARKER(self, leaf: Leaf) -> Iterator[Line]:
816 yield from self.visit_default(leaf)
817 yield from self.line()
819 def __attrs_post_init__(self) -> None:
820 """You are in a twisty little maze of passages."""
822 self.visit_if_stmt = partial(v, keywords={'if', 'else', 'elif'})
823 self.visit_while_stmt = partial(v, keywords={'while', 'else'})
824 self.visit_for_stmt = partial(v, keywords={'for', 'else'})
825 self.visit_try_stmt = partial(v, keywords={'try', 'except', 'else', 'finally'})
826 self.visit_except_clause = partial(v, keywords={'except'})
827 self.visit_funcdef = partial(v, keywords={'def'})
828 self.visit_with_stmt = partial(v, keywords={'with'})
829 self.visit_classdef = partial(v, keywords={'class'})
830 self.visit_async_funcdef = self.visit_async_stmt
831 self.visit_decorated = self.visit_decorators
834 BRACKET = {token.LPAR: token.RPAR, token.LSQB: token.RSQB, token.LBRACE: token.RBRACE}
835 OPENING_BRACKETS = set(BRACKET.keys())
836 CLOSING_BRACKETS = set(BRACKET.values())
837 BRACKETS = OPENING_BRACKETS | CLOSING_BRACKETS
838 ALWAYS_NO_SPACE = CLOSING_BRACKETS | {token.COMMA, STANDALONE_COMMENT}
841 def whitespace(leaf: Leaf) -> str: # noqa C901
842 """Return whitespace prefix if needed for the given `leaf`."""
849 if t in ALWAYS_NO_SPACE:
852 if t == token.COMMENT:
855 assert p is not None, f"INTERNAL ERROR: hand-made leaf without parent: {leaf!r}"
856 if t == token.COLON and p.type not in {syms.subscript, syms.subscriptlist}:
859 prev = leaf.prev_sibling
861 prevp = preceding_leaf(p)
862 if not prevp or prevp.type in OPENING_BRACKETS:
866 return SPACE if prevp.type == token.COMMA else NO
868 if prevp.type == token.EQUAL:
869 if prevp.parent and prevp.parent.type in {
878 elif prevp.type == token.DOUBLESTAR:
879 if prevp.parent and prevp.parent.type in {
888 elif prevp.type == token.COLON:
889 if prevp.parent and prevp.parent.type in {syms.subscript, syms.sliceop}:
894 and prevp.parent.type in {syms.factor, syms.star_expr}
895 and prevp.type in MATH_OPERATORS
899 elif prev.type in OPENING_BRACKETS:
902 if p.type in {syms.parameters, syms.arglist}:
903 # untyped function signatures or calls
907 if not prev or prev.type != token.COMMA:
910 if p.type == syms.varargslist:
915 if prev and prev.type != token.COMMA:
918 elif p.type == syms.typedargslist:
919 # typed function signatures
924 if prev.type != syms.tname:
927 elif prev.type == token.EQUAL:
928 # A bit hacky: if the equal sign has whitespace, it means we
929 # previously found it's a typed argument. So, we're using that, too.
932 elif prev.type != token.COMMA:
935 elif p.type == syms.tname:
938 prevp = preceding_leaf(p)
939 if not prevp or prevp.type != token.COMMA:
942 elif p.type == syms.trailer:
943 # attributes and calls
944 if t == token.LPAR or t == token.RPAR:
949 prevp = preceding_leaf(p)
950 if not prevp or prevp.type != token.NUMBER:
953 elif t == token.LSQB:
956 elif prev.type != token.COMMA:
959 elif p.type == syms.argument:
965 prevp = preceding_leaf(p)
966 if not prevp or prevp.type == token.LPAR:
969 elif prev.type == token.EQUAL or prev.type == token.DOUBLESTAR:
972 elif p.type == syms.decorator:
976 elif p.type == syms.dotted_name:
980 prevp = preceding_leaf(p)
981 if not prevp or prevp.type == token.AT or prevp.type == token.DOT:
984 elif p.type == syms.classdef:
988 if prev and prev.type == token.LPAR:
991 elif p.type == syms.subscript:
994 assert p.parent is not None, "subscripts are always parented"
995 if p.parent.type == syms.subscriptlist:
1003 elif p.type == syms.atom:
1004 if prev and t == token.DOT:
1005 # dots, but not the first one.
1009 p.type == syms.listmaker
1010 or p.type == syms.testlist_gexp
1011 or p.type == syms.subscriptlist
1013 # list interior, including unpacking
1017 elif p.type == syms.dictsetmaker:
1018 # dict and set interior, including unpacking
1022 if prev.type == token.DOUBLESTAR:
1025 elif p.type in {syms.factor, syms.star_expr}:
1028 prevp = preceding_leaf(p)
1029 if not prevp or prevp.type in OPENING_BRACKETS:
1032 prevp_parent = prevp.parent
1033 assert prevp_parent is not None
1034 if prevp.type == token.COLON and prevp_parent.type in {
1035 syms.subscript, syms.sliceop
1039 elif prevp.type == token.EQUAL and prevp_parent.type == syms.argument:
1042 elif t == token.NAME or t == token.NUMBER:
1045 elif p.type == syms.import_from:
1047 if prev and prev.type == token.DOT:
1050 elif t == token.NAME:
1054 if prev and prev.type == token.DOT:
1057 elif p.type == syms.sliceop:
1063 def preceding_leaf(node: Optional[LN]) -> Optional[Leaf]:
1064 """Returns the first leaf that precedes `node`, if any."""
1066 res = node.prev_sibling
1068 if isinstance(res, Leaf):
1072 return list(res.leaves())[-1]
1081 def is_delimiter(leaf: Leaf) -> int:
1082 """Returns the priority of the `leaf` delimiter. Returns 0 if not delimiter.
1084 Higher numbers are higher priority.
1086 if leaf.type == token.COMMA:
1087 return COMMA_PRIORITY
1089 if leaf.type in COMPARATORS:
1090 return COMPARATOR_PRIORITY
1093 leaf.type in MATH_OPERATORS
1095 and leaf.parent.type not in {syms.factor, syms.star_expr}
1097 return MATH_PRIORITY
1102 def generate_comments(leaf: Leaf) -> Iterator[Leaf]:
1103 """Cleans the prefix of the `leaf` and generates comments from it, if any.
1105 Comments in lib2to3 are shoved into the whitespace prefix. This happens
1106 in `pgen2/driver.py:Driver.parse_tokens()`. This was a brilliant implementation
1107 move because it does away with modifying the grammar to include all the
1108 possible places in which comments can be placed.
1110 The sad consequence for us though is that comments don't "belong" anywhere.
1111 This is why this function generates simple parentless Leaf objects for
1112 comments. We simply don't know what the correct parent should be.
1114 No matter though, we can live without this. We really only need to
1115 differentiate between inline and standalone comments. The latter don't
1116 share the line with any code.
1118 Inline comments are emitted as regular token.COMMENT leaves. Standalone
1119 are emitted with a fake STANDALONE_COMMENT token identifier.
1129 for index, line in enumerate(p.split('\n')):
1130 line = line.lstrip()
1133 if not line.startswith('#'):
1136 if index == 0 and leaf.type != token.ENDMARKER:
1137 comment_type = token.COMMENT # simple trailing comment
1139 comment_type = STANDALONE_COMMENT
1140 yield Leaf(comment_type, make_comment(line), prefix='\n' * nlines)
1145 def make_comment(content: str) -> str:
1146 content = content.rstrip()
1150 if content[0] == '#':
1151 content = content[1:]
1152 if content and content[0] not in {' ', '!', '#'}:
1153 content = ' ' + content
1154 return '#' + content
1158 line: Line, line_length: int, inner: bool = False, py36: bool = False
1159 ) -> Iterator[Line]:
1160 """Splits a `line` into potentially many lines.
1162 They should fit in the allotted `line_length` but might not be able to.
1163 `inner` signifies that there were a pair of brackets somewhere around the
1164 current `line`, possibly transitively. This means we can fallback to splitting
1165 by delimiters if the LHS/RHS don't yield any results.
1167 If `py36` is True, splitting may generate syntax that is only compatible
1168 with Python 3.6 and later.
1170 line_str = str(line).strip('\n')
1171 if len(line_str) <= line_length and '\n' not in line_str:
1176 split_funcs = [left_hand_split]
1177 elif line.inside_brackets:
1178 split_funcs = [delimiter_split]
1179 if '\n' not in line_str:
1180 # Only attempt RHS if we don't have multiline strings or comments
1182 split_funcs.append(right_hand_split)
1184 split_funcs = [right_hand_split]
1185 for split_func in split_funcs:
1186 # We are accumulating lines in `result` because we might want to abort
1187 # mission and return the original line in the end, or attempt a different
1189 result: List[Line] = []
1191 for l in split_func(line, py36=py36):
1192 if str(l).strip('\n') == line_str:
1193 raise CannotSplit("Split function returned an unchanged result")
1196 split_line(l, line_length=line_length, inner=True, py36=py36)
1198 except CannotSplit as cs:
1209 def left_hand_split(line: Line, py36: bool = False) -> Iterator[Line]:
1210 """Split line into many lines, starting with the first matching bracket pair.
1212 Note: this usually looks weird, only use this for function definitions.
1213 Prefer RHS otherwise.
1215 head = Line(depth=line.depth)
1216 body = Line(depth=line.depth + 1, inside_brackets=True)
1217 tail = Line(depth=line.depth)
1218 tail_leaves: List[Leaf] = []
1219 body_leaves: List[Leaf] = []
1220 head_leaves: List[Leaf] = []
1221 current_leaves = head_leaves
1222 matching_bracket = None
1223 for leaf in line.leaves:
1225 current_leaves is body_leaves
1226 and leaf.type in CLOSING_BRACKETS
1227 and leaf.opening_bracket is matching_bracket
1229 current_leaves = tail_leaves if body_leaves else head_leaves
1230 current_leaves.append(leaf)
1231 if current_leaves is head_leaves:
1232 if leaf.type in OPENING_BRACKETS:
1233 matching_bracket = leaf
1234 current_leaves = body_leaves
1235 # Since body is a new indent level, remove spurious leading whitespace.
1237 normalize_prefix(body_leaves[0], inside_brackets=True)
1238 # Build the new lines.
1239 for result, leaves in (
1240 (head, head_leaves), (body, body_leaves), (tail, tail_leaves)
1243 result.append(leaf, preformatted=True)
1244 comment_after = line.comments.get(id(leaf))
1246 result.append(comment_after, preformatted=True)
1247 split_succeeded_or_raise(head, body, tail)
1248 for result in (head, body, tail):
1253 def right_hand_split(line: Line, py36: bool = False) -> Iterator[Line]:
1254 """Split line into many lines, starting with the last matching bracket pair."""
1255 head = Line(depth=line.depth)
1256 body = Line(depth=line.depth + 1, inside_brackets=True)
1257 tail = Line(depth=line.depth)
1258 tail_leaves: List[Leaf] = []
1259 body_leaves: List[Leaf] = []
1260 head_leaves: List[Leaf] = []
1261 current_leaves = tail_leaves
1262 opening_bracket = None
1263 for leaf in reversed(line.leaves):
1264 if current_leaves is body_leaves:
1265 if leaf is opening_bracket:
1266 current_leaves = head_leaves if body_leaves else tail_leaves
1267 current_leaves.append(leaf)
1268 if current_leaves is tail_leaves:
1269 if leaf.type in CLOSING_BRACKETS:
1270 opening_bracket = leaf.opening_bracket
1271 current_leaves = body_leaves
1272 tail_leaves.reverse()
1273 body_leaves.reverse()
1274 head_leaves.reverse()
1275 # Since body is a new indent level, remove spurious leading whitespace.
1277 normalize_prefix(body_leaves[0], inside_brackets=True)
1278 # Build the new lines.
1279 for result, leaves in (
1280 (head, head_leaves), (body, body_leaves), (tail, tail_leaves)
1283 result.append(leaf, preformatted=True)
1284 comment_after = line.comments.get(id(leaf))
1286 result.append(comment_after, preformatted=True)
1287 split_succeeded_or_raise(head, body, tail)
1288 for result in (head, body, tail):
1293 def split_succeeded_or_raise(head: Line, body: Line, tail: Line) -> None:
1294 tail_len = len(str(tail).strip())
1297 raise CannotSplit("Splitting brackets produced the same line")
1301 f"Splitting brackets on an empty body to save "
1302 f"{tail_len} characters is not worth it"
1306 def delimiter_split(line: Line, py36: bool = False) -> Iterator[Line]:
1307 """Split according to delimiters of the highest priority.
1309 This kind of split doesn't increase indentation.
1310 If `py36` is True, the split will add trailing commas also in function
1311 signatures that contain * and **.
1314 last_leaf = line.leaves[-1]
1316 raise CannotSplit("Line empty")
1318 delimiters = line.bracket_tracker.delimiters
1320 delimiter_priority = line.bracket_tracker.max_priority(exclude={id(last_leaf)})
1322 raise CannotSplit("No delimiters found")
1324 current_line = Line(depth=line.depth, inside_brackets=line.inside_brackets)
1325 lowest_depth = sys.maxsize
1326 trailing_comma_safe = True
1327 for leaf in line.leaves:
1328 current_line.append(leaf, preformatted=True)
1329 comment_after = line.comments.get(id(leaf))
1331 current_line.append(comment_after, preformatted=True)
1332 lowest_depth = min(lowest_depth, leaf.bracket_depth)
1334 leaf.bracket_depth == lowest_depth
1335 and leaf.type == token.STAR
1336 or leaf.type == token.DOUBLESTAR
1338 trailing_comma_safe = trailing_comma_safe and py36
1339 leaf_priority = delimiters.get(id(leaf))
1340 if leaf_priority == delimiter_priority:
1341 normalize_prefix(current_line.leaves[0], inside_brackets=True)
1344 current_line = Line(depth=line.depth, inside_brackets=line.inside_brackets)
1347 delimiter_priority == COMMA_PRIORITY
1348 and current_line.leaves[-1].type != token.COMMA
1349 and trailing_comma_safe
1351 current_line.append(Leaf(token.COMMA, ','))
1352 normalize_prefix(current_line.leaves[0], inside_brackets=True)
1356 def is_import(leaf: Leaf) -> bool:
1357 """Returns True if the given leaf starts an import statement."""
1364 (v == 'import' and p and p.type == syms.import_name)
1365 or (v == 'from' and p and p.type == syms.import_from)
1370 def normalize_prefix(leaf: Leaf, *, inside_brackets: bool) -> None:
1371 """Leave existing extra newlines if not `inside_brackets`.
1373 Remove everything else. Note: don't use backslashes for formatting or
1374 you'll lose your voting rights.
1376 if not inside_brackets:
1377 spl = leaf.prefix.split('#')
1378 if '\\' not in spl[0]:
1379 nl_count = spl[-1].count('\n')
1382 leaf.prefix = '\n' * nl_count
1388 def is_python36(node: Node) -> bool:
1389 """Returns True if the current file is using Python 3.6+ features.
1391 Currently looking for:
1393 - trailing commas after * or ** in function signatures.
1395 for n in node.pre_order():
1396 if n.type == token.STRING:
1397 value_head = n.value[:2] # type: ignore
1398 if value_head in {'f"', 'F"', "f'", "F'", 'rf', 'fr', 'RF', 'FR'}:
1402 n.type == syms.typedargslist
1404 and n.children[-1].type == token.COMMA
1406 for ch in n.children:
1407 if ch.type == token.STAR or ch.type == token.DOUBLESTAR:
1413 PYTHON_EXTENSIONS = {'.py'}
1414 BLACKLISTED_DIRECTORIES = {
1415 'build', 'buck-out', 'dist', '_build', '.git', '.hg', '.mypy_cache', '.tox', '.venv'
1419 def gen_python_files_in_dir(path: Path) -> Iterator[Path]:
1420 for child in path.iterdir():
1422 if child.name in BLACKLISTED_DIRECTORIES:
1425 yield from gen_python_files_in_dir(child)
1427 elif child.suffix in PYTHON_EXTENSIONS:
1433 """Provides a reformatting counter."""
1434 change_count: int = 0
1436 failure_count: int = 0
1438 def done(self, src: Path, changed: bool) -> None:
1439 """Increment the counter for successful reformatting. Write out a message."""
1441 out(f'reformatted {src}')
1442 self.change_count += 1
1444 out(f'{src} already well formatted, good job.', bold=False)
1445 self.same_count += 1
1447 def failed(self, src: Path, message: str) -> None:
1448 """Increment the counter for failed reformatting. Write out a message."""
1449 err(f'error: cannot format {src}: {message}')
1450 self.failure_count += 1
1453 def return_code(self) -> int:
1454 """Which return code should the app use considering the current state."""
1455 # According to http://tldp.org/LDP/abs/html/exitcodes.html starting with
1456 # 126 we have special returncodes reserved by the shell.
1457 if self.failure_count:
1460 elif self.change_count:
1465 def __str__(self) -> str:
1466 """A color report of the current state.
1468 Use `click.unstyle` to remove colors.
1471 if self.change_count:
1472 s = 's' if self.change_count > 1 else ''
1474 click.style(f'{self.change_count} file{s} reformatted', bold=True)
1477 s = 's' if self.same_count > 1 else ''
1478 report.append(f'{self.same_count} file{s} left unchanged')
1479 if self.failure_count:
1480 s = 's' if self.failure_count > 1 else ''
1483 f'{self.failure_count} file{s} failed to reformat', fg='red'
1486 return ', '.join(report) + '.'
1489 def assert_equivalent(src: str, dst: str) -> None:
1490 """Raises AssertionError if `src` and `dst` aren't equivalent.
1492 This is a temporary sanity check until Black becomes stable.
1498 def _v(node: ast.AST, depth: int = 0) -> Iterator[str]:
1499 """Simple visitor generating strings to compare ASTs by content."""
1500 yield f"{' ' * depth}{node.__class__.__name__}("
1502 for field in sorted(node._fields):
1504 value = getattr(node, field)
1505 except AttributeError:
1508 yield f"{' ' * (depth+1)}{field}="
1510 if isinstance(value, list):
1512 if isinstance(item, ast.AST):
1513 yield from _v(item, depth + 2)
1515 elif isinstance(value, ast.AST):
1516 yield from _v(value, depth + 2)
1519 yield f"{' ' * (depth+2)}{value!r}, # {value.__class__.__name__}"
1521 yield f"{' ' * depth}) # /{node.__class__.__name__}"
1524 src_ast = ast.parse(src)
1525 except Exception as exc:
1526 raise AssertionError(f"cannot parse source: {exc}") from None
1529 dst_ast = ast.parse(dst)
1530 except Exception as exc:
1531 log = dump_to_file(''.join(traceback.format_tb(exc.__traceback__)), dst)
1532 raise AssertionError(
1533 f"INTERNAL ERROR: Black produced invalid code: {exc}. "
1534 f"Please report a bug on https://github.com/ambv/black/issues. "
1535 f"This invalid output might be helpful: {log}"
1538 src_ast_str = '\n'.join(_v(src_ast))
1539 dst_ast_str = '\n'.join(_v(dst_ast))
1540 if src_ast_str != dst_ast_str:
1541 log = dump_to_file(diff(src_ast_str, dst_ast_str, 'src', 'dst'))
1542 raise AssertionError(
1543 f"INTERNAL ERROR: Black produced code that is not equivalent to "
1545 f"Please report a bug on https://github.com/ambv/black/issues. "
1546 f"This diff might be helpful: {log}"
1550 def assert_stable(src: str, dst: str, line_length: int) -> None:
1551 """Raises AssertionError if `dst` reformats differently the second time.
1553 This is a temporary sanity check until Black becomes stable.
1555 newdst = format_str(dst, line_length=line_length)
1558 diff(src, dst, 'source', 'first pass'),
1559 diff(dst, newdst, 'first pass', 'second pass'),
1561 raise AssertionError(
1562 f"INTERNAL ERROR: Black produced different code on the second pass "
1563 f"of the formatter. "
1564 f"Please report a bug on https://github.com/ambv/black/issues. "
1565 f"This diff might be helpful: {log}"
1569 def dump_to_file(*output: str) -> str:
1570 """Dumps `output` to a temporary file. Returns path to the file."""
1573 with tempfile.NamedTemporaryFile(
1574 mode='w', prefix='blk_', suffix='.log', delete=False
1576 for lines in output:
1582 def diff(a: str, b: str, a_name: str, b_name: str) -> str:
1583 """Returns a udiff string between strings `a` and `b`."""
1586 a_lines = [line + '\n' for line in a.split('\n')]
1587 b_lines = [line + '\n' for line in b.split('\n')]
1589 difflib.unified_diff(a_lines, b_lines, fromfile=a_name, tofile=b_name, n=5)
1593 if __name__ == '__main__':