1 | | // Copyright (c) 2014, the Dart project authors. Please see the AUTHORS file
|
2 | | // for details. All rights reserved. Use of this source code is governed by a
|
3 | | // BSD-style license that can be found in the LICENSE file.
|
4 | |
|
5 | | library dart_style.src.call_chain_visitor;
|
6 | |
|
7 | | import 'package:analyzer/dart/ast/ast.dart';
|
8 | | import 'package:analyzer/dart/ast/token.dart';
|
9 | |
|
10 | | import 'argument_list_visitor.dart';
|
11 | | import 'rule/argument.dart';
|
12 | | import 'rule/rule.dart';
|
13 | | import 'source_visitor.dart';
|
14 | |
|
15 | | /// Helper class for [SourceVisitor] that handles visiting and writing a
|
16 | | /// chained series of method invocations, property accesses, and/or prefix
|
17 | | /// expressions. In other words, anything using the "." operator.
|
18 | | class CallChainVisitor {
|
19 | | final SourceVisitor _visitor;
|
20 | |
|
21 | | /// The initial target of the call chain.
|
22 | | ///
|
23 | | /// This may be any expression except [MethodInvocation], [PropertyAccess] or
|
24 | | /// [PrefixedIdentifier].
|
25 | | final Expression _target;
|
26 | |
|
27 | | /// The list of dotted names ([PropertyAccess] and [PrefixedIdentifier] at
|
28 | | /// the start of the call chain.
|
29 | | ///
|
30 | | /// This will be empty if the [_target] is not a [SimpleIdentifier].
|
31 | | final List<Expression> _properties;
|
32 | |
|
33 | | /// The mixed method calls and property accesses in the call chain in the
|
34 | | /// order that they appear in the source.
|
35 | | final List<Expression> _calls;
|
36 | |
|
37 | | /// The method calls containing block function literals that break the method
|
38 | | /// chain and escape its indentation.
|
39 | | ///
|
40 | | /// receiver.a().b().c(() {
|
41 | | /// ;
|
42 | | /// }).d(() {
|
43 | | /// ;
|
44 | | /// }).e();
|
45 | | ///
|
46 | | /// Here, it will contain `c` and `d`.
|
47 | | ///
|
48 | | /// The block calls must be contiguous and must be a suffix of the list of
|
49 | | /// calls (except for the one allowed hanging call). Otherwise, none of them
|
50 | | /// are treated as block calls:
|
51 | | ///
|
52 | | /// receiver
|
53 | | /// .a()
|
54 | | /// .b(() {
|
55 | | /// ;
|
56 | | /// })
|
57 | | /// .c(() {
|
58 | | /// ;
|
59 | | /// })
|
60 | | /// .d()
|
61 | | /// .e();
|
62 | | final List<Expression> _blockCalls;
|
63 | |
|
64 | | /// If there is one or more block calls and a single chained expression after
|
65 | | /// that, this will be that expression.
|
66 | | ///
|
67 | | /// receiver.a().b().c(() {
|
68 | | /// ;
|
69 | | /// }).d(() {
|
70 | | /// ;
|
71 | | /// }).e();
|
72 | | ///
|
73 | | /// We allow a single hanging call after the blocks because it will never
|
74 | | /// need to split before its `.` and this accommodates the common pattern of
|
75 | | /// a trailing `toList()` or `toSet()` after a series of higher-order methods
|
76 | | /// on an iterable.
|
77 | | final Expression _hangingCall;
|
78 | |
|
79 | | /// Whether or not a [Rule] is currently active for the call chain.
|
80 | | bool _ruleEnabled = false;
|
81 | |
|
82 | | /// Whether or not the span wrapping the call chain is currently active.
|
83 | | bool _spanEnded = false;
|
84 | |
|
85 | | /// After the properties are visited (if there are any), this will be the
|
86 | | /// rule used to split between them.
|
87 | | PositionalRule _propertyRule;
|
88 | |
|
89 | | /// Creates a new call chain visitor for [visitor] starting with [node].
|
90 | | ///
|
91 | | /// The [node] is the outermost expression containing the chained "."
|
92 | | /// operators and must be a [MethodInvocation], [PropertyAccess] or
|
93 | | /// [PrefixedIdentifier].
|
94 | 2 | factory CallChainVisitor(SourceVisitor visitor, Expression node) {
|
95 | | Expression target;
|
96 | |
|
97 | | // Recursively walk the chain of calls and turn the tree into a list.
|
98 | 2 | var calls = <Expression>[];
|
99 | 2 | flatten(Expression expression) {
|
100 | | target = expression;
|
101 | |
|
102 | | // Treat index expressions where the target is a valid call in a method
|
103 | | // chain as being part of the call. Handles cases like:
|
104 | | //
|
105 | | // receiver
|
106 | | // .property
|
107 | | // .property[0]
|
108 | | // .property
|
109 | | // .method()[1][2];
|
110 | | var call = expression;
|
111 | 3 | while (call is IndexExpression) call = (call as IndexExpression).target;
|
112 | |
|
113 | 2 | if (SourceVisitor.looksLikeStaticCall(call)) {
|
114 | | // Don't include things that look like static method or constructor
|
115 | | // calls in the call chain because that tends to split up named
|
116 | | // constructors from their class.
|
117 | 3 | } else if (call is MethodInvocation && call.target != null) {
|
118 | 2 | flatten(call.target);
|
119 | 1 | calls.add(expression);
|
120 | 3 | } else if (call is PropertyAccess && call.target != null) {
|
121 | 2 | flatten(call.target);
|
122 | 1 | calls.add(expression);
|
123 | 2 | } else if (call is PrefixedIdentifier) {
|
124 | 4 | flatten(call.prefix);
|
125 | 2 | calls.add(expression);
|
126 | | }
|
127 | | }
|
128 | |
|
129 | 2 | flatten(node);
|
130 | |
|
131 | | // An expression that starts with a series of dotted names gets treated a
|
132 | | // little specially. We don't force leading properties to split with the
|
133 | | // rest of the chain. Allows code like:
|
134 | | //
|
135 | | // address.street.number
|
136 | | // .toString()
|
137 | | // .length;
|
138 | 2 | var properties = <Expression>[];
|
139 | 2 | if (target is SimpleIdentifier) {
|
140 | 4 | properties = calls.takeWhile((call) {
|
141 | | // Step into index expressions to see what the index is on.
|
142 | 2 | while (call is IndexExpression) {
|
143 | 1 | call = (call as IndexExpression).target;
|
144 | | }
|
145 | 2 | return call is! MethodInvocation;
|
146 | 2 | }).toList();
|
147 | | }
|
148 | |
|
149 | 4 | calls.removeRange(0, properties.length);
|
150 | |
|
151 | | // Separate out the block calls, if there are any.
|
152 | | List<Expression> blockCalls;
|
153 | | var hangingCall;
|
154 | |
|
155 | | var inBlockCalls = false;
|
156 | 3 | for (var call in calls) {
|
157 | | // See if this call is a method call whose arguments are block formatted.
|
158 | | var isBlockCall = false;
|
159 | 1 | if (call is MethodInvocation) {
|
160 | 2 | var args = new ArgumentListVisitor(visitor, call.argumentList);
|
161 | 1 | isBlockCall = args.hasBlockArguments;
|
162 | | }
|
163 | |
|
164 | | if (isBlockCall) {
|
165 | | inBlockCalls = true;
|
166 | 1 | if (blockCalls == null) blockCalls = [];
|
167 | 1 | blockCalls.add(call);
|
168 | | } else if (inBlockCalls) {
|
169 | | // We found a non-block call after a block call.
|
170 | 2 | if (call == calls.last) {
|
171 | | // It's the one allowed hanging one, so it's OK.
|
172 | | hangingCall = call;
|
173 | | break;
|
174 | | }
|
175 | |
|
176 | | // Don't allow any of the calls to be block formatted.
|
177 | | blockCalls = null;
|
178 | | break;
|
179 | | }
|
180 | | }
|
181 | |
|
182 | | if (blockCalls != null) {
|
183 | 2 | for (var blockCall in blockCalls) calls.remove(blockCall);
|
184 | | }
|
185 | |
|
186 | | if (hangingCall != null) {
|
187 | 1 | calls.remove(hangingCall);
|
188 | | }
|
189 | |
|
190 | 2 | return new CallChainVisitor._(
|
191 | | visitor, target, properties, calls, blockCalls, hangingCall);
|
192 | | }
|
193 | |
|
194 | 2 | CallChainVisitor._(this._visitor, this._target, this._properties, this._calls,
|
195 | | this._blockCalls, this._hangingCall);
|
196 | |
|
197 | | /// Builds chunks for the call chain.
|
198 | | ///
|
199 | | /// If [unnest] is `false` than this will not close the expression nesting
|
200 | | /// created for the call chain and the caller must end it. Used by cascades
|
201 | | /// to force a cascade after a method chain to be more deeply nested than
|
202 | | /// the methods.
|
203 | 2 | void visit({bool unnest}) {
|
204 | | if (unnest == null) unnest = true;
|
205 | |
|
206 | 6 | _visitor.builder.nestExpression();
|
207 | |
|
208 | | // Try to keep the entire method invocation one line.
|
209 | 6 | _visitor.builder.startSpan();
|
210 | |
|
211 | | // If a split in the target expression forces the first `.` to split, then
|
212 | | // start the rule now so that it surrounds the target.
|
213 | 4 | var splitOnTarget = _forcesSplit(_target);
|
214 | |
|
215 | | if (splitOnTarget) {
|
216 | 6 | if (_properties.length > 1) {
|
217 | 2 | _propertyRule = new PositionalRule(null, 0, 0);
|
218 | 4 | _visitor.builder.startLazyRule(_propertyRule);
|
219 | | } else {
|
220 | 2 | _enableRule(lazy: true);
|
221 | | }
|
222 | | }
|
223 | |
|
224 | 6 | _visitor.visit(_target);
|
225 | |
|
226 | | // Leading properties split like positional arguments: either not at all,
|
227 | | // before one ".", or before all of them.
|
228 | 6 | if (_properties.length == 1) {
|
229 | 4 | _visitor.soloZeroSplit();
|
230 | 6 | _writeCall(_properties.single);
|
231 | 3 | } else if (_properties.length > 1) {
|
232 | | if (!splitOnTarget) {
|
233 | 0 | _propertyRule = new PositionalRule(null, 0, 0);
|
234 | 0 | _visitor.builder.startRule(_propertyRule);
|
235 | | }
|
236 | |
|
237 | 2 | for (var property in _properties) {
|
238 | 4 | _propertyRule.beforeArgument(_visitor.zeroSplit());
|
239 | 1 | _writeCall(property);
|
240 | | }
|
241 | |
|
242 | 3 | _visitor.builder.endRule();
|
243 | | }
|
244 | |
|
245 | | // Indent any block arguments in the chain that don't get special formatting
|
246 | | // below. Only do this if there is more than one argument to avoid spurious
|
247 | | // indentation in cases like:
|
248 | | //
|
249 | | // object.method(wrapper(() {
|
250 | | // body;
|
251 | | // });
|
252 | | // TODO(rnystrom): Come up with a less arbitrary way to express this?
|
253 | 9 | if (_calls.length > 1) _visitor.builder.startBlockArgumentNesting();
|
254 | |
|
255 | | // The chain of calls splits atomically (either all or none). Any block
|
256 | | // arguments inside them get indented to line up with the `.`.
|
257 | 3 | for (var call in _calls) {
|
258 | 1 | _enableRule();
|
259 | 2 | _visitor.zeroSplit();
|
260 | 1 | _writeCall(call);
|
261 | | }
|
262 | |
|
263 | 9 | if (_calls.length > 1) _visitor.builder.endBlockArgumentNesting();
|
264 | |
|
265 | | // If there are block calls, end the chain and write those without any
|
266 | | // extra indentation.
|
267 | 2 | if (_blockCalls != null) {
|
268 | 1 | _enableRule();
|
269 | 2 | _visitor.zeroSplit();
|
270 | 1 | _disableRule();
|
271 | |
|
272 | 2 | for (var blockCall in _blockCalls) {
|
273 | 1 | _writeBlockCall(blockCall);
|
274 | | }
|
275 | |
|
276 | | // If there is a hanging call after the last block, write it without any
|
277 | | // split before the ".".
|
278 | 1 | if (_hangingCall != null) {
|
279 | 2 | _writeCall(_hangingCall);
|
280 | | }
|
281 | | }
|
282 | |
|
283 | 2 | _disableRule();
|
284 | 2 | _endSpan();
|
285 | |
|
286 | 6 | if (unnest) _visitor.builder.unnest();
|
287 | | }
|
288 | |
|
289 | | /// Returns `true` if the method chain should split if a split occurs inside
|
290 | | /// [expression].
|
291 | | ///
|
292 | | /// In most cases, splitting in a method chain's target forces the chain to
|
293 | | /// split too:
|
294 | | ///
|
295 | | /// receiver(very, long, argument,
|
296 | | /// list) // <-- Split here...
|
297 | | /// .method(); // ...forces split here.
|
298 | | ///
|
299 | | /// However, if the target is a collection or function literal (or an
|
300 | | /// argument list ending in one of those), we don't want to split:
|
301 | | ///
|
302 | | /// receiver(inner(() {
|
303 | | /// ;
|
304 | | /// }).method(); // <-- Unsplit.
|
305 | 2 | bool _forcesSplit(Expression expression) {
|
306 | | // TODO(rnystrom): Other cases we may want to consider handling and
|
307 | | // recursing into:
|
308 | | // * ParenthesizedExpression.
|
309 | | // * The right operand in an infix operator call.
|
310 | | // * The body of a `=>` function.
|
311 | |
|
312 | | // Don't split right after a collection literal.
|
313 | 2 | if (expression is ListLiteral) return false;
|
314 | 2 | if (expression is MapLiteral) return false;
|
315 | |
|
316 | | // Don't split right after a non-empty curly-bodied function.
|
317 | 2 | if (expression is FunctionExpression) {
|
318 | 2 | if (expression.body is! BlockFunctionBody) return false;
|
319 | |
|
320 | 4 | return (expression.body as BlockFunctionBody).block.statements.isEmpty;
|
321 | | }
|
322 | |
|
323 | | // If the expression ends in an argument list, base the splitting on the
|
324 | | // last argument.
|
325 | | ArgumentList argumentList;
|
326 | 2 | if (expression is MethodInvocation) {
|
327 | 1 | argumentList = expression.argumentList;
|
328 | 2 | } else if (expression is InstanceCreationExpression) {
|
329 | 1 | argumentList = expression.argumentList;
|
330 | 2 | } else if (expression is FunctionExpressionInvocation) {
|
331 | 1 | argumentList = expression.argumentList;
|
332 | | }
|
333 | |
|
334 | | // Any other kind of expression always splits.
|
335 | | if (argumentList == null) return true;
|
336 | 2 | if (argumentList.arguments.isEmpty) return true;
|
337 | |
|
338 | 2 | var argument = argumentList.arguments.last;
|
339 | |
|
340 | | // If the argument list has a trailing comma, treat it like a collection.
|
341 | 4 | if (argument.endToken.next.type == TokenType.COMMA) return false;
|
342 | |
|
343 | 1 | if (argument is NamedExpression) {
|
344 | 1 | argument = (argument as NamedExpression).expression;
|
345 | | }
|
346 | |
|
347 | | // TODO(rnystrom): This logic is similar (but not identical) to
|
348 | | // ArgumentListVisitor.hasBlockArguments. They overlap conceptually and
|
349 | | // both have their own peculiar heuristics. It would be good to unify and
|
350 | | // rationalize them.
|
351 | |
|
352 | 1 | return _forcesSplit(argument);
|
353 | | }
|
354 | |
|
355 | | /// Writes [call], which must be one of the supported expression types.
|
356 | 2 | void _writeCall(Expression call) {
|
357 | 2 | if (call is IndexExpression) {
|
358 | 3 | _visitor.builder.nestExpression();
|
359 | 2 | _writeCall(call.target);
|
360 | 2 | _visitor.finishIndexExpression(call);
|
361 | 3 | _visitor.builder.unnest();
|
362 | 2 | } else if (call is MethodInvocation) {
|
363 | 1 | _writeInvocation(call);
|
364 | 2 | } else if (call is PropertyAccess) {
|
365 | 3 | _visitor.token(call.operator);
|
366 | 3 | _visitor.visit(call.propertyName);
|
367 | 2 | } else if (call is PrefixedIdentifier) {
|
368 | 6 | _visitor.token(call.period);
|
369 | 6 | _visitor.visit(call.identifier);
|
370 | | } else {
|
371 | | // Unexpected type.
|
372 | 0 | assert(false);
|
373 | | }
|
374 | | }
|
375 | |
|
376 | 1 | void _writeInvocation(MethodInvocation invocation) {
|
377 | 3 | _visitor.token(invocation.operator);
|
378 | 4 | _visitor.token(invocation.methodName.token);
|
379 | |
|
380 | | // If we don't have any block calls, stop the rule after the last method
|
381 | | // call name, but before its arguments. This allows unsplit chains where
|
382 | | // the last argument list wraps, like:
|
383 | | //
|
384 | | // foo().bar().baz(
|
385 | | // argument, list);
|
386 | 6 | if (_blockCalls == null && _calls.isNotEmpty && invocation == _calls.last) {
|
387 | 1 | _disableRule();
|
388 | | }
|
389 | |
|
390 | | // For a single method call on an identifier, stop the span before the
|
391 | | // arguments to make it easier to keep the call name with the target. In
|
392 | | // other words, prefer:
|
393 | | //
|
394 | | // target.method(
|
395 | | // argument, list);
|
396 | | //
|
397 | | // Over:
|
398 | | //
|
399 | | // target
|
400 | | // .method(argument, list);
|
401 | | //
|
402 | | // Alternatively, the way to think of this is try to avoid splitting on the
|
403 | | // "." when calling a single method on a single name. This is especially
|
404 | | // important because the identifier is often a library prefix, and splitting
|
405 | | // there looks really odd.
|
406 | 2 | if (_properties.isEmpty &&
|
407 | 3 | _calls.length == 1 &&
|
408 | 1 | _blockCalls == null &&
|
409 | 2 | _target is SimpleIdentifier) {
|
410 | 1 | _endSpan();
|
411 | | }
|
412 | |
|
413 | 3 | _visitor.builder.nestExpression();
|
414 | 3 | _visitor.visit(invocation.typeArguments);
|
415 | 3 | _visitor.visitArgumentList(invocation.argumentList, nestExpression: false);
|
416 | 3 | _visitor.builder.unnest();
|
417 | | }
|
418 | |
|
419 | 1 | void _writeBlockCall(MethodInvocation invocation) {
|
420 | 3 | _visitor.token(invocation.operator);
|
421 | 4 | _visitor.token(invocation.methodName.token);
|
422 | 3 | _visitor.visit(invocation.typeArguments);
|
423 | 3 | _visitor.visit(invocation.argumentList);
|
424 | | }
|
425 | |
|
426 | | /// If a [Rule] for the method chain is currently active, ends it.
|
427 | 2 | void _disableRule() {
|
428 | 4 | if (_ruleEnabled == false) return;
|
429 | |
|
430 | 6 | _visitor.builder.endRule();
|
431 | 2 | _ruleEnabled = false;
|
432 | | }
|
433 | |
|
434 | | /// Creates a new method chain [Rule] if one is not already active.
|
435 | 2 | void _enableRule({bool lazy: false}) {
|
436 | 2 | if (_ruleEnabled) return;
|
437 | |
|
438 | | // If the properties split, force the calls to split too.
|
439 | 2 | var rule = new Rule();
|
440 | 4 | if (_propertyRule != null) _propertyRule.setNamedArgsRule(rule);
|
441 | |
|
442 | | if (lazy) {
|
443 | 6 | _visitor.builder.startLazyRule(rule);
|
444 | | } else {
|
445 | 3 | _visitor.builder.startRule(rule);
|
446 | | }
|
447 | |
|
448 | 2 | _ruleEnabled = true;
|
449 | | }
|
450 | |
|
451 | | /// Ends the span wrapping the call chain if it hasn't ended already.
|
452 | 2 | void _endSpan() {
|
453 | 2 | if (_spanEnded) return;
|
454 | |
|
455 | 6 | _visitor.builder.endSpan();
|
456 | 2 | _spanEnded = true;
|
457 | | }
|
458 | | }
|