Skip to content

Commit

Permalink
Fluent style
Browse files Browse the repository at this point in the history
  • Loading branch information
konstin committed Jul 28, 2023
1 parent 2a65e6f commit 62c5f74
Show file tree
Hide file tree
Showing 12 changed files with 473 additions and 483 deletions.
Original file line number Diff line number Diff line change
@@ -0,0 +1,74 @@
# Test cases for call chains and optional parentheses
raise OsError("") from a.aaaaa(
aksjdhflsakhdflkjsadlfajkslhfdkjsaldajlahflashdfljahlfksajlhfajfjfsaahflakjslhdfkjalhdskjfa
).a(aaaa)

raise OsError(
"sökdjffffsldkfjlhsakfjhalsökafhsöfdahsödfjösaaksjdllllllllllllll"
) from a.aaaaa(
aksjdhflsakhdflkjsadlfajkslhfdkjsaldajlahflashdfljahlfksajlhfajfjfsaahflakjslhdfkjalhdskjfa
).a(
aaaa
)

blogs1 = Blog.objects.filter(entry__headline__contains="Lennon").filter(
entry__pub_date__year=2008
)

blogs2 = Blog.objects.filter(
entry__headline__contains="Lennon",
).filter(
entry__pub_date__year=2008,
)

raise OsError("") from (
Blog.objects.filter(
entry__headline__contains="Lennon",
)
.filter(
entry__pub_date__year=2008,
)
.filter(
entry__pub_date__year=2008,
)
)

raise OsError("sökdjffffsldkfjlhsakfjhalsökafhsöfdahsödfjösaaksjdllllllllllllll") from (
Blog.objects.filter(
entry__headline__contains="Lennon",
)
.filter(
entry__pub_date__year=2008,
)
.filter(
entry__pub_date__year=2008,
)
)

# Break only after calls and indexing
result = (
session.query(models.Customer.id)
.filter(
models.Customer.account_id == account_id, models.Customer.email == email_address
)
.count()
)

# Currently formatted wrongly (no nested call chains)
raise (
Blog.objects.filter(
entry__headline__contains="Lennon",
).filter(
entry__pub_date__year=2008,
)
+ Blog.objects.filter(
entry__headline__contains="Lennon",
)
.filter(
entry__pub_date__year=2008,
)
.filter(
entry__pub_date__year=2008,
)
).a()

Original file line number Diff line number Diff line change
Expand Up @@ -52,7 +52,7 @@ def f(*args, **kwargs):
hey_this_is_a_very_long_call=1, it_has_funny_attributes_asdf_asdf=1, too_long_for_the_line=1, really=True
)

