Implement fluent style/call chains. See the `call_chains.py` formatting for examples. This isn't fully like black because in `raise A from B` they allow `A` breaking can influence the formatting of `B` even if it is already multiline. Similarity index: | project | main | PR | |--------------|-------|-------| | build | ??? | 0.753 | | django | 0.991 | 0.998 | | transformers | 0.993 | 0.994 | | typeshed | 0.723 | 0.723 | | warehouse | 0.978 | 0.994 | | zulip | 0.992 | 0.994 | Call chain formatting is affected by https://github.com/astral-sh/ruff/issues/627, but i'm cutting scope here. Closes #5343 **Test Plan**: * Added a dedicated call chains test file * The ecosystem checks found some bugs * I manually check django and zulip formatting --------- Co-authored-by: Micha Reiser <micha@reiser.io>
76 lines
2.2 KiB
Rust
76 lines
2.2 KiB
Rust
use crate::expression::CallChainLayout;
|
|
use ruff_formatter::FormatRuleWithOptions;
|
|
use ruff_python_ast::node::AnyNodeRef;
|
|
use ruff_python_ast::{Expr, ExprCall};
|
|
|
|
use crate::expression::parentheses::{NeedsParentheses, OptionalParentheses};
|
|
use crate::prelude::*;
|
|
use crate::FormatNodeRule;
|
|
|
|
#[derive(Default)]
|
|
pub struct FormatExprCall {
|
|
call_chain_layout: CallChainLayout,
|
|
}
|
|
|
|
impl FormatRuleWithOptions<ExprCall, PyFormatContext<'_>> for FormatExprCall {
|
|
type Options = CallChainLayout;
|
|
|
|
fn with_options(mut self, options: Self::Options) -> Self {
|
|
self.call_chain_layout = options;
|
|
self
|
|
}
|
|
}
|
|
|
|
impl FormatNodeRule<ExprCall> for FormatExprCall {
|
|
fn fmt_fields(&self, item: &ExprCall, f: &mut PyFormatter) -> FormatResult<()> {
|
|
let ExprCall {
|
|
range: _,
|
|
func,
|
|
arguments,
|
|
} = item;
|
|
|
|
let call_chain_layout = self.call_chain_layout.apply_in_node(item, f);
|
|
|
|
let fmt_inner = format_with(|f| {
|
|
match func.as_ref() {
|
|
Expr::Attribute(expr) => expr.format().with_options(call_chain_layout).fmt(f)?,
|
|
Expr::Call(expr) => expr.format().with_options(call_chain_layout).fmt(f)?,
|
|
Expr::Subscript(expr) => expr.format().with_options(call_chain_layout).fmt(f)?,
|
|
_ => func.format().fmt(f)?,
|
|
}
|
|
|
|
arguments.format().fmt(f)
|
|
});
|
|
|
|
// Allow to indent the parentheses while
|
|
// ```python
|
|
// g1 = (
|
|
// queryset.distinct().order_by(field.name).values_list(field_name_flat_long_long=True)
|
|
// )
|
|
// ```
|
|
if call_chain_layout == CallChainLayout::Fluent
|
|
&& self.call_chain_layout == CallChainLayout::Default
|
|
{
|
|
group(&fmt_inner).fmt(f)
|
|
} else {
|
|
fmt_inner.fmt(f)
|
|
}
|
|
}
|
|
}
|
|
|
|
impl NeedsParentheses for ExprCall {
|
|
fn needs_parentheses(
|
|
&self,
|
|
_parent: AnyNodeRef,
|
|
context: &PyFormatContext,
|
|
) -> OptionalParentheses {
|
|
if CallChainLayout::from_expression(self.into(), context.source())
|
|
== CallChainLayout::Fluent
|
|
{
|
|
OptionalParentheses::Multiline
|
|
} else {
|
|
self.func.needs_parentheses(self.into(), context)
|
|
}
|
|
}
|
|
}
|