@@ -6,6 +6,7 @@ import 'package:analyzer/dart/ast/ast.dart';
6
6
import 'package:analyzer/dart/ast/token.dart' ;
7
7
8
8
import '../ast_extensions.dart' ;
9
+ import '../constants.dart' ;
9
10
import '../piece/chain.dart' ;
10
11
import '../piece/piece.dart' ;
11
12
import 'piece_factory.dart' ;
@@ -42,6 +43,13 @@ import 'piece_factory.dart';
42
43
class ChainBuilder {
43
44
final PieceFactory _visitor;
44
45
46
+ /// The outermost expression being converted to a chain.
47
+ ///
48
+ /// If it's a [CascadeExpression] , then the chain is the cascade sections.
49
+ /// Otherwise, it's some kind of method call or property access and the chain
50
+ /// is the nested series of selector subexpressions.
51
+ final Expression _root;
52
+
45
53
/// The left-most target of the chain.
46
54
late Piece _target;
47
55
@@ -60,11 +68,38 @@ class ChainBuilder {
60
68
/// The dotted property accesses and method calls following the target.
61
69
final List <ChainCall > _calls = [];
62
70
63
- ChainBuilder (this ._visitor, Expression expression) {
64
- _unwrapCall (expression);
71
+ ChainBuilder (this ._visitor, this ._root) {
72
+ if (_root case CascadeExpression cascade) {
73
+ _visitTarget (cascade.target);
74
+
75
+ for (var section in cascade.cascadeSections) {
76
+ _unwrapCall (section);
77
+ }
78
+ } else {
79
+ _unwrapCall (_root);
80
+ }
65
81
}
66
82
67
83
Piece build () {
84
+ if (_root case CascadeExpression cascade) {
85
+ // If there is only a single section and it can block split, allow it:
86
+ //
87
+ // target..cascade(
88
+ // argument,
89
+ // );
90
+ var blockCallIndex =
91
+ _calls.length == 1 && _calls.single.canSplit ? 0 : - 1 ;
92
+
93
+ var chain = ChainPiece (_target, _calls,
94
+ indent: Indent .cascade,
95
+ blockCallIndex: blockCallIndex,
96
+ allowSplitInTarget: _allowSplitInTarget);
97
+
98
+ if (! cascade.allowInline) chain.pin (State .split);
99
+
100
+ return chain;
101
+ }
102
+
68
103
// If there are no calls, there's no chain.
69
104
if (_calls.isEmpty) return _target;
70
105
@@ -94,14 +129,43 @@ class ChainBuilder {
94
129
_ => - 1 ,
95
130
};
96
131
97
- return ChainPiece (_target, _calls, leadingProperties, blockCallIndex,
132
+ // If a method chain appears as the target of a cascade, then we only
133
+ // indent the method chain +2. That way, with the cascade's own +2, the
134
+ // result is a total of +4. This looks more natural than indenting the
135
+ // method chain +4 relative to the cascade's +2:
136
+ //
137
+ // // Bad:
138
+ // object
139
+ // .method()
140
+ // .method()
141
+ // ..x = 1
142
+ // ..y = 2;
143
+ //
144
+ // // Better:
145
+ // object
146
+ // .method()
147
+ // .method()
148
+ // ..x = 1
149
+ // ..y = 2;
150
+ var indent =
151
+ _root.parent is CascadeExpression ? Indent .cascade : Indent .expression;
152
+
153
+ return ChainPiece (_target, _calls,
154
+ indent: indent,
155
+ leadingProperties: leadingProperties,
156
+ blockCallIndex: blockCallIndex,
98
157
allowSplitInTarget: _allowSplitInTarget);
99
158
}
100
159
101
- /// Given [expression] , which is the outermost expression for some call chain,
102
- /// recursively traverses the selectors to fill in the list of [_calls] .
160
+ /// Given [expression] , which is the expression for some call chain, traverses
161
+ /// the selectors to fill in the list of [_calls] .
162
+ ///
163
+ /// If [_root] is a [CascadeSection] , then this is called once for each
164
+ /// section in the cascade.
103
165
///
104
- /// Initializes [_target] with the innermost subexpression that isn't a part
166
+ /// Otherwise, it's a method chain, and this recursively calls itself for the
167
+ /// targets to unzip and flatten the nested selector expressions. Then it
168
+ /// initializes [_target] with the innermost subexpression that isn't a part
105
169
/// of the call chain. For example, given:
106
170
///
107
171
/// foo.bar()!.baz[0][1].bang()
@@ -112,16 +176,23 @@ class ChainBuilder {
112
176
/// .baz[0][1]
113
177
/// .bang()
114
178
void _unwrapCall (Expression expression) {
179
+ var isCascade = _root is CascadeExpression ;
180
+
115
181
switch (expression) {
116
- case Expression (looksLikeStaticCall: true ):
182
+ case Expression (looksLikeStaticCall: true ) when ! isCascade :
117
183
// Don't include things that look like static method or constructor
118
184
// calls in the call chain because that tends to split up named
119
185
// constructors from their class.
120
186
_visitTarget (expression);
121
187
122
188
// Selectors.
123
- case MethodInvocation (: var target? ):
124
- _unwrapCall (target);
189
+ case AssignmentExpression ():
190
+ var piece = _visitor.createAssignment (expression.leftHandSide,
191
+ expression.operator , expression.rightHandSide);
192
+ _calls.add (ChainCall (piece, CallType .property));
193
+
194
+ case MethodInvocation (: var target) when isCascade || target != null :
195
+ if (target != null ) _unwrapCall (target);
125
196
126
197
var callPiece = _visitor.buildPiece ((b) {
127
198
b.token (expression.operator );
@@ -135,8 +206,8 @@ class ChainBuilder {
135
206
_calls.add (ChainCall (callPiece,
136
207
canSplit ? CallType .splittableCall : CallType .unsplittableCall));
137
208
138
- case PropertyAccess (: var target? ):
139
- _unwrapCall (target);
209
+ case PropertyAccess (: var target):
210
+ if (target != null ) _unwrapCall (target);
140
211
141
212
var piece = _visitor.buildPiece ((b) {
142
213
b.token (expression.operator );
@@ -146,7 +217,7 @@ class ChainBuilder {
146
217
_calls.add (ChainCall (piece, CallType .property));
147
218
148
219
case PrefixedIdentifier (: var prefix):
149
- _unwrapCall (prefix);
220
+ if ( ! isCascade) _unwrapCall (prefix);
150
221
151
222
var piece = _visitor.buildPiece ((b) {
152
223
b.token (expression.period);
@@ -165,6 +236,19 @@ class ChainBuilder {
165
236
});
166
237
});
167
238
239
+ case IndexExpression () when isCascade && _calls.isEmpty:
240
+ // An index expression as the first cascade section should be part of
241
+ // the cascade chain and not part of the target, as in:
242
+ //
243
+ // foo
244
+ // ..[index]
245
+ // ..another();
246
+ //
247
+ // For non-cascade method chains, we keep leave the index as part of
248
+ // the target since the method chain doesn't begin until the first `.`.
249
+ var piece = _visitor.createIndexExpression (null , expression);
250
+ _calls.add (ChainCall (piece, CallType .property));
251
+
168
252
case IndexExpression ():
169
253
_unwrapPostfix (expression.target! , (target) {
170
254
return _visitor.createIndexExpression (target, expression);
@@ -180,7 +264,7 @@ class ChainBuilder {
180
264
181
265
default :
182
266
// Otherwise, it isn't a selector so we've reached the target.
183
- _visitTarget (expression);
267
+ if ( ! isCascade) _visitTarget (expression);
184
268
}
185
269
}
186
270
@@ -194,8 +278,9 @@ class ChainBuilder {
194
278
void _unwrapPostfix (
195
279
Expression operand, Piece Function (Piece target) createPostfix) {
196
280
_unwrapCall (operand);
281
+
197
282
// If we don't have a preceding call to hang the postfix expression off of,
198
- // wrap it around the target expression. For example:
283
+ // make it part of the target expression. For example:
199
284
//
200
285
// (list + another)!
201
286
if (_calls.isEmpty) {
0 commit comments