From 768d2fbc59eacab7b82172da8a8adad86d65ba6d Mon Sep 17 00:00:00 2001 From: MeGaGiGaGon <107241144+MeGaGiGaGon@users.noreply.github.com> Date: Mon, 22 Sep 2025 11:44:36 -0700 Subject: [PATCH 1/6] Fix bug where module doc strings would be treated as normal strings if preceeded by comments This happened because the code didn't account for comments before module doc strings. Since there can be arbitrarily many, this PR pulls the check into a new function so a while loop could go through all the previous blocks until either the start of the file is found, or a non comment block is reached. A new test was also added. --- CHANGES.md | 3 +++ src/black/lines.py | 26 +++++++++++++------ .../cases/module_docstring_after_comment.py | 21 +++++++++++++++ 3 files changed, 42 insertions(+), 8 deletions(-) create mode 100644 tests/data/cases/module_docstring_after_comment.py diff --git a/CHANGES.md b/CHANGES.md index e8d7c0b4e9a..b5de759feb8 100644 --- a/CHANGES.md +++ b/CHANGES.md @@ -10,6 +10,9 @@ +- Fix bug where module doc strings would be treated as normal strings if preceeded by + comments (#4764) + ### Preview style diff --git a/src/black/lines.py b/src/black/lines.py index 21e6cec571d..990dcae32e1 100644 --- a/src/black/lines.py +++ b/src/black/lines.py @@ -559,14 +559,8 @@ def maybe_empty_lines(self, current_line: Line) -> LinesBlock: before, after = self._maybe_empty_lines(current_line) previous_after = self.previous_block.after if self.previous_block else 0 before = max(0, before - previous_after) - if ( - # Always have one empty line after a module docstring - self.previous_block - and self.previous_block.previous_block is None - and len(self.previous_block.original_line.leaves) == 1 - and self.previous_block.original_line.is_docstring - and not (current_line.is_class or current_line.is_def) - ): + # Always have one empty line after a module docstring + if self._line_is_module_docstring(current_line): before = 1 block = LinesBlock( @@ -595,6 +589,22 @@ def maybe_empty_lines(self, current_line: Line) -> LinesBlock: self.previous_block = block return block + def _line_is_module_docstring(self, current_line: Line) -> bool: + previous_block = self.previous_block + if not previous_block: + return False + if ( + len(previous_block.original_line.leaves) != 1 + or not previous_block.original_line.is_docstring + or current_line.is_class + or current_line.is_def + ): + return False + while previous_block := previous_block.previous_block: + if not previous_block.original_line.is_comment: + return False + return True + def _maybe_empty_lines(self, current_line: Line) -> tuple[int, int]: # noqa: C901 max_allowed = 1 if current_line.depth == 0: diff --git a/tests/data/cases/module_docstring_after_comment.py b/tests/data/cases/module_docstring_after_comment.py new file mode 100644 index 00000000000..5bd8e29f3aa --- /dev/null +++ b/tests/data/cases/module_docstring_after_comment.py @@ -0,0 +1,21 @@ +#!/python + +# regression test for #4762 +""" +docstring +""" +from __future__ import annotations + +import os + +# output +#!/python + +# regression test for #4762 +""" +docstring +""" + +from __future__ import annotations + +import os \ No newline at end of file From 3b296256e6ada730c081e10f15b82c2dc7e3769f Mon Sep 17 00:00:00 2001 From: MeGaGiGaGon <107241144+MeGaGiGaGon@users.noreply.github.com> Date: Mon, 22 Sep 2025 11:59:45 -0700 Subject: [PATCH 2/6] Move to preview --- CHANGES.md | 6 +++--- docs/the_black_code_style/future_style.md | 2 ++ src/black/lines.py | 17 ++++++++++++++--- src/black/mode.py | 1 + .../cases/module_docstring_after_comment.py | 3 ++- 5 files changed, 22 insertions(+), 7 deletions(-) diff --git a/CHANGES.md b/CHANGES.md index b5de759feb8..1e9b158a5cb 100644 --- a/CHANGES.md +++ b/CHANGES.md @@ -10,13 +10,13 @@ -- Fix bug where module doc strings would be treated as normal strings if preceeded by - comments (#4764) - ### Preview style +- Fix bug where module doc strings would be treated as normal strings if preceeded by + comments (#4764) + ### Configuration diff --git a/docs/the_black_code_style/future_style.md b/docs/the_black_code_style/future_style.md index 837aec457b0..6ff094b721e 100644 --- a/docs/the_black_code_style/future_style.md +++ b/docs/the_black_code_style/future_style.md @@ -35,6 +35,8 @@ Currently, the following features are included in the preview style: types in `except` and `except*` without `as`. See PEP 758 for details. - `normalize_cr_newlines`: Add `\r` style newlines to the potential newlines to normalize file newlines both from and to. +- `fix_module_docstring_detection`: Fix module doc strings being treated as normal + strings if preceeded by comments. (labels/unstable-features)= diff --git a/src/black/lines.py b/src/black/lines.py index 990dcae32e1..11bf15bfa54 100644 --- a/src/black/lines.py +++ b/src/black/lines.py @@ -559,9 +559,20 @@ def maybe_empty_lines(self, current_line: Line) -> LinesBlock: before, after = self._maybe_empty_lines(current_line) previous_after = self.previous_block.after if self.previous_block else 0 before = max(0, before - previous_after) - # Always have one empty line after a module docstring - if self._line_is_module_docstring(current_line): - before = 1 + if Preview.fix_module_docstring_detection in self.Mode: + # Always have one empty line after a module docstring + if self._line_is_module_docstring(current_line): + before = 1 + else: + if ( + # Always have one empty line after a module docstring + self.previous_block + and self.previous_block.previous_block is None + and len(self.previous_block.original_line.leaves) == 1 + and self.previous_block.original_line.is_docstring + and not (current_line.is_class or current_line.is_def) + ): + before = 1 block = LinesBlock( mode=self.mode, diff --git a/src/black/mode.py b/src/black/mode.py index 85a205949dc..9927f73c4a7 100644 --- a/src/black/mode.py +++ b/src/black/mode.py @@ -236,6 +236,7 @@ class Preview(Enum): # except* without as. See PEP 758 for details. remove_parens_around_except_types = auto() normalize_cr_newlines = auto() + fix_module_docstring_detection = auto() UNSTABLE_FEATURES: set[Preview] = { diff --git a/tests/data/cases/module_docstring_after_comment.py b/tests/data/cases/module_docstring_after_comment.py index 5bd8e29f3aa..6a755b36dd2 100644 --- a/tests/data/cases/module_docstring_after_comment.py +++ b/tests/data/cases/module_docstring_after_comment.py @@ -1,3 +1,4 @@ +# flags: --preview #!/python # regression test for #4762 @@ -18,4 +19,4 @@ from __future__ import annotations -import os \ No newline at end of file +import os From c9e8b0b2ba5381d8c237ace6013209e71e220282 Mon Sep 17 00:00:00 2001 From: MeGaGiGaGon <107241144+MeGaGiGaGon@users.noreply.github.com> Date: Mon, 22 Sep 2025 12:01:00 -0700 Subject: [PATCH 3/6] Fix type in move to preview --- src/black/lines.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/black/lines.py b/src/black/lines.py index 11bf15bfa54..b3fdd4ae3a3 100644 --- a/src/black/lines.py +++ b/src/black/lines.py @@ -559,7 +559,7 @@ def maybe_empty_lines(self, current_line: Line) -> LinesBlock: before, after = self._maybe_empty_lines(current_line) previous_after = self.previous_block.after if self.previous_block else 0 before = max(0, before - previous_after) - if Preview.fix_module_docstring_detection in self.Mode: + if Preview.fix_module_docstring_detection in self.mode: # Always have one empty line after a module docstring if self._line_is_module_docstring(current_line): before = 1 From 1b557c1368271f656c8df746515e120259e89940 Mon Sep 17 00:00:00 2001 From: MeGaGiGaGon <107241144+MeGaGiGaGon@users.noreply.github.com> Date: Mon, 22 Sep 2025 12:02:17 -0700 Subject: [PATCH 4/6] Be consistent in docstring spacing --- CHANGES.md | 2 +- docs/the_black_code_style/future_style.md | 2 +- 2 files changed, 2 insertions(+), 2 deletions(-) diff --git a/CHANGES.md b/CHANGES.md index 1e9b158a5cb..f6ec50fe8ba 100644 --- a/CHANGES.md +++ b/CHANGES.md @@ -14,7 +14,7 @@ -- Fix bug where module doc strings would be treated as normal strings if preceeded by +- Fix bug where module docstrings would be treated as normal strings if preceeded by comments (#4764) ### Configuration diff --git a/docs/the_black_code_style/future_style.md b/docs/the_black_code_style/future_style.md index 6ff094b721e..c1e88c1cba5 100644 --- a/docs/the_black_code_style/future_style.md +++ b/docs/the_black_code_style/future_style.md @@ -35,7 +35,7 @@ Currently, the following features are included in the preview style: types in `except` and `except*` without `as`. See PEP 758 for details. - `normalize_cr_newlines`: Add `\r` style newlines to the potential newlines to normalize file newlines both from and to. -- `fix_module_docstring_detection`: Fix module doc strings being treated as normal +- `fix_module_docstring_detection`: Fix module docstrings being treated as normal strings if preceeded by comments. (labels/unstable-features)= From 79f6ffa6a51e75f5ec7a864f4e8a36505d2f0c43 Mon Sep 17 00:00:00 2001 From: MeGaGiGaGon <107241144+MeGaGiGaGon@users.noreply.github.com> Date: Mon, 22 Sep 2025 12:16:58 -0700 Subject: [PATCH 5/6] Fix self format error (should be fine that this changed since I think it always uses unstable) --- src/blib2to3/pgen2/parse.py | 1 + 1 file changed, 1 insertion(+) diff --git a/src/blib2to3/pgen2/parse.py b/src/blib2to3/pgen2/parse.py index 36e865cdb46..06b3790b115 100644 --- a/src/blib2to3/pgen2/parse.py +++ b/src/blib2to3/pgen2/parse.py @@ -9,6 +9,7 @@ how this parsing engine works. """ + from collections.abc import Callable, Iterator from contextlib import contextmanager from typing import TYPE_CHECKING, Any, Optional, Union, cast From ade03470e19ddc30c3d786d531699c0412e6c61d Mon Sep 17 00:00:00 2001 From: MeGaGiGaGon <107241144+MeGaGiGaGon@users.noreply.github.com> Date: Mon, 22 Sep 2025 12:19:33 -0700 Subject: [PATCH 6/6] update schema --- src/black/resources/black.schema.json | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/src/black/resources/black.schema.json b/src/black/resources/black.schema.json index 549e0e8049f..8ea19908570 100644 --- a/src/black/resources/black.schema.json +++ b/src/black/resources/black.schema.json @@ -88,7 +88,8 @@ "fix_fmt_skip_in_one_liners", "wrap_comprehension_in", "remove_parens_around_except_types", - "normalize_cr_newlines" + "normalize_cr_newlines", + "fix_module_docstring_detection" ] }, "description": "Enable specific features included in the `--unstable` style. Requires `--preview`. No compatibility guarantees are provided on the behavior or existence of any unstable features."