mirror of https://github.com/astral-sh/ruff
Respect `fmt: skip` for compound statements on single line (#20633)
Closes #11216 Essentially the approach is to implement `Format` for a new struct `FormatClause` which is just a clause header _and_ its body. We then have the information we need to see whether there is a skip suppression comment on the last child in the body and it all fits on one line.
This commit is contained in:
parent
8dad289062
commit
62343a101a
149
crates/ruff_python_formatter/resources/test/fixtures/ruff/fmt_skip/compound_one_liners.py
vendored
Normal file
149
crates/ruff_python_formatter/resources/test/fixtures/ruff/fmt_skip/compound_one_liners.py
vendored
Normal file
|
|
@ -0,0 +1,149 @@
|
||||||
|
# Test cases for fmt: skip on compound statements that fit on one line
|
||||||
|
|
||||||
|
# Basic single-line compound statements
|
||||||
|
def simple_func(): return "hello" # fmt: skip
|
||||||
|
if True: print("condition met") # fmt: skip
|
||||||
|
for i in range(5): print(i) # fmt: skip
|
||||||
|
while x < 10: x += 1 # fmt: skip
|
||||||
|
|
||||||
|
# With expressions that would normally trigger formatting
|
||||||
|
def long_params(a, b, c, d, e, f, g): return a + b + c + d + e + f + g # fmt: skip
|
||||||
|
if some_very_long_condition_that_might_wrap: do_something_else_that_is_long() # fmt: skip
|
||||||
|
|
||||||
|
# Nested compound statements (outer should be preserved)
|
||||||
|
if True:
|
||||||
|
for i in range(10): print(i) # fmt: skip
|
||||||
|
|
||||||
|
# Multiple statements in body (should not apply - multiline)
|
||||||
|
if True:
|
||||||
|
x = 1
|
||||||
|
y = 2 # fmt: skip
|
||||||
|
|
||||||
|
# With decorators - decorated function on one line
|
||||||
|
@overload
|
||||||
|
def decorated_func(x: int) -> str: return str(x) # fmt: skip
|
||||||
|
|
||||||
|
@property
|
||||||
|
def prop_method(self): return self._value # fmt: skip
|
||||||
|
|
||||||
|
# Class definitions on one line
|
||||||
|
class SimpleClass: pass # fmt: skip
|
||||||
|
class GenericClass(Generic[T]): pass # fmt: skip
|
||||||
|
|
||||||
|
# Try/except blocks
|
||||||
|
try: risky_operation() # fmt: skip
|
||||||
|
except ValueError: handle_error() # fmt: skip
|
||||||
|
except: handle_any_error() # fmt: skip
|
||||||
|
else: success_case() # fmt: skip
|
||||||
|
finally: cleanup() # fmt: skip
|
||||||
|
|
||||||
|
# Match statements (Python 3.10+)
|
||||||
|
match value:
|
||||||
|
case 1: print("one") # fmt: skip
|
||||||
|
case _: print("other") # fmt: skip
|
||||||
|
|
||||||
|
# With statements
|
||||||
|
with open("file.txt") as f: content = f.read() # fmt: skip
|
||||||
|
with context_manager() as cm: result = cm.process() # fmt: skip
|
||||||
|
|
||||||
|
# Async variants
|
||||||
|
async def async_func(): return await some_call() # fmt: skip
|
||||||
|
async for item in async_iterator(): await process(item) # fmt: skip
|
||||||
|
async with async_context() as ctx: await ctx.work() # fmt: skip
|
||||||
|
|
||||||
|
# Complex expressions that would normally format
|
||||||
|
def complex_expr(): return [x for x in range(100) if x % 2 == 0 and x > 50] # fmt: skip
|
||||||
|
if condition_a and condition_b or (condition_c and not condition_d): execute_complex_logic() # fmt: skip
|
||||||
|
|
||||||
|
# Edge case: comment positioning
|
||||||
|
def func_with_comment(): # some comment
|
||||||
|
return "value" # fmt: skip
|
||||||
|
|
||||||
|
# Edge case: multiple fmt: skip (only last one should matter)
|
||||||
|
def multiple_skip(): return "test" # fmt: skip # fmt: skip
|
||||||
|
|
||||||
|
# Should NOT be affected (already multiline)
|
||||||
|
def multiline_func():
|
||||||
|
return "this should format normally"
|
||||||
|
|
||||||
|
if long_condition_that_spans \
|
||||||
|
and continues_on_next_line:
|
||||||
|
print("multiline condition")
|
||||||
|
|
||||||
|
# Mix of skipped and non-skipped
|
||||||
|
for i in range(10): print(f"item {i}") # fmt: skip
|
||||||
|
for j in range(5):
|
||||||
|
print(f"formatted item {j}")
|
||||||
|
|
||||||
|
# With trailing comma that would normally be removed
|
||||||
|
def trailing_comma_func(a, b, c,): return a + b + c # fmt: skip
|
||||||
|
|
||||||
|
# Dictionary/list comprehensions
|
||||||
|
def dict_comp(): return {k: v for k, v in items.items() if v is not None} # fmt: skip
|
||||||
|
def list_comp(): return [x * 2 for x in numbers if x > threshold_value] # fmt: skip
|
||||||
|
|
||||||
|
# Lambda in one-liner
|
||||||
|
def with_lambda(): return lambda x, y, z: x + y + z if all([x, y, z]) else None # fmt: skip
|
||||||
|
|
||||||
|
# String formatting that would normally be reformatted
|
||||||
|
def format_string(): return f"Hello {name}, you have {count} items in your cart totaling ${total:.2f}" # fmt: skip
|
||||||
|
|
||||||
|
# loop else clauses
|
||||||
|
for i in range(2): print(i) # fmt: skip
|
||||||
|
else: print("this") # fmt: skip
|
||||||
|
|
||||||
|
|
||||||
|
while foo(): print(i) # fmt: skip
|
||||||
|
else: print("this") # fmt: skip
|
||||||
|
|
||||||
|
# again but only the first skip
|
||||||
|
for i in range(2): print(i) # fmt: skip
|
||||||
|
else: print("this")
|
||||||
|
|
||||||
|
|
||||||
|
while foo(): print(i) # fmt: skip
|
||||||
|
else: print("this")
|
||||||
|
|
||||||
|
# again but only the second skip
|
||||||
|
for i in range(2): print(i)
|
||||||
|
else: print("this") # fmt: skip
|
||||||
|
|
||||||
|
|
||||||
|
while foo(): print(i)
|
||||||
|
else: print("this") # fmt: skip
|
||||||
|
|
||||||
|
# multiple statements in body
|
||||||
|
if True: print("this"); print("that") # fmt: skip
|
||||||
|
|
||||||
|
# Examples with more comments
|
||||||
|
|
||||||
|
try: risky_operation() # fmt: skip
|
||||||
|
# leading 1
|
||||||
|
except ValueError: handle_error() # fmt: skip
|
||||||
|
# leading 2
|
||||||
|
except: handle_any_error() # fmt: skip
|
||||||
|
# leading 3
|
||||||
|
else: success_case() # fmt: skip
|
||||||
|
# leading 4
|
||||||
|
finally: cleanup() # fmt: skip
|
||||||
|
# trailing
|
||||||
|
|
||||||
|
# multi-line before colon (should remain as is)
|
||||||
|
if (
|
||||||
|
long_condition
|
||||||
|
): a + b # fmt: skip
|
||||||
|
|
||||||
|
# over-indented comment example
|
||||||
|
# See https://github.com/astral-sh/ruff/pull/20633#issuecomment-3453288910
|
||||||
|
# and https://github.com/astral-sh/ruff/pull/21185
|
||||||
|
|
||||||
|
for x in it: foo()
|
||||||
|
# comment
|
||||||
|
else: bar() # fmt: skip
|
||||||
|
|
||||||
|
|
||||||
|
if this(
|
||||||
|
'is a long',
|
||||||
|
# commented
|
||||||
|
'condition'
|
||||||
|
): with_a_skip # fmt: skip
|
||||||
|
|
@ -7,7 +7,7 @@ use crate::expression::maybe_parenthesize_expression;
|
||||||
use crate::expression::parentheses::Parenthesize;
|
use crate::expression::parentheses::Parenthesize;
|
||||||
use crate::prelude::*;
|
use crate::prelude::*;
|
||||||
use crate::preview::is_remove_parens_around_except_types_enabled;
|
use crate::preview::is_remove_parens_around_except_types_enabled;
|
||||||
use crate::statement::clause::{ClauseHeader, clause_body, clause_header};
|
use crate::statement::clause::{ClauseHeader, clause};
|
||||||
use crate::statement::suite::SuiteKind;
|
use crate::statement::suite::SuiteKind;
|
||||||
|
|
||||||
#[derive(Copy, Clone, Default)]
|
#[derive(Copy, Clone, Default)]
|
||||||
|
|
@ -55,77 +55,68 @@ impl FormatNodeRule<ExceptHandlerExceptHandler> for FormatExceptHandlerExceptHan
|
||||||
|
|
||||||
write!(
|
write!(
|
||||||
f,
|
f,
|
||||||
[
|
[clause(
|
||||||
clause_header(
|
ClauseHeader::ExceptHandler(item),
|
||||||
ClauseHeader::ExceptHandler(item),
|
&format_with(|f: &mut PyFormatter| {
|
||||||
dangling_comments,
|
write!(
|
||||||
&format_with(|f: &mut PyFormatter| {
|
f,
|
||||||
write!(
|
[
|
||||||
f,
|
token("except"),
|
||||||
[
|
match except_handler_kind {
|
||||||
token("except"),
|
ExceptHandlerKind::Regular => None,
|
||||||
match except_handler_kind {
|
ExceptHandlerKind::Starred => Some(token("*")),
|
||||||
ExceptHandlerKind::Regular => None,
|
}
|
||||||
ExceptHandlerKind::Starred => Some(token("*")),
|
]
|
||||||
}
|
)?;
|
||||||
]
|
|
||||||
)?;
|
|
||||||
|
|
||||||
match type_.as_deref() {
|
match type_.as_deref() {
|
||||||
// For tuples of exception types without an `as` name and on 3.14+, the
|
// For tuples of exception types without an `as` name and on 3.14+, the
|
||||||
// parentheses are optional.
|
// parentheses are optional.
|
||||||
//
|
//
|
||||||
// ```py
|
// ```py
|
||||||
// try:
|
// try:
|
||||||
// ...
|
// ...
|
||||||
// except BaseException, Exception: # Ok
|
// except BaseException, Exception: # Ok
|
||||||
// ...
|
// ...
|
||||||
// ```
|
// ```
|
||||||
Some(Expr::Tuple(tuple))
|
Some(Expr::Tuple(tuple))
|
||||||
if f.options().target_version() >= PythonVersion::PY314
|
if f.options().target_version() >= PythonVersion::PY314
|
||||||
&& is_remove_parens_around_except_types_enabled(
|
&& is_remove_parens_around_except_types_enabled(f.context())
|
||||||
f.context(),
|
&& name.is_none() =>
|
||||||
)
|
{
|
||||||
&& name.is_none() =>
|
write!(
|
||||||
{
|
f,
|
||||||
write!(
|
[
|
||||||
f,
|
space(),
|
||||||
[
|
tuple.format().with_options(TupleParentheses::NeverPreserve)
|
||||||
space(),
|
]
|
||||||
tuple
|
)?;
|
||||||
.format()
|
|
||||||
.with_options(TupleParentheses::NeverPreserve)
|
|
||||||
]
|
|
||||||
)?;
|
|
||||||
}
|
|
||||||
Some(type_) => {
|
|
||||||
write!(
|
|
||||||
f,
|
|
||||||
[
|
|
||||||
space(),
|
|
||||||
maybe_parenthesize_expression(
|
|
||||||
type_,
|
|
||||||
item,
|
|
||||||
Parenthesize::IfBreaks
|
|
||||||
)
|
|
||||||
]
|
|
||||||
)?;
|
|
||||||
if let Some(name) = name {
|
|
||||||
write!(f, [space(), token("as"), space(), name.format()])?;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
_ => {}
|
|
||||||
}
|
}
|
||||||
|
Some(type_) => {
|
||||||
|
write!(
|
||||||
|
f,
|
||||||
|
[
|
||||||
|
space(),
|
||||||
|
maybe_parenthesize_expression(
|
||||||
|
type_,
|
||||||
|
item,
|
||||||
|
Parenthesize::IfBreaks
|
||||||
|
)
|
||||||
|
]
|
||||||
|
)?;
|
||||||
|
if let Some(name) = name {
|
||||||
|
write!(f, [space(), token("as"), space(), name.format()])?;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
_ => {}
|
||||||
|
}
|
||||||
|
|
||||||
Ok(())
|
Ok(())
|
||||||
}),
|
}),
|
||||||
),
|
dangling_comments,
|
||||||
clause_body(
|
body,
|
||||||
body,
|
SuiteKind::other(self.last_suite_in_statement),
|
||||||
SuiteKind::other(self.last_suite_in_statement),
|
)]
|
||||||
dangling_comments
|
|
||||||
),
|
|
||||||
]
|
|
||||||
)
|
)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
|
||||||
|
|
@ -5,7 +5,7 @@ use crate::expression::maybe_parenthesize_expression;
|
||||||
use crate::expression::parentheses::Parenthesize;
|
use crate::expression::parentheses::Parenthesize;
|
||||||
use crate::pattern::maybe_parenthesize_pattern;
|
use crate::pattern::maybe_parenthesize_pattern;
|
||||||
use crate::prelude::*;
|
use crate::prelude::*;
|
||||||
use crate::statement::clause::{ClauseHeader, clause_body, clause_header};
|
use crate::statement::clause::{ClauseHeader, clause};
|
||||||
use crate::statement::suite::SuiteKind;
|
use crate::statement::suite::SuiteKind;
|
||||||
|
|
||||||
#[derive(Default)]
|
#[derive(Default)]
|
||||||
|
|
@ -46,23 +46,18 @@ impl FormatNodeRule<MatchCase> for FormatMatchCase {
|
||||||
|
|
||||||
write!(
|
write!(
|
||||||
f,
|
f,
|
||||||
[
|
[clause(
|
||||||
clause_header(
|
ClauseHeader::MatchCase(item),
|
||||||
ClauseHeader::MatchCase(item),
|
&format_args![
|
||||||
dangling_item_comments,
|
token("case"),
|
||||||
&format_args![
|
space(),
|
||||||
token("case"),
|
maybe_parenthesize_pattern(pattern, item),
|
||||||
space(),
|
format_guard
|
||||||
maybe_parenthesize_pattern(pattern, item),
|
],
|
||||||
format_guard
|
dangling_item_comments,
|
||||||
],
|
body,
|
||||||
),
|
SuiteKind::other(self.last_suite_in_statement),
|
||||||
clause_body(
|
)]
|
||||||
body,
|
|
||||||
SuiteKind::other(self.last_suite_in_statement),
|
|
||||||
dangling_item_comments
|
|
||||||
),
|
|
||||||
]
|
|
||||||
)
|
)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
|
||||||
|
|
@ -5,11 +5,12 @@ use ruff_python_ast::{
|
||||||
StmtIf, StmtMatch, StmtTry, StmtWhile, StmtWith, Suite,
|
StmtIf, StmtMatch, StmtTry, StmtWhile, StmtWith, Suite,
|
||||||
};
|
};
|
||||||
use ruff_python_trivia::{SimpleToken, SimpleTokenKind, SimpleTokenizer};
|
use ruff_python_trivia::{SimpleToken, SimpleTokenKind, SimpleTokenizer};
|
||||||
|
use ruff_source_file::LineRanges;
|
||||||
use ruff_text_size::{Ranged, TextRange, TextSize};
|
use ruff_text_size::{Ranged, TextRange, TextSize};
|
||||||
|
|
||||||
use crate::comments::{SourceComment, leading_alternate_branch_comments, trailing_comments};
|
use crate::comments::{SourceComment, leading_alternate_branch_comments, trailing_comments};
|
||||||
use crate::statement::suite::{SuiteKind, as_only_an_ellipsis};
|
use crate::statement::suite::{SuiteKind, as_only_an_ellipsis};
|
||||||
use crate::verbatim::write_suppressed_clause_header;
|
use crate::verbatim::{verbatim_text, write_suppressed_clause_header};
|
||||||
use crate::{has_skip_comment, prelude::*};
|
use crate::{has_skip_comment, prelude::*};
|
||||||
|
|
||||||
/// The header of a compound statement clause.
|
/// The header of a compound statement clause.
|
||||||
|
|
@ -36,7 +37,41 @@ pub(crate) enum ClauseHeader<'a> {
|
||||||
OrElse(ElseClause<'a>),
|
OrElse(ElseClause<'a>),
|
||||||
}
|
}
|
||||||
|
|
||||||
impl ClauseHeader<'_> {
|
impl<'a> ClauseHeader<'a> {
|
||||||
|
/// Returns the last child in the clause body immediately following this clause header.
|
||||||
|
///
|
||||||
|
/// For most clauses, this is the last statement in
|
||||||
|
/// the primary body. For clauses like `try`, it specifically returns the last child
|
||||||
|
/// in the `try` body, not the `except`/`else`/`finally` clauses.
|
||||||
|
///
|
||||||
|
/// This is similar to [`ruff_python_ast::AnyNodeRef::last_child_in_body`]
|
||||||
|
/// but restricted to the clause.
|
||||||
|
pub(crate) fn last_child_in_clause(self) -> Option<AnyNodeRef<'a>> {
|
||||||
|
match self {
|
||||||
|
ClauseHeader::Class(StmtClassDef { body, .. })
|
||||||
|
| ClauseHeader::Function(StmtFunctionDef { body, .. })
|
||||||
|
| ClauseHeader::If(StmtIf { body, .. })
|
||||||
|
| ClauseHeader::ElifElse(ElifElseClause { body, .. })
|
||||||
|
| ClauseHeader::Try(StmtTry { body, .. })
|
||||||
|
| ClauseHeader::MatchCase(MatchCase { body, .. })
|
||||||
|
| ClauseHeader::For(StmtFor { body, .. })
|
||||||
|
| ClauseHeader::While(StmtWhile { body, .. })
|
||||||
|
| ClauseHeader::With(StmtWith { body, .. })
|
||||||
|
| ClauseHeader::ExceptHandler(ExceptHandlerExceptHandler { body, .. })
|
||||||
|
| ClauseHeader::OrElse(
|
||||||
|
ElseClause::Try(StmtTry { orelse: body, .. })
|
||||||
|
| ElseClause::For(StmtFor { orelse: body, .. })
|
||||||
|
| ElseClause::While(StmtWhile { orelse: body, .. }),
|
||||||
|
)
|
||||||
|
| ClauseHeader::TryFinally(StmtTry {
|
||||||
|
finalbody: body, ..
|
||||||
|
}) => body.last().map(AnyNodeRef::from),
|
||||||
|
ClauseHeader::Match(StmtMatch { cases, .. }) => cases
|
||||||
|
.last()
|
||||||
|
.and_then(|case| case.body.last().map(AnyNodeRef::from)),
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
/// The range from the clause keyword up to and including the final colon.
|
/// The range from the clause keyword up to and including the final colon.
|
||||||
pub(crate) fn range(self, source: &str) -> FormatResult<TextRange> {
|
pub(crate) fn range(self, source: &str) -> FormatResult<TextRange> {
|
||||||
let keyword_range = self.first_keyword_range(source)?;
|
let keyword_range = self.first_keyword_range(source)?;
|
||||||
|
|
@ -338,6 +373,28 @@ impl ClauseHeader<'_> {
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
impl<'a> From<ClauseHeader<'a>> for AnyNodeRef<'a> {
|
||||||
|
fn from(value: ClauseHeader<'a>) -> Self {
|
||||||
|
match value {
|
||||||
|
ClauseHeader::Class(stmt_class_def) => stmt_class_def.into(),
|
||||||
|
ClauseHeader::Function(stmt_function_def) => stmt_function_def.into(),
|
||||||
|
ClauseHeader::If(stmt_if) => stmt_if.into(),
|
||||||
|
ClauseHeader::ElifElse(elif_else_clause) => elif_else_clause.into(),
|
||||||
|
ClauseHeader::Try(stmt_try) => stmt_try.into(),
|
||||||
|
ClauseHeader::ExceptHandler(except_handler_except_handler) => {
|
||||||
|
except_handler_except_handler.into()
|
||||||
|
}
|
||||||
|
ClauseHeader::TryFinally(stmt_try) => stmt_try.into(),
|
||||||
|
ClauseHeader::Match(stmt_match) => stmt_match.into(),
|
||||||
|
ClauseHeader::MatchCase(match_case) => match_case.into(),
|
||||||
|
ClauseHeader::For(stmt_for) => stmt_for.into(),
|
||||||
|
ClauseHeader::While(stmt_while) => stmt_while.into(),
|
||||||
|
ClauseHeader::With(stmt_with) => stmt_with.into(),
|
||||||
|
ClauseHeader::OrElse(else_clause) => else_clause.into(),
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
#[derive(Copy, Clone)]
|
#[derive(Copy, Clone)]
|
||||||
pub(crate) enum ElseClause<'a> {
|
pub(crate) enum ElseClause<'a> {
|
||||||
Try(&'a StmtTry),
|
Try(&'a StmtTry),
|
||||||
|
|
@ -345,6 +402,16 @@ pub(crate) enum ElseClause<'a> {
|
||||||
While(&'a StmtWhile),
|
While(&'a StmtWhile),
|
||||||
}
|
}
|
||||||
|
|
||||||
|
impl<'a> From<ElseClause<'a>> for AnyNodeRef<'a> {
|
||||||
|
fn from(value: ElseClause<'a>) -> Self {
|
||||||
|
match value {
|
||||||
|
ElseClause::Try(stmt_try) => stmt_try.into(),
|
||||||
|
ElseClause::For(stmt_for) => stmt_for.into(),
|
||||||
|
ElseClause::While(stmt_while) => stmt_while.into(),
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
pub(crate) struct FormatClauseHeader<'a, 'ast> {
|
pub(crate) struct FormatClauseHeader<'a, 'ast> {
|
||||||
header: ClauseHeader<'a>,
|
header: ClauseHeader<'a>,
|
||||||
/// How to format the clause header
|
/// How to format the clause header
|
||||||
|
|
@ -378,22 +445,6 @@ where
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
impl<'a> FormatClauseHeader<'a, '_> {
|
|
||||||
/// Sets the leading comments that precede an alternate branch.
|
|
||||||
#[must_use]
|
|
||||||
pub(crate) fn with_leading_comments<N>(
|
|
||||||
mut self,
|
|
||||||
comments: &'a [SourceComment],
|
|
||||||
last_node: Option<N>,
|
|
||||||
) -> Self
|
|
||||||
where
|
|
||||||
N: Into<AnyNodeRef<'a>>,
|
|
||||||
{
|
|
||||||
self.leading_comments = Some((comments, last_node.map(Into::into)));
|
|
||||||
self
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
impl<'ast> Format<PyFormatContext<'ast>> for FormatClauseHeader<'_, 'ast> {
|
impl<'ast> Format<PyFormatContext<'ast>> for FormatClauseHeader<'_, 'ast> {
|
||||||
fn fmt(&self, f: &mut Formatter<PyFormatContext<'ast>>) -> FormatResult<()> {
|
fn fmt(&self, f: &mut Formatter<PyFormatContext<'ast>>) -> FormatResult<()> {
|
||||||
if let Some((leading_comments, last_node)) = self.leading_comments {
|
if let Some((leading_comments, last_node)) = self.leading_comments {
|
||||||
|
|
@ -423,13 +474,13 @@ impl<'ast> Format<PyFormatContext<'ast>> for FormatClauseHeader<'_, 'ast> {
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
pub(crate) struct FormatClauseBody<'a> {
|
struct FormatClauseBody<'a> {
|
||||||
body: &'a Suite,
|
body: &'a Suite,
|
||||||
kind: SuiteKind,
|
kind: SuiteKind,
|
||||||
trailing_comments: &'a [SourceComment],
|
trailing_comments: &'a [SourceComment],
|
||||||
}
|
}
|
||||||
|
|
||||||
pub(crate) fn clause_body<'a>(
|
fn clause_body<'a>(
|
||||||
body: &'a Suite,
|
body: &'a Suite,
|
||||||
kind: SuiteKind,
|
kind: SuiteKind,
|
||||||
trailing_comments: &'a [SourceComment],
|
trailing_comments: &'a [SourceComment],
|
||||||
|
|
@ -465,6 +516,84 @@ impl Format<PyFormatContext<'_>> for FormatClauseBody<'_> {
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
pub(crate) struct FormatClause<'a, 'ast> {
|
||||||
|
header: ClauseHeader<'a>,
|
||||||
|
/// How to format the clause header
|
||||||
|
header_formatter: Argument<'a, PyFormatContext<'ast>>,
|
||||||
|
/// Leading comments coming before the branch, together with the previous node, if any. Only relevant
|
||||||
|
/// for alternate branches.
|
||||||
|
leading_comments: Option<(&'a [SourceComment], Option<AnyNodeRef<'a>>)>,
|
||||||
|
/// The trailing comments coming after the colon.
|
||||||
|
trailing_colon_comment: &'a [SourceComment],
|
||||||
|
body: &'a Suite,
|
||||||
|
kind: SuiteKind,
|
||||||
|
}
|
||||||
|
|
||||||
|
impl<'a, 'ast> FormatClause<'a, 'ast> {
|
||||||
|
/// Sets the leading comments that precede an alternate branch.
|
||||||
|
#[must_use]
|
||||||
|
pub(crate) fn with_leading_comments<N>(
|
||||||
|
mut self,
|
||||||
|
comments: &'a [SourceComment],
|
||||||
|
last_node: Option<N>,
|
||||||
|
) -> Self
|
||||||
|
where
|
||||||
|
N: Into<AnyNodeRef<'a>>,
|
||||||
|
{
|
||||||
|
self.leading_comments = Some((comments, last_node.map(Into::into)));
|
||||||
|
self
|
||||||
|
}
|
||||||
|
|
||||||
|
fn clause_header(&self) -> FormatClauseHeader<'a, 'ast> {
|
||||||
|
FormatClauseHeader {
|
||||||
|
header: self.header,
|
||||||
|
formatter: self.header_formatter,
|
||||||
|
leading_comments: self.leading_comments,
|
||||||
|
trailing_colon_comment: self.trailing_colon_comment,
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
fn clause_body(&self) -> FormatClauseBody<'a> {
|
||||||
|
clause_body(self.body, self.kind, self.trailing_colon_comment)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/// Formats a clause, handling the case where the compound
|
||||||
|
/// statement lies on a single line with `# fmt: skip` and
|
||||||
|
/// should be suppressed.
|
||||||
|
pub(crate) fn clause<'a, 'ast, Content>(
|
||||||
|
header: ClauseHeader<'a>,
|
||||||
|
header_formatter: &'a Content,
|
||||||
|
trailing_colon_comment: &'a [SourceComment],
|
||||||
|
body: &'a Suite,
|
||||||
|
kind: SuiteKind,
|
||||||
|
) -> FormatClause<'a, 'ast>
|
||||||
|
where
|
||||||
|
Content: Format<PyFormatContext<'ast>>,
|
||||||
|
{
|
||||||
|
FormatClause {
|
||||||
|
header,
|
||||||
|
header_formatter: Argument::new(header_formatter),
|
||||||
|
leading_comments: None,
|
||||||
|
trailing_colon_comment,
|
||||||
|
body,
|
||||||
|
kind,
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
impl<'ast> Format<PyFormatContext<'ast>> for FormatClause<'_, 'ast> {
|
||||||
|
fn fmt(&self, f: &mut Formatter<PyFormatContext<'ast>>) -> FormatResult<()> {
|
||||||
|
match should_suppress_clause(self, f)? {
|
||||||
|
SuppressClauseHeader::Yes {
|
||||||
|
last_child_in_clause,
|
||||||
|
} => write_suppressed_clause(self, f, last_child_in_clause),
|
||||||
|
SuppressClauseHeader::No => {
|
||||||
|
write!(f, [self.clause_header(), self.clause_body()])
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
/// Finds the range of `keyword` starting the search at `start_position`.
|
/// Finds the range of `keyword` starting the search at `start_position`.
|
||||||
///
|
///
|
||||||
/// If the start position is at the end of the previous statement, the
|
/// If the start position is at the end of the previous statement, the
|
||||||
|
|
@ -587,3 +716,96 @@ fn colon_range(after_keyword_or_condition: TextSize, source: &str) -> FormatResu
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
fn should_suppress_clause<'a>(
|
||||||
|
clause: &FormatClause<'a, '_>,
|
||||||
|
f: &mut Formatter<PyFormatContext<'_>>,
|
||||||
|
) -> FormatResult<SuppressClauseHeader<'a>> {
|
||||||
|
let source = f.context().source();
|
||||||
|
|
||||||
|
let Some(last_child_in_clause) = clause.header.last_child_in_clause() else {
|
||||||
|
return Ok(SuppressClauseHeader::No);
|
||||||
|
};
|
||||||
|
|
||||||
|
// Early return if we don't have a skip comment
|
||||||
|
// to avoid computing header range in the common case
|
||||||
|
if !has_skip_comment(
|
||||||
|
f.context().comments().trailing(last_child_in_clause),
|
||||||
|
source,
|
||||||
|
) {
|
||||||
|
return Ok(SuppressClauseHeader::No);
|
||||||
|
}
|
||||||
|
|
||||||
|
let clause_start = clause.header.range(source)?.end();
|
||||||
|
|
||||||
|
let clause_range = TextRange::new(clause_start, last_child_in_clause.end());
|
||||||
|
|
||||||
|
// Only applies to clauses on a single line
|
||||||
|
if source.contains_line_break(clause_range) {
|
||||||
|
return Ok(SuppressClauseHeader::No);
|
||||||
|
}
|
||||||
|
|
||||||
|
Ok(SuppressClauseHeader::Yes {
|
||||||
|
last_child_in_clause,
|
||||||
|
})
|
||||||
|
}
|
||||||
|
|
||||||
|
#[cold]
|
||||||
|
fn write_suppressed_clause(
|
||||||
|
clause: &FormatClause,
|
||||||
|
f: &mut Formatter<PyFormatContext<'_>>,
|
||||||
|
last_child_in_clause: AnyNodeRef,
|
||||||
|
) -> FormatResult<()> {
|
||||||
|
if let Some((leading_comments, last_node)) = clause.leading_comments {
|
||||||
|
leading_alternate_branch_comments(leading_comments, last_node).fmt(f)?;
|
||||||
|
}
|
||||||
|
|
||||||
|
let header = clause.header;
|
||||||
|
let clause_start = header.first_keyword_range(f.context().source())?.start();
|
||||||
|
|
||||||
|
let comments = f.context().comments().clone();
|
||||||
|
|
||||||
|
let clause_end = last_child_in_clause.end();
|
||||||
|
|
||||||
|
// Write the outer comments and format the node as verbatim
|
||||||
|
write!(
|
||||||
|
f,
|
||||||
|
[
|
||||||
|
source_position(clause_start),
|
||||||
|
verbatim_text(TextRange::new(clause_start, clause_end)),
|
||||||
|
source_position(clause_end),
|
||||||
|
trailing_comments(comments.trailing(last_child_in_clause)),
|
||||||
|
hard_line_break()
|
||||||
|
]
|
||||||
|
)?;
|
||||||
|
|
||||||
|
// We mark comments in the header as formatted as in
|
||||||
|
// the implementation of [`write_suppressed_clause_header`].
|
||||||
|
//
|
||||||
|
// Note that the header may be multi-line and contain
|
||||||
|
// various comments since we only require that the range
|
||||||
|
// starting at the _colon_ and ending at the `# fmt: skip`
|
||||||
|
// fits on one line.
|
||||||
|
header.visit(&mut |child| {
|
||||||
|
for comment in comments.leading_trailing(child) {
|
||||||
|
comment.mark_formatted();
|
||||||
|
}
|
||||||
|
comments.mark_verbatim_node_comments_formatted(child);
|
||||||
|
});
|
||||||
|
|
||||||
|
// Similarly we mark the comments in the body as formatted.
|
||||||
|
// Note that the trailing comments for the last child in the
|
||||||
|
// body have already been handled above.
|
||||||
|
for stmt in clause.body {
|
||||||
|
comments.mark_verbatim_node_comments_formatted(stmt.into());
|
||||||
|
}
|
||||||
|
|
||||||
|
Ok(())
|
||||||
|
}
|
||||||
|
|
||||||
|
enum SuppressClauseHeader<'a> {
|
||||||
|
No,
|
||||||
|
Yes {
|
||||||
|
last_child_in_clause: AnyNodeRef<'a>,
|
||||||
|
},
|
||||||
|
}
|
||||||
|
|
|
||||||
|
|
@ -8,7 +8,7 @@ use crate::comments::format::{
|
||||||
};
|
};
|
||||||
use crate::comments::{SourceComment, leading_comments, trailing_comments};
|
use crate::comments::{SourceComment, leading_comments, trailing_comments};
|
||||||
use crate::prelude::*;
|
use crate::prelude::*;
|
||||||
use crate::statement::clause::{ClauseHeader, clause_body, clause_header};
|
use crate::statement::clause::{ClauseHeader, clause};
|
||||||
use crate::statement::suite::SuiteKind;
|
use crate::statement::suite::SuiteKind;
|
||||||
|
|
||||||
#[derive(Default)]
|
#[derive(Default)]
|
||||||
|
|
@ -65,9 +65,8 @@ impl FormatNodeRule<StmtClassDef> for FormatStmtClassDef {
|
||||||
decorators: decorator_list,
|
decorators: decorator_list,
|
||||||
leading_definition_comments,
|
leading_definition_comments,
|
||||||
},
|
},
|
||||||
clause_header(
|
clause(
|
||||||
ClauseHeader::Class(item),
|
ClauseHeader::Class(item),
|
||||||
trailing_definition_comments,
|
|
||||||
&format_with(|f| {
|
&format_with(|f| {
|
||||||
write!(f, [token("class"), space(), name.format()])?;
|
write!(f, [token("class"), space(), name.format()])?;
|
||||||
|
|
||||||
|
|
@ -132,8 +131,10 @@ impl FormatNodeRule<StmtClassDef> for FormatStmtClassDef {
|
||||||
|
|
||||||
Ok(())
|
Ok(())
|
||||||
}),
|
}),
|
||||||
|
trailing_definition_comments,
|
||||||
|
body,
|
||||||
|
SuiteKind::Class,
|
||||||
),
|
),
|
||||||
clause_body(body, SuiteKind::Class, trailing_definition_comments),
|
|
||||||
]
|
]
|
||||||
)?;
|
)?;
|
||||||
|
|
||||||
|
|
|
||||||
|
|
@ -6,7 +6,7 @@ use crate::expression::expr_tuple::TupleParentheses;
|
||||||
use crate::expression::maybe_parenthesize_expression;
|
use crate::expression::maybe_parenthesize_expression;
|
||||||
use crate::expression::parentheses::Parenthesize;
|
use crate::expression::parentheses::Parenthesize;
|
||||||
use crate::prelude::*;
|
use crate::prelude::*;
|
||||||
use crate::statement::clause::{ClauseHeader, ElseClause, clause_body, clause_header};
|
use crate::statement::clause::{ClauseHeader, ElseClause, clause};
|
||||||
use crate::statement::suite::SuiteKind;
|
use crate::statement::suite::SuiteKind;
|
||||||
|
|
||||||
#[derive(Debug)]
|
#[derive(Debug)]
|
||||||
|
|
@ -50,27 +50,22 @@ impl FormatNodeRule<StmtFor> for FormatStmtFor {
|
||||||
|
|
||||||
write!(
|
write!(
|
||||||
f,
|
f,
|
||||||
[
|
[clause(
|
||||||
clause_header(
|
ClauseHeader::For(item),
|
||||||
ClauseHeader::For(item),
|
&format_args![
|
||||||
trailing_condition_comments,
|
is_async.then_some(format_args![token("async"), space()]),
|
||||||
&format_args![
|
token("for"),
|
||||||
is_async.then_some(format_args![token("async"), space()]),
|
space(),
|
||||||
token("for"),
|
ExprTupleWithoutParentheses(target),
|
||||||
space(),
|
space(),
|
||||||
ExprTupleWithoutParentheses(target),
|
token("in"),
|
||||||
space(),
|
space(),
|
||||||
token("in"),
|
maybe_parenthesize_expression(iter, item, Parenthesize::IfBreaks),
|
||||||
space(),
|
],
|
||||||
maybe_parenthesize_expression(iter, item, Parenthesize::IfBreaks),
|
trailing_condition_comments,
|
||||||
],
|
body,
|
||||||
),
|
SuiteKind::other(orelse.is_empty()),
|
||||||
clause_body(
|
),]
|
||||||
body,
|
|
||||||
SuiteKind::other(orelse.is_empty()),
|
|
||||||
trailing_condition_comments
|
|
||||||
),
|
|
||||||
]
|
|
||||||
)?;
|
)?;
|
||||||
|
|
||||||
if orelse.is_empty() {
|
if orelse.is_empty() {
|
||||||
|
|
@ -84,15 +79,14 @@ impl FormatNodeRule<StmtFor> for FormatStmtFor {
|
||||||
|
|
||||||
write!(
|
write!(
|
||||||
f,
|
f,
|
||||||
[
|
[clause(
|
||||||
clause_header(
|
ClauseHeader::OrElse(ElseClause::For(item)),
|
||||||
ClauseHeader::OrElse(ElseClause::For(item)),
|
&token("else"),
|
||||||
trailing,
|
trailing,
|
||||||
&token("else"),
|
orelse,
|
||||||
)
|
SuiteKind::other(true),
|
||||||
.with_leading_comments(leading, body.last()),
|
)
|
||||||
clause_body(orelse, SuiteKind::other(true), trailing),
|
.with_leading_comments(leading, body.last())]
|
||||||
]
|
|
||||||
)?;
|
)?;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
|
||||||
|
|
@ -4,7 +4,7 @@ use crate::comments::format::{
|
||||||
use crate::expression::maybe_parenthesize_expression;
|
use crate::expression::maybe_parenthesize_expression;
|
||||||
use crate::expression::parentheses::{Parentheses, Parenthesize};
|
use crate::expression::parentheses::{Parentheses, Parenthesize};
|
||||||
use crate::prelude::*;
|
use crate::prelude::*;
|
||||||
use crate::statement::clause::{ClauseHeader, clause_body, clause_header};
|
use crate::statement::clause::{ClauseHeader, clause};
|
||||||
use crate::statement::stmt_class_def::FormatDecorators;
|
use crate::statement::stmt_class_def::FormatDecorators;
|
||||||
use crate::statement::suite::SuiteKind;
|
use crate::statement::suite::SuiteKind;
|
||||||
use ruff_formatter::write;
|
use ruff_formatter::write;
|
||||||
|
|
@ -60,12 +60,13 @@ impl FormatNodeRule<StmtFunctionDef> for FormatStmtFunctionDef {
|
||||||
decorators: decorator_list,
|
decorators: decorator_list,
|
||||||
leading_definition_comments,
|
leading_definition_comments,
|
||||||
},
|
},
|
||||||
clause_header(
|
clause(
|
||||||
ClauseHeader::Function(item),
|
ClauseHeader::Function(item),
|
||||||
trailing_definition_comments,
|
|
||||||
&format_with(|f| format_function_header(f, item)),
|
&format_with(|f| format_function_header(f, item)),
|
||||||
|
trailing_definition_comments,
|
||||||
|
body,
|
||||||
|
SuiteKind::Function,
|
||||||
),
|
),
|
||||||
clause_body(body, SuiteKind::Function, trailing_definition_comments),
|
|
||||||
]
|
]
|
||||||
)?;
|
)?;
|
||||||
|
|
||||||
|
|
|
||||||
|
|
@ -5,7 +5,7 @@ use ruff_text_size::Ranged;
|
||||||
use crate::expression::maybe_parenthesize_expression;
|
use crate::expression::maybe_parenthesize_expression;
|
||||||
use crate::expression::parentheses::Parenthesize;
|
use crate::expression::parentheses::Parenthesize;
|
||||||
use crate::prelude::*;
|
use crate::prelude::*;
|
||||||
use crate::statement::clause::{ClauseHeader, clause_body, clause_header};
|
use crate::statement::clause::{ClauseHeader, clause};
|
||||||
use crate::statement::suite::SuiteKind;
|
use crate::statement::suite::SuiteKind;
|
||||||
|
|
||||||
#[derive(Default)]
|
#[derive(Default)]
|
||||||
|
|
@ -26,22 +26,17 @@ impl FormatNodeRule<StmtIf> for FormatStmtIf {
|
||||||
|
|
||||||
write!(
|
write!(
|
||||||
f,
|
f,
|
||||||
[
|
[clause(
|
||||||
clause_header(
|
ClauseHeader::If(item),
|
||||||
ClauseHeader::If(item),
|
&format_args![
|
||||||
trailing_colon_comment,
|
token("if"),
|
||||||
&format_args![
|
space(),
|
||||||
token("if"),
|
maybe_parenthesize_expression(test, item, Parenthesize::IfBreaks),
|
||||||
space(),
|
],
|
||||||
maybe_parenthesize_expression(test, item, Parenthesize::IfBreaks),
|
trailing_colon_comment,
|
||||||
],
|
body,
|
||||||
),
|
SuiteKind::other(elif_else_clauses.is_empty()),
|
||||||
clause_body(
|
)]
|
||||||
body,
|
|
||||||
SuiteKind::other(elif_else_clauses.is_empty()),
|
|
||||||
trailing_colon_comment
|
|
||||||
),
|
|
||||||
]
|
|
||||||
)?;
|
)?;
|
||||||
|
|
||||||
let mut last_node = body.last().unwrap().into();
|
let mut last_node = body.last().unwrap().into();
|
||||||
|
|
@ -81,9 +76,8 @@ pub(crate) fn format_elif_else_clause(
|
||||||
write!(
|
write!(
|
||||||
f,
|
f,
|
||||||
[
|
[
|
||||||
clause_header(
|
clause(
|
||||||
ClauseHeader::ElifElse(item),
|
ClauseHeader::ElifElse(item),
|
||||||
trailing_colon_comment,
|
|
||||||
&format_with(|f: &mut PyFormatter| {
|
&format_with(|f: &mut PyFormatter| {
|
||||||
f.options()
|
f.options()
|
||||||
.source_map_generation()
|
.source_map_generation()
|
||||||
|
|
@ -103,9 +97,11 @@ pub(crate) fn format_elif_else_clause(
|
||||||
token("else").fmt(f)
|
token("else").fmt(f)
|
||||||
}
|
}
|
||||||
}),
|
}),
|
||||||
|
trailing_colon_comment,
|
||||||
|
body,
|
||||||
|
suite_kind,
|
||||||
)
|
)
|
||||||
.with_leading_comments(leading_comments, last_node),
|
.with_leading_comments(leading_comments, last_node),
|
||||||
clause_body(body, suite_kind, trailing_colon_comment),
|
|
||||||
f.options()
|
f.options()
|
||||||
.source_map_generation()
|
.source_map_generation()
|
||||||
.is_enabled()
|
.is_enabled()
|
||||||
|
|
|
||||||
|
|
@ -9,7 +9,7 @@ use crate::other::except_handler_except_handler::{
|
||||||
ExceptHandlerKind, FormatExceptHandlerExceptHandler,
|
ExceptHandlerKind, FormatExceptHandlerExceptHandler,
|
||||||
};
|
};
|
||||||
use crate::prelude::*;
|
use crate::prelude::*;
|
||||||
use crate::statement::clause::{ClauseHeader, ElseClause, clause_body, clause_header};
|
use crate::statement::clause::{ClauseHeader, ElseClause, clause};
|
||||||
use crate::statement::suite::SuiteKind;
|
use crate::statement::suite::SuiteKind;
|
||||||
use crate::statement::{FormatRefWithRule, Stmt};
|
use crate::statement::{FormatRefWithRule, Stmt};
|
||||||
|
|
||||||
|
|
@ -154,15 +154,14 @@ fn format_case<'a>(
|
||||||
|
|
||||||
write!(
|
write!(
|
||||||
f,
|
f,
|
||||||
[
|
[clause(
|
||||||
clause_header(header, trailing_case_comments, &token(kind.keyword()))
|
header,
|
||||||
.with_leading_comments(leading_case_comments, previous_node),
|
&token(kind.keyword()),
|
||||||
clause_body(
|
trailing_case_comments,
|
||||||
body,
|
body,
|
||||||
SuiteKind::other(last_suite_in_statement),
|
SuiteKind::other(last_suite_in_statement),
|
||||||
trailing_case_comments
|
)
|
||||||
),
|
.with_leading_comments(leading_case_comments, previous_node),]
|
||||||
]
|
|
||||||
)?;
|
)?;
|
||||||
(Some(last), rest)
|
(Some(last), rest)
|
||||||
} else {
|
} else {
|
||||||
|
|
|
||||||
|
|
@ -5,7 +5,7 @@ use ruff_text_size::Ranged;
|
||||||
use crate::expression::maybe_parenthesize_expression;
|
use crate::expression::maybe_parenthesize_expression;
|
||||||
use crate::expression::parentheses::Parenthesize;
|
use crate::expression::parentheses::Parenthesize;
|
||||||
use crate::prelude::*;
|
use crate::prelude::*;
|
||||||
use crate::statement::clause::{ClauseHeader, ElseClause, clause_body, clause_header};
|
use crate::statement::clause::{ClauseHeader, ElseClause, clause};
|
||||||
use crate::statement::suite::SuiteKind;
|
use crate::statement::suite::SuiteKind;
|
||||||
|
|
||||||
#[derive(Default)]
|
#[derive(Default)]
|
||||||
|
|
@ -33,22 +33,17 @@ impl FormatNodeRule<StmtWhile> for FormatStmtWhile {
|
||||||
|
|
||||||
write!(
|
write!(
|
||||||
f,
|
f,
|
||||||
[
|
[clause(
|
||||||
clause_header(
|
ClauseHeader::While(item),
|
||||||
ClauseHeader::While(item),
|
&format_args![
|
||||||
trailing_condition_comments,
|
token("while"),
|
||||||
&format_args![
|
space(),
|
||||||
token("while"),
|
maybe_parenthesize_expression(test, item, Parenthesize::IfBreaks),
|
||||||
space(),
|
],
|
||||||
maybe_parenthesize_expression(test, item, Parenthesize::IfBreaks),
|
trailing_condition_comments,
|
||||||
]
|
body,
|
||||||
),
|
SuiteKind::other(orelse.is_empty()),
|
||||||
clause_body(
|
)]
|
||||||
body,
|
|
||||||
SuiteKind::other(orelse.is_empty()),
|
|
||||||
trailing_condition_comments
|
|
||||||
),
|
|
||||||
]
|
|
||||||
)?;
|
)?;
|
||||||
|
|
||||||
if !orelse.is_empty() {
|
if !orelse.is_empty() {
|
||||||
|
|
@ -60,15 +55,14 @@ impl FormatNodeRule<StmtWhile> for FormatStmtWhile {
|
||||||
|
|
||||||
write!(
|
write!(
|
||||||
f,
|
f,
|
||||||
[
|
[clause(
|
||||||
clause_header(
|
ClauseHeader::OrElse(ElseClause::While(item)),
|
||||||
ClauseHeader::OrElse(ElseClause::While(item)),
|
&token("else"),
|
||||||
trailing,
|
trailing,
|
||||||
&token("else")
|
orelse,
|
||||||
)
|
SuiteKind::other(true),
|
||||||
.with_leading_comments(leading, body.last()),
|
)
|
||||||
clause_body(orelse, SuiteKind::other(true), trailing),
|
.with_leading_comments(leading, body.last()),]
|
||||||
]
|
|
||||||
)?;
|
)?;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
|
||||||
|
|
@ -13,7 +13,7 @@ use crate::expression::parentheses::{
|
||||||
use crate::other::commas;
|
use crate::other::commas;
|
||||||
use crate::other::with_item::WithItemLayout;
|
use crate::other::with_item::WithItemLayout;
|
||||||
use crate::prelude::*;
|
use crate::prelude::*;
|
||||||
use crate::statement::clause::{ClauseHeader, clause_body, clause_header};
|
use crate::statement::clause::{ClauseHeader, clause};
|
||||||
use crate::statement::suite::SuiteKind;
|
use crate::statement::suite::SuiteKind;
|
||||||
|
|
||||||
#[derive(Default)]
|
#[derive(Default)]
|
||||||
|
|
@ -46,106 +46,103 @@ impl FormatNodeRule<StmtWith> for FormatStmtWith {
|
||||||
|
|
||||||
write!(
|
write!(
|
||||||
f,
|
f,
|
||||||
[
|
[clause(
|
||||||
clause_header(
|
ClauseHeader::With(with_stmt),
|
||||||
ClauseHeader::With(with_stmt),
|
&format_with(|f| {
|
||||||
colon_comments,
|
write!(
|
||||||
&format_with(|f| {
|
f,
|
||||||
write!(
|
[
|
||||||
f,
|
with_stmt
|
||||||
[
|
.is_async
|
||||||
with_stmt
|
.then_some(format_args![token("async"), space()]),
|
||||||
.is_async
|
token("with"),
|
||||||
.then_some(format_args![token("async"), space()]),
|
space()
|
||||||
token("with"),
|
]
|
||||||
space()
|
)?;
|
||||||
]
|
|
||||||
)?;
|
|
||||||
|
|
||||||
let layout = WithItemsLayout::from_statement(
|
let layout = WithItemsLayout::from_statement(
|
||||||
with_stmt,
|
with_stmt,
|
||||||
f.context(),
|
f.context(),
|
||||||
parenthesized_comments,
|
parenthesized_comments,
|
||||||
)?;
|
)?;
|
||||||
|
|
||||||
match layout {
|
match layout {
|
||||||
WithItemsLayout::SingleWithTarget(single) => {
|
WithItemsLayout::SingleWithTarget(single) => {
|
||||||
optional_parentheses(&single.format().with_options(
|
optional_parentheses(&single.format().with_options(
|
||||||
WithItemLayout::ParenthesizedContextManagers { single: true },
|
WithItemLayout::ParenthesizedContextManagers { single: true },
|
||||||
))
|
))
|
||||||
.fmt(f)
|
.fmt(f)
|
||||||
}
|
|
||||||
|
|
||||||
WithItemsLayout::SingleWithoutTarget(single) => single
|
|
||||||
.format()
|
|
||||||
.with_options(WithItemLayout::SingleWithoutTarget)
|
|
||||||
.fmt(f),
|
|
||||||
|
|
||||||
WithItemsLayout::SingleParenthesizedContextManager(single) => single
|
|
||||||
.format()
|
|
||||||
.with_options(WithItemLayout::SingleParenthesizedContextManager)
|
|
||||||
.fmt(f),
|
|
||||||
|
|
||||||
WithItemsLayout::ParenthesizeIfExpands => {
|
|
||||||
parenthesize_if_expands(&format_with(|f| {
|
|
||||||
let mut joiner = f.join_comma_separated(
|
|
||||||
with_stmt.body.first().unwrap().start(),
|
|
||||||
);
|
|
||||||
|
|
||||||
for item in &with_stmt.items {
|
|
||||||
joiner.entry_with_line_separator(
|
|
||||||
item,
|
|
||||||
&item.format().with_options(
|
|
||||||
WithItemLayout::ParenthesizedContextManagers {
|
|
||||||
single: with_stmt.items.len() == 1,
|
|
||||||
},
|
|
||||||
),
|
|
||||||
soft_line_break_or_space(),
|
|
||||||
);
|
|
||||||
}
|
|
||||||
joiner.finish()
|
|
||||||
}))
|
|
||||||
.fmt(f)
|
|
||||||
}
|
|
||||||
|
|
||||||
WithItemsLayout::Python38OrOlder => f
|
|
||||||
.join_with(format_args![token(","), space()])
|
|
||||||
.entries(with_stmt.items.iter().map(|item| {
|
|
||||||
item.format().with_options(WithItemLayout::Python38OrOlder {
|
|
||||||
single: with_stmt.items.len() == 1,
|
|
||||||
})
|
|
||||||
}))
|
|
||||||
.finish(),
|
|
||||||
|
|
||||||
WithItemsLayout::Parenthesized => parenthesized(
|
|
||||||
"(",
|
|
||||||
&format_with(|f: &mut PyFormatter| {
|
|
||||||
let mut joiner = f.join_comma_separated(
|
|
||||||
with_stmt.body.first().unwrap().start(),
|
|
||||||
);
|
|
||||||
|
|
||||||
for item in &with_stmt.items {
|
|
||||||
joiner.entry(
|
|
||||||
item,
|
|
||||||
&item.format().with_options(
|
|
||||||
WithItemLayout::ParenthesizedContextManagers {
|
|
||||||
single: with_stmt.items.len() == 1,
|
|
||||||
},
|
|
||||||
),
|
|
||||||
);
|
|
||||||
}
|
|
||||||
|
|
||||||
joiner.finish()
|
|
||||||
}),
|
|
||||||
")",
|
|
||||||
)
|
|
||||||
.with_dangling_comments(parenthesized_comments)
|
|
||||||
.fmt(f),
|
|
||||||
}
|
}
|
||||||
})
|
|
||||||
),
|
WithItemsLayout::SingleWithoutTarget(single) => single
|
||||||
clause_body(&with_stmt.body, SuiteKind::other(true), colon_comments)
|
.format()
|
||||||
]
|
.with_options(WithItemLayout::SingleWithoutTarget)
|
||||||
|
.fmt(f),
|
||||||
|
|
||||||
|
WithItemsLayout::SingleParenthesizedContextManager(single) => single
|
||||||
|
.format()
|
||||||
|
.with_options(WithItemLayout::SingleParenthesizedContextManager)
|
||||||
|
.fmt(f),
|
||||||
|
|
||||||
|
WithItemsLayout::ParenthesizeIfExpands => {
|
||||||
|
parenthesize_if_expands(&format_with(|f| {
|
||||||
|
let mut joiner =
|
||||||
|
f.join_comma_separated(with_stmt.body.first().unwrap().start());
|
||||||
|
|
||||||
|
for item in &with_stmt.items {
|
||||||
|
joiner.entry_with_line_separator(
|
||||||
|
item,
|
||||||
|
&item.format().with_options(
|
||||||
|
WithItemLayout::ParenthesizedContextManagers {
|
||||||
|
single: with_stmt.items.len() == 1,
|
||||||
|
},
|
||||||
|
),
|
||||||
|
soft_line_break_or_space(),
|
||||||
|
);
|
||||||
|
}
|
||||||
|
joiner.finish()
|
||||||
|
}))
|
||||||
|
.fmt(f)
|
||||||
|
}
|
||||||
|
|
||||||
|
WithItemsLayout::Python38OrOlder => f
|
||||||
|
.join_with(format_args![token(","), space()])
|
||||||
|
.entries(with_stmt.items.iter().map(|item| {
|
||||||
|
item.format().with_options(WithItemLayout::Python38OrOlder {
|
||||||
|
single: with_stmt.items.len() == 1,
|
||||||
|
})
|
||||||
|
}))
|
||||||
|
.finish(),
|
||||||
|
|
||||||
|
WithItemsLayout::Parenthesized => parenthesized(
|
||||||
|
"(",
|
||||||
|
&format_with(|f: &mut PyFormatter| {
|
||||||
|
let mut joiner =
|
||||||
|
f.join_comma_separated(with_stmt.body.first().unwrap().start());
|
||||||
|
|
||||||
|
for item in &with_stmt.items {
|
||||||
|
joiner.entry(
|
||||||
|
item,
|
||||||
|
&item.format().with_options(
|
||||||
|
WithItemLayout::ParenthesizedContextManagers {
|
||||||
|
single: with_stmt.items.len() == 1,
|
||||||
|
},
|
||||||
|
),
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
joiner.finish()
|
||||||
|
}),
|
||||||
|
")",
|
||||||
|
)
|
||||||
|
.with_dangling_comments(parenthesized_comments)
|
||||||
|
.fmt(f),
|
||||||
|
}
|
||||||
|
}),
|
||||||
|
colon_comments,
|
||||||
|
&with_stmt.body,
|
||||||
|
SuiteKind::other(true),
|
||||||
|
)]
|
||||||
)
|
)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
|
||||||
|
|
@ -20,24 +20,16 @@ b = [c for c in "A very long string that would normally generate some kind of co
|
||||||
```diff
|
```diff
|
||||||
--- Black
|
--- Black
|
||||||
+++ Ruff
|
+++ Ruff
|
||||||
@@ -1,8 +1,14 @@
|
@@ -1,8 +1,10 @@
|
||||||
-def foo(): return "mock" # fmt: skip
|
def foo(): return "mock" # fmt: skip
|
||||||
-if True: print("yay") # fmt: skip
|
|
||||||
-for i in range(10): print(i) # fmt: skip
|
|
||||||
+def foo():
|
|
||||||
+ return "mock" # fmt: skip
|
|
||||||
+
|
+
|
||||||
+
|
+
|
||||||
+if True:
|
if True: print("yay") # fmt: skip
|
||||||
+ print("yay") # fmt: skip
|
for i in range(10): print(i) # fmt: skip
|
||||||
+for i in range(10):
|
|
||||||
+ print(i) # fmt: skip
|
|
||||||
|
|
||||||
-j = 1 # fmt: skip
|
-j = 1 # fmt: skip
|
||||||
-while j < 10: j += 1 # fmt: skip
|
|
||||||
+j = 1 # fmt: skip
|
+j = 1 # fmt: skip
|
||||||
+while j < 10:
|
while j < 10: j += 1 # fmt: skip
|
||||||
+ j += 1 # fmt: skip
|
|
||||||
|
|
||||||
-b = [c for c in "A very long string that would normally generate some kind of collapse, since it is this long"] # fmt: skip
|
-b = [c for c in "A very long string that would normally generate some kind of collapse, since it is this long"] # fmt: skip
|
||||||
+b = [c for c in "A very long string that would normally generate some kind of collapse, since it is this long"] # fmt: skip
|
+b = [c for c in "A very long string that would normally generate some kind of collapse, since it is this long"] # fmt: skip
|
||||||
|
|
@ -46,18 +38,14 @@ b = [c for c in "A very long string that would normally generate some kind of co
|
||||||
## Ruff Output
|
## Ruff Output
|
||||||
|
|
||||||
```python
|
```python
|
||||||
def foo():
|
def foo(): return "mock" # fmt: skip
|
||||||
return "mock" # fmt: skip
|
|
||||||
|
|
||||||
|
|
||||||
if True:
|
if True: print("yay") # fmt: skip
|
||||||
print("yay") # fmt: skip
|
for i in range(10): print(i) # fmt: skip
|
||||||
for i in range(10):
|
|
||||||
print(i) # fmt: skip
|
|
||||||
|
|
||||||
j = 1 # fmt: skip
|
j = 1 # fmt: skip
|
||||||
while j < 10:
|
while j < 10: j += 1 # fmt: skip
|
||||||
j += 1 # fmt: skip
|
|
||||||
|
|
||||||
b = [c for c in "A very long string that would normally generate some kind of collapse, since it is this long"] # fmt: skip
|
b = [c for c in "A very long string that would normally generate some kind of collapse, since it is this long"] # fmt: skip
|
||||||
```
|
```
|
||||||
|
|
|
||||||
|
|
@ -0,0 +1,341 @@
|
||||||
|
---
|
||||||
|
source: crates/ruff_python_formatter/tests/fixtures.rs
|
||||||
|
input_file: crates/ruff_python_formatter/resources/test/fixtures/ruff/fmt_skip/compound_one_liners.py
|
||||||
|
---
|
||||||
|
## Input
|
||||||
|
```python
|
||||||
|
# Test cases for fmt: skip on compound statements that fit on one line
|
||||||
|
|
||||||
|
# Basic single-line compound statements
|
||||||
|
def simple_func(): return "hello" # fmt: skip
|
||||||
|
if True: print("condition met") # fmt: skip
|
||||||
|
for i in range(5): print(i) # fmt: skip
|
||||||
|
while x < 10: x += 1 # fmt: skip
|
||||||
|
|
||||||
|
# With expressions that would normally trigger formatting
|
||||||
|
def long_params(a, b, c, d, e, f, g): return a + b + c + d + e + f + g # fmt: skip
|
||||||
|
if some_very_long_condition_that_might_wrap: do_something_else_that_is_long() # fmt: skip
|
||||||
|
|
||||||
|
# Nested compound statements (outer should be preserved)
|
||||||
|
if True:
|
||||||
|
for i in range(10): print(i) # fmt: skip
|
||||||
|
|
||||||
|
# Multiple statements in body (should not apply - multiline)
|
||||||
|
if True:
|
||||||
|
x = 1
|
||||||
|
y = 2 # fmt: skip
|
||||||
|
|
||||||
|
# With decorators - decorated function on one line
|
||||||
|
@overload
|
||||||
|
def decorated_func(x: int) -> str: return str(x) # fmt: skip
|
||||||
|
|
||||||
|
@property
|
||||||
|
def prop_method(self): return self._value # fmt: skip
|
||||||
|
|
||||||
|
# Class definitions on one line
|
||||||
|
class SimpleClass: pass # fmt: skip
|
||||||
|
class GenericClass(Generic[T]): pass # fmt: skip
|
||||||
|
|
||||||
|
# Try/except blocks
|
||||||
|
try: risky_operation() # fmt: skip
|
||||||
|
except ValueError: handle_error() # fmt: skip
|
||||||
|
except: handle_any_error() # fmt: skip
|
||||||
|
else: success_case() # fmt: skip
|
||||||
|
finally: cleanup() # fmt: skip
|
||||||
|
|
||||||
|
# Match statements (Python 3.10+)
|
||||||
|
match value:
|
||||||
|
case 1: print("one") # fmt: skip
|
||||||
|
case _: print("other") # fmt: skip
|
||||||
|
|
||||||
|
# With statements
|
||||||
|
with open("file.txt") as f: content = f.read() # fmt: skip
|
||||||
|
with context_manager() as cm: result = cm.process() # fmt: skip
|
||||||
|
|
||||||
|
# Async variants
|
||||||
|
async def async_func(): return await some_call() # fmt: skip
|
||||||
|
async for item in async_iterator(): await process(item) # fmt: skip
|
||||||
|
async with async_context() as ctx: await ctx.work() # fmt: skip
|
||||||
|
|
||||||
|
# Complex expressions that would normally format
|
||||||
|
def complex_expr(): return [x for x in range(100) if x % 2 == 0 and x > 50] # fmt: skip
|
||||||
|
if condition_a and condition_b or (condition_c and not condition_d): execute_complex_logic() # fmt: skip
|
||||||
|
|
||||||
|
# Edge case: comment positioning
|
||||||
|
def func_with_comment(): # some comment
|
||||||
|
return "value" # fmt: skip
|
||||||
|
|
||||||
|
# Edge case: multiple fmt: skip (only last one should matter)
|
||||||
|
def multiple_skip(): return "test" # fmt: skip # fmt: skip
|
||||||
|
|
||||||
|
# Should NOT be affected (already multiline)
|
||||||
|
def multiline_func():
|
||||||
|
return "this should format normally"
|
||||||
|
|
||||||
|
if long_condition_that_spans \
|
||||||
|
and continues_on_next_line:
|
||||||
|
print("multiline condition")
|
||||||
|
|
||||||
|
# Mix of skipped and non-skipped
|
||||||
|
for i in range(10): print(f"item {i}") # fmt: skip
|
||||||
|
for j in range(5):
|
||||||
|
print(f"formatted item {j}")
|
||||||
|
|
||||||
|
# With trailing comma that would normally be removed
|
||||||
|
def trailing_comma_func(a, b, c,): return a + b + c # fmt: skip
|
||||||
|
|
||||||
|
# Dictionary/list comprehensions
|
||||||
|
def dict_comp(): return {k: v for k, v in items.items() if v is not None} # fmt: skip
|
||||||
|
def list_comp(): return [x * 2 for x in numbers if x > threshold_value] # fmt: skip
|
||||||
|
|
||||||
|
# Lambda in one-liner
|
||||||
|
def with_lambda(): return lambda x, y, z: x + y + z if all([x, y, z]) else None # fmt: skip
|
||||||
|
|
||||||
|
# String formatting that would normally be reformatted
|
||||||
|
def format_string(): return f"Hello {name}, you have {count} items in your cart totaling ${total:.2f}" # fmt: skip
|
||||||
|
|
||||||
|
# loop else clauses
|
||||||
|
for i in range(2): print(i) # fmt: skip
|
||||||
|
else: print("this") # fmt: skip
|
||||||
|
|
||||||
|
|
||||||
|
while foo(): print(i) # fmt: skip
|
||||||
|
else: print("this") # fmt: skip
|
||||||
|
|
||||||
|
# again but only the first skip
|
||||||
|
for i in range(2): print(i) # fmt: skip
|
||||||
|
else: print("this")
|
||||||
|
|
||||||
|
|
||||||
|
while foo(): print(i) # fmt: skip
|
||||||
|
else: print("this")
|
||||||
|
|
||||||
|
# again but only the second skip
|
||||||
|
for i in range(2): print(i)
|
||||||
|
else: print("this") # fmt: skip
|
||||||
|
|
||||||
|
|
||||||
|
while foo(): print(i)
|
||||||
|
else: print("this") # fmt: skip
|
||||||
|
|
||||||
|
# multiple statements in body
|
||||||
|
if True: print("this"); print("that") # fmt: skip
|
||||||
|
|
||||||
|
# Examples with more comments
|
||||||
|
|
||||||
|
try: risky_operation() # fmt: skip
|
||||||
|
# leading 1
|
||||||
|
except ValueError: handle_error() # fmt: skip
|
||||||
|
# leading 2
|
||||||
|
except: handle_any_error() # fmt: skip
|
||||||
|
# leading 3
|
||||||
|
else: success_case() # fmt: skip
|
||||||
|
# leading 4
|
||||||
|
finally: cleanup() # fmt: skip
|
||||||
|
# trailing
|
||||||
|
|
||||||
|
# multi-line before colon (should remain as is)
|
||||||
|
if (
|
||||||
|
long_condition
|
||||||
|
): a + b # fmt: skip
|
||||||
|
|
||||||
|
# over-indented comment example
|
||||||
|
# See https://github.com/astral-sh/ruff/pull/20633#issuecomment-3453288910
|
||||||
|
# and https://github.com/astral-sh/ruff/pull/21185
|
||||||
|
|
||||||
|
for x in it: foo()
|
||||||
|
# comment
|
||||||
|
else: bar() # fmt: skip
|
||||||
|
|
||||||
|
|
||||||
|
if this(
|
||||||
|
'is a long',
|
||||||
|
# commented
|
||||||
|
'condition'
|
||||||
|
): with_a_skip # fmt: skip
|
||||||
|
```
|
||||||
|
|
||||||
|
## Output
|
||||||
|
```python
|
||||||
|
# Test cases for fmt: skip on compound statements that fit on one line
|
||||||
|
|
||||||
|
# Basic single-line compound statements
|
||||||
|
def simple_func(): return "hello" # fmt: skip
|
||||||
|
|
||||||
|
|
||||||
|
if True: print("condition met") # fmt: skip
|
||||||
|
for i in range(5): print(i) # fmt: skip
|
||||||
|
while x < 10: x += 1 # fmt: skip
|
||||||
|
|
||||||
|
|
||||||
|
# With expressions that would normally trigger formatting
|
||||||
|
def long_params(a, b, c, d, e, f, g): return a + b + c + d + e + f + g # fmt: skip
|
||||||
|
|
||||||
|
|
||||||
|
if some_very_long_condition_that_might_wrap: do_something_else_that_is_long() # fmt: skip
|
||||||
|
|
||||||
|
# Nested compound statements (outer should be preserved)
|
||||||
|
if True:
|
||||||
|
for i in range(10): print(i) # fmt: skip
|
||||||
|
|
||||||
|
# Multiple statements in body (should not apply - multiline)
|
||||||
|
if True:
|
||||||
|
x = 1
|
||||||
|
y = 2 # fmt: skip
|
||||||
|
|
||||||
|
|
||||||
|
# With decorators - decorated function on one line
|
||||||
|
@overload
|
||||||
|
def decorated_func(x: int) -> str: return str(x) # fmt: skip
|
||||||
|
|
||||||
|
|
||||||
|
@property
|
||||||
|
def prop_method(self): return self._value # fmt: skip
|
||||||
|
|
||||||
|
|
||||||
|
# Class definitions on one line
|
||||||
|
class SimpleClass: pass # fmt: skip
|
||||||
|
|
||||||
|
|
||||||
|
class GenericClass(Generic[T]): pass # fmt: skip
|
||||||
|
|
||||||
|
|
||||||
|
# Try/except blocks
|
||||||
|
try: risky_operation() # fmt: skip
|
||||||
|
except ValueError: handle_error() # fmt: skip
|
||||||
|
except: handle_any_error() # fmt: skip
|
||||||
|
else: success_case() # fmt: skip
|
||||||
|
finally: cleanup() # fmt: skip
|
||||||
|
|
||||||
|
# Match statements (Python 3.10+)
|
||||||
|
match value:
|
||||||
|
case 1: print("one") # fmt: skip
|
||||||
|
case _: print("other") # fmt: skip
|
||||||
|
|
||||||
|
# With statements
|
||||||
|
with open("file.txt") as f: content = f.read() # fmt: skip
|
||||||
|
with context_manager() as cm: result = cm.process() # fmt: skip
|
||||||
|
|
||||||
|
|
||||||
|
# Async variants
|
||||||
|
async def async_func(): return await some_call() # fmt: skip
|
||||||
|
|
||||||
|
|
||||||
|
async for item in async_iterator(): await process(item) # fmt: skip
|
||||||
|
async with async_context() as ctx: await ctx.work() # fmt: skip
|
||||||
|
|
||||||
|
|
||||||
|
# Complex expressions that would normally format
|
||||||
|
def complex_expr(): return [x for x in range(100) if x % 2 == 0 and x > 50] # fmt: skip
|
||||||
|
|
||||||
|
|
||||||
|
if condition_a and condition_b or (condition_c and not condition_d): execute_complex_logic() # fmt: skip
|
||||||
|
|
||||||
|
|
||||||
|
# Edge case: comment positioning
|
||||||
|
def func_with_comment(): # some comment
|
||||||
|
return "value" # fmt: skip
|
||||||
|
|
||||||
|
|
||||||
|
# Edge case: multiple fmt: skip (only last one should matter)
|
||||||
|
def multiple_skip(): return "test" # fmt: skip # fmt: skip
|
||||||
|
|
||||||
|
|
||||||
|
# Should NOT be affected (already multiline)
|
||||||
|
def multiline_func():
|
||||||
|
return "this should format normally"
|
||||||
|
|
||||||
|
|
||||||
|
if long_condition_that_spans and continues_on_next_line:
|
||||||
|
print("multiline condition")
|
||||||
|
|
||||||
|
# Mix of skipped and non-skipped
|
||||||
|
for i in range(10): print(f"item {i}") # fmt: skip
|
||||||
|
for j in range(5):
|
||||||
|
print(f"formatted item {j}")
|
||||||
|
|
||||||
|
|
||||||
|
# With trailing comma that would normally be removed
|
||||||
|
def trailing_comma_func(a, b, c,): return a + b + c # fmt: skip
|
||||||
|
|
||||||
|
|
||||||
|
# Dictionary/list comprehensions
|
||||||
|
def dict_comp(): return {k: v for k, v in items.items() if v is not None} # fmt: skip
|
||||||
|
|
||||||
|
|
||||||
|
def list_comp(): return [x * 2 for x in numbers if x > threshold_value] # fmt: skip
|
||||||
|
|
||||||
|
|
||||||
|
# Lambda in one-liner
|
||||||
|
def with_lambda(): return lambda x, y, z: x + y + z if all([x, y, z]) else None # fmt: skip
|
||||||
|
|
||||||
|
|
||||||
|
# String formatting that would normally be reformatted
|
||||||
|
def format_string(): return f"Hello {name}, you have {count} items in your cart totaling ${total:.2f}" # fmt: skip
|
||||||
|
|
||||||
|
|
||||||
|
# loop else clauses
|
||||||
|
for i in range(2): print(i) # fmt: skip
|
||||||
|
else: print("this") # fmt: skip
|
||||||
|
|
||||||
|
|
||||||
|
while foo(): print(i) # fmt: skip
|
||||||
|
else: print("this") # fmt: skip
|
||||||
|
|
||||||
|
# again but only the first skip
|
||||||
|
for i in range(2): print(i) # fmt: skip
|
||||||
|
else:
|
||||||
|
print("this")
|
||||||
|
|
||||||
|
|
||||||
|
while foo(): print(i) # fmt: skip
|
||||||
|
else:
|
||||||
|
print("this")
|
||||||
|
|
||||||
|
# again but only the second skip
|
||||||
|
for i in range(2):
|
||||||
|
print(i)
|
||||||
|
else: print("this") # fmt: skip
|
||||||
|
|
||||||
|
|
||||||
|
while foo():
|
||||||
|
print(i)
|
||||||
|
else: print("this") # fmt: skip
|
||||||
|
|
||||||
|
# multiple statements in body
|
||||||
|
if True: print("this"); print("that") # fmt: skip
|
||||||
|
|
||||||
|
# Examples with more comments
|
||||||
|
|
||||||
|
try: risky_operation() # fmt: skip
|
||||||
|
# leading 1
|
||||||
|
except ValueError: handle_error() # fmt: skip
|
||||||
|
# leading 2
|
||||||
|
except: handle_any_error() # fmt: skip
|
||||||
|
# leading 3
|
||||||
|
else: success_case() # fmt: skip
|
||||||
|
# leading 4
|
||||||
|
finally: cleanup() # fmt: skip
|
||||||
|
# trailing
|
||||||
|
|
||||||
|
# multi-line before colon (should remain as is)
|
||||||
|
if (
|
||||||
|
long_condition
|
||||||
|
): a + b # fmt: skip
|
||||||
|
|
||||||
|
# over-indented comment example
|
||||||
|
# See https://github.com/astral-sh/ruff/pull/20633#issuecomment-3453288910
|
||||||
|
# and https://github.com/astral-sh/ruff/pull/21185
|
||||||
|
|
||||||
|
for x in it:
|
||||||
|
foo()
|
||||||
|
# comment
|
||||||
|
else: bar() # fmt: skip
|
||||||
|
|
||||||
|
|
||||||
|
if this(
|
||||||
|
'is a long',
|
||||||
|
# commented
|
||||||
|
'condition'
|
||||||
|
): with_a_skip # fmt: skip
|
||||||
|
```
|
||||||
|
|
@ -1,7 +1,6 @@
|
||||||
---
|
---
|
||||||
source: crates/ruff_python_formatter/tests/fixtures.rs
|
source: crates/ruff_python_formatter/tests/fixtures.rs
|
||||||
input_file: crates/ruff_python_formatter/resources/test/fixtures/ruff/fmt_skip/docstrings.py
|
input_file: crates/ruff_python_formatter/resources/test/fixtures/ruff/fmt_skip/docstrings.py
|
||||||
snapshot_kind: text
|
|
||||||
---
|
---
|
||||||
## Input
|
## Input
|
||||||
```python
|
```python
|
||||||
|
|
|
||||||
Loading…
Reference in New Issue