mirror of
https://github.com/astral-sh/ruff.git
synced 2025-07-24 05:25:17 +00:00
Implement Black's rules around newlines before and after class docstrings (#6209)
## Summary Black allows up to one blank line _before_ a class docstring, and enforces one blank line _after_ a class docstring. This PR implements that handling. The cases in `crates/ruff_python_formatter/resources/test/fixtures/ruff/statement/class_definition.py` match Black identically.
This commit is contained in:
parent
5e41f2fc7d
commit
a82eb9544c
4 changed files with 224 additions and 503 deletions
|
@ -1,6 +1,6 @@
|
|||
use ruff_formatter::{write, FormatOwnedWithRule, FormatRefWithRule, FormatRuleWithOptions};
|
||||
use ruff_python_ast::helpers::is_compound_statement;
|
||||
use ruff_python_ast::{Ranged, Stmt, Suite};
|
||||
use ruff_python_ast::{self as ast, Constant, Expr, Ranged, Stmt, Suite};
|
||||
use ruff_python_trivia::{lines_after, lines_before, skip_trailing_trivia};
|
||||
|
||||
use crate::context::{NodeLevel, WithNodeLevel};
|
||||
|
@ -54,24 +54,64 @@ impl FormatRule<Suite, PyFormatContext<'_>> for FormatSuite {
|
|||
|
||||
let mut f = WithNodeLevel::new(node_level, f);
|
||||
|
||||
if matches!(self.kind, SuiteKind::Other)
|
||||
&& is_class_or_function_definition(first)
|
||||
&& !comments.has_leading_comments(first)
|
||||
{
|
||||
// Add an empty line for any nested functions or classes defined within non-function
|
||||
// or class compound statements, e.g., this is stable formatting:
|
||||
// ```python
|
||||
// if True:
|
||||
//
|
||||
// def test():
|
||||
// ...
|
||||
// ```
|
||||
write!(f, [empty_line()])?;
|
||||
}
|
||||
|
||||
write!(f, [first.format()])?;
|
||||
|
||||
// Format the first statement in the body, which often has special formatting rules.
|
||||
let mut last = first;
|
||||
match self.kind {
|
||||
SuiteKind::Other => {
|
||||
if is_class_or_function_definition(first) && !comments.has_leading_comments(first) {
|
||||
// Add an empty line for any nested functions or classes defined within
|
||||
// non-function or class compound statements, e.g., this is stable formatting:
|
||||
// ```python
|
||||
// if True:
|
||||
//
|
||||
// def test():
|
||||
// ...
|
||||
// ```
|
||||
write!(f, [empty_line()])?;
|
||||
}
|
||||
write!(f, [first.format()])?;
|
||||
}
|
||||
SuiteKind::Class if is_docstring(first) => {
|
||||
if !comments.has_leading_comments(first) && lines_before(first.start(), source) > 1
|
||||
{
|
||||
// Allow up to one empty line before a class docstring, e.g., this is
|
||||
// stable formatting:
|
||||
// ```python
|
||||
// class Test:
|
||||
//
|
||||
// """Docstring"""
|
||||
// ```
|
||||
write!(f, [empty_line()])?;
|
||||
}
|
||||
write!(f, [first.format()])?;
|
||||
|
||||
// Enforce an empty line after a class docstring, e.g., these are both stable
|
||||
// formatting:
|
||||
// ```python
|
||||
// class Test:
|
||||
// """Docstring"""
|
||||
//
|
||||
// ...
|
||||
//
|
||||
//
|
||||
// class Test:
|
||||
//
|
||||
// """Docstring"""
|
||||
//
|
||||
// ...
|
||||
// ```
|
||||
if let Some(second) = iter.next() {
|
||||
// Format the subsequent statement immediately. This rule takes precedence
|
||||
// over the rules in the loop below (and most of them won't apply anyway,
|
||||
// e.g., we know the first statement isn't an import).
|
||||
write!(f, [empty_line(), second.format()])?;
|
||||
last = second;
|
||||
}
|
||||
}
|
||||
_ => {
|
||||
write!(f, [first.format()])?;
|
||||
}
|
||||
}
|
||||
|
||||
for statement in iter {
|
||||
if is_class_or_function_definition(last) || is_class_or_function_definition(statement) {
|
||||
|
@ -182,6 +222,20 @@ const fn is_import_definition(stmt: &Stmt) -> bool {
|
|||
matches!(stmt, Stmt::Import(_) | Stmt::ImportFrom(_))
|
||||
}
|
||||
|
||||
fn is_docstring(stmt: &Stmt) -> bool {
|
||||
let Stmt::Expr(ast::StmtExpr { value, .. }) = stmt else {
|
||||
return false;
|
||||
};
|
||||
|
||||
matches!(
|
||||
value.as_ref(),
|
||||
Expr::Constant(ast::ExprConstant {
|
||||
value: Constant::Str(..),
|
||||
..
|
||||
})
|
||||
)
|
||||
}
|
||||
|
||||
impl FormatRuleWithOptions<Suite, PyFormatContext<'_>> for FormatSuite {
|
||||
type Options = SuiteKind;
|
||||
|
||||
|
|
Loading…
Add table
Add a link
Reference in a new issue