X-Git-Url: https://git.madduck.net/etc/vim.git/blobdiff_plain/5fb5cc8c2bd5a0bb1359fb69cdb705b55afade52..64aae0e57d7098bcb9b2cde1aa6aff3b3fd0fc0f:/black.py diff --git a/black.py b/black.py index 0a9d3ea..f305e8d 100644 --- a/black.py +++ b/black.py @@ -12,7 +12,7 @@ from typing import ( Dict, Generic, Iterable, Iterator, List, Optional, Set, Tuple, TypeVar, Union ) -from attr import attrib, dataclass, Factory +from attr import dataclass, Factory import click # lib2to3 fork @@ -21,7 +21,7 @@ from blib2to3 import pygram, pytree from blib2to3.pgen2 import driver, token from blib2to3.pgen2.parse import ParseError -__version__ = "18.3a1" +__version__ = "18.3a2" DEFAULT_LINE_LENGTH = 88 # types syms = pygram.python_symbols @@ -210,8 +210,13 @@ def format_str(src_contents: str, line_length: int) -> FileContent: dst_contents += str(line) else: comments.append(current_line) - for comment in comments: - dst_contents += str(comment) + if comments: + if elt.previous_defs: + # Separate postscriptum comments from the last module-level def. + dst_contents += str(empty_line) + dst_contents += str(empty_line) + for comment in comments: + dst_contents += str(comment) return dst_contents @@ -265,7 +270,7 @@ class Visitor(Generic[T]): @dataclass class DebugVisitor(Visitor[T]): - tree_depth: int = attrib(default=0) + tree_depth: int = 0 def visit_default(self, node: LN) -> Iterator[T]: indent = ' ' * (2 * self.tree_depth) @@ -335,10 +340,10 @@ MATH_PRIORITY = 1 @dataclass class BracketTracker: - depth: int = attrib(default=0) - bracket_match: Dict[Tuple[Depth, NodeType], Leaf] = attrib(default=Factory(dict)) - delimiters: Dict[LeafID, Priority] = attrib(default=Factory(dict)) - previous: Optional[Leaf] = attrib(default=None) + depth: int = 0 + bracket_match: Dict[Tuple[Depth, NodeType], Leaf] = Factory(dict) + delimiters: Dict[LeafID, Priority] = Factory(dict) + previous: Optional[Leaf] = None def mark(self, leaf: Leaf) -> None: if leaf.type == token.COMMENT: @@ -347,8 +352,8 @@ class BracketTracker: if leaf.type in CLOSING_BRACKETS: self.depth -= 1 opening_bracket = self.bracket_match.pop((self.depth, leaf.type)) - leaf.opening_bracket = opening_bracket # type: ignore - leaf.bracket_depth = self.depth # type: ignore + leaf.opening_bracket = opening_bracket + leaf.bracket_depth = self.depth if self.depth == 0: delim = is_delimiter(leaf) if delim: @@ -357,19 +362,25 @@ class BracketTracker: if leaf.type == token.STRING and self.previous.type == token.STRING: self.delimiters[id(self.previous)] = STRING_PRIORITY elif ( - leaf.type == token.NAME and - leaf.value == 'for' and - leaf.parent and - leaf.parent.type in {syms.comp_for, syms.old_comp_for} + leaf.type == token.NAME + and leaf.value == 'for' + and leaf.parent + and leaf.parent.type in {syms.comp_for, syms.old_comp_for} ): self.delimiters[id(self.previous)] = COMPREHENSION_PRIORITY elif ( - leaf.type == token.NAME and - leaf.value == 'if' and - leaf.parent and - leaf.parent.type in {syms.comp_if, syms.old_comp_if} + leaf.type == token.NAME + and leaf.value == 'if' + and leaf.parent + and leaf.parent.type in {syms.comp_if, syms.old_comp_if} ): self.delimiters[id(self.previous)] = COMPREHENSION_PRIORITY + elif ( + leaf.type == token.NAME + and leaf.value in LOGIC_OPERATORS + and leaf.parent + ): + self.delimiters[id(self.previous)] = LOGIC_PRIORITY if leaf.type in OPENING_BRACKETS: self.bracket_match[self.depth, BRACKET[leaf.type]] = leaf self.depth += 1 @@ -389,13 +400,13 @@ class BracketTracker: @dataclass class Line: - depth: int = attrib(default=0) - leaves: List[Leaf] = attrib(default=Factory(list)) - comments: Dict[LeafID, Leaf] = attrib(default=Factory(dict)) - bracket_tracker: BracketTracker = attrib(default=Factory(BracketTracker)) - inside_brackets: bool = attrib(default=False) - has_for: bool = attrib(default=False) - _for_loop_variable: bool = attrib(default=False, init=False) + depth: int = 0 + leaves: List[Leaf] = Factory(list) + comments: Dict[LeafID, Leaf] = Factory(dict) + bracket_tracker: BracketTracker = Factory(BracketTracker) + inside_brackets: bool = False + has_for: bool = False + _for_loop_variable: bool = False def append(self, leaf: Leaf, preformatted: bool = False) -> None: has_value = leaf.value.strip() @@ -432,9 +443,9 @@ class Line: @property def is_class(self) -> bool: return ( - bool(self) and - self.leaves[0].type == token.NAME and - self.leaves[0].value == 'class' + bool(self) + and self.leaves[0].type == token.NAME + and self.leaves[0].value == 'class' ) @property @@ -450,37 +461,37 @@ class Line: except IndexError: second_leaf = None return ( - (first_leaf.type == token.NAME and first_leaf.value == 'def') or - ( - first_leaf.type == token.NAME and - first_leaf.value == 'async' and - second_leaf is not None and - second_leaf.type == token.NAME and - second_leaf.value == 'def' + (first_leaf.type == token.NAME and first_leaf.value == 'def') + or ( + first_leaf.type == token.NAME + and first_leaf.value == 'async' + and second_leaf is not None + and second_leaf.type == token.NAME + and second_leaf.value == 'def' ) ) @property def is_flow_control(self) -> bool: return ( - bool(self) and - self.leaves[0].type == token.NAME and - self.leaves[0].value in FLOW_CONTROL + bool(self) + and self.leaves[0].type == token.NAME + and self.leaves[0].value in FLOW_CONTROL ) @property def is_yield(self) -> bool: return ( - bool(self) and - self.leaves[0].type == token.NAME and - self.leaves[0].value == 'yield' + bool(self) + and self.leaves[0].type == token.NAME + and self.leaves[0].value == 'yield' ) def maybe_remove_trailing_comma(self, closing: Leaf) -> bool: if not ( - self.leaves and - self.leaves[-1].type == token.COMMA and - closing.type in CLOSING_BRACKETS + self.leaves + and self.leaves[-1].type == token.COMMA + and closing.type in CLOSING_BRACKETS ): return False @@ -491,9 +502,9 @@ class Line: # For parens let's check if it's safe to remove the comma. If the # trailing one is the only one, we might mistakenly change a tuple # into a different type by removing the comma. - depth = closing.bracket_depth + 1 # type: ignore + depth = closing.bracket_depth + 1 commas = 0 - opening = closing.opening_bracket # type: ignore + opening = closing.opening_bracket for _opening_index, leaf in enumerate(self.leaves): if leaf is opening: break @@ -505,9 +516,13 @@ class Line: if leaf is closing: break - bracket_depth = leaf.bracket_depth # type: ignore + bracket_depth = leaf.bracket_depth if bracket_depth == depth and leaf.type == token.COMMA: commas += 1 + if leaf.parent and leaf.parent.type == syms.arglist: + commas += 1 + break + if commas > 1: self.leaves.pop() return True @@ -547,8 +562,8 @@ class Line: appended will appear "too long" when splitting. """ if not ( - comment.type == STANDALONE_COMMENT and - self.bracket_tracker.any_open_brackets() + comment.type == STANDALONE_COMMENT + and self.bracket_tracker.any_open_brackets() ): return False @@ -605,11 +620,13 @@ class EmptyLineTracker: """Provides a stateful method that returns the number of potential extra empty lines needed before and after the currently processed line. - Note: this tracker works on lines that haven't been split yet. + Note: this tracker works on lines that haven't been split yet. It assumes + the prefix of the first leaf consists of optional newlines. Those newlines + are consumed by `maybe_empty_lines()` and included in the computation. """ - previous_line: Optional[Line] = attrib(default=None) - previous_after: int = attrib(default=0) - previous_defs: List[int] = attrib(default=Factory(list)) + previous_line: Optional[Line] = None + previous_after: int = 0 + previous_defs: List[int] = Factory(list) def maybe_empty_lines(self, current_line: Line) -> Tuple[int, int]: """Returns the number of extra empty lines before and after the `current_line`. @@ -618,17 +635,28 @@ class EmptyLineTracker: (two on module-level), as well as providing an extra empty line after flow control keywords to make them more prominent. """ + if current_line.is_comment: + # Don't count standalone comments towards previous empty lines. + return 0, 0 + before, after = self._maybe_empty_lines(current_line) + before -= self.previous_after self.previous_after = after self.previous_line = current_line return before, after def _maybe_empty_lines(self, current_line: Line) -> Tuple[int, int]: - before = 0 + if current_line.leaves: + # Consume the first leaf's extra newlines. + first_leaf = current_line.leaves[0] + before = int('\n' in first_leaf.prefix) + first_leaf.prefix = '' + else: + before = 0 depth = current_line.depth while self.previous_defs and self.previous_defs[-1] >= depth: self.previous_defs.pop() - before = (1 if depth else 2) - self.previous_after + before = 1 if depth else 2 is_decorator = current_line.is_decorator if is_decorator or current_line.is_def or current_line.is_class: if not is_decorator: @@ -644,24 +672,23 @@ class EmptyLineTracker: newlines = 2 if current_line.depth: newlines -= 1 - newlines -= self.previous_after return newlines, 0 if current_line.is_flow_control: return before, 1 if ( - self.previous_line and - self.previous_line.is_import and - not current_line.is_import and - depth == self.previous_line.depth + self.previous_line + and self.previous_line.is_import + and not current_line.is_import + and depth == self.previous_line.depth ): return (before or 1), 0 if ( - self.previous_line and - self.previous_line.is_yield and - (not current_line.is_yield or depth != self.previous_line.depth) + self.previous_line + and self.previous_line.is_yield + and (not current_line.is_yield or depth != self.previous_line.depth) ): return (before or 1), 0 @@ -675,8 +702,8 @@ class LineGenerator(Visitor[Line]): Note: destroys the tree it's visiting by mutating prefixes of its leaves in ways that will no longer stringify to valid Python code on the tree. """ - current_line: Line = attrib(default=Factory(Line)) - standalone_comments: List[Leaf] = attrib(default=Factory(list)) + current_line: Line = Factory(Line) + standalone_comments: List[Leaf] = Factory(list) def line(self, indent: int = 0) -> Iterator[Line]: """Generate a line. @@ -804,9 +831,10 @@ BRACKET = {token.LPAR: token.RPAR, token.LSQB: token.RSQB, token.LBRACE: token.R OPENING_BRACKETS = set(BRACKET.keys()) CLOSING_BRACKETS = set(BRACKET.values()) BRACKETS = OPENING_BRACKETS | CLOSING_BRACKETS +ALWAYS_NO_SPACE = CLOSING_BRACKETS | {token.COMMA, token.COLON, STANDALONE_COMMENT} -def whitespace(leaf: Leaf) -> str: +def whitespace(leaf: Leaf) -> str: # noqa C901 """Return whitespace prefix if needed for the given `leaf`.""" NO = '' SPACE = ' ' @@ -814,24 +842,12 @@ def whitespace(leaf: Leaf) -> str: t = leaf.type p = leaf.parent v = leaf.value - if t == token.COLON: - return NO - - if t == token.COMMA: - return NO - - if t == token.RPAR: + if t in ALWAYS_NO_SPACE: return NO if t == token.COMMENT: return DOUBLESPACE - if t == STANDALONE_COMMENT: - return NO - - if t in CLOSING_BRACKETS: - return NO - assert p is not None, f"INTERNAL ERROR: hand-made leaf without parent: {leaf!r}" prev = leaf.prev_sibling if not prev: @@ -863,7 +879,7 @@ def whitespace(leaf: Leaf) -> str: if prevp.parent and prevp.parent.type == syms.subscript: return NO - elif prevp.parent and prevp.parent.type == syms.factor: + elif prevp.parent and prevp.parent.type in {syms.factor, syms.star_expr}: return NO elif prev.type in OPENING_BRACKETS: @@ -960,7 +976,14 @@ def whitespace(leaf: Leaf) -> str: elif p.type == syms.subscript: # indexing - if not prev or prev.type == token.COLON: + if not prev: + assert p.parent is not None, "subscripts are always parented" + if p.parent.type == syms.subscriptlist: + return SPACE + + return NO + + elif prev.type == token.COLON: return NO elif p.type == syms.atom: @@ -969,9 +992,9 @@ def whitespace(leaf: Leaf) -> str: return NO elif ( - p.type == syms.listmaker or - p.type == syms.testlist_gexp or - p.type == syms.subscriptlist + p.type == syms.listmaker + or p.type == syms.testlist_gexp + or p.type == syms.subscriptlist ): # list interior, including unpacking if not prev: @@ -985,7 +1008,7 @@ def whitespace(leaf: Leaf) -> str: if prev.type == token.DOUBLESTAR: return NO - elif p.type == syms.factor or p.type == syms.star_expr: + elif p.type in {syms.factor, syms.star_expr}: # unary ops if not prev: prevp = preceding_leaf(p) @@ -1049,16 +1072,13 @@ def is_delimiter(leaf: Leaf) -> int: if leaf.type == token.COMMA: return COMMA_PRIORITY - if leaf.type == token.NAME and leaf.value in LOGIC_OPERATORS: - return LOGIC_PRIORITY - if leaf.type in COMPARATORS: return COMPARATOR_PRIORITY if ( - leaf.type in MATH_OPERATORS and - leaf.parent and - leaf.parent.type not in {syms.factor, syms.star_expr} + leaf.type in MATH_OPERATORS + and leaf.parent + and leaf.parent.type not in {syms.factor, syms.star_expr} ): return MATH_PRIORITY @@ -1095,7 +1115,7 @@ def generate_comments(leaf: Leaf) -> Iterator[Leaf]: if content and (content[0] not in {' ', '!', '#'}): content = ' ' + content is_standalone_comment = ( - '\n' in before_comment or '\n' in content or leaf.type == token.DEDENT + '\n' in before_comment or '\n' in content or leaf.type == token.ENDMARKER ) if not is_standalone_comment: # simple trailing comment @@ -1178,11 +1198,11 @@ def left_hand_split(line: Line, py36: bool = False) -> Iterator[Line]: matching_bracket = None for leaf in line.leaves: if ( - current_leaves is body_leaves and - leaf.type in CLOSING_BRACKETS and - leaf.opening_bracket is matching_bracket # type: ignore + current_leaves is body_leaves + and leaf.type in CLOSING_BRACKETS + and leaf.opening_bracket is matching_bracket ): - current_leaves = tail_leaves + current_leaves = tail_leaves if body_leaves else head_leaves current_leaves.append(leaf) if current_leaves is head_leaves: if leaf.type in OPENING_BRACKETS: @@ -1200,18 +1220,7 @@ def left_hand_split(line: Line, py36: bool = False) -> Iterator[Line]: comment_after = line.comments.get(id(leaf)) if comment_after: result.append(comment_after, preformatted=True) - # Check if the split succeeded. - tail_len = len(str(tail)) - if not body: - if tail_len == 0: - raise CannotSplit("Splitting brackets produced the same line") - - elif tail_len < 3: - raise CannotSplit( - f"Splitting brackets on an empty body to save " - f"{tail_len} characters is not worth it" - ) - + split_succeeded_or_raise(head, body, tail) for result in (head, body, tail): if result: yield result @@ -1230,11 +1239,11 @@ def right_hand_split(line: Line, py36: bool = False) -> Iterator[Line]: for leaf in reversed(line.leaves): if current_leaves is body_leaves: if leaf is opening_bracket: - current_leaves = head_leaves + current_leaves = head_leaves if body_leaves else tail_leaves current_leaves.append(leaf) if current_leaves is tail_leaves: if leaf.type in CLOSING_BRACKETS: - opening_bracket = leaf.opening_bracket # type: ignore + opening_bracket = leaf.opening_bracket current_leaves = body_leaves tail_leaves.reverse() body_leaves.reverse() @@ -1251,8 +1260,14 @@ def right_hand_split(line: Line, py36: bool = False) -> Iterator[Line]: comment_after = line.comments.get(id(leaf)) if comment_after: result.append(comment_after, preformatted=True) - # Check if the split succeeded. - tail_len = len(str(tail).strip('\n')) + split_succeeded_or_raise(head, body, tail) + for result in (head, body, tail): + if result: + yield result + + +def split_succeeded_or_raise(head: Line, body: Line, tail: Line) -> None: + tail_len = len(str(tail).strip()) if not body: if tail_len == 0: raise CannotSplit("Splitting brackets produced the same line") @@ -1263,10 +1278,6 @@ def right_hand_split(line: Line, py36: bool = False) -> Iterator[Line]: f"{tail_len} characters is not worth it" ) - for result in (head, body, tail): - if result: - yield result - def delimiter_split(line: Line, py36: bool = False) -> Iterator[Line]: """Split according to delimiters of the highest priority. @@ -1296,9 +1307,9 @@ def delimiter_split(line: Line, py36: bool = False) -> Iterator[Line]: current_line.append(comment_after, preformatted=True) lowest_depth = min(lowest_depth, leaf.bracket_depth) if ( - leaf.bracket_depth == lowest_depth and # type: ignore - leaf.type == token.STAR or - leaf.type == token.DOUBLESTAR + leaf.bracket_depth == lowest_depth + and leaf.type == token.STAR + or leaf.type == token.DOUBLESTAR ): trailing_comma_safe = trailing_comma_safe and py36 leaf_priority = delimiters.get(id(leaf)) @@ -1309,9 +1320,9 @@ def delimiter_split(line: Line, py36: bool = False) -> Iterator[Line]: current_line = Line(depth=line.depth, inside_brackets=line.inside_brackets) if current_line: if ( - delimiter_priority == COMMA_PRIORITY and - current_line.leaves[-1].type != token.COMMA and - trailing_comma_safe + delimiter_priority == COMMA_PRIORITY + and current_line.leaves[-1].type != token.COMMA + and trailing_comma_safe ): current_line.append(Leaf(token.COMMA, ',')) normalize_prefix(current_line.leaves[0]) @@ -1324,10 +1335,10 @@ def is_import(leaf: Leaf) -> bool: t = leaf.type v = leaf.value return bool( - t == token.NAME and - ( - (v == 'import' and p and p.type == syms.import_name) or - (v == 'from' and p and p.type == syms.import_from) + t == token.NAME + and ( + (v == 'import' and p and p.type == syms.import_name) + or (v == 'from' and p and p.type == syms.import_from) ) ) @@ -1337,9 +1348,6 @@ def normalize_prefix(leaf: Leaf) -> None: if is_import(leaf): spl = leaf.prefix.split('#', 1) nl_count = spl[0].count('\n') - if len(spl) > 1: - # Skip one newline since it was for a standalone comment. - nl_count -= 1 leaf.prefix = '\n' * nl_count return @@ -1355,14 +1363,14 @@ def is_python36(node: Node) -> bool: """ for n in node.pre_order(): if n.type == token.STRING: - assert isinstance(n, Leaf) - if n.value[:2] in {'f"', 'F"', "f'", "F'", 'rf', 'fr', 'RF', 'FR'}: + value_head = n.value[:2] # type: ignore + if value_head in {'f"', 'F"', "f'", "F'", 'rf', 'fr', 'RF', 'FR'}: return True elif ( - n.type == syms.typedargslist and - n.children and - n.children[-1].type == token.COMMA + n.type == syms.typedargslist + and n.children + and n.children[-1].type == token.COMMA ): for ch in n.children: if ch.type == token.STAR or ch.type == token.DOUBLESTAR: @@ -1392,9 +1400,9 @@ def gen_python_files_in_dir(path: Path) -> Iterator[Path]: @dataclass class Report: """Provides a reformatting counter.""" - change_count: int = attrib(default=0) - same_count: int = attrib(default=0) - failure_count: int = attrib(default=0) + change_count: int = 0 + same_count: int = 0 + failure_count: int = 0 def done(self, src: Path, changed: bool) -> None: """Increment the counter for successful reformatting. Write out a message.""" @@ -1493,7 +1501,7 @@ def assert_equivalent(src: str, dst: str) -> None: raise AssertionError( f"INTERNAL ERROR: Black produced invalid code: {exc}. " f"Please report a bug on https://github.com/ambv/black/issues. " - f"This invalid output might be helpful: {log}", + f"This invalid output might be helpful: {log}" ) from None src_ast_str = '\n'.join(_v(src_ast)) @@ -1504,7 +1512,7 @@ def assert_equivalent(src: str, dst: str) -> None: f"INTERNAL ERROR: Black produced code that is not equivalent to " f"the source. " f"Please report a bug on https://github.com/ambv/black/issues. " - f"This diff might be helpful: {log}", + f"This diff might be helpful: {log}" ) from None @@ -1523,7 +1531,7 @@ def assert_stable(src: str, dst: str, line_length: int) -> None: f"INTERNAL ERROR: Black produced different code on the second pass " f"of the formatter. " f"Please report a bug on https://github.com/ambv/black/issues. " - f"This diff might be helpful: {log}", + f"This diff might be helpful: {log}" ) from None