@@ -51,40 +51,49 @@ class ExpressionContents {
51
51
/// Begins tracking an argument list.
52
52
void beginCall (List <AstNode > arguments) {
53
53
var type = _Type .otherCall;
54
- var hasNontrivialNamedArgument = false ;
54
+
55
+ // Count the non-trivial named arguments in this call.
56
+ var namedArguments = 0 ;
55
57
for (var argument in arguments) {
56
58
if (argument is NamedExpression ) {
57
59
type = _Type .callWithNamedArgument;
58
- if (! _isTrivial (argument.expression)) {
59
- hasNontrivialNamedArgument = true ;
60
- break ;
61
- }
60
+ if (! _isTrivial (argument.expression)) namedArguments++ ;
62
61
}
63
62
}
64
63
65
- if (hasNontrivialNamedArgument) _stack.last.nontrivialCalls++ ;
66
-
67
- _stack.add (_Contents (type));
64
+ _stack.add (_Contents (type, namedArguments: namedArguments));
68
65
}
69
66
70
67
/// Ends the most recently begun call and returns `true` if its argument list
71
68
/// should eagerly split.
72
- bool endCall () {
69
+ bool endCall (List < Expression > arguments ) {
73
70
var contents = _end ();
74
71
75
- // If this argument list has any named arguments and it contains another
76
- // call with any non-trivial named arguments, then split it. The idea here
77
- // is that when scanning a line of code, it's hard to tell which calls own
78
- // which named arguments. If there are named arguments at different levels
79
- // of nesting in an expression tree, it's better to split the outer one to
80
- // make that clearer.
72
+ // If there are "too many" named arguments in this call and the calls it
73
+ // contains, then split it.
74
+ //
75
+ // The basic idea is that when scanning a line of code, it's hard to tell
76
+ // which calls own which named arguments if there are named arguments at
77
+ // multiple levels in the call tree. Splitting makes that clearer. At the
78
+ // same time, it's annoying it the formatter is too aggressive about
79
+ // splitting an expression that feels simple enough to the reader to fit on
80
+ // one line. (Especially because if the formatter does eagerly split it,
81
+ // there's nothing they can do to *prevent* that.)
82
+ //
83
+ // The heuristic here tries to strike a "Goldilocks" balance between not
84
+ // splitting too aggressively or too conservatively. The rule is that the
85
+ // entire call tree must contain at least three named arguments, at least
86
+ // one must be in the outermost call being split, and at least one must
87
+ // *not* be in the outermost call.
88
+ //
89
+ // It would be simpler to split any call that has named arguments at
90
+ // different nesting levels, but that's a little too aggressive and forces
91
+ // common code like this to split:
81
92
//
82
- // With this rule, any two named arguments in the middle of the same line
83
- // will always be owned by the same call. There may be a named argument at
84
- // the very beginning of the line owned by the surrounding call which is
85
- // forced to split.
86
- return (contents.type == _Type .callWithNamedArgument) &&
87
- contents.nontrivialCalls > 0 ;
93
+ // Text('Item 1', style: TextStyle(color: Colors.white));
94
+ return contents.totalNamedArguments > 2 &&
95
+ contents.namedArguments > 0 &&
96
+ contents.nestedNamedArguments > 0 ;
88
97
}
89
98
90
99
/// Begin tracking a collection literal and its contents.
@@ -102,9 +111,9 @@ class ExpressionContents {
102
111
if (contents.collections > 0 ) return true ;
103
112
104
113
// If the collection is itself a named argument in a surrounding call that
105
- // is going to be forced to eagerly split, then split the collection too.
106
- // In this case, the collection is sort of like a vararg argument to the
107
- // call. Prefers:
114
+ // may be be forced to eagerly split, then split the collection too. In
115
+ // that case, the collection is sort of like a vararg argument to the call.
116
+ // Prefers:
108
117
//
109
118
// TabBar(
110
119
// tabs: <Widget>[
@@ -118,8 +127,14 @@ class ExpressionContents {
118
127
// TabBar(
119
128
// tabs: <Widget>[Tab(text: 'Tab 1'), Tab(text: 'Tab 2')],
120
129
// );
121
- return contents.type == _Type .namedCollection &&
122
- contents.nontrivialCalls > 0 ;
130
+ //
131
+ // Splitting a collection is also helpful, because it shows each element
132
+ // in parallel with each on its own line. But that's only true when there
133
+ // are multiple elements, so we don't eagerly split collections with just a
134
+ // single element.
135
+ return elements.length > 1 &&
136
+ contents.type == _Type .namedCollection &&
137
+ contents.totalNamedArguments > 0 ;
123
138
}
124
139
125
140
/// Ends the most recently begun operation and returns its contents.
@@ -129,7 +144,8 @@ class ExpressionContents {
129
144
// Transitively include this operation's contents in the surrounding one.
130
145
var parent = _stack.last;
131
146
parent.collections += contents.collections;
132
- parent.nontrivialCalls += contents.nontrivialCalls;
147
+ parent.nestedNamedArguments +=
148
+ contents.namedArguments + contents.nestedNamedArguments;
133
149
134
150
return contents;
135
151
}
@@ -169,11 +185,19 @@ class _Contents {
169
185
/// this operation.
170
186
int collections = 0 ;
171
187
172
- /// The number of calls with non-trivial named arguments transitively inside
173
- /// this operation.
174
- int nontrivialCalls = 0 ;
188
+ /// The number of non-trivial named arguments in this call's own argument
189
+ /// list.
190
+ int namedArguments = 0 ;
191
+
192
+ /// The number of non-trivial named arguments transitively inside this
193
+ /// operation, but not including the call's own named arguments.
194
+ int nestedNamedArguments = 0 ;
195
+
196
+ _Contents (this .type, {this .namedArguments = 0 });
175
197
176
- _Contents (this .type);
198
+ /// The total number of non-trivial named arguments in this operation's own
199
+ /// argument list and all of transitive contents.
200
+ int get totalNamedArguments => namedArguments + nestedNamedArguments;
177
201
}
178
202
179
203
enum _Type {
0 commit comments