OLD | NEW |
| (Empty) |
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.argument_list_visitor; | |
6 | |
7 import 'package:analyzer/analyzer.dart'; | |
8 | |
9 import 'chunk.dart'; | |
10 import 'rule/argument.dart'; | |
11 import 'rule/rule.dart'; | |
12 import 'source_visitor.dart'; | |
13 | |
14 /// Helper class for [SourceVisitor] that handles visiting and writing an | |
15 /// [ArgumentList], including all of the special code needed to handle function | |
16 /// and collection arguments. | |
17 class ArgumentListVisitor { | |
18 final SourceVisitor _visitor; | |
19 | |
20 final ArgumentList _node; | |
21 | |
22 /// The normal arguments preceding any block function arguments. | |
23 final ArgumentSublist _arguments; | |
24 | |
25 /// The contiguous list of block function arguments, if any. | |
26 /// | |
27 /// Otherwise, this is `null`. | |
28 final List<Expression> _functions; | |
29 | |
30 /// If there are block function arguments, this is the arguments after them. | |
31 /// | |
32 /// Otherwise, this is `null`. | |
33 final ArgumentSublist _argumentsAfterFunctions; | |
34 | |
35 /// Returns `true` if there is only a single positional argument. | |
36 bool get _isSingle => | |
37 _node.arguments.length == 1 && _node.arguments.single is! NamedExpression; | |
38 | |
39 /// Whether this argument list has any collection or block function arguments. | |
40 bool get hasBlockArguments => | |
41 _arguments._collections.isNotEmpty || _functions != null; | |
42 | |
43 /// Whether this argument list should force the containing method chain to | |
44 /// add a level of block nesting. | |
45 bool get nestMethodArguments { | |
46 // If there are block arguments, we don't want the method to force them to | |
47 // the right. | |
48 if (hasBlockArguments) return false; | |
49 | |
50 // Corner case: If there is just a single argument, don't bump the nesting. | |
51 // This lets us avoid spurious indentation in cases like: | |
52 // | |
53 // object.method(function(() { | |
54 // body; | |
55 // })); | |
56 return _node.arguments.length > 1; | |
57 } | |
58 | |
59 factory ArgumentListVisitor(SourceVisitor visitor, ArgumentList node) { | |
60 // Look for a single contiguous range of block function arguments. | |
61 var functionsStart; | |
62 var functionsEnd; | |
63 | |
64 for (var i = 0; i < node.arguments.length; i++) { | |
65 var argument = node.arguments[i]; | |
66 if (_isBlockFunction(argument)) { | |
67 if (functionsStart == null) functionsStart = i; | |
68 | |
69 // The functions must be one contiguous section. | |
70 if (functionsEnd != null && functionsEnd != i) { | |
71 functionsStart = null; | |
72 functionsEnd = null; | |
73 break; | |
74 } | |
75 | |
76 functionsEnd = i + 1; | |
77 } | |
78 } | |
79 | |
80 if (functionsStart == null) { | |
81 // No functions, so there is just a single argument list. | |
82 return new ArgumentListVisitor._(visitor, node, | |
83 new ArgumentSublist(node.arguments, node.arguments), null, null); | |
84 } | |
85 | |
86 // Split the arguments into two independent argument lists with the | |
87 // functions in the middle. | |
88 var argumentsBefore = node.arguments.take(functionsStart).toList(); | |
89 var functions = node.arguments.sublist(functionsStart, functionsEnd); | |
90 var argumentsAfter = node.arguments.skip(functionsEnd).toList(); | |
91 | |
92 return new ArgumentListVisitor._( | |
93 visitor, | |
94 node, | |
95 new ArgumentSublist(node.arguments, argumentsBefore), | |
96 functions, | |
97 new ArgumentSublist(node.arguments, argumentsAfter)); | |
98 } | |
99 | |
100 ArgumentListVisitor._(this._visitor, this._node, this._arguments, | |
101 this._functions, this._argumentsAfterFunctions); | |
102 | |
103 /// Builds chunks for the call chain. | |
104 void visit() { | |
105 // If there is just one positional argument, it tends to look weird to | |
106 // split before it, so try not to. | |
107 if (_isSingle) _visitor.builder.startSpan(); | |
108 | |
109 // Nest around the parentheses in case there are comments before or after | |
110 // them. | |
111 _visitor.builder.nestExpression(); | |
112 _visitor.builder.startSpan(); | |
113 _visitor.token(_node.leftParenthesis); | |
114 | |
115 _arguments.visit(_visitor); | |
116 | |
117 _visitor.builder.endSpan(); | |
118 | |
119 if (_functions != null) { | |
120 // TODO(rnystrom): It might look better to treat the parameter list of the | |
121 // first function as if it were an argument in the preceding argument list | |
122 // instead of just having this little solo split here. That would try to | |
123 // keep the parameter list with other arguments when possible, and, I | |
124 // think, generally look nicer. | |
125 if (_functions.first == _node.arguments.first) { | |
126 _visitor.soloZeroSplit(); | |
127 } else { | |
128 _visitor.soloSplit(); | |
129 } | |
130 | |
131 for (var argument in _functions) { | |
132 if (argument != _functions.first) _visitor.space(); | |
133 | |
134 _visitor.visit(argument); | |
135 | |
136 // Write the trailing comma. | |
137 if (argument != _node.arguments.last) { | |
138 _visitor.token(argument.endToken.next); | |
139 } | |
140 } | |
141 | |
142 _visitor.builder.startSpan(); | |
143 _argumentsAfterFunctions.visit(_visitor); | |
144 _visitor.builder.endSpan(); | |
145 } | |
146 | |
147 _visitor.token(_node.rightParenthesis); | |
148 | |
149 _visitor.builder.unnest(); | |
150 | |
151 if (_isSingle) _visitor.builder.endSpan(); | |
152 } | |
153 | |
154 /// Returns `true` if [expression] is a [FunctionExpression] with a block | |
155 /// body. | |
156 static bool _isBlockFunction(Expression expression) { | |
157 if (expression is NamedExpression) { | |
158 expression = (expression as NamedExpression).expression; | |
159 } | |
160 | |
161 // Allow functions wrapped in dotted method calls like "a.b.c(() { ... })". | |
162 if (expression is MethodInvocation) { | |
163 if (!_isValidWrappingTarget(expression.target)) return false; | |
164 if (expression.argumentList.arguments.length != 1) return false; | |
165 | |
166 return _isBlockFunction(expression.argumentList.arguments.single); | |
167 } | |
168 | |
169 // Curly body functions are. | |
170 if (expression is! FunctionExpression) return false; | |
171 var function = expression as FunctionExpression; | |
172 return function.body is BlockFunctionBody; | |
173 } | |
174 | |
175 /// Returns `true` if [expression] is a valid method invocation target for | |
176 /// an invocation that wraps a function literal argument. | |
177 static bool _isValidWrappingTarget(Expression expression) { | |
178 // Allow bare function calls. | |
179 if (expression == null) return true; | |
180 | |
181 // Allow property accesses. | |
182 while (expression is PropertyAccess) { | |
183 expression = (expression as PropertyAccess).target; | |
184 } | |
185 | |
186 if (expression is PrefixedIdentifier) return true; | |
187 if (expression is SimpleIdentifier) return true; | |
188 | |
189 return false; | |
190 } | |
191 } | |
192 | |
193 /// A range of arguments from a complete argument list. | |
194 /// | |
195 /// One of these typically covers all of the arguments in an invocation. But, | |
196 /// when an argument list has block functions in the middle, the arguments | |
197 /// before and after the functions are treated as separate independent lists. | |
198 /// In that case, there will be two of these. | |
199 class ArgumentSublist { | |
200 /// The full argument list from the AST. | |
201 final List<Expression> _allArguments; | |
202 | |
203 /// The positional arguments, in order. | |
204 final List<Expression> _positional; | |
205 | |
206 /// The named arguments, in order. | |
207 final List<Expression> _named; | |
208 | |
209 /// The arguments that are collection literals that get special formatting. | |
210 final Set<Expression> _collections; | |
211 | |
212 /// The number of leading collections. | |
213 /// | |
214 /// If all arguments are collections, this counts them. | |
215 final int _leadingCollections; | |
216 | |
217 /// The number of trailing collections. | |
218 /// | |
219 /// If all arguments are collections, this is zero. | |
220 final int _trailingCollections; | |
221 | |
222 /// The rule used to split the bodies of all of the collection arguments. | |
223 Rule get _collectionRule { | |
224 // Lazy initialize. | |
225 if (_collectionRuleField == null && _collections.isNotEmpty) { | |
226 _collectionRuleField = new SimpleRule(cost: Cost.splitCollections); | |
227 } | |
228 | |
229 return _collectionRuleField; | |
230 } | |
231 | |
232 Rule _collectionRuleField; | |
233 | |
234 bool get _hasMultipleArguments => _positional.length + _named.length > 1; | |
235 | |
236 factory ArgumentSublist( | |
237 List<Expression> allArguments, List<Expression> arguments) { | |
238 // Assumes named arguments follow all positional ones. | |
239 var positional = | |
240 arguments.takeWhile((arg) => arg is! NamedExpression).toList(); | |
241 var named = arguments.skip(positional.length).toList(); | |
242 | |
243 var collections = arguments.where(_isCollectionArgument).toSet(); | |
244 | |
245 // Count the leading arguments that are collection literals. | |
246 var leadingCollections = 0; | |
247 for (var argument in arguments) { | |
248 if (!collections.contains(argument)) break; | |
249 leadingCollections++; | |
250 } | |
251 | |
252 // Count the trailing arguments that are collection literals. | |
253 var trailingCollections = 0; | |
254 if (leadingCollections != arguments.length) { | |
255 for (var argument in arguments.reversed) { | |
256 if (!collections.contains(argument)) break; | |
257 trailingCollections++; | |
258 } | |
259 } | |
260 | |
261 // If only some of the named arguments are collections, treat none of them | |
262 // specially. Avoids cases like: | |
263 // | |
264 // function( | |
265 // a: arg, | |
266 // b: [ | |
267 // ... | |
268 // ]); | |
269 if (trailingCollections < named.length) trailingCollections = 0; | |
270 | |
271 // Collections must all be a prefix or suffix of the argument list (and not | |
272 // both). | |
273 if (leadingCollections != collections.length) leadingCollections = 0; | |
274 if (trailingCollections != collections.length) trailingCollections = 0; | |
275 | |
276 // Ignore any collections in the middle of the argument list. | |
277 if (leadingCollections == 0 && trailingCollections == 0) { | |
278 collections.clear(); | |
279 } | |
280 | |
281 return new ArgumentSublist._(allArguments, positional, named, collections, | |
282 leadingCollections, trailingCollections); | |
283 } | |
284 | |
285 ArgumentSublist._(this._allArguments, this._positional, this._named, | |
286 this._collections, this._leadingCollections, this._trailingCollections); | |
287 | |
288 void visit(SourceVisitor visitor) { | |
289 var rule = _visitPositional(visitor); | |
290 _visitNamed(visitor, rule); | |
291 } | |
292 | |
293 /// Writes the positional arguments, if any. | |
294 PositionalRule _visitPositional(SourceVisitor visitor) { | |
295 if (_positional.isEmpty) return null; | |
296 | |
297 // Allow splitting after "(". | |
298 var rule; | |
299 if (_positional.length == 1) { | |
300 rule = new SinglePositionalRule(_collectionRule, | |
301 splitsOnInnerRules: _allArguments.length > 1 && | |
302 !_isCollectionArgument(_positional.first)); | |
303 } else { | |
304 // Only count the positional bodies in the positional rule. | |
305 var leadingPositional = _leadingCollections; | |
306 if (_leadingCollections == _positional.length + _named.length) { | |
307 leadingPositional -= _named.length; | |
308 } | |
309 | |
310 var trailingPositional = _trailingCollections - _named.length; | |
311 rule = new MultiplePositionalRule( | |
312 _collectionRule, leadingPositional, trailingPositional); | |
313 } | |
314 | |
315 visitor.builder.startRule(rule); | |
316 | |
317 var chunk; | |
318 if (_isFirstArgument(_positional.first)) { | |
319 chunk = visitor.zeroSplit(); | |
320 } else { | |
321 chunk = visitor.split(); | |
322 } | |
323 rule.beforeArgument(chunk); | |
324 | |
325 // Try to not split the arguments. | |
326 visitor.builder.startSpan(Cost.positionalArguments); | |
327 | |
328 for (var argument in _positional) { | |
329 _visitArgument(visitor, rule, argument); | |
330 | |
331 // Positional arguments split independently. | |
332 if (argument != _positional.last) { | |
333 rule.beforeArgument(visitor.split()); | |
334 } | |
335 } | |
336 | |
337 visitor.builder.endSpan(); | |
338 visitor.builder.endRule(); | |
339 | |
340 return rule; | |
341 } | |
342 | |
343 /// Writes the named arguments, if any. | |
344 void _visitNamed(SourceVisitor visitor, PositionalRule rule) { | |
345 if (_named.isEmpty) return; | |
346 | |
347 var positionalRule = rule; | |
348 var namedRule = new NamedRule(_collectionRule); | |
349 visitor.builder.startRule(namedRule); | |
350 | |
351 // Let the positional args force the named ones to split. | |
352 if (positionalRule != null) { | |
353 positionalRule.setNamedArgsRule(namedRule); | |
354 } | |
355 | |
356 // Split before the first named argument. | |
357 namedRule.beforeArguments( | |
358 visitor.builder.split(space: !_isFirstArgument(_named.first))); | |
359 | |
360 for (var argument in _named) { | |
361 _visitArgument(visitor, namedRule, argument); | |
362 | |
363 // Write the split. | |
364 if (argument != _named.last) visitor.split(); | |
365 } | |
366 | |
367 visitor.builder.endRule(); | |
368 } | |
369 | |
370 void _visitArgument( | |
371 SourceVisitor visitor, ArgumentRule rule, Expression argument) { | |
372 // If we're about to write a collection argument, handle it specially. | |
373 if (_collections.contains(argument)) { | |
374 if (rule != null) rule.beforeCollection(); | |
375 | |
376 // Tell it to use the rule we've already created. | |
377 visitor.setNextLiteralBodyRule(_collectionRule); | |
378 } else if (_hasMultipleArguments) { | |
379 // Corner case: If there is just a single argument, don't bump the | |
380 // nesting. This lets us avoid spurious indentation in cases like: | |
381 // | |
382 // function(function(() { | |
383 // body; | |
384 // })); | |
385 visitor.builder.startBlockArgumentNesting(); | |
386 } | |
387 | |
388 visitor.visit(argument); | |
389 | |
390 if (_collections.contains(argument)) { | |
391 if (rule != null) rule.afterCollection(); | |
392 } else if (_hasMultipleArguments) { | |
393 visitor.builder.endBlockArgumentNesting(); | |
394 } | |
395 | |
396 // Write the trailing comma. | |
397 if (!_isLastArgument(argument)) { | |
398 visitor.token(argument.endToken.next); | |
399 } | |
400 } | |
401 | |
402 bool _isFirstArgument(Expression argument) => argument == _allArguments.first; | |
403 | |
404 bool _isLastArgument(Expression argument) => argument == _allArguments.last; | |
405 | |
406 /// Returns true if [expression] denotes a collection literal argument. | |
407 /// | |
408 /// Similar to block functions, collection arguments can get special | |
409 /// indentation to make them look more statement-like. | |
410 static bool _isCollectionArgument(Expression expression) { | |
411 if (expression is NamedExpression) { | |
412 expression = (expression as NamedExpression).expression; | |
413 } | |
414 | |
415 // TODO(rnystrom): Should we step into parenthesized expressions? | |
416 | |
417 return expression is ListLiteral || expression is MapLiteral; | |
418 } | |
419 } | |
OLD | NEW |