Coverage report for lib/src/call_chain_visitor.dart

Line coverage: 134 / 137 (97.8%)

All files > lib/src/call_chain_visitor.dart

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].
942
  factory CallChainVisitor(SourceVisitor visitor, Expression node) {
95
    Expression target;
96
97
    // Recursively walk the chain of calls and turn the tree into a list.
982
    var calls = <Expression>[];
992
    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;
1113
      while (call is IndexExpression) call = (call as IndexExpression).target;
112
1132
      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.
1173
      } else if (call is MethodInvocation && call.target != null) {
1182
        flatten(call.target);
1191
        calls.add(expression);
1203
      } else if (call is PropertyAccess && call.target != null) {
1212
        flatten(call.target);
1221
        calls.add(expression);
1232
      } else if (call is PrefixedIdentifier) {
1244
        flatten(call.prefix);
1252
        calls.add(expression);
126
      }
127
    }
128
1292
    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;
1382
    var properties = <Expression>[];
1392
    if (target is SimpleIdentifier) {
1404
      properties = calls.takeWhile((call) {
141
        // Step into index expressions to see what the index is on.
1422
        while (call is IndexExpression) {
1431
          call = (call as IndexExpression).target;
144
        }
1452
        return call is! MethodInvocation;
1462
      }).toList();
147
    }
148
1494
    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;
1563
    for (var call in calls) {
157
      // See if this call is a method call whose arguments are block formatted.
158
      var isBlockCall = false;
1591
      if (call is MethodInvocation) {
1602
        var args = new ArgumentListVisitor(visitor, call.argumentList);
1611
        isBlockCall = args.hasBlockArguments;
162
      }
163
164
      if (isBlockCall) {
165
        inBlockCalls = true;
1661
        if (blockCalls == null) blockCalls = [];
1671
        blockCalls.add(call);
168
      } else if (inBlockCalls) {
169
        // We found a non-block call after a block call.
1702
        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) {
1832
      for (var blockCall in blockCalls) calls.remove(blockCall);
184
    }
185
186
    if (hangingCall != null) {
1871
      calls.remove(hangingCall);
188
    }
189
1902
    return new CallChainVisitor._(
191
        visitor, target, properties, calls, blockCalls, hangingCall);
192
  }
193
1942
  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.
