From ff53fc1b97b98075f92fcc954ed81b9252e7c9c1 Mon Sep 17 00:00:00 2001 From: mainj12 <118842653+mainj12@users.noreply.github.com> Date: Sun, 5 Feb 2023 03:35:43 +0000 Subject: [PATCH] Actually add trailing commas to collection literals even if there are terminating comments (#3393) Co-authored-by: Jelle Zijlstra Co-authored-by: Richard Si --- CHANGES.md | 3 + src/black/linegen.py | 62 ++++++++++++++----- src/black/mode.py | 1 + src/black/trans.py | 12 ++-- tests/data/preview/trailing_comma.py | 55 ++++++++++++++++ .../targeting_py39.py | 2 +- 6 files changed, 114 insertions(+), 21 deletions(-) create mode 100644 tests/data/preview/trailing_comma.py diff --git a/CHANGES.md b/CHANGES.md index 2071eb3..a8b556c 100644 --- a/CHANGES.md +++ b/CHANGES.md @@ -14,6 +14,9 @@ +- Add trailing commas to collection literals even if there's a comment after the last + entry (#3393) + ### Configuration diff --git a/src/black/linegen.py b/src/black/linegen.py index 7afb173..c582b2d 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,16 +942,39 @@ 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 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]: +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 @@ -989,7 +1014,8 @@ def delimiter_split(line: Line, features: Collection[Feature] = ()) -> Iterator[ ) 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): @@ -1006,6 +1032,15 @@ def delimiter_split(line: Line, features: Collection[Feature] = ()) -> Iterator[ trailing_comma_safe and Feature.TRAILING_COMMA_IN_CALL in features ) + 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 + ) + leaf_priority = bt.delimiters.get(id(leaf)) if leaf_priority == delimiter_priority: yield current_line @@ -1014,20 +1049,15 @@ def delimiter_split(line: Line, features: Collection[Feature] = ()) -> Iterator[ 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 @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): @@ -1480,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 1af1607..c9a4c2b 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 2360c13..a6a416e 100644 --- a/src/black/trans.py +++ b/src/black/trans.py @@ -32,7 +32,7 @@ from mypy_extensions import trait 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 @@ class StringTransformer(ABC): 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 0000000..5b09c66 --- /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 5cb8763..64f5d09 100644 --- a/tests/data/preview_context_managers/targeting_py39.py +++ b/tests/data/preview_context_managers/targeting_py39.py @@ -84,7 +84,7 @@ with ( # First comment. new_new_new1() as cm1, # Second comment. - new_new_new2() + new_new_new2(), # Last comment. ): pass -- 2.39.2