diff --git a/crates/ruff_python_formatter/resources/test/fixtures/ruff/stub_files/decorated_class_after_function.pyi b/crates/ruff_python_formatter/resources/test/fixtures/ruff/stub_files/decorated_class_after_function.pyi new file mode 100644 index 0000000000..ed41c20d24 --- /dev/null +++ b/crates/ruff_python_formatter/resources/test/fixtures/ruff/stub_files/decorated_class_after_function.pyi @@ -0,0 +1,9 @@ +# Issue #18865: Decorated classes below functions should be separated with blank lines +def hello(): ... +@lambda _, /: _ +class A: ... + +def world(): ... + +@final +class B: ... diff --git a/crates/ruff_python_formatter/src/preview.rs b/crates/ruff_python_formatter/src/preview.rs index 52e0881f3d..ee9b378cb8 100644 --- a/crates/ruff_python_formatter/src/preview.rs +++ b/crates/ruff_python_formatter/src/preview.rs @@ -20,3 +20,10 @@ pub(crate) const fn is_no_chaperone_for_escaped_quote_in_triple_quoted_docstring ) -> bool { context.is_preview() } + +/// Returns `true` if the [`blank_line_before_decorated_class_in_stub`](https://github.com/astral-sh/ruff/issues/18865) preview style is enabled. +pub(crate) const fn is_blank_line_before_decorated_class_in_stub_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 34d8216af7..4071b4ba1f 100644 --- a/crates/ruff_python_formatter/src/statement/suite.rs +++ b/crates/ruff_python_formatter/src/statement/suite.rs @@ -13,6 +13,7 @@ use crate::comments::{ use crate::context::{NodeLevel, TopLevelStatementPosition, WithIndentLevel, WithNodeLevel}; use crate::other::string_literal::StringLiteralKind; use crate::prelude::*; +use crate::preview::is_blank_line_before_decorated_class_in_stub_enabled; use crate::statement::stmt_expr::FormatStmtExpr; use crate::verbatim::{ suppressed_node, write_suppressed_statements_starting_with_leading_comment, @@ -700,10 +701,15 @@ fn stub_suite_can_omit_empty_line(preceding: &Stmt, following: &Stmt, f: &PyForm // // class LockType2: ... // ``` - let class_decorator_instead_of_empty_line = preceding.is_function_def_stmt() - && following - .as_class_def_stmt() - .is_some_and(|class| !class.decorator_list.is_empty()); + // + // However, this behavior is incorrect and should not be replicated in preview mode. + // See: https://github.com/astral-sh/ruff/issues/18865 + let class_decorator_instead_of_empty_line = + !is_blank_line_before_decorated_class_in_stub_enabled(f.context()) + && preceding.is_function_def_stmt() + && following + .as_class_def_stmt() + .is_some_and(|class| !class.decorator_list.is_empty()); // A function definition following a stub function definition // ```python diff --git a/crates/ruff_python_formatter/tests/snapshots/format@stub_files__decorated_class_after_function.pyi.snap b/crates/ruff_python_formatter/tests/snapshots/format@stub_files__decorated_class_after_function.pyi.snap new file mode 100644 index 0000000000..a507735686 --- /dev/null +++ b/crates/ruff_python_formatter/tests/snapshots/format@stub_files__decorated_class_after_function.pyi.snap @@ -0,0 +1,46 @@ +--- +source: crates/ruff_python_formatter/tests/fixtures.rs +input_file: crates/ruff_python_formatter/resources/test/fixtures/ruff/stub_files/decorated_class_after_function.pyi +--- +## Input +```python +# Issue #18865: Decorated classes below functions should be separated with blank lines +def hello(): ... +@lambda _, /: _ +class A: ... + +def world(): ... + +@final +class B: ... +``` + +## Output +```python +# Issue #18865: Decorated classes below functions should be separated with blank lines +def hello(): ... +@lambda _, /: _ +class A: ... + +def world(): ... +@final +class B: ... +``` + + +## Preview changes +```diff +--- Stable ++++ Preview +@@ -1,8 +1,10 @@ + # Issue #18865: Decorated classes below functions should be separated with blank lines + def hello(): ... ++ + @lambda _, /: _ + class A: ... + + def world(): ... ++ + @final + class B: ... +``` diff --git a/crates/ruff_python_formatter/tests/snapshots/format@stub_files__top_level.pyi.snap b/crates/ruff_python_formatter/tests/snapshots/format@stub_files__top_level.pyi.snap index 563fe96da4..e3afefb66e 100644 --- a/crates/ruff_python_formatter/tests/snapshots/format@stub_files__top_level.pyi.snap +++ b/crates/ruff_python_formatter/tests/snapshots/format@stub_files__top_level.pyi.snap @@ -1,7 +1,6 @@ --- source: crates/ruff_python_formatter/tests/fixtures.rs input_file: crates/ruff_python_formatter/resources/test/fixtures/ruff/stub_files/top_level.pyi -snapshot_kind: text --- ## Input ```python @@ -43,3 +42,18 @@ class LockType3: ... @final class LockType4: ... ``` + + +## Preview changes +```diff +--- Stable ++++ Preview +@@ -4,6 +4,7 @@ + def count2(): ... + @final + def count3(): ... ++ + @final + class LockType1: ... + +```