# TODO(konstin): Call chains/fluent interface (https://black.readthedocs.io/en/stable/the_black_code_style/current_style.html#call-chains)
# Call chains/fluent interface (https://black.readthedocs.io/en/stable/the_black_code_style/current_style.html#call-chains)
result = (
session.query(models.Customer.id)
.filter(
Expand Down
94 changes: 75 additions & 19 deletions crates/ruff_python_formatter/src/expression/expr_attribute.rs
Original file line number Diff line number Diff line change
@@ -1,15 +1,25 @@
use ruff_python_ast::{Constant, Expr, ExprAttribute, ExprConstant};

use ruff_formatter::write;
use ruff_formatter::{write, FormatRuleWithOptions};
use ruff_python_ast::node::AnyNodeRef;
use ruff_python_ast::{Constant, Expr, ExprAttribute, ExprConstant};

use crate::comments::{leading_comments, trailing_comments};
use crate::expression::parentheses::{NeedsParentheses, OptionalParentheses, Parentheses};
use crate::prelude::*;
use crate::FormatNodeRule;

#[derive(Default)]
pub struct FormatExprAttribute;
pub struct FormatExprAttribute {
parentheses: Option<Parentheses>,
}

impl FormatRuleWithOptions<ExprAttribute, PyFormatContext<'_>> for FormatExprAttribute {
type Options = Option<Parentheses>;

fn with_options(mut self, options: Self::Options) -> Self {
self.parentheses = options;
self
}
}

impl FormatNodeRule<ExprAttribute> for FormatExprAttribute {
fn fmt_fields(&self, item: &ExprAttribute, f: &mut PyFormatter) -> FormatResult<()> {
Expand Down Expand Up @@ -37,11 +47,22 @@ impl FormatNodeRule<ExprAttribute> for FormatExprAttribute {

if needs_parentheses {
value.format().with_options(Parentheses::Always).fmt(f)?;
} else if let Expr::Attribute(expr_attribute) = value.as_ref() {
// We're in a attribute chain (`a.b.c`). The outermost node adds parentheses if
// required, the inner ones don't need them so we skip the `Expr` formatting that
// normally adds the parentheses.
expr_attribute.format().fmt(f)?;
} else if self.parentheses == Some(Parentheses::FluentStyle) {
// Fluent style: We need to pass the parenthese on to inner attributes or call chains
match value.as_ref() {
Expr::Attribute(_) => value
.format()
.with_options(Parentheses::FluentStyle)
.fmt(f)?,
Expr::Call(_) | Expr::Subscript(_) => {
value
.format()
.with_options(Parentheses::FluentStyle)
.fmt(f)?;
soft_line_break().fmt(f)?
}
_ => value.format().fmt(f)?,
}
} else {
value.format().fmt(f)?;
}
Expand All @@ -50,16 +71,51 @@ impl FormatNodeRule<ExprAttribute> for FormatExprAttribute {
hard_line_break().fmt(f)?;
}

write!(
f,
[
text("."),
trailing_comments(trailing_dot_comments),
(!leading_attribute_comments.is_empty()).then_some(hard_line_break()),
leading_comments(leading_attribute_comments),
attr.format()
]
)
if self.parentheses == Some(Parentheses::FluentStyle) {
// Fluent style has line breaks before the dot
// ```python
// blogs3 = (
// Blog.objects.filter(
// entry__headline__contains="Lennon",
// )
// .filter(
// entry__pub_date__year=2008,
// )
// .filter(
// entry__pub_date__year=2008,
// )
// )
// ```
write!(
f,
[
(!leading_attribute_comments.is_empty()).then_some(hard_line_break()),
leading_comments(leading_attribute_comments),
text("."),
trailing_comments(trailing_dot_comments),
attr.format()
]
)
} else {
// Regular style
// ```python
// blogs2 = Blog.objects.filter(
// entry__headline__contains="Lennon",
// ).filter(
// entry__pub_date__year=2008,
// )
// ```
write!(
f,
[
text("."),
trailing_comments(trailing_dot_comments),
(!leading_attribute_comments.is_empty()).then_some(hard_line_break()),
leading_comments(leading_attribute_comments),
attr.format()
]
)
}
}

fn fmt_dangling_comments(
Expand Down
48 changes: 33 additions & 15 deletions crates/ruff_python_formatter/src/expression/expr_call.rs
Original file line number Diff line number Diff line change
@@ -1,11 +1,10 @@
use ruff_formatter::{write, FormatRuleWithOptions};
use ruff_python_ast::node::AnyNodeRef;
use ruff_python_ast::{Expr, ExprCall, Ranged};
use ruff_python_trivia::{SimpleTokenKind, SimpleTokenizer};
use ruff_text_size::{TextRange, TextSize};

use crate::builders::empty_parenthesized_with_dangling_comments;
use ruff_formatter::write;
use ruff_python_ast::node::AnyNodeRef;
use ruff_python_trivia::{SimpleTokenKind, SimpleTokenizer};

use crate::expression::expr_generator_exp::GeneratorExpParentheses;
use crate::expression::parentheses::{
parenthesized, NeedsParentheses, OptionalParentheses, Parentheses,
Expand All @@ -14,7 +13,18 @@ use crate::prelude::*;
use crate::FormatNodeRule;

#[derive(Default)]
pub struct FormatExprCall;
pub struct FormatExprCall {
parentheses: Option<Parentheses>,
}

impl FormatRuleWithOptions<ExprCall, PyFormatContext<'_>> for FormatExprCall {
type Options = Option<Parentheses>;

fn with_options(mut self, options: Self::Options) -> Self {
self.parentheses = options;
self
}
}

impl FormatNodeRule<ExprCall> for FormatExprCall {
fn fmt_fields(&self, item: &ExprCall, f: &mut PyFormatter) -> FormatResult<()> {
Expand All @@ -25,6 +35,19 @@ impl FormatNodeRule<ExprCall> for FormatExprCall {
keywords,
} = item;

if self.parentheses == Some(Parentheses::FluentStyle) {
// Fluent style: We need to pass the parenthese on to inner attributes or call chains
match func.as_ref() {
Expr::Attribute(_) | Expr::Call(_) | Expr::Subscript(_) => func
.format()
.with_options(Parentheses::FluentStyle)
.fmt(f)?,
_ => func.format().fmt(f)?,
}
} else {
func.format().fmt(f)?;
}

// We have a case with `f()` without any argument, which is a special case because we can
// have a comment with no node attachment inside:
// ```python
Expand All @@ -36,14 +59,11 @@ impl FormatNodeRule<ExprCall> for FormatExprCall {
let comments = f.context().comments().clone();
return write!(
f,
[
func.format(),
empty_parenthesized_with_dangling_comments(
text("("),
comments.dangling_comments(item),
text(")"),
)
]
[empty_parenthesized_with_dangling_comments(
text("("),
comments.dangling_comments(item),
text(")"),
)]
);
}

Expand Down Expand Up @@ -88,7 +108,6 @@ impl FormatNodeRule<ExprCall> for FormatExprCall {
write!(
f,
[
func.format(),
// The outer group is for things like
// ```python
// get_collection(
Expand All @@ -104,7 +123,6 @@ impl FormatNodeRule<ExprCall> for FormatExprCall {
// hey_this_is_a_very_long_call, it_has_funny_attributes_asdf_asdf, really=True
// )
// ```
// TODO(konstin): Doesn't work see wrongly formatted test
parenthesized("(", &group(&all_args), ")")
]
)
Expand Down
Loading

0 comments on commit 62c5f74

Please sign in to comment.