OLD | NEW |
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.argument_list_visitor; | 5 library dart_style.src.argument_list_visitor; |
6 | 6 |
| 7 import 'dart:math' as math; |
| 8 |
7 import 'package:analyzer/analyzer.dart'; | 9 import 'package:analyzer/analyzer.dart'; |
| 10 import 'package:analyzer/src/generated/scanner.dart'; |
8 | 11 |
9 import 'chunk.dart'; | 12 import 'chunk.dart'; |
10 import 'rule/argument.dart'; | 13 import 'rule/argument.dart'; |
11 import 'rule/rule.dart'; | 14 import 'rule/rule.dart'; |
12 import 'source_visitor.dart'; | 15 import 'source_visitor.dart'; |
13 | 16 |
14 /// Helper class for [SourceVisitor] that handles visiting and writing an | 17 /// Helper class for [SourceVisitor] that handles visiting and writing an |
15 /// [ArgumentList], including all of the special code needed to handle function | 18 /// [ArgumentList], including all of the special code needed to handle function |
16 /// and collection arguments. | 19 /// and collection arguments. |
17 class ArgumentListVisitor { | 20 class ArgumentListVisitor { |
(...skipping 12 matching lines...) Expand all Loading... |
30 /// If there are block function arguments, this is the arguments after them. | 33 /// If there are block function arguments, this is the arguments after them. |
31 /// | 34 /// |
32 /// Otherwise, this is `null`. | 35 /// Otherwise, this is `null`. |
33 final ArgumentSublist _argumentsAfterFunctions; | 36 final ArgumentSublist _argumentsAfterFunctions; |
34 | 37 |
35 /// Returns `true` if there is only a single positional argument. | 38 /// Returns `true` if there is only a single positional argument. |
36 bool get _isSingle => | 39 bool get _isSingle => |
37 _node.arguments.length == 1 && _node.arguments.single is! NamedExpression; | 40 _node.arguments.length == 1 && _node.arguments.single is! NamedExpression; |
38 | 41 |
39 /// Whether this argument list has any collection or block function arguments. | 42 /// Whether this argument list has any collection or block function arguments. |
| 43 // TODO(rnystrom): Returning true based on collections is non-optimal. It |
| 44 // forces a method chain to break into two but the result collection may not |
| 45 // actually split which can lead to a method chain that's allowed to break |
| 46 // where it shouldn't. |
40 bool get hasBlockArguments => | 47 bool get hasBlockArguments => |
41 _arguments._collections.isNotEmpty || _functions != null; | 48 _arguments._collections.isNotEmpty || _functions != null; |
42 | 49 |
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) { | 50 factory ArgumentListVisitor(SourceVisitor visitor, ArgumentList node) { |
60 // Look for a single contiguous range of block function arguments. | 51 // Look for a single contiguous range of block function arguments. |
61 var functionsStart; | 52 var functionsStart; |
62 var functionsEnd; | 53 var functionsEnd; |
63 | 54 |
64 for (var i = 0; i < node.arguments.length; i++) { | 55 for (var i = 0; i < node.arguments.length; i++) { |
65 var argument = node.arguments[i]; | 56 var argument = node.arguments[i]; |
66 if (_isBlockFunction(argument)) { | 57 if (_isBlockFunction(argument)) { |
67 if (functionsStart == null) functionsStart = i; | 58 if (functionsStart == null) functionsStart = i; |
68 | 59 |
(...skipping 75 matching lines...) Expand 10 before | Expand all | Expand 10 after Loading... |
144 _visitor.builder.endSpan(); | 135 _visitor.builder.endSpan(); |
145 } | 136 } |
146 | 137 |
147 _visitor.token(_node.rightParenthesis); | 138 _visitor.token(_node.rightParenthesis); |
148 | 139 |
149 _visitor.builder.unnest(); | 140 _visitor.builder.unnest(); |
150 | 141 |
151 if (_isSingle) _visitor.builder.endSpan(); | 142 if (_isSingle) _visitor.builder.endSpan(); |
152 } | 143 } |
153 | 144 |
154 /// Returns `true` if [expression] is a [FunctionExpression] with a block | 145 /// Returns `true` if [expression] is a [FunctionExpression] with a non-empty |
155 /// body. | 146 /// block body. |
156 static bool _isBlockFunction(Expression expression) { | 147 static bool _isBlockFunction(Expression expression) { |
157 if (expression is NamedExpression) { | 148 if (expression is NamedExpression) { |
158 expression = (expression as NamedExpression).expression; | 149 expression = (expression as NamedExpression).expression; |
159 } | 150 } |
160 | 151 |
161 // Allow functions wrapped in dotted method calls like "a.b.c(() { ... })". | 152 // Allow functions wrapped in dotted method calls like "a.b.c(() { ... })". |
162 if (expression is MethodInvocation) { | 153 if (expression is MethodInvocation) { |
163 if (!_isValidWrappingTarget(expression.target)) return false; | 154 if (!_isValidWrappingTarget(expression.target)) return false; |
164 if (expression.argumentList.arguments.length != 1) return false; | 155 if (expression.argumentList.arguments.length != 1) return false; |
165 | 156 |
166 return _isBlockFunction(expression.argumentList.arguments.single); | 157 return _isBlockFunction(expression.argumentList.arguments.single); |
167 } | 158 } |
168 | 159 |
169 // Curly body functions are. | 160 if (expression is InstanceCreationExpression) { |
| 161 if (expression.argumentList.arguments.length != 1) return false; |
| 162 |
| 163 return _isBlockFunction(expression.argumentList.arguments.single); |
| 164 } |
| 165 |
| 166 // Must be a function. |
170 if (expression is! FunctionExpression) return false; | 167 if (expression is! FunctionExpression) return false; |
| 168 |
| 169 // With a curly body. |
171 var function = expression as FunctionExpression; | 170 var function = expression as FunctionExpression; |
172 return function.body is BlockFunctionBody; | 171 if (function.body is! BlockFunctionBody) return false; |
| 172 |
| 173 // That isn't empty. |
| 174 var body = function.body as BlockFunctionBody; |
| 175 return body.block.statements.isNotEmpty || |
| 176 body.block.rightBracket.precedingComments != null; |
173 } | 177 } |
174 | 178 |
175 /// Returns `true` if [expression] is a valid method invocation target for | 179 /// Returns `true` if [expression] is a valid method invocation target for |
176 /// an invocation that wraps a function literal argument. | 180 /// an invocation that wraps a function literal argument. |
177 static bool _isValidWrappingTarget(Expression expression) { | 181 static bool _isValidWrappingTarget(Expression expression) { |
178 // Allow bare function calls. | 182 // Allow bare function calls. |
179 if (expression == null) return true; | 183 if (expression == null) return true; |
180 | 184 |
181 // Allow property accesses. | 185 // Allow property accesses. |
182 while (expression is PropertyAccess) { | 186 while (expression is PropertyAccess) { |
(...skipping 16 matching lines...) Expand all Loading... |
199 class ArgumentSublist { | 203 class ArgumentSublist { |
200 /// The full argument list from the AST. | 204 /// The full argument list from the AST. |
201 final List<Expression> _allArguments; | 205 final List<Expression> _allArguments; |
202 | 206 |
203 /// The positional arguments, in order. | 207 /// The positional arguments, in order. |
204 final List<Expression> _positional; | 208 final List<Expression> _positional; |
205 | 209 |
206 /// The named arguments, in order. | 210 /// The named arguments, in order. |
207 final List<Expression> _named; | 211 final List<Expression> _named; |
208 | 212 |
209 /// The arguments that are collection literals that get special formatting. | 213 /// Maps each argument that is a collection literal that get special |
210 final Set<Expression> _collections; | 214 /// formatting to the token for the collection's open bracket. |
| 215 final Map<Expression, Token> _collections; |
211 | 216 |
212 /// The number of leading collections. | 217 /// The number of leading collections. |
213 /// | 218 /// |
214 /// If all arguments are collections, this counts them. | 219 /// If all arguments are collections, this counts them. |
215 final int _leadingCollections; | 220 final int _leadingCollections; |
216 | 221 |
217 /// The number of trailing collections. | 222 /// The number of trailing collections. |
218 /// | 223 /// |
219 /// If all arguments are collections, this is zero. | 224 /// If all arguments are collections, this is zero. |
220 final int _trailingCollections; | 225 final int _trailingCollections; |
221 | 226 |
222 /// The rule used to split the bodies of all of the collection arguments. | 227 /// The rule used to split the bodies of all of the collection arguments. |
223 Rule get _collectionRule { | 228 Rule get collectionRule => _collectionRule; |
224 // Lazy initialize. | 229 Rule _collectionRule; |
225 if (_collectionRuleField == null && _collections.isNotEmpty) { | |
226 _collectionRuleField = new SimpleRule(cost: Cost.splitCollections); | |
227 } | |
228 | 230 |
229 return _collectionRuleField; | 231 /// The most recent chunk that split before an argument. |
230 } | 232 Chunk get previousSplit => _previousSplit; |
231 | 233 Chunk _previousSplit; |
232 Rule _collectionRuleField; | |
233 | 234 |
234 bool get _hasMultipleArguments => _positional.length + _named.length > 1; | 235 bool get _hasMultipleArguments => _positional.length + _named.length > 1; |
235 | 236 |
236 factory ArgumentSublist( | 237 factory ArgumentSublist( |
237 List<Expression> allArguments, List<Expression> arguments) { | 238 List<Expression> allArguments, List<Expression> arguments) { |
238 // Assumes named arguments follow all positional ones. | 239 // Assumes named arguments follow all positional ones. |
239 var positional = | 240 var positional = |
240 arguments.takeWhile((arg) => arg is! NamedExpression).toList(); | 241 arguments.takeWhile((arg) => arg is! NamedExpression).toList(); |
241 var named = arguments.skip(positional.length).toList(); | 242 var named = arguments.skip(positional.length).toList(); |
242 | 243 |
243 var collections = arguments.where(_isCollectionArgument).toSet(); | 244 var collections = {}; |
| 245 for (var argument in arguments) { |
| 246 var bracket = _getCollectionBracket(argument); |
| 247 if (bracket != null) collections[argument] = bracket; |
| 248 } |
244 | 249 |
245 // Count the leading arguments that are collection literals. | 250 // Count the leading arguments that are collection literals. |
246 var leadingCollections = 0; | 251 var leadingCollections = 0; |
247 for (var argument in arguments) { | 252 for (var argument in arguments) { |
248 if (!collections.contains(argument)) break; | 253 if (!collections.containsKey(argument)) break; |
249 leadingCollections++; | 254 leadingCollections++; |
250 } | 255 } |
251 | 256 |
252 // Count the trailing arguments that are collection literals. | 257 // Count the trailing arguments that are collection literals. |
253 var trailingCollections = 0; | 258 var trailingCollections = 0; |
254 if (leadingCollections != arguments.length) { | 259 if (leadingCollections != arguments.length) { |
255 for (var argument in arguments.reversed) { | 260 for (var argument in arguments.reversed) { |
256 if (!collections.contains(argument)) break; | 261 if (!collections.containsKey(argument)) break; |
257 trailingCollections++; | 262 trailingCollections++; |
258 } | 263 } |
259 } | 264 } |
260 | 265 |
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 | 266 // Collections must all be a prefix or suffix of the argument list (and not |
272 // both). | 267 // both). |
273 if (leadingCollections != collections.length) leadingCollections = 0; | 268 if (leadingCollections != collections.length) leadingCollections = 0; |
274 if (trailingCollections != collections.length) trailingCollections = 0; | 269 if (trailingCollections != collections.length) trailingCollections = 0; |
275 | 270 |
276 // Ignore any collections in the middle of the argument list. | 271 // Ignore any collections in the middle of the argument list. |
277 if (leadingCollections == 0 && trailingCollections == 0) { | 272 if (leadingCollections == 0 && trailingCollections == 0) { |
278 collections.clear(); | 273 collections.clear(); |
279 } | 274 } |
280 | 275 |
281 return new ArgumentSublist._(allArguments, positional, named, collections, | 276 return new ArgumentSublist._(allArguments, positional, named, collections, |
282 leadingCollections, trailingCollections); | 277 leadingCollections, trailingCollections); |
283 } | 278 } |
284 | 279 |
285 ArgumentSublist._(this._allArguments, this._positional, this._named, | 280 ArgumentSublist._(this._allArguments, this._positional, this._named, |
286 this._collections, this._leadingCollections, this._trailingCollections); | 281 this._collections, this._leadingCollections, this._trailingCollections); |
287 | 282 |
288 void visit(SourceVisitor visitor) { | 283 void visit(SourceVisitor visitor) { |
| 284 if (_collections.isNotEmpty) { |
| 285 _collectionRule = new Rule(Cost.splitCollections); |
| 286 } |
| 287 |
289 var rule = _visitPositional(visitor); | 288 var rule = _visitPositional(visitor); |
290 _visitNamed(visitor, rule); | 289 _visitNamed(visitor, rule); |
291 } | 290 } |
292 | 291 |
293 /// Writes the positional arguments, if any. | 292 /// Writes the positional arguments, if any. |
294 PositionalRule _visitPositional(SourceVisitor visitor) { | 293 PositionalRule _visitPositional(SourceVisitor visitor) { |
295 if (_positional.isEmpty) return null; | 294 if (_positional.isEmpty) return null; |
296 | 295 |
297 // Allow splitting after "(". | 296 // Allow splitting after "(". |
298 var rule; | 297 var rule; |
299 if (_positional.length == 1) { | 298 if (_positional.length == 1) { |
300 rule = new SinglePositionalRule(_collectionRule, | 299 rule = new SinglePositionalRule(_collectionRule, |
301 splitsOnInnerRules: _allArguments.length > 1 && | 300 splitsOnInnerRules: _allArguments.length > 1 && |
302 !_isCollectionArgument(_positional.first)); | 301 !_collections.containsKey(_positional.first)); |
303 } else { | 302 } else { |
304 // Only count the positional bodies in the positional rule. | 303 // Only count the collections in the positional rule. |
305 var leadingPositional = _leadingCollections; | 304 var leadingCollections = |
306 if (_leadingCollections == _positional.length + _named.length) { | 305 math.min(_leadingCollections, _positional.length); |
307 leadingPositional -= _named.length; | 306 var trailingCollections = |
308 } | 307 math.max(_trailingCollections - _named.length, 0); |
309 | |
310 var trailingPositional = _trailingCollections - _named.length; | |
311 rule = new MultiplePositionalRule( | 308 rule = new MultiplePositionalRule( |
312 _collectionRule, leadingPositional, trailingPositional); | 309 _collectionRule, leadingCollections, trailingCollections); |
313 } | 310 } |
314 | 311 |
315 visitor.builder.startRule(rule); | 312 _visitArguments(visitor, _positional, 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; | 313 return rule; |
341 } | 314 } |
342 | 315 |
343 /// Writes the named arguments, if any. | 316 /// Writes the named arguments, if any. |
344 void _visitNamed(SourceVisitor visitor, PositionalRule rule) { | 317 void _visitNamed(SourceVisitor visitor, PositionalRule positionalRule) { |
345 if (_named.isEmpty) return; | 318 if (_named.isEmpty) return; |
346 | 319 |
347 var positionalRule = rule; | 320 // Only count the collections in the named rule. |
348 var namedRule = new NamedRule(_collectionRule); | 321 var leadingCollections = |
349 visitor.builder.startRule(namedRule); | 322 math.max(_leadingCollections - _positional.length, 0); |
| 323 var trailingCollections = math.min(_trailingCollections, _named.length); |
| 324 var namedRule = |
| 325 new NamedRule(_collectionRule, leadingCollections, trailingCollections); |
350 | 326 |
351 // Let the positional args force the named ones to split. | 327 // Let the positional args force the named ones to split. |
352 if (positionalRule != null) { | 328 if (positionalRule != null) { |
353 positionalRule.setNamedArgsRule(namedRule); | 329 positionalRule.setNamedArgsRule(namedRule); |
354 } | 330 } |
355 | 331 |
356 // Split before the first named argument. | 332 _visitArguments(visitor, _named, namedRule); |
357 namedRule.beforeArguments( | 333 } |
358 visitor.builder.split(space: !_isFirstArgument(_named.first))); | |
359 | 334 |
360 for (var argument in _named) { | 335 void _visitArguments( |
361 _visitArgument(visitor, namedRule, argument); | 336 SourceVisitor visitor, List<Expression> arguments, ArgumentRule rule) { |
| 337 visitor.builder.startRule(rule); |
| 338 |
| 339 // Split before the first argument. |
| 340 _previousSplit = |
| 341 visitor.builder.split(space: !_isFirstArgument(arguments.first)); |
| 342 rule.beforeArgument(_previousSplit); |
| 343 |
| 344 // Try to not split the positional arguments. |
| 345 if (arguments == _positional) { |
| 346 visitor.builder.startSpan(Cost.positionalArguments); |
| 347 } |
| 348 |
| 349 for (var argument in arguments) { |
| 350 _visitArgument(visitor, rule, argument); |
362 | 351 |
363 // Write the split. | 352 // Write the split. |
364 if (argument != _named.last) visitor.split(); | 353 if (argument != arguments.last) { |
| 354 _previousSplit = visitor.split(); |
| 355 rule.beforeArgument(_previousSplit); |
| 356 } |
365 } | 357 } |
366 | 358 |
| 359 if (arguments == _positional) visitor.builder.endSpan(); |
| 360 |
367 visitor.builder.endRule(); | 361 visitor.builder.endRule(); |
368 } | 362 } |
369 | 363 |
370 void _visitArgument( | 364 void _visitArgument( |
371 SourceVisitor visitor, ArgumentRule rule, Expression argument) { | 365 SourceVisitor visitor, ArgumentRule rule, Expression argument) { |
372 // If we're about to write a collection argument, handle it specially. | 366 // If we're about to write a collection argument, handle it specially. |
373 if (_collections.contains(argument)) { | 367 if (_collections.containsKey(argument)) { |
374 if (rule != null) rule.beforeCollection(); | 368 if (rule != null) rule.beforeCollection(); |
375 | 369 |
376 // Tell it to use the rule we've already created. | 370 // Tell it to use the rule we've already created. |
377 visitor.setNextLiteralBodyRule(_collectionRule); | 371 visitor.beforeCollection(_collections[argument], this); |
378 } else if (_hasMultipleArguments) { | 372 } else if (_hasMultipleArguments) { |
379 // Corner case: If there is just a single argument, don't bump the | 373 // Edge case: If there is just a single argument, don't bump the nesting. |
380 // nesting. This lets us avoid spurious indentation in cases like: | 374 // This lets us avoid spurious indentation in cases like: |
381 // | 375 // |
382 // function(function(() { | 376 // function(function(() { |
383 // body; | 377 // body; |
384 // })); | 378 // })); |
385 visitor.builder.startBlockArgumentNesting(); | 379 visitor.builder.startBlockArgumentNesting(); |
386 } | 380 } |
387 | 381 |
388 visitor.visit(argument); | 382 visitor.visit(argument); |
389 | 383 |
390 if (_collections.contains(argument)) { | 384 if (_collections.containsKey(argument)) { |
391 if (rule != null) rule.afterCollection(); | 385 if (rule != null) rule.afterCollection(); |
392 } else if (_hasMultipleArguments) { | 386 } else if (_hasMultipleArguments) { |
393 visitor.builder.endBlockArgumentNesting(); | 387 visitor.builder.endBlockArgumentNesting(); |
394 } | 388 } |
395 | 389 |
396 // Write the trailing comma. | 390 // Write the trailing comma. |
397 if (!_isLastArgument(argument)) { | 391 if (!_isLastArgument(argument)) { |
398 visitor.token(argument.endToken.next); | 392 visitor.token(argument.endToken.next); |
399 } | 393 } |
400 } | 394 } |
401 | 395 |
402 bool _isFirstArgument(Expression argument) => argument == _allArguments.first; | 396 bool _isFirstArgument(Expression argument) => argument == _allArguments.first; |
403 | 397 |
404 bool _isLastArgument(Expression argument) => argument == _allArguments.last; | 398 bool _isLastArgument(Expression argument) => argument == _allArguments.last; |
405 | 399 |
406 /// Returns true if [expression] denotes a collection literal argument. | 400 /// Returns the token for the left bracket if [expression] denotes a |
| 401 /// collection literal argument. |
407 /// | 402 /// |
408 /// Similar to block functions, collection arguments can get special | 403 /// Similar to block functions, collection arguments can get special |
409 /// indentation to make them look more statement-like. | 404 /// indentation to make them look more statement-like. |
410 static bool _isCollectionArgument(Expression expression) { | 405 static Token _getCollectionBracket(Expression expression) { |
411 if (expression is NamedExpression) { | 406 if (expression is NamedExpression) { |
412 expression = (expression as NamedExpression).expression; | 407 expression = (expression as NamedExpression).expression; |
413 } | 408 } |
414 | 409 |
415 // TODO(rnystrom): Should we step into parenthesized expressions? | 410 // TODO(rnystrom): Should we step into parenthesized expressions? |
416 | 411 |
417 return expression is ListLiteral || expression is MapLiteral; | 412 if (expression is ListLiteral) return expression.leftBracket; |
| 413 if (expression is MapLiteral) return expression.leftBracket; |
| 414 |
| 415 // Not a collection literal. |
| 416 return null; |
418 } | 417 } |
419 } | 418 } |
OLD | NEW |