@@ -441,97 +441,111 @@ class DelimitedListBuilder {
441
441
/// Stores the result of this calculation by setting flags on the
442
442
/// [ListElement] s.
443
443
void _setBlockArgument (List <AstNode > arguments) {
444
- var firstIsUnindentedAdjacentStrings = false ;
445
- var functions = < int > [] ;
446
- var collections = < int > [];
447
- var adjacentStrings = < int > [];
448
-
449
- for ( var i = 0 ; i < arguments.length; i ++ ) {
450
- var argument = arguments[i];
451
- // See if it's an expression that supports block formatting.
452
- var format = switch (argument) {
453
- AdjacentStrings (indentStrings : true ) =>
454
- BlockFormat .indentedAdjacentStrings ,
455
- AdjacentStrings () => BlockFormat .unindentedAdjacentStrings,
456
- Expression () => argument.blockFormatType,
457
- DartPattern () when argument.canBlockSplit => BlockFormat .collection,
458
- _ => BlockFormat .none,
459
- } ;
460
-
461
- if ( i == 0 && format == BlockFormat .unindentedAdjacentStrings ) {
462
- firstIsUnindentedAdjacentStrings = true ;
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 ;
463
463
}
464
464
465
- switch (format) {
466
- case BlockFormat .function:
467
- functions.add (i);
468
- case BlockFormat .collection:
469
- collections.add (i);
470
- case BlockFormat .invocation:
471
- // We don't allow function calls as block elements partially for style
472
- // and partially for performance. It often doesn't look great to let
473
- // nested function calls pack arbitrarily deeply as block arguments:
474
- //
475
- // ScaffoldMessenger.of(context).showSnackBar(SnackBar(
476
- // content: Text(
477
- // localizations.demoSnackbarsAction,
478
- // )));
479
- //
480
- // This is better when expanded like:
481
- //
482
- // ScaffoldMessenger.of(context).showSnackBar(
483
- // SnackBar(
484
- // content: Text(
485
- // localizations.demoSnackbarsAction,
486
- // ),
487
- // ),
488
- // );
489
- //
490
- // Also, when invocations can be block arguments, which themselves
491
- // may contain block arguments, it's easy to run into combinatorial
492
- // performance in the solver as it tries to determine which of the
493
- // nested calls should and shouldn't be block formatted.
494
- break ;
495
- case BlockFormat .indentedAdjacentStrings:
496
- case BlockFormat .unindentedAdjacentStrings:
497
- adjacentStrings.add (i);
498
- case BlockFormat .none:
499
- break ; // Not a block element.
500
- }
465
+ return false ;
501
466
}
502
467
503
- // TODO(tall): These heuristics will probably need some iteration.
504
- switch ((functions, collections, adjacentStrings)) {
505
- // Only allow block formatting in an argument list containing adjacent
506
- // strings when:
507
- //
508
- // 1. The block argument is a function expression.
509
- // 2. It is the second argument, following an adjacent strings expression.
510
- // 3. There are no other adjacent strings in the argument list.
511
- //
512
- // This matches the `test()` and `group()` and other similar APIs where
513
- // you have a message string followed by a block-like function expression
514
- // but little else.
515
- // TODO(tall): We may want to iterate on these heuristics. For now,
516
- // starting with something very narrowly targeted.
517
- 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
+ )) {
518
495
// The adjacent strings.
519
496
_elements[0 ].allowNewlinesWhenUnsplit = true ;
520
- if (firstIsUnindentedAdjacentStrings) {
497
+ if (arguments[0 ].blockFormatType ==
498
+ BlockFormat .unindentedAdjacentStrings) {
521
499
_elements[0 ].indentWhenBlockFormatted = true ;
522
500
}
523
501
524
502
// The block-formattable function.
525
503
_elements[1 ].allowNewlinesWhenUnsplit = true ;
504
+ return ;
505
+ }
506
+ }
526
507
527
- // A function expression takes precedence over other block arguments.
528
- case ([var element], _, _):
529
- _elements[element].allowNewlinesWhenUnsplit = true ;
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
+ }
530
+
531
+ case BlockFormat .collection:
532
+ if (collectionIndex >= 0 ) {
533
+ collectionIndex = - 2 ;
534
+ } else {
535
+ collectionIndex = i;
536
+ }
530
537
531
- // A single collection literal can be block formatted even if there are
532
- // other arguments.
533
- case ([], [var element], _):
534
- _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
+ }
535
544
}
545
+
546
+ if (functionIndex >= 0 ) return functionIndex;
547
+ if (collectionIndex >= 0 ) return collectionIndex;
548
+
549
+ return - 1 ;
536
550
}
537
551
}
0 commit comments