Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Implement blank_line_after_nested_stub_class preview style #9155

Merged
merged 3 commits into from Jan 30, 2024
Merged
Show file tree
Hide file tree
Changes from 2 commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Jump to
Jump to file
Failed to load files.
Diff view
Diff view
@@ -0,0 +1,6 @@
[
{
"source_type": "Stub",
"preview": "enabled"
}
]
@@ -0,0 +1,183 @@
class Top1:
pass
class Top2:
pass

class Top:
class Ellipsis: ...
class Ellipsis: ...

class Top:
class Ellipsis: ...
class Pass:
pass

class Top:
class Ellipsis: ...
class_variable = 1

class Top:
class TrailingComment:
pass
# comment
class Other:
pass

class Top:
class CommentWithEllipsis: ...
# comment
class Other: ...

class Top:
class TrailingCommentWithMultipleBlankLines:
pass


# comment
class Other:
pass

class Top:
class Nested:
pass

# comment
class LeadingComment:
pass

class Top:
@decorator
class Ellipsis: ...
class Ellipsis: ...

class Top:
@decorator
class Ellipsis: ...
@decorator
class Ellipsis: ...

class Top:
@decorator
class Ellipsis: ...
@decorator
class Pass:
pass

class Top:
class Foo:
pass




class AfterMultipleEmptyLines:
pass

class Top:
class Nested11:
class Nested12:
pass
class Nested21:
pass

class Top:
class Nested11:
class Nested12:
pass
# comment
class Nested21:
pass

class Top:
class Nested11:
class Nested12:
pass
# comment
class Nested21:
pass
# comment

class Top1:
class Nested:
pass
class Top2:
pass

class Top1:
class Nested:
pass
# comment
class Top2:
pass

class Top1:
class Nested:
pass
# comment
class Top2:
pass

if foo:
class Nested1:
pass
class Nested2:
pass
else:
pass

if foo:
class Nested1:
pass
class Nested2:
pass
# comment
elif bar:
class Nested1:
pass
# comment
else:
pass

if top1:
class Nested:
pass
if top2:
pass

if top1:
class Nested:
pass
# comment
if top2:
pass

if top1:
class Nested:
pass
# comment
if top2:
pass

try:
class Try:
pass
except:
class Except:
pass
foo = 1

match foo:
case 1:
class Nested:
pass
case 2:
class Nested:
pass
case _:
class Nested:
pass
foo = 1

class Eof:
class Nested:
pass
@@ -0,0 +1,6 @@
[
{
"source_type": "Stub",
"preview": "enabled"
}
]
@@ -0,0 +1,17 @@
# A separate file to test out the behavior when there are a mix of blank lines
# and comments at EOF just after a nested stub class.

class Top:
class Nested1:
class Nested12:
pass
# comment
class Nested2:
pass



# comment



43 changes: 39 additions & 4 deletions crates/ruff_python_formatter/src/comments/format.rs
@@ -1,8 +1,7 @@
use std::borrow::Cow;

use ruff_formatter::{format_args, write, FormatError, FormatOptions, SourceCode};
use ruff_python_ast::PySourceType;
use ruff_python_ast::{AnyNodeRef, AstNode};
use ruff_python_ast::{AnyNodeRef, AstNode, NodeKind, PySourceType};
use ruff_python_trivia::{
is_pragma_comment, lines_after, lines_after_ignoring_trivia, lines_before,
};
Expand All @@ -11,6 +10,8 @@ use ruff_text_size::{Ranged, TextLen, TextRange};
use crate::comments::{CommentLinePosition, SourceComment};
use crate::context::NodeLevel;
use crate::prelude::*;
use crate::preview::is_blank_line_after_nested_stub_class_enabled;
use crate::statement::suite::should_insert_blank_line_after_class_in_stub_file;

/// Formats the leading comments of a node.
pub(crate) fn leading_node_comments<T>(node: &T) -> FormatLeadingComments
Expand Down Expand Up @@ -85,7 +86,23 @@ pub(crate) struct FormatLeadingAlternateBranchComments<'a> {

