Chromium Code Reviews
chromiumcodereview-hr@appspot.gserviceaccount.com (chromiumcodereview-hr) | Please choose your nickname with Settings | Help | Chromium Project | Gerrit Changes | Sign out
(507)

Side by Side Diff: packages/dart_style/lib/src/call_chain_visitor.dart

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

Powered by Google App Engine
This is Rietveld 408576698