mirror of
https://github.com/astral-sh/ruff.git
synced 2025-08-02 18:02:58 +00:00
130 lines
4.2 KiB
Rust
130 lines
4.2 KiB
Rust
use crate::comments::{leading_alternate_branch_comments, trailing_comments, SourceComment};
|
|
use crate::expression::parentheses::Parenthesize;
|
|
use crate::prelude::*;
|
|
use crate::FormatNodeRule;
|
|
use ruff_formatter::{write, FormatError};
|
|
use rustpython_parser::ast::{Ranged, Stmt, StmtIf, Suite};
|
|
|
|
#[derive(Default)]
|
|
pub struct FormatStmtIf;
|
|
|
|
impl FormatNodeRule<StmtIf> for FormatStmtIf {
|
|
fn fmt_fields(&self, item: &StmtIf, f: &mut PyFormatter) -> FormatResult<()> {
|
|
let comments = f.context().comments().clone();
|
|
|
|
let mut current = IfOrElIf::If(item);
|
|
let mut else_comments: &[SourceComment];
|
|
let mut last_node_of_previous_body = None;
|
|
|
|
loop {
|
|
let current_statement = current.statement();
|
|
let StmtIf {
|
|
test, body, orelse, ..
|
|
} = current_statement;
|
|
|
|
let first_statement = body.first().ok_or(FormatError::SyntaxError)?;
|
|
let trailing = comments.dangling_comments(current_statement);
|
|
|
|
let trailing_if_comments_end = trailing
|
|
.partition_point(|comment| comment.slice().start() < first_statement.start());
|
|
|
|
let (if_trailing_comments, trailing_alternate_comments) =
|
|
trailing.split_at(trailing_if_comments_end);
|
|
|
|
if current.is_elif() {
|
|
let elif_leading = comments.leading_comments(current_statement);
|
|
// Manually format the leading comments because the formatting bypasses `NodeRule::fmt`
|
|
write!(
|
|
f,
|
|
[
|
|
leading_alternate_branch_comments(elif_leading, last_node_of_previous_body),
|
|
source_position(current_statement.start())
|
|
]
|
|
)?;
|
|
}
|
|
|
|
write!(
|
|
f,
|
|
[
|
|
text(current.keyword()),
|
|
space(),
|
|
test.format().with_options(Parenthesize::IfBreaks),
|
|
text(":"),
|
|
trailing_comments(if_trailing_comments),
|
|
block_indent(&body.format())
|
|
]
|
|
)?;
|
|
|
|
// RustPython models `elif` by setting the body to a single `if` statement. The `orelse`
|
|
// of the most inner `if` statement then becomes the `else` of the whole `if` chain.
|
|
// That's why it's necessary to take the comments here from the most inner `elif`.
|
|
else_comments = trailing_alternate_comments;
|
|
last_node_of_previous_body = body.last();
|
|
|
|
if let Some(elif) = else_if(orelse) {
|
|
current = elif;
|
|
} else {
|
|
break;
|
|
}
|
|
}
|
|
|
|
let orelse = ¤t.statement().orelse;
|
|
|
|
if !orelse.is_empty() {
|
|
// Leading comments are always own line comments
|
|
let leading_else_comments_end =
|
|
else_comments.partition_point(|comment| comment.position().is_own_line());
|
|
let (else_leading, else_trailing) = else_comments.split_at(leading_else_comments_end);
|
|
|
|
write!(
|
|
f,
|
|
[
|
|
leading_alternate_branch_comments(else_leading, last_node_of_previous_body),
|
|
text("else:"),
|
|
trailing_comments(else_trailing),
|
|
block_indent(&orelse.format())
|
|
]
|
|
)?;
|
|
}
|
|
|
|
Ok(())
|
|
}
|
|
|
|
fn fmt_dangling_comments(&self, _node: &StmtIf, _f: &mut PyFormatter) -> FormatResult<()> {
|
|
// Handled by `fmt_fields`
|
|
Ok(())
|
|
}
|
|
}
|
|
|
|
fn else_if(or_else: &Suite) -> Option<IfOrElIf> {
|
|
if let [Stmt::If(if_stmt)] = or_else.as_slice() {
|
|
Some(IfOrElIf::ElIf(if_stmt))
|
|
} else {
|
|
None
|
|
}
|
|
}
|
|
|
|
enum IfOrElIf<'a> {
|
|
If(&'a StmtIf),
|
|
ElIf(&'a StmtIf),
|
|
}
|
|
|
|
impl<'a> IfOrElIf<'a> {
|
|
const fn statement(&self) -> &'a StmtIf {
|
|
match self {
|
|
IfOrElIf::If(statement) => statement,
|
|
IfOrElIf::ElIf(statement) => statement,
|
|
}
|
|
}
|
|
|
|
const fn keyword(&self) -> &'static str {
|
|
match self {
|
|
IfOrElIf::If(_) => "if",
|
|
IfOrElIf::ElIf(_) => "elif",
|
|
}
|
|
}
|
|
|
|
const fn is_elif(&self) -> bool {
|
|
matches!(self, IfOrElIf::ElIf(_))
|
|
}
|
|
}
|