Chromium Code Reviews
chromiumcodereview-hr@appspot.gserviceaccount.com (chromiumcodereview-hr) | Please choose your nickname with Settings | Help | Chromium Project | Gerrit Changes | Sign out
(112)

Side by Side Diff: lib/src/argument_list_visitor.dart

Issue 1252323003: Allow arguments before and after block-formatted functions. (Closed) Base URL: https://github.com/dart-lang/dart_style.git@master
Patch Set: Update pubspec and changelog. Created 5 years, 4 months ago
Use n/p to move between diff chunks; N/P to move between comments. Draft comments are only viewable by you.
Jump to:
View unified diff | Download patch
« no previous file with comments | « CHANGELOG.md ('k') | lib/src/chunk.dart » ('j') | no next file with comments »
Toggle Intra-line Diffs ('i') | Expand Comments ('e') | Collapse Comments ('c') | Show Comments Hide Comments ('s')
OLDNEW
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 }
OLDNEW
« no previous file with comments | « CHANGELOG.md ('k') | lib/src/chunk.dart » ('j') | no next file with comments »

Powered by Google App Engine
This is Rietveld 408576698