mirror of
https://github.com/astral-sh/ruff.git
synced 2025-09-26 11:59:10 +00:00
Implement blank_line_after_nested_stub_class
preview style (#9155)
## Summary This PR implements the `blank_line_after_nested_stub_class` preview style in the formatter. The logic is divided into 3 parts: 1. In between preceding and following nodes at top level and nested suite 2. When there's a trailing comment after the class 3. When there is no following node from (1) which is the case when it's the last or the only node in a suite We handle (3) with `FormatLeadingAlternateBranchComments`. ## Test Plan - Add new test cases and update existing snapshots - Checked the `typeshed` diff fixes: #8891
This commit is contained in:
parent
79f0522eb7
commit
541aef4e6c
13 changed files with 891 additions and 29 deletions
|
@ -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;
|
||||
|
||||
|
@ -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(
|
||||
|
|
|
@ -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,
|
||||
|
@ -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(
|
||||
|
|
|
@ -1,4 +1,6 @@
|
|||
use ruff_formatter::{write, FormatOwnedWithRule, FormatRefWithRule, FormatRuleWithOptions};
|
||||
use ruff_formatter::{
|
||||
write, FormatContext, FormatOwnedWithRule, FormatRefWithRule, FormatRuleWithOptions,
|
||||
};
|
||||
use ruff_python_ast::helpers::is_compound_statement;
|
||||
use ruff_python_ast::AnyNodeRef;
|
||||
use ruff_python_ast::{self as ast, Expr, PySourceType, Stmt, Suite};
|
||||
|
@ -12,8 +14,8 @@ use crate::context::{NodeLevel, TopLevelStatementPosition, WithIndentLevel, With
|
|||
use crate::expression::expr_string_literal::ExprStringLiteralKind;
|
||||
use crate::prelude::*;
|
||||
use crate::preview::{
|
||||
is_dummy_implementations_enabled, is_module_docstring_newlines_enabled,
|
||||
is_no_blank_line_before_class_docstring_enabled,
|
||||
is_blank_line_after_nested_stub_class_enabled, is_dummy_implementations_enabled,
|
||||
is_module_docstring_newlines_enabled, is_no_blank_line_before_class_docstring_enabled,
|
||||
};
|
||||
use crate::statement::stmt_expr::FormatStmtExpr;
|
||||
use crate::verbatim::{
|
||||
|
@ -470,17 +472,23 @@ fn stub_file_empty_lines(
|
|||
let empty_line_condition = preceding_comments.has_trailing()
|
||||
|| following_comments.has_leading()
|
||||
|| !stub_suite_can_omit_empty_line(preceding, following, f);
|
||||
let require_empty_line = should_insert_blank_line_after_class_in_stub_file(
|
||||
preceding.into(),
|
||||
Some(following.into()),
|
||||
f.context(),
|
||||
);
|
||||
match kind {
|
||||
SuiteKind::TopLevel => {
|
||||
if empty_line_condition {
|
||||
if empty_line_condition || require_empty_line {
|
||||
empty_line().fmt(f)
|
||||
} else {
|
||||
hard_line_break().fmt(f)
|
||||
}
|
||||
}
|
||||
SuiteKind::Class | SuiteKind::Other | SuiteKind::Function => {
|
||||
if empty_line_condition
|
||||
&& lines_after_ignoring_end_of_line_trivia(preceding.end(), source) > 1
|
||||
if (empty_line_condition
|
||||
&& lines_after_ignoring_end_of_line_trivia(preceding.end(), source) > 1)
|
||||
|| require_empty_line
|
||||
{
|
||||
empty_line().fmt(f)
|
||||
} else {
|
||||
|
@ -490,6 +498,122 @@ fn stub_file_empty_lines(
|
|||
}
|
||||
}
|
||||
|
||||
/// Checks if an empty line should be inserted after a class definition.
|
||||
///
|
||||
/// This is only valid if the [`blank_line_after_nested_stub_class`](https://github.com/astral-sh/ruff/issues/8891)
|
||||
/// preview rule is enabled and the source to be formatted is a stub file.
|
||||
///
|
||||
/// If `following` is `None`, then the preceding node is the last one in a suite. The
|
||||
/// caller needs to make sure that the suite which the preceding node is part of is
|
||||
/// followed by an alternate branch and shouldn't be a top-level suite.
|
||||
pub(crate) fn should_insert_blank_line_after_class_in_stub_file(
|
||||
preceding: AnyNodeRef<'_>,
|
||||
following: Option<AnyNodeRef<'_>>,
|
||||
context: &PyFormatContext,
|
||||
) -> bool {
|
||||
if !(is_blank_line_after_nested_stub_class_enabled(context)
|
||||
&& context.options().source_type().is_stub())
|
||||
{
|
||||
return false;
|
||||
}
|
||||
let comments = context.comments();
|
||||
match preceding.as_stmt_class_def() {
|
||||
Some(class) if contains_only_an_ellipsis(&class.body, comments) => {
|
||||
let Some(following) = following else {
|
||||
// The formatter is at the start of an alternate branch such as
|
||||
// an `else` block.
|
||||
//
|
||||
// ```python
|
||||
// if foo:
|
||||
// class Nested:
|
||||
// pass
|
||||
// else:
|
||||
// pass
|
||||
// ```
|
||||
//
|
||||
// In the above code, the preceding node is the `Nested` class
|
||||
// which has no following node.
|
||||
return true;
|
||||
};
|
||||
|
||||
// If the preceding class has decorators, then we need to add an empty
|
||||
// line even if it only contains ellipsis.
|
||||
//
|
||||
// ```python
|
||||
// class Top:
|
||||
// @decorator
|
||||
// class Nested1: ...
|
||||
// foo = 1
|
||||
// ```
|
||||
let preceding_has_decorators = !class.decorator_list.is_empty();
|
||||
|
||||
// If the following statement is a class definition, then an empty line
|
||||
// should be inserted if it (1) doesn't just contain ellipsis, or (2) has decorators.
|
||||
//
|
||||
// ```python
|
||||
// class Top:
|
||||
// class Nested1: ...
|
||||
// class Nested2:
|
||||
// pass
|
||||
//
|
||||
// class Top:
|
||||
// class Nested1: ...
|
||||
// @decorator
|
||||
// class Nested2: ...
|
||||
// ```
|
||||
//
|
||||
// Both of the above examples should add a blank line in between.
|
||||
let following_is_class_without_only_ellipsis_or_has_decorators =
|
||||
following.as_stmt_class_def().is_some_and(|following| {
|
||||
!contains_only_an_ellipsis(&following.body, comments)
|
||||
|| !following.decorator_list.is_empty()
|
||||
});
|
||||
|
||||
preceding_has_decorators
|
||||
|| following_is_class_without_only_ellipsis_or_has_decorators
|
||||
|| following.is_stmt_function_def()
|
||||
}
|
||||
Some(_) => {
|
||||
// Preceding statement is a class definition whose body isn't only an ellipsis.
|
||||
// Here, we should only add a blank line if the class doesn't have a trailing
|
||||
// own line comment as that's handled by the class formatting itself.
|
||||
!comments.has_trailing_own_line(preceding)
|
||||
}
|
||||
None => {
|
||||
// If preceding isn't a class definition, let's check if the last statement
|
||||
// in the body, going all the way down, is a class definition.
|
||||
//
|
||||
// ```python
|
||||
// if foo:
|
||||
// if bar:
|
||||
// class Nested:
|
||||
// pass
|
||||
// if other:
|
||||
// pass
|
||||
// ```
|
||||
//
|
||||
// But, if it contained a trailing own line comment, then it's handled
|
||||
// by the class formatting itself.
|
||||
//
|
||||
// ```python
|
||||
// if foo:
|
||||
// if bar:
|
||||
// class Nested:
|
||||
// pass
|
||||
// # comment
|
||||
// if other:
|
||||
// pass
|
||||
// ```
|
||||
std::iter::successors(
|
||||
preceding.last_child_in_body(),
|
||||
AnyNodeRef::last_child_in_body,
|
||||
)
|
||||
.take_while(|last_child| !comments.has_trailing_own_line(*last_child))
|
||||
.any(|last_child| last_child.is_stmt_class_def())
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/// Only a function to compute it lazily
|
||||
fn stub_suite_can_omit_empty_line(preceding: &Stmt, following: &Stmt, f: &PyFormatter) -> bool {
|
||||
// Two subsequent class definitions that both have an ellipsis only body
|
||||
|
|
Loading…
Add table
Add a link
Reference in a new issue