mirror of
https://github.com/astral-sh/ruff.git
synced 2025-09-28 04:45:01 +00:00
Add initial formatter implementation (#2883)
# Summary This PR contains the code for the autoformatter proof-of-concept. ## Crate structure The primary formatting hook is the `fmt` function in `crates/ruff_python_formatter/src/lib.rs`. The current formatter approach is outlined in `crates/ruff_python_formatter/src/lib.rs`, and is structured as follows: - Tokenize the code using the RustPython lexer. - In `crates/ruff_python_formatter/src/trivia.rs`, extract a variety of trivia tokens from the token stream. These include comments, trailing commas, and empty lines. - Generate the AST via the RustPython parser. - In `crates/ruff_python_formatter/src/cst.rs`, convert the AST to a CST structure. As of now, the CST is nearly identical to the AST, except that every node gets a `trivia` vector. But we might want to modify it further. - In `crates/ruff_python_formatter/src/attachment.rs`, attach each trivia token to the corresponding CST node. The logic for this is mostly in `decorate_trivia` and is ported almost directly from Prettier (given each token, find its preceding, following, and enclosing nodes, then attach the token to the appropriate node in a second pass). - In `crates/ruff_python_formatter/src/newlines.rs`, normalize newlines to match Black’s preferences. This involves traversing the CST and inserting or removing `TriviaToken` values as we go. - Call `format!` on the CST, which delegates to type-specific formatter implementations (e.g., `crates/ruff_python_formatter/src/format/stmt.rs` for `Stmt` nodes, and similar for `Expr` nodes; the others are trivial). Those type-specific implementations delegate to kind-specific functions (e.g., `format_func_def`). ## Testing and iteration The formatter is being developed against the Black test suite, which was copied over in-full to `crates/ruff_python_formatter/resources/test/fixtures/black`. The Black fixtures had to be modified to create `[insta](https://github.com/mitsuhiko/insta)`-compatible snapshots, which now exist in the repo. My approach thus far has been to try and improve coverage by tackling fixtures one-by-one. ## What works, and what doesn’t - *Most* nodes are supported at a basic level (though there are a few stragglers at time of writing, like `StmtKind::Try`). - Newlines are properly preserved in most cases. - Magic trailing commas are properly preserved in some (but not all) cases. - Trivial leading and trailing standalone comments mostly work (although maybe not at the end of a file). - Inline comments, and comments within expressions, often don’t work -- they work in a few cases, but it’s one-off right now. (We’re probably associating them with the “right” nodes more often than we are actually rendering them in the right place.) - We don’t properly normalize string quotes. (At present, we just repeat any constants verbatim.) - We’re mishandling a bunch of wrapping cases (if we treat Black as the reference implementation). Here are a few examples (demonstrating Black's stable behavior): ```py # In some cases, if the end expression is "self-closing" (functions, # lists, dictionaries, sets, subscript accesses, and any length-two # boolean operations that end in these elments), Black # will wrap like this... if some_expression and f( b, c, d, ): pass # ...whereas we do this: if ( some_expression and f( b, c, d, ) ): pass # If function arguments can fit on a single line, then Black will # format them like this, rather than exploding them vertically. if f( a, b, c, d, e, f, g, ... ): pass ``` - We don’t properly preserve parentheses in all cases. Black preserves parentheses in some but not all cases.
This commit is contained in:
parent
f661c90bd7
commit
ca49b00e55
134 changed files with 12044 additions and 18 deletions
33
crates/ruff_python_formatter/src/format/alias.rs
Normal file
33
crates/ruff_python_formatter/src/format/alias.rs
Normal file
|
@ -0,0 +1,33 @@
|
|||
use ruff_formatter::prelude::*;
|
||||
use ruff_formatter::write;
|
||||
use ruff_text_size::TextSize;
|
||||
|
||||
use crate::context::ASTFormatContext;
|
||||
use crate::cst::Alias;
|
||||
use crate::shared_traits::AsFormat;
|
||||
|
||||
pub struct FormatAlias<'a> {
|
||||
item: &'a Alias,
|
||||
}
|
||||
|
||||
impl AsFormat<ASTFormatContext<'_>> for Alias {
|
||||
type Format<'a> = FormatAlias<'a>;
|
||||
|
||||
fn format(&self) -> Self::Format<'_> {
|
||||
FormatAlias { item: self }
|
||||
}
|
||||
}
|
||||
|
||||
impl Format<ASTFormatContext<'_>> for FormatAlias<'_> {
|
||||
fn fmt(&self, f: &mut Formatter<ASTFormatContext>) -> FormatResult<()> {
|
||||
let alias = self.item;
|
||||
|
||||
write!(f, [dynamic_text(&alias.node.name, TextSize::default())])?;
|
||||
if let Some(asname) = &alias.node.asname {
|
||||
write!(f, [text(" as ")])?;
|
||||
write!(f, [dynamic_text(asname, TextSize::default())])?;
|
||||
}
|
||||
|
||||
Ok(())
|
||||
}
|
||||
}
|
33
crates/ruff_python_formatter/src/format/arg.rs
Normal file
33
crates/ruff_python_formatter/src/format/arg.rs
Normal file
|
@ -0,0 +1,33 @@
|
|||
use ruff_formatter::prelude::*;
|
||||
use ruff_formatter::write;
|
||||
use ruff_text_size::TextSize;
|
||||
|
||||
use crate::context::ASTFormatContext;
|
||||
use crate::cst::Arg;
|
||||
use crate::shared_traits::AsFormat;
|
||||
|
||||
pub struct FormatArg<'a> {
|
||||
item: &'a Arg,
|
||||
}
|
||||
|
||||
impl AsFormat<ASTFormatContext<'_>> for Arg {
|
||||
type Format<'a> = FormatArg<'a>;
|
||||
|
||||
fn format(&self) -> Self::Format<'_> {
|
||||
FormatArg { item: self }
|
||||
}
|
||||
}
|
||||
|
||||
impl Format<ASTFormatContext<'_>> for FormatArg<'_> {
|
||||
fn fmt(&self, f: &mut Formatter<ASTFormatContext>) -> FormatResult<()> {
|
||||
let arg = self.item;
|
||||
|
||||
write!(f, [dynamic_text(&arg.node.arg, TextSize::default())])?;
|
||||
if let Some(annotation) = &arg.node.annotation {
|
||||
write!(f, [text(": ")])?;
|
||||
write!(f, [annotation.format()])?;
|
||||
}
|
||||
|
||||
Ok(())
|
||||
}
|
||||
}
|
123
crates/ruff_python_formatter/src/format/arguments.rs
Normal file
123
crates/ruff_python_formatter/src/format/arguments.rs
Normal file
|
@ -0,0 +1,123 @@
|
|||
use ruff_formatter::prelude::*;
|
||||
use ruff_formatter::{format_args, write, Format};
|
||||
|
||||
use crate::context::ASTFormatContext;
|
||||
use crate::cst::Arguments;
|
||||
use crate::shared_traits::AsFormat;
|
||||
|
||||
pub struct FormatArguments<'a> {
|
||||
item: &'a Arguments,
|
||||
}
|
||||
|
||||
impl AsFormat<ASTFormatContext<'_>> for Arguments {
|
||||
type Format<'a> = FormatArguments<'a>;
|
||||
|
||||
fn format(&self) -> Self::Format<'_> {
|
||||
FormatArguments { item: self }
|
||||
}
|
||||
}
|
||||
|
||||
impl Format<ASTFormatContext<'_>> for FormatArguments<'_> {
|
||||
fn fmt(&self, f: &mut Formatter<ASTFormatContext<'_>>) -> FormatResult<()> {
|
||||
let args = self.item;
|
||||
|
||||
let mut first = true;
|
||||
|
||||
let defaults_start = args.posonlyargs.len() + args.args.len() - args.defaults.len();
|
||||
for (i, arg) in args.posonlyargs.iter().chain(&args.args).enumerate() {
|
||||
if !std::mem::take(&mut first) {
|
||||
write!(f, [text(",")])?;
|
||||
write!(f, [soft_line_break_or_space()])?;
|
||||
}
|
||||
|
||||
write!(
|
||||
f,
|
||||
[group(&format_args![format_with(|f| {
|
||||
write!(f, [arg.format()])?;
|
||||
if let Some(i) = i.checked_sub(defaults_start) {
|
||||
if arg.node.annotation.is_some() {
|
||||
write!(f, [space()])?;
|
||||
write!(f, [text("=")])?;
|
||||
write!(f, [space()])?;
|
||||
} else {
|
||||
write!(f, [text("=")])?;
|
||||
}
|
||||
write!(f, [args.defaults[i].format()])?;
|
||||
}
|
||||
Ok(())
|
||||
})])]
|
||||
)?;
|
||||
|
||||
if i + 1 == args.posonlyargs.len() {
|
||||
if !std::mem::take(&mut first) {
|
||||
write!(f, [text(",")])?;
|
||||
write!(f, [soft_line_break_or_space()])?;
|
||||
}
|
||||
write!(f, [text("/")])?;
|
||||
}
|
||||
}
|
||||
|
||||
if let Some(vararg) = &args.vararg {
|
||||
if !std::mem::take(&mut first) {
|
||||
write!(f, [text(",")])?;
|
||||
write!(f, [soft_line_break_or_space()])?;
|
||||
}
|
||||
first = false;
|
||||
|
||||
write!(f, [text("*")])?;
|
||||
write!(f, [vararg.format()])?;
|
||||
} else if !args.kwonlyargs.is_empty() {
|
||||
if !std::mem::take(&mut first) {
|
||||
write!(f, [text(",")])?;
|
||||
write!(f, [soft_line_break_or_space()])?;
|
||||
}
|
||||
first = false;
|
||||
|
||||
write!(f, [text("*")])?;
|
||||
}
|
||||
|
||||
let defaults_start = args.kwonlyargs.len() - args.kw_defaults.len();
|
||||
for (i, kwarg) in args.kwonlyargs.iter().enumerate() {
|
||||
if !std::mem::take(&mut first) {
|
||||
write!(f, [text(",")])?;
|
||||
write!(f, [soft_line_break_or_space()])?;
|
||||
}
|
||||
|
||||
write!(
|
||||
f,
|
||||
[group(&format_args![format_with(|f| {
|
||||
write!(f, [kwarg.format()])?;
|
||||
if let Some(default) = i
|
||||
.checked_sub(defaults_start)
|
||||
.and_then(|i| args.kw_defaults.get(i))
|
||||
{
|
||||
if kwarg.node.annotation.is_some() {
|
||||
write!(f, [space()])?;
|
||||
write!(f, [text("=")])?;
|
||||
write!(f, [space()])?;
|
||||
} else {
|
||||
write!(f, [text("=")])?;
|
||||
}
|
||||
write!(f, [default.format()])?;
|
||||
}
|
||||
Ok(())
|
||||
})])]
|
||||
)?;
|
||||
}
|
||||
if let Some(kwarg) = &args.kwarg {
|
||||
if !std::mem::take(&mut first) {
|
||||
write!(f, [text(",")])?;
|
||||
write!(f, [soft_line_break_or_space()])?;
|
||||
}
|
||||
|
||||
write!(f, [text("**")])?;
|
||||
write!(f, [kwarg.format()])?;
|
||||
}
|
||||
|
||||
if !first {
|
||||
write!(f, [if_group_breaks(&text(","))])?;
|
||||
}
|
||||
|
||||
Ok(())
|
||||
}
|
||||
}
|
32
crates/ruff_python_formatter/src/format/boolop.rs
Normal file
32
crates/ruff_python_formatter/src/format/boolop.rs
Normal file
|
@ -0,0 +1,32 @@
|
|||
use ruff_formatter::prelude::*;
|
||||
use ruff_formatter::write;
|
||||
|
||||
use crate::context::ASTFormatContext;
|
||||
use crate::cst::Boolop;
|
||||
use crate::shared_traits::AsFormat;
|
||||
|
||||
pub struct FormatBoolop<'a> {
|
||||
item: &'a Boolop,
|
||||
}
|
||||
|
||||
impl AsFormat<ASTFormatContext<'_>> for Boolop {
|
||||
type Format<'a> = FormatBoolop<'a>;
|
||||
|
||||
fn format(&self) -> Self::Format<'_> {
|
||||
FormatBoolop { item: self }
|
||||
}
|
||||
}
|
||||
|
||||
impl Format<ASTFormatContext<'_>> for FormatBoolop<'_> {
|
||||
fn fmt(&self, f: &mut Formatter<ASTFormatContext>) -> FormatResult<()> {
|
||||
let boolop = self.item;
|
||||
write!(
|
||||
f,
|
||||
[text(match boolop {
|
||||
Boolop::And => "and",
|
||||
Boolop::Or => "or",
|
||||
})]
|
||||
)?;
|
||||
Ok(())
|
||||
}
|
||||
}
|
47
crates/ruff_python_formatter/src/format/builders.rs
Normal file
47
crates/ruff_python_formatter/src/format/builders.rs
Normal file
|
@ -0,0 +1,47 @@
|
|||
use ruff_formatter::prelude::*;
|
||||
use ruff_formatter::{write, Format};
|
||||
use ruff_text_size::TextSize;
|
||||
|
||||
use crate::context::ASTFormatContext;
|
||||
use crate::cst::Stmt;
|
||||
use crate::shared_traits::AsFormat;
|
||||
|
||||
#[derive(Copy, Clone)]
|
||||
pub struct Block<'a> {
|
||||
body: &'a [Stmt],
|
||||
}
|
||||
|
||||
impl Format<ASTFormatContext<'_>> for Block<'_> {
|
||||
fn fmt(&self, f: &mut Formatter<ASTFormatContext<'_>>) -> FormatResult<()> {
|
||||
for (i, stmt) in self.body.iter().enumerate() {
|
||||
if i > 0 {
|
||||
write!(f, [hard_line_break()])?;
|
||||
}
|
||||
write!(f, [stmt.format()])?;
|
||||
}
|
||||
Ok(())
|
||||
}
|
||||
}
|
||||
|
||||
#[inline]
|
||||
pub fn block(body: &[Stmt]) -> Block {
|
||||
Block { body }
|
||||
}
|
||||
|
||||
pub(crate) const fn join_names(names: &[String]) -> JoinNames {
|
||||
JoinNames { names }
|
||||
}
|
||||
|
||||
pub(crate) struct JoinNames<'a> {
|
||||
names: &'a [String],
|
||||
}
|
||||
|
||||
impl<Context> Format<Context> for JoinNames<'_> {
|
||||
fn fmt(&self, f: &mut Formatter<Context>) -> FormatResult<()> {
|
||||
let mut join = f.join_with(text(", "));
|
||||
for name in self.names {
|
||||
join.entry(&dynamic_text(name, TextSize::default()));
|
||||
}
|
||||
join.finish()
|
||||
}
|
||||
}
|
40
crates/ruff_python_formatter/src/format/cmpop.rs
Normal file
40
crates/ruff_python_formatter/src/format/cmpop.rs
Normal file
|
@ -0,0 +1,40 @@
|
|||
use ruff_formatter::prelude::*;
|
||||
use ruff_formatter::write;
|
||||
|
||||
use crate::context::ASTFormatContext;
|
||||
use crate::cst::Cmpop;
|
||||
use crate::shared_traits::AsFormat;
|
||||
|
||||
pub struct FormatCmpop<'a> {
|
||||
item: &'a Cmpop,
|
||||
}
|
||||
|
||||
impl AsFormat<ASTFormatContext<'_>> for Cmpop {
|
||||
type Format<'a> = FormatCmpop<'a>;
|
||||
|
||||
fn format(&self) -> Self::Format<'_> {
|
||||
FormatCmpop { item: self }
|
||||
}
|
||||
}
|
||||
|
||||
impl Format<ASTFormatContext<'_>> for FormatCmpop<'_> {
|
||||
fn fmt(&self, f: &mut Formatter<ASTFormatContext>) -> FormatResult<()> {
|
||||
let unaryop = self.item;
|
||||
write!(
|
||||
f,
|
||||
[text(match unaryop {
|
||||
Cmpop::Eq => "==",
|
||||
Cmpop::NotEq => "!=",
|
||||
Cmpop::Lt => "<",
|
||||
Cmpop::LtE => "<=",
|
||||
Cmpop::Gt => ">",
|
||||
Cmpop::GtE => ">=",
|
||||
Cmpop::Is => "is",
|
||||
Cmpop::IsNot => "is not",
|
||||
Cmpop::In => "in",
|
||||
Cmpop::NotIn => "not in",
|
||||
})]
|
||||
)?;
|
||||
Ok(())
|
||||
}
|
||||
}
|
43
crates/ruff_python_formatter/src/format/comprehension.rs
Normal file
43
crates/ruff_python_formatter/src/format/comprehension.rs
Normal file
|
@ -0,0 +1,43 @@
|
|||
use ruff_formatter::prelude::*;
|
||||
use ruff_formatter::write;
|
||||
|
||||
use crate::context::ASTFormatContext;
|
||||
use crate::cst::Comprehension;
|
||||
use crate::shared_traits::AsFormat;
|
||||
|
||||
pub struct FormatComprehension<'a> {
|
||||
item: &'a Comprehension,
|
||||
}
|
||||
|
||||
impl AsFormat<ASTFormatContext<'_>> for Comprehension {
|
||||
type Format<'a> = FormatComprehension<'a>;
|
||||
|
||||
fn format(&self) -> Self::Format<'_> {
|
||||
FormatComprehension { item: self }
|
||||
}
|
||||
}
|
||||
|
||||
impl Format<ASTFormatContext<'_>> for FormatComprehension<'_> {
|
||||
fn fmt(&self, f: &mut Formatter<ASTFormatContext<'_>>) -> FormatResult<()> {
|
||||
let comprehension = self.item;
|
||||
|
||||
write!(f, [soft_line_break_or_space()])?;
|
||||
write!(f, [text("for")])?;
|
||||
write!(f, [space()])?;
|
||||
// TODO(charlie): If this is an unparenthesized tuple, we need to avoid expanding it.
|
||||
// Should this be set on the context?
|
||||
write!(f, [group(&comprehension.target.format())])?;
|
||||
write!(f, [space()])?;
|
||||
write!(f, [text("in")])?;
|
||||
write!(f, [space()])?;
|
||||
write!(f, [group(&comprehension.iter.format())])?;
|
||||
for if_clause in &comprehension.ifs {
|
||||
write!(f, [soft_line_break_or_space()])?;
|
||||
write!(f, [text("if")])?;
|
||||
write!(f, [space()])?;
|
||||
write!(f, [if_clause.format()])?;
|
||||
}
|
||||
|
||||
Ok(())
|
||||
}
|
||||
}
|
923
crates/ruff_python_formatter/src/format/expr.rs
Normal file
923
crates/ruff_python_formatter/src/format/expr.rs
Normal file
|
@ -0,0 +1,923 @@
|
|||
#![allow(unused_variables, clippy::too_many_arguments)]
|
||||
|
||||
use ruff_formatter::prelude::*;
|
||||
use ruff_formatter::{format_args, write};
|
||||
use ruff_text_size::TextSize;
|
||||
use rustpython_parser::ast::Constant;
|
||||
|
||||
use crate::builders::literal;
|
||||
use crate::context::ASTFormatContext;
|
||||
use crate::core::types::Range;
|
||||
use crate::cst::{
|
||||
Arguments, Boolop, Cmpop, Comprehension, Expr, ExprKind, Keyword, Operator, Unaryop,
|
||||
};
|
||||
use crate::format::helpers::{is_self_closing, is_simple_power, is_simple_slice};
|
||||
use crate::shared_traits::AsFormat;
|
||||
use crate::trivia::{Parenthesize, Relationship, TriviaKind};
|
||||
|
||||
pub struct FormatExpr<'a> {
|
||||
item: &'a Expr,
|
||||
}
|
||||
|
||||
fn format_starred(
|
||||
f: &mut Formatter<ASTFormatContext<'_>>,
|
||||
expr: &Expr,
|
||||
value: &Expr,
|
||||
) -> FormatResult<()> {
|
||||
write!(f, [text("*"), value.format()])?;
|
||||
|
||||
// Apply any inline comments.
|
||||
let mut first = true;
|
||||
for range in expr.trivia.iter().filter_map(|trivia| {
|
||||
if matches!(trivia.relationship, Relationship::Trailing) {
|
||||
if let TriviaKind::InlineComment(range) = trivia.kind {
|
||||
Some(range)
|
||||
} else {
|
||||
None
|
||||
}
|
||||
} else {
|
||||
None
|
||||
}
|
||||
}) {
|
||||
if std::mem::take(&mut first) {
|
||||
write!(f, [text(" ")])?;
|
||||
}
|
||||
write!(f, [literal(range)])?;
|
||||
}
|
||||
|
||||
Ok(())
|
||||
}
|
||||
|
||||
fn format_name(
|
||||
f: &mut Formatter<ASTFormatContext<'_>>,
|
||||
expr: &Expr,
|
||||
_id: &str,
|
||||
) -> FormatResult<()> {
|
||||
write!(f, [literal(Range::from_located(expr))])?;
|
||||
|
||||
// Apply any inline comments.
|
||||
let mut first = true;
|
||||
for range in expr.trivia.iter().filter_map(|trivia| {
|
||||
if matches!(trivia.relationship, Relationship::Trailing) {
|
||||
if let TriviaKind::InlineComment(range) = trivia.kind {
|
||||
Some(range)
|
||||
} else {
|
||||
None
|
||||
}
|
||||
} else {
|
||||
None
|
||||
}
|
||||
}) {
|
||||
if std::mem::take(&mut first) {
|
||||
write!(f, [text(" ")])?;
|
||||
}
|
||||
write!(f, [literal(range)])?;
|
||||
}
|
||||
|
||||
Ok(())
|
||||
}
|
||||
|
||||
fn format_subscript(
|
||||
f: &mut Formatter<ASTFormatContext<'_>>,
|
||||
expr: &Expr,
|
||||
value: &Expr,
|
||||
slice: &Expr,
|
||||
) -> FormatResult<()> {
|
||||
write!(
|
||||
f,
|
||||
[
|
||||
value.format(),
|
||||
text("["),
|
||||
group(&format_args![soft_block_indent(&slice.format())]),
|
||||
text("]")
|
||||
]
|
||||
)?;
|
||||
Ok(())
|
||||
}
|
||||
|
||||
fn format_tuple(
|
||||
f: &mut Formatter<ASTFormatContext<'_>>,
|
||||
expr: &Expr,
|
||||
elts: &[Expr],
|
||||
) -> FormatResult<()> {
|
||||
// If we're already parenthesized, avoid adding any "mandatory" parentheses.
|
||||
// TODO(charlie): We also need to parenthesize tuples on the right-hand side of an
|
||||
// assignment if the target is exploded. And sometimes the tuple gets exploded, like
|
||||
// if the LHS is an exploded list? Lots of edge cases here.
|
||||
if elts.len() == 1 {
|
||||
write!(
|
||||
f,
|
||||
[group(&format_args![soft_block_indent(&format_with(|f| {
|
||||
write!(f, [elts[0].format()])?;
|
||||
write!(f, [text(",")])?;
|
||||
Ok(())
|
||||
}))])]
|
||||
)?;
|
||||
} else if !elts.is_empty() {
|
||||
write!(
|
||||
f,
|
||||
[group(&format_with(|f| {
|
||||
if matches!(expr.parentheses, Parenthesize::IfExpanded) {
|
||||
write!(f, [if_group_breaks(&text("("))])?;
|
||||
}
|
||||
if matches!(
|
||||
expr.parentheses,
|
||||
Parenthesize::IfExpanded | Parenthesize::Always
|
||||
) {
|
||||
write!(
|
||||
f,
|
||||
[soft_block_indent(&format_with(|f| {
|
||||
// TODO(charlie): If the magic trailing comma isn't present, and the
|
||||
// tuple is _already_ expanded, we're not supposed to add this.
|
||||
let magic_trailing_comma = expr
|
||||
.trivia
|
||||
.iter()
|
||||
.any(|c| matches!(c.kind, TriviaKind::MagicTrailingComma));
|
||||
if magic_trailing_comma {
|
||||
write!(f, [expand_parent()])?;
|
||||
}
|
||||
for (i, elt) in elts.iter().enumerate() {
|
||||
write!(f, [elt.format()])?;
|
||||
if i < elts.len() - 1 {
|
||||
write!(f, [text(",")])?;
|
||||
write!(f, [soft_line_break_or_space()])?;
|
||||
} else {
|
||||
if magic_trailing_comma {
|
||||
write!(f, [if_group_breaks(&text(","))])?;
|
||||
}
|
||||
}
|
||||
}
|
||||
Ok(())
|
||||
}))]
|
||||
)?;
|
||||
} else {
|
||||
let magic_trailing_comma = expr
|
||||
.trivia
|
||||
.iter()
|
||||
.any(|c| matches!(c.kind, TriviaKind::MagicTrailingComma));
|
||||
if magic_trailing_comma {
|
||||
write!(f, [expand_parent()])?;
|
||||
}
|
||||
for (i, elt) in elts.iter().enumerate() {
|
||||
write!(f, [elt.format()])?;
|
||||
if i < elts.len() - 1 {
|
||||
write!(f, [text(",")])?;
|
||||
write!(f, [soft_line_break_or_space()])?;
|
||||
} else {
|
||||
if magic_trailing_comma {
|
||||
write!(f, [if_group_breaks(&text(","))])?;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
if matches!(expr.parentheses, Parenthesize::IfExpanded) {
|
||||
write!(f, [if_group_breaks(&text(")"))])?;
|
||||
}
|
||||
Ok(())
|
||||
}))]
|
||||
)?;
|
||||
}
|
||||
Ok(())
|
||||
}
|
||||
|
||||
fn format_slice(
|
||||
f: &mut Formatter<ASTFormatContext<'_>>,
|
||||
expr: &Expr,
|
||||
lower: Option<&Expr>,
|
||||
upper: Option<&Expr>,
|
||||
step: Option<&Expr>,
|
||||
) -> FormatResult<()> {
|
||||
// https://black.readthedocs.io/en/stable/the_black_code_style/current_style.html#slices
|
||||
let is_simple = lower.map_or(true, is_simple_slice)
|
||||
&& upper.map_or(true, is_simple_slice)
|
||||
&& step.map_or(true, is_simple_slice);
|
||||
|
||||
if let Some(lower) = lower {
|
||||
write!(f, [lower.format()])?;
|
||||
if !is_simple {
|
||||
write!(f, [space()])?;
|
||||
}
|
||||
}
|
||||
write!(f, [text(":")])?;
|
||||
if let Some(upper) = upper {
|
||||
if !is_simple {
|
||||
write!(f, [space()])?;
|
||||
}
|
||||
write!(f, [upper.format()])?;
|
||||
if !is_simple && step.is_some() {
|
||||
write!(f, [space()])?;
|
||||
}
|
||||
}
|
||||
if let Some(step) = step {
|
||||
if !is_simple && upper.is_some() {
|
||||
write!(f, [space()])?;
|
||||
}
|
||||
write!(f, [text(":")])?;
|
||||
if !is_simple {
|
||||
write!(f, [space()])?;
|
||||
}
|
||||
write!(f, [step.format()])?;
|
||||
}
|
||||
|
||||
Ok(())
|
||||
}
|
||||
|
||||
fn format_list(
|
||||
f: &mut Formatter<ASTFormatContext<'_>>,
|
||||
expr: &Expr,
|
||||
elts: &[Expr],
|
||||
) -> FormatResult<()> {
|
||||
write!(f, [text("[")])?;
|
||||
if !elts.is_empty() {
|
||||
let magic_trailing_comma = expr
|
||||
.trivia
|
||||
.iter()
|
||||
.any(|c| matches!(c.kind, TriviaKind::MagicTrailingComma));
|
||||
write!(
|
||||
f,
|
||||
[group(&format_args![soft_block_indent(&format_with(|f| {
|
||||
if magic_trailing_comma {
|
||||
write!(f, [expand_parent()])?;
|
||||
}
|
||||
for (i, elt) in elts.iter().enumerate() {
|
||||
write!(f, [elt.format()])?;
|
||||
if i < elts.len() - 1 {
|
||||
write!(f, [text(",")])?;
|
||||
write!(f, [soft_line_break_or_space()])?;
|
||||
} else {
|
||||
write!(f, [if_group_breaks(&text(","))])?;
|
||||
}
|
||||
}
|
||||
Ok(())
|
||||
}))])]
|
||||
)?;
|
||||
}
|
||||
write!(f, [text("]")])?;
|
||||
Ok(())
|
||||
}
|
||||
|
||||
fn format_set(
|
||||
f: &mut Formatter<ASTFormatContext<'_>>,
|
||||
expr: &Expr,
|
||||
elts: &[Expr],
|
||||
) -> FormatResult<()> {
|
||||
if elts.is_empty() {
|
||||
write!(f, [text("set()")])?;
|
||||
Ok(())
|
||||
} else {
|
||||
write!(f, [text("{")])?;
|
||||
if !elts.is_empty() {
|
||||
let magic_trailing_comma = expr
|
||||
.trivia
|
||||
.iter()
|
||||
.any(|c| matches!(c.kind, TriviaKind::MagicTrailingComma));
|
||||
write!(
|
||||
f,
|
||||
[group(&format_args![soft_block_indent(&format_with(|f| {
|
||||
if magic_trailing_comma {
|
||||
write!(f, [expand_parent()])?;
|
||||
}
|
||||
for (i, elt) in elts.iter().enumerate() {
|
||||
write!(f, [group(&format_args![elt.format()])])?;
|
||||
if i < elts.len() - 1 {
|
||||
write!(f, [text(",")])?;
|
||||
write!(f, [soft_line_break_or_space()])?;
|
||||
} else {
|
||||
write!(f, [if_group_breaks(&text(","))])?;
|
||||
}
|
||||
}
|
||||
Ok(())
|
||||
}))])]
|
||||
)?;
|
||||
}
|
||||
write!(f, [text("}")])?;
|
||||
Ok(())
|
||||
}
|
||||
}
|
||||
|
||||
fn format_call(
|
||||
f: &mut Formatter<ASTFormatContext<'_>>,
|
||||
expr: &Expr,
|
||||
func: &Expr,
|
||||
args: &[Expr],
|
||||
keywords: &[Keyword],
|
||||
) -> FormatResult<()> {
|
||||
write!(f, [func.format()])?;
|
||||
if args.is_empty() && keywords.is_empty() {
|
||||
write!(f, [text("(")])?;
|
||||
write!(f, [text(")")])?;
|
||||
} else {
|
||||
write!(f, [text("(")])?;
|
||||
|
||||
let magic_trailing_comma = expr
|
||||
.trivia
|
||||
.iter()
|
||||
.any(|c| matches!(c.kind, TriviaKind::MagicTrailingComma));
|
||||
write!(
|
||||
f,
|
||||
[group(&format_args![soft_block_indent(&format_with(|f| {
|
||||
if magic_trailing_comma {
|
||||
write!(f, [expand_parent()])?;
|
||||
}
|
||||
|
||||
for (i, arg) in args.iter().enumerate() {
|
||||
write!(f, [group(&format_args![arg.format()])])?;
|
||||
if i < args.len() - 1 || !keywords.is_empty() {
|
||||
write!(f, [text(",")])?;
|
||||
write!(f, [soft_line_break_or_space()])?;
|
||||
} else {
|
||||
if magic_trailing_comma || (args.len() + keywords.len() > 1) {
|
||||
write!(f, [if_group_breaks(&text(","))])?;
|
||||
}
|
||||
}
|
||||
}
|
||||
for (i, keyword) in keywords.iter().enumerate() {
|
||||
write!(
|
||||
f,
|
||||
[group(&format_args![&format_with(|f| {
|
||||
if let Some(arg) = &keyword.node.arg {
|
||||
write!(f, [dynamic_text(arg, TextSize::default())])?;
|
||||
write!(f, [text("=")])?;
|
||||
write!(f, [keyword.node.value.format()])?;
|
||||
} else {
|
||||
write!(f, [text("**")])?;
|
||||
write!(f, [keyword.node.value.format()])?;
|
||||
}
|
||||
Ok(())
|
||||
})])]
|
||||
)?;
|
||||
if i < keywords.len() - 1 {
|
||||
write!(f, [text(",")])?;
|
||||
write!(f, [soft_line_break_or_space()])?;
|
||||
} else {
|
||||
if magic_trailing_comma || (args.len() + keywords.len() > 1) {
|
||||
write!(f, [if_group_breaks(&text(","))])?;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// Apply any dangling trailing comments.
|
||||
for trivia in &expr.trivia {
|
||||
if matches!(trivia.relationship, Relationship::Dangling) {
|
||||
if let TriviaKind::StandaloneComment(range) = trivia.kind {
|
||||
write!(f, [expand_parent()])?;
|
||||
write!(f, [hard_line_break()])?;
|
||||
write!(f, [literal(range)])?;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
Ok(())
|
||||
}))])]
|
||||
)?;
|
||||
write!(f, [text(")")])?;
|
||||
}
|
||||
Ok(())
|
||||
}
|
||||
|
||||
fn format_list_comp(
|
||||
f: &mut Formatter<ASTFormatContext<'_>>,
|
||||
expr: &Expr,
|
||||
elt: &Expr,
|
||||
generators: &[Comprehension],
|
||||
) -> FormatResult<()> {
|
||||
write!(f, [text("[")])?;
|
||||
write!(
|
||||
f,
|
||||
[group(&format_args![soft_block_indent(&format_with(|f| {
|
||||
write!(f, [elt.format()])?;
|
||||
for generator in generators {
|
||||
write!(f, [generator.format()])?;
|
||||
}
|
||||
Ok(())
|
||||
}))])]
|
||||
)?;
|
||||
write!(f, [text("]")])?;
|
||||
Ok(())
|
||||
}
|
||||
|
||||
fn format_set_comp(
|
||||
f: &mut Formatter<ASTFormatContext<'_>>,
|
||||
expr: &Expr,
|
||||
elt: &Expr,
|
||||
generators: &[Comprehension],
|
||||
) -> FormatResult<()> {
|
||||
write!(f, [text("{")])?;
|
||||
write!(
|
||||
f,
|
||||
[group(&format_args![soft_block_indent(&format_with(|f| {
|
||||
write!(f, [elt.format()])?;
|
||||
for generator in generators {
|
||||
write!(f, [generator.format()])?;
|
||||
}
|
||||
Ok(())
|
||||
}))])]
|
||||
)?;
|
||||
write!(f, [text("}")])?;
|
||||
Ok(())
|
||||
}
|
||||
|
||||
fn format_dict_comp(
|
||||
f: &mut Formatter<ASTFormatContext<'_>>,
|
||||
expr: &Expr,
|
||||
key: &Expr,
|
||||
value: &Expr,
|
||||
generators: &[Comprehension],
|
||||
) -> FormatResult<()> {
|
||||
write!(f, [text("{")])?;
|
||||
write!(
|
||||
f,
|
||||
[group(&format_args![soft_block_indent(&format_with(|f| {
|
||||
write!(f, [key.format()])?;
|
||||
write!(f, [text(":")])?;
|
||||
write!(f, [space()])?;
|
||||
write!(f, [value.format()])?;
|
||||
for generator in generators {
|
||||
write!(f, [generator.format()])?;
|
||||
}
|
||||
Ok(())
|
||||
}))])]
|
||||
)?;
|
||||
write!(f, [text("}")])?;
|
||||
Ok(())
|
||||
}
|
||||
|
||||
fn format_generator_exp(
|
||||
f: &mut Formatter<ASTFormatContext<'_>>,
|
||||
expr: &Expr,
|
||||
elt: &Expr,
|
||||
generators: &[Comprehension],
|
||||
) -> FormatResult<()> {
|
||||
write!(
|
||||
f,
|
||||
[group(&format_args![soft_block_indent(&format_with(|f| {
|
||||
write!(f, [elt.format()])?;
|
||||
for generator in generators {
|
||||
write!(f, [generator.format()])?;
|
||||
}
|
||||
Ok(())
|
||||
}))])]
|
||||
)?;
|
||||
Ok(())
|
||||
}
|
||||
|
||||
fn format_await(
|
||||
f: &mut Formatter<ASTFormatContext<'_>>,
|
||||
expr: &Expr,
|
||||
value: &Expr,
|
||||
) -> FormatResult<()> {
|
||||
write!(f, [text("await")])?;
|
||||
write!(f, [space()])?;
|
||||
if is_self_closing(value) {
|
||||
write!(f, [group(&format_args![value.format()])])?;
|
||||
} else {
|
||||
write!(
|
||||
f,
|
||||
[group(&format_args![
|
||||
if_group_breaks(&text("(")),
|
||||
soft_block_indent(&format_args![value.format()]),
|
||||
if_group_breaks(&text(")")),
|
||||
])]
|
||||
)?;
|
||||
}
|
||||
Ok(())
|
||||
}
|
||||
|
||||
fn format_yield(
|
||||
f: &mut Formatter<ASTFormatContext<'_>>,
|
||||
expr: &Expr,
|
||||
value: Option<&Expr>,
|
||||
) -> FormatResult<()> {
|
||||
write!(f, [text("yield")])?;
|
||||
if let Some(value) = value {
|
||||
write!(f, [space()])?;
|
||||
if is_self_closing(value) {
|
||||
write!(f, [group(&format_args![value.format()])])?;
|
||||
} else {
|
||||
write!(
|
||||
f,
|
||||
[group(&format_args![
|
||||
if_group_breaks(&text("(")),
|
||||
soft_block_indent(&format_args![value.format()]),
|
||||
if_group_breaks(&text(")")),
|
||||
])]
|
||||
)?;
|
||||
}
|
||||
}
|
||||
Ok(())
|
||||
}
|
||||
|
||||
fn format_yield_from(
|
||||
f: &mut Formatter<ASTFormatContext<'_>>,
|
||||
expr: &Expr,
|
||||
value: &Expr,
|
||||
) -> FormatResult<()> {
|
||||
write!(
|
||||
f,
|
||||
[group(&format_args![soft_block_indent(&format_with(|f| {
|
||||
write!(f, [text("yield")])?;
|
||||
write!(f, [space()])?;
|
||||
write!(f, [text("from")])?;
|
||||
write!(f, [space()])?;
|
||||
if is_self_closing(value) {
|
||||
write!(f, [value.format()])?;
|
||||
} else {
|
||||
write!(
|
||||
f,
|
||||
[group(&format_args![
|
||||
if_group_breaks(&text("(")),
|
||||
soft_block_indent(&format_args![value.format()]),
|
||||
if_group_breaks(&text(")")),
|
||||
])]
|
||||
)?;
|
||||
}
|
||||
Ok(())
|
||||
})),])]
|
||||
)?;
|
||||
Ok(())
|
||||
}
|
||||
|
||||
fn format_compare(
|
||||
f: &mut Formatter<ASTFormatContext<'_>>,
|
||||
expr: &Expr,
|
||||
left: &Expr,
|
||||
ops: &[Cmpop],
|
||||
comparators: &[Expr],
|
||||
) -> FormatResult<()> {
|
||||
write!(f, [group(&format_args![left.format()])])?;
|
||||
for (i, op) in ops.iter().enumerate() {
|
||||
write!(f, [soft_line_break_or_space()])?;
|
||||
write!(f, [op.format()])?;
|
||||
write!(f, [space()])?;
|
||||
write!(f, [group(&format_args![comparators[i].format()])])?;
|
||||
}
|
||||
Ok(())
|
||||
}
|
||||
|
||||
fn format_joined_str(
|
||||
f: &mut Formatter<ASTFormatContext<'_>>,
|
||||
expr: &Expr,
|
||||
_values: &[Expr],
|
||||
) -> FormatResult<()> {
|
||||
write!(f, [literal(Range::from_located(expr))])?;
|
||||
Ok(())
|
||||
}
|
||||
|
||||
fn format_constant(
|
||||
f: &mut Formatter<ASTFormatContext<'_>>,
|
||||
expr: &Expr,
|
||||
_constant: &Constant,
|
||||
_kind: Option<&str>,
|
||||
) -> FormatResult<()> {
|
||||
write!(f, [literal(Range::from_located(expr))])?;
|
||||
Ok(())
|
||||
}
|
||||
|
||||
fn format_dict(
|
||||
f: &mut Formatter<ASTFormatContext<'_>>,
|
||||
expr: &Expr,
|
||||
keys: &[Option<Expr>],
|
||||
values: &[Expr],
|
||||
) -> FormatResult<()> {
|
||||
write!(f, [text("{")])?;
|
||||
if !keys.is_empty() {
|
||||
let magic_trailing_comma = expr
|
||||
.trivia
|
||||
.iter()
|
||||
.any(|c| matches!(c.kind, TriviaKind::MagicTrailingComma));
|
||||
write!(
|
||||
f,
|
||||
[soft_block_indent(&format_with(|f| {
|
||||
if magic_trailing_comma {
|
||||
write!(f, [expand_parent()])?;
|
||||
}
|
||||
for (i, (k, v)) in keys.iter().zip(values).enumerate() {
|
||||
if let Some(k) = k {
|
||||
write!(f, [k.format()])?;
|
||||
write!(f, [text(":")])?;
|
||||
write!(f, [space()])?;
|
||||
if is_self_closing(v) {
|
||||
write!(f, [v.format()])?;
|
||||
} else {
|
||||
write!(
|
||||
f,
|
||||
[group(&format_args![
|
||||
if_group_breaks(&text("(")),
|
||||
soft_block_indent(&format_args![v.format()]),
|
||||
if_group_breaks(&text(")")),
|
||||
])]
|
||||
)?;
|
||||
}
|
||||
} else {
|
||||
write!(f, [text("**")])?;
|
||||
if is_self_closing(v) {
|
||||
write!(f, [v.format()])?;
|
||||
} else {
|
||||
write!(
|
||||
f,
|
||||
[group(&format_args![
|
||||
if_group_breaks(&text("(")),
|
||||
soft_block_indent(&format_args![v.format()]),
|
||||
if_group_breaks(&text(")")),
|
||||
])]
|
||||
)?;
|
||||
}
|
||||
}
|
||||
if i < keys.len() - 1 {
|
||||
write!(f, [text(",")])?;
|
||||
write!(f, [soft_line_break_or_space()])?;
|
||||
} else {
|
||||
write!(f, [if_group_breaks(&text(","))])?;
|
||||
}
|
||||
}
|
||||
Ok(())
|
||||
}))]
|
||||
)?;
|
||||
}
|
||||
write!(f, [text("}")])?;
|
||||
Ok(())
|
||||
}
|
||||
|
||||
fn format_attribute(
|
||||
f: &mut Formatter<ASTFormatContext<'_>>,
|
||||
expr: &Expr,
|
||||
value: &Expr,
|
||||
attr: &str,
|
||||
) -> FormatResult<()> {
|
||||
write!(f, [value.format()])?;
|
||||
write!(f, [text(".")])?;
|
||||
write!(f, [dynamic_text(attr, TextSize::default())])?;
|
||||
|
||||
// Apply any inline comments.
|
||||
let mut first = true;
|
||||
for range in expr.trivia.iter().filter_map(|trivia| {
|
||||
if matches!(trivia.relationship, Relationship::Trailing) {
|
||||
if let TriviaKind::InlineComment(range) = trivia.kind {
|
||||
Some(range)
|
||||
} else {
|
||||
None
|
||||
}
|
||||
} else {
|
||||
None
|
||||
}
|
||||
}) {
|
||||
if std::mem::take(&mut first) {
|
||||
write!(f, [text(" ")])?;
|
||||
}
|
||||
write!(f, [literal(range)])?;
|
||||
}
|
||||
|
||||
Ok(())
|
||||
}
|
||||
|
||||
fn format_bool_op(
|
||||
f: &mut Formatter<ASTFormatContext<'_>>,
|
||||
expr: &Expr,
|
||||
op: &Boolop,
|
||||
values: &[Expr],
|
||||
) -> FormatResult<()> {
|
||||
let mut first = true;
|
||||
for value in values {
|
||||
if std::mem::take(&mut first) {
|
||||
write!(f, [group(&format_args![value.format()])])?;
|
||||
} else {
|
||||
write!(f, [soft_line_break_or_space()])?;
|
||||
write!(f, [op.format()])?;
|
||||
write!(f, [space()])?;
|
||||
write!(f, [group(&format_args![value.format()])])?;
|
||||
}
|
||||
}
|
||||
|
||||
// Apply any inline comments.
|
||||
let mut first = true;
|
||||
for range in expr.trivia.iter().filter_map(|trivia| {
|
||||
if matches!(trivia.relationship, Relationship::Trailing) {
|
||||
if let TriviaKind::InlineComment(range) = trivia.kind {
|
||||
Some(range)
|
||||
} else {
|
||||
None
|
||||
}
|
||||
} else {
|
||||
None
|
||||
}
|
||||
}) {
|
||||
if std::mem::take(&mut first) {
|
||||
write!(f, [text(" ")])?;
|
||||
}
|
||||
write!(f, [literal(range)])?;
|
||||
}
|
||||
|
||||
Ok(())
|
||||
}
|
||||
|
||||
fn format_bin_op(
|
||||
f: &mut Formatter<ASTFormatContext<'_>>,
|
||||
expr: &Expr,
|
||||
left: &Expr,
|
||||
op: &Operator,
|
||||
right: &Expr,
|
||||
) -> FormatResult<()> {
|
||||
// https://black.readthedocs.io/en/stable/the_black_code_style/current_style.html#line-breaks-binary-operators
|
||||
let is_simple =
|
||||
matches!(op, Operator::Pow) && (is_simple_power(left) && is_simple_power(right));
|
||||
|
||||
write!(f, [left.format()])?;
|
||||
if !is_simple {
|
||||
write!(f, [soft_line_break_or_space()])?;
|
||||
}
|
||||
write!(f, [op.format()])?;
|
||||
if !is_simple {
|
||||
write!(f, [space()])?;
|
||||
}
|
||||
write!(f, [group(&format_args![right.format()])])?;
|
||||
|
||||
// Apply any inline comments.
|
||||
let mut first = true;
|
||||
for range in expr.trivia.iter().filter_map(|trivia| {
|
||||
if matches!(trivia.relationship, Relationship::Trailing) {
|
||||
if let TriviaKind::InlineComment(range) = trivia.kind {
|
||||
Some(range)
|
||||
} else {
|
||||
None
|
||||
}
|
||||
} else {
|
||||
None
|
||||
}
|
||||
}) {
|
||||
if std::mem::take(&mut first) {
|
||||
write!(f, [text(" ")])?;
|
||||
}
|
||||
write!(f, [literal(range)])?;
|
||||
}
|
||||
|
||||
Ok(())
|
||||
}
|
||||
|
||||
fn format_unary_op(
|
||||
f: &mut Formatter<ASTFormatContext<'_>>,
|
||||
expr: &Expr,
|
||||
op: &Unaryop,
|
||||
operand: &Expr,
|
||||
) -> FormatResult<()> {
|
||||
write!(f, [op.format()])?;
|
||||
// TODO(charlie): Do this in the normalization pass.
|
||||
if !matches!(op, Unaryop::Not)
|
||||
&& matches!(
|
||||
operand.node,
|
||||
ExprKind::BoolOp { .. } | ExprKind::Compare { .. } | ExprKind::BinOp { .. }
|
||||
)
|
||||
{
|
||||
let parenthesized = matches!(operand.parentheses, Parenthesize::Always);
|
||||
if !parenthesized {
|
||||
write!(f, [text("(")])?;
|
||||
}
|
||||
write!(f, [operand.format()])?;
|
||||
if !parenthesized {
|
||||
write!(f, [text(")")])?;
|
||||
}
|
||||
} else {
|
||||
write!(f, [operand.format()])?;
|
||||
}
|
||||
Ok(())
|
||||
}
|
||||
|
||||
fn format_lambda(
|
||||
f: &mut Formatter<ASTFormatContext<'_>>,
|
||||
expr: &Expr,
|
||||
args: &Arguments,
|
||||
body: &Expr,
|
||||
) -> FormatResult<()> {
|
||||
write!(f, [text("lambda")])?;
|
||||
if !args.args.is_empty() {
|
||||
write!(f, [space()])?;
|
||||
write!(f, [args.format()])?;
|
||||
}
|
||||
write!(f, [text(":")])?;
|
||||
write!(f, [space()])?;
|
||||
write!(f, [body.format()])?;
|
||||
Ok(())
|
||||
}
|
||||
|
||||
fn format_if_exp(
|
||||
f: &mut Formatter<ASTFormatContext<'_>>,
|
||||
expr: &Expr,
|
||||
test: &Expr,
|
||||
body: &Expr,
|
||||
orelse: &Expr,
|
||||
) -> FormatResult<()> {
|
||||
write!(f, [group(&format_args![body.format()])])?;
|
||||
write!(f, [soft_line_break_or_space()])?;
|
||||
write!(f, [text("if")])?;
|
||||
write!(f, [space()])?;
|
||||
write!(f, [group(&format_args![test.format()])])?;
|
||||
write!(f, [soft_line_break_or_space()])?;
|
||||
write!(f, [text("else")])?;
|
||||
write!(f, [space()])?;
|
||||
write!(f, [group(&format_args![orelse.format()])])?;
|
||||
Ok(())
|
||||
}
|
||||
|
||||
impl Format<ASTFormatContext<'_>> for FormatExpr<'_> {
|
||||
fn fmt(&self, f: &mut Formatter<ASTFormatContext<'_>>) -> FormatResult<()> {
|
||||
if matches!(self.item.parentheses, Parenthesize::Always) {
|
||||
write!(f, [text("(")])?;
|
||||
}
|
||||
|
||||
// Any leading comments come on the line before.
|
||||
for trivia in &self.item.trivia {
|
||||
if matches!(trivia.relationship, Relationship::Leading) {
|
||||
if let TriviaKind::StandaloneComment(range) = trivia.kind {
|
||||
write!(f, [expand_parent()])?;
|
||||
write!(f, [literal(range)])?;
|
||||
write!(f, [hard_line_break()])?;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
match &self.item.node {
|
||||
ExprKind::BoolOp { op, values } => format_bool_op(f, self.item, op, values),
|
||||
// ExprKind::NamedExpr { .. } => {}
|
||||
ExprKind::BinOp { left, op, right } => format_bin_op(f, self.item, left, op, right),
|
||||
ExprKind::UnaryOp { op, operand } => format_unary_op(f, self.item, op, operand),
|
||||
ExprKind::Lambda { args, body } => format_lambda(f, self.item, args, body),
|
||||
ExprKind::IfExp { test, body, orelse } => {
|
||||
format_if_exp(f, self.item, test, body, orelse)
|
||||
}
|
||||
ExprKind::Dict { keys, values } => format_dict(f, self.item, keys, values),
|
||||
ExprKind::Set { elts, .. } => format_set(f, self.item, elts),
|
||||
ExprKind::ListComp { elt, generators } => {
|
||||
format_list_comp(f, self.item, elt, generators)
|
||||
}
|
||||
ExprKind::SetComp { elt, generators } => format_set_comp(f, self.item, elt, generators),
|
||||
ExprKind::DictComp {
|
||||
key,
|
||||
value,
|
||||
generators,
|
||||
} => format_dict_comp(f, self.item, key, value, generators),
|
||||
ExprKind::GeneratorExp { elt, generators } => {
|
||||
format_generator_exp(f, self.item, elt, generators)
|
||||
}
|
||||
ExprKind::Await { value } => format_await(f, self.item, value),
|
||||
ExprKind::Yield { value } => format_yield(f, self.item, value.as_deref()),
|
||||
ExprKind::YieldFrom { value } => format_yield_from(f, self.item, value),
|
||||
ExprKind::Compare {
|
||||
left,
|
||||
ops,
|
||||
comparators,
|
||||
} => format_compare(f, self.item, left, ops, comparators),
|
||||
ExprKind::Call {
|
||||
func,
|
||||
args,
|
||||
keywords,
|
||||
} => format_call(f, self.item, func, args, keywords),
|
||||
// ExprKind::FormattedValue { .. } => {}
|
||||
ExprKind::JoinedStr { values } => format_joined_str(f, self.item, values),
|
||||
ExprKind::Constant { value, kind } => {
|
||||
format_constant(f, self.item, value, kind.as_deref())
|
||||
}
|
||||
ExprKind::Attribute { value, attr, .. } => format_attribute(f, self.item, value, attr),
|
||||
ExprKind::Subscript { value, slice, .. } => {
|
||||
format_subscript(f, self.item, value, slice)
|
||||
}
|
||||
ExprKind::Starred { value, .. } => format_starred(f, self.item, value),
|
||||
ExprKind::Name { id, .. } => format_name(f, self.item, id),
|
||||
ExprKind::List { elts, .. } => format_list(f, self.item, elts),
|
||||
ExprKind::Tuple { elts, .. } => format_tuple(f, self.item, elts),
|
||||
ExprKind::Slice { lower, upper, step } => format_slice(
|
||||
f,
|
||||
self.item,
|
||||
lower.as_deref(),
|
||||
upper.as_deref(),
|
||||
step.as_deref(),
|
||||
),
|
||||
_ => {
|
||||
unimplemented!("Implement ExprKind: {:?}", self.item.node)
|
||||
}
|
||||
}?;
|
||||
|
||||
// Any trailing comments come on the lines after.
|
||||
for trivia in &self.item.trivia {
|
||||
if matches!(trivia.relationship, Relationship::Trailing) {
|
||||
if let TriviaKind::StandaloneComment(range) = trivia.kind {
|
||||
write!(f, [expand_parent()])?;
|
||||
write!(f, [literal(range)])?;
|
||||
write!(f, [hard_line_break()])?;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
if matches!(self.item.parentheses, Parenthesize::Always) {
|
||||
write!(f, [text(")")])?;
|
||||
}
|
||||
|
||||
Ok(())
|
||||
}
|
||||
}
|
||||
|
||||
impl AsFormat<ASTFormatContext<'_>> for Expr {
|
||||
type Format<'a> = FormatExpr<'a>;
|
||||
|
||||
fn format(&self) -> Self::Format<'_> {
|
||||
FormatExpr { item: self }
|
||||
}
|
||||
}
|
87
crates/ruff_python_formatter/src/format/helpers.rs
Normal file
87
crates/ruff_python_formatter/src/format/helpers.rs
Normal file
|
@ -0,0 +1,87 @@
|
|||
use crate::cst::{Expr, ExprKind, Unaryop};
|
||||
|
||||
pub fn is_self_closing(expr: &Expr) -> bool {
|
||||
match &expr.node {
|
||||
ExprKind::Tuple { .. }
|
||||
| ExprKind::List { .. }
|
||||
| ExprKind::Set { .. }
|
||||
| ExprKind::Dict { .. }
|
||||
| ExprKind::ListComp { .. }
|
||||
| ExprKind::SetComp { .. }
|
||||
| ExprKind::DictComp { .. }
|
||||
| ExprKind::GeneratorExp { .. }
|
||||
| ExprKind::Call { .. }
|
||||
| ExprKind::Name { .. }
|
||||
| ExprKind::Constant { .. }
|
||||
| ExprKind::Subscript { .. } => true,
|
||||
ExprKind::Lambda { body, .. } => is_self_closing(body),
|
||||
ExprKind::BinOp { left, right, .. } => {
|
||||
matches!(left.node, ExprKind::Constant { .. } | ExprKind::Name { .. })
|
||||
&& matches!(
|
||||
right.node,
|
||||
ExprKind::Tuple { .. }
|
||||
| ExprKind::List { .. }
|
||||
| ExprKind::Set { .. }
|
||||
| ExprKind::Dict { .. }
|
||||
| ExprKind::ListComp { .. }
|
||||
| ExprKind::SetComp { .. }
|
||||
| ExprKind::DictComp { .. }
|
||||
| ExprKind::GeneratorExp { .. }
|
||||
| ExprKind::Call { .. }
|
||||
| ExprKind::Subscript { .. }
|
||||
)
|
||||
}
|
||||
ExprKind::BoolOp { values, .. } => values.last().map_or(false, |expr| {
|
||||
matches!(
|
||||
expr.node,
|
||||
ExprKind::Tuple { .. }
|
||||
| ExprKind::List { .. }
|
||||
| ExprKind::Set { .. }
|
||||
| ExprKind::Dict { .. }
|
||||
| ExprKind::ListComp { .. }
|
||||
| ExprKind::SetComp { .. }
|
||||
| ExprKind::DictComp { .. }
|
||||
| ExprKind::GeneratorExp { .. }
|
||||
| ExprKind::Call { .. }
|
||||
| ExprKind::Subscript { .. }
|
||||
)
|
||||
}),
|
||||
ExprKind::UnaryOp { operand, .. } => is_self_closing(operand),
|
||||
_ => false,
|
||||
}
|
||||
}
|
||||
|
||||
/// Return `true` if an [`Expr`] adheres to Black's definition of a non-complex
|
||||
/// expression, in the context of a slice operation.
|
||||
pub fn is_simple_slice(expr: &Expr) -> bool {
|
||||
match &expr.node {
|
||||
ExprKind::UnaryOp { op, operand } => {
|
||||
if matches!(op, Unaryop::Not) {
|
||||
false
|
||||
} else {
|
||||
is_simple_slice(operand)
|
||||
}
|
||||
}
|
||||
ExprKind::Constant { .. } => true,
|
||||
ExprKind::Name { .. } => true,
|
||||
_ => false,
|
||||
}
|
||||
}
|
||||
|
||||
/// Return `true` if an [`Expr`] adheres to Black's definition of a non-complex
|
||||
/// expression, in the context of a power operation.
|
||||
pub fn is_simple_power(expr: &Expr) -> bool {
|
||||
match &expr.node {
|
||||
ExprKind::UnaryOp { op, operand } => {
|
||||
if matches!(op, Unaryop::Not) {
|
||||
false
|
||||
} else {
|
||||
is_simple_slice(operand)
|
||||
}
|
||||
}
|
||||
ExprKind::Constant { .. } => true,
|
||||
ExprKind::Name { .. } => true,
|
||||
ExprKind::Attribute { .. } => true,
|
||||
_ => false,
|
||||
}
|
||||
}
|
13
crates/ruff_python_formatter/src/format/mod.rs
Normal file
13
crates/ruff_python_formatter/src/format/mod.rs
Normal file
|
@ -0,0 +1,13 @@
|
|||
mod alias;
|
||||
mod arg;
|
||||
mod arguments;
|
||||
mod boolop;
|
||||
pub mod builders;
|
||||
mod cmpop;
|
||||
mod comprehension;
|
||||
mod expr;
|
||||
mod helpers;
|
||||
mod operator;
|
||||
mod stmt;
|
||||
mod unaryop;
|
||||
mod withitem;
|
45
crates/ruff_python_formatter/src/format/operator.rs
Normal file
45
crates/ruff_python_formatter/src/format/operator.rs
Normal file
|
@ -0,0 +1,45 @@
|
|||
use ruff_formatter::prelude::*;
|
||||
use ruff_formatter::write;
|
||||
|
||||
use crate::context::ASTFormatContext;
|
||||
use crate::cst::Operator;
|
||||
use crate::shared_traits::AsFormat;
|
||||
|
||||
pub struct FormatOperator<'a> {
|
||||
item: &'a Operator,
|
||||
}
|
||||
|
||||
impl AsFormat<ASTFormatContext<'_>> for Operator {
|
||||
type Format<'a> = FormatOperator<'a>;
|
||||
|
||||
fn format(&self) -> Self::Format<'_> {
|
||||
FormatOperator { item: self }
|
||||
}
|
||||
}
|
||||
|
||||
impl Format<ASTFormatContext<'_>> for FormatOperator<'_> {
|
||||
fn fmt(&self, f: &mut Formatter<ASTFormatContext<'_>>) -> FormatResult<()> {
|
||||
let operator = self.item;
|
||||
|
||||
write!(
|
||||
f,
|
||||
[text(match operator {
|
||||
Operator::Add => "+",
|
||||
Operator::Sub => "-",
|
||||
Operator::Mult => "*",
|
||||
Operator::MatMult => "@",
|
||||
Operator::Div => "/",
|
||||
Operator::Mod => "%",
|
||||
Operator::Pow => "**",
|
||||
Operator::LShift => "<<",
|
||||
Operator::RShift => ">>",
|
||||
Operator::BitOr => "|",
|
||||
Operator::BitXor => "^",
|
||||
Operator::BitAnd => "&",
|
||||
Operator::FloorDiv => "//",
|
||||
})]
|
||||
)?;
|
||||
|
||||
Ok(())
|
||||
}
|
||||
}
|
829
crates/ruff_python_formatter/src/format/stmt.rs
Normal file
829
crates/ruff_python_formatter/src/format/stmt.rs
Normal file
|
@ -0,0 +1,829 @@
|
|||
#![allow(unused_variables, clippy::too_many_arguments)]
|
||||
|
||||
use ruff_formatter::prelude::*;
|
||||
use ruff_formatter::{format_args, write};
|
||||
use ruff_text_size::TextSize;
|
||||
|
||||
use crate::builders::literal;
|
||||
use crate::context::ASTFormatContext;
|
||||
use crate::cst::{Alias, Arguments, Expr, ExprKind, Keyword, Stmt, StmtKind, Withitem};
|
||||
use crate::format::builders::{block, join_names};
|
||||
use crate::format::helpers::is_self_closing;
|
||||
use crate::shared_traits::AsFormat;
|
||||
use crate::trivia::{Parenthesize, Relationship, TriviaKind};
|
||||
|
||||
fn format_break(f: &mut Formatter<ASTFormatContext<'_>>) -> FormatResult<()> {
|
||||
write!(f, [text("break")])
|
||||
}
|
||||
|
||||
fn format_pass(f: &mut Formatter<ASTFormatContext<'_>>, stmt: &Stmt) -> FormatResult<()> {
|
||||
// Write the statement body.
|
||||
write!(f, [text("pass")])?;
|
||||
|
||||
// Apply any inline comments.
|
||||
let mut first = true;
|
||||
for range in stmt.trivia.iter().filter_map(|trivia| {
|
||||
if matches!(trivia.relationship, Relationship::Trailing) {
|
||||
if let TriviaKind::InlineComment(range) = trivia.kind {
|
||||
Some(range)
|
||||
} else {
|
||||
None
|
||||
}
|
||||
} else {
|
||||
None
|
||||
}
|
||||
}) {
|
||||
if std::mem::take(&mut first) {
|
||||
write!(f, [text(" ")])?;
|
||||
}
|
||||
write!(f, [literal(range)])?;
|
||||
}
|
||||
|
||||
Ok(())
|
||||
}
|
||||
|
||||
fn format_continue(f: &mut Formatter<ASTFormatContext<'_>>) -> FormatResult<()> {
|
||||
write!(f, [text("continue")])
|
||||
}
|
||||
|
||||
fn format_global(f: &mut Formatter<ASTFormatContext<'_>>, names: &[String]) -> FormatResult<()> {
|
||||
write!(f, [text("global")])?;
|
||||
if !names.is_empty() {
|
||||
write!(f, [space(), join_names(names)])?;
|
||||
}
|
||||
Ok(())
|
||||
}
|
||||
|
||||
fn format_nonlocal(f: &mut Formatter<ASTFormatContext<'_>>, names: &[String]) -> FormatResult<()> {
|
||||
write!(f, [text("nonlocal")])?;
|
||||
if !names.is_empty() {
|
||||
write!(f, [space(), join_names(names)])?;
|
||||
}
|
||||
Ok(())
|
||||
}
|
||||
|
||||
fn format_delete(f: &mut Formatter<ASTFormatContext<'_>>, targets: &[Expr]) -> FormatResult<()> {
|
||||
write!(f, [text("del")])?;
|
||||
|
||||
match targets.len() {
|
||||
0 => Ok(()),
|
||||
1 => write!(f, [space(), targets[0].format()]),
|
||||
_ => {
|
||||
write!(
|
||||
f,
|
||||
[
|
||||
space(),
|
||||
group(&format_args![
|
||||
if_group_breaks(&text("(")),
|
||||
soft_block_indent(&format_with(|f| {
|
||||
for (i, target) in targets.iter().enumerate() {
|
||||
write!(f, [target.format()])?;
|
||||
|
||||
if i < targets.len() - 1 {
|
||||
write!(f, [text(","), soft_line_break_or_space()])?;
|
||||
} else {
|
||||
write!(f, [if_group_breaks(&text(","))])?;
|
||||
}
|
||||
}
|
||||
Ok(())
|
||||
})),
|
||||
if_group_breaks(&text(")")),
|
||||
])
|
||||
]
|
||||
)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
fn format_class_def(
|
||||
f: &mut Formatter<ASTFormatContext<'_>>,
|
||||
name: &str,
|
||||
bases: &[Expr],
|
||||
keywords: &[Keyword],
|
||||
body: &[Stmt],
|
||||
decorator_list: &[Expr],
|
||||
) -> FormatResult<()> {
|
||||
for decorator in decorator_list {
|
||||
write!(f, [text("@"), decorator.format(), hard_line_break()])?;
|
||||
}
|
||||
|
||||
write!(
|
||||
f,
|
||||
[
|
||||
text("class"),
|
||||
space(),
|
||||
dynamic_text(name, TextSize::default())
|
||||
]
|
||||
)?;
|
||||
|
||||
if !bases.is_empty() || !keywords.is_empty() {
|
||||
let format_bases = format_with(|f| {
|
||||
for (i, expr) in bases.iter().enumerate() {
|
||||
write!(f, [expr.format()])?;
|
||||
|
||||
if i < bases.len() - 1 || !keywords.is_empty() {
|
||||
write!(f, [text(","), soft_line_break_or_space()])?;
|
||||
} else {
|
||||
write!(f, [if_group_breaks(&text(","))])?;
|
||||
}
|
||||
|
||||
for (i, keyword) in keywords.iter().enumerate() {
|
||||
if let Some(arg) = &keyword.node.arg {
|
||||
write!(
|
||||
f,
|
||||
[
|
||||
dynamic_text(arg, TextSize::default()),
|
||||
text("="),
|
||||
keyword.node.value.format()
|
||||
]
|
||||
)?;
|
||||
} else {
|
||||
write!(f, [text("**"), keyword.node.value.format()])?;
|
||||
}
|
||||
if i < keywords.len() - 1 {
|
||||
write!(f, [text(","), soft_line_break_or_space()])?;
|
||||
} else {
|
||||
write!(f, [if_group_breaks(&text(","))])?;
|
||||
}
|
||||
}
|
||||
}
|
||||
Ok(())
|
||||
});
|
||||
|
||||
write!(
|
||||
f,
|
||||
[
|
||||
text("("),
|
||||
group(&soft_block_indent(&format_bases)),
|
||||
text(")")
|
||||
]
|
||||
)?;
|
||||
}
|
||||
|
||||
write!(f, [text(":"), block_indent(&block(body))])
|
||||
}
|
||||
|
||||
fn format_func_def(
|
||||
f: &mut Formatter<ASTFormatContext<'_>>,
|
||||
stmt: &Stmt,
|
||||
name: &str,
|
||||
args: &Arguments,
|
||||
returns: Option<&Expr>,
|
||||
body: &[Stmt],
|
||||
decorator_list: &[Expr],
|
||||
async_: bool,
|
||||
) -> FormatResult<()> {
|
||||
for decorator in decorator_list {
|
||||
write!(f, [text("@"), decorator.format(), hard_line_break()])?;
|
||||
}
|
||||
if async_ {
|
||||
write!(f, [text("async"), space()])?;
|
||||
}
|
||||
write!(
|
||||
f,
|
||||
[
|
||||
text("def"),
|
||||
space(),
|
||||
dynamic_text(name, TextSize::default()),
|
||||
text("("),
|
||||
group(&soft_block_indent(&format_with(|f| {
|
||||
if stmt
|
||||
.trivia
|
||||
.iter()
|
||||
.any(|c| matches!(c.kind, TriviaKind::MagicTrailingComma))
|
||||
{
|
||||
write!(f, [expand_parent()])?;
|
||||
}
|
||||
write!(f, [args.format()])
|
||||
}))),
|
||||
text(")")
|
||||
]
|
||||
)?;
|
||||
|
||||
if let Some(returns) = returns {
|
||||
write!(f, [text(" -> "), returns.format()])?;
|
||||
}
|
||||
|
||||
write!(f, [text(":")])?;
|
||||
|
||||
// Apply any inline comments.
|
||||
let mut first = true;
|
||||
for range in stmt.trivia.iter().filter_map(|trivia| {
|
||||
if matches!(trivia.relationship, Relationship::Trailing) {
|
||||
if let TriviaKind::InlineComment(range) = trivia.kind {
|
||||
Some(range)
|
||||
} else {
|
||||
None
|
||||
}
|
||||
} else {
|
||||
None
|
||||
}
|
||||
}) {
|
||||
if std::mem::take(&mut first) {
|
||||
write!(f, [text(" ")])?;
|
||||
}
|
||||
write!(f, [literal(range)])?;
|
||||
}
|
||||
|
||||
write!(f, [block_indent(&format_args![block(body)])])
|
||||
}
|
||||
|
||||
fn format_assign(
|
||||
f: &mut Formatter<ASTFormatContext<'_>>,
|
||||
stmt: &Stmt,
|
||||
targets: &[Expr],
|
||||
value: &Expr,
|
||||
) -> FormatResult<()> {
|
||||
write!(f, [targets[0].format()])?;
|
||||
|
||||
for target in &targets[1..] {
|
||||
// TODO(charlie): This doesn't match Black's behavior. We need to parenthesize
|
||||
// this expression sometimes.
|
||||
write!(f, [text(" = "), target.format()])?;
|
||||
}
|
||||
write!(f, [text(" = ")])?;
|
||||
if is_self_closing(value) {
|
||||
write!(f, [group(&value.format())])?;
|
||||
} else {
|
||||
write!(
|
||||
f,
|
||||
[group(&format_args![
|
||||
if_group_breaks(&text("(")),
|
||||
soft_block_indent(&value.format()),
|
||||
if_group_breaks(&text(")")),
|
||||
])]
|
||||
)?;
|
||||
}
|
||||
|
||||
// Apply any inline comments.
|
||||
let mut first = true;
|
||||
for range in stmt.trivia.iter().filter_map(|trivia| {
|
||||
if matches!(trivia.relationship, Relationship::Trailing) {
|
||||
if let TriviaKind::InlineComment(range) = trivia.kind {
|
||||
Some(range)
|
||||
} else {
|
||||
None
|
||||
}
|
||||
} else {
|
||||
None
|
||||
}
|
||||
}) {
|
||||
if std::mem::take(&mut first) {
|
||||
write!(f, [text(" ")])?;
|
||||
}
|
||||
write!(f, [literal(range)])?;
|
||||
}
|
||||
|
||||
Ok(())
|
||||
}
|
||||
|
||||
fn format_ann_assign(
|
||||
f: &mut Formatter<ASTFormatContext<'_>>,
|
||||
stmt: &Stmt,
|
||||
target: &Expr,
|
||||
annotation: &Expr,
|
||||
value: Option<&Expr>,
|
||||
simple: usize,
|
||||
) -> FormatResult<()> {
|
||||
let need_parens = matches!(target.node, ExprKind::Name { .. }) && simple == 0;
|
||||
if need_parens {
|
||||
write!(f, [text("(")])?;
|
||||
}
|
||||
write!(f, [target.format()])?;
|
||||
if need_parens {
|
||||
write!(f, [text(")")])?;
|
||||
}
|
||||
write!(f, [text(": "), annotation.format()])?;
|
||||
|
||||
if let Some(value) = value {
|
||||
write!(
|
||||
f,
|
||||
[
|
||||
space(),
|
||||
text("="),
|
||||
space(),
|
||||
group(&format_args![
|
||||
if_group_breaks(&text("(")),
|
||||
soft_block_indent(&value.format()),
|
||||
if_group_breaks(&text(")")),
|
||||
])
|
||||
]
|
||||
)?;
|
||||
}
|
||||
|
||||
Ok(())
|
||||
}
|
||||
|
||||
fn format_for(
|
||||
f: &mut Formatter<ASTFormatContext<'_>>,
|
||||
stmt: &Stmt,
|
||||
target: &Expr,
|
||||
iter: &Expr,
|
||||
body: &[Stmt],
|
||||
_orelse: &[Stmt],
|
||||
_type_comment: Option<&str>,
|
||||
) -> FormatResult<()> {
|
||||
write!(
|
||||
f,
|
||||
[
|
||||
text("for"),
|
||||
space(),
|
||||
group(&target.format()),
|
||||
space(),
|
||||
text("in"),
|
||||
space(),
|
||||
group(&iter.format()),
|
||||
text(":"),
|
||||
block_indent(&block(body))
|
||||
]
|
||||
)
|
||||
}
|
||||
|
||||
fn format_while(
|
||||
f: &mut Formatter<ASTFormatContext<'_>>,
|
||||
stmt: &Stmt,
|
||||
test: &Expr,
|
||||
body: &[Stmt],
|
||||
orelse: &[Stmt],
|
||||
) -> FormatResult<()> {
|
||||
write!(f, [text("while"), space()])?;
|
||||
if is_self_closing(test) {
|
||||
write!(f, [test.format()])?;
|
||||
} else {
|
||||
write!(
|
||||
f,
|
||||
[group(&format_args![
|
||||
if_group_breaks(&text("(")),
|
||||
soft_block_indent(&test.format()),
|
||||
if_group_breaks(&text(")")),
|
||||
])]
|
||||
)?;
|
||||
}
|
||||
write!(f, [text(":"), block_indent(&block(body))])?;
|
||||
if !orelse.is_empty() {
|
||||
write!(f, [text("else:"), block_indent(&block(orelse))])?;
|
||||
}
|
||||
Ok(())
|
||||
}
|
||||
|
||||
fn format_if(
|
||||
f: &mut Formatter<ASTFormatContext<'_>>,
|
||||
test: &Expr,
|
||||
body: &[Stmt],
|
||||
orelse: &[Stmt],
|
||||
) -> FormatResult<()> {
|
||||
write!(f, [text("if"), space()])?;
|
||||
if is_self_closing(test) {
|
||||
write!(f, [test.format()])?;
|
||||
} else {
|
||||
write!(
|
||||
f,
|
||||
[group(&format_args![
|
||||
if_group_breaks(&text("(")),
|
||||
soft_block_indent(&test.format()),
|
||||
if_group_breaks(&text(")")),
|
||||
])]
|
||||
)?;
|
||||
}
|
||||
write!(f, [text(":"), block_indent(&block(body))])?;
|
||||
if !orelse.is_empty() {
|
||||
if orelse.len() == 1 {
|
||||
if let StmtKind::If { test, body, orelse } = &orelse[0].node {
|
||||
write!(f, [text("el")])?;
|
||||
format_if(f, test, body, orelse)?;
|
||||
} else {
|
||||
write!(f, [text("else:"), block_indent(&block(orelse))])?;
|
||||
}
|
||||
} else {
|
||||
write!(f, [text("else:"), block_indent(&block(orelse))])?;
|
||||
}
|
||||
}
|
||||
Ok(())
|
||||
}
|
||||
|
||||
fn format_raise(
|
||||
f: &mut Formatter<ASTFormatContext<'_>>,
|
||||
stmt: &Stmt,
|
||||
exc: Option<&Expr>,
|
||||
cause: Option<&Expr>,
|
||||
) -> FormatResult<()> {
|
||||
write!(f, [text("raise")])?;
|
||||
if let Some(exc) = exc {
|
||||
write!(f, [space(), exc.format()])?;
|
||||
if let Some(cause) = cause {
|
||||
write!(f, [space(), text("from"), space(), cause.format()])?;
|
||||
}
|
||||
}
|
||||
Ok(())
|
||||
}
|
||||
|
||||
fn format_return(
|
||||
f: &mut Formatter<ASTFormatContext<'_>>,
|
||||
value: Option<&Expr>,
|
||||
) -> FormatResult<()> {
|
||||
write!(f, [text("return")])?;
|
||||
if let Some(value) = value {
|
||||
write!(f, [space(), value.format()])?;
|
||||
}
|
||||
Ok(())
|
||||
}
|
||||
|
||||
fn format_assert(
|
||||
f: &mut Formatter<ASTFormatContext<'_>>,
|
||||
stmt: &Stmt,
|
||||
test: &Expr,
|
||||
msg: Option<&Expr>,
|
||||
) -> FormatResult<()> {
|
||||
write!(f, [text("assert"), space()])?;
|
||||
write!(
|
||||
f,
|
||||
[group(&format_args![
|
||||
if_group_breaks(&text("(")),
|
||||
soft_block_indent(&test.format()),
|
||||
if_group_breaks(&text(")")),
|
||||
])]
|
||||
)?;
|
||||
if let Some(msg) = msg {
|
||||
write!(
|
||||
f,
|
||||
[
|
||||
text(","),
|
||||
space(),
|
||||
group(&format_args![
|
||||
if_group_breaks(&text("(")),
|
||||
soft_block_indent(&msg.format()),
|
||||
if_group_breaks(&text(")")),
|
||||
])
|
||||
]
|
||||
)?;
|
||||
}
|
||||
Ok(())
|
||||
}
|
||||
|
||||
fn format_import(
|
||||
f: &mut Formatter<ASTFormatContext<'_>>,
|
||||
stmt: &Stmt,
|
||||
names: &[Alias],
|
||||
) -> FormatResult<()> {
|
||||
write!(
|
||||
f,
|
||||
[
|
||||
text("import"),
|
||||
space(),
|
||||
group(&format_args![
|
||||
if_group_breaks(&text("(")),
|
||||
soft_block_indent(&format_with(|f| {
|
||||
for (i, name) in names.iter().enumerate() {
|
||||
write!(f, [name.format()])?;
|
||||
if i < names.len() - 1 {
|
||||
write!(f, [text(","), soft_line_break_or_space()])?;
|
||||
} else {
|
||||
write!(f, [if_group_breaks(&text(","))])?;
|
||||
}
|
||||
}
|
||||
Ok(())
|
||||
})),
|
||||
if_group_breaks(&text(")")),
|
||||
])
|
||||
]
|
||||
)
|
||||
}
|
||||
|
||||
fn format_import_from(
|
||||
f: &mut Formatter<ASTFormatContext<'_>>,
|
||||
stmt: &Stmt,
|
||||
module: Option<&str>,
|
||||
names: &[Alias],
|
||||
level: Option<&usize>,
|
||||
) -> FormatResult<()> {
|
||||
write!(f, [text("from")])?;
|
||||
write!(f, [space()])?;
|
||||
|
||||
if let Some(level) = level {
|
||||
for _ in 0..*level {
|
||||
write!(f, [text(".")])?;
|
||||
}
|
||||
}
|
||||
if let Some(module) = module {
|
||||
write!(f, [dynamic_text(module, TextSize::default())])?;
|
||||
}
|
||||
write!(f, [space()])?;
|
||||
|
||||
write!(f, [text("import")])?;
|
||||
write!(f, [space()])?;
|
||||
|
||||
if names.iter().any(|name| name.node.name == "*") {
|
||||
write!(f, [text("*")])?;
|
||||
} else {
|
||||
let magic_trailing_comma = stmt
|
||||
.trivia
|
||||
.iter()
|
||||
.any(|c| matches!(c.kind, TriviaKind::MagicTrailingComma));
|
||||
write!(
|
||||
f,
|
||||
[group(&format_args![
|
||||
if_group_breaks(&text("(")),
|
||||
soft_block_indent(&format_with(|f| {
|
||||
if magic_trailing_comma {
|
||||
write!(f, [expand_parent()])?;
|
||||
}
|
||||
for (i, name) in names.iter().enumerate() {
|
||||
write!(f, [name.format()])?;
|
||||
if i < names.len() - 1 {
|
||||
write!(f, [text(",")])?;
|
||||
write!(f, [soft_line_break_or_space()])?;
|
||||
} else {
|
||||
write!(f, [if_group_breaks(&text(","))])?;
|
||||
}
|
||||
}
|
||||
Ok(())
|
||||
})),
|
||||
if_group_breaks(&text(")")),
|
||||
])]
|
||||
)?;
|
||||
}
|
||||
|
||||
// Apply any inline comments.
|
||||
let mut first = true;
|
||||
for range in stmt.trivia.iter().filter_map(|trivia| {
|
||||
if matches!(trivia.relationship, Relationship::Trailing) {
|
||||
if let TriviaKind::InlineComment(range) = trivia.kind {
|
||||
Some(range)
|
||||
} else {
|
||||
None
|
||||
}
|
||||
} else {
|
||||
None
|
||||
}
|
||||
}) {
|
||||
if std::mem::take(&mut first) {
|
||||
write!(f, [text(" ")])?;
|
||||
}
|
||||
write!(f, [literal(range)])?;
|
||||
}
|
||||
|
||||
Ok(())
|
||||
}
|
||||
|
||||
fn format_expr(
|
||||
f: &mut Formatter<ASTFormatContext<'_>>,
|
||||
stmt: &Stmt,
|
||||
expr: &Expr,
|
||||
) -> FormatResult<()> {
|
||||
if matches!(stmt.parentheses, Parenthesize::Always) {
|
||||
write!(
|
||||
f,
|
||||
[group(&format_args![
|
||||
text("("),
|
||||
soft_block_indent(&format_args![expr.format()]),
|
||||
text(")"),
|
||||
])]
|
||||
)?;
|
||||
} else if is_self_closing(expr) {
|
||||
write!(f, [group(&format_args![expr.format()])])?;
|
||||
} else {
|
||||
write!(
|
||||
f,
|
||||
[group(&format_args![
|
||||
if_group_breaks(&text("(")),
|
||||
soft_block_indent(&format_args![expr.format()]),
|
||||
if_group_breaks(&text(")")),
|
||||
])]
|
||||
)?;
|
||||
}
|
||||
|
||||
// Apply any inline comments.
|
||||
let mut first = true;
|
||||
for range in stmt.trivia.iter().filter_map(|trivia| {
|
||||
if matches!(trivia.relationship, Relationship::Trailing) {
|
||||
if let TriviaKind::InlineComment(range) = trivia.kind {
|
||||
Some(range)
|
||||
} else {
|
||||
None
|
||||
}
|
||||
} else {
|
||||
None
|
||||
}
|
||||
}) {
|
||||
if std::mem::take(&mut first) {
|
||||
write!(f, [text(" ")])?;
|
||||
}
|
||||
write!(f, [literal(range)])?;
|
||||
}
|
||||
|
||||
Ok(())
|
||||
}
|
||||
|
||||
fn format_with_(
|
||||
f: &mut Formatter<ASTFormatContext<'_>>,
|
||||
stmt: &Stmt,
|
||||
items: &[Withitem],
|
||||
body: &[Stmt],
|
||||
type_comment: Option<&str>,
|
||||
async_: bool,
|
||||
) -> FormatResult<()> {
|
||||
if async_ {
|
||||
write!(f, [text("async"), space()])?;
|
||||
}
|
||||
|
||||
write!(
|
||||
f,
|
||||
[
|
||||
text("with"),
|
||||
space(),
|
||||
group(&format_args![
|
||||
if_group_breaks(&text("(")),
|
||||
soft_block_indent(&format_with(|f| {
|
||||
for (i, item) in items.iter().enumerate() {
|
||||
write!(f, [item.format()])?;
|
||||
if i < items.len() - 1 {
|
||||
write!(f, [text(","), soft_line_break_or_space()])?;
|
||||
} else {
|
||||
write!(f, [if_group_breaks(&text(","))])?;
|
||||
}
|
||||
}
|
||||
Ok(())
|
||||
})),
|
||||
if_group_breaks(&text(")")),
|
||||
]),
|
||||
text(":"),
|
||||
block_indent(&block(body))
|
||||
]
|
||||
)
|
||||
}
|
||||
|
||||
pub struct FormatStmt<'a> {
|
||||
item: &'a Stmt,
|
||||
}
|
||||
|
||||
impl Format<ASTFormatContext<'_>> for FormatStmt<'_> {
|
||||
fn fmt(&self, f: &mut Formatter<ASTFormatContext<'_>>) -> FormatResult<()> {
|
||||
// Any leading comments come on the line before.
|
||||
for trivia in &self.item.trivia {
|
||||
if matches!(trivia.relationship, Relationship::Leading) {
|
||||
match trivia.kind {
|
||||
TriviaKind::EmptyLine => {
|
||||
write!(f, [empty_line()])?;
|
||||
}
|
||||
TriviaKind::StandaloneComment(range) => {
|
||||
write!(f, [literal(range), hard_line_break()])?;
|
||||
}
|
||||
_ => {}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
match &self.item.node {
|
||||
StmtKind::Pass => format_pass(f, self.item),
|
||||
StmtKind::Break => format_break(f),
|
||||
StmtKind::Continue => format_continue(f),
|
||||
StmtKind::Global { names } => format_global(f, names),
|
||||
StmtKind::Nonlocal { names } => format_nonlocal(f, names),
|
||||
StmtKind::FunctionDef {
|
||||
name,
|
||||
args,
|
||||
body,
|
||||
decorator_list,
|
||||
returns,
|
||||
..
|
||||
} => format_func_def(
|
||||
f,
|
||||
self.item,
|
||||
name,
|
||||
args,
|
||||
returns.as_deref(),
|
||||
body,
|
||||
decorator_list,
|
||||
false,
|
||||
),
|
||||
StmtKind::AsyncFunctionDef {
|
||||
name,
|
||||
args,
|
||||
body,
|
||||
decorator_list,
|
||||
returns,
|
||||
..
|
||||
} => format_func_def(
|
||||
f,
|
||||
self.item,
|
||||
name,
|
||||
args,
|
||||
returns.as_deref(),
|
||||
body,
|
||||
decorator_list,
|
||||
true,
|
||||
),
|
||||
StmtKind::ClassDef {
|
||||
name,
|
||||
bases,
|
||||
keywords,
|
||||
body,
|
||||
decorator_list,
|
||||
} => format_class_def(f, name, bases, keywords, body, decorator_list),
|
||||
StmtKind::Return { value } => format_return(f, value.as_ref()),
|
||||
StmtKind::Delete { targets } => format_delete(f, targets),
|
||||
StmtKind::Assign { targets, value, .. } => format_assign(f, self.item, targets, value),
|
||||
// StmtKind::AugAssign { .. } => {}
|
||||
StmtKind::AnnAssign {
|
||||
target,
|
||||
annotation,
|
||||
value,
|
||||
simple,
|
||||
} => format_ann_assign(f, self.item, target, annotation, value.as_deref(), *simple),
|
||||
StmtKind::For {
|
||||
target,
|
||||
iter,
|
||||
body,
|
||||
orelse,
|
||||
type_comment,
|
||||
} => format_for(
|
||||
f,
|
||||
self.item,
|
||||
target,
|
||||
iter,
|
||||
body,
|
||||
orelse,
|
||||
type_comment.as_deref(),
|
||||
),
|
||||
// StmtKind::AsyncFor { .. } => {}
|
||||
StmtKind::While { test, body, orelse } => {
|
||||
format_while(f, self.item, test, body, orelse)
|
||||
}
|
||||
StmtKind::If { test, body, orelse } => format_if(f, test, body, orelse),
|
||||
StmtKind::With {
|
||||
items,
|
||||
body,
|
||||
type_comment,
|
||||
} => format_with_(
|
||||
f,
|
||||
self.item,
|
||||
items,
|
||||
body,
|
||||
type_comment.as_ref().map(String::as_str),
|
||||
false,
|
||||
),
|
||||
StmtKind::AsyncWith {
|
||||
items,
|
||||
body,
|
||||
type_comment,
|
||||
} => format_with_(
|
||||
f,
|
||||
self.item,
|
||||
items,
|
||||
body,
|
||||
type_comment.as_ref().map(String::as_str),
|
||||
true,
|
||||
),
|
||||
// StmtKind::Match { .. } => {}
|
||||
StmtKind::Raise { exc, cause } => {
|
||||
format_raise(f, self.item, exc.as_deref(), cause.as_deref())
|
||||
}
|
||||
// StmtKind::Try { .. } => {}
|
||||
StmtKind::Assert { test, msg } => {
|
||||
format_assert(f, self.item, test, msg.as_ref().map(|expr| &**expr))
|
||||
}
|
||||
StmtKind::Import { names } => format_import(f, self.item, names),
|
||||
StmtKind::ImportFrom {
|
||||
module,
|
||||
names,
|
||||
level,
|
||||
} => format_import_from(
|
||||
f,
|
||||
self.item,
|
||||
module.as_ref().map(String::as_str),
|
||||
names,
|
||||
level.as_ref(),
|
||||
),
|
||||
// StmtKind::Nonlocal { .. } => {}
|
||||
StmtKind::Expr { value } => format_expr(f, self.item, value),
|
||||
_ => {
|
||||
unimplemented!("Implement StmtKind: {:?}", self.item.node)
|
||||
}
|
||||
}?;
|
||||
|
||||
// Any trailing comments come on the lines after.
|
||||
for trivia in &self.item.trivia {
|
||||
if matches!(trivia.relationship, Relationship::Trailing) {
|
||||
match trivia.kind {
|
||||
TriviaKind::EmptyLine => {
|
||||
write!(f, [empty_line()])?;
|
||||
}
|
||||
TriviaKind::StandaloneComment(range) => {
|
||||
write!(f, [literal(range), hard_line_break()])?;
|
||||
}
|
||||
_ => {}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
Ok(())
|
||||
}
|
||||
}
|
||||
|
||||
impl AsFormat<ASTFormatContext<'_>> for Stmt {
|
||||
type Format<'a> = FormatStmt<'a>;
|
||||
|
||||
fn format(&self) -> Self::Format<'_> {
|
||||
FormatStmt { item: self }
|
||||
}
|
||||
}
|
37
crates/ruff_python_formatter/src/format/unaryop.rs
Normal file
37
crates/ruff_python_formatter/src/format/unaryop.rs
Normal file
|
@ -0,0 +1,37 @@
|
|||
use ruff_formatter::prelude::*;
|
||||
use ruff_formatter::write;
|
||||
|
||||
use crate::context::ASTFormatContext;
|
||||
use crate::cst::Unaryop;
|
||||
use crate::shared_traits::AsFormat;
|
||||
|
||||
pub struct FormatUnaryop<'a> {
|
||||
item: &'a Unaryop,
|
||||
}
|
||||
|
||||
impl AsFormat<ASTFormatContext<'_>> for Unaryop {
|
||||
type Format<'a> = FormatUnaryop<'a>;
|
||||
|
||||
fn format(&self) -> Self::Format<'_> {
|
||||
FormatUnaryop { item: self }
|
||||
}
|
||||
}
|
||||
|
||||
impl Format<ASTFormatContext<'_>> for FormatUnaryop<'_> {
|
||||
fn fmt(&self, f: &mut Formatter<ASTFormatContext>) -> FormatResult<()> {
|
||||
let unaryop = self.item;
|
||||
write!(
|
||||
f,
|
||||
[
|
||||
text(match unaryop {
|
||||
Unaryop::Invert => "~",
|
||||
Unaryop::Not => "not",
|
||||
Unaryop::UAdd => "+",
|
||||
Unaryop::USub => "-",
|
||||
}),
|
||||
matches!(unaryop, Unaryop::Not).then_some(space())
|
||||
]
|
||||
)?;
|
||||
Ok(())
|
||||
}
|
||||
}
|
32
crates/ruff_python_formatter/src/format/withitem.rs
Normal file
32
crates/ruff_python_formatter/src/format/withitem.rs
Normal file
|
@ -0,0 +1,32 @@
|
|||
use ruff_formatter::prelude::*;
|
||||
use ruff_formatter::write;
|
||||
|
||||
use crate::context::ASTFormatContext;
|
||||
use crate::cst::Withitem;
|
||||
use crate::shared_traits::AsFormat;
|
||||
|
||||
pub struct FormatWithitem<'a> {
|
||||
item: &'a Withitem,
|
||||
}
|
||||
|
||||
impl AsFormat<ASTFormatContext<'_>> for Withitem {
|
||||
type Format<'a> = FormatWithitem<'a>;
|
||||
|
||||
fn format(&self) -> Self::Format<'_> {
|
||||
FormatWithitem { item: self }
|
||||
}
|
||||
}
|
||||
|
||||
impl Format<ASTFormatContext<'_>> for FormatWithitem<'_> {
|
||||
fn fmt(&self, f: &mut Formatter<ASTFormatContext>) -> FormatResult<()> {
|
||||
let withitem = self.item;
|
||||
|
||||
write!(f, [withitem.context_expr.format()])?;
|
||||
if let Some(optional_vars) = &withitem.optional_vars {
|
||||
write!(f, [space(), text("as"), space()])?;
|
||||
write!(f, [optional_vars.format()])?;
|
||||
}
|
||||
|
||||
Ok(())
|
||||
}
|
||||
}
|
Loading…
Add table
Add a link
Reference in a new issue