From 033057c39d4e87296866e3ca88dbd2b3c70d586c Mon Sep 17 00:00:00 2001 From: mainj12 Date: Tue, 22 Nov 2022 18:02:00 +0000 Subject: [PATCH 1/3] Adding trailing commas to dictionaries when comments are present at the end --- CHANGES.md | 2 + src/black/linegen.py | 38 ++++++++++---- .../simple_cases/function_trailing_comma.py | 52 +++++++++++++++++++ 3 files changed, 83 insertions(+), 9 deletions(-) diff --git a/CHANGES.md b/CHANGES.md index 992b3800405..2c2fb7c5732 100644 --- a/CHANGES.md +++ b/CHANGES.md @@ -21,6 +21,8 @@ parentheses (#3307) - Correctly handle trailing commas that are inside a line's leading non-nested parens (#3370) +- Correctly handle trailing commas inside dictionaries where a comment is after the + final entry (#3393) ### Configuration diff --git a/src/black/linegen.py b/src/black/linegen.py index 219495e9a5e..2d968ab3c80 100644 --- a/src/black/linegen.py +++ b/src/black/linegen.py @@ -830,6 +830,25 @@ def split_wrapper(line: Line, features: Collection[Feature] = ()) -> Iterator[Li return split_wrapper +def _get_last_non_comment_leaf(line: Line) -> Optional[int]: + for leaf_idx in range(len(line.leaves) - 1, 0, -1): + if line.leaves[leaf_idx].type != STANDALONE_COMMENT: + return leaf_idx + return None + + +def _safe_add_trailing_comma(safe: bool, delimiter_priority: int, line: Line) -> Line: + if ( + safe + and delimiter_priority == COMMA_PRIORITY + and line.leaves[-1].type != token.COMMA + and line.leaves[-1].type != STANDALONE_COMMENT + ): + new_comma = Leaf(token.COMMA, ",") + line.append(new_comma) + return line + + @dont_increase_indentation def delimiter_split(line: Line, features: Collection[Feature] = ()) -> Iterator[Line]: """Split according to delimiters of the highest priority. @@ -871,7 +890,8 @@ def append_to_line(leaf: Leaf) -> Iterator[Line]: ) current_line.append(leaf) - for leaf in line.leaves: + last_non_comment_leaf = _get_last_non_comment_leaf(line) + for leaf_idx, leaf in enumerate(line.leaves): yield from append_to_line(leaf) for comment_after in line.comments_after(leaf): @@ -888,6 +908,11 @@ def append_to_line(leaf: Leaf) -> Iterator[Line]: trailing_comma_safe and Feature.TRAILING_COMMA_IN_CALL in features ) + if last_leaf.type == STANDALONE_COMMENT and leaf_idx == last_non_comment_leaf: + current_line = _safe_add_trailing_comma( + trailing_comma_safe, delimiter_priority, current_line + ) + leaf_priority = bt.delimiters.get(id(leaf)) if leaf_priority == delimiter_priority: yield current_line @@ -896,14 +921,9 @@ def append_to_line(leaf: Leaf) -> Iterator[Line]: mode=line.mode, depth=line.depth, inside_brackets=line.inside_brackets ) if current_line: - if ( - trailing_comma_safe - and delimiter_priority == COMMA_PRIORITY - and current_line.leaves[-1].type != token.COMMA - and current_line.leaves[-1].type != STANDALONE_COMMENT - ): - new_comma = Leaf(token.COMMA, ",") - current_line.append(new_comma) + current_line = _safe_add_trailing_comma( + trailing_comma_safe, delimiter_priority, current_line + ) yield current_line diff --git a/tests/data/simple_cases/function_trailing_comma.py b/tests/data/simple_cases/function_trailing_comma.py index abe9617c0e9..2040d501fc2 100644 --- a/tests/data/simple_cases/function_trailing_comma.py +++ b/tests/data/simple_cases/function_trailing_comma.py @@ -1,5 +1,29 @@ def f(a,): d = {'key': 'value',} + e = { + "a": fun(msg, "ts"), + "longggggggggggggggid": ..., + "longgggggggggggggggggggkey": ..., "created": ... + # "longkey": ... + } + f = [ + arg1, + arg2, + arg3, arg4 + # comment + ] + g = ( + arg1, + arg2, + arg3, arg4 + # comment + ) + h = { + arg1, + arg2, + arg3, arg4 + # comment + } tup = (1,) def f2(a,b,): @@ -68,6 +92,34 @@ def f( d = { "key": "value", } + e = { + "a": fun(msg, "ts"), + "longggggggggggggggid": ..., + "longgggggggggggggggggggkey": ..., + "created": ..., + # "longkey": ... + } + f = [ + arg1, + arg2, + arg3, + arg4, + # comment + ] + g = ( + arg1, + arg2, + arg3, + arg4, + # comment + ) + h = { + arg1, + arg2, + arg3, + arg4, + # comment + } tup = (1,) From 379bbc56c9aa6af786499d4de087e0c7d8cabc40 Mon Sep 17 00:00:00 2001 From: Richard Si Date: Sat, 4 Feb 2023 21:27:01 -0500 Subject: [PATCH 2/3] Only enable under preview This was annoying to change because I had to first allow transformers to also receive a Mode() argument. --- src/black/linegen.py | 26 ++++++--- src/black/mode.py | 1 + src/black/trans.py | 12 ++-- tests/data/preview/trailing_comma.py | 55 +++++++++++++++++++ .../targeting_py39.py | 2 +- .../simple_cases/function_trailing_comma.py | 52 ------------------ 6 files changed, 83 insertions(+), 65 deletions(-) create mode 100644 tests/data/preview/trailing_comma.py diff --git a/src/black/linegen.py b/src/black/linegen.py index 17d0f5ebf12..c582b2d6dff 100644 --- a/src/black/linegen.py +++ b/src/black/linegen.py @@ -520,7 +520,7 @@ def transform_line( else: def _rhs( - self: object, line: Line, features: Collection[Feature] + self: object, line: Line, features: Collection[Feature], mode: Mode ) -> Iterator[Line]: """Wraps calls to `right_hand_split`. @@ -604,7 +604,9 @@ class _BracketSplitComponent(Enum): tail = auto() -def left_hand_split(line: Line, _features: Collection[Feature] = ()) -> Iterator[Line]: +def left_hand_split( + line: Line, _features: Collection[Feature], mode: Mode +) -> 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. @@ -940,8 +942,10 @@ def dont_increase_indentation(split_func: Transformer) -> Transformer: """ @wraps(split_func) - def split_wrapper(line: Line, features: Collection[Feature] = ()) -> Iterator[Line]: - for split_line in split_func(line, features): + def split_wrapper( + line: Line, features: Collection[Feature], mode: Mode + ) -> Iterator[Line]: + for split_line in split_func(line, features, mode): normalize_prefix(split_line.leaves[0], inside_brackets=True) yield split_line @@ -968,7 +972,9 @@ def _safe_add_trailing_comma(safe: bool, delimiter_priority: int, line: Line) -> @dont_increase_indentation -def delimiter_split(line: Line, features: Collection[Feature] = ()) -> Iterator[Line]: +def delimiter_split( + line: Line, features: Collection[Feature], mode: Mode +) -> Iterator[Line]: """Split according to delimiters of the highest priority. If the appropriate Features are given, the split will add trailing commas @@ -1026,7 +1032,11 @@ def append_to_line(leaf: Leaf) -> Iterator[Line]: trailing_comma_safe and Feature.TRAILING_COMMA_IN_CALL in features ) - if last_leaf.type == STANDALONE_COMMENT and leaf_idx == last_non_comment_leaf: + if ( + Preview.add_trailing_comma_consistently in mode + and last_leaf.type == STANDALONE_COMMENT + and leaf_idx == last_non_comment_leaf + ): current_line = _safe_add_trailing_comma( trailing_comma_safe, delimiter_priority, current_line ) @@ -1047,7 +1057,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], mode: Mode ) -> Iterator[Line]: """Split standalone comments from the rest of the line.""" if not line.contains_standalone_comments(0): @@ -1500,7 +1510,7 @@ def run_transformer( if not line_str: line_str = line_to_string(line) result: List[Line] = [] - for transformed_line in transform(line, features): + for transformed_line in transform(line, features, mode): if str(transformed_line).strip("\n") == line_str: raise CannotTransform("Line transformer returned an unchanged result") diff --git a/src/black/mode.py b/src/black/mode.py index 1af16070073..c9a4c2b080c 100644 --- a/src/black/mode.py +++ b/src/black/mode.py @@ -153,6 +153,7 @@ def supports_feature(target_versions: Set[TargetVersion], feature: Feature) -> b class Preview(Enum): """Individual preview style features.""" + add_trailing_comma_consistently = auto() hex_codes_in_unicode_sequences = auto() prefer_splitting_right_hand_side_of_assignments = auto() # NOTE: string_processing requires wrap_long_dict_values_in_parens diff --git a/src/black/trans.py b/src/black/trans.py index 2360c13f06a..a6a416e71bc 100644 --- a/src/black/trans.py +++ b/src/black/trans.py @@ -32,7 +32,7 @@ from black.comments import contains_pragma_comment from black.lines import Line, append_leaves -from black.mode import Feature +from black.mode import Feature, Mode from black.nodes import ( CLOSING_BRACKETS, OPENING_BRACKETS, @@ -63,7 +63,7 @@ class CannotTransform(Exception): # types T = TypeVar("T") LN = Union[Leaf, Node] -Transformer = Callable[[Line, Collection[Feature]], Iterator[Line]] +Transformer = Callable[[Line, Collection[Feature], Mode], Iterator[Line]] Index = int NodeType = int ParserState = int @@ -81,7 +81,9 @@ def TErr(err_msg: str) -> Err[CannotTransform]: return Err(cant_transform) -def hug_power_op(line: Line, features: Collection[Feature]) -> Iterator[Line]: +def hug_power_op( + line: Line, features: Collection[Feature], mode: Mode +) -> Iterator[Line]: """A transformer which normalizes spacing around power operators.""" # Performance optimization to avoid unnecessary Leaf clones and other ops. @@ -228,7 +230,9 @@ def do_transform( yield an CannotTransform after that point.) """ - def __call__(self, line: Line, _features: Collection[Feature]) -> Iterator[Line]: + def __call__( + self, line: Line, _features: Collection[Feature], _mode: Mode + ) -> Iterator[Line]: """ StringTransformer instances have a call signature that mirrors that of the Transformer type. diff --git a/tests/data/preview/trailing_comma.py b/tests/data/preview/trailing_comma.py new file mode 100644 index 00000000000..5b09c664606 --- /dev/null +++ b/tests/data/preview/trailing_comma.py @@ -0,0 +1,55 @@ +e = { + "a": fun(msg, "ts"), + "longggggggggggggggid": ..., + "longgggggggggggggggggggkey": ..., "created": ... + # "longkey": ... +} +f = [ + arg1, + arg2, + arg3, arg4 + # comment +] +g = ( + arg1, + arg2, + arg3, arg4 + # comment +) +h = { + arg1, + arg2, + arg3, arg4 + # comment +} + +# output + +e = { + "a": fun(msg, "ts"), + "longggggggggggggggid": ..., + "longgggggggggggggggggggkey": ..., + "created": ..., + # "longkey": ... +} +f = [ + arg1, + arg2, + arg3, + arg4, + # comment +] +g = ( + arg1, + arg2, + arg3, + arg4, + # comment +) +h = { + arg1, + arg2, + arg3, + arg4, + # comment +} diff --git a/tests/data/preview_context_managers/targeting_py39.py b/tests/data/preview_context_managers/targeting_py39.py index 5cb8763040a..64f5d09bbe8 100644 --- a/tests/data/preview_context_managers/targeting_py39.py +++ b/tests/data/preview_context_managers/targeting_py39.py @@ -84,7 +84,7 @@ # First comment. new_new_new1() as cm1, # Second comment. - new_new_new2() + new_new_new2(), # Last comment. ): pass diff --git a/tests/data/simple_cases/function_trailing_comma.py b/tests/data/simple_cases/function_trailing_comma.py index b873e735c4d..92f46e27516 100644 --- a/tests/data/simple_cases/function_trailing_comma.py +++ b/tests/data/simple_cases/function_trailing_comma.py @@ -1,29 +1,5 @@ def f(a,): d = {'key': 'value',} - e = { - "a": fun(msg, "ts"), - "longggggggggggggggid": ..., - "longgggggggggggggggggggkey": ..., "created": ... - # "longkey": ... - } - f = [ - arg1, - arg2, - arg3, arg4 - # comment - ] - g = ( - arg1, - arg2, - arg3, arg4 - # comment - ) - h = { - arg1, - arg2, - arg3, arg4 - # comment - } tup = (1,) def f2(a,b,): @@ -92,34 +68,6 @@ def f( d = { "key": "value", } - e = { - "a": fun(msg, "ts"), - "longggggggggggggggid": ..., - "longgggggggggggggggggggkey": ..., - "created": ..., - # "longkey": ... - } - f = [ - arg1, - arg2, - arg3, - arg4, - # comment - ] - g = ( - arg1, - arg2, - arg3, - arg4, - # comment - ) - h = { - arg1, - arg2, - arg3, - arg4, - # comment - } tup = (1,) From aa6f08232160e76f62e8d36ede35f809fec21b88 Mon Sep 17 00:00:00 2001 From: Richard Si Date: Sat, 4 Feb 2023 21:30:50 -0500 Subject: [PATCH 3/3] Reword changelog entry --- CHANGES.md | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/CHANGES.md b/CHANGES.md index 517c20facce..a8b556cb7e8 100644 --- a/CHANGES.md +++ b/CHANGES.md @@ -14,8 +14,8 @@ -- Correctly handle trailing commas inside dictionaries where a comment is after the - final entry (#3393) +- Add trailing commas to collection literals even if there's a comment after the last + entry (#3393) ### Configuration