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 'package:analyzer/analyzer.dart'; | 7 import 'package:analyzer/analyzer.dart'; |
8 | 8 |
9 import 'chunk.dart'; | 9 import 'chunk.dart'; |
10 import 'rule/argument.dart'; | 10 import 'rule/argument.dart'; |
11 import 'rule/rule.dart'; | 11 import 'rule/rule.dart'; |
12 import 'source_visitor.dart'; | 12 import 'source_visitor.dart'; |
13 | 13 |
14 /// Helper class for [SourceVisitor] that handles visiting and writing an | 14 /// Helper class for [SourceVisitor] that handles visiting and writing an |
15 /// [ArgumentList], including all of the special code needed to handle block | 15 /// [ArgumentList], including all of the special code needed to handle function |
16 /// arguments. | 16 /// and collection arguments. |
17 class ArgumentListVisitor { | 17 class ArgumentListVisitor { |
18 final SourceVisitor _visitor; | 18 final SourceVisitor _visitor; |
19 | 19 |
20 final ArgumentList _node; | 20 final ArgumentList _node; |
21 | 21 |
22 /// The positional arguments, in order. | 22 /// The normal arguments preceding any block function arguments. |
23 final List<Expression> _positional; | 23 final ArgumentSublist _arguments; |
24 | 24 |
25 /// The named arguments, in order. | 25 /// The contiguous list of block function arguments, if any. |
26 final List<Expression> _named; | 26 /// |
| 27 /// Otherwise, this is `null`. |
| 28 final List<Expression> _functions; |
27 | 29 |
28 /// The set of arguments that are valid block literals. | 30 /// If there are block function arguments, this is the arguments after them. |
29 final Set<Expression> _blockArguments; | |
30 | |
31 /// The number of leading block arguments. | |
32 /// | 31 /// |
33 /// If all arguments are block arguments, this counts them. | 32 /// Otherwise, this is `null`. |
34 final int _leadingBlockArguments; | 33 final ArgumentSublist _argumentsAfterFunctions; |
35 | |
36 /// The number of trailing block arguments. | |
37 /// | |
38 /// If all arguments are block arguments, this is zero. | |
39 final int _trailingBlockArguments; | |
40 | |
41 /// The rule used to split the bodies of all of the block arguments. | |
42 Rule get _blockArgumentRule { | |
43 // Lazy initialize. | |
44 if (_blockRule == null && _blockArguments.isNotEmpty) { | |
45 _blockRule = new SimpleRule(cost: Cost.splitBlocks); | |
46 } | |
47 | |
48 return _blockRule; | |
49 } | |
50 | |
51 Rule _blockRule; | |
52 | 34 |
53 /// Returns `true` if there is only a single positional argument. | 35 /// Returns `true` if there is only a single positional argument. |
54 bool get _isSingle => _positional.length == 1 && _named.isEmpty; | 36 bool get _isSingle => |
| 37 _node.arguments.length == 1 && _node.arguments.single is! NamedExpression; |
55 | 38 |
56 /// Whether this argument list has any block arguments that are functions. | 39 /// Whether this argument list has any collection or block function arguments. |
57 bool get hasFunctionBlockArguments => _blockArguments.any(_isBlockFunction); | 40 bool get hasBlockArguments => |
58 | 41 _arguments._collections.isNotEmpty || _functions != null; |
59 bool get hasBlockArguments => _blockArguments.isNotEmpty; | |
60 | 42 |
61 /// Whether this argument list should force the containing method chain to | 43 /// Whether this argument list should force the containing method chain to |
62 /// add a level of block nesting. | 44 /// add a level of block nesting. |
63 bool get nestMethodArguments { | 45 bool get nestMethodArguments { |
64 // If there are block arguments, we don't want the method to force them to | 46 // If there are block arguments, we don't want the method to force them to |
65 // the right. | 47 // the right. |
66 if (_blockArguments.isNotEmpty) return false; | 48 if (hasBlockArguments) return false; |
67 | 49 |
68 // Corner case: If there is just a single argument, don't bump the nesting. | 50 // Corner case: If there is just a single argument, don't bump the nesting. |
69 // This lets us avoid spurious indentation in cases like: | 51 // This lets us avoid spurious indentation in cases like: |
70 // | 52 // |
71 // object.method(function(() { | 53 // object.method(function(() { |
72 // body; | 54 // body; |
73 // })); | 55 // })); |
74 return _node.arguments.length > 1; | 56 return _node.arguments.length > 1; |
75 } | 57 } |
76 | 58 |
77 factory ArgumentListVisitor(SourceVisitor visitor, ArgumentList node) { | 59 factory ArgumentListVisitor(SourceVisitor visitor, ArgumentList node) { |
78 // Assumes named arguments follow all positional ones. | 60 // Look for a single contiguous range of block function arguments. |
79 var positional = | 61 var functionsStart; |
80 node.arguments.takeWhile((arg) => arg is! NamedExpression).toList(); | 62 var functionsEnd; |
81 var named = node.arguments.skip(positional.length).toList(); | |
82 | 63 |
83 var blocks = node.arguments.where(_isBlockArgument).toSet(); | 64 for (var i = 0; i < node.arguments.length; i++) { |
| 65 var argument = node.arguments[i]; |
| 66 if (_isBlockFunction(argument)) { |
| 67 if (functionsStart == null) functionsStart = i; |
84 | 68 |
85 // Count the leading arguments that are block literals. | 69 // The functions must be one contiguous section. |
86 var leadingBlocks = 0; | 70 if (functionsEnd != null && functionsEnd != i) { |
87 for (var argument in node.arguments) { | 71 functionsStart = null; |
88 if (!blocks.contains(argument)) break; | 72 functionsEnd = null; |
89 leadingBlocks++; | 73 break; |
90 } | 74 } |
91 | 75 |
92 // Count the trailing arguments that are block literals. | 76 functionsEnd = i + 1; |
93 var trailingBlocks = 0; | |
94 if (leadingBlocks != node.arguments.length) { | |
95 for (var argument in node.arguments.reversed) { | |
96 if (!blocks.contains(argument)) break; | |
97 trailingBlocks++; | |
98 } | 77 } |
99 } | 78 } |
100 | 79 |
101 // If only some of the named arguments are blocks, treat none of them as | 80 if (functionsStart == null) { |
102 // blocks. Avoids cases like: | 81 // No functions, so there is just a single argument list. |
103 // | 82 return new ArgumentListVisitor._(visitor, node, |
104 // function( | 83 new ArgumentSublist(node.arguments, node.arguments), null, null); |
105 // a: arg, | |
106 // b: [ | |
107 // ... | |
108 // ]); | |
109 if (trailingBlocks < named.length) trailingBlocks = 0; | |
110 | |
111 // Blocks must all be a prefix or suffix of the argument list (and not | |
112 // both). | |
113 if (leadingBlocks != blocks.length) leadingBlocks = 0; | |
114 if (trailingBlocks != blocks.length) trailingBlocks = 0; | |
115 | |
116 // Ignore any blocks in the middle of the argument list. | |
117 if (leadingBlocks == 0 && trailingBlocks == 0) { | |
118 blocks.clear(); | |
119 } | 84 } |
120 | 85 |
121 return new ArgumentListVisitor._(visitor, node, positional, named, blocks, | 86 // Split the arguments into two independent argument lists with the |
122 leadingBlocks, trailingBlocks); | 87 // functions in the middle. |
| 88 var argumentsBefore = node.arguments.take(functionsStart).toList(); |
| 89 var functions = node.arguments.sublist(functionsStart, functionsEnd); |
| 90 var argumentsAfter = node.arguments.skip(functionsEnd).toList(); |
| 91 |
| 92 return new ArgumentListVisitor._( |
| 93 visitor, |
| 94 node, |
| 95 new ArgumentSublist(node.arguments, argumentsBefore), |
| 96 functions, |
| 97 new ArgumentSublist(node.arguments, argumentsAfter)); |
123 } | 98 } |
124 | 99 |
125 ArgumentListVisitor._( | 100 ArgumentListVisitor._( |
126 this._visitor, | 101 this._visitor, |
127 this._node, | 102 this._node, |
128 this._positional, | 103 this._arguments, |
129 this._named, | 104 this._functions, |
130 this._blockArguments, | 105 this._argumentsAfterFunctions); |
131 this._leadingBlockArguments, | |
132 this._trailingBlockArguments); | |
133 | 106 |
134 /// Builds chunks for the call chain. | 107 /// Builds chunks for the call chain. |
135 void visit() { | 108 void visit() { |
136 // If there is just one positional argument, it tends to look weird to | 109 // If there is just one positional argument, it tends to look weird to |
137 // split before it, so try not to. | 110 // split before it, so try not to. |
138 if (_isSingle) _visitor.builder.startSpan(); | 111 if (_isSingle) _visitor.builder.startSpan(); |
139 | 112 |
140 // Nest around the parentheses in case there are comments before or after | 113 // Nest around the parentheses in case there are comments before or after |
141 // them. | 114 // them. |
142 _visitor.builder.nestExpression(); | 115 _visitor.builder.nestExpression(); |
143 _visitor.builder.startSpan(); | 116 _visitor.builder.startSpan(); |
144 _visitor.token(_node.leftParenthesis); | 117 _visitor.token(_node.leftParenthesis); |
145 | 118 |
146 var rule = _writePositional(); | 119 _arguments.visit(_visitor); |
147 _writeNamed(rule); | 120 |
| 121 _visitor.builder.endSpan(); |
| 122 |
| 123 if (_functions != null) { |
| 124 // TODO(rnystrom): It might look better to treat the parameter list of the |
| 125 // first function as if it were an argument in the preceding argument list |
| 126 // instead of just having this little solo split here. That would try to |
| 127 // keep the parameter list with other arguments when possible, and, I |
| 128 // think, generally look nicer. |
| 129 if (_functions.first == _node.arguments.first) { |
| 130 _visitor.soloZeroSplit(); |
| 131 } else { |
| 132 _visitor.soloSplit(); |
| 133 } |
| 134 |
| 135 for (var argument in _functions) { |
| 136 if (argument != _functions.first) _visitor.space(); |
| 137 |
| 138 _visitor.visit(argument); |
| 139 |
| 140 // Write the trailing comma. |
| 141 if (argument != _node.arguments.last) { |
| 142 _visitor.token(argument.endToken.next); |
| 143 } |
| 144 } |
| 145 |
| 146 _visitor.builder.startSpan(); |
| 147 _argumentsAfterFunctions.visit(_visitor); |
| 148 _visitor.builder.endSpan(); |
| 149 } |
148 | 150 |
149 _visitor.token(_node.rightParenthesis); | 151 _visitor.token(_node.rightParenthesis); |
150 | 152 |
151 _visitor.builder.endSpan(); | |
152 _visitor.builder.unnest(); | 153 _visitor.builder.unnest(); |
153 | 154 |
154 if (_isSingle) _visitor.builder.endSpan(); | 155 if (_isSingle) _visitor.builder.endSpan(); |
155 } | 156 } |
156 | 157 |
| 158 /// Returns `true` if [expression] is a [FunctionExpression] with a block |
| 159 /// body. |
| 160 static bool _isBlockFunction(Expression expression) { |
| 161 if (expression is NamedExpression) { |
| 162 expression = (expression as NamedExpression).expression; |
| 163 } |
| 164 |
| 165 // Curly body functions are. |
| 166 if (expression is! FunctionExpression) return false; |
| 167 var function = expression as FunctionExpression; |
| 168 return function.body is BlockFunctionBody; |
| 169 } |
| 170 } |
| 171 |
| 172 /// A range of arguments from a complete argument list. |
| 173 /// |
| 174 /// One of these typically covers all of the arguments in an invocation. But, |
| 175 /// when an argument list has block functions in the middle, the arguments |
| 176 /// before and after the functions are treated as separate independent lists. |
| 177 /// In that case, there will be two of these. |
| 178 class ArgumentSublist { |
| 179 /// The full argument list from the AST. |
| 180 final List<Expression> _allArguments; |
| 181 |
| 182 /// The positional arguments, in order. |
| 183 final List<Expression> _positional; |
| 184 |
| 185 /// The named arguments, in order. |
| 186 final List<Expression> _named; |
| 187 |
| 188 /// The arguments that are collection literals that get special formatting. |
| 189 final Set<Expression> _collections; |
| 190 |
| 191 /// The number of leading collections. |
| 192 /// |
| 193 /// If all arguments are collections, this counts them. |
| 194 final int _leadingCollections; |
| 195 |
| 196 /// The number of trailing collections. |
| 197 /// |
| 198 /// If all arguments are collections, this is zero. |
| 199 final int _trailingCollections; |
| 200 |
| 201 /// The rule used to split the bodies of all of the collection arguments. |
| 202 Rule get _collectionRule { |
| 203 // Lazy initialize. |
| 204 if (_collectionRuleField == null && _collections.isNotEmpty) { |
| 205 _collectionRuleField = new SimpleRule(cost: Cost.splitCollections); |
| 206 } |
| 207 |
| 208 return _collectionRuleField; |
| 209 } |
| 210 |
| 211 Rule _collectionRuleField; |
| 212 |
| 213 bool get _hasMultipleArguments => _positional.length + _named.length > 1; |
| 214 |
| 215 factory ArgumentSublist( |
| 216 List<Expression> allArguments, List<Expression> arguments) { |
| 217 // Assumes named arguments follow all positional ones. |
| 218 var positional = |
| 219 arguments.takeWhile((arg) => arg is! NamedExpression).toList(); |
| 220 var named = arguments.skip(positional.length).toList(); |
| 221 |
| 222 var collections = arguments.where(_isCollectionArgument).toSet(); |
| 223 |
| 224 // Count the leading arguments that are collection literals. |
| 225 var leadingCollections = 0; |
| 226 for (var argument in arguments) { |
| 227 if (!collections.contains(argument)) break; |
| 228 leadingCollections++; |
| 229 } |
| 230 |
| 231 // Count the trailing arguments that are collection literals. |
| 232 var trailingCollections = 0; |
| 233 if (leadingCollections != arguments.length) { |
| 234 for (var argument in arguments.reversed) { |
| 235 if (!collections.contains(argument)) break; |
| 236 trailingCollections++; |
| 237 } |
| 238 } |
| 239 |
| 240 // If only some of the named arguments are collections, treat none of them |
| 241 // specially. Avoids cases like: |
| 242 // |
| 243 // function( |
| 244 // a: arg, |
| 245 // b: [ |
| 246 // ... |
| 247 // ]); |
| 248 if (trailingCollections < named.length) trailingCollections = 0; |
| 249 |
| 250 // Collections must all be a prefix or suffix of the argument list (and not |
| 251 // both). |
| 252 if (leadingCollections != collections.length) leadingCollections = 0; |
| 253 if (trailingCollections != collections.length) trailingCollections = 0; |
| 254 |
| 255 // Ignore any collections in the middle of the argument list. |
| 256 if (leadingCollections == 0 && trailingCollections == 0) { |
| 257 collections.clear(); |
| 258 } |
| 259 |
| 260 return new ArgumentSublist._(allArguments, positional, named, collections, |
| 261 leadingCollections, trailingCollections); |
| 262 } |
| 263 |
| 264 ArgumentSublist._(this._allArguments, this._positional, this._named, |
| 265 this._collections, this._leadingCollections, this._trailingCollections); |
| 266 |
| 267 void visit(SourceVisitor visitor) { |
| 268 var rule = _visitPositional(visitor); |
| 269 _visitNamed(visitor, rule); |
| 270 } |
| 271 |
157 /// Writes the positional arguments, if any. | 272 /// Writes the positional arguments, if any. |
158 PositionalRule _writePositional() { | 273 PositionalRule _visitPositional(SourceVisitor visitor) { |
159 if (_positional.isEmpty) return null; | 274 if (_positional.isEmpty) return null; |
160 | 275 |
161 // Allow splitting after "(". | 276 // Allow splitting after "(". |
162 var rule; | 277 var rule; |
163 if (_positional.length == 1) { | 278 if (_positional.length == 1) { |
164 rule = new SinglePositionalRule(_blockArgumentRule); | 279 rule = new SinglePositionalRule(_collectionRule, |
| 280 splitsOnInnerRules: _allArguments.length > 1 && |
| 281 !_isCollectionArgument(_positional.first)); |
165 } else { | 282 } else { |
166 // Only count the positional bodies in the positional rule. | 283 // Only count the positional bodies in the positional rule. |
167 var leadingPositional = _leadingBlockArguments; | 284 var leadingPositional = _leadingCollections; |
168 if (_leadingBlockArguments == _node.arguments.length) { | 285 if (_leadingCollections == _positional.length + _named.length) { |
169 leadingPositional -= _named.length; | 286 leadingPositional -= _named.length; |
170 } | 287 } |
171 | 288 |
172 var trailingPositional = _trailingBlockArguments - _named.length; | 289 var trailingPositional = _trailingCollections - _named.length; |
173 rule = new MultiplePositionalRule( | 290 rule = new MultiplePositionalRule( |
174 _blockArgumentRule, leadingPositional, trailingPositional); | 291 _collectionRule, leadingPositional, trailingPositional); |
175 } | 292 } |
176 | 293 |
177 _visitor.builder.startRule(rule); | 294 visitor.builder.startRule(rule); |
178 rule.beforeArgument(_visitor.zeroSplit()); | 295 |
| 296 var chunk; |
| 297 if (_isFirstArgument(_positional.first)) { |
| 298 chunk = visitor.zeroSplit(); |
| 299 } else { |
| 300 chunk = visitor.split(); |
| 301 } |
| 302 rule.beforeArgument(chunk); |
179 | 303 |
180 // Try to not split the arguments. | 304 // Try to not split the arguments. |
181 _visitor.builder.startSpan(Cost.positionalArguments); | 305 visitor.builder.startSpan(Cost.positionalArguments); |
182 | 306 |
183 for (var argument in _positional) { | 307 for (var argument in _positional) { |
184 _writeArgument(rule, argument); | 308 _visitArgument(visitor, rule, argument); |
185 | 309 |
186 // Positional arguments split independently. | 310 // Positional arguments split independently. |
187 if (argument != _positional.last) { | 311 if (argument != _positional.last) { |
188 rule.beforeArgument(_visitor.split()); | 312 rule.beforeArgument(visitor.split()); |
189 } | 313 } |
190 } | 314 } |
191 | 315 |
192 _visitor.builder.endSpan(); | 316 visitor.builder.endSpan(); |
193 _visitor.builder.endRule(); | 317 visitor.builder.endRule(); |
194 | 318 |
195 return rule; | 319 return rule; |
196 } | 320 } |
197 | 321 |
198 /// Writes the named arguments, if any. | 322 /// Writes the named arguments, if any. |
199 void _writeNamed(PositionalRule rule) { | 323 void _visitNamed(SourceVisitor visitor, PositionalRule rule) { |
200 if (_named.isEmpty) return; | 324 if (_named.isEmpty) return; |
201 | 325 |
202 var positionalRule = rule; | 326 var positionalRule = rule; |
203 var namedRule = new NamedRule(_blockArgumentRule); | 327 var namedRule = new NamedRule(_collectionRule); |
204 _visitor.builder.startRule(namedRule); | 328 visitor.builder.startRule(namedRule); |
205 | 329 |
206 // Let the positional args force the named ones to split. | 330 // Let the positional args force the named ones to split. |
207 if (positionalRule != null) { | 331 if (positionalRule != null) { |
208 positionalRule.setNamedArgsRule(namedRule); | 332 positionalRule.setNamedArgsRule(namedRule); |
209 } | 333 } |
210 | 334 |
211 // Split before the first named argument. | 335 // Split before the first named argument. |
212 namedRule | 336 namedRule.beforeArguments(visitor.builder.split( |
213 .beforeArguments(_visitor.builder.split(space: _positional.isNotEmpty)); | 337 space: !_isFirstArgument(_named.first))); |
214 | 338 |
215 for (var argument in _named) { | 339 for (var argument in _named) { |
216 _writeArgument(namedRule, argument); | 340 _visitArgument(visitor, namedRule, argument); |
217 | 341 |
218 // Write the split. | 342 // Write the split. |
219 if (argument != _named.last) _visitor.split(); | 343 if (argument != _named.last) visitor.split(); |
220 } | 344 } |
221 | 345 |
222 _visitor.builder.endRule(); | 346 visitor.builder.endRule(); |
223 } | 347 } |
224 | 348 |
225 void _writeArgument(ArgumentRule rule, Expression argument) { | 349 void _visitArgument(SourceVisitor visitor, ArgumentRule rule, Expression argum
ent) { |
226 // If we're about to write a block argument, handle it specially. | 350 // If we're about to write a collection argument, handle it specially. |
227 if (_blockArguments.contains(argument)) { | 351 if (_collections.contains(argument)) { |
228 if (rule != null) rule.beforeBlockArgument(); | 352 if (rule != null) rule.beforeCollection(); |
229 | 353 |
230 // Tell it to use the rule we've already created. | 354 // Tell it to use the rule we've already created. |
231 _visitor.setNextLiteralBodyRule(_blockArgumentRule); | 355 visitor.setNextLiteralBodyRule(_collectionRule); |
232 } else if (_node.arguments.length > 1) { | 356 } else if (_hasMultipleArguments) { |
233 // Corner case: If there is just a single argument, don't bump the | 357 // Corner case: If there is just a single argument, don't bump the |
234 // nesting. This lets us avoid spurious indentation in cases like: | 358 // nesting. This lets us avoid spurious indentation in cases like: |
235 // | 359 // |
236 // function(function(() { | 360 // function(function(() { |
237 // body; | 361 // body; |
238 // })); | 362 // })); |
239 _visitor.builder.startBlockArgumentNesting(); | 363 visitor.builder.startBlockArgumentNesting(); |
240 } | 364 } |
241 | 365 |
242 _visitor.visit(argument); | 366 visitor.visit(argument); |
243 | 367 |
244 if (_blockArguments.contains(argument)) { | 368 if (_collections.contains(argument)) { |
245 if (rule != null) rule.afterBlockArgument(); | 369 if (rule != null) rule.afterCollection(); |
246 } else if (_node.arguments.length > 1) { | 370 } else if (_hasMultipleArguments) { |
247 _visitor.builder.endBlockArgumentNesting(); | 371 visitor.builder.endBlockArgumentNesting(); |
248 } | 372 } |
249 | 373 |
250 // Write the trailing comma. | 374 // Write the trailing comma. |
251 if (argument != _node.arguments.last) { | 375 if (!_isLastArgument(argument)) { |
252 _visitor.token(argument.endToken.next); | 376 visitor.token(argument.endToken.next); |
253 } | 377 } |
254 } | 378 } |
255 | 379 |
256 /// Returns true if [expression] denotes a block argument. | 380 bool _isFirstArgument(Expression argument) => argument == _allArguments.first; |
| 381 |
| 382 bool _isLastArgument(Expression argument) => argument == _allArguments.last; |
| 383 |
| 384 /// Returns true if [expression] denotes a collection literal argument. |
257 /// | 385 /// |
258 /// That means a collection literal or a function expression with a block | 386 /// Similar to block functions, collection arguments can get special |
259 /// body. Block arguments can get special indentation to make them look more | 387 /// indentation to make them look more statement-like. |
260 /// statement-like. | 388 static bool _isCollectionArgument(Expression expression) { |
261 static bool _isBlockArgument(Expression expression) { | |
262 if (expression is NamedExpression) { | 389 if (expression is NamedExpression) { |
263 expression = (expression as NamedExpression).expression; | 390 expression = (expression as NamedExpression).expression; |
264 } | 391 } |
265 | 392 |
266 // TODO(rnystrom): Should we step into parenthesized expressions? | 393 // TODO(rnystrom): Should we step into parenthesized expressions? |
267 | 394 |
268 // Collections are bodies. | 395 return expression is ListLiteral || expression is MapLiteral; |
269 if (expression is ListLiteral) return true; | |
270 if (expression is MapLiteral) return true; | |
271 | |
272 // Curly body functions are. | |
273 if (expression is! FunctionExpression) return false; | |
274 var function = expression as FunctionExpression; | |
275 return function.body is BlockFunctionBody; | |
276 } | 396 } |
277 | 397 } |
278 /// Returns `true` if [expression] is a [FunctionExpression] with a block | |
279 /// body. | |
280 static bool _isBlockFunction(Expression expression) { | |
281 if (expression is NamedExpression) { | |
282 expression = (expression as NamedExpression).expression; | |
283 } | |
284 | |
285 // Curly body functions are. | |
286 if (expression is! FunctionExpression) return false; | |
287 var function = expression as FunctionExpression; | |
288 return function.body is BlockFunctionBody; | |
289 } | |
290 } | |
OLD | NEW |