@@ -62,8 +62,6 @@ class DelimitedListBuilder {
62
62
});
63
63
}
64
64
65
- if (_style.allowBlockElement) _setBlockElementFormatting ();
66
-
67
65
var piece =
68
66
ListPiece (_leftBracket, _elements, _blanksAfter, _rightBracket, _style);
69
67
if (_mustSplit || forceSplit) piece.pin (State .split);
@@ -134,8 +132,8 @@ class DelimitedListBuilder {
134
132
/// [addCommentsBefore()] for the first token in the [piece] .
135
133
///
136
134
/// Assumes there is no comma after this piece.
137
- void add (Piece piece, [ BlockFormat format = BlockFormat .none] ) {
138
- _elements.add (ListElementPiece (_leadingComments, piece, format ));
135
+ void add (Piece piece) {
136
+ _elements.add (ListElementPiece (_leadingComments, piece));
139
137
_leadingComments.clear ();
140
138
_commentsBeforeComma = CommentSequence .empty;
141
139
}
@@ -152,25 +150,33 @@ class DelimitedListBuilder {
152
150
// Handle comments between the preceding element and this one.
153
151
addCommentsBefore (element.firstNonCommentToken);
154
152
155
- // See if it's an expression that supports block formatting.
156
- var format = switch (element) {
157
- AdjacentStrings (indentStrings: true ) =>
158
- BlockFormat .indentedAdjacentStrings,
159
- AdjacentStrings () => BlockFormat .unindentedAdjacentStrings,
160
- Expression () => element.blockFormatType,
161
- DartPattern () when element.canBlockSplit => BlockFormat .collection,
162
- _ => BlockFormat .none,
163
- };
164
-
165
153
// Traverse the element itself.
166
- add (_visitor.nodePiece (element), format );
154
+ add (_visitor.nodePiece (element));
167
155
168
156
var nextToken = element.endToken.next! ;
169
157
if (nextToken.lexeme == ',' ) {
170
158
_commentsBeforeComma = _visitor.comments.takeCommentsBefore (nextToken);
171
159
}
172
160
}
173
161
162
+ /// Visits a list of [elements] .
163
+ ///
164
+ /// If [allowBlockArgument] is `true` , then allows one element to receive
165
+ /// block formatting if appropriate, as in:
166
+ ///
167
+ /// function(argument, [
168
+ /// block,
169
+ /// like,
170
+ /// ], argument);
171
+ void visitAll (List <AstNode > elements, {bool allowBlockArgument = false }) {
172
+ for (var i = 0 ; i < elements.length; i++ ) {
173
+ var element = elements[i];
174
+ visit (element);
175
+ }
176
+
177
+ if (allowBlockArgument) _setBlockArgument (elements);
178
+ }
179
+
174
180
/// Inserts an inner left delimiter between two elements.
175
181
///
176
182
/// This is used for parameter lists when there are both mandatory and
@@ -398,6 +404,14 @@ class DelimitedListBuilder {
398
404
);
399
405
}
400
406
407
+ /// Given an argument list, determines which if any of the arguments should
408
+ /// get special block-like formatting as in the list literal in:
409
+ ///
410
+ /// function(argument, [
411
+ /// block,
412
+ /// like,
413
+ /// ], argument);
414
+ ///
401
415
/// Looks at the [BlockFormat] types of all of the elements to determine if
402
416
/// one of them should be block formatted.
403
417
///
@@ -426,85 +440,112 @@ class DelimitedListBuilder {
426
440
///
427
441
/// Stores the result of this calculation by setting flags on the
428
442
/// [ListElement] s.
429
- void _setBlockElementFormatting () {
430
- // TODO(tall): These heuristics will probably need some iteration.
431
- var functions = < int > [];
432
- var collections = < int > [];
433
- var adjacentStrings = < int > [];
434
-
435
- for (var i = 0 ; i < _elements.length; i++ ) {
436
- switch (_elements[i].blockFormat) {
437
- case BlockFormat .function:
438
- functions.add (i);
439
- case BlockFormat .collection:
440
- collections.add (i);
441
- case BlockFormat .invocation:
442
- // We don't allow function calls as block elements partially for style
443
- // and partially for performance. It often doesn't look great to let
444
- // nested function calls pack arbitrarily deeply as block arguments:
445
- //
446
- // ScaffoldMessenger.of(context).showSnackBar(SnackBar(
447
- // content: Text(
448
- // localizations.demoSnackbarsAction,
449
- // )));
450
- //
451
- // This is better when expanded like:
452
- //
453
- // ScaffoldMessenger.of(context).showSnackBar(
454
- // SnackBar(
455
- // content: Text(
456
- // localizations.demoSnackbarsAction,
457
- // ),
458
- // ),
459
- // );
460
- //
461
- // Also, when invocations can be block arguments, which themselves
462
- // may contain block arguments, it's easy to run into combinatorial
463
- // performance in the solver as it tries to determine which of the
464
- // nested calls should and shouldn't be block formatted.
465
- break ;
466
- case BlockFormat .indentedAdjacentStrings:
467
- case BlockFormat .unindentedAdjacentStrings:
468
- adjacentStrings.add (i);
469
- case BlockFormat .none:
470
- break ; // Not a block element.
443
+ void _setBlockArgument (List <AstNode > arguments) {
444
+ var candidateIndex = _candidateBlockArgument (arguments);
445
+ if (candidateIndex == - 1 ) return ;
446
+
447
+ // Only allow up to one trailing argument after the block argument. This
448
+ // handles the common `tags` and `timeout` named arguments in `test()` and
449
+ // `group()` while still mostly having the block argument be at the end of
450
+ // the argument list.
451
+ if (candidateIndex < arguments.length - 2 ) return ;
452
+
453
+ // If there are multiple named arguments, they should never end up on
454
+ // separate lines (unless the whole argument list fully splits). Otherwise,
455
+ // it's too easy for an argument name to get buried in the middle of a line.
456
+ // So we look for named arguments before, on, and after the candidate
457
+ // argument. If more than one of those sections of arguments has a named
458
+ // argument, then we don't allow the block argument.
459
+ var namedSections = 0 ;
460
+ bool hasNamedArgument (int from, int to) {
461
+ for (var i = from; i < to; i++ ) {
462
+ if (arguments[i] is NamedExpression ) return true ;
471
463
}
464
+
465
+ return false ;
472
466
}
473
467
474
- switch ((functions, collections, adjacentStrings)) {
475
- // Only allow block formatting in an argument list containing adjacent
476
- // strings when:
477
- //
478
- // 1. The block argument is a function expression.
479
- // 2. It is the second argument, following an adjacent strings expression.
480
- // 3. There are no other adjacent strings in the argument list.
481
- //
482
- // This matches the `test()` and `group()` and other similar APIs where
483
- // you have a message string followed by a block-like function expression
484
- // but little else.
485
- // TODO(tall): We may want to iterate on these heuristics. For now,
486
- // starting with something very narrowly targeted.
487
- case ([1 ], _, [0 ]):
468
+ if (hasNamedArgument (0 , candidateIndex)) namedSections++ ;
469
+ if (hasNamedArgument (candidateIndex, candidateIndex + 1 )) namedSections++ ;
470
+ if (hasNamedArgument (candidateIndex + 1 , arguments.length)) namedSections++ ;
471
+
472
+ if (namedSections > 1 ) return ;
473
+
474
+ // Edge case: If the first argument is adjacent strings and the second
475
+ // argument is a function literal, with optionally a third non-block
476
+ // argument, then treat the function as the block argument.
477
+ //
478
+ // This matches the `test()` and `group()` and other similar APIs where
479
+ // you have a message string followed by a block-like function expression
480
+ // but little else, as in:
481
+ //
482
+ // test('Some long test description '
483
+ // 'that splits into multiple lines.', () {
484
+ // expect(1 + 2, 3);
485
+ // });
486
+ if (candidateIndex == 1 &&
487
+ arguments[0 ] is ! NamedExpression &&
488
+ arguments[1 ] is ! NamedExpression ) {
489
+ if ((arguments[0 ].blockFormatType, arguments[1 ].blockFormatType)
490
+ case (
491
+ BlockFormat .unindentedAdjacentStrings ||
492
+ BlockFormat .indentedAdjacentStrings,
493
+ BlockFormat .function
494
+ )) {
488
495
// The adjacent strings.
489
496
_elements[0 ].allowNewlinesWhenUnsplit = true ;
490
- if (_elements[0 ].blockFormat == BlockFormat .unindentedAdjacentStrings) {
497
+ if (arguments[0 ].blockFormatType ==
498
+ BlockFormat .unindentedAdjacentStrings) {
491
499
_elements[0 ].indentWhenBlockFormatted = true ;
492
500
}
493
501
494
502
// The block-formattable function.
495
503
_elements[1 ].allowNewlinesWhenUnsplit = true ;
504
+ return ;
505
+ }
506
+ }
507
+
508
+ // If we get here, we have a block argument.
509
+ _elements[candidateIndex].allowNewlinesWhenUnsplit = true ;
510
+ }
511
+
512
+ /// If an argument in [arguments] is a candidate to be block formatted,
513
+ /// returns its index.
514
+ ///
515
+ /// Otherwise, returns `-1` .
516
+ int _candidateBlockArgument (List <AstNode > arguments) {
517
+ var functionIndex = - 1 ;
518
+ var collectionIndex = - 1 ;
519
+ // var stringIndex = -1;
520
+
521
+ for (var i = 0 ; i < arguments.length; i++ ) {
522
+ // See if it's an expression that supports block formatting.
523
+ switch (arguments[i].blockFormatType) {
524
+ case BlockFormat .function:
525
+ if (functionIndex >= 0 ) {
526
+ functionIndex = - 2 ;
527
+ } else {
528
+ functionIndex = i;
529
+ }
496
530
497
- // A function expression takes precedence over other block arguments.
498
- case ([var element], _, _):
499
- _elements[element].allowNewlinesWhenUnsplit = true ;
531
+ case BlockFormat .collection:
532
+ if (collectionIndex >= 0 ) {
533
+ collectionIndex = - 2 ;
534
+ } else {
535
+ collectionIndex = i;
536
+ }
500
537
501
- // A single collection literal can be block formatted even if there are
502
- // other arguments.
503
- case ([], [var element], _):
504
- _elements[element].allowNewlinesWhenUnsplit = true ;
538
+ case BlockFormat .invocation:
539
+ case BlockFormat .indentedAdjacentStrings:
540
+ case BlockFormat .unindentedAdjacentStrings:
541
+ case BlockFormat .none:
542
+ break ; // Normal argument.
543
+ }
505
544
}
506
545
507
- // If we get here, there are no block element, or it's ambiguous as to
508
- // which one should be it so none are.
546
+ if (functionIndex >= 0 ) return functionIndex;
547
+ if (collectionIndex >= 0 ) return collectionIndex;
548
+
549
+ return - 1 ;
509
550
}
510
551
}
0 commit comments