From 59c9ebb0a76cc6132ee6d3b08e21086f2913dab6 Mon Sep 17 00:00:00 2001 From: Vlad Emelianov Date: Tue, 19 Nov 2019 04:55:33 +0100 Subject: [PATCH 01/30] Fix tuple closing leaf lookup --- black.py | 22 ++++++++++++++-------- tests/data/remove_parens.py | 28 ++++++++++++++++++++++++++++ 2 files changed, 42 insertions(+), 8 deletions(-) diff --git a/black.py b/black.py index 68c0052fdfe..8d492b58d2e 100644 --- a/black.py +++ b/black.py @@ -1272,17 +1272,23 @@ def is_collection_with_optional_trailing_comma(self) -> bool: Note that the trailing comma in a 1-tuple is not optional. """ + if self.inside_brackets: + return False + if not self.leaves or len(self.leaves) < 4: return False - # Look for and address a trailing colon. - if self.leaves[-1].type == token.COLON: - closer = self.leaves[-2] - close_index = -2 - else: - closer = self.leaves[-1] - close_index = -1 - if closer.type not in CLOSING_BRACKETS or self.inside_brackets: + # Look for and address a visible trailing colon. + # ignore trailing colon + close_index = len(self.leaves) - 1 + closer = self.leaves[close_index] + while closer.type == token.COLON or closer.value == "": + close_index = close_index - 1 + if close_index == 0: + return False + closer = self.leaves[close_index] + + if closer.type not in CLOSING_BRACKETS: return False if closer.type == token.RPAR: diff --git a/tests/data/remove_parens.py b/tests/data/remove_parens.py index afc34010c30..c1a252c02d9 100644 --- a/tests/data/remove_parens.py +++ b/tests/data/remove_parens.py @@ -55,6 +55,19 @@ def example8(): return (((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((None))))))))))))))))))))))))))))))))))))))))))))))))))))))))))))))))))))))) + +def example_tuple(): + return ((((1, 2, 3)))) + + +def example_tuple_trailing_comma(): + return (((1, 2, 3,))) + + +def example_list(): + return ((([1, 2, 3]))) + + # output x = 1 x = 1.2 @@ -142,3 +155,18 @@ def example7(): def example8(): return None + +def example_tuple(): + return (1, 2, 3) + + +def example_tuple_trailing_comma(): + return ( + 1, + 2, + 3, + ) + + +def example_list(): + return [1, 2, 3] \ No newline at end of file From 81893919f0e5f9ad441b562a3b237898f891f35b Mon Sep 17 00:00:00 2001 From: Vlad Emelianov Date: Tue, 19 Nov 2019 05:19:36 +0100 Subject: [PATCH 02/30] Fix behavior for lines with only invisible parentheses --- black.py | 30 ++++++++++++++++++++++++------ tests/data/remove_parens.py | 3 +-- 2 files changed, 25 insertions(+), 8 deletions(-) diff --git a/black.py b/black.py index 8d492b58d2e..aba6786f046 100644 --- a/black.py +++ b/black.py @@ -1278,19 +1278,37 @@ def is_collection_with_optional_trailing_comma(self) -> bool: if not self.leaves or len(self.leaves) < 4: return False - # Look for and address a visible trailing colon. - # ignore trailing colon + # Look for and address a trailing colon. close_index = len(self.leaves) - 1 closer = self.leaves[close_index] - while closer.type == token.COLON or closer.value == "": - close_index = close_index - 1 - if close_index == 0: - return False + + # ignore trailing colon + if closer.type == token.COLON: + close_index -= 1 closer = self.leaves[close_index] if closer.type not in CLOSING_BRACKETS: return False + # try to find the last visible closer + # if it exists - use it as a closer + if closer.value == "": + visible_close_index = close_index + while visible_close_index > 0: + visible_close_index -= 1 + visible_closer = self.leaves[visible_close_index] + if visible_closer.type not in CLOSING_BRACKETS: + break + if visible_closer.value == "": + continue + + closer = visible_closer + close_index = visible_close_index + break + + if closer.type not in CLOSING_BRACKETS: + return False + if closer.type == token.RPAR: # Tuples require an extra check, because if there's only # one element in the tuple removing the comma unmakes the diff --git a/tests/data/remove_parens.py b/tests/data/remove_parens.py index c1a252c02d9..57327bd548e 100644 --- a/tests/data/remove_parens.py +++ b/tests/data/remove_parens.py @@ -55,7 +55,6 @@ def example8(): return (((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((None))))))))))))))))))))))))))))))))))))))))))))))))))))))))))))))))))))))) - def example_tuple(): return ((((1, 2, 3)))) @@ -169,4 +168,4 @@ def example_tuple_trailing_comma(): def example_list(): - return [1, 2, 3] \ No newline at end of file + return [1, 2, 3] From c924ba3790b6a45757a37bd42e2155eda61b63b4 Mon Sep 17 00:00:00 2001 From: Vlad Emelianov Date: Tue, 19 Nov 2019 19:49:27 +0100 Subject: [PATCH 03/30] WIP: new is_collection_with_optional_trailing_comma logic --- black.py | 116 ++++++++++++++++++++++++++----------------------------- 1 file changed, 55 insertions(+), 61 deletions(-) diff --git a/black.py b/black.py index aba6786f046..68e8dd7b5a3 100644 --- a/black.py +++ b/black.py @@ -1270,85 +1270,79 @@ def is_stub_class(self) -> bool: def is_collection_with_optional_trailing_comma(self) -> bool: """Is this line a collection literal with a trailing comma that's optional? - Note that the trailing comma in a 1-tuple is not optional. + Note that the trailing comma in a 1-tuple and a 1-subscript is not optional. """ + if self.is_def or self.is_import: + return False + if self.inside_brackets: return False if not self.leaves or len(self.leaves) < 4: return False - # Look for and address a trailing colon. - close_index = len(self.leaves) - 1 - closer = self.leaves[close_index] + # make sure that we have only one top-level collection + opener: Optional[Leaf] = None + closer: Optional[Leaf] = None + opener_index: int = 0 + closer_index: int = 0 + comma_indexes: List[int] = [] + depth_counter = 0 + for leaf_index, leaf in enumerate(self.leaves): + # use comma indexes only on in the top-level collection + if depth_counter == 1 and leaf.type == token.COMMA: + comma_indexes.append(leaf_index) + if leaf.type in OPENING_BRACKETS: + # we have more that one top-level collection, abort + if depth_counter == 0 and opener is not None and opener.value: + return False - # ignore trailing colon - if closer.type == token.COLON: - close_index -= 1 - closer = self.leaves[close_index] + # do not increase depth in bracket is invisible + if leaf.value: + depth_counter += 1 - if closer.type not in CLOSING_BRACKETS: - return False + # visible opener already found, skip the rest + if opener and opener.value: + continue - # try to find the last visible closer - # if it exists - use it as a closer - if closer.value == "": - visible_close_index = close_index - while visible_close_index > 0: - visible_close_index -= 1 - visible_closer = self.leaves[visible_close_index] - if visible_closer.type not in CLOSING_BRACKETS: - break - if visible_closer.value == "": + # this opener is not right after the previous one, skip it + if opener and leaf_index > opener_index + 1: continue - closer = visible_closer - close_index = visible_close_index - break + opener_index = leaf_index + opener = leaf + if leaf.type in CLOSING_BRACKETS: + # do not decrease depth in bracket is invisible + if leaf.value: + depth_counter -= 1 + + # brackets are not valid, abort just in case + if depth_counter < 0: + return False - if closer.type not in CLOSING_BRACKETS: + # visible closer already found, skip invisible + if closer and not leaf.value and leaf_index == closer_index + 1: + continue + + closer = leaf + closer_index = leaf_index + + # no brackets found - abort + if opener is None or closer is None: return False - if closer.type == token.RPAR: - # Tuples require an extra check, because if there's only - # one element in the tuple removing the comma unmakes the - # tuple. - # - # We also check for parens before looking for the trailing - # comma because in some cases (eg assigning a dict - # literal) the literal gets wrapped in temporary parens - # during parsing. This case is covered by the - # collections.py test data. - opener = closer.opening_bracket - for _open_index, leaf in enumerate(self.leaves): - if leaf is opener: - break + # no commas found in collection - abort + if not comma_indexes: + return False - else: - # Couldn't find the matching opening paren, play it safe. - return False + # 1-item tuple - abort + if opener.type == token.LPAR and len(comma_indexes) == 1: + return False - commas = 0 - comma_depth = self.leaves[close_index - 1].bracket_depth - for leaf in self.leaves[_open_index + 1 : close_index]: - if leaf.bracket_depth == comma_depth and leaf.type == token.COMMA: - commas += 1 - if commas > 1: - # We haven't looked yet for the trailing comma because - # we might also have caught noop parens. - return self.leaves[close_index - 1].type == token.COMMA - - elif commas == 1: - return False # it's either a one-tuple or didn't have a trailing comma - - if self.leaves[close_index - 1].type in CLOSING_BRACKETS: - close_index -= 1 - closer = self.leaves[close_index] - if closer.type == token.RPAR: - # TODO: this is a gut feeling. Will we ever see this? - return False + last_comma_index = comma_indexes[-1] - if self.leaves[close_index - 1].type != token.COMMA: + # last comma is not just before closing bracket - abort + if last_comma_index != closer_index - 1: return False return True From 0dd5a0d4563de88815b094eff16989d38a88f9f5 Mon Sep 17 00:00:00 2001 From: Vlad Emelianov Date: Tue, 19 Nov 2019 21:08:47 +0100 Subject: [PATCH 04/30] Fix top-level collection check and function defs --- black.py | 5 +---- tests/data/collections.py | 24 ++++++++++++++++++++++++ tests/data/function.py | 4 +++- tests/data/function2.py | 4 +++- 4 files changed, 31 insertions(+), 6 deletions(-) diff --git a/black.py b/black.py index 68e8dd7b5a3..a6ea6e106d2 100644 --- a/black.py +++ b/black.py @@ -1272,9 +1272,6 @@ def is_collection_with_optional_trailing_comma(self) -> bool: Note that the trailing comma in a 1-tuple and a 1-subscript is not optional. """ - if self.is_def or self.is_import: - return False - if self.inside_brackets: return False @@ -1290,7 +1287,7 @@ def is_collection_with_optional_trailing_comma(self) -> bool: depth_counter = 0 for leaf_index, leaf in enumerate(self.leaves): # use comma indexes only on in the top-level collection - if depth_counter == 1 and leaf.type == token.COMMA: + if depth_counter < 2 and leaf.type == token.COMMA: comma_indexes.append(leaf_index) if leaf.type in OPENING_BRACKETS: # we have more that one top-level collection, abort diff --git a/tests/data/collections.py b/tests/data/collections.py index ebe8d3c5200..c821099a4c5 100644 --- a/tests/data/collections.py +++ b/tests/data/collections.py @@ -50,6 +50,15 @@ division_result_tuple = (6/2,) print("foo %r", (foo.bar,)) +subscript_one_item[1] +subscript_multiple_items[1, 2, {3: 4}] +subscript_one_item = subscript_one_item[1] +subscript_multiple_items = subscript_multiple_items[1, 2, {3: 4}] +subscript_one_item[1,] +subscript_multiple_items[1, 2, {3: 4},] +subscript_one_item = subscript_one_item[1,] +subscript_multiple_items = subscript_multiple_items[1, 2, {3: 4},] + if True: IGNORED_TYPES_FOR_ATTRIBUTE_CHECKING = ( Config.IGNORED_TYPES_FOR_ATTRIBUTE_CHECKING @@ -143,6 +152,21 @@ division_result_tuple = (6 / 2,) print("foo %r", (foo.bar,)) +subscript_one_item[1] +subscript_multiple_items[1, 2, {3: 4}] +subscript_one_item = subscript_one_item[1] +subscript_multiple_items = subscript_multiple_items[1, 2, {3: 4}] +subscript_one_item[ + 1, +] +subscript_multiple_items[ + 1, 2, {3: 4}, +] +subscript_one_item = subscript_one_item[1,] +subscript_multiple_items = subscript_multiple_items[ + 1, 2, {3: 4}, +] + if True: IGNORED_TYPES_FOR_ATTRIBUTE_CHECKING = ( Config.IGNORED_TYPES_FOR_ATTRIBUTE_CHECKING diff --git a/tests/data/function.py b/tests/data/function.py index 51234a1e9b4..d4f8274af62 100644 --- a/tests/data/function.py +++ b/tests/data/function.py @@ -230,7 +230,9 @@ def trailing_comma(): } -def f(a, **kwargs,) -> A: +def f( + a, **kwargs, +) -> A: return ( yield from A( very_long_argument_name1=very_long_value_for_the_argument, diff --git a/tests/data/function2.py b/tests/data/function2.py index a6773d429cd..0520f73de9c 100644 --- a/tests/data/function2.py +++ b/tests/data/function2.py @@ -25,7 +25,9 @@ def inner(): # output -def f(a, **kwargs,) -> A: +def f( + a, **kwargs, +) -> A: with cache_dir(): if something: result = CliRunner().invoke( From e8349214bb9390988f6e5b94ac9d63543da46333 Mon Sep 17 00:00:00 2001 From: Vlad Emelianov Date: Tue, 19 Nov 2019 21:20:13 +0100 Subject: [PATCH 05/30] Rewrite comments --- black.py | 12 ++++++------ 1 file changed, 6 insertions(+), 6 deletions(-) diff --git a/black.py b/black.py index a6ea6e106d2..3902983e5f1 100644 --- a/black.py +++ b/black.py @@ -1290,7 +1290,7 @@ def is_collection_with_optional_trailing_comma(self) -> bool: if depth_counter < 2 and leaf.type == token.COMMA: comma_indexes.append(leaf_index) if leaf.type in OPENING_BRACKETS: - # we have more that one top-level collection, abort + # more that one top-level collection if depth_counter == 0 and opener is not None and opener.value: return False @@ -1313,7 +1313,7 @@ def is_collection_with_optional_trailing_comma(self) -> bool: if leaf.value: depth_counter -= 1 - # brackets are not valid, abort just in case + # brackets are not valid if depth_counter < 0: return False @@ -1324,21 +1324,21 @@ def is_collection_with_optional_trailing_comma(self) -> bool: closer = leaf closer_index = leaf_index - # no brackets found - abort + # no brackets found if opener is None or closer is None: return False - # no commas found in collection - abort + # no commas found in collection if not comma_indexes: return False - # 1-item tuple - abort + # 1-item tuple if opener.type == token.LPAR and len(comma_indexes) == 1: return False last_comma_index = comma_indexes[-1] - # last comma is not just before closing bracket - abort + # last comma is not just before closing bracket if last_comma_index != closer_index - 1: return False From 5cf899afbd4029d135df49358f3ad44ca716bfd0 Mon Sep 17 00:00:00 2001 From: Vlad Emelianov Date: Tue, 19 Nov 2019 21:59:36 +0100 Subject: [PATCH 06/30] Use LeafID instead of int type --- black.py | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/black.py b/black.py index 3902983e5f1..0511cdb9dd2 100644 --- a/black.py +++ b/black.py @@ -1281,9 +1281,9 @@ def is_collection_with_optional_trailing_comma(self) -> bool: # make sure that we have only one top-level collection opener: Optional[Leaf] = None closer: Optional[Leaf] = None - opener_index: int = 0 - closer_index: int = 0 - comma_indexes: List[int] = [] + opener_index: LeafID = 0 + closer_index: LeafID = 0 + comma_indexes: List[LeafID] = [] depth_counter = 0 for leaf_index, leaf in enumerate(self.leaves): # use comma indexes only on in the top-level collection From f4eb91d6f1b7b55318ac24a48c1556a860e38539 Mon Sep 17 00:00:00 2001 From: Vlad Emelianov Date: Wed, 20 Nov 2019 01:01:05 +0100 Subject: [PATCH 07/30] Add separate handling for LHS and RHS --- black.py | 279 +++++++++++++++++++++++++++++++++++++++++-------------- 1 file changed, 208 insertions(+), 71 deletions(-) diff --git a/black.py b/black.py index 0511cdb9dd2..9bb387f430e 100644 --- a/black.py +++ b/black.py @@ -72,7 +72,7 @@ Priority = int Index = int LN = Union[Leaf, Node] -SplitFunc = Callable[["Line", Collection["Feature"]], Iterator["Line"]] +SplitFunc = Callable[["Line", Collection["Feature"], int], Iterator["Line"]] Timestamp = float FileSize = int CacheInfo = Tuple[Timestamp, FileSize] @@ -525,6 +525,7 @@ def reformat_one( write_cache(cache, [src], mode) report.done(src, changed) except Exception as exc: + raise report.failed(src, str(exc)) @@ -1266,17 +1267,84 @@ def is_stub_class(self) -> bool: Leaf(token.DOT, ".") for _ in range(3) ] - @property - def is_collection_with_optional_trailing_comma(self) -> bool: + def get_assignment_leaf_id(self) -> Optional[LeafID]: + depth = 0 + for leaf_id, leaf in enumerate(self.leaves): + if leaf.type in OPENING_BRACKETS: + depth += 1 + if leaf.type in CLOSING_BRACKETS: + depth -= 1 + if leaf.value in ASSIGNMENTS and depth == 0: + return leaf_id + return None + + def get_left_hand_side(self) -> Optional["Line"]: + if self.is_def: + return self + + if self.contains_uncollapsable_type_comments(): + return None + + if self.inside_brackets: + return None + + assignment_leaf_id = self.get_assignment_leaf_id() + if assignment_leaf_id is None: + return None + + leaves = self.leaves[:assignment_leaf_id] + comments: Dict[LeafID, List[Leaf]] = {} + for comment_leaf_id, comment_leaves in self.comments.items(): + if comment_leaf_id < assignment_leaf_id: + comments[comment_leaf_id] = comment_leaves + + return Line( + depth=self.depth, + leaves=leaves, + comments=comments, + inside_brackets=self.inside_brackets, + should_explode=self.should_explode, + ) + + def get_right_hand_side(self) -> Optional["Line"]: + if self.is_def: + return None + + if self.contains_uncollapsable_type_comments(): + return self + + if self.inside_brackets: + return self + + assignment_leaf_id = self.get_assignment_leaf_id() + if assignment_leaf_id is None: + return self + + leaves = self.leaves[assignment_leaf_id:] + comments: Dict[LeafID, List[Leaf]] = {} + for comment_leaf_id, comment_leaves in self.comments.items(): + if comment_leaf_id >= assignment_leaf_id: + comments[comment_leaf_id] = comment_leaves + + return Line( + depth=self.depth, + leaves=leaves, + comments=comments, + bracket_tracker=self.bracket_tracker, + inside_brackets=self.inside_brackets, + should_explode=self.should_explode, + ) + + def get_optional_trailing_comma_leaf_id(self) -> Optional[LeafID]: """Is this line a collection literal with a trailing comma that's optional? Note that the trailing comma in a 1-tuple and a 1-subscript is not optional. """ if self.inside_brackets: - return False + return None if not self.leaves or len(self.leaves) < 4: - return False + return None # make sure that we have only one top-level collection opener: Optional[Leaf] = None @@ -1292,7 +1360,7 @@ def is_collection_with_optional_trailing_comma(self) -> bool: if leaf.type in OPENING_BRACKETS: # more that one top-level collection if depth_counter == 0 and opener is not None and opener.value: - return False + return None # do not increase depth in bracket is invisible if leaf.value: @@ -1315,7 +1383,7 @@ def is_collection_with_optional_trailing_comma(self) -> bool: # brackets are not valid if depth_counter < 0: - return False + return None # visible closer already found, skip invisible if closer and not leaf.value and leaf_index == closer_index + 1: @@ -1326,23 +1394,24 @@ def is_collection_with_optional_trailing_comma(self) -> bool: # no brackets found if opener is None or closer is None: - return False + return None # no commas found in collection if not comma_indexes: - return False + return None - # 1-item tuple + # 1-item tuple or subscript if opener.type == token.LPAR and len(comma_indexes) == 1: - return False + if not self.is_def and not self.is_import: + return None last_comma_index = comma_indexes[-1] # last comma is not just before closing bracket if last_comma_index != closer_index - 1: - return False + return None - return True + return last_comma_index @property def is_def(self) -> bool: @@ -1577,14 +1646,15 @@ def __str__(self) -> str: return "\n" indent = " " * self.depth - leaves = iter(self.leaves) - first = next(leaves) - res = f"{first.prefix}{indent}{first.value}" - for leaf in leaves: - res += str(leaf) + res = "" + for leaf_id, leaf in enumerate(self.leaves): + if leaf_id == 0: + res = f"{leaf.prefix}{indent}{leaf.value}" + continue + res = f"{res}{leaf}" for comment in itertools.chain.from_iterable(self.comments.values()): - res += str(comment) - return res + "\n" + res = f"{res}{comment}" + return f"{res}\n" def __bool__(self) -> bool: """Return True if the line has leaves or comments.""" @@ -2408,69 +2478,39 @@ def make_comment(content: str) -> str: return "#" + content -def split_line( +def split_line_side( line: Line, + first_line_length: int, line_length: int, inner: bool = False, features: Collection[Feature] = (), + split_funcs: Collection[SplitFunc] = (), ) -> Iterator[Line]: - """Split a `line` into potentially many lines. - - They should fit in the allotted `line_length` but might not be able to. - `inner` signifies that there were a pair of brackets somewhere around the - current `line`, possibly transitively. This means we can fallback to splitting - by delimiters if the LHS/RHS don't yield any results. - - `features` are syntactical features that may be used in the output. - """ - if line.is_comment: - yield line - return + optional_trailing_comma_leaf_id = line.get_optional_trailing_comma_leaf_id() + if optional_trailing_comma_leaf_id and line.is_def: + line.leaves[optional_trailing_comma_leaf_id].value = "" + optional_trailing_comma_leaf_id = None line_str = str(line).strip("\n") - if ( not line.contains_uncollapsable_type_comments() and not line.should_explode - and not line.is_collection_with_optional_trailing_comma + and optional_trailing_comma_leaf_id is None and ( - is_line_short_enough(line, line_length=line_length, line_str=line_str) + is_line_short_enough(line, line_str=line_str, line_length=first_line_length) or line.contains_unsplittable_type_ignore() ) ): yield line return - split_funcs: List[SplitFunc] - if line.is_def: - split_funcs = [left_hand_split] - else: - - def rhs(line: Line, features: Collection[Feature]) -> Iterator[Line]: - for omit in generate_trailers_to_omit(line, line_length): - lines = list(right_hand_split(line, line_length, features, omit=omit)) - if is_line_short_enough(lines[0], line_length=line_length): - yield from lines - return - - # All splits failed, best effort split with no omits. - # This mostly happens to multiline strings that are by definition - # reported as not fitting a single line. - # line_length=1 here was historically a bug that somehow became a feature. - # See #762 and #781 for the full story. - yield from right_hand_split(line, line_length=1, features=features) - - if line.inside_brackets: - split_funcs = [delimiter_split, standalone_comment_split, rhs] - else: - split_funcs = [rhs] for split_func in split_funcs: # We are accumulating lines in `result` because we might want to abort # mission and return the original line in the end, or attempt a different # split altogether. result: List[Line] = [] try: - for l in split_func(line, features): + for l in split_func(line, features=features, line_length=line_length): if str(l).strip("\n") == line_str: raise CannotSplit("Split function returned an unchanged result") @@ -2481,16 +2521,107 @@ def rhs(line: Line, features: Collection[Feature]) -> Iterator[Line]: ) except CannotSplit: continue + yield from result + return + yield line - else: - yield from result - break - else: +def right_hand_split_with_omit( + line: Line, features: Collection[Feature], line_length: int = 999 +) -> Iterator[Line]: + for omit in generate_trailers_to_omit(line, line_length): + lines = list( + right_hand_split( + line, features=features, line_length=line_length, omit=omit + ) + ) + if is_line_short_enough(lines[0], line_length=line_length): + yield from lines + return + + # All splits failed, best effort split with no omits. + # This mostly happens to multiline strings that are by definition + # reported as not fitting a single line. + # line_length=1 here was historically a bug that somehow became a feature. + # See #762 and #781 for the full story. + yield from right_hand_split(line, features=features, line_length=1) + + +def split_line( + line: Line, + line_length: int, + inner: bool = False, + features: Collection[Feature] = (), +) -> Iterator[Line]: + """Split a `line` into potentially many lines. + + They should fit in the allotted `line_length` but might not be able to. + `inner` signifies that there were a pair of brackets somewhere around the + current `line`, possibly transitively. This means we can fallback to splitting + by delimiters if the LHS/RHS don't yield any results. + + `features` are syntactical features that may be used in the output. + """ + if line.is_comment: yield line + return + left_hand_side = line.get_left_hand_side() + right_hand_side = line.get_right_hand_side() + # print('line', str(line)) + # print('left_hand_side', left_hand_side and left_hand_side.leaves) + # print('right_hand_side', right_hand_side and right_hand_side.leaves) + + split_funcs: List[SplitFunc] = [left_hand_split] + if line.inside_brackets: + split_funcs = [delimiter_split, standalone_comment_split, left_hand_split] + + result: List[Line] = [] + if left_hand_side: + for left_hand_side_line in split_line_side( + line=left_hand_side, + line_length=line_length, + first_line_length=line_length, + inner=inner, + features=features, + split_funcs=split_funcs, + ): + result.append(left_hand_side_line) -def left_hand_split(line: Line, features: Collection[Feature] = ()) -> Iterator[Line]: + if right_hand_side: + first_line_length = line_length + if result: + first_line_length = line_length - len(str(result[-1]).rstrip("\n").lstrip()) + split_funcs = [right_hand_split_with_omit] + if line.inside_brackets: + split_funcs = [ + delimiter_split, + standalone_comment_split, + right_hand_split_with_omit, + ] + for line_index, right_hand_side_line in enumerate( + split_line_side( + line=right_hand_side, + line_length=line_length, + first_line_length=first_line_length, + inner=inner, + features=features, + split_funcs=split_funcs, + ) + ): + if line_index == 0 and result: + result[-1].leaves.extend(right_hand_side_line.leaves) + result[-1].comments.update(right_hand_side_line.comments) + continue + result.append(right_hand_side_line) + + for line in result: + yield line + + +def left_hand_split( + line: Line, features: Collection[Feature], line_length: int, +) -> Iterator[Line]: """Split line into many lines, starting with the first matching bracket pair. Note: this usually looks weird, only use this for function definitions. @@ -2528,8 +2659,8 @@ def left_hand_split(line: Line, features: Collection[Feature] = ()) -> Iterator[ def right_hand_split( line: Line, + features: Collection[Feature], line_length: int, - features: Collection[Feature] = (), omit: Collection[LeafID] = (), ) -> Iterator[Line]: """Split line into many lines, starting with the last matching bracket pair. @@ -2588,7 +2719,9 @@ def right_hand_split( ): omit = {id(closing_bracket), *omit} try: - yield from right_hand_split(line, line_length, features=features, omit=omit) + yield from right_hand_split( + line, features=features, line_length=line_length, omit=omit + ) return except CannotSplit: @@ -2690,8 +2823,10 @@ def dont_increase_indentation(split_func: SplitFunc) -> SplitFunc: """ @wraps(split_func) - def split_wrapper(line: Line, features: Collection[Feature] = ()) -> Iterator[Line]: - for l in split_func(line, features): + def split_wrapper( + line: Line, features: Collection[Feature], line_length: int = 999 + ) -> Iterator[Line]: + for l in split_func(line, features, line_length): normalize_prefix(l.leaves[0], inside_brackets=True) yield l @@ -2699,7 +2834,9 @@ def split_wrapper(line: Line, features: Collection[Feature] = ()) -> Iterator[Li @dont_increase_indentation -def delimiter_split(line: Line, features: Collection[Feature] = ()) -> Iterator[Line]: +def delimiter_split( + line: Line, features: Collection[Feature], _line_length: int = 999 +) -> Iterator[Line]: """Split according to delimiters of the highest priority. If the appropriate Features are given, the split will add trailing commas @@ -2770,7 +2907,7 @@ def append_to_line(leaf: Leaf) -> Iterator[Line]: @dont_increase_indentation def standalone_comment_split( - line: Line, features: Collection[Feature] = () + line: Line, features: Collection[Feature] = (), _line_length: int = 999, ) -> Iterator[Line]: """Split standalone comments from the rest of the line.""" if not line.contains_standalone_comments(0): From caa0f5ae1a2c6792a3698415b95a0b3a8347a071 Mon Sep 17 00:00:00 2001 From: Vlad Emelianov Date: Wed, 20 Nov 2019 03:26:25 +0100 Subject: [PATCH 08/30] Reverted some changes to be as close as possible to current behavior --- black.py | 64 ++++++++++++++++++++++++++++++++------------------------ 1 file changed, 37 insertions(+), 27 deletions(-) diff --git a/black.py b/black.py index 9bb387f430e..e56bb4ee651 100644 --- a/black.py +++ b/black.py @@ -525,7 +525,6 @@ def reformat_one( write_cache(cache, [src], mode) report.done(src, changed) except Exception as exc: - raise report.failed(src, str(exc)) @@ -2510,13 +2509,22 @@ def split_line_side( # split altogether. result: List[Line] = [] try: - for l in split_func(line, features=features, line_length=line_length): - if str(l).strip("\n") == line_str: + for sub_line_index, sub_line in enumerate( + split_func(line, features=features, line_length=line_length) + ): + if str(sub_line).strip("\n") == line_str: raise CannotSplit("Split function returned an unchanged result") + sub_line_length = first_line_length + if sub_line_index > 0: + sub_line_length = line_length + result.extend( split_line( - l, line_length=line_length, inner=True, features=features + sub_line, + line_length=sub_line_length, + inner=True, + features=features, ) ) except CannotSplit: @@ -2530,14 +2538,17 @@ def right_hand_split_with_omit( line: Line, features: Collection[Feature], line_length: int = 999 ) -> Iterator[Line]: for omit in generate_trailers_to_omit(line, line_length): - lines = list( - right_hand_split( - line, features=features, line_length=line_length, omit=omit + try: + lines = list( + right_hand_split( + line, features=features, line_length=line_length, omit=omit + ) ) - ) - if is_line_short_enough(lines[0], line_length=line_length): - yield from lines - return + except CannotSplit: + continue + + yield from lines + return # All splits failed, best effort split with no omits. # This mostly happens to multiline strings that are by definition @@ -2591,7 +2602,7 @@ def split_line( if right_hand_side: first_line_length = line_length if result: - first_line_length = line_length - len(str(result[-1]).rstrip("\n").lstrip()) + first_line_length = max(line_length - len(str(result[-1]).rstrip("\n").lstrip()), 1) split_funcs = [right_hand_split_with_omit] if line.inside_brackets: split_funcs = [ @@ -2722,24 +2733,23 @@ def right_hand_split( yield from right_hand_split( line, features=features, line_length=line_length, omit=omit ) + except CannotSplit as e: + pass + else: return - except CannotSplit: - if not ( - can_be_split(body) - or is_line_short_enough(body, line_length=line_length) - ): - raise CannotSplit( - "Splitting failed, body is still too long and can't be split." - ) + if not can_be_split(body) and not is_line_short_enough(body, line_length=line_length): + raise CannotSplit( + "Splitting failed, body is still too long and can't be split.", + ) - elif head.contains_multiline_strings() or tail.contains_multiline_strings(): - raise CannotSplit( - "The current optional pair of parentheses is bound to fail to " - "satisfy the splitting algorithm because the head or the tail " - "contains multiline strings which by definition never fit one " - "line." - ) + if head.contains_multiline_strings() or tail.contains_multiline_strings(): + raise CannotSplit( + "The current optional pair of parentheses is bound to fail to " + "satisfy the splitting algorithm because the head or the tail " + "contains multiline strings which by definition never fit one " + "line." + ) ensure_visible(opening_bracket) ensure_visible(closing_bracket) From de02a616035d0485c3ffa65b1936c5d6f232a49b Mon Sep 17 00:00:00 2001 From: Vlad Emelianov Date: Wed, 20 Nov 2019 05:28:35 +0100 Subject: [PATCH 09/30] Final version --- black.py | 201 ++++++++++++++++++++------ tests/data/expression.diff | 20 +-- tests/data/expression.py | 8 +- tests/data/function.py | 4 +- tests/data/function2.py | 4 +- tests/data/function_trailing_comma.py | 4 +- tests/data/tupleassign.py | 11 +- 7 files changed, 168 insertions(+), 84 deletions(-) diff --git a/black.py b/black.py index e56bb4ee651..3d32d2404be 100644 --- a/black.py +++ b/black.py @@ -72,7 +72,7 @@ Priority = int Index = int LN = Union[Leaf, Node] -SplitFunc = Callable[["Line", Collection["Feature"], int], Iterator["Line"]] +SplitFunc = Callable[["Line", Collection["Feature"], int, int], Iterator["Line"]] Timestamp = float FileSize = int CacheInfo = Tuple[Timestamp, FileSize] @@ -1266,18 +1266,30 @@ def is_stub_class(self) -> bool: Leaf(token.DOT, ".") for _ in range(3) ] - def get_assignment_leaf_id(self) -> Optional[LeafID]: + def get_assignment_index(self) -> Optional[int]: + """ + Get fthe first unbracketed assignment index. + """ depth = 0 - for leaf_id, leaf in enumerate(self.leaves): + for leaf_index, leaf in enumerate(self.leaves): if leaf.type in OPENING_BRACKETS: depth += 1 if leaf.type in CLOSING_BRACKETS: depth -= 1 if leaf.value in ASSIGNMENTS and depth == 0: - return leaf_id + return leaf_index return None def get_left_hand_side(self) -> Optional["Line"]: + """ + Get left hand side of line or None. + + - function definition - full line is LHS-value + - if line is inside brackets - LHS is None + - if line has uncollapsable type comment - LHS is None + - if line has no unbracketed assignment - LHS is None + - if line has unbracketed assignment - LHS is line before it + """ if self.is_def: return self @@ -1287,14 +1299,14 @@ def get_left_hand_side(self) -> Optional["Line"]: if self.inside_brackets: return None - assignment_leaf_id = self.get_assignment_leaf_id() - if assignment_leaf_id is None: + assignment_index = self.get_assignment_index() + if assignment_index is None: return None - leaves = self.leaves[:assignment_leaf_id] + leaves = self.leaves[:assignment_index] comments: Dict[LeafID, List[Leaf]] = {} for comment_leaf_id, comment_leaves in self.comments.items(): - if comment_leaf_id < assignment_leaf_id: + if comment_leaf_id < assignment_index: comments[comment_leaf_id] = comment_leaves return Line( @@ -1306,6 +1318,15 @@ def get_left_hand_side(self) -> Optional["Line"]: ) def get_right_hand_side(self) -> Optional["Line"]: + """ + Get right hand side of line or None. + + - function definition - RHS is None + - if line is inside brackets - Full line is RHS + - if line has uncollapsable type comment - Full line is RHS + - if line has no unbracketed assignment - Full line is RHS + - if line has unbracketed assignment - LHS is line starting from it + """ if self.is_def: return None @@ -1315,14 +1336,14 @@ def get_right_hand_side(self) -> Optional["Line"]: if self.inside_brackets: return self - assignment_leaf_id = self.get_assignment_leaf_id() - if assignment_leaf_id is None: + assignment_index = self.get_assignment_index() + if assignment_index is None: return self - leaves = self.leaves[assignment_leaf_id:] + leaves = self.leaves[assignment_index:] comments: Dict[LeafID, List[Leaf]] = {} for comment_leaf_id, comment_leaves in self.comments.items(): - if comment_leaf_id >= assignment_leaf_id: + if comment_leaf_id >= assignment_index: comments[comment_leaf_id] = comment_leaves return Line( @@ -1334,7 +1355,7 @@ def get_right_hand_side(self) -> Optional["Line"]: should_explode=self.should_explode, ) - def get_optional_trailing_comma_leaf_id(self) -> Optional[LeafID]: + def get_optional_trailing_comma_index(self) -> Optional[int]: """Is this line a collection literal with a trailing comma that's optional? Note that the trailing comma in a 1-tuple and a 1-subscript is not optional. @@ -1348,9 +1369,9 @@ def get_optional_trailing_comma_leaf_id(self) -> Optional[LeafID]: # make sure that we have only one top-level collection opener: Optional[Leaf] = None closer: Optional[Leaf] = None - opener_index: LeafID = 0 - closer_index: LeafID = 0 - comma_indexes: List[LeafID] = [] + opener_index: int = 0 + closer_index: int = 0 + comma_indexes: List[int] = [] depth_counter = 0 for leaf_index, leaf in enumerate(self.leaves): # use comma indexes only on in the top-level collection @@ -2477,6 +2498,41 @@ def make_comment(content: str) -> str: return "#" + content +def should_be_rendered_as_single_line(line: Line, line_length: int) -> bool: + """ + Whether `line` should be rendered with no line breaks. + + Returns `False` if `line`: + + - contains uncollapsable type comments + - should explode + - does not fit `line_length` and does not have unsplittable type ignore + - line has optional trailing comma and is not a function definition + + Arguments: + line -- Original line. + line_length -- Max line length. + + Returns: + True if line does not meet any conditions above. + """ + if line.contains_uncollapsable_type_comments(): + return False + + if line.should_explode: + return False + + if not is_line_short_enough(line, line_length=line_length): + if not line.contains_unsplittable_type_ignore(): + return False + + optional_trailing_comma_index = line.get_optional_trailing_comma_index() + if optional_trailing_comma_index is not None and not line.is_def: + return False + + return True + + def split_line_side( line: Line, first_line_length: int, @@ -2485,24 +2541,42 @@ def split_line_side( features: Collection[Feature] = (), split_funcs: Collection[SplitFunc] = (), ) -> Iterator[Line]: - optional_trailing_comma_leaf_id = line.get_optional_trailing_comma_leaf_id() - if optional_trailing_comma_leaf_id and line.is_def: - line.leaves[optional_trailing_comma_leaf_id].value = "" - optional_trailing_comma_leaf_id = None + """ + Split LHS or RHS line. + + - if line fits in `first_line_length` and should not be exploded + and is not a function definition it is yielded unchanged. + - if line fits in `first_line_length` and should not be exploded + and is a function definition it is yielded without optional trailing comma. + + Otherwise, `split_funcs` are applied one by one: + + - if `split_func` fails with `CannotSplit` - next `split_func` is used + - if the last `split_func` fails with `CannotSplit` - line is yielded unchainged + - if `split_func` was successful - all lines from it's result are yieded + + Arguments: + line -- LHS or RHS line. + first_line_length -- Ma length of the first line, used for RHS. + line_length -- FOllowing lines max length. + inner -- Whether line has brackets outside. + features -- Features to use. + split_funcs -- Split functions to apply. + + Yields: + A `Line` part of the original `Line`. + """ + if should_be_rendered_as_single_line(line, first_line_length): + # remove trailing comma from function definitions + if line.is_def: + optional_trailing_comma_index = line.get_optional_trailing_comma_index() + if optional_trailing_comma_index is not None: + line.leaves[optional_trailing_comma_index].value = "" - line_str = str(line).strip("\n") - if ( - not line.contains_uncollapsable_type_comments() - and not line.should_explode - and optional_trailing_comma_leaf_id is None - and ( - is_line_short_enough(line, line_str=line_str, line_length=first_line_length) - or line.contains_unsplittable_type_ignore() - ) - ): yield line return + line_str = str(line).strip("\n") for split_func in split_funcs: # We are accumulating lines in `result` because we might want to abort # mission and return the original line in the end, or attempt a different @@ -2510,7 +2584,7 @@ def split_line_side( result: List[Line] = [] try: for sub_line_index, sub_line in enumerate( - split_func(line, features=features, line_length=line_length) + split_func(line, features, line_length, first_line_length) ): if str(sub_line).strip("\n") == line_str: raise CannotSplit("Split function returned an unchanged result") @@ -2535,15 +2609,30 @@ def split_line_side( def right_hand_split_with_omit( - line: Line, features: Collection[Feature], line_length: int = 999 + line: Line, features: Collection[Feature], line_length: int, first_line_length: int ) -> Iterator[Line]: + """Split line into many lines, starting with the last matching bracket pair. + + Acts like `right_hand_split`, but on fail adds trailers to omit one by one. + On last fail splits line with a minimum `line_length` (probably this code is outdated), + + Note: running this function modifies `bracket_depth` on the leaves of `line`. + """ for omit in generate_trailers_to_omit(line, line_length): try: lines = list( right_hand_split( - line, features=features, line_length=line_length, omit=omit + line, + features=features, + line_length=line_length, + first_line_length=first_line_length, + omit=omit, ) ) + if lines and not is_line_short_enough( + lines[0], line_length=first_line_length + ): + raise CannotSplit("First line is too long") except CannotSplit: continue @@ -2555,7 +2644,9 @@ def right_hand_split_with_omit( # reported as not fitting a single line. # line_length=1 here was historically a bug that somehow became a feature. # See #762 and #781 for the full story. - yield from right_hand_split(line, features=features, line_length=1) + yield from right_hand_split( + line, features=features, line_length=1, first_line_length=1 + ) def split_line( @@ -2579,9 +2670,6 @@ def split_line( left_hand_side = line.get_left_hand_side() right_hand_side = line.get_right_hand_side() - # print('line', str(line)) - # print('left_hand_side', left_hand_side and left_hand_side.leaves) - # print('right_hand_side', right_hand_side and right_hand_side.leaves) split_funcs: List[SplitFunc] = [left_hand_split] if line.inside_brackets: @@ -2602,7 +2690,9 @@ def split_line( if right_hand_side: first_line_length = line_length if result: - first_line_length = max(line_length - len(str(result[-1]).rstrip("\n").lstrip()), 1) + first_line_length = max( + line_length - len(str(result[-1]).rstrip("\n").lstrip()), 1 + ) split_funcs = [right_hand_split_with_omit] if line.inside_brackets: split_funcs = [ @@ -2631,7 +2721,10 @@ def split_line( def left_hand_split( - line: Line, features: Collection[Feature], line_length: int, + line: Line, + features: Collection[Feature], + line_length: int, + _first_line_length: int, ) -> Iterator[Line]: """Split line into many lines, starting with the first matching bracket pair. @@ -2672,6 +2765,7 @@ def right_hand_split( line: Line, features: Collection[Feature], line_length: int, + first_line_length: int, omit: Collection[LeafID] = (), ) -> Iterator[Line]: """Split line into many lines, starting with the last matching bracket pair. @@ -2726,19 +2820,25 @@ def right_hand_split( # there are no standalone comments in the body and not body.contains_standalone_comments(0) # and we can actually remove the parens - and can_omit_invisible_parens(body, line_length) + and can_omit_invisible_parens(body, first_line_length) ): omit = {id(closing_bracket), *omit} try: yield from right_hand_split( - line, features=features, line_length=line_length, omit=omit + line, + features=features, + line_length=line_length, + first_line_length=line_length, + omit=omit, ) except CannotSplit as e: pass else: return - if not can_be_split(body) and not is_line_short_enough(body, line_length=line_length): + if not can_be_split(body) and not is_line_short_enough( + body, line_length=line_length + ): raise CannotSplit( "Splitting failed, body is still too long and can't be split.", ) @@ -2834,9 +2934,12 @@ def dont_increase_indentation(split_func: SplitFunc) -> SplitFunc: @wraps(split_func) def split_wrapper( - line: Line, features: Collection[Feature], line_length: int = 999 + line: Line, + features: Collection[Feature], + line_length: int, + first_line_length: int, ) -> Iterator[Line]: - for l in split_func(line, features, line_length): + for l in split_func(line, features, line_length, first_line_length): normalize_prefix(l.leaves[0], inside_brackets=True) yield l @@ -2845,7 +2948,10 @@ def split_wrapper( @dont_increase_indentation def delimiter_split( - line: Line, features: Collection[Feature], _line_length: int = 999 + line: Line, + features: Collection[Feature], + _line_length: int, + _first_line_length: int, ) -> Iterator[Line]: """Split according to delimiters of the highest priority. @@ -2917,7 +3023,10 @@ def append_to_line(leaf: Leaf) -> Iterator[Line]: @dont_increase_indentation def standalone_comment_split( - line: Line, features: Collection[Feature] = (), _line_length: int = 999, + line: Line, + features: Collection[Feature], + _line_length: int, + _first_line_length: int, ) -> Iterator[Line]: """Split standalone comments from the rest of the line.""" if not line.contains_standalone_comments(0): diff --git a/tests/data/expression.diff b/tests/data/expression.diff index 629e1012f87..a83fa420ce1 100644 --- a/tests/data/expression.diff +++ b/tests/data/expression.diff @@ -130,7 +130,7 @@ call(**self.screen_kwargs) call(b, **self.screen_kwargs) lukasz.langa.pl -@@ -94,23 +127,25 @@ +@@ -94,11 +127,13 @@ 1.0 .real ....__class__ list[str] @@ -145,22 +145,7 @@ ] xxxx_xxxxx_xxxx_xxx: Callable[..., List[SomeClass]] = classmethod( # type: ignore sync(async_xxxx_xxx_xxxx_xxxxx_xxxx_xxx.__func__) - ) - xxxx_xxx_xxxx_xxxxx_xxxx_xxx: Callable[..., List[SomeClass]] = classmethod( # type: ignore - sync(async_xxxx_xxx_xxxx_xxxxx_xxxx_xxx.__func__) - ) --xxxx_xxx_xxxx_xxxxx_xxxx_xxx: Callable[ -- ..., List[SomeClass] --] = classmethod(sync(async_xxxx_xxx_xxxx_xxxxx_xxxx_xxx.__func__)) # type: ignore -+xxxx_xxx_xxxx_xxxxx_xxxx_xxx: Callable[..., List[SomeClass]] = classmethod( -+ sync(async_xxxx_xxx_xxxx_xxxxx_xxxx_xxx.__func__) -+) # type: ignore - slice[0] - slice[0:1] - slice[0:1:2] - slice[:] - slice[:-1] -@@ -134,113 +169,171 @@ +@@ -132,113 +167,171 @@ numpy[-(c + 1) :, d] numpy[:, l[-2]] numpy[:, ::-1] @@ -404,4 +389,3 @@ return True last_call() # standalone comment at ENDMARKER - diff --git a/tests/data/expression.py b/tests/data/expression.py index 3bcf52b54c4..b9e21879c53 100644 --- a/tests/data/expression.py +++ b/tests/data/expression.py @@ -106,9 +106,7 @@ xxxx_xxx_xxxx_xxxxx_xxxx_xxx: Callable[..., List[SomeClass]] = classmethod( # type: ignore sync(async_xxxx_xxx_xxxx_xxxxx_xxxx_xxx.__func__) ) -xxxx_xxx_xxxx_xxxxx_xxxx_xxx: Callable[ - ..., List[SomeClass] -] = classmethod(sync(async_xxxx_xxx_xxxx_xxxxx_xxxx_xxx.__func__)) # type: ignore +xxxx_xxx_xxxx_xxxxx_xxxx_xxx: Callable[..., List[SomeClass]] = classmethod(sync(async_xxxx_xxx_xxxx_xxxxx_xxxx_xxx.__func__)) # type: ignore slice[0] slice[0:1] slice[0:1:2] @@ -391,9 +389,7 @@ async def f(): xxxx_xxx_xxxx_xxxxx_xxxx_xxx: Callable[..., List[SomeClass]] = classmethod( # type: ignore sync(async_xxxx_xxx_xxxx_xxxxx_xxxx_xxx.__func__) ) -xxxx_xxx_xxxx_xxxxx_xxxx_xxx: Callable[..., List[SomeClass]] = classmethod( - sync(async_xxxx_xxx_xxxx_xxxxx_xxxx_xxx.__func__) -) # type: ignore +xxxx_xxx_xxxx_xxxxx_xxxx_xxx: Callable[..., List[SomeClass]] = classmethod(sync(async_xxxx_xxx_xxxx_xxxxx_xxxx_xxx.__func__)) # type: ignore slice[0] slice[0:1] slice[0:1:2] diff --git a/tests/data/function.py b/tests/data/function.py index d4f8274af62..4754588e38d 100644 --- a/tests/data/function.py +++ b/tests/data/function.py @@ -230,9 +230,7 @@ def trailing_comma(): } -def f( - a, **kwargs, -) -> A: +def f(a, **kwargs) -> A: return ( yield from A( very_long_argument_name1=very_long_value_for_the_argument, diff --git a/tests/data/function2.py b/tests/data/function2.py index 0520f73de9c..e08f62df1fa 100644 --- a/tests/data/function2.py +++ b/tests/data/function2.py @@ -25,9 +25,7 @@ def inner(): # output -def f( - a, **kwargs, -) -> A: +def f(a, **kwargs) -> A: with cache_dir(): if something: result = CliRunner().invoke( diff --git a/tests/data/function_trailing_comma.py b/tests/data/function_trailing_comma.py index fcd81ad7d96..94684f49cc1 100644 --- a/tests/data/function_trailing_comma.py +++ b/tests/data/function_trailing_comma.py @@ -11,11 +11,11 @@ def xxxxxxxxxxxxxxxxxxxxxxxxxxxx() -> Set[ # output -def f(a,): +def f(a): ... -def f(a: int = 1,): +def f(a: int = 1): ... diff --git a/tests/data/tupleassign.py b/tests/data/tupleassign.py index d948fee52f6..653b4aba6d5 100644 --- a/tests/data/tupleassign.py +++ b/tests/data/tupleassign.py @@ -10,12 +10,11 @@ # This is a standalone comment. -( - sdfjklsdfsjldkflkjsf, - sdfjsdfjlksdljkfsdlkf, - sdfsdjfklsdfjlksdljkf, - sdsfsdfjskdflsfsdf, -) = (1, 2, 3) +sdfjklsdfsjldkflkjsf, sdfjsdfjlksdljkfsdlkf, sdfsdjfklsdfjlksdljkf, sdsfsdfjskdflsfsdf = ( + 1, + 2, + 3, +) # This is as well. (this_will_be_wrapped_in_parens,) = struct.unpack(b"12345678901234567890") From 73c621c421755729f01c8ba03714d52db7b3b42e Mon Sep 17 00:00:00 2001 From: Vlad Emelianov Date: Wed, 20 Nov 2019 05:52:05 +0100 Subject: [PATCH 10/30] Move `should_be_rendered_as_single_line` to `Line` as a method --- black.py | 89 ++++++++++++++++++++++++++++++++------------------------ 1 file changed, 51 insertions(+), 38 deletions(-) diff --git a/black.py b/black.py index 3d32d2404be..4c6256696a7 100644 --- a/black.py +++ b/black.py @@ -1355,10 +1355,58 @@ def get_right_hand_side(self) -> Optional["Line"]: should_explode=self.should_explode, ) + def should_be_rendered_as_single_line(self, line_length: int) -> bool: + """ + Whether line should be rendered with no line breaks. + + Returns `False` if line: + + - contains uncollapsable type comments + - should explode + - does not fit `line_length` and does not have unsplittable type ignore + - line has optional trailing comma and is not a function definition + + Arguments: + line_length -- Max line length. + + Returns: + True if line does not meet any conditions above. + """ + if self.contains_uncollapsable_type_comments(): + return False + + if self.should_explode: + return False + + if not is_line_short_enough(self, line_length=line_length): + if not self.contains_unsplittable_type_ignore(): + return False + + optional_trailing_comma_index = self.get_optional_trailing_comma_index() + if optional_trailing_comma_index is not None and not self.is_def: + return False + + return True + def get_optional_trailing_comma_index(self) -> Optional[int]: - """Is this line a collection literal with a trailing comma that's optional? + """ + Get index of the top-level optional trailing comma or `None`. + + Does not lookup trailing commas if line: + + - is inside brackets + - has less than 4 leaves - Note that the trailing comma in a 1-tuple and a 1-subscript is not optional. + Index can be found for: + + - function definitions + - tuples with multiple items + - implicit tuples with multiple items + - subscripts with multiple items + - lists, dicts, sets + - import statements + + Note: the trailing comma in a 1-tuple and a 1-subscript is not optional. """ if self.inside_brackets: return None @@ -2498,41 +2546,6 @@ def make_comment(content: str) -> str: return "#" + content -def should_be_rendered_as_single_line(line: Line, line_length: int) -> bool: - """ - Whether `line` should be rendered with no line breaks. - - Returns `False` if `line`: - - - contains uncollapsable type comments - - should explode - - does not fit `line_length` and does not have unsplittable type ignore - - line has optional trailing comma and is not a function definition - - Arguments: - line -- Original line. - line_length -- Max line length. - - Returns: - True if line does not meet any conditions above. - """ - if line.contains_uncollapsable_type_comments(): - return False - - if line.should_explode: - return False - - if not is_line_short_enough(line, line_length=line_length): - if not line.contains_unsplittable_type_ignore(): - return False - - optional_trailing_comma_index = line.get_optional_trailing_comma_index() - if optional_trailing_comma_index is not None and not line.is_def: - return False - - return True - - def split_line_side( line: Line, first_line_length: int, @@ -2566,7 +2579,7 @@ def split_line_side( Yields: A `Line` part of the original `Line`. """ - if should_be_rendered_as_single_line(line, first_line_length): + if line.should_be_rendered_as_single_line(first_line_length): # remove trailing comma from function definitions if line.is_def: optional_trailing_comma_index = line.get_optional_trailing_comma_index() From 2758bab616e46039234b9cf4e0c1fe96a638b50c Mon Sep 17 00:00:00 2001 From: Vlad Emelianov Date: Wed, 20 Nov 2019 05:53:52 +0100 Subject: [PATCH 11/30] Remove line_str argument from is_line_short_enough --- black.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/black.py b/black.py index 4c6256696a7..f76f715bf0f 100644 --- a/black.py +++ b/black.py @@ -4220,7 +4220,7 @@ def enumerate_with_length( yield index, leaf, length -def is_line_short_enough(line: Line, *, line_length: int, line_str: str = "") -> bool: +def is_line_short_enough(line: Line, line_length: int) -> bool: """Return True if `line` is no longer than `line_length`. Uses the provided `line_str` rendering, if any, otherwise computes a new one. From efa31fef0171d0a00bc99377bd2a2247c2617e32 Mon Sep 17 00:00:00 2001 From: Vlad Emelianov Date: Wed, 20 Nov 2019 06:05:05 +0100 Subject: [PATCH 12/30] Fix undefined argument usage --- black.py | 3 +-- 1 file changed, 1 insertion(+), 2 deletions(-) diff --git a/black.py b/black.py index f76f715bf0f..4fe9cb22ab6 100644 --- a/black.py +++ b/black.py @@ -4225,8 +4225,7 @@ def is_line_short_enough(line: Line, line_length: int) -> bool: Uses the provided `line_str` rendering, if any, otherwise computes a new one. """ - if not line_str: - line_str = str(line).strip("\n") + line_str = str(line).strip("\n") return ( len(line_str) <= line_length and "\n" not in line_str # multiline strings From a570e46a919ae12a842720c0e4619a4a3816f826 Mon Sep 17 00:00:00 2001 From: Vlad Emelianov Date: Wed, 20 Nov 2019 07:00:08 +0100 Subject: [PATCH 13/30] Prevent unwanted lines yield from right_hand_split --- black.py | 15 +++++++++------ 1 file changed, 9 insertions(+), 6 deletions(-) diff --git a/black.py b/black.py index 4fe9cb22ab6..fd18161707f 100644 --- a/black.py +++ b/black.py @@ -2837,16 +2837,19 @@ def right_hand_split( ): omit = {id(closing_bracket), *omit} try: - yield from right_hand_split( - line, - features=features, - line_length=line_length, - first_line_length=line_length, - omit=omit, + sub_lines = list( + right_hand_split( + line, + features=features, + line_length=line_length, + first_line_length=first_line_length, + omit=omit, + ) ) except CannotSplit as e: pass else: + yield from sub_lines return if not can_be_split(body) and not is_line_short_enough( From 53d0d02345a5d5ebe1ccb1a29fbc9b47a1cd526f Mon Sep 17 00:00:00 2001 From: Vlad Emelianov Date: Wed, 20 Nov 2019 18:05:08 +0100 Subject: [PATCH 14/30] Fix flake8 warnings --- black.py | 124 +++++++++++++++++++++++++++++++++++++++---------------- 1 file changed, 88 insertions(+), 36 deletions(-) diff --git a/black.py b/black.py index fd18161707f..9fda6372fe5 100644 --- a/black.py +++ b/black.py @@ -57,7 +57,9 @@ from _black_version import version as __version__ DEFAULT_LINE_LENGTH = 88 -DEFAULT_EXCLUDES = r"/(\.eggs|\.git|\.hg|\.mypy_cache|\.nox|\.tox|\.venv|\.svn|_build|buck-out|build|dist)/" # noqa: B950 +DEFAULT_EXCLUDES = ( + r"/(\.eggs|\.git|\.hg|\.mypy_cache|\.nox|\.tox|\.venv|\.svn|_build|buck-out|build|dist)/" # noqa: B950 +) DEFAULT_INCLUDES = r"\.pyi?$" CACHE_DIR = Path(user_cache_dir("black", version=__version__)) @@ -1266,7 +1268,7 @@ def is_stub_class(self) -> bool: Leaf(token.DOT, ".") for _ in range(3) ] - def get_assignment_index(self) -> Optional[int]: + def _get_assignment_index(self) -> Optional[int]: """ Get fthe first unbracketed assignment index. """ @@ -1299,7 +1301,7 @@ def get_left_hand_side(self) -> Optional["Line"]: if self.inside_brackets: return None - assignment_index = self.get_assignment_index() + assignment_index = self._get_assignment_index() if assignment_index is None: return None @@ -1336,7 +1338,7 @@ def get_right_hand_side(self) -> Optional["Line"]: if self.inside_brackets: return self - assignment_index = self.get_assignment_index() + assignment_index = self._get_assignment_index() if assignment_index is None: return self @@ -1388,33 +1390,23 @@ def should_be_rendered_as_single_line(self, line_length: int) -> bool: return True - def get_optional_trailing_comma_index(self) -> Optional[int]: + def _get_top_level_collection_indexes(self) -> Optional[Tuple[int, int, List[int]]]: """ - Get index of the top-level optional trailing comma or `None`. - - Does not lookup trailing commas if line: - - - is inside brackets - - has less than 4 leaves - - Index can be found for: + Get top level colelction opener, closer and comma indexes. + + - if top-level collection is not found - return `None` + - if there is more than one top-level collection - return `None` + - if brackets are invalid - return `None` + - if collection has visible brackets - `opener_index` and `closer_index` + point to them + - if collection has only invisible brackets - `opener_index` and `closer_index` + point to them + - `comma_indexes` include only top-level collection comma indexes, + nested are not included - - function definitions - - tuples with multiple items - - implicit tuples with multiple items - - subscripts with multiple items - - lists, dicts, sets - - import statements - - Note: the trailing comma in a 1-tuple and a 1-subscript is not optional. + Returns: + A tuple of `opener_index`, `closer_index` and `comma_indexes` or `None`. """ - if self.inside_brackets: - return None - - if not self.leaves or len(self.leaves) < 4: - return None - - # make sure that we have only one top-level collection opener: Optional[Leaf] = None closer: Optional[Leaf] = None opener_index: int = 0 @@ -1460,10 +1452,43 @@ def get_optional_trailing_comma_index(self) -> Optional[int]: closer = leaf closer_index = leaf_index - # no brackets found - if opener is None or closer is None: + return opener_index, closer_index, comma_indexes + + def get_optional_trailing_comma_index(self) -> Optional[int]: + """ + Get index of the top-level optional trailing comma or `None`. + + Does not lookup trailing commas if line: + + - is inside brackets + - has less than 4 leaves + + Index can be found for: + + - function definitions + - tuples with multiple items + - implicit tuples with multiple items + - subscripts with multiple items + - lists, dicts, sets + - import statements + + Note: the trailing comma in a 1-tuple and a 1-subscript is not optional. + """ + if self.inside_brackets: + return None + + if not self.leaves or len(self.leaves) < 4: return None + top_level_collection_indexes = self._get_top_level_collection_indexes() + + # no top level collection found or multiple collections + if top_level_collection_indexes is None: + return None + + opener_index, closer_index, comma_indexes = top_level_collection_indexes + opener = self.leaves[opener_index] + # no commas found in collection if not comma_indexes: return None @@ -1708,6 +1733,30 @@ def is_complex_subscript(self, leaf: Leaf) -> bool: n.type in TEST_DESCENDANTS for n in subscript_start.pre_order() ) + def is_short_enough( + self, first_line_length: int = None, line_length: int = 0 + ) -> bool: + """Return True if `line` is no longer than `line_length`. + + Handles multi lines. + """ + if self.contains_standalone_comments(): + return False + + line_str = str(self).strip("\n") + if line_length == 0 and "\n" in line_str: + return False + + sub_lines = line_str.split("\n") + sub_line_length = first_line_length + for sub_line in sub_lines: + if len(sub_line) > sub_line_length: + return False + + sub_line_length = line_length + + return True + def __str__(self) -> str: """Render the line.""" if not self: @@ -2561,7 +2610,7 @@ def split_line_side( and is not a function definition it is yielded unchanged. - if line fits in `first_line_length` and should not be exploded and is a function definition it is yielded without optional trailing comma. - + Otherwise, `split_funcs` are applied one by one: - if `split_func` fails with `CannotSplit` - next `split_func` is used @@ -2627,7 +2676,7 @@ def right_hand_split_with_omit( """Split line into many lines, starting with the last matching bracket pair. Acts like `right_hand_split`, but on fail adds trailers to omit one by one. - On last fail splits line with a minimum `line_length` (probably this code is outdated), + On last fail splits line with a minimum `line_length`. Note: running this function modifies `bracket_depth` on the leaves of `line`. """ @@ -2834,6 +2883,10 @@ def right_hand_split( and not body.contains_standalone_comments(0) # and we can actually remove the parens and can_omit_invisible_parens(body, first_line_length) + and ( + not can_be_split(head) + or head.is_short_enough(first_line_length, line_length) + ) ): omit = {id(closing_bracket), *omit} try: @@ -2846,15 +2899,13 @@ def right_hand_split( omit=omit, ) ) - except CannotSplit as e: + except CannotSplit: pass else: yield from sub_lines return - if not can_be_split(body) and not is_line_short_enough( - body, line_length=line_length - ): + if not can_be_split(body) and not body.is_short_enough(line_length): raise CannotSplit( "Splitting failed, body is still too long and can't be split.", ) @@ -4229,6 +4280,7 @@ def is_line_short_enough(line: Line, line_length: int) -> bool: Uses the provided `line_str` rendering, if any, otherwise computes a new one. """ line_str = str(line).strip("\n") + return ( len(line_str) <= line_length and "\n" not in line_str # multiline strings From 3bca90ac36d816175e00d8fa927a3b3183ce5e4e Mon Sep 17 00:00:00 2001 From: Vlad Emelianov Date: Wed, 20 Nov 2019 18:09:53 +0100 Subject: [PATCH 15/30] Remove accidental debug changes --- black.py | 8 +------- 1 file changed, 1 insertion(+), 7 deletions(-) diff --git a/black.py b/black.py index 9fda6372fe5..a4c8fb0222e 100644 --- a/black.py +++ b/black.py @@ -57,9 +57,7 @@ from _black_version import version as __version__ DEFAULT_LINE_LENGTH = 88 -DEFAULT_EXCLUDES = ( - r"/(\.eggs|\.git|\.hg|\.mypy_cache|\.nox|\.tox|\.venv|\.svn|_build|buck-out|build|dist)/" # noqa: B950 -) +DEFAULT_EXCLUDES = r"/(\.eggs|\.git|\.hg|\.mypy_cache|\.nox|\.tox|\.venv|\.svn|_build|buck-out|build|dist)/" # noqa: B950 DEFAULT_INCLUDES = r"\.pyi?$" CACHE_DIR = Path(user_cache_dir("black", version=__version__)) @@ -2883,10 +2881,6 @@ def right_hand_split( and not body.contains_standalone_comments(0) # and we can actually remove the parens and can_omit_invisible_parens(body, first_line_length) - and ( - not can_be_split(head) - or head.is_short_enough(first_line_length, line_length) - ) ): omit = {id(closing_bracket), *omit} try: From cd3ce50f1fb928bfd2d0d338aeb72139a129d936 Mon Sep 17 00:00:00 2001 From: Vlad Emelianov Date: Wed, 20 Nov 2019 20:11:02 +0100 Subject: [PATCH 16/30] Add last line length handling and fix unsplittable type ignore behavior --- black.py | 132 ++++++++++++++++++++++++++------------ tests/data/tupleassign.py | 11 ++-- 2 files changed, 97 insertions(+), 46 deletions(-) diff --git a/black.py b/black.py index a4c8fb0222e..d17d8b0a824 100644 --- a/black.py +++ b/black.py @@ -243,9 +243,7 @@ def read_pyproject_toml( if ctx.default_map is None: ctx.default_map = {} - ctx.default_map.update( # type: ignore # bad types in .pyi - {k.replace("--", "").replace("-", "_"): v for k, v in config.items()} - ) + ctx.default_map.update({k.replace("--", "").replace("-", "_"): v for k, v in config.items()}) # type: ignore # bad types in .pyi return value @@ -1266,7 +1264,7 @@ def is_stub_class(self) -> bool: Leaf(token.DOT, ".") for _ in range(3) ] - def _get_assignment_index(self) -> Optional[int]: + def get_assignment_index(self) -> Optional[int]: """ Get fthe first unbracketed assignment index. """ @@ -1280,36 +1278,39 @@ def _get_assignment_index(self) -> Optional[int]: return leaf_index return None + def get_source_line_numbers(self) -> Set[int]: + """ + Get a set of `lineno` of all leaves. + + Does not include `0` line number of generated nodes. + + Can be used to check if line has multiple lines in source. + """ + return set([i.lineno for i in self.leaves if i.lineno]) + def get_left_hand_side(self) -> Optional["Line"]: """ Get left hand side of line or None. - function definition - full line is LHS-value - if line is inside brackets - LHS is None - - if line has uncollapsable type comment - LHS is None - if line has no unbracketed assignment - LHS is None - if line has unbracketed assignment - LHS is line before it """ if self.is_def: return self - if self.contains_uncollapsable_type_comments(): - return None - if self.inside_brackets: return None - assignment_index = self._get_assignment_index() + assignment_index = self.get_assignment_index() if assignment_index is None: return None leaves = self.leaves[:assignment_index] comments: Dict[LeafID, List[Leaf]] = {} - for comment_leaf_id, comment_leaves in self.comments.items(): - if comment_leaf_id < assignment_index: - comments[comment_leaf_id] = comment_leaves - return Line( + result = Line( depth=self.depth, leaves=leaves, comments=comments, @@ -1317,26 +1318,43 @@ def get_left_hand_side(self) -> Optional["Line"]: should_explode=self.should_explode, ) + for comment_leaf_id, comment_leaves in self.comments.items(): + for leaf in self.leaves: + if id(leaf) == comment_leaf_id: + break + else: + leaf = None + + # add comments for leaves in LHS + if comment_leaf_id < assignment_index: + comments[comment_leaf_id] = comment_leaves + + # add unsplittable type ignore from RHS if it is on the same lines + unsplittable_type_ignore_data = self.get_unsplittable_type_ignore() + if unsplittable_type_ignore_data: + line_leaf, comment_leaf = unsplittable_type_ignore_data + line_numbers = result.get_source_line_numbers().union({0}) + if line_leaf.lineno in line_numbers: + comments[id(line_leaf)] = [comment_leaf] + + return result + def get_right_hand_side(self) -> Optional["Line"]: """ Get right hand side of line or None. - function definition - RHS is None - if line is inside brackets - Full line is RHS - - if line has uncollapsable type comment - Full line is RHS - if line has no unbracketed assignment - Full line is RHS - if line has unbracketed assignment - LHS is line starting from it """ if self.is_def: return None - if self.contains_uncollapsable_type_comments(): - return self - if self.inside_brackets: return self - assignment_index = self._get_assignment_index() + assignment_index = self.get_assignment_index() if assignment_index is None: return self @@ -1359,27 +1377,38 @@ def should_be_rendered_as_single_line(self, line_length: int) -> bool: """ Whether line should be rendered with no line breaks. + Returns `True` if line: + + - contains uncollapsable type comments and has single line in source + Returns `False` if line: - - contains uncollapsable type comments + - contains uncollapsable type comments and has multiple lines in source - should explode - does not fit `line_length` and does not have unsplittable type ignore - line has optional trailing comma and is not a function definition + If no conditions are met, returns `True` + Arguments: line_length -- Max line length. Returns: - True if line does not meet any conditions above. + Boolean according to conditions above. """ if self.contains_uncollapsable_type_comments(): - return False + # split lines to leave uncollapsable type comments where they were + if len(self.get_source_line_numbers()) > 1: + return False + + # otherwise, always render as single line + return True if self.should_explode: return False if not is_line_short_enough(self, line_length=line_length): - if not self.contains_unsplittable_type_ignore(): + if not self.get_unsplittable_type_ignore(): return False optional_trailing_comma_index = self.get_optional_trailing_comma_index() @@ -1589,7 +1618,15 @@ def contains_uncollapsable_type_comments(self) -> bool: return False - def contains_unsplittable_type_ignore(self) -> bool: + def get_unsplittable_type_ignore(self) -> Optional[Tuple[Leaf, Leaf]]: + """ + Get line leaf and comment leaf of unsplittable type ignore. + + If there is no unsplittable type ignore in line - returns `None`. + + Returns: + A tuple with line `leaf` and comment `leaf` or None. + """ if not self.leaves: return False @@ -1614,11 +1651,12 @@ def contains_unsplittable_type_ignore(self) -> bool: # invisible paren could have been added at the end of the # line. for node in self.leaves[-2:]: + leaf_id = id(node) for comment in self.comments.get(id(node), []): if is_type_comment(comment, " ignore"): - return True + return node, comment - return False + return None def contains_multiline_strings(self) -> bool: for leaf in self.leaves: @@ -2173,14 +2211,7 @@ def whitespace(leaf: Leaf, *, complex_subscript: bool) -> str: # noqa: C901 ): return NO - elif ( - prevp.type == token.RIGHTSHIFT - and prevp.parent - and prevp.parent.type == syms.shift_expr - and prevp.prev_sibling - and prevp.prev_sibling.type == token.NAME - and prevp.prev_sibling.value == "print" # type: ignore - ): + elif prevp.type == token.RIGHTSHIFT and prevp.parent and prevp.parent.type == syms.shift_expr and prevp.prev_sibling and prevp.prev_sibling.type == token.NAME and prevp.prev_sibling.value == "print": # type: ignore # Python 2 print chevron return NO @@ -2596,6 +2627,7 @@ def make_comment(content: str) -> str: def split_line_side( line: Line, first_line_length: int, + last_line_length: int, line_length: int, inner: bool = False, features: Collection[Feature] = (), @@ -2626,7 +2658,8 @@ def split_line_side( Yields: A `Line` part of the original `Line`. """ - if line.should_be_rendered_as_single_line(first_line_length): + single_line_length = min(first_line_length, last_line_length) + if line.should_be_rendered_as_single_line(single_line_length): # remove trailing comma from function definitions if line.is_def: optional_trailing_comma_index = line.get_optional_trailing_comma_index() @@ -2731,16 +2764,23 @@ def split_line( left_hand_side = line.get_left_hand_side() right_hand_side = line.get_right_hand_side() - split_funcs: List[SplitFunc] = [left_hand_split] - if line.inside_brackets: - split_funcs = [delimiter_split, standalone_comment_split, left_hand_split] - result: List[Line] = [] if left_hand_side: + split_funcs: List[SplitFunc] = [left_hand_split] + if line.inside_brackets: + split_funcs = [delimiter_split, standalone_comment_split, left_hand_split] + last_line_length = line_length + if right_hand_side: + if right_hand_side.get_unsplittable_type_ignore(): + last_line_length = 1 + else: + assignment = line.leaves[line.get_assignment_index()] + last_line_length = line_length - len(assignment.value) - 3 for left_hand_side_line in split_line_side( line=left_hand_side, line_length=line_length, first_line_length=line_length, + last_line_length=last_line_length, inner=inner, features=features, split_funcs=split_funcs, @@ -2753,7 +2793,7 @@ def split_line( first_line_length = max( line_length - len(str(result[-1]).rstrip("\n").lstrip()), 1 ) - split_funcs = [right_hand_split_with_omit] + split_funcs: List[SplitFunc] = [right_hand_split_with_omit] if line.inside_brackets: split_funcs = [ delimiter_split, @@ -2765,6 +2805,7 @@ def split_line( line=right_hand_side, line_length=line_length, first_line_length=first_line_length, + last_line_length=line_length, inner=inner, features=features, split_funcs=split_funcs, @@ -2809,13 +2850,20 @@ def left_hand_split( if leaf.type in OPENING_BRACKETS: matching_bracket = leaf current_leaves = body_leaves + if current_leaves is tail_leaves: + if leaf.type in CLOSING_BRACKETS: + opening_bracket = leaf.opening_bracket + closing_bracket = leaf if not matching_bracket: - raise CannotSplit("No brackets found") + raise CannotSplit("No brackets found: List[SplitFunc]") head = bracket_split_build_line(head_leaves, line, matching_bracket) body = bracket_split_build_line(body_leaves, line, matching_bracket, is_body=True) tail = bracket_split_build_line(tail_leaves, line, matching_bracket) bracket_split_succeeded_or_raise(head, body, tail) + + ensure_visible(opening_bracket) + ensure_visible(closing_bracket) for result in (head, body, tail): if result: yield result @@ -3331,7 +3379,9 @@ def normalize_invisible_parens(node: Node, parens_after: Set[str]) -> None: if child.type == token.LPAR: # make parentheses invisible child.value = "" # type: ignore - node.children[-1].value = "" # type: ignore + node.children[ + -1 + ].value = "" # type: ignore elif child.type != token.STAR: # insert invisible parentheses node.insert_child(index, Leaf(token.LPAR, "")) diff --git a/tests/data/tupleassign.py b/tests/data/tupleassign.py index 653b4aba6d5..8742503d332 100644 --- a/tests/data/tupleassign.py +++ b/tests/data/tupleassign.py @@ -10,11 +10,12 @@ # This is a standalone comment. -sdfjklsdfsjldkflkjsf, sdfjsdfjlksdljkfsdlkf, sdfsdjfklsdfjlksdljkf, sdsfsdfjskdflsfsdf = ( - 1, - 2, - 3, -) +( + sdfjklsdfsjldkflkjsf, + sdfjsdfjlksdljkfsdlkf, + sdfsdjfklsdfjlksdljkf, + sdsfsdfjskdflsfsdf, +) = 1, 2, 3 # This is as well. (this_will_be_wrapped_in_parens,) = struct.unpack(b"12345678901234567890") From f86921663eec85cf6420e204e98ed0f4c94651b8 Mon Sep 17 00:00:00 2001 From: Vlad Emelianov Date: Wed, 20 Nov 2019 20:20:17 +0100 Subject: [PATCH 17/30] Flake8 --- black.py | 1 - 1 file changed, 1 deletion(-) diff --git a/black.py b/black.py index d17d8b0a824..a3312cf4d3f 100644 --- a/black.py +++ b/black.py @@ -1651,7 +1651,6 @@ def get_unsplittable_type_ignore(self) -> Optional[Tuple[Leaf, Leaf]]: # invisible paren could have been added at the end of the # line. for node in self.leaves[-2:]: - leaf_id = id(node) for comment in self.comments.get(id(node), []): if is_type_comment(comment, " ignore"): return node, comment From ffae728f665d3b2a01f422dbace15afd35d99a29 Mon Sep 17 00:00:00 2001 From: Vlad Emelianov Date: Wed, 20 Nov 2019 20:21:55 +0100 Subject: [PATCH 18/30] Fix line too long --- black.py | 4 +++- 1 file changed, 3 insertions(+), 1 deletion(-) diff --git a/black.py b/black.py index a3312cf4d3f..d6027f8b90a 100644 --- a/black.py +++ b/black.py @@ -243,7 +243,9 @@ def read_pyproject_toml( if ctx.default_map is None: ctx.default_map = {} - ctx.default_map.update({k.replace("--", "").replace("-", "_"): v for k, v in config.items()}) # type: ignore # bad types in .pyi + ctx.default_map.update( # type: ignore # bad types in .pyi + {k.replace("--", "").replace("-", "_"): v for k, v in config.items()} + ) return value From 7bca1798cec1d3f82abfd898d045aad18cb71963 Mon Sep 17 00:00:00 2001 From: Vlad Emelianov Date: Wed, 20 Nov 2019 20:22:59 +0100 Subject: [PATCH 19/30] FIx another line too long --- black.py | 9 ++++++++- 1 file changed, 8 insertions(+), 1 deletion(-) diff --git a/black.py b/black.py index d6027f8b90a..a8005b11b5a 100644 --- a/black.py +++ b/black.py @@ -2212,7 +2212,14 @@ def whitespace(leaf: Leaf, *, complex_subscript: bool) -> str: # noqa: C901 ): return NO - elif prevp.type == token.RIGHTSHIFT and prevp.parent and prevp.parent.type == syms.shift_expr and prevp.prev_sibling and prevp.prev_sibling.type == token.NAME and prevp.prev_sibling.value == "print": # type: ignore + elif ( + prevp.type == token.RIGHTSHIFT + and prevp.parent + and prevp.parent.type == syms.shift_expr + and prevp.prev_sibling + and prevp.prev_sibling.type == token.NAME + and prevp.prev_sibling.value == "print" # type: ignore + ): # Python 2 print chevron return NO From f35d1f7f66a8a1e26d879831a8bd0490b55ae3c9 Mon Sep 17 00:00:00 2001 From: Vlad Emelianov Date: Wed, 20 Nov 2019 20:28:02 +0100 Subject: [PATCH 20/30] Revert accidental change --- black.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/black.py b/black.py index a8005b11b5a..830705de80c 100644 --- a/black.py +++ b/black.py @@ -2863,7 +2863,7 @@ def left_hand_split( opening_bracket = leaf.opening_bracket closing_bracket = leaf if not matching_bracket: - raise CannotSplit("No brackets found: List[SplitFunc]") + raise CannotSplit("No brackets found") head = bracket_split_build_line(head_leaves, line, matching_bracket) body = bracket_split_build_line(body_leaves, line, matching_bracket, is_body=True) From 65a4a503cd39073f2bb25fb15e6d7afcd0c6361b Mon Sep 17 00:00:00 2001 From: Vlad Emelianov Date: Wed, 20 Nov 2019 20:34:45 +0100 Subject: [PATCH 21/30] Fix mypy warnings --- black.py | 33 ++++++++++----------------------- 1 file changed, 10 insertions(+), 23 deletions(-) diff --git a/black.py b/black.py index 830705de80c..64208895ef0 100644 --- a/black.py +++ b/black.py @@ -1320,17 +1320,6 @@ def get_left_hand_side(self) -> Optional["Line"]: should_explode=self.should_explode, ) - for comment_leaf_id, comment_leaves in self.comments.items(): - for leaf in self.leaves: - if id(leaf) == comment_leaf_id: - break - else: - leaf = None - - # add comments for leaves in LHS - if comment_leaf_id < assignment_index: - comments[comment_leaf_id] = comment_leaves - # add unsplittable type ignore from RHS if it is on the same lines unsplittable_type_ignore_data = self.get_unsplittable_type_ignore() if unsplittable_type_ignore_data: @@ -1630,7 +1619,7 @@ def get_unsplittable_type_ignore(self) -> Optional[Tuple[Leaf, Leaf]]: A tuple with line `leaf` and comment `leaf` or None. """ if not self.leaves: - return False + return None # If a 'type: ignore' is attached to the end of a line, we # can't split the line, because we can't know which of the @@ -1770,9 +1759,7 @@ def is_complex_subscript(self, leaf: Leaf) -> bool: n.type in TEST_DESCENDANTS for n in subscript_start.pre_order() ) - def is_short_enough( - self, first_line_length: int = None, line_length: int = 0 - ) -> bool: + def is_short_enough(self, first_line_length: int, line_length: int = 0) -> bool: """Return True if `line` is no longer than `line_length`. Handles multi lines. @@ -2771,10 +2758,10 @@ def split_line( left_hand_side = line.get_left_hand_side() right_hand_side = line.get_right_hand_side() - + split_funcs: List[SplitFunc] result: List[Line] = [] if left_hand_side: - split_funcs: List[SplitFunc] = [left_hand_split] + split_funcs = [left_hand_split] if line.inside_brackets: split_funcs = [delimiter_split, standalone_comment_split, left_hand_split] last_line_length = line_length @@ -2782,8 +2769,10 @@ def split_line( if right_hand_side.get_unsplittable_type_ignore(): last_line_length = 1 else: - assignment = line.leaves[line.get_assignment_index()] - last_line_length = line_length - len(assignment.value) - 3 + assignment_index = line.get_assignment_index() + if assignment_index: + assignment = line.leaves[assignment_index] + last_line_length = line_length - len(assignment.value) - 3 for left_hand_side_line in split_line_side( line=left_hand_side, line_length=line_length, @@ -2801,7 +2790,7 @@ def split_line( first_line_length = max( line_length - len(str(result[-1]).rstrip("\n").lstrip()), 1 ) - split_funcs: List[SplitFunc] = [right_hand_split_with_omit] + split_funcs = [right_hand_split_with_omit] if line.inside_brackets: split_funcs = [ delimiter_split, @@ -3387,9 +3376,7 @@ def normalize_invisible_parens(node: Node, parens_after: Set[str]) -> None: if child.type == token.LPAR: # make parentheses invisible child.value = "" # type: ignore - node.children[ - -1 - ].value = "" # type: ignore + node.children[-1].value = "" # type: ignore elif child.type != token.STAR: # insert invisible parentheses node.insert_child(index, Leaf(token.LPAR, "")) From 96a48525aef59ab95125a9d98fa95eee8c558980 Mon Sep 17 00:00:00 2001 From: Vlad Emelianov Date: Wed, 20 Nov 2019 20:37:25 +0100 Subject: [PATCH 22/30] Make _get_assignment_index private --- black.py | 14 ++++++-------- 1 file changed, 6 insertions(+), 8 deletions(-) diff --git a/black.py b/black.py index 64208895ef0..0b18f9abc6b 100644 --- a/black.py +++ b/black.py @@ -1266,7 +1266,7 @@ def is_stub_class(self) -> bool: Leaf(token.DOT, ".") for _ in range(3) ] - def get_assignment_index(self) -> Optional[int]: + def _get_assignment_index(self) -> Optional[int]: """ Get fthe first unbracketed assignment index. """ @@ -1305,7 +1305,7 @@ def get_left_hand_side(self) -> Optional["Line"]: if self.inside_brackets: return None - assignment_index = self.get_assignment_index() + assignment_index = self._get_assignment_index() if assignment_index is None: return None @@ -1345,7 +1345,7 @@ def get_right_hand_side(self) -> Optional["Line"]: if self.inside_brackets: return self - assignment_index = self.get_assignment_index() + assignment_index = self._get_assignment_index() if assignment_index is None: return self @@ -2765,14 +2765,12 @@ def split_line( if line.inside_brackets: split_funcs = [delimiter_split, standalone_comment_split, left_hand_split] last_line_length = line_length - if right_hand_side: + if right_hand_side and right_hand_side.leaves: if right_hand_side.get_unsplittable_type_ignore(): last_line_length = 1 else: - assignment_index = line.get_assignment_index() - if assignment_index: - assignment = line.leaves[assignment_index] - last_line_length = line_length - len(assignment.value) - 3 + assignment = right_hand_side.leaves[0] + last_line_length = line_length - len(assignment.value) - 3 for left_hand_side_line in split_line_side( line=left_hand_side, line_length=line_length, From aff3273dc5f6df5d303d6f4e54514eae341a7870 Mon Sep 17 00:00:00 2001 From: Vlad Emelianov Date: Wed, 20 Nov 2019 20:38:50 +0100 Subject: [PATCH 23/30] Update docstrings --- black.py | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/black.py b/black.py index 0b18f9abc6b..7ab9acf29ea 100644 --- a/black.py +++ b/black.py @@ -2644,7 +2644,8 @@ def split_line_side( Arguments: line -- LHS or RHS line. - first_line_length -- Ma length of the first line, used for RHS. + first_line_length -- Max length of the first line, used for RHS. + last_line_length -- Max length of the last line, used for LHS. line_length -- FOllowing lines max length. inner -- Whether line has brackets outside. features -- Features to use. From 7ab3fecb39a62d36ef55169192c10bd2af712af2 Mon Sep 17 00:00:00 2001 From: Vlad Emelianov Date: Wed, 20 Nov 2019 20:41:52 +0100 Subject: [PATCH 24/30] Update docstrings --- black.py | 6 ++++++ 1 file changed, 6 insertions(+) diff --git a/black.py b/black.py index 7ab9acf29ea..b6baa76a6b4 100644 --- a/black.py +++ b/black.py @@ -1298,6 +1298,9 @@ def get_left_hand_side(self) -> Optional["Line"]: - if line is inside brackets - LHS is None - if line has no unbracketed assignment - LHS is None - if line has unbracketed assignment - LHS is line before it + + Returns: + A new `Line` or itself. """ if self.is_def: return self @@ -1338,6 +1341,9 @@ def get_right_hand_side(self) -> Optional["Line"]: - if line is inside brackets - Full line is RHS - if line has no unbracketed assignment - Full line is RHS - if line has unbracketed assignment - LHS is line starting from it + + Returns: + A new `Line` or itself. """ if self.is_def: return None From 46fc89deed7de3de0ab0c49827f00451275d91f2 Mon Sep 17 00:00:00 2001 From: Vlad Emelianov Date: Thu, 21 Nov 2019 00:06:44 +0100 Subject: [PATCH 25/30] Make trailing comma behavior consistent across LHS and RHS --- black.py | 233 ++++++++++++-------------- tests/data/collections.py | 26 +-- tests/data/comments7.py | 7 +- tests/data/expression.diff | 9 +- tests/data/expression.py | 5 +- tests/data/function.py | 8 +- tests/data/function2.py | 5 +- tests/data/function_trailing_comma.py | 8 +- 8 files changed, 150 insertions(+), 151 deletions(-) diff --git a/black.py b/black.py index b6baa76a6b4..6a332eaf0c1 100644 --- a/black.py +++ b/black.py @@ -149,8 +149,14 @@ class Feature(Enum): VERSION_TO_FEATURES: Dict[TargetVersion, Set[Feature]] = { TargetVersion.PY27: {Feature.ASYNC_IDENTIFIERS}, - TargetVersion.PY33: {Feature.UNICODE_LITERALS, Feature.ASYNC_IDENTIFIERS}, - TargetVersion.PY34: {Feature.UNICODE_LITERALS, Feature.ASYNC_IDENTIFIERS}, + TargetVersion.PY33: { + Feature.UNICODE_LITERALS, + Feature.ASYNC_IDENTIFIERS, + }, + TargetVersion.PY34: { + Feature.UNICODE_LITERALS, + Feature.ASYNC_IDENTIFIERS, + }, TargetVersion.PY35: { Feature.UNICODE_LITERALS, Feature.TRAILING_COMMA_IN_CALL, @@ -1409,83 +1415,59 @@ def should_be_rendered_as_single_line(self, line_length: int) -> bool: return False optional_trailing_comma_index = self.get_optional_trailing_comma_index() - if optional_trailing_comma_index is not None and not self.is_def: + if optional_trailing_comma_index is not None: return False return True - def _get_top_level_collection_indexes(self) -> Optional[Tuple[int, int, List[int]]]: + def _get_collection_comma_indexes( + self, opener_index: int, closer_index: int + ) -> List[int]: """ - Get top level colelction opener, closer and comma indexes. - - - if top-level collection is not found - return `None` - - if there is more than one top-level collection - return `None` - - if brackets are invalid - return `None` - - if collection has visible brackets - `opener_index` and `closer_index` - point to them - - if collection has only invisible brackets - `opener_index` and `closer_index` - point to them - - `comma_indexes` include only top-level collection comma indexes, - nested are not included + Get top level comma indexes in `leaves`. + + Arguments: + opener_index -- Index of opening bracket in `leaves`. + closer_index -- Index of opening bracket in `leaves`. Returns: - A tuple of `opener_index`, `closer_index` and `comma_indexes` or `None`. + A list of top-level comma indexes in `leaves`. """ - opener: Optional[Leaf] = None - closer: Optional[Leaf] = None - opener_index: int = 0 - closer_index: int = 0 - comma_indexes: List[int] = [] + result: List[int] = [] + depth_counter = 0 - for leaf_index, leaf in enumerate(self.leaves): - # use comma indexes only on in the top-level collection - if depth_counter < 2 and leaf.type == token.COMMA: - comma_indexes.append(leaf_index) + for collection_leaf_index, leaf in enumerate( + self.leaves[opener_index + 1 : closer_index] + ): + # use comma indexes only on the top-level + if depth_counter == 0 and leaf.type == token.COMMA: + result.append(opener_index + collection_leaf_index + 1) if leaf.type in OPENING_BRACKETS: - # more that one top-level collection - if depth_counter == 0 and opener is not None and opener.value: - return None - - # do not increase depth in bracket is invisible - if leaf.value: - depth_counter += 1 - - # visible opener already found, skip the rest - if opener and opener.value: - continue - - # this opener is not right after the previous one, skip it - if opener and leaf_index > opener_index + 1: - continue - - opener_index = leaf_index - opener = leaf + depth_counter += 1 if leaf.type in CLOSING_BRACKETS: - # do not decrease depth in bracket is invisible - if leaf.value: - depth_counter -= 1 + depth_counter -= 1 - # brackets are not valid - if depth_counter < 0: - return None - - # visible closer already found, skip invisible - if closer and not leaf.value and leaf_index == closer_index + 1: - continue + return result - closer = leaf - closer_index = leaf_index + def get_leaf_index(self, leaf: Leaf) -> Optional[int]: + """ + Get leaf index in `leaves`. - return opener_index, closer_index, comma_indexes + Arguments: + leaf -- Line leaf. - def get_optional_trailing_comma_index(self) -> Optional[int]: + Returns: + Leaf index in `leaves` or `None`. """ - Get index of the top-level optional trailing comma or `None`. + for leaf_index, line_leaf in enumerate(self.leaves): + if leaf is line_leaf: + return leaf_index - Does not lookup trailing commas if line: + return None - - is inside brackets - - has less than 4 leaves + def get_optional_trailing_comma_index(self, max_depth: int = 0) -> Optional[int]: + """ + Get index of the top-level optional trailing comma or `None`. Index can be found for: @@ -1498,37 +1480,52 @@ def get_optional_trailing_comma_index(self) -> Optional[int]: Note: the trailing comma in a 1-tuple and a 1-subscript is not optional. """ - if self.inside_brackets: - return None + top_comma_indexes = self._get_collection_comma_indexes(0, len(self.leaves)) + if top_comma_indexes and len(top_comma_indexes) > 1: + last_comma_index = top_comma_indexes[-1] + if last_comma_index == len(self.leaves) - 1: + return last_comma_index - if not self.leaves or len(self.leaves) < 4: - return None - - top_level_collection_indexes = self._get_top_level_collection_indexes() - - # no top level collection found or multiple collections - if top_level_collection_indexes is None: - return None + depth = 0 + for leaf_reversed_index, leaf in enumerate(reversed(self.leaves)): + if leaf.type in CLOSING_BRACKETS: + depth += 1 + if leaf.type in OPENING_BRACKETS: + depth -= 1 + if max_depth and depth > max_depth: + continue + if leaf.type not in CLOSING_BRACKETS: + continue - opener_index, closer_index, comma_indexes = top_level_collection_indexes - opener = self.leaves[opener_index] + closer_index = len(self.leaves) - leaf_reversed_index - 1 + opener_index = self.get_leaf_index(leaf.opening_bracket) + if opener_index is None: + continue - # no commas found in collection - if not comma_indexes: - return None + comma_indexes = self._get_collection_comma_indexes( + opener_index, closer_index + ) + if not comma_indexes: + continue - # 1-item tuple or subscript - if opener.type == token.LPAR and len(comma_indexes) == 1: - if not self.is_def and not self.is_import: - return None + last_comma_index = comma_indexes[-1] + if last_comma_index != closer_index - 1: + continue - last_comma_index = comma_indexes[-1] + # potential 1-item subscript + if leaf.type == token.RSQB and len(comma_indexes) == 1 and opener_index > 0: + head_index = opener_index - 1 + head = self.leaves[head_index] + # it is a 1-item subscript + if head.type in SUBSCRIPT_HEADS: + continue - # last comma is not just before closing bracket - if last_comma_index != closer_index - 1: - return None + is_def = self.is_def and depth == 1 + if leaf.type == token.RPAR and not is_def and len(comma_indexes) == 1: + continue + return last_comma_index - return last_comma_index + return None @property def is_def(self) -> bool: @@ -1583,35 +1580,15 @@ def contains_standalone_comments(self, depth_limit: int = sys.maxsize) -> bool: return False def contains_uncollapsable_type_comments(self) -> bool: - ignored_ids = set() - try: - last_leaf = self.leaves[-1] - ignored_ids.add(id(last_leaf)) - if last_leaf.type == token.COMMA or ( - last_leaf.type == token.RPAR and not last_leaf.value - ): - # When trailing commas or optional parens are inserted by Black for - # consistency, comments after the previous last element are not moved - # (they don't have to, rendering will still be correct). So we ignore - # trailing commas and invisible. - last_leaf = self.leaves[-2] - ignored_ids.add(id(last_leaf)) - except IndexError: - return False - # A type comment is uncollapsable if it is attached to a leaf # that isn't at the end of the line (since that could cause it # to get associated to a different argument) or if there are # comments before it (since that could cause it to get hidden # behind a comment. - comment_seen = False - for leaf_id, comments in self.comments.items(): + for comments in self.comments.values(): for comment in comments: if is_type_comment(comment): - if leaf_id not in ignored_ids or comment_seen: - return True - - comment_seen = True + return True return False @@ -2131,6 +2108,7 @@ def __post_init__(self) -> None: CLOSING_BRACKETS = set(BRACKET.values()) BRACKETS = OPENING_BRACKETS | CLOSING_BRACKETS ALWAYS_NO_SPACE = CLOSING_BRACKETS | {token.COMMA, STANDALONE_COMMENT} +SUBSCRIPT_HEADS = CLOSING_BRACKETS | {token.STRING, token.NAME} def whitespace(leaf: Leaf, *, complex_subscript: bool) -> str: # noqa: C901 @@ -2633,6 +2611,7 @@ def split_line_side( inner: bool = False, features: Collection[Feature] = (), split_funcs: Collection[SplitFunc] = (), + inside_blackets_split_funcs: Collection[SplitFunc] = (), ) -> Iterator[Line]: """ Split LHS or RHS line. @@ -2662,17 +2641,15 @@ def split_line_side( """ single_line_length = min(first_line_length, last_line_length) if line.should_be_rendered_as_single_line(single_line_length): - # remove trailing comma from function definitions - if line.is_def: - optional_trailing_comma_index = line.get_optional_trailing_comma_index() - if optional_trailing_comma_index is not None: - line.leaves[optional_trailing_comma_index].value = "" - yield line return + line_split_funcs = split_funcs + if line.inside_brackets: + line_split_funcs = [*inside_blackets_split_funcs, *split_funcs] + line_str = str(line).strip("\n") - for split_func in split_funcs: + for split_func in line_split_funcs: # We are accumulating lines in `result` because we might want to abort # mission and return the original line in the end, or attempt a different # split altogether. @@ -2684,16 +2661,20 @@ def split_line_side( if str(sub_line).strip("\n") == line_str: raise CannotSplit("Split function returned an unchanged result") - sub_line_length = first_line_length + first_sub_line_length = first_line_length if sub_line_index > 0: - sub_line_length = line_length + first_sub_line_length = line_length result.extend( - split_line( + split_line_side( sub_line, - line_length=sub_line_length, + line_length=line_length, + first_line_length=first_sub_line_length, + last_line_length=line_length, inner=True, features=features, + split_funcs=split_funcs, + inside_blackets_split_funcs=inside_blackets_split_funcs, ) ) except CannotSplit: @@ -2768,9 +2749,6 @@ def split_line( split_funcs: List[SplitFunc] result: List[Line] = [] if left_hand_side: - split_funcs = [left_hand_split] - if line.inside_brackets: - split_funcs = [delimiter_split, standalone_comment_split, left_hand_split] last_line_length = line_length if right_hand_side and right_hand_side.leaves: if right_hand_side.get_unsplittable_type_ignore(): @@ -2785,7 +2763,8 @@ def split_line( last_line_length=last_line_length, inner=inner, features=features, - split_funcs=split_funcs, + split_funcs=[left_hand_split], + inside_blackets_split_funcs=[delimiter_split, standalone_comment_split], ): result.append(left_hand_side_line) @@ -2795,13 +2774,6 @@ def split_line( first_line_length = max( line_length - len(str(result[-1]).rstrip("\n").lstrip()), 1 ) - split_funcs = [right_hand_split_with_omit] - if line.inside_brackets: - split_funcs = [ - delimiter_split, - standalone_comment_split, - right_hand_split_with_omit, - ] for line_index, right_hand_side_line in enumerate( split_line_side( line=right_hand_side, @@ -2810,7 +2782,8 @@ def split_line( last_line_length=line_length, inner=inner, features=features, - split_funcs=split_funcs, + split_funcs=[right_hand_split_with_omit], + inside_blackets_split_funcs=[delimiter_split, standalone_comment_split], ) ): if line_index == 0 and result: diff --git a/tests/data/collections.py b/tests/data/collections.py index c821099a4c5..50b93e1d1b5 100644 --- a/tests/data/collections.py +++ b/tests/data/collections.py @@ -69,11 +69,11 @@ ec2client.get_waiter('instance_stopped').wait( InstanceIds=[instance.id], WaiterConfig={ - 'Delay': 5, + 'Delay': 5 }) ec2client.get_waiter("instance_stopped").wait( InstanceIds=[instance.id], - WaiterConfig={"Delay": 5,}, + WaiterConfig={"Delay": 5}, ) ec2client.get_waiter("instance_stopped").wait( InstanceIds=[instance.id], WaiterConfig={"Delay": 5,}, @@ -156,15 +156,17 @@ subscript_multiple_items[1, 2, {3: 4}] subscript_one_item = subscript_one_item[1] subscript_multiple_items = subscript_multiple_items[1, 2, {3: 4}] -subscript_one_item[ - 1, -] +subscript_one_item[1,] subscript_multiple_items[ - 1, 2, {3: 4}, + 1, + 2, + {3: 4}, ] subscript_one_item = subscript_one_item[1,] subscript_multiple_items = subscript_multiple_items[ - 1, 2, {3: 4}, + 1, + 2, + {3: 4}, ] if True: @@ -175,11 +177,15 @@ if True: ec2client.get_waiter("instance_stopped").wait( - InstanceIds=[instance.id], WaiterConfig={"Delay": 5,} + InstanceIds=[instance.id], WaiterConfig={"Delay": 5} ) ec2client.get_waiter("instance_stopped").wait( - InstanceIds=[instance.id], WaiterConfig={"Delay": 5,}, + InstanceIds=[instance.id], + WaiterConfig={"Delay": 5}, ) ec2client.get_waiter("instance_stopped").wait( - InstanceIds=[instance.id], WaiterConfig={"Delay": 5,}, + InstanceIds=[instance.id], + WaiterConfig={ + "Delay": 5, + }, ) diff --git a/tests/data/comments7.py b/tests/data/comments7.py index 948b3b03a2b..1f340cb5a29 100644 --- a/tests/data/comments7.py +++ b/tests/data/comments7.py @@ -94,7 +94,12 @@ def func(): def func(): c = call( - 0.0123, 0.0456, 0.0789, 0.0123, 0.0789, a[-1], # type: ignore + 0.0123, + 0.0456, + 0.0789, + 0.0123, + 0.0789, + a[-1], # type: ignore ) # The type: ignore exception only applies to line length, not diff --git a/tests/data/expression.diff b/tests/data/expression.diff index a83fa420ce1..e3c603e1433 100644 --- a/tests/data/expression.diff +++ b/tests/data/expression.diff @@ -130,7 +130,7 @@ call(**self.screen_kwargs) call(b, **self.screen_kwargs) lukasz.langa.pl -@@ -94,11 +127,13 @@ +@@ -94,11 +127,16 @@ 1.0 .real ....__class__ list[str] @@ -138,14 +138,17 @@ tuple[str, ...] -tuple[str, int, float, dict[str, int],] +tuple[ -+ str, int, float, dict[str, int], ++ str, ++ int, ++ float, ++ dict[str, int], +] very_long_variable_name_filters: t.List[ t.Tuple[str, t.Union[str, t.List[t.Optional[str]]]], ] xxxx_xxxxx_xxxx_xxx: Callable[..., List[SomeClass]] = classmethod( # type: ignore sync(async_xxxx_xxx_xxxx_xxxxx_xxxx_xxx.__func__) -@@ -132,113 +167,171 @@ +@@ -132,113 +170,171 @@ numpy[-(c + 1) :, d] numpy[:, l[-2]] numpy[:, ::-1] diff --git a/tests/data/expression.py b/tests/data/expression.py index b9e21879c53..c13280e4a1e 100644 --- a/tests/data/expression.py +++ b/tests/data/expression.py @@ -378,7 +378,10 @@ async def f(): dict[str, int] tuple[str, ...] tuple[ - str, int, float, dict[str, int], + str, + int, + float, + dict[str, int], ] very_long_variable_name_filters: t.List[ t.Tuple[str, t.Union[str, t.List[t.Optional[str]]]], diff --git a/tests/data/function.py b/tests/data/function.py index 4754588e38d..c78ed4a1d3a 100644 --- a/tests/data/function.py +++ b/tests/data/function.py @@ -82,8 +82,7 @@ def trailing_comma(): D: 0.1 * (10.0 / 12), } def f( - a, - **kwargs, + a, **kwargs, ) -> A: return ( yield from A( @@ -230,7 +229,10 @@ def trailing_comma(): } -def f(a, **kwargs) -> A: +def f( + a, + **kwargs, +) -> A: return ( yield from A( very_long_argument_name1=very_long_value_for_the_argument, diff --git a/tests/data/function2.py b/tests/data/function2.py index e08f62df1fa..cfc259ea7bd 100644 --- a/tests/data/function2.py +++ b/tests/data/function2.py @@ -25,7 +25,10 @@ def inner(): # output -def f(a, **kwargs) -> A: +def f( + a, + **kwargs, +) -> A: with cache_dir(): if something: result = CliRunner().invoke( diff --git a/tests/data/function_trailing_comma.py b/tests/data/function_trailing_comma.py index 94684f49cc1..0d2c3a18ffb 100644 --- a/tests/data/function_trailing_comma.py +++ b/tests/data/function_trailing_comma.py @@ -11,11 +11,15 @@ def xxxxxxxxxxxxxxxxxxxxxxxxxxxx() -> Set[ # output -def f(a): +def f( + a, +): ... -def f(a: int = 1): +def f( + a: int = 1, +): ... From 42923a244179e87a7c53ac05c437c2c0eff69900 Mon Sep 17 00:00:00 2001 From: Vlad Emelianov Date: Thu, 21 Nov 2019 00:20:56 +0100 Subject: [PATCH 26/30] Update driver to pass tests --- blib2to3/pgen2/driver.py | 5 ++++- 1 file changed, 4 insertions(+), 1 deletion(-) diff --git a/blib2to3/pgen2/driver.py b/blib2to3/pgen2/driver.py index 052c94883cf..a488383f1fb 100644 --- a/blib2to3/pgen2/driver.py +++ b/blib2to3/pgen2/driver.py @@ -128,7 +128,10 @@ def parse_stream(self, stream: IO[Text], debug: bool = False) -> NL: return self.parse_stream_raw(stream, debug) def parse_file( - self, filename: Path, encoding: Optional[Text] = None, debug: bool = False, + self, + filename: Path, + encoding: Optional[Text] = None, + debug: bool = False, ) -> NL: """Parse a file and return the syntax tree.""" with io.open(filename, "r", encoding=encoding) as stream: From 7c6d774d65d3c614b2c0712d14c5a533716a2b5e Mon Sep 17 00:00:00 2001 From: Vlad Emelianov Date: Sat, 23 Nov 2019 05:51:23 +0100 Subject: [PATCH 27/30] Fix typo --- black.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/black.py b/black.py index 6a332eaf0c1..c346fead478 100644 --- a/black.py +++ b/black.py @@ -1274,7 +1274,7 @@ def is_stub_class(self) -> bool: def _get_assignment_index(self) -> Optional[int]: """ - Get fthe first unbracketed assignment index. + Get the first unbracketed assignment index. """ depth = 0 for leaf_index, leaf in enumerate(self.leaves): From f147295221f8f06f61ce7c6e7e025eb87f8d4fff Mon Sep 17 00:00:00 2001 From: Vlad Emelianov Date: Sat, 23 Nov 2019 05:56:24 +0100 Subject: [PATCH 28/30] Address reviews comments --- black.py | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/black.py b/black.py index c346fead478..0856b364117 100644 --- a/black.py +++ b/black.py @@ -1294,7 +1294,7 @@ def get_source_line_numbers(self) -> Set[int]: Can be used to check if line has multiple lines in source. """ - return set([i.lineno for i in self.leaves if i.lineno]) + return {i.lineno for i in self.leaves if i.lineno} def get_left_hand_side(self) -> Optional["Line"]: """ @@ -4291,7 +4291,7 @@ def enumerate_with_length( yield index, leaf, length -def is_line_short_enough(line: Line, line_length: int) -> bool: +def is_line_short_enough(line: Line, *, line_length: int) -> bool: """Return True if `line` is no longer than `line_length`. Uses the provided `line_str` rendering, if any, otherwise computes a new one. From c11855e68b3b6aa27c773633943bd096fc082455 Mon Sep 17 00:00:00 2001 From: Vlad Emelianov Date: Sat, 23 Nov 2019 05:59:27 +0100 Subject: [PATCH 29/30] Revert VERSION_TO_FEATURES formatting --- black.py | 10 ++-------- 1 file changed, 2 insertions(+), 8 deletions(-) diff --git a/black.py b/black.py index 0856b364117..260f9da4ec4 100644 --- a/black.py +++ b/black.py @@ -149,14 +149,8 @@ class Feature(Enum): VERSION_TO_FEATURES: Dict[TargetVersion, Set[Feature]] = { TargetVersion.PY27: {Feature.ASYNC_IDENTIFIERS}, - TargetVersion.PY33: { - Feature.UNICODE_LITERALS, - Feature.ASYNC_IDENTIFIERS, - }, - TargetVersion.PY34: { - Feature.UNICODE_LITERALS, - Feature.ASYNC_IDENTIFIERS, - }, + TargetVersion.PY33: {Feature.UNICODE_LITERALS, Feature.ASYNC_IDENTIFIERS}, + TargetVersion.PY34: {Feature.UNICODE_LITERALS, Feature.ASYNC_IDENTIFIERS}, TargetVersion.PY35: { Feature.UNICODE_LITERALS, Feature.TRAILING_COMMA_IN_CALL, From 7e516d98183902a5a7931c46ab69a27eafbf46b3 Mon Sep 17 00:00:00 2001 From: Vlad Emelianov Date: Sat, 23 Nov 2019 06:04:46 +0100 Subject: [PATCH 30/30] Reverted driver formatting --- blib2to3/pgen2/driver.py | 5 +---- 1 file changed, 1 insertion(+), 4 deletions(-) diff --git a/blib2to3/pgen2/driver.py b/blib2to3/pgen2/driver.py index a488383f1fb..81940f78f0f 100644 --- a/blib2to3/pgen2/driver.py +++ b/blib2to3/pgen2/driver.py @@ -128,10 +128,7 @@ def parse_stream(self, stream: IO[Text], debug: bool = False) -> NL: return self.parse_stream_raw(stream, debug) def parse_file( - self, - filename: Path, - encoding: Optional[Text] = None, - debug: bool = False, + self, filename: Path, encoding: Optional[Text] = None, debug: bool = False ) -> NL: """Parse a file and return the syntax tree.""" with io.open(filename, "r", encoding=encoding) as stream: