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'; |
| 11 import 'rule/rule.dart'; |
11 import 'source_visitor.dart'; | 12 import 'source_visitor.dart'; |
12 | 13 |
13 /// Helper class for [SourceVisitor] that handles visiting and writing a | 14 /// Helper class for [SourceVisitor] that handles visiting and writing a |
14 /// chained series of method invocations, property accesses, and/or prefix | 15 /// chained series of method invocations, property accesses, and/or prefix |
15 /// expressions. In other words, anything using the "." operator. | 16 /// expressions. In other words, anything using the "." operator. |
16 class CallChainVisitor { | 17 class CallChainVisitor { |
17 final SourceVisitor _visitor; | 18 final SourceVisitor _visitor; |
18 | 19 |
19 /// The initial target of the call chain. | 20 /// The initial target of the call chain. |
20 /// | 21 /// |
21 /// This may be any expression except [MethodInvocation], [PropertyAccess] or | 22 /// This may be any expression except [MethodInvocation], [PropertyAccess] or |
22 /// [PrefixedIdentifier]. | 23 /// [PrefixedIdentifier]. |
23 final Expression _target; | 24 final Expression _target; |
24 | 25 |
25 /// The list of dotted names ([PropertyAccess] and [PrefixedIdentifier] at | 26 /// The list of dotted names ([PropertyAccess] and [PrefixedIdentifier] at |
26 /// the start of the call chain. | 27 /// the start of the call chain. |
27 /// | 28 /// |
28 /// This will be empty if the [_target] is not a [SimpleIdentifier]. | 29 /// This will be empty if the [_target] is not a [SimpleIdentifier]. |
29 final List<Expression> _properties; | 30 final List<Expression> _properties; |
30 | 31 |
31 /// The mixed method calls and property accesses in the call chain in the | 32 /// The mixed method calls and property accesses in the call chain in the |
32 /// order that they appear in the source. | 33 /// order that they appear in the source. |
33 final List<Expression> _calls; | 34 final List<Expression> _calls; |
34 | 35 |
| 36 /// The method calls containing block function literals that break the method |
| 37 /// chain and escape its indentation. |
| 38 /// |
| 39 /// receiver.a().b().c(() { |
| 40 /// ; |
| 41 /// }).d(() { |
| 42 /// ; |
| 43 /// }).e(); |
| 44 /// |
| 45 /// Here, it will contain `c` and `d`. |
| 46 /// |
| 47 /// The block calls must be contiguous and must be a suffix of the list of |
| 48 /// calls (except for the one allowed hanging call). Otherwise, none of them |
| 49 /// are treated as block calls: |
| 50 /// |
| 51 /// receiver |
| 52 /// .a() |
| 53 /// .b(() { |
| 54 /// ; |
| 55 /// }) |
| 56 /// .c(() { |
| 57 /// ; |
| 58 /// }) |
| 59 /// .d() |
| 60 /// .e(); |
| 61 final List<Expression> _blockCalls; |
| 62 |
| 63 /// If there is one or more block calls and a single chained expression after |
| 64 /// that, this will be that expression. |
| 65 /// |
| 66 /// receiver.a().b().c(() { |
| 67 /// ; |
| 68 /// }).d(() { |
| 69 /// ; |
| 70 /// }).e(); |
| 71 /// |
| 72 /// We allow a single hanging call after the blocks because it will never |
| 73 /// need to split before its `.` and this accommodates the common pattern of |
| 74 /// a trailing `toList()` or `toSet()` after a series of higher-order methods |
| 75 /// on an iterable. |
| 76 final Expression _hangingCall; |
| 77 |
35 /// Whether or not a [Rule] is currently active for the call chain. | 78 /// Whether or not a [Rule] is currently active for the call chain. |
36 bool _ruleEnabled = false; | 79 bool _ruleEnabled = false; |
37 | 80 |
38 /// Whether or not the span wrapping the call chain is currently active. | 81 /// Whether or not the span wrapping the call chain is currently active. |
39 bool _spanEnded = false; | 82 bool _spanEnded = false; |
40 | 83 |
| 84 /// After the properties are visited (if there are any), this will be the |
| 85 /// rule used to split between them. |
| 86 PositionalRule _propertyRule; |
| 87 |
41 /// Creates a new call chain visitor for [visitor] starting with [node]. | 88 /// Creates a new call chain visitor for [visitor] starting with [node]. |
42 /// | 89 /// |
43 /// The [node] is the outermost expression containing the chained "." | 90 /// The [node] is the outermost expression containing the chained "." |
44 /// operators and must be a [MethodInvocation], [PropertyAccess] or | 91 /// operators and must be a [MethodInvocation], [PropertyAccess] or |
45 /// [PrefixedIdentifier]. | 92 /// [PrefixedIdentifier]. |
46 factory CallChainVisitor(SourceVisitor visitor, Expression node) { | 93 factory CallChainVisitor(SourceVisitor visitor, Expression node) { |
47 var target; | 94 var target; |
48 | 95 |
49 // 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. |
50 var calls = []; | 97 var calls = []; |
51 flatten(expression) { | 98 flatten(expression) { |
52 target = expression; | 99 target = expression; |
53 | 100 |
54 if (expression is MethodInvocation && expression.target != null) { | 101 // Treat index expressions where the target is a valid call in a method |
55 flatten(expression.target); | 102 // chain as being part of the call. Handles cases like: |
| 103 // |
| 104 // receiver |
| 105 // .property |
| 106 // .property[0] |
| 107 // .property |
| 108 // .method()[1][2]; |
| 109 var call = expression; |
| 110 while (call is IndexExpression) call = call.target; |
| 111 |
| 112 if (call is MethodInvocation && call.target != null) { |
| 113 flatten(call.target); |
56 calls.add(expression); | 114 calls.add(expression); |
57 } else if (expression is PropertyAccess && expression.target != null) { | 115 } else if (call is PropertyAccess && call.target != null) { |
58 flatten(expression.target); | 116 flatten(call.target); |
59 calls.add(expression); | 117 calls.add(expression); |
60 } else if (expression is PrefixedIdentifier) { | 118 } else if (call is PrefixedIdentifier) { |
61 flatten(expression.prefix); | 119 flatten(call.prefix); |
62 calls.add(expression); | 120 calls.add(expression); |
63 } | 121 } |
64 } | 122 } |
65 | 123 |
66 flatten(node); | 124 flatten(node); |
67 | 125 |
68 // 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 |
69 // 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 |
70 // rest of the chain. Allows code like: | 128 // rest of the chain. Allows code like: |
71 // | 129 // |
72 // address.street.number | 130 // address.street.number |
73 // .toString() | 131 // .toString() |
74 // .length; | 132 // .length; |
75 var properties = []; | 133 var properties = []; |
76 if (target is SimpleIdentifier) { | 134 if (target is SimpleIdentifier) { |
77 properties = | 135 properties = calls.takeWhile((call) { |
78 calls.takeWhile((call) => call is! MethodInvocation).toList(); | 136 // Step into index expressions to see what the index is on. |
| 137 while (call is IndexExpression) call = call.target; |
| 138 return call is! MethodInvocation; |
| 139 }).toList(); |
79 } | 140 } |
80 | 141 |
81 calls.removeRange(0, properties.length); | 142 calls.removeRange(0, properties.length); |
82 | 143 |
83 return new CallChainVisitor._(visitor, target, properties, calls); | 144 // Separate out the block calls, if there are any. |
| 145 var blockCalls; |
| 146 var hangingCall; |
| 147 |
| 148 var inBlockCalls = false; |
| 149 for (var call in calls) { |
| 150 // See if this call is a method call whose arguments are block formatted. |
| 151 var isBlockCall = false; |
| 152 if (call is MethodInvocation) { |
| 153 var args = new ArgumentListVisitor(visitor, call.argumentList); |
| 154 isBlockCall = args.hasBlockArguments; |
| 155 } |
| 156 |
| 157 if (isBlockCall) { |
| 158 inBlockCalls = true; |
| 159 if (blockCalls == null) blockCalls = []; |
| 160 blockCalls.add(call); |
| 161 } else if (inBlockCalls) { |
| 162 // We found a non-block call after a block call. |
| 163 if (call == calls.last) { |
| 164 // It's the one allowed hanging one, so it's OK. |
| 165 hangingCall = call; |
| 166 break; |
| 167 } |
| 168 |
| 169 // Don't allow any of the calls to be block formatted. |
| 170 blockCalls = null; |
| 171 break; |
| 172 } |
| 173 } |
| 174 |
| 175 if (blockCalls != null) { |
| 176 for (var blockCall in blockCalls) calls.remove(blockCall); |
| 177 } |
| 178 |
| 179 if (hangingCall != null) { |
| 180 calls.remove(hangingCall); |
| 181 } |
| 182 |
| 183 return new CallChainVisitor._( |
| 184 visitor, target, properties, calls, blockCalls, hangingCall); |
84 } | 185 } |
85 | 186 |
86 CallChainVisitor._( | 187 CallChainVisitor._(this._visitor, this._target, this._properties, this._calls, |
87 this._visitor, this._target, this._properties, this._calls); | 188 this._blockCalls, this._hangingCall); |
88 | 189 |
89 /// Builds chunks for the call chain. | 190 /// Builds chunks for the call chain. |
90 /// | 191 /// |
91 /// If [unnest] is `false` than this will not close the expression nesting | 192 /// 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 | 193 /// 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 | 194 /// to force a cascade after a method chain to be more deeply nested than |
94 /// the methods. | 195 /// the methods. |
95 void visit({bool unnest}) { | 196 void visit({bool unnest}) { |
96 if (unnest == null) unnest = true; | 197 if (unnest == null) unnest = true; |
97 | 198 |
98 _visitor.builder.nestExpression(); | 199 _visitor.builder.nestExpression(); |
99 | 200 |
100 // Try to keep the entire method invocation one line. | 201 // Try to keep the entire method invocation one line. |
101 _visitor.builder.startSpan(); | 202 _visitor.builder.startSpan(); |
102 | 203 |
| 204 // If a split in the target expression forces the first `.` to split, then |
| 205 // start the rule now so that it surrounds the target. |
| 206 var splitOnTarget = _forcesSplit(_target); |
| 207 |
| 208 if (splitOnTarget) { |
| 209 if (_properties.length > 1) { |
| 210 _propertyRule = new MultiplePositionalRule(null, 0, 0); |
| 211 _visitor.builder.startLazyRule(_propertyRule); |
| 212 } else if (_calls.isNotEmpty) { |
| 213 _enableRule(lazy: true); |
| 214 } |
| 215 } |
| 216 |
103 _visitor.visit(_target); | 217 _visitor.visit(_target); |
104 | 218 |
105 // Leading properties split like positional arguments: either not at all, | 219 // Leading properties split like positional arguments: either not at all, |
106 // before one ".", or before all of them. | 220 // before one ".", or before all of them. |
107 if (_properties.length == 1) { | 221 if (_properties.length == 1) { |
108 _visitor.soloZeroSplit(); | 222 _visitor.soloZeroSplit(); |
109 _writeCall(_properties.single); | 223 _writeCall(_properties.single); |
110 } else if (_properties.length > 1) { | 224 } else if (_properties.length > 1) { |
111 var argRule = new MultiplePositionalRule(null, 0, 0); | 225 if (!splitOnTarget) { |
112 _visitor.builder.startRule(argRule); | 226 _propertyRule = new MultiplePositionalRule(null, 0, 0); |
| 227 _visitor.builder.startRule(_propertyRule); |
| 228 } |
113 | 229 |
114 for (var property in _properties) { | 230 for (var property in _properties) { |
115 argRule.beforeArgument(_visitor.zeroSplit()); | 231 _propertyRule.beforeArgument(_visitor.zeroSplit()); |
116 _writeCall(property); | 232 _writeCall(property); |
117 } | 233 } |
118 | 234 |
119 _visitor.builder.endRule(); | 235 _visitor.builder.endRule(); |
120 } | 236 } |
121 | 237 |
122 // The remaining chain of calls generally split atomically (either all or | 238 // Indent any block arguments in the chain that don't get special formatting |
123 // none), except that block arguments may split a chain into two parts. | 239 // below. Only do this if there is more than one argument to avoid spurious |
| 240 // indentation in cases like: |
| 241 // |
| 242 // object.method(wrapper(() { |
| 243 // body; |
| 244 // }); |
| 245 // TODO(rnystrom): Come up with a less arbitrary way to express this? |
| 246 if (_calls.length > 1) _visitor.builder.startBlockArgumentNesting(); |
| 247 |
| 248 // The chain of calls splits atomically (either all or none). Any block |
| 249 // arguments inside them get indented to line up with the `.`. |
124 for (var call in _calls) { | 250 for (var call in _calls) { |
125 _enableRule(); | 251 _enableRule(); |
126 _visitor.zeroSplit(); | 252 _visitor.zeroSplit(); |
127 _writeCall(call); | 253 _writeCall(call); |
128 } | 254 } |
129 | 255 |
| 256 if (_calls.length > 1) _visitor.builder.endBlockArgumentNesting(); |
| 257 |
| 258 // If there are block calls, end the chain and write those without any |
| 259 // extra indentation. |
| 260 if (_blockCalls != null) { |
| 261 _enableRule(); |
| 262 _visitor.zeroSplit(); |
| 263 _disableRule(); |
| 264 |
| 265 for (var blockCall in _blockCalls) { |
| 266 _writeBlockCall(blockCall); |
| 267 } |
| 268 |
| 269 // If there is a hanging call after the last block, write it without any |
| 270 // split before the ".". |
| 271 if (_hangingCall != null) { |
| 272 _writeCall(_hangingCall); |
| 273 } |
| 274 } |
| 275 |
130 _disableRule(); | 276 _disableRule(); |
131 _endSpan(); | 277 _endSpan(); |
132 | 278 |
133 if (unnest) _visitor.builder.unnest(); | 279 if (unnest) _visitor.builder.unnest(); |
134 } | 280 } |
135 | 281 |
| 282 /// Returns `true` if the method chain should split if a split occurs inside |
| 283 /// [expression]. |
| 284 /// |
| 285 /// In most cases, splitting in a method chain's target forces the chain to |
| 286 /// split too: |
| 287 /// |
| 288 /// receiver(very, long, argument, |
| 289 /// list) // <-- Split here... |
| 290 /// .method(); // ...forces split here. |
| 291 /// |
| 292 /// However, if the target is a collection or function literal (or an |
| 293 /// argument list ending in one of those), we don't want to split: |
| 294 /// |
| 295 /// receiver(inner(() { |
| 296 /// ; |
| 297 /// }).method(); // <-- Unsplit. |
| 298 bool _forcesSplit(Expression expression) { |
| 299 // TODO(rnystrom): Other cases we may want to consider handling and |
| 300 // recursing into: |
| 301 // * ParenthesizedExpression. |
| 302 // * The right operand in an infix operator call. |
| 303 // * The body of a `=>` function. |
| 304 |
| 305 // Don't split right after a collection literal. |
| 306 if (expression is ListLiteral) return false; |
| 307 if (expression is MapLiteral) return false; |
| 308 |
| 309 // Don't split right after a non-empty curly-bodied function. |
| 310 if (expression is FunctionExpression) { |
| 311 if (expression.body is! BlockFunctionBody) return false; |
| 312 |
| 313 return (expression.body as BlockFunctionBody).block.statements.isEmpty; |
| 314 } |
| 315 |
| 316 // If the expression ends in an argument list, base the splitting on the |
| 317 // last argument. |
| 318 var argumentList; |
| 319 if (expression is MethodInvocation) { |
| 320 argumentList = expression.argumentList; |
| 321 } else if (expression is InstanceCreationExpression) { |
| 322 argumentList = expression.argumentList; |
| 323 } else if (expression is FunctionExpressionInvocation) { |
| 324 argumentList = expression.argumentList; |
| 325 } |
| 326 |
| 327 // Any other kind of expression always splits. |
| 328 if (argumentList == null) return true; |
| 329 if (argumentList.arguments.isEmpty) return true; |
| 330 |
| 331 var argument = argumentList.arguments.last; |
| 332 if (argument is NamedExpression) argument = argument.expression; |
| 333 |
| 334 // TODO(rnystrom): This logic is similar (but not identical) to |
| 335 // ArgumentListVisitor.hasBlockArguments. They overlap conceptually and |
| 336 // both have their own peculiar heuristics. It would be good to unify and |
| 337 // rationalize them. |
| 338 |
| 339 return _forcesSplit(argument); |
| 340 } |
| 341 |
136 /// Writes [call], which must be one of the supported expression types. | 342 /// Writes [call], which must be one of the supported expression types. |
137 void _writeCall(Expression call) { | 343 void _writeCall(Expression call) { |
138 if (call is MethodInvocation) { | 344 if (call is IndexExpression) { |
| 345 _visitor.builder.nestExpression(); |
| 346 _writeCall(call.target); |
| 347 _visitor.finishIndexExpression(call); |
| 348 _visitor.builder.unnest(); |
| 349 } else if (call is MethodInvocation) { |
139 _writeInvocation(call); | 350 _writeInvocation(call); |
140 } else if (call is PropertyAccess) { | 351 } else if (call is PropertyAccess) { |
141 _writePropertyAccess(call); | 352 _visitor.token(call.operator); |
| 353 _visitor.visit(call.propertyName); |
142 } else if (call is PrefixedIdentifier) { | 354 } else if (call is PrefixedIdentifier) { |
143 _writePrefixedIdentifier(call); | 355 _visitor.token(call.period); |
| 356 _visitor.visit(call.identifier); |
144 } else { | 357 } else { |
145 // Unexpected type. | 358 // Unexpected type. |
146 assert(false); | 359 assert(false); |
147 } | 360 } |
148 } | 361 } |
149 | 362 |
150 void _writeInvocation(MethodInvocation invocation) { | 363 void _writeInvocation(MethodInvocation invocation) { |
151 _visitor.token(invocation.operator); | 364 _visitor.token(invocation.operator); |
152 _visitor.token(invocation.methodName.token); | 365 _visitor.token(invocation.methodName.token); |
153 | 366 |
154 // If a method's argument list includes any block arguments, there's a | 367 // If we don't have any block calls, stop the rule after the last method |
155 // good chance it will split. Treat the chains before and after that as | 368 // call name, but before its arguments. This allows unsplit chains where |
156 // separate unrelated method chains. | 369 // the last argument list wraps, like: |
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 // | 370 // |
171 // foo().bar().baz( | 371 // foo().bar().baz( |
172 // argument, list); | 372 // argument, list); |
173 // | 373 if (_blockCalls == null && _calls.isNotEmpty && invocation == _calls.last) { |
174 // Also stop the rule to split the argument list at any call with | 374 _disableRule(); |
175 // block arguments. This makes for nicer chains of higher-order method | 375 } |
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 | 376 |
187 // For a single method call on an identifier, stop the span before the | 377 // 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 | 378 // arguments to make it easier to keep the call name with the target. In |
189 // other words, prefer: | 379 // other words, prefer: |
190 // | 380 // |
191 // target.method( | 381 // target.method( |
192 // argument, list); | 382 // argument, list); |
193 // | 383 // |
194 // Over: | 384 // Over: |
195 // | 385 // |
196 // target | 386 // target |
197 // .method(argument, list); | 387 // .method(argument, list); |
198 // | 388 // |
199 // Alternatively, the way to think of this is try to avoid splitting on the | 389 // 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 | 390 // "." when calling a single method on a single name. This is especially |
201 // important because the identifier is often a library prefix, and splitting | 391 // important because the identifier is often a library prefix, and splitting |
202 // there looks really odd. | 392 // there looks really odd. |
203 if (_properties.isEmpty && | 393 if (_properties.isEmpty && |
204 _calls.length == 1 && | 394 _calls.length == 1 && |
| 395 _blockCalls == null && |
205 _target is SimpleIdentifier) { | 396 _target is SimpleIdentifier) { |
206 _endSpan(); | 397 _endSpan(); |
207 } | 398 } |
208 | 399 |
209 _visitor.visit(invocation.argumentList); | 400 _visitor.visit(invocation.argumentList); |
210 | |
211 if (args.nestMethodArguments) _visitor.builder.endBlockArgumentNesting(); | |
212 } | 401 } |
213 | 402 |
214 void _writePropertyAccess(PropertyAccess property) { | 403 void _writeBlockCall(MethodInvocation invocation) { |
215 _visitor.token(property.operator); | 404 _visitor.token(invocation.operator); |
216 _visitor.visit(property.propertyName); | 405 _visitor.token(invocation.methodName.token); |
217 } | 406 _visitor.visit(invocation.argumentList); |
218 | |
219 void _writePrefixedIdentifier(PrefixedIdentifier prefix) { | |
220 _visitor.token(prefix.period); | |
221 _visitor.visit(prefix.identifier); | |
222 } | 407 } |
223 | 408 |
224 /// If a [Rule] for the method chain is currently active, ends it. | 409 /// If a [Rule] for the method chain is currently active, ends it. |
225 void _disableRule() { | 410 void _disableRule() { |
226 if (_ruleEnabled == false) return; | 411 if (_ruleEnabled == false) return; |
227 | 412 |
228 _visitor.builder.endRule(); | 413 _visitor.builder.endRule(); |
229 _ruleEnabled = false; | 414 _ruleEnabled = false; |
230 } | 415 } |
231 | 416 |
232 /// Creates a new method chain [Rule] if one is not already active. | 417 /// Creates a new method chain [Rule] if one is not already active. |
233 void _enableRule() { | 418 void _enableRule({bool lazy: false}) { |
234 if (_ruleEnabled) return; | 419 if (_ruleEnabled) return; |
235 | 420 |
236 _visitor.builder.startRule(); | 421 // If the properties split, force the calls to split too. |
| 422 var rule = new Rule(); |
| 423 if (_propertyRule != null) _propertyRule.setNamedArgsRule(rule); |
| 424 |
| 425 if (lazy) { |
| 426 _visitor.builder.startLazyRule(rule); |
| 427 } else { |
| 428 _visitor.builder.startRule(rule); |
| 429 } |
| 430 |
237 _ruleEnabled = true; | 431 _ruleEnabled = true; |
238 } | 432 } |
239 | 433 |
240 /// Ends the span wrapping the call chain if it hasn't ended already. | 434 /// Ends the span wrapping the call chain if it hasn't ended already. |
241 void _endSpan() { | 435 void _endSpan() { |
242 if (_spanEnded) return; | 436 if (_spanEnded) return; |
243 | 437 |
244 _visitor.builder.endSpan(); | 438 _visitor.builder.endSpan(); |
245 _spanEnded = true; | 439 _spanEnded = true; |
246 } | 440 } |
247 } | 441 } |
OLD | NEW |