From 79313b79557d6d1434f57413bd3d415418a867e8 Mon Sep 17 00:00:00 2001 From: Henri Holopainen Date: Fri, 20 Oct 2023 18:02:15 +0300 Subject: [PATCH 01/10] Add test cases --- tests/data/cases/fmtskip9.py | 19 +++++++++++++++++++ 1 file changed, 19 insertions(+) create mode 100644 tests/data/cases/fmtskip9.py diff --git a/tests/data/cases/fmtskip9.py b/tests/data/cases/fmtskip9.py new file mode 100644 index 00000000000..7212740fc42 --- /dev/null +++ b/tests/data/cases/fmtskip9.py @@ -0,0 +1,19 @@ +foo = 123 # fmt: skip # noqa: E501 # pylint +bar = ( + 123 , + ( 1 + 5 ) # pylint # fmt:skip +) +baz = "a" + "b" # pylint; fmt: skip; noqa: E501 +skip_will_not_work = "a" + "b" # pylint fmt:skip +skip_will_not_work2 = "a" + "b" # some text; fmt:skip happens to be part of it + +# output + +foo = 123 # fmt: skip # noqa: E501 # pylint +bar = ( + 123 , + ( 1 + 5 ) # pylint # fmt:skip +) +baz = "a" + "b" # pylint; fmt: skip; noqa: E501 +skip_will_not_work = "a" + "b" # pylint fmt:skip +skip_will_not_work2 = "a" + "b" # some text; fmt:skip happens to be part of it From 3acdf062b661dab55307f378b9f969be38aa38c6 Mon Sep 17 00:00:00 2001 From: Henri Holopainen Date: Fri, 20 Oct 2023 18:07:05 +0300 Subject: [PATCH 02/10] Enable complex fmt:skip comments --- src/black/comments.py | 46 ++++++++++++++++++++++++++++++++++++------- 1 file changed, 39 insertions(+), 7 deletions(-) diff --git a/src/black/comments.py b/src/black/comments.py index 226968bff98..b949fa05b80 100644 --- a/src/black/comments.py +++ b/src/black/comments.py @@ -20,10 +20,11 @@ FMT_OFF: Final = {"# fmt: off", "# fmt:off", "# yapf: disable"} FMT_SKIP: Final = {"# fmt: skip", "# fmt:skip"} -FMT_PASS: Final = {*FMT_OFF, *FMT_SKIP} FMT_ON: Final = {"# fmt: on", "# fmt:on", "# yapf: enable"} COMMENT_EXCEPTIONS = " !:#'" +_COMMENT_PREFIX = "# " +_COMMENT_LIST_SEPARATOR = ";" @dataclass @@ -145,18 +146,24 @@ def convert_one_fmt_off_pair(node: Node) -> bool: for leaf in node.leaves(): previous_consumed = 0 for comment in list_comments(leaf.prefix, is_endmarker=False): - if comment.value not in FMT_PASS: + should_pass_fmt = comment.value in FMT_OFF or _contains_fmt_skip_comment( + comment.value + ) + if not should_pass_fmt: previous_consumed = comment.consumed continue # We only want standalone comments. If there's no previous leaf or # the previous leaf is indentation, it's a standalone comment in # disguise. - if comment.value in FMT_PASS and comment.type != STANDALONE_COMMENT: + if should_pass_fmt and comment.type != STANDALONE_COMMENT: prev = preceding_leaf(leaf) if prev: if comment.value in FMT_OFF and prev.type not in WHITESPACE: continue - if comment.value in FMT_SKIP and prev.type in WHITESPACE: + if ( + _contains_fmt_skip_comment(comment.value) + and prev.type in WHITESPACE + ): continue ignored_nodes = list(generate_ignored_nodes(leaf, comment)) @@ -168,7 +175,7 @@ def convert_one_fmt_off_pair(node: Node) -> bool: prefix = first.prefix if comment.value in FMT_OFF: first.prefix = prefix[comment.consumed :] - if comment.value in FMT_SKIP: + if _contains_fmt_skip_comment(comment.value): first.prefix = "" standalone_comment_prefix = prefix else: @@ -178,7 +185,7 @@ def convert_one_fmt_off_pair(node: Node) -> bool: hidden_value = "".join(str(n) for n in ignored_nodes) if comment.value in FMT_OFF: hidden_value = comment.value + "\n" + hidden_value - if comment.value in FMT_SKIP: + if _contains_fmt_skip_comment(comment.value): hidden_value += " " + comment.value if hidden_value.endswith("\n"): # That happens when one of the `ignored_nodes` ended with a NEWLINE @@ -211,7 +218,7 @@ def generate_ignored_nodes(leaf: Leaf, comment: ProtoComment) -> Iterator[LN]: If comment is skip, returns leaf only. Stops at the end of the block. """ - if comment.value in FMT_SKIP: + if _contains_fmt_skip_comment(comment.value): yield from _generate_ignored_nodes_from_fmt_skip(leaf, comment) return container: Optional[LN] = container_of(leaf) @@ -327,3 +334,28 @@ def contains_pragma_comment(comment_list: List[Leaf]) -> bool: return True return False + + +def _contains_fmt_skip_comment(comment_line: str) -> bool: + """ + Checks if the given comment contains FMT_SKIP alone or paired with other pragma. + Matching styles: + # fmt:skip <-- single comment + # noqa:XXX # fmt:skip <-- multiple comments + # pylint:XXX; fmt:skip <-- list of comments (; separated) + """ + semantic_comment_blocks = [ + comment_line, + *[ + _COMMENT_PREFIX + comment.strip() + for comment in comment_line.split(_COMMENT_PREFIX)[1:] + ], + *[ + _COMMENT_PREFIX + comment.strip() + for comment in comment_line.strip(_COMMENT_PREFIX).split( + _COMMENT_LIST_SEPARATOR + ) + ], + ] + + return any(comment in FMT_SKIP for comment in semantic_comment_blocks) From 2f76762274eb38dfb6f774ddfc73d11245d5b27e Mon Sep 17 00:00:00 2001 From: Henri Holopainen Date: Fri, 20 Oct 2023 18:19:33 +0300 Subject: [PATCH 03/10] Update documentation --- docs/the_black_code_style/current_style.md | 12 +++++++----- 1 file changed, 7 insertions(+), 5 deletions(-) diff --git a/docs/the_black_code_style/current_style.md b/docs/the_black_code_style/current_style.md index ff757a8276b..5b081c6eae3 100644 --- a/docs/the_black_code_style/current_style.md +++ b/docs/the_black_code_style/current_style.md @@ -8,12 +8,14 @@ deliberately limited and rarely added. Previous formatting is taken into account little as possible, with rare exceptions like the magic trailing comma. The coding style used by _Black_ can be viewed as a strict subset of PEP 8. -_Black_ reformats entire files in place. It doesn't reformat lines that end with +_Black_ reformats entire files in place. It doesn't reformat lines that contain `# fmt: skip` or blocks that start with `# fmt: off` and end with `# fmt: on`. -`# fmt: on/off` must be on the same level of indentation and in the same block, meaning -no unindents beyond the initial indentation level between them. It also recognizes -[YAPF](https://github.com/google/yapf)'s block comments to the same effect, as a -courtesy for straddling code. +`# fmt: skip` can be mixed with other pragmas either with multiple comments (e.g. +`# fmt: skip # pylint # noqa`) or as a semicolon separeted list (e.g. +`# fmt: skip; pylint; noqa`). `# fmt: on/off` must be on the same level of indentation +and in the same block, meaning no unindents beyond the initial indentation level between +them. It also recognizes [YAPF](https://github.com/google/yapf)'s block comments to the +same effect, as a courtesy for straddling code. The rest of this document describes the current formatting style. If you're interested in trying out where the style is heading, see [future style](./future_style.md) and try From c04d82fff97dbb88d8f77123c60d2e457e9201b1 Mon Sep 17 00:00:00 2001 From: Henri Holopainen Date: Fri, 20 Oct 2023 18:19:53 +0300 Subject: [PATCH 04/10] Update changelog --- CHANGES.md | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/CHANGES.md b/CHANGES.md index 79b5c6034e8..a48047d6f2f 100644 --- a/CHANGES.md +++ b/CHANGES.md @@ -16,7 +16,7 @@ ### Configuration - +- Add support for single line format skip with other pragmas on the same line (#XXXX) ### Packaging From 44ae0d8910370cc8437d678c82e650f01c5853ec Mon Sep 17 00:00:00 2001 From: Henri Holopainen Date: Fri, 20 Oct 2023 18:27:31 +0300 Subject: [PATCH 05/10] Update PR number --- CHANGES.md | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/CHANGES.md b/CHANGES.md index a48047d6f2f..d1b3310d97a 100644 --- a/CHANGES.md +++ b/CHANGES.md @@ -16,7 +16,7 @@ ### Configuration -- Add support for single line format skip with other pragmas on the same line (#XXXX) +- Add support for single line format skip with other pragmas on the same line (#3959) ### Packaging From def770367f62daace1bdabef167c3dbbf11d13e6 Mon Sep 17 00:00:00 2001 From: Henri Holopainen Date: Fri, 20 Oct 2023 19:26:16 +0300 Subject: [PATCH 06/10] Wording --- CHANGES.md | 2 +- docs/the_black_code_style/current_style.md | 4 ++-- src/black/comments.py | 8 ++++---- 3 files changed, 7 insertions(+), 7 deletions(-) diff --git a/CHANGES.md b/CHANGES.md index d1b3310d97a..2dc173dc34f 100644 --- a/CHANGES.md +++ b/CHANGES.md @@ -16,7 +16,7 @@ ### Configuration -- Add support for single line format skip with other pragmas on the same line (#3959) +- Add support for single line format skip with other comments on the same line (#3959) ### Packaging diff --git a/docs/the_black_code_style/current_style.md b/docs/the_black_code_style/current_style.md index 5b081c6eae3..f59c1853f72 100644 --- a/docs/the_black_code_style/current_style.md +++ b/docs/the_black_code_style/current_style.md @@ -10,8 +10,8 @@ used by _Black_ can be viewed as a strict subset of PEP 8. _Black_ reformats entire files in place. It doesn't reformat lines that contain `# fmt: skip` or blocks that start with `# fmt: off` and end with `# fmt: on`. -`# fmt: skip` can be mixed with other pragmas either with multiple comments (e.g. -`# fmt: skip # pylint # noqa`) or as a semicolon separeted list (e.g. +`# fmt: skip` can be mixed with other pragmas/comments either with multiple comments +(e.g. `# fmt: skip # pylint # noqa`) or as a semicolon separeted list (e.g. `# fmt: skip; pylint; noqa`). `# fmt: on/off` must be on the same level of indentation and in the same block, meaning no unindents beyond the initial indentation level between them. It also recognizes [YAPF](https://github.com/google/yapf)'s block comments to the diff --git a/src/black/comments.py b/src/black/comments.py index b949fa05b80..ea792c301cf 100644 --- a/src/black/comments.py +++ b/src/black/comments.py @@ -338,11 +338,11 @@ def contains_pragma_comment(comment_list: List[Leaf]) -> bool: def _contains_fmt_skip_comment(comment_line: str) -> bool: """ - Checks if the given comment contains FMT_SKIP alone or paired with other pragma. + Checks if the given comment contains FMT_SKIP alone or paired with other comments. Matching styles: - # fmt:skip <-- single comment - # noqa:XXX # fmt:skip <-- multiple comments - # pylint:XXX; fmt:skip <-- list of comments (; separated) + # fmt:skip <-- single comment + # noqa:XXX # fmt:skip # a nice line <-- multiple comments + # pylint:XXX; fmt:skip <-- list of comments (; separated) """ semantic_comment_blocks = [ comment_line, From 7a356c24c0de727a989d0740275b4474a195b94a Mon Sep 17 00:00:00 2001 From: Henri Holopainen Date: Tue, 24 Oct 2023 00:05:55 +0300 Subject: [PATCH 07/10] Put feature behind Preview toggle --- src/black/__init__.py | 2 +- src/black/comments.py | 61 ++++++++++++++++++++++++------------------- src/black/mode.py | 1 + 3 files changed, 36 insertions(+), 28 deletions(-) diff --git a/src/black/__init__.py b/src/black/__init__.py index 188a4f79f0e..7cf93b89e42 100644 --- a/src/black/__init__.py +++ b/src/black/__init__.py @@ -1099,7 +1099,7 @@ def _format_str_once(src_contents: str, *, mode: Mode) -> str: for feature in {Feature.PARENTHESIZED_CONTEXT_MANAGERS} if supports_feature(versions, feature) } - normalize_fmt_off(src_node) + normalize_fmt_off(src_node, mode) lines = LineGenerator(mode=mode, features=context_manager_features) elt = EmptyLineTracker(mode=mode) split_line_features = { diff --git a/src/black/comments.py b/src/black/comments.py index ea792c301cf..862fc7607cc 100644 --- a/src/black/comments.py +++ b/src/black/comments.py @@ -3,6 +3,7 @@ from functools import lru_cache from typing import Final, Iterator, List, Optional, Union +from black.mode import Mode, Preview from black.nodes import ( CLOSING_BRACKETS, STANDALONE_COMMENT, @@ -131,14 +132,14 @@ def make_comment(content: str) -> str: return "#" + content -def normalize_fmt_off(node: Node) -> None: +def normalize_fmt_off(node: Node, mode: Mode) -> None: """Convert content between `# fmt: off`/`# fmt: on` into standalone comments.""" try_again = True while try_again: - try_again = convert_one_fmt_off_pair(node) + try_again = convert_one_fmt_off_pair(node, mode) -def convert_one_fmt_off_pair(node: Node) -> bool: +def convert_one_fmt_off_pair(node: Node, mode: Mode) -> bool: """Convert content of a single `# fmt: off`/`# fmt: on` into a standalone comment. Returns True if a pair was converted. @@ -147,7 +148,7 @@ def convert_one_fmt_off_pair(node: Node) -> bool: previous_consumed = 0 for comment in list_comments(leaf.prefix, is_endmarker=False): should_pass_fmt = comment.value in FMT_OFF or _contains_fmt_skip_comment( - comment.value + comment.value, mode ) if not should_pass_fmt: previous_consumed = comment.consumed @@ -161,12 +162,12 @@ def convert_one_fmt_off_pair(node: Node) -> bool: if comment.value in FMT_OFF and prev.type not in WHITESPACE: continue if ( - _contains_fmt_skip_comment(comment.value) + _contains_fmt_skip_comment(comment.value, mode) and prev.type in WHITESPACE ): continue - ignored_nodes = list(generate_ignored_nodes(leaf, comment)) + ignored_nodes = list(generate_ignored_nodes(leaf, comment, mode)) if not ignored_nodes: continue @@ -175,7 +176,7 @@ def convert_one_fmt_off_pair(node: Node) -> bool: prefix = first.prefix if comment.value in FMT_OFF: first.prefix = prefix[comment.consumed :] - if _contains_fmt_skip_comment(comment.value): + if _contains_fmt_skip_comment(comment.value, mode): first.prefix = "" standalone_comment_prefix = prefix else: @@ -185,7 +186,7 @@ def convert_one_fmt_off_pair(node: Node) -> bool: hidden_value = "".join(str(n) for n in ignored_nodes) if comment.value in FMT_OFF: hidden_value = comment.value + "\n" + hidden_value - if _contains_fmt_skip_comment(comment.value): + if _contains_fmt_skip_comment(comment.value, mode): hidden_value += " " + comment.value if hidden_value.endswith("\n"): # That happens when one of the `ignored_nodes` ended with a NEWLINE @@ -212,13 +213,15 @@ def convert_one_fmt_off_pair(node: Node) -> bool: return False -def generate_ignored_nodes(leaf: Leaf, comment: ProtoComment) -> Iterator[LN]: +def generate_ignored_nodes( + leaf: Leaf, comment: ProtoComment, mode: Mode +) -> Iterator[LN]: """Starting from the container of `leaf`, generate all leaves until `# fmt: on`. If comment is skip, returns leaf only. Stops at the end of the block. """ - if _contains_fmt_skip_comment(comment.value): + if _contains_fmt_skip_comment(comment.value, mode): yield from _generate_ignored_nodes_from_fmt_skip(leaf, comment) return container: Optional[LN] = container_of(leaf) @@ -336,26 +339,30 @@ def contains_pragma_comment(comment_list: List[Leaf]) -> bool: return False -def _contains_fmt_skip_comment(comment_line: str) -> bool: +def _contains_fmt_skip_comment(comment_line: str, mode: Mode) -> bool: """ Checks if the given comment contains FMT_SKIP alone or paired with other comments. Matching styles: - # fmt:skip <-- single comment - # noqa:XXX # fmt:skip # a nice line <-- multiple comments - # pylint:XXX; fmt:skip <-- list of comments (; separated) + # fmt:skip <-- single comment + # noqa:XXX # fmt:skip # a nice line <-- multiple comments (Preview) + # pylint:XXX; fmt:skip <-- list of comments (; separated, Preview) """ - semantic_comment_blocks = [ - comment_line, - *[ - _COMMENT_PREFIX + comment.strip() - for comment in comment_line.split(_COMMENT_PREFIX)[1:] - ], - *[ - _COMMENT_PREFIX + comment.strip() - for comment in comment_line.strip(_COMMENT_PREFIX).split( - _COMMENT_LIST_SEPARATOR - ) - ], - ] + semantic_comment_blocks = ( + [ + comment_line, + *[ + _COMMENT_PREFIX + comment.strip() + for comment in comment_line.split(_COMMENT_PREFIX)[1:] + ], + *[ + _COMMENT_PREFIX + comment.strip() + for comment in comment_line.strip(_COMMENT_PREFIX).split( + _COMMENT_LIST_SEPARATOR + ) + ], + ] + if Preview.single_line_format_skip_with_multiple_comments in mode + else [comment_line] + ) return any(comment in FMT_SKIP for comment in semantic_comment_blocks) diff --git a/src/black/mode.py b/src/black/mode.py index 309f22dae94..b6b651e011e 100644 --- a/src/black/mode.py +++ b/src/black/mode.py @@ -190,6 +190,7 @@ class Preview(Enum): module_docstring_newlines = auto() accept_raw_docstrings = auto() fix_power_op_line_length = auto() + single_line_format_skip_with_multiple_comments = auto() class Deprecated(UserWarning): From bc846ff7ab536644f2aaf49b3e355130096ff571 Mon Sep 17 00:00:00 2001 From: Henri Holopainen Date: Tue, 24 Oct 2023 00:06:13 +0300 Subject: [PATCH 08/10] Add preview flag to tests --- tests/data/cases/fmtskip9.py | 1 + 1 file changed, 1 insertion(+) diff --git a/tests/data/cases/fmtskip9.py b/tests/data/cases/fmtskip9.py index 7212740fc42..efde662baa8 100644 --- a/tests/data/cases/fmtskip9.py +++ b/tests/data/cases/fmtskip9.py @@ -1,3 +1,4 @@ +# flags: --preview foo = 123 # fmt: skip # noqa: E501 # pylint bar = ( 123 , From 52d999f5099c23839e3f56f1cae8264ae343f11c Mon Sep 17 00:00:00 2001 From: Henri Holopainen Date: Tue, 24 Oct 2023 00:08:57 +0300 Subject: [PATCH 09/10] Rename test file --- ... => preview_single_line_format_skip_with_multiple_comments.py} | 0 1 file changed, 0 insertions(+), 0 deletions(-) rename tests/data/cases/{fmtskip9.py => preview_single_line_format_skip_with_multiple_comments.py} (100%) diff --git a/tests/data/cases/fmtskip9.py b/tests/data/cases/preview_single_line_format_skip_with_multiple_comments.py similarity index 100% rename from tests/data/cases/fmtskip9.py rename to tests/data/cases/preview_single_line_format_skip_with_multiple_comments.py From 471f11725bec0376d67ed0da37b26b2d93367e06 Mon Sep 17 00:00:00 2001 From: Henri Holopainen Date: Tue, 24 Oct 2023 23:25:00 +0300 Subject: [PATCH 10/10] Move changelog line to correct place --- CHANGES.md | 6 +----- 1 file changed, 1 insertion(+), 5 deletions(-) diff --git a/CHANGES.md b/CHANGES.md index de8b315dd33..c6295ae1f1f 100644 --- a/CHANGES.md +++ b/CHANGES.md @@ -16,7 +16,7 @@ ### Configuration - +- Add support for single line format skip with other comments on the same line (#3959) ### Packaging @@ -58,10 +58,6 @@ - Fix merging implicit multiline strings that have inline comments (#3956) - Allow empty first line after block open before a comment or compound statement (#3967) -### Configuration - -- Add support for single line format skip with other comments on the same line (#3959) - ### Packaging - Change Dockerfile to hatch + compile black (#3965)