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 'dart:math' as math;
|
8 | |
|
9 | | import 'package:analyzer/dart/ast/ast.dart';
|
10 | | import 'package:analyzer/dart/ast/token.dart';
|
11 | |
|
12 | | import 'chunk.dart';
|
13 | | import 'rule/argument.dart';
|
14 | | import 'rule/rule.dart';
|
15 | | import 'source_visitor.dart';
|
16 | |
|
17 | | /// Helper class for [SourceVisitor] that handles visiting and writing an
|
18 | | /// [ArgumentList], including all of the special code needed to handle
|
19 | | /// block-formatted arguments.
|
20 | | class ArgumentListVisitor {
|
21 | | final SourceVisitor _visitor;
|
22 | |
|
23 | | /// The "(" before the argument list.
|
24 | | final Token _leftParenthesis;
|
25 | |
|
26 | | /// The ")" after the argument list.
|
27 | | final Token _rightParenthesis;
|
28 | |
|
29 | | /// All of the arguments, positional, named, and functions, in the argument
|
30 | | /// list.
|
31 | | final List<Expression> _allArguments;
|
32 | |
|
33 | | /// The normal arguments preceding any block function arguments.
|
34 | | final ArgumentSublist _arguments;
|
35 | |
|
36 | | /// The contiguous list of block function arguments, if any.
|
37 | | ///
|
38 | | /// Otherwise, this is `null`.
|
39 | | final List<Expression> _functions;
|
40 | |
|
41 | | /// If there are block function arguments, this is the arguments after them.
|
42 | | ///
|
43 | | /// Otherwise, this is `null`.
|
44 | | final ArgumentSublist _argumentsAfterFunctions;
|
45 | |
|
46 | | /// Returns `true` if there is only a single positional argument.
|
47 | 3 | bool get _isSingle =>
|
48 | 18 | _allArguments.length == 1 && _allArguments.single is! NamedExpression;
|
49 | |
|
50 | | /// Whether this argument list has any arguments that should be formatted as
|
51 | | /// blocks.
|
52 | | // TODO(rnystrom): Returning true based on collections is non-optimal. It
|
53 | | // forces a method chain to break into two but the result collection may not
|
54 | | // actually split which can lead to a method chain that's allowed to break
|
55 | | // where it shouldn't.
|
56 | 1 | bool get hasBlockArguments =>
|
57 | 4 | _arguments._blocks.isNotEmpty || _functions != null;
|
58 | |
|
59 | 3 | factory ArgumentListVisitor(SourceVisitor visitor, ArgumentList node) {
|
60 | 3 | return new ArgumentListVisitor.forArguments(
|
61 | 9 | visitor, node.leftParenthesis, node.rightParenthesis, node.arguments);
|
62 | | }
|
63 | |
|
64 | 3 | factory ArgumentListVisitor.forArguments(
|
65 | | SourceVisitor visitor,
|
66 | | Token leftParenthesis,
|
67 | | Token rightParenthesis,
|
68 | | List<Expression> arguments) {
|
69 | | // Look for a single contiguous range of block function arguments.
|
70 | | var functionsStart;
|
71 | | var functionsEnd;
|
72 | |
|
73 | 9 | for (var i = 0; i < arguments.length; i++) {
|
74 | 3 | var argument = arguments[i];
|
75 | 3 | if (_isBlockFunction(argument)) {
|
76 | | if (functionsStart == null) functionsStart = i;
|
77 | |
|
78 | | // The functions must be one contiguous section.
|
79 | 1 | if (functionsEnd != null && functionsEnd != i) {
|
80 | | functionsStart = null;
|
81 | | functionsEnd = null;
|
82 | | break;
|
83 | | }
|
84 | |
|
85 | 2 | functionsEnd = i + 1;
|
86 | | }
|
87 | | }
|
88 | |
|
89 | | // Edge case: If all of the arguments are named, but they aren't all
|
90 | | // functions, then don't handle the functions specially. A function with a
|
91 | | // bunch of named arguments tends to look best when they are all lined up,
|
92 | | // even the function ones (unless they are all functions).
|
93 | | //
|
94 | | // Prefers:
|
95 | | //
|
96 | | // function(
|
97 | | // named: () {
|
98 | | // something();
|
99 | | // },
|
100 | | // another: argument);
|
101 | | //
|
102 | | // Over:
|
103 | | //
|
104 | | // function(named: () {
|
105 | | // something();
|
106 | | // },
|
107 | | // another: argument);
|
108 | | if (functionsStart != null &&
|
109 | 4 | arguments[0] is NamedExpression &&
|
110 | 3 | (functionsStart > 0 || functionsEnd < arguments.length)) {
|
111 | | functionsStart = null;
|
112 | | }
|
113 | |
|
114 | | // Edge case: If all of the function arguments are named and there are
|
115 | | // other named arguments that are "=>" functions, then don't treat the
|
116 | | // block-bodied functions specially. In a mixture of the two function
|
117 | | // styles, it looks cleaner to treat them all like normal expressions so
|
118 | | // that the named arguments line up.
|
119 | | if (functionsStart != null &&
|
120 | 4 | arguments[functionsStart] is NamedExpression) {
|
121 | 1 | bool isArrow(NamedExpression named) {
|
122 | 1 | var expression = named.expression;
|
123 | |
|
124 | 1 | if (expression is FunctionExpression) {
|
125 | 2 | return expression.body is ExpressionFunctionBody;
|
126 | | }
|
127 | |
|
128 | | return false;
|
129 | | }
|
130 | |
|
131 | 2 | for (var i = 0; i < functionsStart; i++) {
|
132 | 2 | if (arguments[i] is! NamedExpression) continue;
|
133 | |
|
134 | 0 | if (isArrow(arguments[i])) {
|
135 | | functionsStart = null;
|
136 | | break;
|
137 | | }
|
138 | | }
|
139 | |
|
140 | 2 | for (var i = functionsEnd; i < arguments.length; i++) {
|
141 | 2 | if (isArrow(arguments[i])) {
|
142 | | functionsStart = null;
|
143 | | break;
|
144 | | }
|
145 | | }
|
146 | | }
|
147 | |
|
148 | | if (functionsStart == null) {
|
149 | | // No functions, so there is just a single argument list.
|
150 | 3 | return new ArgumentListVisitor._(
|
151 | | visitor,
|
152 | | leftParenthesis,
|
153 | | rightParenthesis,
|
154 | | arguments,
|
155 | 3 | new ArgumentSublist(arguments, arguments),
|
156 | | null,
|
157 | | null);
|
158 | | }
|
159 | |
|
160 | | // Split the arguments into two independent argument lists with the
|
161 | | // functions in the middle.
|
162 | 4 | var argumentsBefore = arguments.take(functionsStart).toList();
|
163 | 2 | var functions = arguments.sublist(functionsStart, functionsEnd);
|
164 | 4 | var argumentsAfter = arguments.skip(functionsEnd).toList();
|
165 | |
|
166 | 2 | return new ArgumentListVisitor._(
|
167 | | visitor,
|
168 | | leftParenthesis,
|
169 | | rightParenthesis,
|
170 | | arguments,
|
171 | 2 | new ArgumentSublist(arguments, argumentsBefore),
|
172 | | functions,
|
173 | 2 | new ArgumentSublist(arguments, argumentsAfter));
|
174 | | }
|
175 | |
|
176 | 3 | ArgumentListVisitor._(
|
177 | | this._visitor,
|
178 | | this._leftParenthesis,
|
179 | | this._rightParenthesis,
|
180 | | this._allArguments,
|
181 | | this._arguments,
|
182 | | this._functions,
|
183 | | this._argumentsAfterFunctions);
|
184 | |
|
185 | | /// Builds chunks for the argument list.
|
186 | 3 | void visit() {
|
187 | | // If there is just one positional argument, it tends to look weird to
|
188 | | // split before it, so try not to.
|
189 | 12 | if (_isSingle) _visitor.builder.startSpan();
|
190 | |
|
191 | 9 | _visitor.builder.startSpan();
|
192 | 9 | _visitor.token(_leftParenthesis);
|
193 | |
|
194 | 9 | _arguments.visit(_visitor);
|
195 | |
|
196 | 9 | _visitor.builder.endSpan();
|
197 | |
|
198 | 3 | if (_functions != null) {
|
199 | | // TODO(rnystrom): It might look better to treat the parameter list of the
|
200 | | // first function as if it were an argument in the preceding argument list
|
201 | | // instead of just having this little solo split here. That would try to
|
202 | | // keep the parameter list with other arguments when possible, and, I
|
203 | | // think, generally look nicer.
|
204 | 10 | if (_functions.first == _allArguments.first) {
|
205 | 4 | _visitor.soloZeroSplit();
|
206 | | } else {
|
207 | 2 | _visitor.soloSplit();
|
208 | | }
|
209 | |
|
210 | 4 | for (var argument in _functions) {
|
211 | 8 | if (argument != _functions.first) _visitor.space();
|
212 | |
|
213 | 4 | _visitor.visit(argument);
|
214 | |
|
215 | | // Write the following comma.
|
216 | 8 | if (argument.endToken.next.type == TokenType.COMMA) {
|
217 | 4 | _visitor.token(argument.endToken.next);
|
218 | | }
|
219 | | }
|
220 | |
|
221 | 6 | _visitor.builder.startSpan();
|
222 | 6 | _argumentsAfterFunctions.visit(_visitor);
|
223 | 6 | _visitor.builder.endSpan();
|
224 | | }
|
225 | |
|
226 | 9 | _visitor.token(_rightParenthesis);
|
227 | |
|
228 | 12 | if (_isSingle) _visitor.builder.endSpan();
|
229 | | }
|
230 | |
|
231 | | /// Returns `true` if [expression] is a [FunctionExpression] with a non-empty
|
232 | | /// block body.
|
233 | 3 | static bool _isBlockFunction(Expression expression) {
|
234 | 3 | if (expression is NamedExpression) {
|
235 | 1 | expression = (expression as NamedExpression).expression;
|
236 | | }
|
237 | |
|
238 | | // Allow functions wrapped in dotted method calls like "a.b.c(() { ... })".
|
239 | 3 | if (expression is MethodInvocation) {
|
240 | 2 | if (!_isValidWrappingTarget(expression.target)) return false;
|
241 | 4 | if (expression.argumentList.arguments.length != 1) return false;
|
242 | |
|
243 | 4 | return _isBlockFunction(expression.argumentList.arguments.single);
|
244 | | }
|
245 | |
|
246 | 3 | if (expression is InstanceCreationExpression) {
|
247 | 8 | if (expression.argumentList.arguments.length != 1) return false;
|
248 | |
|
249 | 4 | return _isBlockFunction(expression.argumentList.arguments.single);
|
250 | | }
|
251 | |
|
252 | | // Allow immediately-invoked functions like "() { ... }()".
|
253 | 3 | if (expression is FunctionExpressionInvocation) {
|
254 | | var invocation = expression as FunctionExpressionInvocation;
|
255 | 3 | if (invocation.argumentList.arguments.isNotEmpty) return false;
|
256 | |
|
257 | 1 | expression = invocation.function;
|
258 | | }
|
259 | |
|
260 | | // Unwrap parenthesized expressions.
|
261 | 3 | while (expression is ParenthesizedExpression) {
|
262 | 1 | expression = (expression as ParenthesizedExpression).expression;
|
263 | | }
|
264 | |
|
265 | | // Must be a function.
|
266 | 3 | if (expression is! FunctionExpression) return false;
|
267 | |
|
268 | | // With a curly body.
|
269 | | var function = expression as FunctionExpression;
|
270 | 4 | if (function.body is! BlockFunctionBody) return false;
|
271 | |
|
272 | | // That isn't empty.
|
273 | 2 | var body = function.body as BlockFunctionBody;
|
274 | 6 | return body.block.statements.isNotEmpty ||
|
275 | 3 | body.block.rightBracket.precedingComments != null;
|
276 | | }
|
277 | |
|
278 | | /// Returns `true` if [expression] is a valid method invocation target for
|
279 | | /// an invocation that wraps a function literal argument.
|
280 | 1 | static bool _isValidWrappingTarget(Expression expression) {
|
281 | | // Allow bare function calls.
|
282 | | if (expression == null) return true;
|
283 | |
|
284 | | // Allow property accesses.
|
285 | 1 | while (expression is PropertyAccess) {
|
286 | 1 | expression = (expression as PropertyAccess).target;
|
287 | | }
|
288 | |
|
289 | 1 | if (expression is PrefixedIdentifier) return true;
|
290 | 1 | if (expression is SimpleIdentifier) return true;
|
291 | |
|
292 | | return false;
|
293 | | }
|
294 | | }
|
295 | |
|
296 | | /// A range of arguments from a complete argument list.
|
297 | | ///
|
298 | | /// One of these typically covers all of the arguments in an invocation. But,
|
299 | | /// when an argument list has block functions in the middle, the arguments
|
300 | | /// before and after the functions are treated as separate independent lists.
|
301 | | /// In that case, there will be two of these.
|
302 | | class ArgumentSublist {
|
303 | | /// The full argument list from the AST.
|
304 | | final List<Expression> _allArguments;
|
305 | |
|
306 | | /// The positional arguments, in order.
|
307 | | final List<Expression> _positional;
|
308 | |
|
309 | | /// The named arguments, in order.
|
310 | | final List<Expression> _named;
|
311 | |
|
312 | | /// Maps each block argument, excluding functions, to the first token for that
|
313 | | /// argument.
|
314 | | final Map<Expression, Token> _blocks;
|
315 | |
|
316 | | /// The number of leading block arguments, excluding functions.
|
317 | | ///
|
318 | | /// If all arguments are blocks, this counts them.
|
319 | | final int _leadingBlocks;
|
320 | |
|
321 | | /// The number of trailing blocks arguments.
|
322 | | ///
|
323 | | /// If all arguments are blocks, this is zero.
|
324 | | final int _trailingBlocks;
|
325 | |
|
326 | | /// The rule used to split the bodies of all block arguments.
|
327 | 2 | Rule get blockRule => _blockRule;
|
328 | | Rule _blockRule;
|
329 | |
|
330 | | /// The most recent chunk that split before an argument.
|
331 | 2 | Chunk get previousSplit => _previousSplit;
|
332 | | Chunk _previousSplit;
|
333 | |
|
334 | 3 | factory ArgumentSublist(
|
335 | | List<Expression> allArguments, List<Expression> arguments) {
|
336 | | // Assumes named arguments follow all positional ones.
|
337 | | var positional =
|
338 | 12 | arguments.takeWhile((arg) => arg is! NamedExpression).toList();
|
339 | 9 | var named = arguments.skip(positional.length).toList();
|
340 | |
|
341 | 3 | var blocks = <Expression, Token>{};
|
342 | 6 | for (var argument in arguments) {
|
343 | 3 | var bracket = _blockToken(argument);
|
344 | 1 | if (bracket != null) blocks[argument] = bracket;
|
345 | | }
|
346 | |
|
347 | | // Count the leading arguments that are blocks.
|
348 | | var leadingBlocks = 0;
|
349 | 6 | for (var argument in arguments) {
|
350 | 3 | if (!blocks.containsKey(argument)) break;
|
351 | 1 | leadingBlocks++;
|
352 | | }
|
353 | |
|
354 | | // Count the trailing arguments that are blocks.
|
355 | | var trailingBlocks = 0;
|
356 | 6 | if (leadingBlocks != arguments.length) {
|
357 | 6 | for (var argument in arguments.reversed) {
|
358 | 3 | if (!blocks.containsKey(argument)) break;
|
359 | 1 | trailingBlocks++;
|
360 | | }
|
361 | | }
|
362 | |
|
363 | | // Blocks must all be a prefix or suffix of the argument list (and not
|
364 | | // both).
|
365 | 6 | if (leadingBlocks != blocks.length) leadingBlocks = 0;
|
366 | 6 | if (trailingBlocks != blocks.length) trailingBlocks = 0;
|
367 | |
|
368 | | // Ignore any blocks in the middle of the argument list.
|
369 | 6 | if (leadingBlocks == 0 && trailingBlocks == 0) {
|
370 | 3 | blocks.clear();
|
371 | | }
|
372 | |
|
373 | 3 | return new ArgumentSublist._(
|
374 | | allArguments, positional, named, blocks, leadingBlocks, trailingBlocks);
|
375 | | }
|
376 | |
|
377 | 3 | ArgumentSublist._(this._allArguments, this._positional, this._named,
|
378 | | this._blocks, this._leadingBlocks, this._trailingBlocks);
|
379 | |
|
380 | 3 | void visit(SourceVisitor visitor) {
|
381 | 6 | if (_blocks.isNotEmpty) {
|
382 | 2 | _blockRule = new Rule(Cost.splitBlocks);
|
383 | | }
|
384 | |
|
385 | 3 | var rule = _visitPositional(visitor);
|
386 | 3 | _visitNamed(visitor, rule);
|
387 | | }
|
388 | |
|
389 | | /// Writes the positional arguments, if any.
|
390 | 3 | PositionalRule _visitPositional(SourceVisitor visitor) {
|
391 | 6 | if (_positional.isEmpty) return null;
|
392 | |
|
393 | | // Allow splitting after "(".
|
394 | | // Only count the blocks in the positional rule.
|
395 | 14 | var leadingBlocks = math.min(_leadingBlocks, _positional.length);
|
396 | 17 | var trailingBlocks = math.max(_trailingBlocks - _named.length, 0);
|
397 | 6 | var rule = new PositionalRule(_blockRule, leadingBlocks, trailingBlocks);
|
398 | 6 | _visitArguments(visitor, _positional, rule);
|
399 | |
|
400 | | return rule;
|
401 | | }
|
402 | |
|
403 | | /// Writes the named arguments, if any.
|
404 | 3 | void _visitNamed(SourceVisitor visitor, PositionalRule positionalRule) {
|
405 | 6 | if (_named.isEmpty) return;
|
406 | |
|
407 | | // Only count the blocks in the named rule.
|
408 | 9 | var leadingBlocks = math.max(_leadingBlocks - _positional.length, 0);
|
409 | 8 | var trailingBlocks = math.min(_trailingBlocks, _named.length);
|
410 | 2 | var namedRule = new NamedRule(_blockRule, leadingBlocks, trailingBlocks);
|
411 | |
|
412 | | // Let the positional args force the named ones to split.
|
413 | | if (positionalRule != null) {
|
414 | 1 | positionalRule.setNamedArgsRule(namedRule);
|
415 | | }
|
416 | |
|
417 | 2 | _visitArguments(visitor, _named, namedRule);
|
418 | | }
|
419 | |
|
420 | 3 | void _visitArguments(
|
421 | | SourceVisitor visitor, List<Expression> arguments, ArgumentRule rule) {
|
422 | 6 | visitor.builder.startRule(rule);
|
423 | |
|
424 | | // Split before the first argument.
|
425 | 3 | _previousSplit =
|
426 | 18 | visitor.builder.split(space: arguments.first != _allArguments.first);
|
427 | 6 | rule.beforeArgument(_previousSplit);
|
428 | |
|
429 | | // Try to not split the positional arguments.
|
430 | 6 | if (arguments == _positional) {
|
431 | 6 | visitor.builder.startSpan(Cost.positionalArguments);
|
432 | | }
|
433 | |
|
434 | 6 | for (var argument in arguments) {
|
435 | 3 | _visitArgument(visitor, rule, argument);
|
436 | |
|
437 | | // Write the split.
|
438 | 6 | if (argument != arguments.last) {
|
439 | 2 | _previousSplit = visitor.split();
|
440 | 2 | rule.beforeArgument(_previousSplit);
|
441 | | }
|
442 | | }
|
443 | |
|
444 | 12 | if (arguments == _positional) visitor.builder.endSpan();
|
445 | |
|
446 | 6 | visitor.builder.endRule();
|
447 | | }
|
448 | |
|
449 | 3 | void _visitArgument(
|
450 | | SourceVisitor visitor, ArgumentRule rule, Expression argument) {
|
451 | | // If we're about to write a block argument, handle it specially.
|
452 | 6 | if (_blocks.containsKey(argument)) {
|
453 | 1 | rule.disableSplitOnInnerRules();
|
454 | |
|
455 | | // Tell it to use the rule we've already created.
|
456 | 3 | visitor.beforeBlock(_blocks[argument], this);
|
457 | 9 | } else if (_allArguments.length > 1) {
|
458 | | // Edge case: Only bump the nesting if there are multiple arguments. This
|
459 | | // lets us avoid spurious indentation in cases like:
|
460 | | //
|
461 | | // function(function(() {
|
462 | | // body;
|
463 | | // }));
|
464 | 2 | visitor.builder.startBlockArgumentNesting();
|
465 | 3 | } else if (argument is! NamedExpression) {
|
466 | | // Edge case: Likewise, don't force the argument to split if there is
|
467 | | // only a single positional one, like:
|
468 | | //
|
469 | | // outer(inner(
|
470 | | // longArgument));
|
471 | 3 | rule.disableSplitOnInnerRules();
|
472 | | }
|
473 | |
|
474 | 3 | if (argument is NamedExpression) {
|
475 | 1 | visitor.visitNamedArgument(argument, rule as NamedRule);
|
476 | | } else {
|
477 | 3 | visitor.visit(argument);
|
478 | | }
|
479 | |
|
480 | 6 | if (_blocks.containsKey(argument)) {
|
481 | 1 | rule.enableSplitOnInnerRules();
|
482 | 9 | } else if (_allArguments.length > 1) {
|
483 | 2 | visitor.builder.endBlockArgumentNesting();
|
484 | 3 | } else if (argument is! NamedExpression) {
|
485 | 3 | rule.enableSplitOnInnerRules();
|
486 | | }
|
487 | |
|
488 | | // Write the following comma.
|
489 | 12 | if (argument.endToken.next.type == TokenType.COMMA) {
|
490 | 3 | visitor.token(argument.endToken.next);
|
491 | | }
|
492 | | }
|
493 | |
|
494 | | /// If [expression] can be formatted as a block, returns the token that opens
|
495 | | /// the block, such as a collection's bracket.
|
496 | | ///
|
497 | | /// Block-formatted arguments can get special indentation to make them look
|
498 | | /// more statement-like.
|
499 | 3 | static Token _blockToken(Expression expression) {
|
500 | 3 | if (expression is NamedExpression) {
|
501 | 1 | expression = (expression as NamedExpression).expression;
|
502 | | }
|
503 | |
|
504 | | // TODO(rnystrom): Should we step into parenthesized expressions?
|
505 | |
|
506 | 4 | if (expression is ListLiteral) return expression.leftBracket;
|
507 | 4 | if (expression is MapLiteral) return expression.leftBracket;
|
508 | 5 | if (expression is SingleStringLiteral && expression.isMultiline) {
|
509 | 1 | return expression.beginToken;
|
510 | | }
|
511 | |
|
512 | | // Not a collection literal.
|
513 | | return null;
|
514 | | }
|
515 | | }
|