From 74769a1ec4f6a8cc4f029328f17bd7ae9baa19d3 Mon Sep 17 00:00:00 2001 From: Sagi Shadur Date: Sun, 25 Oct 2020 23:33:31 +0200 Subject: [PATCH 1/6] Add "# fmt: skip" directive to black --- src/black/__init__.py | 86 ++++++++++++++++++++++-------------------- tests/data/fmtskip.py | 3 ++ tests/data/fmtskip2.py | 17 +++++++++ tests/data/fmtskip3.py | 20 ++++++++++ tests/test_format.py | 3 ++ 5 files changed, 89 insertions(+), 40 deletions(-) create mode 100644 tests/data/fmtskip.py create mode 100644 tests/data/fmtskip2.py create mode 100644 tests/data/fmtskip3.py diff --git a/src/black/__init__.py b/src/black/__init__.py index e09838d866a..7fb84efdc6f 100644 --- a/src/black/__init__.py +++ b/src/black/__init__.py @@ -2515,6 +2515,8 @@ def is_split_before_delimiter(leaf: Leaf, previous: Optional[Leaf] = None) -> Pr FMT_OFF = {"# fmt: off", "# fmt:off", "# yapf: disable"} +FMT_SKIP = {"# fmt: skip", "# fmt:skip"} +FMT_PASS = {*FMT_OFF, *FMT_SKIP} FMT_ON = {"# fmt: on", "# fmt:on", "# yapf: enable"} @@ -5296,57 +5298,61 @@ 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 in FMT_OFF: - # 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.type != STANDALONE_COMMENT: - prev = preceding_leaf(leaf) - if prev and prev.type not in WHITESPACE: - continue - - ignored_nodes = list(generate_ignored_nodes(leaf)) - if not ignored_nodes: + if comment.value not in FMT_PASS: + 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_OFF and comment.type != STANDALONE_COMMENT: + prev = preceding_leaf(leaf) + if prev and prev.type not in WHITESPACE: continue - first = ignored_nodes[0] # Can be a container node with the `leaf`. - parent = first.parent - prefix = first.prefix - first.prefix = prefix[comment.consumed :] - hidden_value = ( - comment.value + "\n" + "".join(str(n) for n in ignored_nodes) - ) - if hidden_value.endswith("\n"): - # That happens when one of the `ignored_nodes` ended with a NEWLINE - # leaf (possibly followed by a DEDENT). - hidden_value = hidden_value[:-1] - first_idx: Optional[int] = None - for ignored in ignored_nodes: - index = ignored.remove() - if first_idx is None: - first_idx = index - assert parent is not None, "INTERNAL ERROR: fmt: on/off handling (1)" - assert first_idx is not None, "INTERNAL ERROR: fmt: on/off handling (2)" - parent.insert_child( - first_idx, - Leaf( - STANDALONE_COMMENT, - hidden_value, - prefix=prefix[:previous_consumed] + "\n" * comment.newlines, - ), - ) - return True + ignored_nodes = list(generate_ignored_nodes(leaf, comment)) + if not ignored_nodes: + continue - previous_consumed = comment.consumed + first = ignored_nodes[0] # Can be a container node with the `leaf`. + parent = first.parent + prefix = first.prefix + first.prefix = prefix[comment.consumed :] + hidden_value = "".join(str(n) for n in ignored_nodes) + if comment.value in FMT_OFF: + hidden_value = comment.value + "\n" + hidden_value + if hidden_value.endswith("\n"): + # That happens when one of the `ignored_nodes` ended with a NEWLINE + # leaf (possibly followed by a DEDENT). + hidden_value = hidden_value[:-1] + first_idx: Optional[int] = None + for ignored in ignored_nodes: + index = ignored.remove() + if first_idx is None: + first_idx = index + assert parent is not None, "INTERNAL ERROR: fmt: on/off handling (1)" + assert first_idx is not None, "INTERNAL ERROR: fmt: on/off handling (2)" + parent.insert_child( + first_idx, + Leaf( + STANDALONE_COMMENT, + hidden_value, + prefix=prefix[:previous_consumed] + "\n" * comment.newlines, + ), + ) + return True return False -def generate_ignored_nodes(leaf: Leaf) -> Iterator[LN]: +def generate_ignored_nodes(leaf: Leaf, comment: ProtoComment) -> 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 comment.value in FMT_SKIP: + yield leaf.parent + return container: Optional[LN] = container_of(leaf) while container is not None and container.type != token.ENDMARKER: if is_fmt_on(container): diff --git a/tests/data/fmtskip.py b/tests/data/fmtskip.py new file mode 100644 index 00000000000..1d5836fc031 --- /dev/null +++ b/tests/data/fmtskip.py @@ -0,0 +1,3 @@ +a, b = 1, 2 +c = 6 # fmt: skip +d = 5 diff --git a/tests/data/fmtskip2.py b/tests/data/fmtskip2.py new file mode 100644 index 00000000000..e6248117aa9 --- /dev/null +++ b/tests/data/fmtskip2.py @@ -0,0 +1,17 @@ +l1 = ["This list should be broken up", "into multiple lines", "because it is way too long"] +l2 = ["But this list shouldn't", "even though it also has", "way too many characters in it"] # fmt: skip +l3 = ["I have", "trailing comma", "so I should be braked",] + +# output + +l1 = [ + "This list should be broken up", + "into multiple lines", + "because it is way too long", +] +l2 = ["But this list shouldn't", "even though it also has", "way too many characters in it"] # fmt: skip +l3 = [ + "I have", + "trailing comma", + "so I should be braked", +] \ No newline at end of file diff --git a/tests/data/fmtskip3.py b/tests/data/fmtskip3.py new file mode 100644 index 00000000000..6e166888e21 --- /dev/null +++ b/tests/data/fmtskip3.py @@ -0,0 +1,20 @@ +a = 3 +# fmt: off +b, c = 1, 2 +d = 6 # fmt: skip +e = 5 +# fmt: on +f = ["This is a very long line that should be formatted into a clearer line ", "by rearranging."] + +# output + +a = 3 +# fmt: off +b, c = 1, 2 +d = 6 # fmt: skip +e = 5 +# fmt: on +f = [ + "This is a very long line that should be formatted into a clearer line ", + "by rearranging.", +] diff --git a/tests/test_format.py b/tests/test_format.py index e4677707e3c..869921238c7 100644 --- a/tests/test_format.py +++ b/tests/test_format.py @@ -37,6 +37,9 @@ "fmtonoff2", "fmtonoff3", "fmtonoff4", + "fmtskip", + "fmtskip2", + "fmtskip3", "fstring", "function", "function2", From ee382243697427246bde662213e49d98de691166 Mon Sep 17 00:00:00 2001 From: Sagi Shadur Date: Sat, 31 Oct 2020 20:38:25 +0200 Subject: [PATCH 2/6] Fix mypy error --- src/black/__init__.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/black/__init__.py b/src/black/__init__.py index 7fb84efdc6f..c95ab90f3f9 100644 --- a/src/black/__init__.py +++ b/src/black/__init__.py @@ -5350,7 +5350,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 comment.value in FMT_SKIP and leaf.parent is not None: yield leaf.parent return container: Optional[LN] = container_of(leaf) From 705f91ecc6523f2073d622f8a127f9a6223f9b07 Mon Sep 17 00:00:00 2001 From: Sagi Shadur Date: Wed, 2 Dec 2020 09:17:31 +0200 Subject: [PATCH 3/6] Fix bug when trying to skip a blank line --- src/black/__init__.py | 9 ++++++--- tests/data/fmtskip4.py | 13 +++++++++++++ tests/test_format.py | 1 + 3 files changed, 20 insertions(+), 3 deletions(-) create mode 100644 tests/data/fmtskip4.py diff --git a/src/black/__init__.py b/src/black/__init__.py index c95ab90f3f9..d877e07157f 100644 --- a/src/black/__init__.py +++ b/src/black/__init__.py @@ -5304,10 +5304,13 @@ def convert_one_fmt_off_pair(node: Node) -> bool: # 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_OFF and comment.type != STANDALONE_COMMENT: + if comment.value in FMT_PASS and comment.type != STANDALONE_COMMENT: prev = preceding_leaf(leaf) - if prev and prev.type not in WHITESPACE: - continue + 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: + continue ignored_nodes = list(generate_ignored_nodes(leaf, comment)) if not ignored_nodes: diff --git a/tests/data/fmtskip4.py b/tests/data/fmtskip4.py new file mode 100644 index 00000000000..aadd77d0e53 --- /dev/null +++ b/tests/data/fmtskip4.py @@ -0,0 +1,13 @@ +a = 2 +# fmt: skip +l = [1, 2, 3,] + +# output + +a = 2 +# fmt: skip +l = [ + 1, + 2, + 3, +] \ No newline at end of file diff --git a/tests/test_format.py b/tests/test_format.py index 869921238c7..75b653fa7d1 100644 --- a/tests/test_format.py +++ b/tests/test_format.py @@ -40,6 +40,7 @@ "fmtskip", "fmtskip2", "fmtskip3", + "fmtskip4", "fstring", "function", "function2", From a65e1869a94b65b1e7203c4b9ed70c969d10a962 Mon Sep 17 00:00:00 2001 From: Sagi Shadur Date: Fri, 15 Jan 2021 11:29:02 +0200 Subject: [PATCH 4/6] Fix bug in skip format on one line only in a condition. --- src/black/__init__.py | 21 ++++++++++++++++++--- tests/data/fmtskip5.py | 22 ++++++++++++++++++++++ tests/test_format.py | 1 + 3 files changed, 41 insertions(+), 3 deletions(-) create mode 100644 tests/data/fmtskip5.py diff --git a/src/black/__init__.py b/src/black/__init__.py index 96f5670e718..4bdf80a7c7f 100644 --- a/src/black/__init__.py +++ b/src/black/__init__.py @@ -5391,6 +5391,8 @@ 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: + hidden_value += " " + comment.value if hidden_value.endswith("\n"): # That happens when one of the `ignored_nodes` ended with a NEWLINE # leaf (possibly followed by a DEDENT). @@ -5421,10 +5423,23 @@ 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 and leaf.parent is not None: - yield leaf.parent - return container: Optional[LN] = container_of(leaf) + if comment.value in FMT_SKIP: + prev_sibling = leaf.prev_sibling + if comment.value in leaf.prefix and prev_sibling is not None: + leaf.prefix = leaf.prefix.replace(comment.value, "") + siblings = [prev_sibling] + while ( + "\n" not in prev_sibling.prefix + and prev_sibling.prev_sibling is not None + ): + prev_sibling = prev_sibling.prev_sibling + siblings.insert(0, prev_sibling) + for sibling in siblings: + yield sibling + elif leaf.parent is not None: + yield leaf.parent + return while container is not None and container.type != token.ENDMARKER: if is_fmt_on(container): return diff --git a/tests/data/fmtskip5.py b/tests/data/fmtskip5.py new file mode 100644 index 00000000000..d7b15e0ff41 --- /dev/null +++ b/tests/data/fmtskip5.py @@ -0,0 +1,22 @@ +a, b, c = 3, 4, 5 +if ( + a == 3 + and b != 9 # fmt: skip + and c is not None +): + print("I'm good!") +else: + print("I'm bad") + + +# output + +a, b, c = 3, 4, 5 +if ( + a == 3 + and b != 9 # fmt: skip + and c is not None +): + print("I'm good!") +else: + print("I'm bad") diff --git a/tests/test_format.py b/tests/test_format.py index 75b653fa7d1..e0cb0b74195 100644 --- a/tests/test_format.py +++ b/tests/test_format.py @@ -41,6 +41,7 @@ "fmtskip2", "fmtskip3", "fmtskip4", + "fmtskip5", "fstring", "function", "function2", From 335e25f8007859ca53c61466f06901ab3179d139 Mon Sep 17 00:00:00 2001 From: Sagi Shadur Date: Mon, 15 Feb 2021 09:59:56 +0200 Subject: [PATCH 5/6] Add `# fmt: skip` to README.md --- README.md | 5 +++-- 1 file changed, 3 insertions(+), 2 deletions(-) diff --git a/README.md b/README.md index 97df938eab2..74ba1abf554 100644 --- a/README.md +++ b/README.md @@ -239,8 +239,9 @@ feeling confident, use `--fast`. _Black_ is a PEP 8 compliant opinionated formatter. _Black_ reformats entire files in place. It is not configurable. It doesn't take previous formatting into account. Your main option of configuring _Black_ is that it doesn't reformat blocks that start with -`# fmt: off` and end with `# fmt: on`. `# fmt: on/off` have to be on the same level of -indentation. To learn more about _Black_'s opinions, to go +`# fmt: off` and end with `# fmt: on`, or lines that ends with `# fmt: skip`. +Pay attention that `# fmt: on/off` have to be on the same level of indentation. +To learn more about _Black_'s opinions, to go [the_black_code_style](https://github.com/psf/black/blob/master/docs/the_black_code_style.md). Please refer to this document before submitting an issue. What seems like a bug might be From d525104aea26b379950ba6ae36cfda6b42d13f37 Mon Sep 17 00:00:00 2001 From: Sagi Shadur Date: Mon, 15 Feb 2021 10:08:04 +0200 Subject: [PATCH 6/6] Fix README.md using `prettier` --- README.md | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/README.md b/README.md index 74ba1abf554..89ef4271d41 100644 --- a/README.md +++ b/README.md @@ -239,9 +239,9 @@ feeling confident, use `--fast`. _Black_ is a PEP 8 compliant opinionated formatter. _Black_ reformats entire files in place. It is not configurable. It doesn't take previous formatting into account. Your main option of configuring _Black_ is that it doesn't reformat blocks that start with -`# fmt: off` and end with `# fmt: on`, or lines that ends with `# fmt: skip`. -Pay attention that `# fmt: on/off` have to be on the same level of indentation. -To learn more about _Black_'s opinions, to go +`# fmt: off` and end with `# fmt: on`, or lines that ends with `# fmt: skip`. Pay +attention that `# fmt: on/off` have to be on the same level of indentation. To learn +more about _Black_'s opinions, to go [the_black_code_style](https://github.com/psf/black/blob/master/docs/the_black_code_style.md). Please refer to this document before submitting an issue. What seems like a bug might be