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::prelude::*;
|
||||
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;
|
||||
|
||||
#[derive(Copy, Clone, Default)]
|
||||
|
|
@ -55,10 +55,8 @@ impl FormatNodeRule<ExceptHandlerExceptHandler> for FormatExceptHandlerExceptHan
|
|||
|
||||
write!(
|
||||
f,
|
||||
[
|
||||
clause_header(
|
||||
[clause(
|
||||
ClauseHeader::ExceptHandler(item),
|
||||
dangling_comments,
|
||||
&format_with(|f: &mut PyFormatter| {
|
||||
write!(
|
||||
f,
|
||||
|
|
@ -83,18 +81,14 @@ impl FormatNodeRule<ExceptHandlerExceptHandler> for FormatExceptHandlerExceptHan
|
|||
// ```
|
||||
Some(Expr::Tuple(tuple))
|
||||
if f.options().target_version() >= PythonVersion::PY314
|
||||
&& is_remove_parens_around_except_types_enabled(
|
||||
f.context(),
|
||||
)
|
||||
&& is_remove_parens_around_except_types_enabled(f.context())
|
||||
&& name.is_none() =>
|
||||
{
|
||||
write!(
|
||||
f,
|
||||
[
|
||||
space(),
|
||||
tuple
|
||||
.format()
|
||||
.with_options(TupleParentheses::NeverPreserve)
|
||||
tuple.format().with_options(TupleParentheses::NeverPreserve)
|
||||
]
|
||||
)?;
|
||||
}
|
||||
|
|
@ -119,13 +113,10 @@ impl FormatNodeRule<ExceptHandlerExceptHandler> for FormatExceptHandlerExceptHan
|
|||
|
||||
Ok(())
|
||||
}),
|
||||
),
|
||||
clause_body(
|
||||
dangling_comments,
|
||||
body,
|
||||
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::pattern::maybe_parenthesize_pattern;
|
||||
use crate::prelude::*;
|
||||
use crate::statement::clause::{ClauseHeader, clause_body, clause_header};
|
||||
use crate::statement::clause::{ClauseHeader, clause};
|
||||
use crate::statement::suite::SuiteKind;
|
||||
|
||||
#[derive(Default)]
|
||||
|
|
@ -46,23 +46,18 @@ impl FormatNodeRule<MatchCase> for FormatMatchCase {
|
|||
|
||||
write!(
|
||||
f,
|
||||
[
|
||||
clause_header(
|
||||
[clause(
|
||||
ClauseHeader::MatchCase(item),
|
||||
dangling_item_comments,
|
||||
&format_args![
|
||||
token("case"),
|
||||
space(),
|
||||
maybe_parenthesize_pattern(pattern, item),
|
||||
format_guard
|
||||
],
|
||||
),
|
||||
clause_body(
|
||||
dangling_item_comments,
|
||||
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,
|
||||
};
|
||||
use ruff_python_trivia::{SimpleToken, SimpleTokenKind, SimpleTokenizer};
|
||||
use ruff_source_file::LineRanges;
|
||||
use ruff_text_size::{Ranged, TextRange, TextSize};
|
||||
|
||||
use crate::comments::{SourceComment, leading_alternate_branch_comments, trailing_comments};
|
||||
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::*};
|
||||
|
||||
/// The header of a compound statement clause.
|
||||
|
|
@ -36,7 +37,41 @@ pub(crate) enum ClauseHeader<'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.
|
||||
pub(crate) fn range(self, source: &str) -> FormatResult<TextRange> {
|
||||
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)]
|
||||
pub(crate) enum ElseClause<'a> {
|
||||
Try(&'a StmtTry),
|
||||
|
|
@ -345,6 +402,16 @@ pub(crate) enum ElseClause<'a> {
|
|||
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> {
|
||||
header: ClauseHeader<'a>,
|
||||
/// 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> {
|
||||
fn fmt(&self, f: &mut Formatter<PyFormatContext<'ast>>) -> FormatResult<()> {
|
||||
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,
|
||||
kind: SuiteKind,
|
||||
trailing_comments: &'a [SourceComment],
|
||||
}
|
||||
|
||||
pub(crate) fn clause_body<'a>(
|
||||
fn clause_body<'a>(
|
||||
body: &'a Suite,
|
||||
kind: SuiteKind,
|
||||
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`.
|
||||
///
|
||||
/// 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::prelude::*;
|
||||
use crate::statement::clause::{ClauseHeader, clause_body, clause_header};
|
||||
use crate::statement::clause::{ClauseHeader, clause};
|
||||
use crate::statement::suite::SuiteKind;
|
||||
|
||||
#[derive(Default)]
|
||||
|
|
@ -65,9 +65,8 @@ impl FormatNodeRule<StmtClassDef> for FormatStmtClassDef {
|
|||
decorators: decorator_list,
|
||||
leading_definition_comments,
|
||||
},
|
||||
clause_header(
|
||||
clause(
|
||||
ClauseHeader::Class(item),
|
||||
trailing_definition_comments,
|
||||
&format_with(|f| {
|
||||
write!(f, [token("class"), space(), name.format()])?;
|
||||
|
||||
|
|
@ -132,8 +131,10 @@ impl FormatNodeRule<StmtClassDef> for FormatStmtClassDef {
|
|||
|
||||
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::parentheses::Parenthesize;
|
||||
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;
|
||||
|
||||
#[derive(Debug)]
|
||||
|
|
@ -50,10 +50,8 @@ impl FormatNodeRule<StmtFor> for FormatStmtFor {
|
|||
|
||||
write!(
|
||||
f,
|
||||
[
|
||||
clause_header(
|
||||
[clause(
|
||||
ClauseHeader::For(item),
|
||||
trailing_condition_comments,
|
||||
&format_args![
|
||||
is_async.then_some(format_args![token("async"), space()]),
|
||||
token("for"),
|
||||
|
|
@ -64,13 +62,10 @@ impl FormatNodeRule<StmtFor> for FormatStmtFor {
|
|||
space(),
|
||||
maybe_parenthesize_expression(iter, item, Parenthesize::IfBreaks),
|
||||
],
|
||||
),
|
||||
clause_body(
|
||||
trailing_condition_comments,
|
||||
body,
|
||||
SuiteKind::other(orelse.is_empty()),
|
||||
trailing_condition_comments
|
||||
),
|
||||
]
|
||||
),]
|
||||
)?;
|
||||
|
||||
if orelse.is_empty() {
|
||||
|
|
@ -84,15 +79,14 @@ impl FormatNodeRule<StmtFor> for FormatStmtFor {
|
|||
|
||||
write!(
|
||||
f,
|
||||
[
|
||||
clause_header(
|
||||
[clause(
|
||||
ClauseHeader::OrElse(ElseClause::For(item)),
|
||||
trailing,
|
||||
&token("else"),
|
||||
trailing,
|
||||
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::parentheses::{Parentheses, Parenthesize};
|
||||
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::suite::SuiteKind;
|
||||
use ruff_formatter::write;
|
||||
|
|
@ -60,12 +60,13 @@ impl FormatNodeRule<StmtFunctionDef> for FormatStmtFunctionDef {
|
|||
decorators: decorator_list,
|
||||
leading_definition_comments,
|
||||
},
|
||||
clause_header(
|
||||
clause(
|
||||
ClauseHeader::Function(item),
|
||||
trailing_definition_comments,
|
||||
&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::parentheses::Parenthesize;
|
||||
use crate::prelude::*;
|
||||
use crate::statement::clause::{ClauseHeader, clause_body, clause_header};
|
||||
use crate::statement::clause::{ClauseHeader, clause};
|
||||
use crate::statement::suite::SuiteKind;
|
||||
|
||||
#[derive(Default)]
|
||||
|
|
@ -26,22 +26,17 @@ impl FormatNodeRule<StmtIf> for FormatStmtIf {
|
|||
|
||||
write!(
|
||||
f,
|
||||
[
|
||||
clause_header(
|
||||
[clause(
|
||||
ClauseHeader::If(item),
|
||||
trailing_colon_comment,
|
||||
&format_args![
|
||||
token("if"),
|
||||
space(),
|
||||
maybe_parenthesize_expression(test, item, Parenthesize::IfBreaks),
|
||||
],
|
||||
),
|
||||
clause_body(
|
||||
trailing_colon_comment,
|
||||
body,
|
||||
SuiteKind::other(elif_else_clauses.is_empty()),
|
||||
trailing_colon_comment
|
||||
),
|
||||
]
|
||||
)]
|
||||
)?;
|
||||
|
||||
let mut last_node = body.last().unwrap().into();
|
||||
|
|
@ -81,9 +76,8 @@ pub(crate) fn format_elif_else_clause(
|
|||
write!(
|
||||
f,
|
||||
[
|
||||
clause_header(
|
||||
clause(
|
||||
ClauseHeader::ElifElse(item),
|
||||
trailing_colon_comment,
|
||||
&format_with(|f: &mut PyFormatter| {
|
||||
f.options()
|
||||
.source_map_generation()
|
||||
|
|
@ -103,9 +97,11 @@ pub(crate) fn format_elif_else_clause(
|
|||
token("else").fmt(f)
|
||||
}
|
||||
}),
|
||||
trailing_colon_comment,
|
||||
body,
|
||||
suite_kind,
|
||||
)
|
||||
.with_leading_comments(leading_comments, last_node),
|
||||
clause_body(body, suite_kind, trailing_colon_comment),
|
||||
f.options()
|
||||
.source_map_generation()
|
||||
.is_enabled()
|
||||
|
|
|
|||
|
|
@ -9,7 +9,7 @@ use crate::other::except_handler_except_handler::{
|
|||
ExceptHandlerKind, FormatExceptHandlerExceptHandler,
|
||||
};
|
||||
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::{FormatRefWithRule, Stmt};
|
||||
|
||||
|
|
@ -154,15 +154,14 @@ fn format_case<'a>(
|
|||
|
||||
write!(
|
||||
f,
|
||||
[
|
||||
clause_header(header, trailing_case_comments, &token(kind.keyword()))
|
||||
.with_leading_comments(leading_case_comments, previous_node),
|
||||
clause_body(
|
||||
[clause(
|
||||
header,
|
||||
&token(kind.keyword()),
|
||||
trailing_case_comments,
|
||||
body,
|
||||
SuiteKind::other(last_suite_in_statement),
|
||||
trailing_case_comments
|
||||
),
|
||||
]
|
||||
)
|
||||
.with_leading_comments(leading_case_comments, previous_node),]
|
||||
)?;
|
||||
(Some(last), rest)
|
||||
} else {
|
||||
|
|
|
|||
|
|
@ -5,7 +5,7 @@ use ruff_text_size::Ranged;
|
|||
use crate::expression::maybe_parenthesize_expression;
|
||||
use crate::expression::parentheses::Parenthesize;
|
||||
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;
|
||||
|
||||
#[derive(Default)]
|
||||
|
|
@ -33,22 +33,17 @@ impl FormatNodeRule<StmtWhile> for FormatStmtWhile {
|
|||
|
||||
write!(
|
||||
f,
|
||||
[
|
||||
clause_header(
|
||||
[clause(
|
||||
ClauseHeader::While(item),
|
||||
trailing_condition_comments,
|
||||
&format_args![
|
||||
token("while"),
|
||||
space(),
|
||||
maybe_parenthesize_expression(test, item, Parenthesize::IfBreaks),
|
||||
]
|
||||
),
|
||||
clause_body(
|
||||
],
|
||||
trailing_condition_comments,
|
||||
body,
|
||||
SuiteKind::other(orelse.is_empty()),
|
||||
trailing_condition_comments
|
||||
),
|
||||
]
|
||||
)]
|
||||
)?;
|
||||
|
||||
if !orelse.is_empty() {
|
||||
|
|
@ -60,15 +55,14 @@ impl FormatNodeRule<StmtWhile> for FormatStmtWhile {
|
|||
|
||||
write!(
|
||||
f,
|
||||
[
|
||||
clause_header(
|
||||
[clause(
|
||||
ClauseHeader::OrElse(ElseClause::While(item)),
|
||||
&token("else"),
|
||||
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::with_item::WithItemLayout;
|
||||
use crate::prelude::*;
|
||||
use crate::statement::clause::{ClauseHeader, clause_body, clause_header};
|
||||
use crate::statement::clause::{ClauseHeader, clause};
|
||||
use crate::statement::suite::SuiteKind;
|
||||
|
||||
#[derive(Default)]
|
||||
|
|
@ -46,10 +46,8 @@ impl FormatNodeRule<StmtWith> for FormatStmtWith {
|
|||
|
||||
write!(
|
||||
f,
|
||||
[
|
||||
clause_header(
|
||||
[clause(
|
||||
ClauseHeader::With(with_stmt),
|
||||
colon_comments,
|
||||
&format_with(|f| {
|
||||
write!(
|
||||
f,
|
||||
|
|
@ -88,9 +86,8 @@ impl FormatNodeRule<StmtWith> for FormatStmtWith {
|
|||
|
||||
WithItemsLayout::ParenthesizeIfExpands => {
|
||||
parenthesize_if_expands(&format_with(|f| {
|
||||
let mut joiner = f.join_comma_separated(
|
||||
with_stmt.body.first().unwrap().start(),
|
||||
);
|
||||
let mut joiner =
|
||||
f.join_comma_separated(with_stmt.body.first().unwrap().start());
|
||||
|
||||
for item in &with_stmt.items {
|
||||
joiner.entry_with_line_separator(
|
||||
|
|
@ -120,9 +117,8 @@ impl FormatNodeRule<StmtWith> for FormatStmtWith {
|
|||
WithItemsLayout::Parenthesized => parenthesized(
|
||||
"(",
|
||||
&format_with(|f: &mut PyFormatter| {
|
||||
let mut joiner = f.join_comma_separated(
|
||||
with_stmt.body.first().unwrap().start(),
|
||||
);
|
||||
let mut joiner =
|
||||
f.join_comma_separated(with_stmt.body.first().unwrap().start());
|
||||
|
||||
for item in &with_stmt.items {
|
||||
joiner.entry(
|
||||
|
|
@ -142,10 +138,11 @@ impl FormatNodeRule<StmtWith> for FormatStmtWith {
|
|||
.with_dangling_comments(parenthesized_comments)
|
||||
.fmt(f),
|
||||
}
|
||||
})
|
||||
),
|
||||
clause_body(&with_stmt.body, SuiteKind::other(true), colon_comments)
|
||||
]
|
||||
}),
|
||||
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
|
||||
--- Black
|
||||
+++ Ruff
|
||||
@@ -1,8 +1,14 @@
|
||||
-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
|
||||
@@ -1,8 +1,10 @@
|
||||
def foo(): return "mock" # fmt: skip
|
||||
+
|
||||
+
|
||||
+if True:
|
||||
+ print("yay") # fmt: skip
|
||||
+for i in range(10):
|
||||
+ print(i) # fmt: skip
|
||||
if True: print("yay") # fmt: skip
|
||||
for i in range(10): print(i) # fmt: skip
|
||||
|
||||
-j = 1 # fmt: skip
|
||||
-while j < 10: j += 1 # fmt: skip
|
||||
+j = 1 # fmt: skip
|
||||
+while j < 10:
|
||||
+ j += 1 # fmt: skip
|
||||
while j < 10: 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
|
||||
|
|
@ -46,18 +38,14 @@ b = [c for c in "A very long string that would normally generate some kind of co
|
|||
## Ruff Output
|
||||
|
||||
```python
|
||||
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
|
||||
if True: print("yay") # fmt: skip
|
||||
for i in range(10): print(i) # fmt: skip
|
||||
|
||||
j = 1 # fmt: skip
|
||||
while j < 10:
|
||||
j += 1 # fmt: skip
|
||||
while j < 10: 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
|
||||
```
|
||||
|
|
|
|||
|
|
@ -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
|
||||
input_file: crates/ruff_python_formatter/resources/test/fixtures/ruff/fmt_skip/docstrings.py
|
||||
snapshot_kind: text
|
||||
---
|
||||
## Input
|
||||
```python
|
||||
|
|
|
|||
Loading…
Reference in New Issue