| 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.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'; |
| (...skipping 76 matching lines...) Expand 10 before | Expand all | Expand 10 after Loading... |
| 87 | 87 |
| 88 /// Creates a new call chain visitor for [visitor] starting with [node]. | 88 /// Creates a new call chain visitor for [visitor] starting with [node]. |
| 89 /// | 89 /// |
| 90 /// The [node] is the outermost expression containing the chained "." | 90 /// The [node] is the outermost expression containing the chained "." |
| 91 /// operators and must be a [MethodInvocation], [PropertyAccess] or | 91 /// operators and must be a [MethodInvocation], [PropertyAccess] or |
| 92 /// [PrefixedIdentifier]. | 92 /// [PrefixedIdentifier]. |
| 93 factory CallChainVisitor(SourceVisitor visitor, Expression node) { | 93 factory CallChainVisitor(SourceVisitor visitor, Expression node) { |
| 94 var target; | 94 var target; |
| 95 | 95 |
| 96 // 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. |
| 97 var calls = []; | 97 var calls = <Expression>[]; |
| 98 flatten(expression) { | 98 flatten(expression) { |
| 99 target = expression; | 99 target = expression; |
| 100 | 100 |
| 101 // Treat index expressions where the target is a valid call in a method | 101 // Treat index expressions where the target is a valid call in a method |
| 102 // chain as being part of the call. Handles cases like: | 102 // chain as being part of the call. Handles cases like: |
| 103 // | 103 // |
| 104 // receiver | 104 // receiver |
| 105 // .property | 105 // .property |
| 106 // .property[0] | 106 // .property[0] |
| 107 // .property | 107 // .property |
| (...skipping 15 matching lines...) Expand all Loading... |
| 123 | 123 |
| 124 flatten(node); | 124 flatten(node); |
| 125 | 125 |
| 126 // 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 |
| 127 // 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 |
| 128 // rest of the chain. Allows code like: | 128 // rest of the chain. Allows code like: |
| 129 // | 129 // |
| 130 // address.street.number | 130 // address.street.number |
| 131 // .toString() | 131 // .toString() |
| 132 // .length; | 132 // .length; |
| 133 var properties = []; | 133 var properties = <Expression>[]; |
| 134 if (target is SimpleIdentifier) { | 134 if (target is SimpleIdentifier) { |
| 135 properties = calls.takeWhile((call) { | 135 properties = calls.takeWhile((call) { |
| 136 // Step into index expressions to see what the index is on. | 136 // Step into index expressions to see what the index is on. |
| 137 while (call is IndexExpression) call = call.target; | 137 while (call is IndexExpression) { |
| 138 call = (call as IndexExpression).target; |
| 139 } |
| 138 return call is! MethodInvocation; | 140 return call is! MethodInvocation; |
| 139 }).toList(); | 141 }).toList(); |
| 140 } | 142 } |
| 141 | 143 |
| 142 calls.removeRange(0, properties.length); | 144 calls.removeRange(0, properties.length); |
| 143 | 145 |
| 144 // Separate out the block calls, if there are any. | 146 // Separate out the block calls, if there are any. |
| 145 var blockCalls; | 147 List<Expression> blockCalls; |
| 146 var hangingCall; | 148 var hangingCall; |
| 147 | 149 |
| 148 var inBlockCalls = false; | 150 var inBlockCalls = false; |
| 149 for (var call in calls) { | 151 for (var call in calls) { |
| 150 // See if this call is a method call whose arguments are block formatted. | 152 // See if this call is a method call whose arguments are block formatted. |
| 151 var isBlockCall = false; | 153 var isBlockCall = false; |
| 152 if (call is MethodInvocation) { | 154 if (call is MethodInvocation) { |
| 153 var args = new ArgumentListVisitor(visitor, call.argumentList); | 155 var args = new ArgumentListVisitor(visitor, call.argumentList); |
| 154 isBlockCall = args.hasBlockArguments; | 156 isBlockCall = args.hasBlockArguments; |
| 155 } | 157 } |
| (...skipping 44 matching lines...) Expand 10 before | Expand all | Expand 10 after Loading... |
| 200 | 202 |
| 201 // Try to keep the entire method invocation one line. | 203 // Try to keep the entire method invocation one line. |
| 202 _visitor.builder.startSpan(); | 204 _visitor.builder.startSpan(); |
| 203 | 205 |
| 204 // If a split in the target expression forces the first `.` to split, then | 206 // If a split in the target expression forces the first `.` to split, then |
| 205 // start the rule now so that it surrounds the target. | 207 // start the rule now so that it surrounds the target. |
| 206 var splitOnTarget = _forcesSplit(_target); | 208 var splitOnTarget = _forcesSplit(_target); |
| 207 | 209 |
| 208 if (splitOnTarget) { | 210 if (splitOnTarget) { |
| 209 if (_properties.length > 1) { | 211 if (_properties.length > 1) { |
| 210 _propertyRule = new MultiplePositionalRule(null, 0, 0); | 212 _propertyRule = new PositionalRule(null, 0, 0); |
| 211 _visitor.builder.startLazyRule(_propertyRule); | 213 _visitor.builder.startLazyRule(_propertyRule); |
| 212 } else if (_calls.isNotEmpty) { | 214 } else if (_calls.isNotEmpty) { |
| 213 _enableRule(lazy: true); | 215 _enableRule(lazy: true); |
| 214 } | 216 } |
| 215 } | 217 } |
| 216 | 218 |
| 217 _visitor.visit(_target); | 219 _visitor.visit(_target); |
| 218 | 220 |
| 219 // Leading properties split like positional arguments: either not at all, | 221 // Leading properties split like positional arguments: either not at all, |
| 220 // before one ".", or before all of them. | 222 // before one ".", or before all of them. |
| 221 if (_properties.length == 1) { | 223 if (_properties.length == 1) { |
| 222 _visitor.soloZeroSplit(); | 224 _visitor.soloZeroSplit(); |
| 223 _writeCall(_properties.single); | 225 _writeCall(_properties.single); |
| 224 } else if (_properties.length > 1) { | 226 } else if (_properties.length > 1) { |
| 225 if (!splitOnTarget) { | 227 if (!splitOnTarget) { |
| 226 _propertyRule = new MultiplePositionalRule(null, 0, 0); | 228 _propertyRule = new PositionalRule(null, 0, 0); |
| 227 _visitor.builder.startRule(_propertyRule); | 229 _visitor.builder.startRule(_propertyRule); |
| 228 } | 230 } |
| 229 | 231 |
| 230 for (var property in _properties) { | 232 for (var property in _properties) { |
| 231 _propertyRule.beforeArgument(_visitor.zeroSplit()); | 233 _propertyRule.beforeArgument(_visitor.zeroSplit()); |
| 232 _writeCall(property); | 234 _writeCall(property); |
| 233 } | 235 } |
| 234 | 236 |
| 235 _visitor.builder.endRule(); | 237 _visitor.builder.endRule(); |
| 236 } | 238 } |
| (...skipping 71 matching lines...) Expand 10 before | Expand all | Expand 10 after Loading... |
| 308 | 310 |
| 309 // Don't split right after a non-empty curly-bodied function. | 311 // Don't split right after a non-empty curly-bodied function. |
| 310 if (expression is FunctionExpression) { | 312 if (expression is FunctionExpression) { |
| 311 if (expression.body is! BlockFunctionBody) return false; | 313 if (expression.body is! BlockFunctionBody) return false; |
| 312 | 314 |
| 313 return (expression.body as BlockFunctionBody).block.statements.isEmpty; | 315 return (expression.body as BlockFunctionBody).block.statements.isEmpty; |
| 314 } | 316 } |
| 315 | 317 |
| 316 // If the expression ends in an argument list, base the splitting on the | 318 // If the expression ends in an argument list, base the splitting on the |
| 317 // last argument. | 319 // last argument. |
| 318 var argumentList; | 320 ArgumentList argumentList; |
| 319 if (expression is MethodInvocation) { | 321 if (expression is MethodInvocation) { |
| 320 argumentList = expression.argumentList; | 322 argumentList = expression.argumentList; |
| 321 } else if (expression is InstanceCreationExpression) { | 323 } else if (expression is InstanceCreationExpression) { |
| 322 argumentList = expression.argumentList; | 324 argumentList = expression.argumentList; |
| 323 } else if (expression is FunctionExpressionInvocation) { | 325 } else if (expression is FunctionExpressionInvocation) { |
| 324 argumentList = expression.argumentList; | 326 argumentList = expression.argumentList; |
| 325 } | 327 } |
| 326 | 328 |
| 327 // Any other kind of expression always splits. | 329 // Any other kind of expression always splits. |
| 328 if (argumentList == null) return true; | 330 if (argumentList == null) return true; |
| 329 if (argumentList.arguments.isEmpty) return true; | 331 if (argumentList.arguments.isEmpty) return true; |
| 330 | 332 |
| 331 var argument = argumentList.arguments.last; | 333 var argument = argumentList.arguments.last; |
| 332 if (argument is NamedExpression) argument = argument.expression; | 334 if (argument is NamedExpression) { |
| 335 argument = (argument as NamedExpression).expression; |
| 336 } |
| 333 | 337 |
| 334 // TODO(rnystrom): This logic is similar (but not identical) to | 338 // TODO(rnystrom): This logic is similar (but not identical) to |
| 335 // ArgumentListVisitor.hasBlockArguments. They overlap conceptually and | 339 // ArgumentListVisitor.hasBlockArguments. They overlap conceptually and |
| 336 // both have their own peculiar heuristics. It would be good to unify and | 340 // both have their own peculiar heuristics. It would be good to unify and |
| 337 // rationalize them. | 341 // rationalize them. |
| 338 | 342 |
| 339 return _forcesSplit(argument); | 343 return _forcesSplit(argument); |
| 340 } | 344 } |
| 341 | 345 |
| 342 /// Writes [call], which must be one of the supported expression types. | 346 /// Writes [call], which must be one of the supported expression types. |
| (...skipping 47 matching lines...) Expand 10 before | Expand all | Expand 10 after Loading... |
| 390 // "." when calling a single method on a single name. This is especially | 394 // "." when calling a single method on a single name. This is especially |
| 391 // important because the identifier is often a library prefix, and splitting | 395 // important because the identifier is often a library prefix, and splitting |
| 392 // there looks really odd. | 396 // there looks really odd. |
| 393 if (_properties.isEmpty && | 397 if (_properties.isEmpty && |
| 394 _calls.length == 1 && | 398 _calls.length == 1 && |
| 395 _blockCalls == null && | 399 _blockCalls == null && |
| 396 _target is SimpleIdentifier) { | 400 _target is SimpleIdentifier) { |
| 397 _endSpan(); | 401 _endSpan(); |
| 398 } | 402 } |
| 399 | 403 |
| 400 _visitor.visit(invocation.argumentList); | 404 _visitor.builder.nestExpression(); |
| 405 _visitor.visit(invocation.typeArguments); |
| 406 _visitor.visitArgumentList(invocation.argumentList, nestExpression: false); |
| 407 _visitor.builder.unnest(); |
| 401 } | 408 } |
| 402 | 409 |
| 403 void _writeBlockCall(MethodInvocation invocation) { | 410 void _writeBlockCall(MethodInvocation invocation) { |
| 404 _visitor.token(invocation.operator); | 411 _visitor.token(invocation.operator); |
| 405 _visitor.token(invocation.methodName.token); | 412 _visitor.token(invocation.methodName.token); |
| 413 _visitor.visit(invocation.typeArguments); |
| 406 _visitor.visit(invocation.argumentList); | 414 _visitor.visit(invocation.argumentList); |
| 407 } | 415 } |
| 408 | 416 |
| 409 /// If a [Rule] for the method chain is currently active, ends it. | 417 /// If a [Rule] for the method chain is currently active, ends it. |
| 410 void _disableRule() { | 418 void _disableRule() { |
| 411 if (_ruleEnabled == false) return; | 419 if (_ruleEnabled == false) return; |
| 412 | 420 |
| 413 _visitor.builder.endRule(); | 421 _visitor.builder.endRule(); |
| 414 _ruleEnabled = false; | 422 _ruleEnabled = false; |
| 415 } | 423 } |
| (...skipping 16 matching lines...) Expand all Loading... |
| 432 } | 440 } |
| 433 | 441 |
| 434 /// Ends the span wrapping the call chain if it hasn't ended already. | 442 /// Ends the span wrapping the call chain if it hasn't ended already. |
| 435 void _endSpan() { | 443 void _endSpan() { |
| 436 if (_spanEnded) return; | 444 if (_spanEnded) return; |
| 437 | 445 |
| 438 _visitor.builder.endSpan(); | 446 _visitor.builder.endSpan(); |
| 439 _spanEnded = true; | 447 _spanEnded = true; |
| 440 } | 448 } |
| 441 } | 449 } |
| OLD | NEW |