1
- use ruff_python_ast:: { Constant , Expr , ExprAttribute , ExprConstant } ;
2
-
3
- use ruff_formatter:: write;
1
+ use ruff_formatter:: { write, FormatRuleWithOptions } ;
4
2
use ruff_python_ast:: node:: AnyNodeRef ;
3
+ use ruff_python_ast:: { Constant , Expr , ExprAttribute , ExprConstant } ;
5
4
6
5
use crate :: comments:: { leading_comments, trailing_comments} ;
7
6
use crate :: expression:: parentheses:: { NeedsParentheses , OptionalParentheses , Parentheses } ;
8
7
use crate :: prelude:: * ;
9
8
use crate :: FormatNodeRule ;
10
9
11
10
#[ derive( Default ) ]
12
- pub struct FormatExprAttribute ;
11
+ pub struct FormatExprAttribute {
12
+ /// Parentheses for fluent style
13
+ parentheses : Option < Parentheses > ,
14
+ }
15
+
16
+ impl FormatRuleWithOptions < ExprAttribute , PyFormatContext < ' _ > > for FormatExprAttribute {
17
+ type Options = Option < Parentheses > ;
18
+
19
+ fn with_options ( mut self , options : Self :: Options ) -> Self {
20
+ self . parentheses = options;
21
+ self
22
+ }
23
+ }
13
24
14
25
impl FormatNodeRule < ExprAttribute > for FormatExprAttribute {
15
26
fn fmt_fields ( & self , item : & ExprAttribute , f : & mut PyFormatter ) -> FormatResult < ( ) > {
@@ -37,11 +48,18 @@ impl FormatNodeRule<ExprAttribute> for FormatExprAttribute {
37
48
38
49
if needs_parentheses {
39
50
value. format ( ) . with_options ( Parentheses :: Always ) . fmt ( f) ?;
40
- } else if let Expr :: Attribute ( expr_attribute) = value. as_ref ( ) {
41
- // We're in a attribute chain (`a.b.c`). The outermost node adds parentheses if
42
- // required, the inner ones don't need them so we skip the `Expr` formatting that
43
- // normally adds the parentheses.
44
- expr_attribute. format ( ) . fmt ( f) ?;
51
+ } else if self . parentheses == Some ( Parentheses :: FluentStyle ) {
52
+ // Fluent style: We need to pass the parentheses on to inner attributes or call chains
53
+ value
54
+ . format ( )
55
+ . with_options ( Parentheses :: FluentStyle )
56
+ . fmt ( f) ?;
57
+ match value. as_ref ( ) {
58
+ Expr :: Call ( _) | Expr :: Subscript ( _) => {
59
+ soft_line_break ( ) . fmt ( f) ?;
60
+ }
61
+ _ => { }
62
+ }
45
63
} else {
46
64
value. format ( ) . fmt ( f) ?;
47
65
}
@@ -50,16 +68,51 @@ impl FormatNodeRule<ExprAttribute> for FormatExprAttribute {
50
68
hard_line_break ( ) . fmt ( f) ?;
51
69
}
52
70
53
- write ! (
54
- f,
55
- [
56
- text( "." ) ,
57
- trailing_comments( trailing_dot_comments) ,
58
- ( !leading_attribute_comments. is_empty( ) ) . then_some( hard_line_break( ) ) ,
59
- leading_comments( leading_attribute_comments) ,
60
- attr. format( )
61
- ]
62
- )
71
+ if self . parentheses == Some ( Parentheses :: FluentStyle ) {
72
+ // Fluent style has line breaks before the dot
73
+ // ```python
74
+ // blogs3 = (
75
+ // Blog.objects.filter(
76
+ // entry__headline__contains="Lennon",
77
+ // )
78
+ // .filter(
79
+ // entry__pub_date__year=2008,
80
+ // )
81
+ // .filter(
82
+ // entry__pub_date__year=2008,
83
+ // )
84
+ // )
85
+ // ```
86
+ write ! (
87
+ f,
88
+ [
89
+ ( !leading_attribute_comments. is_empty( ) ) . then_some( hard_line_break( ) ) ,
90
+ leading_comments( leading_attribute_comments) ,
91
+ text( "." ) ,
92
+ trailing_comments( trailing_dot_comments) ,
93
+ attr. format( )
94
+ ]
95
+ )
96
+ } else {
97
+ // Regular style
98
+ // ```python
99
+ // blogs2 = Blog.objects.filter(
100
+ // entry__headline__contains="Lennon",
101
+ // ).filter(
102
+ // entry__pub_date__year=2008,
103
+ // )
104
+ // ```
105
+ write ! (
106
+ f,
107
+ [
108
+ text( "." ) ,
109
+ trailing_comments( trailing_dot_comments) ,
110
+ ( !leading_attribute_comments. is_empty( ) ) . then_some( hard_line_break( ) ) ,
111
+ leading_comments( leading_attribute_comments) ,
112
+ attr. format( )
113
+ ]
114
+ )
115
+ }
63
116
}
64
117
65
118
fn fmt_dangling_comments (
0 commit comments