Skip to content

Commit

Permalink
Implement blank_line_after_nested_stub_class preview style
Browse files Browse the repository at this point in the history
  • Loading branch information
dhruvmanila committed Jan 29, 2024
1 parent ffd13e6 commit 30bbd6c
Show file tree
Hide file tree
Showing 12 changed files with 852 additions and 47 deletions.
@@ -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



111 changes: 73 additions & 38 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::blank_line_after_nested_stub_class_condition;

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

impl Format<PyFormatContext<'_>> for FormatLeadingAlternateBranchComments<'_> {
fn fmt(&self, f: &mut PyFormatter) -> FormatResult<()> {
let empty_line_condition = if is_blank_line_after_nested_stub_class_enabled(f.context()) {
self.last_node.map_or(false, |preceding| {
blank_line_after_nested_stub_class_condition(preceding, None, &f)
})
} else {
false
};

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!(
f,
[empty_lines(lines_before(
first_leading.start(),
f.context().source()
))]
)?;
if empty_line_condition {
write!(f, [empty_line()])?;
} else {
// Leading comments only preserves the lines after the comment but not before.
// Insert the necessary lines.
write!(
f,
[empty_lines(lines_before(
first_leading.start(),
f.context().source()
))]
)?;
}

write!(f, [leading_comments(self.comments)])?;
} else if let Some(last_preceding) = self.last_node {
// The leading comments formatting ensures that it preserves the right amount of lines
// after We need to take care of this ourselves, if there's no leading `else` comment.
// Since the `last_node` could be a compound node, we need to skip _all_ trivia.
//
// For example, here, when formatting the `if` statement, the `last_node` (the `while`)
// would end at the end of `pass`, but we want to skip _all_ comments:
// ```python
// if True:
// while True:
// pass
// # comment
//
// # comment
// else:
// ...
// ```
//
// `lines_after_ignoring_trivia` is safe here, as we _know_ that the `else` doesn't
// have any leading comments.
write!(
f,
[empty_lines(lines_after_ignoring_trivia(
last_preceding.end(),
f.context().source()
))]
)?;
if empty_line_condition {
write!(f, [empty_line()])?;
} else {
// The leading comments formatting ensures that it preserves the right amount of lines
// after We need to take care of this ourselves, if there's no leading `else` comment.
// Since the `last_node` could be a compound node, we need to skip _all_ trivia.
//
// For example, here, when formatting the `if` statement, the `last_node` (the `while`)
// would end at the end of `pass`, but we want to skip _all_ comments:
// ```python
// if True:
// while True:
// pass
// # comment
//
// # comment
// else:
// ...
// ```
//
// `lines_after_ignoring_trivia` is safe here, as we _know_ that the `else` doesn't
// have any leading comments.
write!(
f,
[empty_lines(lines_after_ignoring_trivia(
last_preceding.end(),
f.context().source()
))]
)?;
}
}

Ok(())
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,
),
(_, 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
7 changes: 5 additions & 2 deletions crates/ruff_python_formatter/src/statement/stmt_class_def.rs
@@ -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

0 comments on commit 30bbd6c

Please sign in to comment.