mirror of
https://github.com/astral-sh/ruff.git
synced 2025-08-04 02:38:25 +00:00
Insert empty line between suite and alternative branch after def/class (#12294)
When there is a function or class definition at the end of a suite followed by the beginning of an alternative block, we have to insert a single empty line between them. In the if-else-statement example below, we insert an empty line after the `foo` in the if-block, but none after the else-block `foo`, since in the latter case the enclosing suite already adds empty lines. ```python if sys.version_info >= (3, 10): def foo(): return "new" else: def foo(): return "old" class Bar: pass ``` To do so, we track whether the current suite is the last one in the current statement with a new option on the suite kind. Fixes #12199 --------- Co-authored-by: Micha Reiser <micha@reiser.io>
This commit is contained in:
parent
ecd4b4d943
commit
9a817a2922
25 changed files with 699 additions and 149 deletions
|
@ -249,6 +249,68 @@ if True:
|
|||
print()
|
||||
|
||||
|
||||
if True:
|
||||
def a():
|
||||
return 1
|
||||
else:
|
||||
pass
|
||||
|
||||
if True:
|
||||
# fmt: off
|
||||
def a():
|
||||
return 1
|
||||
# fmt: on
|
||||
else:
|
||||
pass
|
||||
|
||||
match True:
|
||||
case 1:
|
||||
def a():
|
||||
return 1
|
||||
case 1:
|
||||
def a():
|
||||
return 1
|
||||
|
||||
try:
|
||||
def a():
|
||||
return 1
|
||||
except RuntimeError:
|
||||
def a():
|
||||
return 1
|
||||
|
||||
try:
|
||||
def a():
|
||||
return 1
|
||||
finally:
|
||||
def a():
|
||||
return 1
|
||||
|
||||
try:
|
||||
def a():
|
||||
return 1
|
||||
except RuntimeError:
|
||||
def a():
|
||||
return 1
|
||||
except ZeroDivisionError:
|
||||
def a():
|
||||
return 1
|
||||
else:
|
||||
def a():
|
||||
return 1
|
||||
finally:
|
||||
def a():
|
||||
return 1
|
||||
|
||||
if raw:
|
||||
def show_file(lines):
|
||||
for line in lines:
|
||||
pass
|
||||
# Trailing comment not on function or class
|
||||
|
||||
else:
|
||||
pass
|
||||
|
||||
|
||||
# NOTE: Please keep this the last block in this file. This tests that we don't insert
|
||||
# empty line(s) at the end of the file due to nested function
|
||||
if True:
|
||||
|
|
|
@ -154,8 +154,14 @@ def f():
|
|||
pass
|
||||
|
||||
|
||||
if True:
|
||||
def a():
|
||||
return 1
|
||||
else:
|
||||
pass
|
||||
|
||||
|
||||
# comment
|
||||
|
||||
x = 1
|
||||
|
||||
|
|
|
@ -2,6 +2,7 @@ use ruff_python_ast::ElifElseClause;
|
|||
|
||||
use crate::prelude::*;
|
||||
use crate::statement::stmt_if::format_elif_else_clause;
|
||||
use crate::statement::suite::SuiteKind;
|
||||
|
||||
/// Note that this implementation misses the leading newlines before the leading comments because
|
||||
/// it does not have access to the last node of the previous branch. The `StmtIf` therefore doesn't
|
||||
|
@ -11,6 +12,15 @@ pub struct FormatElifElseClause;
|
|||
|
||||
impl FormatNodeRule<ElifElseClause> for FormatElifElseClause {
|
||||
fn fmt_fields(&self, item: &ElifElseClause, f: &mut PyFormatter) -> FormatResult<()> {
|
||||
format_elif_else_clause(item, f, None)
|
||||
format_elif_else_clause(
|
||||
item,
|
||||
f,
|
||||
None,
|
||||
SuiteKind::Other {
|
||||
// For stability, we can't insert an empty line if we don't know if the outer suite
|
||||
// also does.
|
||||
last_suite_in_statement: true,
|
||||
},
|
||||
)
|
||||
}
|
||||
}
|
||||
|
|
|
@ -6,9 +6,10 @@ use crate::expression::maybe_parenthesize_expression;
|
|||
use crate::expression::parentheses::Parenthesize;
|
||||
use crate::prelude::*;
|
||||
use crate::statement::clause::{clause_body, clause_header, ClauseHeader};
|
||||
use crate::statement::suite::SuiteKind;
|
||||
|
||||
#[derive(Copy, Clone, Default)]
|
||||
pub enum ExceptHandlerKind {
|
||||
pub(crate) enum ExceptHandlerKind {
|
||||
#[default]
|
||||
Regular,
|
||||
Starred,
|
||||
|
@ -16,16 +17,18 @@ pub enum ExceptHandlerKind {
|
|||
|
||||
#[derive(Default)]
|
||||
pub struct FormatExceptHandlerExceptHandler {
|
||||
except_handler_kind: ExceptHandlerKind,
|
||||
pub(crate) except_handler_kind: ExceptHandlerKind,
|
||||
pub(crate) last_suite_in_statement: bool,
|
||||
}
|
||||
|
||||
impl FormatRuleWithOptions<ExceptHandlerExceptHandler, PyFormatContext<'_>>
|
||||
for FormatExceptHandlerExceptHandler
|
||||
{
|
||||
type Options = ExceptHandlerKind;
|
||||
type Options = FormatExceptHandlerExceptHandler;
|
||||
|
||||
fn with_options(mut self, options: Self::Options) -> Self {
|
||||
self.except_handler_kind = options;
|
||||
self.except_handler_kind = options.except_handler_kind;
|
||||
self.last_suite_in_statement = options.last_suite_in_statement;
|
||||
self
|
||||
}
|
||||
}
|
||||
|
@ -36,6 +39,7 @@ impl FormatNodeRule<ExceptHandlerExceptHandler> for FormatExceptHandlerExceptHan
|
|||
item: &ExceptHandlerExceptHandler,
|
||||
f: &mut PyFormatter,
|
||||
) -> FormatResult<()> {
|
||||
let except_handler_kind = self.except_handler_kind;
|
||||
let ExceptHandlerExceptHandler {
|
||||
range: _,
|
||||
type_,
|
||||
|
@ -57,7 +61,7 @@ impl FormatNodeRule<ExceptHandlerExceptHandler> for FormatExceptHandlerExceptHan
|
|||
f,
|
||||
[
|
||||
token("except"),
|
||||
match self.except_handler_kind {
|
||||
match except_handler_kind {
|
||||
ExceptHandlerKind::Regular => None,
|
||||
ExceptHandlerKind::Starred => Some(token("*")),
|
||||
}
|
||||
|
@ -84,7 +88,11 @@ impl FormatNodeRule<ExceptHandlerExceptHandler> for FormatExceptHandlerExceptHan
|
|||
Ok(())
|
||||
}),
|
||||
),
|
||||
clause_body(body, dangling_comments),
|
||||
clause_body(
|
||||
body,
|
||||
SuiteKind::other(self.last_suite_in_statement),
|
||||
dangling_comments
|
||||
),
|
||||
]
|
||||
)
|
||||
}
|
||||
|
|
|
@ -1,4 +1,4 @@
|
|||
use ruff_formatter::write;
|
||||
use ruff_formatter::{write, FormatRuleWithOptions};
|
||||
use ruff_python_ast::AstNode;
|
||||
use ruff_python_ast::MatchCase;
|
||||
|
||||
|
@ -6,9 +6,21 @@ use crate::builders::parenthesize_if_expands;
|
|||
use crate::expression::parentheses::{NeedsParentheses, OptionalParentheses, Parentheses};
|
||||
use crate::prelude::*;
|
||||
use crate::statement::clause::{clause_body, clause_header, ClauseHeader};
|
||||
use crate::statement::suite::SuiteKind;
|
||||
|
||||
#[derive(Default)]
|
||||
pub struct FormatMatchCase;
|
||||
pub struct FormatMatchCase {
|
||||
last_suite_in_statement: bool,
|
||||
}
|
||||
|
||||
impl FormatRuleWithOptions<MatchCase, PyFormatContext<'_>> for FormatMatchCase {
|
||||
type Options = bool;
|
||||
|
||||
fn with_options(mut self, options: Self::Options) -> Self {
|
||||
self.last_suite_in_statement = options;
|
||||
self
|
||||
}
|
||||
}
|
||||
|
||||
impl FormatNodeRule<MatchCase> for FormatMatchCase {
|
||||
fn fmt_fields(&self, item: &MatchCase, f: &mut PyFormatter) -> FormatResult<()> {
|
||||
|
@ -63,7 +75,11 @@ impl FormatNodeRule<MatchCase> for FormatMatchCase {
|
|||
Ok(())
|
||||
}),
|
||||
),
|
||||
clause_body(body, dangling_item_comments),
|
||||
clause_body(
|
||||
body,
|
||||
SuiteKind::other(self.last_suite_in_statement),
|
||||
dangling_item_comments
|
||||
),
|
||||
]
|
||||
)
|
||||
}
|
||||
|
|
|
@ -380,21 +380,14 @@ pub(crate) struct FormatClauseBody<'a> {
|
|||
trailing_comments: &'a [SourceComment],
|
||||
}
|
||||
|
||||
impl<'a> FormatClauseBody<'a> {
|
||||
#[must_use]
|
||||
pub(crate) fn with_kind(mut self, kind: SuiteKind) -> Self {
|
||||
self.kind = kind;
|
||||
self
|
||||
}
|
||||
}
|
||||
|
||||
pub(crate) fn clause_body<'a>(
|
||||
body: &'a Suite,
|
||||
kind: SuiteKind,
|
||||
trailing_comments: &'a [SourceComment],
|
||||
) -> FormatClauseBody<'a> {
|
||||
FormatClauseBody {
|
||||
body,
|
||||
kind: SuiteKind::default(),
|
||||
kind,
|
||||
trailing_comments,
|
||||
}
|
||||
}
|
||||
|
|
|
@ -132,7 +132,7 @@ impl FormatNodeRule<StmtClassDef> for FormatStmtClassDef {
|
|||
Ok(())
|
||||
}),
|
||||
),
|
||||
clause_body(body, trailing_definition_comments).with_kind(SuiteKind::Class),
|
||||
clause_body(body, SuiteKind::Class, trailing_definition_comments),
|
||||
]
|
||||
)?;
|
||||
|
||||
|
|
|
@ -7,6 +7,7 @@ use crate::expression::maybe_parenthesize_expression;
|
|||
use crate::expression::parentheses::Parenthesize;
|
||||
use crate::prelude::*;
|
||||
use crate::statement::clause::{clause_body, clause_header, ClauseHeader, ElseClause};
|
||||
use crate::statement::suite::SuiteKind;
|
||||
|
||||
#[derive(Debug)]
|
||||
struct ExprTupleWithoutParentheses<'a>(&'a Expr);
|
||||
|
@ -63,7 +64,11 @@ impl FormatNodeRule<StmtFor> for FormatStmtFor {
|
|||
maybe_parenthesize_expression(iter, item, Parenthesize::IfBreaks),
|
||||
],
|
||||
),
|
||||
clause_body(body, trailing_condition_comments),
|
||||
clause_body(
|
||||
body,
|
||||
SuiteKind::other(orelse.is_empty()),
|
||||
trailing_condition_comments
|
||||
),
|
||||
]
|
||||
)?;
|
||||
|
||||
|
@ -85,7 +90,7 @@ impl FormatNodeRule<StmtFor> for FormatStmtFor {
|
|||
&token("else"),
|
||||
)
|
||||
.with_leading_comments(leading, body.last()),
|
||||
clause_body(orelse, trailing),
|
||||
clause_body(orelse, SuiteKind::other(true), trailing),
|
||||
]
|
||||
)?;
|
||||
}
|
||||
|
|
|
@ -66,7 +66,7 @@ impl FormatNodeRule<StmtFunctionDef> for FormatStmtFunctionDef {
|
|||
trailing_definition_comments,
|
||||
&format_with(|f| format_function_header(f, item)),
|
||||
),
|
||||
clause_body(body, trailing_definition_comments).with_kind(SuiteKind::Function),
|
||||
clause_body(body, SuiteKind::Function, trailing_definition_comments),
|
||||
]
|
||||
)?;
|
||||
|
||||
|
|
|
@ -1,12 +1,12 @@
|
|||
use ruff_formatter::{format_args, write};
|
||||
use ruff_python_ast::AnyNodeRef;
|
||||
use ruff_python_ast::{ElifElseClause, StmtIf};
|
||||
use ruff_python_ast::{AnyNodeRef, ElifElseClause, StmtIf};
|
||||
use ruff_text_size::Ranged;
|
||||
|
||||
use crate::expression::maybe_parenthesize_expression;
|
||||
use crate::expression::parentheses::Parenthesize;
|
||||
use crate::prelude::*;
|
||||
use crate::statement::clause::{clause_body, clause_header, ClauseHeader};
|
||||
use crate::statement::suite::SuiteKind;
|
||||
|
||||
#[derive(Default)]
|
||||
pub struct FormatStmtIf;
|
||||
|
@ -35,13 +35,22 @@ impl FormatNodeRule<StmtIf> for FormatStmtIf {
|
|||
maybe_parenthesize_expression(test, item, Parenthesize::IfBreaks),
|
||||
],
|
||||
),
|
||||
clause_body(body, trailing_colon_comment),
|
||||
clause_body(
|
||||
body,
|
||||
SuiteKind::other(elif_else_clauses.is_empty()),
|
||||
trailing_colon_comment
|
||||
),
|
||||
]
|
||||
)?;
|
||||
|
||||
let mut last_node = body.last().unwrap().into();
|
||||
for clause in elif_else_clauses {
|
||||
format_elif_else_clause(clause, f, Some(last_node))?;
|
||||
format_elif_else_clause(
|
||||
clause,
|
||||
f,
|
||||
Some(last_node),
|
||||
SuiteKind::other(clause == elif_else_clauses.last().unwrap()),
|
||||
)?;
|
||||
last_node = clause.body.last().unwrap().into();
|
||||
}
|
||||
|
||||
|
@ -55,6 +64,7 @@ pub(crate) fn format_elif_else_clause(
|
|||
item: &ElifElseClause,
|
||||
f: &mut PyFormatter,
|
||||
last_node: Option<AnyNodeRef>,
|
||||
suite_kind: SuiteKind,
|
||||
) -> FormatResult<()> {
|
||||
let ElifElseClause {
|
||||
range: _,
|
||||
|
@ -93,7 +103,7 @@ pub(crate) fn format_elif_else_clause(
|
|||
}),
|
||||
)
|
||||
.with_leading_comments(leading_comments, last_node),
|
||||
clause_body(body, trailing_colon_comment),
|
||||
clause_body(body, suite_kind, trailing_colon_comment),
|
||||
f.options()
|
||||
.source_map_generation()
|
||||
.is_enabled()
|
||||
|
|
|
@ -48,6 +48,7 @@ impl FormatNodeRule<StmtMatch> for FormatStmtMatch {
|
|||
let mut last_case = first;
|
||||
|
||||
for case in cases_iter {
|
||||
let last_suite_in_statement = Some(case) == cases.last();
|
||||
write!(
|
||||
f,
|
||||
[block_indent(&format_args!(
|
||||
|
@ -55,7 +56,7 @@ impl FormatNodeRule<StmtMatch> for FormatStmtMatch {
|
|||
comments.leading(case),
|
||||
last_case.body.last(),
|
||||
),
|
||||
case.format()
|
||||
case.format().with_options(last_suite_in_statement)
|
||||
))]
|
||||
)?;
|
||||
last_case = case;
|
||||
|
|
|
@ -5,9 +5,12 @@ use ruff_text_size::Ranged;
|
|||
use crate::comments;
|
||||
use crate::comments::leading_alternate_branch_comments;
|
||||
use crate::comments::SourceComment;
|
||||
use crate::other::except_handler_except_handler::ExceptHandlerKind;
|
||||
use crate::other::except_handler_except_handler::{
|
||||
ExceptHandlerKind, FormatExceptHandlerExceptHandler,
|
||||
};
|
||||
use crate::prelude::*;
|
||||
use crate::statement::clause::{clause_body, clause_header, ClauseHeader, ElseClause};
|
||||
use crate::statement::suite::SuiteKind;
|
||||
use crate::statement::{FormatRefWithRule, Stmt};
|
||||
|
||||
#[derive(Default)]
|
||||
|
@ -16,13 +19,15 @@ pub struct FormatStmtTry;
|
|||
#[derive(Copy, Clone, Default)]
|
||||
pub struct FormatExceptHandler {
|
||||
except_handler_kind: ExceptHandlerKind,
|
||||
last_suite_in_statement: bool,
|
||||
}
|
||||
|
||||
impl FormatRuleWithOptions<ExceptHandler, PyFormatContext<'_>> for FormatExceptHandler {
|
||||
type Options = ExceptHandlerKind;
|
||||
type Options = FormatExceptHandler;
|
||||
|
||||
fn with_options(mut self, options: Self::Options) -> Self {
|
||||
self.except_handler_kind = options;
|
||||
self.except_handler_kind = options.except_handler_kind;
|
||||
self.last_suite_in_statement = options.last_suite_in_statement;
|
||||
self
|
||||
}
|
||||
}
|
||||
|
@ -32,7 +37,10 @@ impl FormatRule<ExceptHandler, PyFormatContext<'_>> for FormatExceptHandler {
|
|||
match item {
|
||||
ExceptHandler::ExceptHandler(except_handler) => except_handler
|
||||
.format()
|
||||
.with_options(self.except_handler_kind)
|
||||
.with_options(FormatExceptHandlerExceptHandler {
|
||||
except_handler_kind: self.except_handler_kind,
|
||||
last_suite_in_statement: self.last_suite_in_statement,
|
||||
})
|
||||
.fmt(f),
|
||||
}
|
||||
}
|
||||
|
@ -56,8 +64,8 @@ impl FormatNodeRule<StmtTry> for FormatStmtTry {
|
|||
let StmtTry {
|
||||
body,
|
||||
handlers,
|
||||
orelse: _,
|
||||
finalbody: _,
|
||||
orelse,
|
||||
finalbody,
|
||||
is_star,
|
||||
range: _,
|
||||
} = item;
|
||||
|
@ -65,31 +73,51 @@ impl FormatNodeRule<StmtTry> for FormatStmtTry {
|
|||
let comments_info = f.context().comments().clone();
|
||||
let mut dangling_comments = comments_info.dangling(item);
|
||||
|
||||
(_, dangling_comments) = format_case(item, CaseKind::Try, None, dangling_comments, f)?;
|
||||
(_, dangling_comments) =
|
||||
format_case(item, CaseKind::Try, None, dangling_comments, false, f)?;
|
||||
let mut previous_node = body.last();
|
||||
|
||||
for handler in handlers {
|
||||
let handler_comments = comments_info.leading(handler);
|
||||
let ExceptHandler::ExceptHandler(except_handler) = handler;
|
||||
let except_handler_kind = if *is_star {
|
||||
ExceptHandlerKind::Starred
|
||||
} else {
|
||||
ExceptHandlerKind::Regular
|
||||
};
|
||||
let last_suite_in_statement =
|
||||
handler == handlers.last().unwrap() && orelse.is_empty() && finalbody.is_empty();
|
||||
|
||||
write!(
|
||||
f,
|
||||
[
|
||||
leading_alternate_branch_comments(handler_comments, previous_node),
|
||||
&handler.format().with_options(if *is_star {
|
||||
ExceptHandlerKind::Starred
|
||||
} else {
|
||||
ExceptHandlerKind::Regular
|
||||
}),
|
||||
&handler.format().with_options(FormatExceptHandler {
|
||||
except_handler_kind,
|
||||
last_suite_in_statement
|
||||
})
|
||||
]
|
||||
)?;
|
||||
previous_node = match handler {
|
||||
ExceptHandler::ExceptHandler(handler) => handler.body.last(),
|
||||
};
|
||||
previous_node = except_handler.body.last();
|
||||
}
|
||||
|
||||
(previous_node, dangling_comments) =
|
||||
format_case(item, CaseKind::Else, previous_node, dangling_comments, f)?;
|
||||
(previous_node, dangling_comments) = format_case(
|
||||
item,
|
||||
CaseKind::Else,
|
||||
previous_node,
|
||||
dangling_comments,
|
||||
finalbody.is_empty(),
|
||||
f,
|
||||
)?;
|
||||
|
||||
format_case(item, CaseKind::Finally, previous_node, dangling_comments, f)?;
|
||||
format_case(
|
||||
item,
|
||||
CaseKind::Finally,
|
||||
previous_node,
|
||||
dangling_comments,
|
||||
true,
|
||||
f,
|
||||
)?;
|
||||
|
||||
write!(f, [comments::dangling_comments(dangling_comments)])
|
||||
}
|
||||
|
@ -100,6 +128,7 @@ fn format_case<'a>(
|
|||
kind: CaseKind,
|
||||
previous_node: Option<&'a Stmt>,
|
||||
dangling_comments: &'a [SourceComment],
|
||||
last_suite_in_statement: bool,
|
||||
f: &mut PyFormatter,
|
||||
) -> FormatResult<(Option<&'a Stmt>, &'a [SourceComment])> {
|
||||
let body = match kind {
|
||||
|
@ -129,7 +158,11 @@ fn format_case<'a>(
|
|||
[
|
||||
clause_header(header, trailing_case_comments, &token(kind.keyword()))
|
||||
.with_leading_comments(leading_case_comments, previous_node),
|
||||
clause_body(body, trailing_case_comments),
|
||||
clause_body(
|
||||
body,
|
||||
SuiteKind::other(last_suite_in_statement),
|
||||
trailing_case_comments
|
||||
),
|
||||
]
|
||||
)?;
|
||||
(Some(last), rest)
|
||||
|
|
|
@ -7,6 +7,7 @@ use crate::expression::maybe_parenthesize_expression;
|
|||
use crate::expression::parentheses::Parenthesize;
|
||||
use crate::prelude::*;
|
||||
use crate::statement::clause::{clause_body, clause_header, ClauseHeader, ElseClause};
|
||||
use crate::statement::suite::SuiteKind;
|
||||
|
||||
#[derive(Default)]
|
||||
pub struct FormatStmtWhile;
|
||||
|
@ -42,7 +43,11 @@ impl FormatNodeRule<StmtWhile> for FormatStmtWhile {
|
|||
maybe_parenthesize_expression(test, item, Parenthesize::IfBreaks),
|
||||
]
|
||||
),
|
||||
clause_body(body, trailing_condition_comments),
|
||||
clause_body(
|
||||
body,
|
||||
SuiteKind::other(orelse.is_empty()),
|
||||
trailing_condition_comments
|
||||
),
|
||||
]
|
||||
)?;
|
||||
|
||||
|
@ -62,7 +67,7 @@ impl FormatNodeRule<StmtWhile> for FormatStmtWhile {
|
|||
&token("else")
|
||||
)
|
||||
.with_leading_comments(leading, body.last()),
|
||||
clause_body(orelse, trailing),
|
||||
clause_body(orelse, SuiteKind::other(true), trailing),
|
||||
]
|
||||
)?;
|
||||
}
|
||||
|
|
|
@ -14,6 +14,7 @@ use crate::other::commas;
|
|||
use crate::other::with_item::WithItemLayout;
|
||||
use crate::prelude::*;
|
||||
use crate::statement::clause::{clause_body, clause_header, ClauseHeader};
|
||||
use crate::statement::suite::SuiteKind;
|
||||
use crate::PythonVersion;
|
||||
|
||||
#[derive(Default)]
|
||||
|
@ -124,7 +125,7 @@ impl FormatNodeRule<StmtWith> for FormatStmtWith {
|
|||
}
|
||||
})
|
||||
),
|
||||
clause_body(&with_stmt.body, colon_comments)
|
||||
clause_body(&with_stmt.body, SuiteKind::other(true), colon_comments)
|
||||
]
|
||||
)
|
||||
}
|
||||
|
|
|
@ -20,7 +20,7 @@ use crate::verbatim::{
|
|||
};
|
||||
|
||||
/// Level at which the [`Suite`] appears in the source code.
|
||||
#[derive(Copy, Clone, Debug, Default, PartialEq, Eq)]
|
||||
#[derive(Copy, Clone, Debug, PartialEq, Eq)]
|
||||
pub enum SuiteKind {
|
||||
/// Statements at the module level / top level
|
||||
TopLevel,
|
||||
|
@ -32,23 +32,62 @@ pub enum SuiteKind {
|
|||
Class,
|
||||
|
||||
/// Statements in any other body (e.g., `if` or `while`).
|
||||
#[default]
|
||||
Other,
|
||||
Other {
|
||||
/// Whether this suite is the last suite in the current statement.
|
||||
///
|
||||
/// Below, `last_suite_in_statement` is `false` for the suite containing `foo10` and `foo12`
|
||||
/// and `true` for the suite containing `bar`.
|
||||
/// ```python
|
||||
/// if sys.version_info >= (3, 10):
|
||||
/// def foo10():
|
||||
/// return "new"
|
||||
/// elif sys.version_info >= (3, 12):
|
||||
/// def foo12():
|
||||
/// return "new"
|
||||
/// else:
|
||||
/// def bar():
|
||||
/// return "old"
|
||||
/// ```
|
||||
///
|
||||
/// When this value is true, we don't insert trailing empty lines since the containing suite
|
||||
/// will do that.
|
||||
last_suite_in_statement: bool,
|
||||
},
|
||||
}
|
||||
|
||||
#[derive(Debug)]
|
||||
pub struct FormatSuite {
|
||||
kind: SuiteKind,
|
||||
}
|
||||
|
||||
impl Default for FormatSuite {
|
||||
impl Default for SuiteKind {
|
||||
fn default() -> Self {
|
||||
FormatSuite {
|
||||
kind: SuiteKind::Other,
|
||||
Self::Other {
|
||||
// For stability, we can't insert an empty line if we don't know if the outer suite
|
||||
// also does.
|
||||
last_suite_in_statement: true,
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
impl SuiteKind {
|
||||
/// See [`SuiteKind::Other`].
|
||||
pub fn other(last_suite_in_statement: bool) -> Self {
|
||||
Self::Other {
|
||||
last_suite_in_statement,
|
||||
}
|
||||
}
|
||||
|
||||
pub fn last_suite_in_statement(self) -> bool {
|
||||
match self {
|
||||
Self::Other {
|
||||
last_suite_in_statement,
|
||||
} => last_suite_in_statement,
|
||||
_ => true,
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
#[derive(Debug, Default)]
|
||||
pub struct FormatSuite {
|
||||
kind: SuiteKind,
|
||||
}
|
||||
|
||||
impl FormatRule<Suite, PyFormatContext<'_>> for FormatSuite {
|
||||
fn fmt(&self, statements: &Suite, f: &mut PyFormatter) -> FormatResult<()> {
|
||||
let mut iter = statements.iter();
|
||||
|
@ -64,7 +103,7 @@ impl FormatRule<Suite, PyFormatContext<'_>> for FormatSuite {
|
|||
TopLevelStatementPosition::Other
|
||||
}),
|
||||
),
|
||||
SuiteKind::Function | SuiteKind::Class | SuiteKind::Other => {
|
||||
SuiteKind::Function | SuiteKind::Class | SuiteKind::Other { .. } => {
|
||||
NodeLevel::CompoundStatement
|
||||
}
|
||||
};
|
||||
|
@ -78,7 +117,7 @@ impl FormatRule<Suite, PyFormatContext<'_>> for FormatSuite {
|
|||
|
||||
// Format the first statement in the body, which often has special formatting rules.
|
||||
let first = match self.kind {
|
||||
SuiteKind::Other => {
|
||||
SuiteKind::Other { .. } => {
|
||||
if is_class_or_function_definition(first)
|
||||
&& !comments.has_leading(first)
|
||||
&& !source_type.is_stub()
|
||||
|
@ -161,55 +200,7 @@ impl FormatRule<Suite, PyFormatContext<'_>> for FormatSuite {
|
|||
// Here we insert empty lines even if the preceding has a trailing own line comment
|
||||
true
|
||||
} else {
|
||||
// Find nested class or function definitions that need an empty line after them.
|
||||
//
|
||||
// ```python
|
||||
// def f():
|
||||
// if True:
|
||||
//
|
||||
// def double(s):
|
||||
// return s + s
|
||||
//
|
||||
// print("below function")
|
||||
// ```
|
||||
std::iter::successors(
|
||||
Some(AnyNodeRef::from(preceding)),
|
||||
AnyNodeRef::last_child_in_body,
|
||||
)
|
||||
.take_while(|last_child|
|
||||
// If there is a comment between preceding and following the empty lines were
|
||||
// inserted before the comment by preceding and there are no extra empty lines
|
||||
// after the comment.
|
||||
// ```python
|
||||
// class Test:
|
||||
// def a(self):
|
||||
// pass
|
||||
// # trailing comment
|
||||
//
|
||||
//
|
||||
// # two lines before, one line after
|
||||
//
|
||||
// c = 30
|
||||
// ````
|
||||
// This also includes nested class/function definitions, so we stop recursing
|
||||
// once we see a node with a trailing own line comment:
|
||||
// ```python
|
||||
// def f():
|
||||
// if True:
|
||||
//
|
||||
// def double(s):
|
||||
// return s + s
|
||||
//
|
||||
// # nested trailing own line comment
|
||||
// print("below function with trailing own line comment")
|
||||
// ```
|
||||
!comments.has_trailing_own_line(*last_child))
|
||||
.any(|last_child| {
|
||||
matches!(
|
||||
last_child,
|
||||
AnyNodeRef::StmtFunctionDef(_) | AnyNodeRef::StmtClassDef(_)
|
||||
)
|
||||
})
|
||||
trailing_function_or_class_def(Some(preceding), &comments).is_some()
|
||||
};
|
||||
|
||||
// Add empty lines before and after a function or class definition. If the preceding
|
||||
|
@ -248,7 +239,7 @@ impl FormatRule<Suite, PyFormatContext<'_>> for FormatSuite {
|
|||
SuiteKind::TopLevel => {
|
||||
write!(f, [empty_line(), empty_line()])?;
|
||||
}
|
||||
SuiteKind::Function | SuiteKind::Class | SuiteKind::Other => {
|
||||
SuiteKind::Function | SuiteKind::Class | SuiteKind::Other { .. } => {
|
||||
empty_line().fmt(f)?;
|
||||
}
|
||||
}
|
||||
|
@ -280,7 +271,7 @@ impl FormatRule<Suite, PyFormatContext<'_>> for FormatSuite {
|
|||
},
|
||||
}
|
||||
}
|
||||
SuiteKind::Function | SuiteKind::Class | SuiteKind::Other => {
|
||||
SuiteKind::Function | SuiteKind::Class | SuiteKind::Other { .. } => {
|
||||
empty_line().fmt(f)?;
|
||||
}
|
||||
}
|
||||
|
@ -319,7 +310,7 @@ impl FormatRule<Suite, PyFormatContext<'_>> for FormatSuite {
|
|||
write!(f, [empty_line(), empty_line()])?;
|
||||
}
|
||||
},
|
||||
SuiteKind::Function | SuiteKind::Class | SuiteKind::Other => {
|
||||
SuiteKind::Function | SuiteKind::Class | SuiteKind::Other { .. } => {
|
||||
empty_line().fmt(f)?;
|
||||
}
|
||||
},
|
||||
|
@ -413,10 +404,129 @@ impl FormatRule<Suite, PyFormatContext<'_>> for FormatSuite {
|
|||
empty_line_after_docstring = false;
|
||||
}
|
||||
|
||||
self.between_alternative_blocks_empty_line(statements, &comments, f)?;
|
||||
|
||||
Ok(())
|
||||
}
|
||||
}
|
||||
|
||||
impl FormatSuite {
|
||||
/// Add an empty line between a function or class and an alternative body.
|
||||
///
|
||||
/// We only insert an empty if we're between suites in a multi-suite statement. In the
|
||||
/// if-else-statement below, we insert an empty line after the `foo` in the if-block, but none
|
||||
/// after the else-block `foo`, since in the latter case the enclosing suite already adds
|
||||
/// empty lines.
|
||||
///
|
||||
/// ```python
|
||||
/// if sys.version_info >= (3, 10):
|
||||
/// def foo():
|
||||
/// return "new"
|
||||
/// else:
|
||||
/// def foo():
|
||||
/// return "old"
|
||||
/// class Bar:
|
||||
/// pass
|
||||
/// ```
|
||||
fn between_alternative_blocks_empty_line(
|
||||
&self,
|
||||
statements: &Suite,
|
||||
comments: &Comments,
|
||||
f: &mut PyFormatter,
|
||||
) -> FormatResult<()> {
|
||||
if self.kind.last_suite_in_statement() {
|
||||
// If we're at the end of the current statement, the outer suite will insert one or
|
||||
// two empty lines already.
|
||||
return Ok(());
|
||||
}
|
||||
|
||||
let Some(last_def_or_class) = trailing_function_or_class_def(statements.last(), comments)
|
||||
else {
|
||||
// An empty line is only inserted for function and class definitions.
|
||||
return Ok(());
|
||||
};
|
||||
|
||||
// Skip the last trailing own line comment of the suite, if any, otherwise we count
|
||||
// the lines wrongly by stopping at that comment.
|
||||
let node_with_last_trailing_comment = std::iter::successors(
|
||||
statements.last().map(AnyNodeRef::from),
|
||||
AnyNodeRef::last_child_in_body,
|
||||
)
|
||||
.find(|last_child| comments.has_trailing_own_line(*last_child));
|
||||
|
||||
let end_of_def_or_class = node_with_last_trailing_comment
|
||||
.and_then(|child| comments.trailing(child).last().map(Ranged::end))
|
||||
.unwrap_or(last_def_or_class.end());
|
||||
let existing_newlines =
|
||||
lines_after_ignoring_end_of_line_trivia(end_of_def_or_class, f.context().source());
|
||||
if existing_newlines < 2 {
|
||||
if f.context().is_preview() {
|
||||
empty_line().fmt(f)?;
|
||||
} else {
|
||||
if last_def_or_class.is_stmt_class_def() && f.options().source_type().is_stub() {
|
||||
empty_line().fmt(f)?;
|
||||
}
|
||||
}
|
||||
}
|
||||
Ok(())
|
||||
}
|
||||
}
|
||||
|
||||
/// Find nested class or function definitions that need an empty line after them.
|
||||
///
|
||||
/// ```python
|
||||
/// def f():
|
||||
/// if True:
|
||||
///
|
||||
/// def double(s):
|
||||
/// return s + s
|
||||
///
|
||||
/// print("below function")
|
||||
/// ```
|
||||
fn trailing_function_or_class_def<'a>(
|
||||
preceding: Option<&'a Stmt>,
|
||||
comments: &Comments,
|
||||
) -> Option<AnyNodeRef<'a>> {
|
||||
std::iter::successors(
|
||||
preceding.map(AnyNodeRef::from),
|
||||
AnyNodeRef::last_child_in_body,
|
||||
)
|
||||
.take_while(|last_child|
|
||||
// If there is a comment between preceding and following the empty lines were
|
||||
// inserted before the comment by preceding and there are no extra empty lines
|
||||
// after the comment.
|
||||
// ```python
|
||||
// class Test:
|
||||
// def a(self):
|
||||
// pass
|
||||
// # trailing comment
|
||||
//
|
||||
//
|
||||
// # two lines before, one line after
|
||||
//
|
||||
// c = 30
|
||||
// ````
|
||||
// This also includes nested class/function definitions, so we stop recursing
|
||||
// once we see a node with a trailing own line comment:
|
||||
// ```python
|
||||
// def f():
|
||||
// if True:
|
||||
//
|
||||
// def double(s):
|
||||
// return s + s
|
||||
//
|
||||
// # nested trailing own line comment
|
||||
// print("below function with trailing own line comment")
|
||||
// ```
|
||||
!comments.has_trailing_own_line(*last_child))
|
||||
.find(|last_child| {
|
||||
matches!(
|
||||
last_child,
|
||||
AnyNodeRef::StmtFunctionDef(_) | AnyNodeRef::StmtClassDef(_)
|
||||
)
|
||||
})
|
||||
}
|
||||
|
||||
/// Stub files have bespoke rules for empty lines.
|
||||
///
|
||||
/// These rules are ported from black (preview mode at time of writing) using the stubs test case:
|
||||
|
@ -447,7 +557,7 @@ fn stub_file_empty_lines(
|
|||
hard_line_break().fmt(f)
|
||||
}
|
||||
}
|
||||
SuiteKind::Class | SuiteKind::Other | SuiteKind::Function => {
|
||||
SuiteKind::Class | SuiteKind::Other { .. } | SuiteKind::Function => {
|
||||
if (empty_line_condition
|
||||
&& lines_after_ignoring_end_of_line_trivia(preceding.end(), source) > 1)
|
||||
|| require_empty_line
|
||||
|
@ -477,26 +587,14 @@ pub(crate) fn should_insert_blank_line_after_class_in_stub_file(
|
|||
return false;
|
||||
}
|
||||
|
||||
let Some(following) = following else {
|
||||
// We handle newlines at the end of a suite in `between_alternative_blocks_empty_line`.
|
||||
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.
|
||||
//
|
||||
|
@ -916,7 +1014,9 @@ def trailing_func():
|
|||
|
||||
#[test]
|
||||
fn nested_level() {
|
||||
let formatted = format_suite(SuiteKind::Other);
|
||||
let formatted = format_suite(SuiteKind::Other {
|
||||
last_suite_in_statement: true,
|
||||
});
|
||||
|
||||
assert_eq!(
|
||||
formatted,
|
||||
|
|
|
@ -505,5 +505,3 @@ def foo():
|
|||
def bar():
|
||||
pass
|
||||
```
|
||||
|
||||
|
||||
|
|
|
@ -220,5 +220,3 @@ else:
|
|||
with hmm_but_this_should_get_two_preceding_newlines():
|
||||
pass
|
||||
```
|
||||
|
||||
|
||||
|
|
|
@ -255,5 +255,3 @@ class Conditional:
|
|||
def l(self): ...
|
||||
def m(self): ...
|
||||
```
|
||||
|
||||
|
||||
|
|
|
@ -255,6 +255,68 @@ if True:
|
|||
print()
|
||||
|
||||
|
||||
if True:
|
||||
def a():
|
||||
return 1
|
||||
else:
|
||||
pass
|
||||
|
||||
if True:
|
||||
# fmt: off
|
||||
def a():
|
||||
return 1
|
||||
# fmt: on
|
||||
else:
|
||||
pass
|
||||
|
||||
match True:
|
||||
case 1:
|
||||
def a():
|
||||
return 1
|
||||
case 1:
|
||||
def a():
|
||||
return 1
|
||||
|
||||
try:
|
||||
def a():
|
||||
return 1
|
||||
except RuntimeError:
|
||||
def a():
|
||||
return 1
|
||||
|
||||
try:
|
||||
def a():
|
||||
return 1
|
||||
finally:
|
||||
def a():
|
||||
return 1
|
||||
|
||||
try:
|
||||
def a():
|
||||
return 1
|
||||
except RuntimeError:
|
||||
def a():
|
||||
return 1
|
||||
except ZeroDivisionError:
|
||||
def a():
|
||||
return 1
|
||||
else:
|
||||
def a():
|
||||
return 1
|
||||
finally:
|
||||
def a():
|
||||
return 1
|
||||
|
||||
if raw:
|
||||
def show_file(lines):
|
||||
for line in lines:
|
||||
pass
|
||||
# Trailing comment not on function or class
|
||||
|
||||
else:
|
||||
pass
|
||||
|
||||
|
||||
# NOTE: Please keep this the last block in this file. This tests that we don't insert
|
||||
# empty line(s) at the end of the file due to nested function
|
||||
if True:
|
||||
|
@ -558,6 +620,85 @@ if True:
|
|||
print()
|
||||
|
||||
|
||||
if True:
|
||||
|
||||
def a():
|
||||
return 1
|
||||
else:
|
||||
pass
|
||||
|
||||
if True:
|
||||
# fmt: off
|
||||
def a():
|
||||
return 1
|
||||
# fmt: on
|
||||
else:
|
||||
pass
|
||||
|
||||
match True:
|
||||
case 1:
|
||||
|
||||
def a():
|
||||
return 1
|
||||
case 1:
|
||||
|
||||
def a():
|
||||
return 1
|
||||
|
||||
|
||||
try:
|
||||
|
||||
def a():
|
||||
return 1
|
||||
except RuntimeError:
|
||||
|
||||
def a():
|
||||
return 1
|
||||
|
||||
|
||||
try:
|
||||
|
||||
def a():
|
||||
return 1
|
||||
finally:
|
||||
|
||||
def a():
|
||||
return 1
|
||||
|
||||
|
||||
try:
|
||||
|
||||
def a():
|
||||
return 1
|
||||
except RuntimeError:
|
||||
|
||||
def a():
|
||||
return 1
|
||||
except ZeroDivisionError:
|
||||
|
||||
def a():
|
||||
return 1
|
||||
else:
|
||||
|
||||
def a():
|
||||
return 1
|
||||
finally:
|
||||
|
||||
def a():
|
||||
return 1
|
||||
|
||||
|
||||
if raw:
|
||||
|
||||
def show_file(lines):
|
||||
for line in lines:
|
||||
pass
|
||||
# Trailing comment not on function or class
|
||||
|
||||
else:
|
||||
pass
|
||||
|
||||
|
||||
# NOTE: Please keep this the last block in this file. This tests that we don't insert
|
||||
# empty line(s) at the end of the file due to nested function
|
||||
if True:
|
||||
|
@ -593,4 +734,63 @@ def overload4(a: int): ...
|
|||
```
|
||||
|
||||
|
||||
|
||||
## Preview changes
|
||||
```diff
|
||||
--- Stable
|
||||
+++ Preview
|
||||
@@ -277,6 +277,7 @@
|
||||
|
||||
def a():
|
||||
return 1
|
||||
+
|
||||
else:
|
||||
pass
|
||||
|
||||
@@ -293,6 +294,7 @@
|
||||
|
||||
def a():
|
||||
return 1
|
||||
+
|
||||
case 1:
|
||||
|
||||
def a():
|
||||
@@ -303,6 +305,7 @@
|
||||
|
||||
def a():
|
||||
return 1
|
||||
+
|
||||
except RuntimeError:
|
||||
|
||||
def a():
|
||||
@@ -313,6 +316,7 @@
|
||||
|
||||
def a():
|
||||
return 1
|
||||
+
|
||||
finally:
|
||||
|
||||
def a():
|
||||
@@ -323,18 +327,22 @@
|
||||
|
||||
def a():
|
||||
return 1
|
||||
+
|
||||
except RuntimeError:
|
||||
|
||||
def a():
|
||||
return 1
|
||||
+
|
||||
except ZeroDivisionError:
|
||||
|
||||
def a():
|
||||
return 1
|
||||
+
|
||||
else:
|
||||
|
||||
def a():
|
||||
return 1
|
||||
+
|
||||
finally:
|
||||
|
||||
def a():
|
||||
```
|
||||
|
|
|
@ -160,11 +160,17 @@ def f():
|
|||
pass
|
||||
|
||||
|
||||
if True:
|
||||
def a():
|
||||
return 1
|
||||
else:
|
||||
pass
|
||||
|
||||
|
||||
# comment
|
||||
|
||||
x = 1
|
||||
|
||||
```
|
||||
|
||||
## Output
|
||||
|
@ -302,10 +308,28 @@ x = 1
|
|||
def f():
|
||||
pass
|
||||
|
||||
if True:
|
||||
def a():
|
||||
return 1
|
||||
else:
|
||||
pass
|
||||
|
||||
# comment
|
||||
|
||||
x = 1
|
||||
```
|
||||
|
||||
|
||||
|
||||
## Preview changes
|
||||
```diff
|
||||
--- Stable
|
||||
+++ Preview
|
||||
@@ -134,6 +134,7 @@
|
||||
if True:
|
||||
def a():
|
||||
return 1
|
||||
+
|
||||
else:
|
||||
pass
|
||||
|
||||
```
|
||||
|
|
|
@ -1042,3 +1042,26 @@ def func[T](
|
|||
lotsoflongargs5: T,
|
||||
) -> T: ...
|
||||
```
|
||||
|
||||
|
||||
## Preview changes
|
||||
```diff
|
||||
--- Stable
|
||||
+++ Preview
|
||||
@@ -161,6 +161,7 @@
|
||||
|
||||
def f1():
|
||||
pass # a
|
||||
+
|
||||
else:
|
||||
pass
|
||||
|
||||
@@ -170,6 +171,7 @@
|
||||
def f2():
|
||||
pass
|
||||
# a
|
||||
+
|
||||
else:
|
||||
pass
|
||||
|
||||
```
|
||||
|
|
|
@ -609,4 +609,22 @@ if parent_body:
|
|||
```
|
||||
|
||||
|
||||
|
||||
## Preview changes
|
||||
```diff
|
||||
--- Stable
|
||||
+++ Preview
|
||||
@@ -93,11 +93,13 @@
|
||||
def f():
|
||||
pass
|
||||
# 1
|
||||
+
|
||||
elif True:
|
||||
|
||||
def f():
|
||||
pass
|
||||
# 2
|
||||
+
|
||||
else:
|
||||
|
||||
def f():
|
||||
```
|
||||
|
|
|
@ -366,4 +366,28 @@ finally:
|
|||
```
|
||||
|
||||
|
||||
|
||||
## Preview changes
|
||||
```diff
|
||||
--- Stable
|
||||
+++ Preview
|
||||
@@ -117,16 +117,19 @@
|
||||
def f():
|
||||
pass
|
||||
# a
|
||||
+
|
||||
except:
|
||||
|
||||
def f():
|
||||
pass
|
||||
# b
|
||||
+
|
||||
else:
|
||||
|
||||
def f():
|
||||
pass
|
||||
# c
|
||||
+
|
||||
finally:
|
||||
|
||||
def f():
|
||||
```
|
||||
|
|
|
@ -414,6 +414,3 @@ class Eof:
|
|||
class Nested:
|
||||
pass
|
||||
```
|
||||
|
||||
|
||||
|
||||
|
|
|
@ -311,4 +311,24 @@ class ComplexStatements:
|
|||
```
|
||||
|
||||
|
||||
|
||||
## Preview changes
|
||||
```diff
|
||||
--- Stable
|
||||
+++ Preview
|
||||
@@ -132,6 +132,7 @@
|
||||
if sys.version_info >= (3, 12):
|
||||
@classmethod
|
||||
def fromtimestamp(cls, timestamp: float, tz: float | None = ...) -> Self: ...
|
||||
+
|
||||
else:
|
||||
@classmethod
|
||||
def fromtimestamp(cls, __timestamp: float, tz: float | None = ...) -> Self: ...
|
||||
@@ -141,6 +142,7 @@
|
||||
if sys.version_info >= (3, 8):
|
||||
@classmethod
|
||||
def now(cls, tz: float | None = None) -> Self: ...
|
||||
+
|
||||
else:
|
||||
@classmethod
|
||||
def now(cls, tz: None = None) -> Self: ...
|
||||
```
|
||||
|
|
Loading…
Add table
Add a link
Reference in a new issue