Merge branch 'aa-use-internal-api' of https://github.com/scheglov/dart_style into scheglov-aa-use-internal-api
diff --git a/CHANGELOG.md b/CHANGELOG.md
index ae28abd..a73d161 100644
--- a/CHANGELOG.md
+++ b/CHANGELOG.md
@@ -1,3 +1,7 @@
+# 2.2.2
+
+* Format named arguments anywhere (#1072).
+
 # 2.2.1
 
 * Require `package:analyzer` version `2.6.0`.
diff --git a/lib/src/argument_list_visitor.dart b/lib/src/argument_list_visitor.dart
index 9f4c6f2..d839600 100644
--- a/lib/src/argument_list_visitor.dart
+++ b/lib/src/argument_list_visitor.dart
@@ -63,87 +63,9 @@
       Token leftParenthesis,
       Token rightParenthesis,
       List<Expression> arguments) {
-    // Look for a single contiguous range of block function arguments.
-    int? functionsStart;
-    int? functionsEnd;
+    var functionRange = _contiguousFunctions(arguments);
 
-    for (var i = 0; i < arguments.length; i++) {
-      var argument = arguments[i];
-      if (_isBlockFunction(argument)) {
-        functionsStart ??= i;
-
-        // The functions must be one contiguous section.
-        if (functionsEnd != null && functionsEnd != i) {
-          functionsStart = null;
-          functionsEnd = null;
-          break;
-        }
-
-        functionsEnd = i + 1;
-      }
-    }
-
-    // Edge case: If all of the arguments are named, but they aren't all
-    // functions, then don't handle the functions specially. A function with a
-    // bunch of named arguments tends to look best when they are all lined up,
-    // even the function ones (unless they are all functions).
-    //
-    // Prefers:
-    //
-    //     function(
-    //         named: () {
-    //           something();
-    //         },
-    //         another: argument);
-    //
-    // Over:
-    //
-    //     function(named: () {
-    //       something();
-    //     },
-    //         another: argument);
-    if (functionsStart != null &&
-        arguments[0] is NamedExpression &&
-        (functionsStart > 0 || functionsEnd! < arguments.length)) {
-      functionsStart = null;
-    }
-
-    // Edge case: If all of the function arguments are named and there are
-    // other named arguments that are "=>" functions, then don't treat the
-    // block-bodied functions specially. In a mixture of the two function
-    // styles, it looks cleaner to treat them all like normal expressions so
-    // that the named arguments line up.
-    if (functionsStart != null &&
-        arguments[functionsStart] is NamedExpression) {
-      bool isArrow(NamedExpression named) {
-        var expression = named.expression;
-
-        if (expression is FunctionExpression) {
-          return expression.body is ExpressionFunctionBody;
-        }
-
-        return false;
-      }
-
-      for (var i = 0; i < functionsStart!; i++) {
-        var argument = arguments[i];
-        if (argument is! NamedExpression) continue;
-
-        if (isArrow(argument)) {
-          functionsStart = null;
-          break;
-        }
-      }
-
-      for (var i = functionsEnd!; i < arguments.length; i++) {
-        if (isArrow(arguments[i] as NamedExpression)) {
-          functionsStart = null;
-          break;
-        }
-      }
-    }
-
-    if (functionsStart == null) {
+    if (functionRange == null) {
       // No functions, so there is just a single argument list.
       return ArgumentListVisitor._(visitor, leftParenthesis, rightParenthesis,
           arguments, ArgumentSublist(arguments, arguments), null, null);
@@ -151,9 +73,9 @@
 
     // Split the arguments into two independent argument lists with the
     // functions in the middle.
-    var argumentsBefore = arguments.take(functionsStart).toList();
-    var functions = arguments.sublist(functionsStart, functionsEnd);
-    var argumentsAfter = arguments.skip(functionsEnd!).toList();
+    var argumentsBefore = arguments.take(functionRange[0]).toList();
+    var functions = arguments.sublist(functionRange[0], functionRange[1]);
+    var argumentsAfter = arguments.skip(functionRange[1]).toList();
 
     return ArgumentListVisitor._(
         visitor,
@@ -224,6 +146,83 @@
     if (_isSingle) _visitor.builder.endSpan();
   }
 
+  /// Look for a single contiguous range of block function [arguments] that
+  /// should receive special formatting.
+  ///
+  /// Returns a list of (start, end] indexes if found, otherwise returns `null`.
+  static List<int>? _contiguousFunctions(List<Expression> arguments) {
+    int? functionsStart;
+    var functionsEnd = -1;
+
+    // Find the range of block function arguments, if any.
+    for (var i = 0; i < arguments.length; i++) {
+      var argument = arguments[i];
+      if (_isBlockFunction(argument)) {
+        functionsStart ??= i;
+
+        // The functions must be one contiguous section.
+        if (functionsEnd != -1 && functionsEnd != i) return null;
+
+        functionsEnd = i + 1;
+      }
+    }
+
+    if (functionsStart == null) return null;
+
+    // Edge case: If all of the arguments are named, but they aren't all
+    // functions, then don't handle the functions specially. A function with a
+    // bunch of named arguments tends to look best when they are all lined up,
+    // even the function ones (unless they are all functions).
+    //
+    // Prefers:
+    //
+    //     function(
+    //         named: () {
+    //           something();
+    //         },
+    //         another: argument);
+    //
+    // Over:
+    //
+    //     function(named: () {
+    //       something();
+    //     },
+    //         another: argument);
+    if (_isAllNamed(arguments) &&
+        (functionsStart > 0 || functionsEnd < arguments.length)) {
+      return null;
+    }
+
+    // Edge case: If all of the function arguments are named and there are
+    // other named arguments that are "=>" functions, then don't treat the
+    // block-bodied functions specially. In a mixture of the two function
+    // styles, it looks cleaner to treat them all like normal expressions so
+    // that the named arguments line up.
+    if (_isAllNamed(arguments.sublist(functionsStart, functionsEnd))) {
+      bool isNamedArrow(Expression expression) {
+        if (expression is! NamedExpression) return false;
+        expression = expression.expression;
+
+        return expression is FunctionExpression &&
+            expression.body is ExpressionFunctionBody;
+      }
+
+      for (var i = 0; i < functionsStart; i++) {
+        if (isNamedArrow(arguments[i])) return null;
+      }
+
+      for (var i = functionsEnd; i < arguments.length; i++) {
+        if (isNamedArrow(arguments[i])) return null;
+      }
+    }
+
+    return [functionsStart, functionsEnd];
+  }
+
+  /// Returns `true` if every expression in [arguments] is named.
+  static bool _isAllNamed(List<Expression> arguments) =>
+      arguments.every((argument) => argument is NamedExpression);
+
   /// Returns `true` if [expression] is a [FunctionExpression] with a non-empty
   /// block body.
   static bool _isBlockFunction(Expression expression) {
@@ -295,10 +294,15 @@
   /// The full argument list from the AST.
   final List<Expression> _allArguments;
 
-  /// The positional arguments, in order.
+  /// If all positional arguments occur before all named arguments, then this
+  /// contains the positional arguments, in order. Otherwise (there are no
+  /// positional arguments or they are interleaved with named ones), this is
+  /// empty.
   final List<Expression> _positional;
 
-  /// The named arguments, in order.
+  /// The named arguments, in order. If there are any named arguments that occur
+  /// before positional arguments, then all arguments are treated as named and
+  /// end up in this list.
   final List<Expression> _named;
 
   /// Maps each block argument, excluding functions, to the first token for that
@@ -325,10 +329,9 @@
 
   factory ArgumentSublist(
       List<Expression> allArguments, List<Expression> arguments) {
-    // Assumes named arguments follow all positional ones.
-    var positional =
-        arguments.takeWhile((arg) => arg is! NamedExpression).toList();
-    var named = arguments.skip(positional.length).toList();
+    var argumentLists = _splitArgumentLists(arguments);
+    var positional = argumentLists[0];
+    var named = argumentLists[1];
 
     var blocks = <Expression, Token>{};
     for (var argument in arguments) {
@@ -358,9 +361,7 @@
     if (trailingBlocks != blocks.length) trailingBlocks = 0;
 
     // Ignore any blocks in the middle of the argument list.
-    if (leadingBlocks == 0 && trailingBlocks == 0) {
-      blocks.clear();
-    }
+    if (leadingBlocks == 0 && trailingBlocks == 0) blocks.clear();
 
     return ArgumentSublist._(
         allArguments, positional, named, blocks, leadingBlocks, trailingBlocks);
@@ -484,6 +485,37 @@
     }
   }
 
+  /// Splits [arguments] into two lists: the list of leading positional
+  /// arguments and the list of trailing named arguments.
+  ///
+  /// If positional arguments are interleaved with the named arguments then
+  /// all arguments are treat as named since that provides simpler, consistent
+  /// output.
+  ///
+  /// Returns a list of two lists: the positional arguments then the named ones.
+  static List<List<Expression>> _splitArgumentLists(
+      List<Expression> arguments) {
+    var positional = <Expression>[];
+    var named = <Expression>[];
+    var inNamed = false;
+    for (var argument in arguments) {
+      if (argument is NamedExpression) {
+        inNamed = true;
+      } else if (inNamed) {
+        // Got a positional argument after a named one.
+        return [[], arguments];
+      }
+
+      if (inNamed) {
+        named.add(argument);
+      } else {
+        positional.add(argument);
+      }
+    }
+
+    return [positional, named];
+  }
+
   /// If [expression] can be formatted as a block, returns the token that opens
   /// the block, such as a collection's bracket.
   ///
diff --git a/test/splitting/arguments.stmt b/test/splitting/arguments.stmt
index f9ecb7c..6a4fb03 100644
--- a/test/splitting/arguments.stmt
+++ b/test/splitting/arguments.stmt
@@ -215,4 +215,135 @@
 fn(
   named: argument,
   another: argument,
+);
+>>> many arguments mixed named
+method(first, name1: second, third, name2: fourth, name3: fifth, sixth,
+seventh, name4: eighth, ninth, tenth);
+<<<
+method(
+    first,
+    name1: second,
+    third,
+    name2: fourth,
+    name3: fifth,
+    sixth,
+    seventh,
+    name4: eighth,
+    ninth,
+    tenth);
+>>> wrap before first argument, named first
+longFunctionIsLoooooooooooooong(name: argument, argument);
+<<<
+longFunctionIsLoooooooooooooong(
+    name: argument, argument);
+>>> named first
+function(name: firstArg * second, third * fourthAndLongest);
+<<<
+function(
+    name: firstArg * second,
+    third * fourthAndLongest);
+>>> arguments, nested
+someFunctionOne(someArgument,
+someFunctionTwo(name1: argument, argument, name2: argument),
+someFunctionTwo(argument, name: argument, argument),
+someArgument, someArgument);
+<<<
+someFunctionOne(
+    someArgument,
+    someFunctionTwo(
+        name1: argument,
+        argument,
+        name2: argument),
+    someFunctionTwo(
+        argument,
+        name: argument,
+        argument),
+    someArgument,
+    someArgument);
+>>> keep mixed positional and named on first line
+foo(arg, foo: arg, 1, bar: 2);
+<<<
+foo(arg, foo: arg, 1, bar: 2);
+>>> treat mixed named and positional as all named
+reallyLongMethod(argument, foo: first, second);
+<<<
+reallyLongMethod(
+    argument, foo: first, second);
+>>>
+reallyLongMethod(argument, argument, foo: first, bar: second, third);
+<<<
+reallyLongMethod(
+    argument,
+    argument,
+    foo: first,
+    bar: second,
+    third);
+>>>
+reallyLongMethod(leadingNamed: first, second, baz: third);
+<<<
+reallyLongMethod(
+    leadingNamed: first,
+    second,
+    baz: third);
+>>> trailing collections are indented in mixed named/positional arguments
+function(argument, a: argument, argument, b: argument,
+[element, element, element, element],
+c: {'key': value, 'other key': value, 'third key': value});
+<<<
+function(
+    argument,
+    a: argument,
+    argument,
+    b: argument,
+    [
+      element,
+      element,
+      element,
+      element
+    ],
+    c: {
+      'key': value,
+      'other key': value,
+      'third key': value
+    });
+>>> all trailing collections
+function([element, element], name: [element, element], {'key': value, 'other key': value, 'third key': value});
+<<<
+function([
+  element,
+  element
+], name: [
+  element,
+  element
+], {
+  'key': value,
+  'other key': value,
+  'third key': value
+});
+>>> non-collection non-preceding argument forces all collections to indent
+function([element, element, element, element], name: argument,
+{'key': value, 'other key': value, 'third key': value}, () {;});
+<<<
+function(
+    [
+      element,
+      element,
+      element,
+      element
+    ],
+    name: argument,
+    {
+      'key': value,
+      'other key': value,
+      'third key': value
+    }, () {
+  ;
+});
+>>> trailing comma with mixed named
+fn(argument,name: argument  ,argument  ,  );
+<<<
+fn(
+  argument,
+  name: argument,
+  argument,
 );
\ No newline at end of file
diff --git a/test/splitting/function_arguments.stmt b/test/splitting/function_arguments.stmt
index a94e99d..cf860fa 100644
--- a/test/splitting/function_arguments.stmt
+++ b/test/splitting/function_arguments.stmt
@@ -201,6 +201,54 @@
       ;
     },
     c: argument);
+>>> named args anywhere with leading non-function
+{
+  longFunction(argument, b: () {;}, c: () {;});
+  longFunction(a: argument, () {;}, c: () {;});
+  longFunction(a: argument, b: () {;}, () {;});
+}
+<<<
+{
+  longFunction(argument, b: () {
+    ;
+  }, c: () {
+    ;
+  });
+  longFunction(a: argument, () {
+    ;
+  }, c: () {
+    ;
+  });
+  longFunction(a: argument, b: () {
+    ;
+  }, () {
+    ;
+  });
+}
+>>> named args anywhere with trailing non-function
+{
+  longFunction(() {;}, b: () {;}, c: argument);
+  longFunction(a: () {;}, () {;}, c: argument);
+  longFunction(a: () {;}, b: () {;}, argument);
+}
+<<<
+{
+  longFunction(() {
+    ;
+  }, b: () {
+    ;
+  }, c: argument);
+  longFunction(a: () {
+    ;
+  }, () {
+    ;
+  }, c: argument);
+  longFunction(a: () {
+    ;
+  }, b: () {
+    ;
+  }, argument);
+}
 >>> no extra indent for expression function argument with trailing comma
 function(() => P(p,),a: () => [a,],);
 <<<
@@ -228,4 +276,64 @@
       P(
     p,
   ),
-);
\ No newline at end of file
+);
+>>> unsplit named arguments with trailing positional closure
+function(a: argument, b: argument, () {;});
+<<<
+function(a: argument, b: argument, () {
+  ;
+});
+>>> split named arguments with trailing positional closure
+function(a: argument, b: argument, c: argument, () {;});
+<<<
+function(
+    a: argument,
+    b: argument,
+    c: argument, () {
+  ;
+});
+>>> unsingle split leading positional, named arguments with trailing positional closure
+function(argument, b: argument, () {;});
+<<<
+function(argument, b: argument, () {
+  ;
+});
+>>> single split leading positional, named arguments with trailing positional closure
+function(argument, b: argument, c: argument, () {;});
+<<<
+function(argument,
+    b: argument, c: argument, () {
+  ;
+});
+>>> split leading positional, named arguments with trailing positional closure
+function(argument, argument, argument, argument, argument, b: argument, c: argument, d: argument, () {;});
+<<<
+function(argument, argument, argument,
+    argument, argument,
+    b: argument,
+    c: argument,
+    d: argument, () {
+  ;
+});
+>>> multiple trailing positional closures
+function(argument, b: argument, c: argument, () {;}, () {;});
+<<<
+function(argument,
+    b: argument, c: argument, () {
+  ;
+}, () {
+  ;
+});
+>>> mixed named and positional trailing closures
+function(argument, b: argument, c: argument, () {;}, d: () {;}, () {;}, e: () {;});
+<<<
+function(argument,
+    b: argument, c: argument, () {
+  ;
+}, d: () {
+  ;
+}, () {
+  ;
+}, e: () {
+  ;
+});
\ No newline at end of file
diff --git a/test/splitting/list_arguments.stmt b/test/splitting/list_arguments.stmt
index ebb0248..759510d 100644
--- a/test/splitting/list_arguments.stmt
+++ b/test/splitting/list_arguments.stmt
@@ -233,6 +233,30 @@
       element
     ],
     c: third);
+>>> mixed named and positional forces nesting
+method(a:first,[element, element, element, element],c:third);
+<<<
+method(
+    a: first,
+    [
+      element,
+      element,
+      element,
+      element
+    ],
+    c: third);
+>>> mixed named and positional forces nesting
+method(a:first,b:[element, element, element, element],third);
+<<<
+method(
+    a: first,
+    b: [
+      element,
+      element,
+      element,
+      element
+    ],
+    third);
 >>> nothing but named list args does not nest
 longFunctionName(a: [element, element, element, element],
 b: [element, element, element, element], c: [element, element, element, element]);