2032
  void visit({bool unnest}) {
204
    if (unnest == null) unnest = true;
205
2066
    _visitor.builder.nestExpression();
207
208
    // Try to keep the entire method invocation one line.
2096
    _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.
2134
    var splitOnTarget = _forcesSplit(_target);
214
215
    if (splitOnTarget) {
2166
      if (_properties.length > 1) {
2172
        _propertyRule = new PositionalRule(null, 0, 0);
2184
        _visitor.builder.startLazyRule(_propertyRule);
219
      } else {
2202
        _enableRule(lazy: true);
221
      }
222
    }
223
2246
    _visitor.visit(_target);
225
226
    // Leading properties split like positional arguments: either not at all,
227
    // before one ".", or before all of them.
2286
    if (_properties.length == 1) {
2294
      _visitor.soloZeroSplit();
2306
      _writeCall(_properties.single);
2313
    } else if (_properties.length > 1) {
232
      if (!splitOnTarget) {
2330
        _propertyRule = new PositionalRule(null, 0, 0);
2340
        _visitor.builder.startRule(_propertyRule);
235
      }
236
2372
      for (var property in _properties) {
2384
        _propertyRule.beforeArgument(_visitor.zeroSplit());
2391
        _writeCall(property);
240
      }
241
2423
      _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?
2539
    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 `.`.
2573
    for (var call in _calls) {
2581
      _enableRule();
2592
      _visitor.zeroSplit();
2601
      _writeCall(call);
261
    }
262
2639
    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.
2672
    if (_blockCalls != null) {
2681
      _enableRule();
2692
      _visitor.zeroSplit();
2701
      _disableRule();
271
2722
      for (var blockCall in _blockCalls) {
2731
        _writeBlockCall(blockCall);
274
      }
275
276
      // If there is a hanging call after the last block, write it without any
277
      // split before the ".".
2781
      if (_hangingCall != null) {
2792
        _writeCall(_hangingCall);
280
      }
281
    }
282
2832
    _disableRule();
2842
    _endSpan();
285
2866
    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.
3052
  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.
3132
    if (expression is ListLiteral) return false;
3142
    if (expression is MapLiteral) return false;
315
316
    // Don't split right after a non-empty curly-bodied function.
3172
    if (expression is FunctionExpression) {
3182
      if (expression.body is! BlockFunctionBody) return false;
319
3204
      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;
3262
    if (expression is MethodInvocation) {
3271
      argumentList = expression.argumentList;
3282
    } else if (expression is InstanceCreationExpression) {
3291
      argumentList = expression.argumentList;
3302
    } else if (expression is FunctionExpressionInvocation) {
3311
      argumentList = expression.argumentList;
332
    }
333
334
    // Any other kind of expression always splits.
335
    if (argumentList == null) return true;
3362
    if (argumentList.arguments.isEmpty) return true;
337
3382
    var argument = argumentList.arguments.last;
339
340
    // If the argument list has a trailing comma, treat it like a collection.
3414
    if (argument.endToken.next.type == TokenType.COMMA) return false;
342
3431
    if (argument is NamedExpression) {
3441
      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
3521
    return _forcesSplit(argument);
353
  }
354
355
  /// Writes [call], which must be one of the supported expression types.
3562
  void _writeCall(Expression call) {
3572
    if (call is IndexExpression) {
3583
      _visitor.builder.nestExpression();
3592
      _writeCall(call.target);
3602
      _visitor.finishIndexExpression(call);
3613
      _visitor.builder.unnest();
3622
    } else if (call is MethodInvocation) {
3631
      _writeInvocation(call);
3642
    } else if (call is PropertyAccess) {
3653
      _visitor.token(call.operator);
3663
      _visitor.visit(call.propertyName);
3672
    } else if (call is PrefixedIdentifier) {
3686
      _visitor.token(call.period);
3696
      _visitor.visit(call.identifier);
370
    } else {
371
      // Unexpected type.
3720
      assert(false);
373
    }
374
  }
375
3761
  void _writeInvocation(MethodInvocation invocation) {
3773
    _visitor.token(invocation.operator);
3784
    _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);
3866
    if (_blockCalls == null && _calls.isNotEmpty && invocation == _calls.last) {
3871
      _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.
4062
    if (_properties.isEmpty &&
4073
        _calls.length == 1 &&
4081
        _blockCalls == null &&
4092
        _target is SimpleIdentifier) {
4101
      _endSpan();
411
    }
412
4133
    _visitor.builder.nestExpression();
4143
    _visitor.visit(invocation.typeArguments);
4153
    _visitor.visitArgumentList(invocation.argumentList, nestExpression: false);
4163
    _visitor.builder.unnest();
417
  }
418
4191
  void _writeBlockCall(MethodInvocation invocation) {
4203
    _visitor.token(invocation.operator);
4214
    _visitor.token(invocation.methodName.token);
4223
    _visitor.visit(invocation.typeArguments);
4233
    _visitor.visit(invocation.argumentList);
424
  }
425
426
  /// If a [Rule] for the method chain is currently active, ends it.
4272
  void _disableRule() {
4284
    if (_ruleEnabled == false) return;
429
4306
    _visitor.builder.endRule();
4312
    _ruleEnabled = false;
432
  }
433
434
  /// Creates a new method chain [Rule] if one is not already active.
4352
  void _enableRule({bool lazy: false}) {
4362
    if (_ruleEnabled) return;
437
438
    // If the properties split, force the calls to split too.
4392
    var rule = new Rule();
4404
    if (_propertyRule != null) _propertyRule.setNamedArgsRule(rule);
441
442
    if (lazy) {
4436
      _visitor.builder.startLazyRule(rule);
444
    } else {
4453
      _visitor.builder.startRule(rule);
446
    }
447
4482
    _ruleEnabled = true;
449
  }
450
451
  /// Ends the span wrapping the call chain if it hasn't ended already.
4522
  void _endSpan() {
4532
    if (_spanEnded) return;
454
4556
    _visitor.builder.endSpan();
4562
    _spanEnded = true;
457
  }
458
}