Skip to content

Commit

Permalink
Insert empty line between suite and alternative branch after def/class
Browse files Browse the repository at this point in the history
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
  • Loading branch information
konstin committed Jul 12, 2024
1 parent 5b21922 commit ba1f552
Show file tree
Hide file tree
Showing 25 changed files with 476 additions and 387 deletions.
Original file line number Diff line number Diff line change
Expand Up @@ -249,6 +249,51 @@ def y():
print()


if True:
def a():
return 1
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


# 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:
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -154,8 +154,14 @@ def f():
pass


if True:
def a():
return 1
else:
pass


# comment

x = 1

3 changes: 2 additions & 1 deletion crates/ruff_python_formatter/src/other/elif_else_clause.rs
Original file line number Diff line number Diff line change
Expand Up @@ -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
Expand All @@ -11,6 +12,6 @@ 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::default())
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -6,6 +6,7 @@ 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 {
Expand Down Expand Up @@ -36,56 +37,65 @@ impl FormatNodeRule<ExceptHandlerExceptHandler> for FormatExceptHandlerExceptHan
item: &ExceptHandlerExceptHandler,
f: &mut PyFormatter,
) -> FormatResult<()> {
let ExceptHandlerExceptHandler {
range: _,
type_,
name,
body,
} = item;
format_except_handler(item, self.except_handler_kind, true, f)
}
}

let comments_info = f.context().comments().clone();
let dangling_comments = comments_info.dangling(item);
pub(crate) fn format_except_handler(
item: &ExceptHandlerExceptHandler,
except_handler_kind: ExceptHandlerKind,
last_suite_in_statement: bool,
f: &mut PyFormatter,
) -> FormatResult<()> {
let ExceptHandlerExceptHandler {
range: _,
type_,
name,
body,
} = item;

write!(
f,
[
clause_header(
ClauseHeader::ExceptHandler(item),
dangling_comments,
&format_with(|f| {
let comments_info = f.context().comments().clone();
let dangling_comments = comments_info.dangling(item);

write!(
f,
[
clause_header(
ClauseHeader::ExceptHandler(item),
dangling_comments,
&format_with(|f| {
write!(
f,
[
token("except"),
match except_handler_kind {
ExceptHandlerKind::Regular => None,
ExceptHandlerKind::Starred => Some(token("*")),
}
]
)?;

if let Some(type_) = type_ {
write!(
f,
[
token("except"),
match self.except_handler_kind {
ExceptHandlerKind::Regular => None,
ExceptHandlerKind::Starred => Some(token("*")),
}
space(),
maybe_parenthesize_expression(type_, item, Parenthesize::IfBreaks)
]
)?;

if let Some(type_) = type_ {
write!(
f,
[
space(),
maybe_parenthesize_expression(
type_,
item,
Parenthesize::IfBreaks
)
]
)?;
if let Some(name) = name {
write!(f, [space(), token("as"), space(), name.format()])?;
}
if let Some(name) = name {
write!(f, [space(), token("as"), space(), name.format()])?;
}
}

Ok(())
}),
),
clause_body(body, dangling_comments),
]
)
}
Ok(())
}),
),
clause_body(
body,
SuiteKind::other(last_suite_in_statement),
dangling_comments
),
]
)
}
22 changes: 19 additions & 3 deletions crates/ruff_python_formatter/src/other/match_case.rs
Original file line number Diff line number Diff line change
@@ -1,14 +1,26 @@
use ruff_formatter::write;
use ruff_formatter::{write, FormatRuleWithOptions};
use ruff_python_ast::AstNode;
use ruff_python_ast::MatchCase;

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<()> {
Expand Down Expand Up @@ -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
),
]
)
}
Expand Down
11 changes: 2 additions & 9 deletions crates/ruff_python_formatter/src/statement/clause.rs
Original file line number Diff line number Diff line change
Expand Up @@ -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,
}
}
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -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),
]
)?;

Expand Down
9 changes: 7 additions & 2 deletions crates/ruff_python_formatter/src/statement/stmt_for.rs
Original file line number Diff line number Diff line change
Expand Up @@ -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);
Expand Down Expand Up @@ -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
),
]
)?;

Expand All @@ -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),
]
)?;
}
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -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),
]
)?;

Expand Down
20 changes: 15 additions & 5 deletions crates/ruff_python_formatter/src/statement/stmt_if.rs
Original file line number Diff line number Diff line change
@@ -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;
Expand Down Expand Up @@ -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();
}

Expand All @@ -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: _,
Expand Down Expand Up @@ -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()
Expand Down
5 changes: 3 additions & 2 deletions crates/ruff_python_formatter/src/statement/stmt_match.rs
Original file line number Diff line number Diff line change
Expand Up @@ -36,7 +36,7 @@ impl FormatNodeRule<StmtMatch> for FormatStmtMatch {
)
.fmt(f)?;

let mut cases_iter = cases.iter();
let mut cases_iter = cases.iter().peekable();
let Some(first) = cases_iter.next() else {
return Ok(());
};
Expand All @@ -48,14 +48,15 @@ impl FormatNodeRule<StmtMatch> for FormatStmtMatch {
let mut last_case = first;

for case in cases_iter {
let last_suite_in_statement = case == cases.last().unwrap();
write!(
f,
[block_indent(&format_args!(
leading_alternate_branch_comments(
comments.leading(case),
last_case.body.last(),
),
case.format()
case.format().with_options(last_suite_in_statement)
))]
)?;
last_case = case;
Expand Down
Loading

0 comments on commit ba1f552

Please sign in to comment.