Chromium Code Reviews

Side by Side Diff: dart_style/lib/src/argument_list_visitor.dart

Issue 1400473008: Roll Observatory packages and add a roll script (Closed) Base URL: git@github.com:dart-lang/observatory_pub_packages.git@master
Patch Set: Created 5 years, 2 months ago
Use n/p to move between diff chunks; N/P to move between comments.
Jump to:
View unified diff |
« no previous file with comments | « dart_style/lib/src/._formatter_options.dart ('k') | dart_style/lib/src/call_chain_visitor.dart » ('j') | no next file with comments »
Toggle Intra-line Diffs ('i') | Expand Comments ('e') | Collapse Comments ('c') | Show Comments Hide Comments ('s')
OLDNEW
(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 }
OLDNEW
« no previous file with comments | « dart_style/lib/src/._formatter_options.dart ('k') | dart_style/lib/src/call_chain_visitor.dart » ('j') | no next file with comments »

Powered by Google App Engine