From 09296e3e3cf18eeea1b6f6391fb43e50e49af00a Mon Sep 17 00:00:00 2001 From: Dhruv Manilawala Date: Tue, 19 Dec 2023 00:43:20 -0600 Subject: [PATCH] Implement `no_blank_line_before_class_docstring` preview style (#9154) ## Summary This PR implements the `no_blank_line_before_class_docstring` preview style. ## Test Plan Update existing snapshots. ### Formatter ecosystem `main` | project | similarity index | total files | changed files | |----------------|------------------:|------------------:|------------------:| | cpython | 0.75804 | 1799 | 1648 | | django | 0.99984 | 2772 | 34 | | home-assistant | 0.99955 | 10596 | 213 | | poetry | 0.99905 | 321 | 15 | | transformers | 0.99967 | 2657 | 324 | | twine | 1.00000 | 33 | 0 | | typeshed | 0.99980 | 3669 | 18 | | warehouse | 0.99976 | 654 | 14 | | zulip | 0.99958 | 1459 | 36 | `dhruv/no-blank-line-docstring` | project | similarity index | total files | changed files | |----------------|------------------:|------------------:|------------------:| | cpython | 0.75804 | 1799 | 1648 | | django | 0.99984 | 2772 | 34 | | home-assistant | 0.99955 | 10596 | 213 | | poetry | 0.99905 | 321 | 15 | | transformers | 0.99967 | 2657 | 324 | | twine | 1.00000 | 33 | 0 | | typeshed | 0.99980 | 3669 | 18 | | warehouse | 0.99976 | 654 | 14 | | zulip | 0.99958 | 1459 | 36 | fixes: #8888 --- ...k_line_before_class_docstring.options.json | 5 + .../ruff/blank_line_before_class_docstring.py | 38 +++++ crates/ruff_python_formatter/src/preview.rs | 9 ++ .../src/statement/suite.rs | 11 ++ ...iew_no_blank_line_before_docstring.py.snap | 134 ------------------ ...compatibility@cases__raw_docstring.py.snap | 14 +- ...@blank_line_before_class_docstring.py.snap | 95 +++++++++++++ .../tests/snapshots/format@preview.py.snap | 1 - ...format@statement__class_definition.py.snap | 25 +++- 9 files changed, 184 insertions(+), 148 deletions(-) create mode 100644 crates/ruff_python_formatter/resources/test/fixtures/ruff/blank_line_before_class_docstring.options.json create mode 100644 crates/ruff_python_formatter/resources/test/fixtures/ruff/blank_line_before_class_docstring.py delete mode 100644 crates/ruff_python_formatter/tests/snapshots/black_compatibility@cases__preview_no_blank_line_before_docstring.py.snap create mode 100644 crates/ruff_python_formatter/tests/snapshots/format@blank_line_before_class_docstring.py.snap diff --git a/crates/ruff_python_formatter/resources/test/fixtures/ruff/blank_line_before_class_docstring.options.json b/crates/ruff_python_formatter/resources/test/fixtures/ruff/blank_line_before_class_docstring.options.json new file mode 100644 index 0000000000000..8925dd0a8280f --- /dev/null +++ b/crates/ruff_python_formatter/resources/test/fixtures/ruff/blank_line_before_class_docstring.options.json @@ -0,0 +1,5 @@ +[ + { + "preview": "enabled" + } +] diff --git a/crates/ruff_python_formatter/resources/test/fixtures/ruff/blank_line_before_class_docstring.py b/crates/ruff_python_formatter/resources/test/fixtures/ruff/blank_line_before_class_docstring.py new file mode 100644 index 0000000000000..a8dbbafebbfd1 --- /dev/null +++ b/crates/ruff_python_formatter/resources/test/fixtures/ruff/blank_line_before_class_docstring.py @@ -0,0 +1,38 @@ +class NormalDocstring: + + """This is a docstring.""" + + +class DocstringWithComment0: + # This is a comment + """This is a docstring.""" + + +class DocstringWithComment1: + # This is a comment + + """This is a docstring.""" + + +class DocstringWithComment2: + + # This is a comment + """This is a docstring.""" + + +class DocstringWithComment3: + + # This is a comment + + """This is a docstring.""" + + +class DocstringWithComment4: + + + # This is a comment + + + """This is a docstring.""" + + diff --git a/crates/ruff_python_formatter/src/preview.rs b/crates/ruff_python_formatter/src/preview.rs index 90379d1ad8fab..4de87cf05c911 100644 --- a/crates/ruff_python_formatter/src/preview.rs +++ b/crates/ruff_python_formatter/src/preview.rs @@ -24,3 +24,12 @@ pub(crate) const fn is_prefer_splitting_right_hand_side_of_assignments_enabled( ) -> bool { context.is_preview() } + +/// Returns `true` if the [`no_blank_line_before_class_docstring`] preview style is enabled. +/// +/// [`no_blank_line_before_class_docstring`]: https://github.com/astral-sh/ruff/issues/8888 +pub(crate) const fn is_no_blank_line_before_class_docstring_enabled( + context: &PyFormatContext, +) -> bool { + context.is_preview() +} diff --git a/crates/ruff_python_formatter/src/statement/suite.rs b/crates/ruff_python_formatter/src/statement/suite.rs index 8b3d9e1a5efbd..f811e882f2524 100644 --- a/crates/ruff_python_formatter/src/statement/suite.rs +++ b/crates/ruff_python_formatter/src/statement/suite.rs @@ -11,6 +11,7 @@ use crate::comments::{ use crate::context::{NodeLevel, TopLevelStatementPosition, WithIndentLevel, WithNodeLevel}; use crate::expression::expr_string_literal::ExprStringLiteralKind; use crate::prelude::*; +use crate::preview::is_no_blank_line_before_class_docstring_enabled; use crate::statement::stmt_expr::FormatStmtExpr; use crate::verbatim::{ suppressed_node, write_suppressed_statements_starting_with_leading_comment, @@ -108,14 +109,24 @@ impl FormatRule> for FormatSuite { if !comments.has_leading(first) && lines_before(first.start(), source) > 1 && !source_type.is_stub() + && !is_no_blank_line_before_class_docstring_enabled(f.context()) { // Allow up to one empty line before a class docstring, e.g., this is // stable formatting: + // // ```python // class Test: // // """Docstring""" // ``` + // + // But, in preview mode, we don't want to allow any empty lines before a + // class docstring, e.g., this is preview formatting: + // + // ```python + // class Test: + // """Docstring""" + // ``` empty_line().fmt(f)?; } diff --git a/crates/ruff_python_formatter/tests/snapshots/black_compatibility@cases__preview_no_blank_line_before_docstring.py.snap b/crates/ruff_python_formatter/tests/snapshots/black_compatibility@cases__preview_no_blank_line_before_docstring.py.snap deleted file mode 100644 index bd93e24292838..0000000000000 --- a/crates/ruff_python_formatter/tests/snapshots/black_compatibility@cases__preview_no_blank_line_before_docstring.py.snap +++ /dev/null @@ -1,134 +0,0 @@ ---- -source: crates/ruff_python_formatter/tests/fixtures.rs -input_file: crates/ruff_python_formatter/resources/test/fixtures/black/cases/preview_no_blank_line_before_docstring.py ---- -## Input - -```python -def line_before_docstring(): - - """Please move me up""" - - -class LineBeforeDocstring: - - """Please move me up""" - - -class EvenIfThereIsAMethodAfter: - - """I'm the docstring""" - def method(self): - pass - - -class TwoLinesBeforeDocstring: - - - """I want to be treated the same as if I were closer""" - - -class MultilineDocstringsAsWell: - - """I'm so far - - and on so many lines... - """ -``` - -## Black Differences - -```diff ---- Black -+++ Ruff -@@ -3,10 +3,12 @@ - - - class LineBeforeDocstring: -+ - """Please move me up""" - - - class EvenIfThereIsAMethodAfter: -+ - """I'm the docstring""" - - def method(self): -@@ -14,10 +16,12 @@ - - - class TwoLinesBeforeDocstring: -+ - """I want to be treated the same as if I were closer""" - - - class MultilineDocstringsAsWell: -+ - """I'm so far - - and on so many lines... -``` - -## Ruff Output - -```python -def line_before_docstring(): - """Please move me up""" - - -class LineBeforeDocstring: - - """Please move me up""" - - -class EvenIfThereIsAMethodAfter: - - """I'm the docstring""" - - def method(self): - pass - - -class TwoLinesBeforeDocstring: - - """I want to be treated the same as if I were closer""" - - -class MultilineDocstringsAsWell: - - """I'm so far - - and on so many lines... - """ -``` - -## Black Output - -```python -def line_before_docstring(): - """Please move me up""" - - -class LineBeforeDocstring: - """Please move me up""" - - -class EvenIfThereIsAMethodAfter: - """I'm the docstring""" - - def method(self): - pass - - -class TwoLinesBeforeDocstring: - """I want to be treated the same as if I were closer""" - - -class MultilineDocstringsAsWell: - """I'm so far - - and on so many lines... - """ -``` - - diff --git a/crates/ruff_python_formatter/tests/snapshots/black_compatibility@cases__raw_docstring.py.snap b/crates/ruff_python_formatter/tests/snapshots/black_compatibility@cases__raw_docstring.py.snap index 8b27585265471..bc66189376a79 100644 --- a/crates/ruff_python_formatter/tests/snapshots/black_compatibility@cases__raw_docstring.py.snap +++ b/crates/ruff_python_formatter/tests/snapshots/black_compatibility@cases__raw_docstring.py.snap @@ -27,30 +27,21 @@ class UpperCaseR: ```diff --- Black +++ Ruff -@@ -1,4 +1,5 @@ - class C: -+ - r"""Raw""" - - -@@ -7,8 +8,9 @@ +@@ -7,7 +7,7 @@ class SingleQuotes: - r'''Raw''' - + r"""Raw""" -+ + class UpperCaseR: - R"""Raw""" ``` ## Ruff Output ```python class C: - r"""Raw""" @@ -59,7 +50,6 @@ def f(): class SingleQuotes: - r"""Raw""" diff --git a/crates/ruff_python_formatter/tests/snapshots/format@blank_line_before_class_docstring.py.snap b/crates/ruff_python_formatter/tests/snapshots/format@blank_line_before_class_docstring.py.snap new file mode 100644 index 0000000000000..b3986d0395ff1 --- /dev/null +++ b/crates/ruff_python_formatter/tests/snapshots/format@blank_line_before_class_docstring.py.snap @@ -0,0 +1,95 @@ +--- +source: crates/ruff_python_formatter/tests/fixtures.rs +input_file: crates/ruff_python_formatter/resources/test/fixtures/ruff/blank_line_before_class_docstring.py +--- +## Input +```python +class NormalDocstring: + + """This is a docstring.""" + + +class DocstringWithComment0: + # This is a comment + """This is a docstring.""" + + +class DocstringWithComment1: + # This is a comment + + """This is a docstring.""" + + +class DocstringWithComment2: + + # This is a comment + """This is a docstring.""" + + +class DocstringWithComment3: + + # This is a comment + + """This is a docstring.""" + + +class DocstringWithComment4: + + + # This is a comment + + + """This is a docstring.""" + + +``` + +## Outputs +### Output 1 +``` +indent-style = space +line-width = 88 +indent-width = 4 +quote-style = Double +line-ending = LineFeed +magic-trailing-comma = Respect +docstring-code = Disabled +docstring-code-line-width = "dynamic" +preview = Enabled +``` + +```python +class NormalDocstring: + """This is a docstring.""" + + +class DocstringWithComment0: + # This is a comment + """This is a docstring.""" + + +class DocstringWithComment1: + # This is a comment + + """This is a docstring.""" + + +class DocstringWithComment2: + # This is a comment + """This is a docstring.""" + + +class DocstringWithComment3: + # This is a comment + + """This is a docstring.""" + + +class DocstringWithComment4: + # This is a comment + + """This is a docstring.""" +``` + + + diff --git a/crates/ruff_python_formatter/tests/snapshots/format@preview.py.snap b/crates/ruff_python_formatter/tests/snapshots/format@preview.py.snap index fdedcce512dd8..888c6f938a304 100644 --- a/crates/ruff_python_formatter/tests/snapshots/format@preview.py.snap +++ b/crates/ruff_python_formatter/tests/snapshots/format@preview.py.snap @@ -198,7 +198,6 @@ def reference_docstring_newlines(): class RemoveNewlineBeforeClassDocstring: - """Black's `Preview.no_blank_line_before_class_docstring`""" diff --git a/crates/ruff_python_formatter/tests/snapshots/format@statement__class_definition.py.snap b/crates/ruff_python_formatter/tests/snapshots/format@statement__class_definition.py.snap index de82b7126c37a..eec7f540f77b5 100644 --- a/crates/ruff_python_formatter/tests/snapshots/format@statement__class_definition.py.snap +++ b/crates/ruff_python_formatter/tests/snapshots/format@statement__class_definition.py.snap @@ -513,7 +513,30 @@ class QuerySet(AltersData): class Test( -@@ -159,20 +158,17 @@ +@@ -94,7 +93,6 @@ + + + class Test: +- + """Docstring""" + + +@@ -111,14 +109,12 @@ + + + class Test: +- + """Docstring""" + + x = 1 + + + class Test: +- + """Docstring""" + + # comment +@@ -159,20 +155,17 @@ @dataclass # Copied from transformers.models.clip.modeling_clip.CLIPOutput with CLIP->AltCLIP