| 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.call_chain_visitor; | |
| 6 | |
| 7 import 'package:analyzer/analyzer.dart'; | |
| 8 | |
| 9 import 'argument_list_visitor.dart'; | |
| 10 import 'rule/argument.dart'; | |
| 11 import 'source_visitor.dart'; | |
| 12 | |
| 13 /// Helper class for [SourceVisitor] that handles visiting and writing a | |
| 14 /// chained series of method invocations, property accesses, and/or prefix | |
| 15 /// expressions. In other words, anything using the "." operator. | |
| 16 class CallChainVisitor { | |
| 17 final SourceVisitor _visitor; | |
| 18 | |
| 19 /// The initial target of the call chain. | |
| 20 /// | |
| 21 /// This may be any expression except [MethodInvocation], [PropertyAccess] or | |
| 22 /// [PrefixedIdentifier]. | |
| 23 final Expression _target; | |
| 24 | |
| 25 /// The list of dotted names ([PropertyAccess] and [PrefixedIdentifier] at | |
| 26 /// the start of the call chain. | |
| 27 /// | |
| 28 /// This will be empty if the [_target] is not a [SimpleIdentifier]. | |
| 29 final List<Expression> _properties; | |
| 30 | |
| 31 /// The mixed method calls and property accesses in the call chain in the | |
| 32 /// order that they appear in the source. | |
| 33 final List<Expression> _calls; | |
| 34 | |
| 35 /// Whether or not a [Rule] is currently active for the call chain. | |
| 36 bool _ruleEnabled = false; | |
| 37 | |
| 38 /// Whether or not the span wrapping the call chain is currently active. | |
| 39 bool _spanEnded = false; | |
| 40 | |
| 41 /// Creates a new call chain visitor for [visitor] starting with [node]. | |
| 42 /// | |
| 43 /// The [node] is the outermost expression containing the chained "." | |
| 44 /// operators and must be a [MethodInvocation], [PropertyAccess] or | |
| 45 /// [PrefixedIdentifier]. | |
| 46 factory CallChainVisitor(SourceVisitor visitor, Expression node) { | |
| 47 var target; | |
| 48 | |
| 49 // Recursively walk the chain of calls and turn the tree into a list. | |
| 50 var calls = []; | |
| 51 flatten(expression) { | |
| 52 target = expression; | |
| 53 | |
| 54 if (expression is MethodInvocation && expression.target != null) { | |
| 55 flatten(expression.target); | |
| 56 calls.add(expression); | |
| 57 } else if (expression is PropertyAccess && expression.target != null) { | |
| 58 flatten(expression.target); | |
| 59 calls.add(expression); | |
| 60 } else if (expression is PrefixedIdentifier) { | |
| 61 flatten(expression.prefix); | |
| 62 calls.add(expression); | |
| 63 } | |
| 64 } | |
| 65 | |
| 66 flatten(node); | |
| 67 | |
| 68 // An expression that starts with a series of dotted names gets treated a | |
| 69 // little specially. We don't force leading properties to split with the | |
| 70 // rest of the chain. Allows code like: | |
| 71 // | |
| 72 // address.street.number | |
| 73 // .toString() | |
| 74 // .length; | |
| 75 var properties = []; | |
| 76 if (target is SimpleIdentifier) { | |
| 77 properties = | |
| 78 calls.takeWhile((call) => call is! MethodInvocation).toList(); | |
| 79 } | |
| 80 | |
| 81 calls.removeRange(0, properties.length); | |
| 82 | |
| 83 return new CallChainVisitor._(visitor, target, properties, calls); | |
| 84 } | |
| 85 | |
| 86 CallChainVisitor._( | |
| 87 this._visitor, this._target, this._properties, this._calls); | |
| 88 | |
| 89 /// Builds chunks for the call chain. | |
| 90 /// | |
| 91 /// If [unnest] is `false` than this will not close the expression nesting | |
| 92 /// created for the call chain and the caller must end it. Used by cascades | |
| 93 /// to force a cascade after a method chain to be more deeply nested than | |
| 94 /// the methods. | |
| 95 void visit({bool unnest}) { | |
| 96 if (unnest == null) unnest = true; | |
| 97 | |
| 98 _visitor.builder.nestExpression(); | |
| 99 | |
| 100 // Try to keep the entire method invocation one line. | |
| 101 _visitor.builder.startSpan(); | |
| 102 | |
| 103 _visitor.visit(_target); | |
| 104 | |
| 105 // Leading properties split like positional arguments: either not at all, | |
| 106 // before one ".", or before all of them. | |
| 107 if (_properties.length == 1) { | |
| 108 _visitor.soloZeroSplit(); | |
| 109 _writeCall(_properties.single); | |
| 110 } else if (_properties.length > 1) { | |
| 111 var argRule = new MultiplePositionalRule(null, 0, 0); | |
| 112 _visitor.builder.startRule(argRule); | |
| 113 | |
| 114 for (var property in _properties) { | |
| 115 argRule.beforeArgument(_visitor.zeroSplit()); | |
| 116 _writeCall(property); | |
| 117 } | |
| 118 | |
| 119 _visitor.builder.endRule(); | |
| 120 } | |
| 121 | |
| 122 // The remaining chain of calls generally split atomically (either all or | |
| 123 // none), except that block arguments may split a chain into two parts. | |
| 124 for (var call in _calls) { | |
| 125 _enableRule(); | |
| 126 _visitor.zeroSplit(); | |
| 127 _writeCall(call); | |
| 128 } | |
| 129 | |
| 130 _disableRule(); | |
| 131 _endSpan(); | |
| 132 | |
| 133 if (unnest) _visitor.builder.unnest(); | |
| 134 } | |
| 135 | |
| 136 /// Writes [call], which must be one of the supported expression types. | |
| 137 void _writeCall(Expression call) { | |
| 138 if (call is MethodInvocation) { | |
| 139 _writeInvocation(call); | |
| 140 } else if (call is PropertyAccess) { | |
| 141 _writePropertyAccess(call); | |
| 142 } else if (call is PrefixedIdentifier) { | |
| 143 _writePrefixedIdentifier(call); | |
| 144 } else { | |
| 145 // Unexpected type. | |
| 146 assert(false); | |
| 147 } | |
| 148 } | |
| 149 | |
| 150 void _writeInvocation(MethodInvocation invocation) { | |
| 151 _visitor.token(invocation.operator); | |
| 152 _visitor.token(invocation.methodName.token); | |
| 153 | |
| 154 // If a method's argument list includes any block arguments, there's a | |
| 155 // good chance it will split. Treat the chains before and after that as | |
| 156 // separate unrelated method chains. | |
| 157 // | |
| 158 // This is kind of a hack since it treats methods before and after a | |
| 159 // collection literal argument differently even when the collection | |
| 160 // doesn't split, but it works out OK in practice. | |
| 161 // | |
| 162 // Doing something more precise would require setting up a bunch of complex | |
| 163 // constraints between various rules. You'd basically have to say "if the | |
| 164 // block argument splits then allow the chain after it to split | |
| 165 // independently, otherwise force it to follow the previous chain". | |
| 166 var args = new ArgumentListVisitor(_visitor, invocation.argumentList); | |
| 167 | |
| 168 // Stop the rule after the last call, but before its arguments. This | |
| 169 // allows unsplit chains where the last argument list wraps, like: | |
| 170 // | |
| 171 // foo().bar().baz( | |
| 172 // argument, list); | |
| 173 // | |
| 174 // Also stop the rule to split the argument list at any call with | |
| 175 // block arguments. This makes for nicer chains of higher-order method | |
| 176 // calls, like: | |
| 177 // | |
| 178 // items.map((element) { | |
| 179 // ... | |
| 180 // }).where((element) { | |
| 181 // ... | |
| 182 // }); | |
| 183 if (invocation == _calls.last || args.hasBlockArguments) _disableRule(); | |
| 184 | |
| 185 if (args.nestMethodArguments) _visitor.builder.startBlockArgumentNesting(); | |
| 186 | |
| 187 // For a single method call on an identifier, stop the span before the | |
| 188 // arguments to make it easier to keep the call name with the target. In | |
| 189 // other words, prefer: | |
| 190 // | |
| 191 // target.method( | |
| 192 // argument, list); | |
| 193 // | |
| 194 // Over: | |
| 195 // | |
| 196 // target | |
| 197 // .method(argument, list); | |
| 198 // | |
| 199 // Alternatively, the way to think of this is try to avoid splitting on the | |
| 200 // "." when calling a single method on a single name. This is especially | |
| 201 // important because the identifier is often a library prefix, and splitting | |
| 202 // there looks really odd. | |
| 203 if (_properties.isEmpty && | |
| 204 _calls.length == 1 && | |
| 205 _target is SimpleIdentifier) { | |
| 206 _endSpan(); | |
| 207 } | |
| 208 | |
| 209 _visitor.visit(invocation.argumentList); | |
| 210 | |
| 211 if (args.nestMethodArguments) _visitor.builder.endBlockArgumentNesting(); | |
| 212 } | |
| 213 | |
| 214 void _writePropertyAccess(PropertyAccess property) { | |
| 215 _visitor.token(property.operator); | |
| 216 _visitor.visit(property.propertyName); | |
| 217 } | |
| 218 | |
| 219 void _writePrefixedIdentifier(PrefixedIdentifier prefix) { | |
| 220 _visitor.token(prefix.period); | |
| 221 _visitor.visit(prefix.identifier); | |
| 222 } | |
| 223 | |
| 224 /// If a [Rule] for the method chain is currently active, ends it. | |
| 225 void _disableRule() { | |
| 226 if (_ruleEnabled == false) return; | |
| 227 | |
| 228 _visitor.builder.endRule(); | |
| 229 _ruleEnabled = false; | |
| 230 } | |
| 231 | |
| 232 /// Creates a new method chain [Rule] if one is not already active. | |
| 233 void _enableRule() { | |
| 234 if (_ruleEnabled) return; | |
| 235 | |
| 236 _visitor.builder.startRule(); | |
| 237 _ruleEnabled = true; | |
| 238 } | |
| 239 | |
| 240 /// Ends the span wrapping the call chain if it hasn't ended already. | |
| 241 void _endSpan() { | |
| 242 if (_spanEnded) return; | |
| 243 | |
| 244 _visitor.builder.endSpan(); | |
| 245 _spanEnded = true; | |
| 246 } | |
| 247 } | |
| OLD | NEW |