impl Format<PyFormatContext<'_>> for FormatLeadingAlternateBranchComments<'_> {
fn fmt(&self, f: &mut PyFormatter) -> FormatResult<()> {
if let Some(first_leading) = self.comments.first() {
let require_empty_line = if is_blank_line_after_nested_stub_class_enabled(f.context())
&& f.options().source_type().is_stub()
{
self.last_node.map_or(false, |preceding| {
should_insert_blank_line_after_class_in_stub_file(
preceding,
None,
f.context().comments(),
)
})
} else {
false
};

if require_empty_line {
write!(f, [empty_line(), leading_comments(self.comments)])?;
} else if let Some(first_leading) = self.comments.first() {
// Leading comments only preserves the lines after the comment but not before.
// Insert the necessary lines.
write!(
Expand Down Expand Up @@ -513,14 +530,32 @@ fn strip_comment_prefix(comment_text: &str) -> FormatResult<&str> {
/// ```
///
/// This builder will insert two empty lines before the comment.
///
/// # Preview
///
/// For preview style, this builder will insert a single empty line after a
/// class definition in a stub file.
///
/// For example, given:
/// ```python
/// class Foo:
/// pass
/// # comment
/// ```
///
/// This builder will insert a single empty line before the comment.
pub(crate) fn empty_lines_before_trailing_comments<'a>(
f: &PyFormatter,
comments: &'a [SourceComment],
node_kind: NodeKind,
) -> FormatEmptyLinesBeforeTrailingComments<'a> {
// Black has different rules for stub vs. non-stub and top level vs. indented
let empty_lines = match (f.options().source_type(), f.context().node_level()) {
(PySourceType::Stub, NodeLevel::TopLevel(_)) => 1,
(PySourceType::Stub, _) => 0,
(PySourceType::Stub, _) => u32::from(
is_blank_line_after_nested_stub_class_enabled(f.context())
&& node_kind == NodeKind::StmtClassDef,
),
MichaReiser marked this conversation as resolved.
Show resolved Hide resolved
(_, NodeLevel::TopLevel(_)) => 2,
(_, _) => 1,
};
Expand Down
7 changes: 7 additions & 0 deletions crates/ruff_python_formatter/src/preview.rs
Expand Up @@ -48,6 +48,13 @@ pub(crate) const fn is_wrap_multiple_context_managers_in_parens_enabled(
context.is_preview()
}

/// Returns `true` if the [`blank_line_after_nested_stub_class`](https://github.com/astral-sh/ruff/issues/8891) preview style is enabled.
pub(crate) const fn is_blank_line_after_nested_stub_class_enabled(
context: &PyFormatContext,
) -> bool {
context.is_preview()
}

/// Returns `true` if the [`module_docstring_newlines`](https://github.com/astral-sh/ruff/issues/7995) preview style is enabled.
pub(crate) const fn is_module_docstring_newlines_enabled(context: &PyFormatContext) -> bool {
context.is_preview()
Expand Down
@@ -1,5 +1,5 @@
use ruff_formatter::write;
use ruff_python_ast::{Decorator, StmtClassDef};
use ruff_python_ast::{Decorator, NodeKind, StmtClassDef};
use ruff_python_trivia::lines_after_ignoring_end_of_line_trivia;
use ruff_text_size::Ranged;

Expand Down Expand Up @@ -152,7 +152,10 @@ impl FormatNodeRule<StmtClassDef> for FormatStmtClassDef {
//
// # comment
// ```
empty_lines_before_trailing_comments(f, comments.trailing(item)).fmt(f)
empty_lines_before_trailing_comments(f, comments.trailing(item), NodeKind::StmtClassDef)
.fmt(f)?;

Ok(())
}

fn fmt_dangling_comments(
Expand Down
@@ -1,5 +1,5 @@
use ruff_formatter::write;
use ruff_python_ast::StmtFunctionDef;
use ruff_python_ast::{NodeKind, StmtFunctionDef};

use crate::comments::format::{
empty_lines_after_leading_comments, empty_lines_before_trailing_comments,
Expand Down Expand Up @@ -87,7 +87,8 @@ impl FormatNodeRule<StmtFunctionDef> for FormatStmtFunctionDef {
//
// # comment
// ```
empty_lines_before_trailing_comments(f, comments.trailing(item)).fmt(f)
empty_lines_before_trailing_comments(f, comments.trailing(item), NodeKind::StmtFunctionDef)
.fmt(f)
}

fn fmt_dangling_comments(
Expand Down