mirror of https://github.com/astral-sh/ruff
implement preview layout
This commit is contained in:
parent
73a6f47a0d
commit
6cc18dd6aa
|
|
@ -10,6 +10,7 @@ use crate::expression::parentheses::{
|
||||||
NeedsParentheses, OptionalParentheses, Parentheses, is_expression_parenthesized,
|
NeedsParentheses, OptionalParentheses, Parentheses, is_expression_parenthesized,
|
||||||
};
|
};
|
||||||
use crate::prelude::*;
|
use crate::prelude::*;
|
||||||
|
use crate::preview::is_fluent_layout_split_first_call_enabled;
|
||||||
|
|
||||||
#[derive(Default)]
|
#[derive(Default)]
|
||||||
pub struct FormatExprAttribute {
|
pub struct FormatExprAttribute {
|
||||||
|
|
@ -47,20 +48,26 @@ impl FormatNodeRule<ExprAttribute> for FormatExprAttribute {
|
||||||
)
|
)
|
||||||
};
|
};
|
||||||
|
|
||||||
if call_chain_layout == CallChainLayout::Fluent {
|
if matches!(call_chain_layout, CallChainLayout::Fluent(_)) {
|
||||||
if parenthesize_value {
|
if parenthesize_value {
|
||||||
// Don't propagate the call chain layout.
|
// Don't propagate the call chain layout.
|
||||||
value.format().with_options(Parentheses::Always).fmt(f)?;
|
value.format().with_options(Parentheses::Always).fmt(f)?;
|
||||||
} else {
|
} else {
|
||||||
match value.as_ref() {
|
match value.as_ref() {
|
||||||
Expr::Attribute(expr) => {
|
Expr::Attribute(expr) => {
|
||||||
expr.format().with_options(call_chain_layout).fmt(f)?;
|
expr.format()
|
||||||
|
.with_options(call_chain_layout.after_attribute())
|
||||||
|
.fmt(f)?;
|
||||||
}
|
}
|
||||||
Expr::Call(expr) => {
|
Expr::Call(expr) => {
|
||||||
expr.format().with_options(call_chain_layout).fmt(f)?;
|
expr.format()
|
||||||
|
.with_options(call_chain_layout.after_attribute())
|
||||||
|
.fmt(f)?;
|
||||||
}
|
}
|
||||||
Expr::Subscript(expr) => {
|
Expr::Subscript(expr) => {
|
||||||
expr.format().with_options(call_chain_layout).fmt(f)?;
|
expr.format()
|
||||||
|
.with_options(call_chain_layout.after_attribute())
|
||||||
|
.fmt(f)?;
|
||||||
}
|
}
|
||||||
_ => {
|
_ => {
|
||||||
value.format().with_options(Parentheses::Never).fmt(f)?;
|
value.format().with_options(Parentheses::Never).fmt(f)?;
|
||||||
|
|
@ -105,8 +112,30 @@ impl FormatNodeRule<ExprAttribute> for FormatExprAttribute {
|
||||||
// Allow the `.` on its own line if this is a fluent call chain
|
// Allow the `.` on its own line if this is a fluent call chain
|
||||||
// and the value either requires parenthesizing or is a call or subscript expression
|
// and the value either requires parenthesizing or is a call or subscript expression
|
||||||
// (it's a fluent chain but not the first element).
|
// (it's a fluent chain but not the first element).
|
||||||
else if call_chain_layout == CallChainLayout::Fluent {
|
//
|
||||||
if parenthesize_value || value.is_call_expr() || value.is_subscript_expr() {
|
// In preview we also break _at_ the first call in the chain.
|
||||||
|
// For example:
|
||||||
|
//
|
||||||
|
// ```diff
|
||||||
|
// # stable formatting vs. preview
|
||||||
|
// x = (
|
||||||
|
// - df.merge()
|
||||||
|
// + df
|
||||||
|
// + .merge()
|
||||||
|
// .groupby()
|
||||||
|
// .agg()
|
||||||
|
// .filter()
|
||||||
|
// )
|
||||||
|
// ```
|
||||||
|
else if matches!(call_chain_layout, CallChainLayout::Fluent(_)) {
|
||||||
|
if parenthesize_value
|
||||||
|
|| value.is_call_expr()
|
||||||
|
|| value.is_subscript_expr()
|
||||||
|
// Remember to update the doc-comment above when
|
||||||
|
// stabilizing this behavior.
|
||||||
|
|| (is_fluent_layout_split_first_call_enabled(f.context())
|
||||||
|
&& call_chain_layout.is_first_call_like())
|
||||||
|
{
|
||||||
soft_line_break().fmt(f)?;
|
soft_line_break().fmt(f)?;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
@ -149,7 +178,7 @@ impl FormatNodeRule<ExprAttribute> for FormatExprAttribute {
|
||||||
});
|
});
|
||||||
|
|
||||||
let is_call_chain_root = self.call_chain_layout == CallChainLayout::Default
|
let is_call_chain_root = self.call_chain_layout == CallChainLayout::Default
|
||||||
&& call_chain_layout == CallChainLayout::Fluent;
|
&& matches!(call_chain_layout, CallChainLayout::Fluent(_));
|
||||||
if is_call_chain_root {
|
if is_call_chain_root {
|
||||||
write!(f, [group(&format_inner)])
|
write!(f, [group(&format_inner)])
|
||||||
} else {
|
} else {
|
||||||
|
|
@ -165,12 +194,15 @@ impl NeedsParentheses for ExprAttribute {
|
||||||
context: &PyFormatContext,
|
context: &PyFormatContext,
|
||||||
) -> OptionalParentheses {
|
) -> OptionalParentheses {
|
||||||
// Checks if there are any own line comments in an attribute chain (a.b.c).
|
// Checks if there are any own line comments in an attribute chain (a.b.c).
|
||||||
if CallChainLayout::from_expression(
|
if matches!(
|
||||||
self.into(),
|
CallChainLayout::from_expression(
|
||||||
context.comments().ranges(),
|
self.into(),
|
||||||
context.source(),
|
context.comments().ranges(),
|
||||||
) == CallChainLayout::Fluent
|
context.source(),
|
||||||
{
|
context
|
||||||
|
),
|
||||||
|
CallChainLayout::Fluent(_)
|
||||||
|
) {
|
||||||
OptionalParentheses::Multiline
|
OptionalParentheses::Multiline
|
||||||
} else if context.comments().has_dangling(self) {
|
} else if context.comments().has_dangling(self) {
|
||||||
OptionalParentheses::Always
|
OptionalParentheses::Always
|
||||||
|
|
|
||||||
|
|
@ -47,7 +47,10 @@ impl FormatNodeRule<ExprCall> for FormatExprCall {
|
||||||
func.format().with_options(Parentheses::Always).fmt(f)
|
func.format().with_options(Parentheses::Always).fmt(f)
|
||||||
} else {
|
} else {
|
||||||
match func.as_ref() {
|
match func.as_ref() {
|
||||||
Expr::Attribute(expr) => expr.format().with_options(call_chain_layout).fmt(f),
|
Expr::Attribute(expr) => expr
|
||||||
|
.format()
|
||||||
|
.with_options(call_chain_layout.call_like_attribute())
|
||||||
|
.fmt(f),
|
||||||
Expr::Call(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),
|
Expr::Subscript(expr) => expr.format().with_options(call_chain_layout).fmt(f),
|
||||||
_ => func.format().with_options(Parentheses::Never).fmt(f),
|
_ => func.format().with_options(Parentheses::Never).fmt(f),
|
||||||
|
|
@ -67,7 +70,7 @@ impl FormatNodeRule<ExprCall> for FormatExprCall {
|
||||||
// queryset.distinct().order_by(field.name).values_list(field_name_flat_long_long=True)
|
// queryset.distinct().order_by(field.name).values_list(field_name_flat_long_long=True)
|
||||||
// )
|
// )
|
||||||
// ```
|
// ```
|
||||||
if call_chain_layout == CallChainLayout::Fluent
|
if matches!(call_chain_layout, CallChainLayout::Fluent(_))
|
||||||
&& self.call_chain_layout == CallChainLayout::Default
|
&& self.call_chain_layout == CallChainLayout::Default
|
||||||
{
|
{
|
||||||
group(&fmt_func).fmt(f)
|
group(&fmt_func).fmt(f)
|
||||||
|
|
@ -83,12 +86,15 @@ impl NeedsParentheses for ExprCall {
|
||||||
_parent: AnyNodeRef,
|
_parent: AnyNodeRef,
|
||||||
context: &PyFormatContext,
|
context: &PyFormatContext,
|
||||||
) -> OptionalParentheses {
|
) -> OptionalParentheses {
|
||||||
if CallChainLayout::from_expression(
|
if matches!(
|
||||||
self.into(),
|
CallChainLayout::from_expression(
|
||||||
context.comments().ranges(),
|
self.into(),
|
||||||
context.source(),
|
context.comments().ranges(),
|
||||||
) == CallChainLayout::Fluent
|
context.source(),
|
||||||
{
|
context
|
||||||
|
),
|
||||||
|
CallChainLayout::Fluent(_)
|
||||||
|
) {
|
||||||
OptionalParentheses::Multiline
|
OptionalParentheses::Multiline
|
||||||
} else if context.comments().has_dangling(self) {
|
} else if context.comments().has_dangling(self) {
|
||||||
OptionalParentheses::Always
|
OptionalParentheses::Always
|
||||||
|
|
|
||||||
|
|
@ -51,7 +51,10 @@ impl FormatNodeRule<ExprSubscript> for FormatExprSubscript {
|
||||||
value.format().with_options(Parentheses::Always).fmt(f)
|
value.format().with_options(Parentheses::Always).fmt(f)
|
||||||
} else {
|
} else {
|
||||||
match value.as_ref() {
|
match value.as_ref() {
|
||||||
Expr::Attribute(expr) => expr.format().with_options(call_chain_layout).fmt(f),
|
Expr::Attribute(expr) => expr
|
||||||
|
.format()
|
||||||
|
.with_options(call_chain_layout.call_like_attribute())
|
||||||
|
.fmt(f),
|
||||||
Expr::Call(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),
|
Expr::Subscript(expr) => expr.format().with_options(call_chain_layout).fmt(f),
|
||||||
_ => value.format().with_options(Parentheses::Never).fmt(f),
|
_ => value.format().with_options(Parentheses::Never).fmt(f),
|
||||||
|
|
@ -72,7 +75,7 @@ impl FormatNodeRule<ExprSubscript> for FormatExprSubscript {
|
||||||
});
|
});
|
||||||
|
|
||||||
let is_call_chain_root = self.call_chain_layout == CallChainLayout::Default
|
let is_call_chain_root = self.call_chain_layout == CallChainLayout::Default
|
||||||
&& call_chain_layout == CallChainLayout::Fluent;
|
&& matches!(call_chain_layout, CallChainLayout::Fluent(_));
|
||||||
if is_call_chain_root {
|
if is_call_chain_root {
|
||||||
write!(f, [group(&format_inner)])
|
write!(f, [group(&format_inner)])
|
||||||
} else {
|
} else {
|
||||||
|
|
@ -88,12 +91,15 @@ impl NeedsParentheses for ExprSubscript {
|
||||||
context: &PyFormatContext,
|
context: &PyFormatContext,
|
||||||
) -> OptionalParentheses {
|
) -> OptionalParentheses {
|
||||||
{
|
{
|
||||||
if CallChainLayout::from_expression(
|
if matches!(
|
||||||
self.into(),
|
CallChainLayout::from_expression(
|
||||||
context.comments().ranges(),
|
self.into(),
|
||||||
context.source(),
|
context.comments().ranges(),
|
||||||
) == CallChainLayout::Fluent
|
context.source(),
|
||||||
{
|
context
|
||||||
|
),
|
||||||
|
CallChainLayout::Fluent(_)
|
||||||
|
) {
|
||||||
OptionalParentheses::Multiline
|
OptionalParentheses::Multiline
|
||||||
} else if is_expression_parenthesized(
|
} else if is_expression_parenthesized(
|
||||||
self.value.as_ref().into(),
|
self.value.as_ref().into(),
|
||||||
|
|
|
||||||
|
|
@ -19,7 +19,9 @@ use crate::expression::parentheses::{
|
||||||
optional_parentheses, parenthesized,
|
optional_parentheses, parenthesized,
|
||||||
};
|
};
|
||||||
use crate::prelude::*;
|
use crate::prelude::*;
|
||||||
use crate::preview::is_hug_parens_with_braces_and_square_brackets_enabled;
|
use crate::preview::{
|
||||||
|
is_fluent_layout_more_often_enabled, is_hug_parens_with_braces_and_square_brackets_enabled,
|
||||||
|
};
|
||||||
|
|
||||||
mod binary_like;
|
mod binary_like;
|
||||||
pub(crate) mod expr_attribute;
|
pub(crate) mod expr_attribute;
|
||||||
|
|
@ -883,21 +885,62 @@ pub enum CallChainLayout {
|
||||||
Default,
|
Default,
|
||||||
|
|
||||||
/// A nested call chain element that uses fluent style.
|
/// A nested call chain element that uses fluent style.
|
||||||
Fluent,
|
Fluent(AttributeState),
|
||||||
|
|
||||||
/// A nested call chain element not using fluent style.
|
/// A nested call chain element not using fluent style.
|
||||||
NonFluent,
|
NonFluent,
|
||||||
}
|
}
|
||||||
|
|
||||||
|
#[derive(Debug, Clone, Copy, PartialEq, Eq)]
|
||||||
|
pub enum AttributeState {
|
||||||
|
CallsOrSubscriptsPreceding(u32),
|
||||||
|
FirstCallOrSubscript,
|
||||||
|
BeforeFirstCallOrSubscript,
|
||||||
|
}
|
||||||
|
|
||||||
impl CallChainLayout {
|
impl CallChainLayout {
|
||||||
|
pub(crate) fn call_like_attribute(self) -> Self {
|
||||||
|
match self {
|
||||||
|
Self::Fluent(AttributeState::CallsOrSubscriptsPreceding(x)) => {
|
||||||
|
if x > 1 {
|
||||||
|
Self::Fluent(AttributeState::CallsOrSubscriptsPreceding(x - 1))
|
||||||
|
} else {
|
||||||
|
Self::Fluent(AttributeState::FirstCallOrSubscript)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
_ => self,
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
pub(crate) fn after_attribute(self) -> Self {
|
||||||
|
match self {
|
||||||
|
Self::Fluent(AttributeState::FirstCallOrSubscript) => {
|
||||||
|
Self::Fluent(AttributeState::BeforeFirstCallOrSubscript)
|
||||||
|
}
|
||||||
|
_ => self,
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
pub(crate) fn is_first_call_like(self) -> bool {
|
||||||
|
matches!(self, Self::Fluent(AttributeState::FirstCallOrSubscript))
|
||||||
|
}
|
||||||
|
|
||||||
pub(crate) fn from_expression(
|
pub(crate) fn from_expression(
|
||||||
mut expr: ExprRef,
|
mut expr: ExprRef,
|
||||||
comment_ranges: &CommentRanges,
|
comment_ranges: &CommentRanges,
|
||||||
source: &str,
|
source: &str,
|
||||||
|
// This can be deleted once the preview style
|
||||||
|
// is stabilized
|
||||||
|
context: &PyFormatContext,
|
||||||
) -> Self {
|
) -> Self {
|
||||||
|
let mut call_like_count = 0;
|
||||||
|
let mut was_in_call_like = false;
|
||||||
let mut first_attr_value_parenthesized = false;
|
let mut first_attr_value_parenthesized = false;
|
||||||
|
|
||||||
|
// We can delete this and its uses below once
|
||||||
|
// the preview style [] is stabilized.
|
||||||
let mut attributes_after_parentheses = 0;
|
let mut attributes_after_parentheses = 0;
|
||||||
|
|
||||||
loop {
|
loop {
|
||||||
match expr {
|
match expr {
|
||||||
ExprRef::Attribute(ast::ExprAttribute { value, .. }) => {
|
ExprRef::Attribute(ast::ExprAttribute { value, .. }) => {
|
||||||
|
|
@ -907,6 +950,7 @@ impl CallChainLayout {
|
||||||
// data[:100].T
|
// data[:100].T
|
||||||
// ^^^^^^^^^^ value
|
// ^^^^^^^^^^ value
|
||||||
// ```
|
// ```
|
||||||
|
call_like_count += u32::from(was_in_call_like);
|
||||||
if is_expression_parenthesized(value.into(), comment_ranges, source) {
|
if is_expression_parenthesized(value.into(), comment_ranges, source) {
|
||||||
// `(a).b`. We preserve these parentheses so don't recurse
|
// `(a).b`. We preserve these parentheses so don't recurse
|
||||||
first_attr_value_parenthesized = true;
|
first_attr_value_parenthesized = true;
|
||||||
|
|
@ -914,7 +958,7 @@ impl CallChainLayout {
|
||||||
} else if matches!(value.as_ref(), Expr::Call(_) | Expr::Subscript(_)) {
|
} else if matches!(value.as_ref(), Expr::Call(_) | Expr::Subscript(_)) {
|
||||||
attributes_after_parentheses += 1;
|
attributes_after_parentheses += 1;
|
||||||
}
|
}
|
||||||
|
was_in_call_like = false;
|
||||||
expr = ExprRef::from(value.as_ref());
|
expr = ExprRef::from(value.as_ref());
|
||||||
}
|
}
|
||||||
// ```
|
// ```
|
||||||
|
|
@ -934,9 +978,16 @@ impl CallChainLayout {
|
||||||
break;
|
break;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
was_in_call_like = true;
|
||||||
expr = ExprRef::from(inner.as_ref());
|
expr = ExprRef::from(inner.as_ref());
|
||||||
}
|
}
|
||||||
_ => {
|
_ => {
|
||||||
|
// We count the first call in the chain even
|
||||||
|
// if it is not part of an attribute, e.g.
|
||||||
|
//
|
||||||
|
// f().g()
|
||||||
|
// ^^^ count this
|
||||||
|
call_like_count += u32::from(was_in_call_like);
|
||||||
break;
|
break;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
@ -945,7 +996,7 @@ impl CallChainLayout {
|
||||||
if attributes_after_parentheses + u32::from(first_attr_value_parenthesized) < 2 {
|
if attributes_after_parentheses + u32::from(first_attr_value_parenthesized) < 2 {
|
||||||
CallChainLayout::NonFluent
|
CallChainLayout::NonFluent
|
||||||
} else {
|
} else {
|
||||||
CallChainLayout::Fluent
|
CallChainLayout::Fluent(AttributeState::CallsOrSubscriptsPreceding(call_like_count))
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
@ -963,12 +1014,13 @@ impl CallChainLayout {
|
||||||
item.into(),
|
item.into(),
|
||||||
f.context().comments().ranges(),
|
f.context().comments().ranges(),
|
||||||
f.context().source(),
|
f.context().source(),
|
||||||
|
f.context(),
|
||||||
)
|
)
|
||||||
} else {
|
} else {
|
||||||
CallChainLayout::NonFluent
|
CallChainLayout::NonFluent
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
layout @ (CallChainLayout::Fluent | CallChainLayout::NonFluent) => layout,
|
layout @ (CallChainLayout::Fluent(_) | CallChainLayout::NonFluent) => layout,
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
|
||||||
|
|
@ -59,3 +59,17 @@ pub(crate) const fn is_avoid_parens_for_long_as_captures_enabled(
|
||||||
pub(crate) const fn is_parenthesize_lambda_bodies_enabled(context: &PyFormatContext) -> bool {
|
pub(crate) const fn is_parenthesize_lambda_bodies_enabled(context: &PyFormatContext) -> bool {
|
||||||
context.is_preview()
|
context.is_preview()
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/// Returns `true` if the
|
||||||
|
/// [`fluent_layout_split_first_call`]() preview
|
||||||
|
/// style is enabled.
|
||||||
|
pub(crate) const fn is_fluent_layout_split_first_call_enabled(context: &PyFormatContext) -> bool {
|
||||||
|
context.is_preview()
|
||||||
|
}
|
||||||
|
|
||||||
|
/// Returns `true` if the
|
||||||
|
/// [`fluent_layout_more_often`]() preview
|
||||||
|
/// style is enabled.
|
||||||
|
pub(crate) const fn is_fluent_layout_more_often_enabled(context: &PyFormatContext) -> bool {
|
||||||
|
context.is_preview()
|
||||||
|
}
|
||||||
|
|
|
||||||
Loading…
Reference in New Issue