Allow arguments before and after block-formatted functions.

This treats functions differently from collection literal arguments.
After testing this on a large corpus, that seems to produce the best
output. With this change, the arguments preceding and following a series
of functions are treated as completely independent argument lists, each
with their own splitting choice.

Fixes #369.

R=pquitslund@google.com

Review URL: https://chromiumcodereview.appspot.com//1252323003 .
diff --git a/CHANGELOG.md b/CHANGELOG.md
index 5c264d8..602970f 100644
--- a/CHANGELOG.md
+++ b/CHANGELOG.md
@@ -1,3 +1,7 @@
+# 0.2.0-rc.4
+
+* Smarter indentation for function arguments (#369).
+
 # 0.2.0-rc.3
 
 * Optimize splitting complex lines (#391).
diff --git a/lib/src/argument_list_visitor.dart b/lib/src/argument_list_visitor.dart
index 61e820b..4acb3b4 100644
--- a/lib/src/argument_list_visitor.dart
+++ b/lib/src/argument_list_visitor.dart
@@ -12,58 +12,40 @@
 import 'source_visitor.dart';
 
 /// Helper class for [SourceVisitor] that handles visiting and writing an
-/// [ArgumentList], including all of the special code needed to handle block
-/// arguments.
+/// [ArgumentList], including all of the special code needed to handle function
+/// and collection arguments.
 class ArgumentListVisitor {
   final SourceVisitor _visitor;
 
   final ArgumentList _node;
 
-  /// The positional arguments, in order.
-  final List<Expression> _positional;
+  /// The normal arguments preceding any block function arguments.
+  final ArgumentSublist _arguments;
 
-  /// The named arguments, in order.
-  final List<Expression> _named;
-
-  /// The set of arguments that are valid block literals.
-  final Set<Expression> _blockArguments;
-
-  /// The number of leading block arguments.
+  /// The contiguous list of block function arguments, if any.
   ///
-  /// If all arguments are block arguments, this counts them.
-  final int _leadingBlockArguments;
+  /// Otherwise, this is `null`.
+  final List<Expression> _functions;
 
-  /// The number of trailing block arguments.
+  /// If there are block function arguments, this is the arguments after them.
   ///
-  /// If all arguments are block arguments, this is zero.
-  final int _trailingBlockArguments;
-
-  /// The rule used to split the bodies of all of the block arguments.
-  Rule get _blockArgumentRule {
-    // Lazy initialize.
-    if (_blockRule == null && _blockArguments.isNotEmpty) {
-      _blockRule = new SimpleRule(cost: Cost.splitBlocks);
-    }
-
-    return _blockRule;
-  }
-
-  Rule _blockRule;
+  /// Otherwise, this is `null`.
+  final ArgumentSublist _argumentsAfterFunctions;
 
   /// Returns `true` if there is only a single positional argument.
-  bool get _isSingle => _positional.length == 1 && _named.isEmpty;
+  bool get _isSingle =>
+      _node.arguments.length == 1 && _node.arguments.single is! NamedExpression;
 
-  /// Whether this argument list has any block arguments that are functions.
-  bool get hasFunctionBlockArguments => _blockArguments.any(_isBlockFunction);
-
-  bool get hasBlockArguments => _blockArguments.isNotEmpty;
+  /// Whether this argument list has any collection or block function arguments.
+  bool get hasBlockArguments =>
+      _arguments._collections.isNotEmpty || _functions != null;
 
   /// Whether this argument list should force the containing method chain to
   /// add a level of block nesting.
   bool get nestMethodArguments {
     // If there are block arguments, we don't want the method to force them to
     // the right.
-    if (_blockArguments.isNotEmpty) return false;
+    if (hasBlockArguments) return false;
 
     // Corner case: If there is just a single argument, don't bump the nesting.
     // This lets us avoid spurious indentation in cases like:
@@ -75,61 +57,52 @@
   }
 
   factory ArgumentListVisitor(SourceVisitor visitor, ArgumentList node) {
-    // Assumes named arguments follow all positional ones.
-    var positional =
-        node.arguments.takeWhile((arg) => arg is! NamedExpression).toList();
-    var named = node.arguments.skip(positional.length).toList();
+    // Look for a single contiguous range of block function arguments.
+    var functionsStart;
+    var functionsEnd;
 
-    var blocks = node.arguments.where(_isBlockArgument).toSet();
+    for (var i = 0; i < node.arguments.length; i++) {
+      var argument = node.arguments[i];
+      if (_isBlockFunction(argument)) {
+        if (functionsStart == null) functionsStart = i;
 
-    // Count the leading arguments that are block literals.
-    var leadingBlocks = 0;
-    for (var argument in node.arguments) {
-      if (!blocks.contains(argument)) break;
-      leadingBlocks++;
-    }
+        // The functions must be one contiguous section.
+        if (functionsEnd != null && functionsEnd != i) {
+          functionsStart = null;
+          functionsEnd = null;
+          break;
+        }
 
-    // Count the trailing arguments that are block literals.
-    var trailingBlocks = 0;
-    if (leadingBlocks != node.arguments.length) {
-      for (var argument in node.arguments.reversed) {
-        if (!blocks.contains(argument)) break;
-        trailingBlocks++;
+        functionsEnd = i + 1;
       }
     }
 
-    // If only some of the named arguments are blocks, treat none of them as
-    // blocks. Avoids cases like:
-    //
-    //     function(
-    //         a: arg,
-    //         b: [
-    //       ...
-    //     ]);
-    if (trailingBlocks < named.length) trailingBlocks = 0;
-
-    // Blocks must all be a prefix or suffix of the argument list (and not
-    // both).
-    if (leadingBlocks != blocks.length) leadingBlocks = 0;
-    if (trailingBlocks != blocks.length) trailingBlocks = 0;
-
-    // Ignore any blocks in the middle of the argument list.
-    if (leadingBlocks == 0 && trailingBlocks == 0) {
-      blocks.clear();
+    if (functionsStart == null) {
+      // No functions, so there is just a single argument list.
+      return new ArgumentListVisitor._(visitor, node,
+          new ArgumentSublist(node.arguments, node.arguments), null, null);
     }
 
-    return new ArgumentListVisitor._(visitor, node, positional, named, blocks,
-        leadingBlocks, trailingBlocks);
+    // Split the arguments into two independent argument lists with the
+    // functions in the middle.
+    var argumentsBefore = node.arguments.take(functionsStart).toList();
+    var functions = node.arguments.sublist(functionsStart, functionsEnd);
+    var argumentsAfter = node.arguments.skip(functionsEnd).toList();
+
+    return new ArgumentListVisitor._(
+        visitor,
+        node,
+        new ArgumentSublist(node.arguments, argumentsBefore),
+        functions,
+        new ArgumentSublist(node.arguments, argumentsAfter));
   }
 
   ArgumentListVisitor._(
       this._visitor,
       this._node,
-      this._positional,
-      this._named,
-      this._blockArguments,
-      this._leadingBlockArguments,
-      this._trailingBlockArguments);
+      this._arguments,
+      this._functions,
+      this._argumentsAfterFunctions);
 
   /// Builds chunks for the call chain.
   void visit() {
@@ -143,138 +116,45 @@
     _visitor.builder.startSpan();
     _visitor.token(_node.leftParenthesis);
 
-    var rule = _writePositional();
-    _writeNamed(rule);
+    _arguments.visit(_visitor);
+
+    _visitor.builder.endSpan();
+
+    if (_functions != null) {
+      // TODO(rnystrom): It might look better to treat the parameter list of the
+      // first function as if it were an argument in the preceding argument list
+      // instead of just having this little solo split here. That would try to
+      // keep the parameter list with other arguments when possible, and, I
+      // think, generally look nicer.
+      if (_functions.first == _node.arguments.first) {
+        _visitor.soloZeroSplit();
+      } else {
+        _visitor.soloSplit();
+      }
+
+      for (var argument in _functions) {
+        if (argument != _functions.first) _visitor.space();
+
+        _visitor.visit(argument);
+
+        // Write the trailing comma.
+        if (argument != _node.arguments.last) {
+          _visitor.token(argument.endToken.next);
+        }
+      }
+
+      _visitor.builder.startSpan();
+      _argumentsAfterFunctions.visit(_visitor);
+      _visitor.builder.endSpan();
+    }
 
     _visitor.token(_node.rightParenthesis);
 
-    _visitor.builder.endSpan();
     _visitor.builder.unnest();
 
     if (_isSingle) _visitor.builder.endSpan();
   }
 
-  /// Writes the positional arguments, if any.
-  PositionalRule _writePositional() {
-    if (_positional.isEmpty) return null;
-
-    // Allow splitting after "(".
-    var rule;
-    if (_positional.length == 1) {
-      rule = new SinglePositionalRule(_blockArgumentRule);
-    } else {
-      // Only count the positional bodies in the positional rule.
-      var leadingPositional = _leadingBlockArguments;
-      if (_leadingBlockArguments == _node.arguments.length) {
-        leadingPositional -= _named.length;
-      }
-
-      var trailingPositional = _trailingBlockArguments - _named.length;
-      rule = new MultiplePositionalRule(
-          _blockArgumentRule, leadingPositional, trailingPositional);
-    }
-
-    _visitor.builder.startRule(rule);
-    rule.beforeArgument(_visitor.zeroSplit());
-
-    // Try to not split the arguments.
-    _visitor.builder.startSpan(Cost.positionalArguments);
-
-    for (var argument in _positional) {
-      _writeArgument(rule, argument);
-
-      // Positional arguments split independently.
-      if (argument != _positional.last) {
-        rule.beforeArgument(_visitor.split());
-      }
-    }
-
-    _visitor.builder.endSpan();
-    _visitor.builder.endRule();
-
-    return rule;
-  }
-
-  /// Writes the named arguments, if any.
-  void _writeNamed(PositionalRule rule) {
-    if (_named.isEmpty) return;
-
-    var positionalRule = rule;
-    var namedRule = new NamedRule(_blockArgumentRule);
-    _visitor.builder.startRule(namedRule);
-
-    // Let the positional args force the named ones to split.
-    if (positionalRule != null) {
-      positionalRule.setNamedArgsRule(namedRule);
-    }
-
-    // Split before the first named argument.
-    namedRule
-        .beforeArguments(_visitor.builder.split(space: _positional.isNotEmpty));
-
-    for (var argument in _named) {
-      _writeArgument(namedRule, argument);
-
-      // Write the split.
-      if (argument != _named.last) _visitor.split();
-    }
-
-    _visitor.builder.endRule();
-  }
-
-  void _writeArgument(ArgumentRule rule, Expression argument) {
-    // If we're about to write a block argument, handle it specially.
-    if (_blockArguments.contains(argument)) {
-      if (rule != null) rule.beforeBlockArgument();
-
-      // Tell it to use the rule we've already created.
-      _visitor.setNextLiteralBodyRule(_blockArgumentRule);
-    } else if (_node.arguments.length > 1) {
-      // Corner case: If there is just a single argument, don't bump the
-      // nesting. This lets us avoid spurious indentation in cases like:
-      //
-      //     function(function(() {
-      //       body;
-      //     }));
-      _visitor.builder.startBlockArgumentNesting();
-    }
-
-    _visitor.visit(argument);
-
-    if (_blockArguments.contains(argument)) {
-      if (rule != null) rule.afterBlockArgument();
-    } else if (_node.arguments.length > 1) {
-      _visitor.builder.endBlockArgumentNesting();
-    }
-
-    // Write the trailing comma.
-    if (argument != _node.arguments.last) {
-      _visitor.token(argument.endToken.next);
-    }
-  }
-
-  /// Returns true if [expression] denotes a block argument.
-  ///
-  /// That means a collection literal or a function expression with a block
-  /// body. Block arguments can get special indentation to make them look more
-  /// statement-like.
-  static bool _isBlockArgument(Expression expression) {
-    if (expression is NamedExpression) {
-      expression = (expression as NamedExpression).expression;
-    }
-
-    // TODO(rnystrom): Should we step into parenthesized expressions?
-
-    // Collections are bodies.
-    if (expression is ListLiteral) return true;
-    if (expression is MapLiteral) return true;
-
-    // Curly body functions are.
-    if (expression is! FunctionExpression) return false;
-    var function = expression as FunctionExpression;
-    return function.body is BlockFunctionBody;
-  }
-
   /// Returns `true` if [expression] is a [FunctionExpression] with a block
   /// body.
   static bool _isBlockFunction(Expression expression) {
@@ -288,3 +168,230 @@
     return function.body is BlockFunctionBody;
   }
 }
+
+/// A range of arguments from a complete argument list.
+///
+/// One of these typically covers all of the arguments in an invocation. But,
+/// when an argument list has block functions in the middle, the arguments
+/// before and after the functions are treated as separate independent lists.
+/// In that case, there will be two of these.
+class ArgumentSublist {
+  /// The full argument list from the AST.
+  final List<Expression> _allArguments;
+
+  /// The positional arguments, in order.
+  final List<Expression> _positional;
+
+  /// The named arguments, in order.
+  final List<Expression> _named;
+
+  /// The arguments that are collection literals that get special formatting.
+  final Set<Expression> _collections;
+
+  /// The number of leading collections.
+  ///
+  /// If all arguments are collections, this counts them.
+  final int _leadingCollections;
+
+  /// The number of trailing collections.
+  ///
+  /// If all arguments are collections, this is zero.
+  final int _trailingCollections;
+
+  /// The rule used to split the bodies of all of the collection arguments.
+  Rule get _collectionRule {
+    // Lazy initialize.
+    if (_collectionRuleField == null && _collections.isNotEmpty) {
+      _collectionRuleField = new SimpleRule(cost: Cost.splitCollections);
+    }
+
+    return _collectionRuleField;
+  }
+
+  Rule _collectionRuleField;
+
+  bool get _hasMultipleArguments => _positional.length + _named.length > 1;
+
+  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 collections = arguments.where(_isCollectionArgument).toSet();
+
+    // Count the leading arguments that are collection literals.
+    var leadingCollections = 0;
+    for (var argument in arguments) {
+      if (!collections.contains(argument)) break;
+      leadingCollections++;
+    }
+
+    // Count the trailing arguments that are collection literals.
+    var trailingCollections = 0;
+    if (leadingCollections != arguments.length) {
+      for (var argument in arguments.reversed) {
+        if (!collections.contains(argument)) break;
+        trailingCollections++;
+      }
+    }
+
+    // If only some of the named arguments are collections, treat none of them
+    // specially. Avoids cases like:
+    //
+    //     function(
+    //         a: arg,
+    //         b: [
+    //       ...
+    //     ]);
+    if (trailingCollections < named.length) trailingCollections = 0;
+
+    // Collections must all be a prefix or suffix of the argument list (and not
+    // both).
+    if (leadingCollections != collections.length) leadingCollections = 0;
+    if (trailingCollections != collections.length) trailingCollections = 0;
+
+    // Ignore any collections in the middle of the argument list.
+    if (leadingCollections == 0 && trailingCollections == 0) {
+      collections.clear();
+    }
+
+    return new ArgumentSublist._(allArguments, positional, named, collections,
+        leadingCollections, trailingCollections);
+  }
+
+  ArgumentSublist._(this._allArguments, this._positional, this._named,
+      this._collections, this._leadingCollections, this._trailingCollections);
+
+  void visit(SourceVisitor visitor) {
+    var rule = _visitPositional(visitor);
+    _visitNamed(visitor, rule);
+  }
+
+  /// Writes the positional arguments, if any.
+  PositionalRule _visitPositional(SourceVisitor visitor) {
+    if (_positional.isEmpty) return null;
+
+    // Allow splitting after "(".
+    var rule;
+    if (_positional.length == 1) {
+      rule = new SinglePositionalRule(_collectionRule,
+          splitsOnInnerRules: _allArguments.length > 1 &&
+              !_isCollectionArgument(_positional.first));
+    } else {
+      // Only count the positional bodies in the positional rule.
+      var leadingPositional = _leadingCollections;
+      if (_leadingCollections == _positional.length + _named.length) {
+        leadingPositional -= _named.length;
+      }
+
+      var trailingPositional = _trailingCollections - _named.length;
+      rule = new MultiplePositionalRule(
+          _collectionRule, leadingPositional, trailingPositional);
+    }
+
+    visitor.builder.startRule(rule);
+
+    var chunk;
+    if (_isFirstArgument(_positional.first)) {
+      chunk = visitor.zeroSplit();
+    } else {
+      chunk = visitor.split();
+    }
+    rule.beforeArgument(chunk);
+
+    // Try to not split the arguments.
+    visitor.builder.startSpan(Cost.positionalArguments);
+
+    for (var argument in _positional) {
+      _visitArgument(visitor, rule, argument);
+
+      // Positional arguments split independently.
+      if (argument != _positional.last) {
+        rule.beforeArgument(visitor.split());
+      }
+    }
+
+    visitor.builder.endSpan();
+    visitor.builder.endRule();
+
+    return rule;
+  }
+
+  /// Writes the named arguments, if any.
+  void _visitNamed(SourceVisitor visitor, PositionalRule rule) {
+    if (_named.isEmpty) return;
+
+    var positionalRule = rule;
+    var namedRule = new NamedRule(_collectionRule);
+    visitor.builder.startRule(namedRule);
+
+    // Let the positional args force the named ones to split.
+    if (positionalRule != null) {
+      positionalRule.setNamedArgsRule(namedRule);
+    }
+
+    // Split before the first named argument.
+    namedRule.beforeArguments(visitor.builder.split(
+        space: !_isFirstArgument(_named.first)));
+
+    for (var argument in _named) {
+      _visitArgument(visitor, namedRule, argument);
+
+      // Write the split.
+      if (argument != _named.last) visitor.split();
+    }
+
+    visitor.builder.endRule();
+  }
+
+  void _visitArgument(SourceVisitor visitor, ArgumentRule rule, Expression argument) {
+    // If we're about to write a collection argument, handle it specially.
+    if (_collections.contains(argument)) {
+      if (rule != null) rule.beforeCollection();
+
+      // Tell it to use the rule we've already created.
+      visitor.setNextLiteralBodyRule(_collectionRule);
+    } else if (_hasMultipleArguments) {
+      // Corner case: If there is just a single argument, don't bump the
+      // nesting. This lets us avoid spurious indentation in cases like:
+      //
+      //     function(function(() {
+      //       body;
+      //     }));
+      visitor.builder.startBlockArgumentNesting();
+    }
+
+    visitor.visit(argument);
+
+    if (_collections.contains(argument)) {
+      if (rule != null) rule.afterCollection();
+    } else if (_hasMultipleArguments) {
+      visitor.builder.endBlockArgumentNesting();
+    }
+
+    // Write the trailing comma.
+    if (!_isLastArgument(argument)) {
+      visitor.token(argument.endToken.next);
+    }
+  }
+
+  bool _isFirstArgument(Expression argument) => argument == _allArguments.first;
+
+  bool _isLastArgument(Expression argument) => argument == _allArguments.last;
+
+  /// Returns true if [expression] denotes a collection literal argument.
+  ///
+  /// Similar to block functions, collection arguments can get special
+  /// indentation to make them look more statement-like.
+  static bool _isCollectionArgument(Expression expression) {
+    if (expression is NamedExpression) {
+      expression = (expression as NamedExpression).expression;
+    }
+
+    // TODO(rnystrom): Should we step into parenthesized expressions?
+
+    return expression is ListLiteral || expression is MapLiteral;
+  }
+}
\ No newline at end of file
diff --git a/lib/src/chunk.dart b/lib/src/chunk.dart
index bb2d98b..e92a85b 100644
--- a/lib/src/chunk.dart
+++ b/lib/src/chunk.dart
@@ -293,11 +293,11 @@
   /// Splitting inside the brackets of a list with only one element.
   static const singleElementList = 2;
 
-  /// Splitting the internals of literal block arguments.
+  /// Splitting the internals of collection literal arguments.
   ///
   /// Used to prefer splitting at the argument boundary over splitting the
-  /// block contents.
-  static const splitBlocks = 2;
+  /// collection contents.
+  static const splitCollections = 2;
 
   /// Splitting before a type argument or type parameter.
   static const typeArgument = 4;
diff --git a/lib/src/rule/argument.dart b/lib/src/rule/argument.dart
index 95768a4..8e08fba 100644
--- a/lib/src/rule/argument.dart
+++ b/lib/src/rule/argument.dart
@@ -9,36 +9,36 @@
 
 /// Base class for a rule that handles argument or parameter lists.
 abstract class ArgumentRule extends Rule {
-  /// The rule used to split block arguments in the argument list, if any.
-  final Rule _blockRule;
+  /// The rule used to split collections in the argument list, if any.
+  final Rule _collectionRule;
 
   /// If true, then inner rules that are written will force this rule to split.
   ///
-  /// Temporarily disabled while writing block arguments so that they can be
+  /// Temporarily disabled while writing collectio arguments so that they can be
   /// multi-line without forcing the whole argument list to split.
   bool _trackInnerRules = true;
 
-  /// Don't split when an inner block rule splits.
+  /// Don't split when an inner collection rule splits.
   bool get splitsOnInnerRules => _trackInnerRules;
 
   /// Creates a new rule for a positional argument list.
   ///
-  /// If [_blockRule] is given, it is the rule used to split the block
-  /// arguments in the list.
-  ArgumentRule(this._blockRule);
+  /// If [_collectionRule] is given, it is the rule used to split the
+  /// collections in the list.
+  ArgumentRule(this._collectionRule);
 
-  /// Called before a block argument is written.
+  /// Called before a collection argument is written.
   ///
-  /// Disables tracking inner rules while a block argument is being written.
-  void beforeBlockArgument() {
+  /// Disables tracking inner rules while a collection argument is written.
+  void beforeCollection() {
     assert(_trackInnerRules == true);
     _trackInnerRules = false;
   }
 
-  /// Called after a block argument is complete.
+  /// Called after a collection argument is complete.
   ///
-  /// Re-enables tracking inner rules after a block argument is complete.
-  void afterBlockArgument() {
+  /// Re-enables tracking inner rules.
+  void afterCollection() {
     assert(_trackInnerRules == false);
     _trackInnerRules = true;
   }
@@ -55,9 +55,9 @@
 
   /// Creates a new rule for a positional argument list.
   ///
-  /// If [blockRule] is given, it is the rule used to split the block arguments
-  /// in the list.
-  PositionalRule(Rule blockRule) : super(blockRule);
+  /// If [collectionRule] is given, it is the rule used to split the collection
+  /// arguments in the list.
+  PositionalRule(Rule collectionRule) : super(collectionRule);
 
   /// Remembers [chunk] as containing the split that occurs right before an
   /// argument in the list.
@@ -96,20 +96,26 @@
 }
 
 /// Split rule for a call with a single positional argument (which may or may
-/// not be a block argument.)
+/// not be a collection argument.)
 class SinglePositionalRule extends PositionalRule {
   int get numValues => 2;
 
-  /// If there is only a single argument, allow it to split internally without
-  /// forcing a split before the argument.
-  bool get splitsOnInnerRules => false;
+  /// If there is only a single non-collection argument, allow it to split
+  /// internally without forcing a split before the argument.
+  final bool splitsOnInnerRules;
+
+  bool hack = false;
 
   /// Creates a new rule for a positional argument list.
   ///
-  /// If [blockRule] is given, it is the rule used to split the block arguments
-  /// in the list. If [isSingleArgument] is `true`, then the argument list will
-  /// only contain a single argument.
-  SinglePositionalRule(Rule blockRule) : super(blockRule);
+  /// If [collectionRule] is given, it is the rule used to split the
+  /// collections in the list. If [splitsOnInnerRules] is `true`, then we will
+  /// split before the argument if the argument itself contains a split.
+  SinglePositionalRule(Rule collectionRule, {bool splitsOnInnerRules})
+      : super(collectionRule),
+        splitsOnInnerRules = splitsOnInnerRules
+            != null
+            ? splitsOnInnerRules : false;
 
   bool isSplit(int value, Chunk chunk) => value == 1;
 
@@ -117,12 +123,12 @@
     var constrained = super.constrain(value, other);
     if (constrained != null) return constrained;
 
-    if (other != _blockRule) return null;
+    if (other != _collectionRule) return null;
 
-    // If we aren't splitting any args, we can split the block.
+    // If we aren't splitting any args, we can split the collection.
     if (value == 0) return null;
 
-    // We are splitting before a block, so don't let it split internally.
+    // We are splitting before a collection, so don't let it split internally.
     return 0;
   }
 
@@ -143,40 +149,41 @@
 ///
 /// Then there is a value that splits before every argument.
 ///
-/// Finally, if there are block arguments, there is another value that splits
-/// before all of the non-block arguments, but does not split before the block
-/// ones, so that they can split internally.
+/// Finally, if there are collection arguments, there is another value that
+/// splits before all of the non-collection arguments, but does not split
+/// before the collections, so that they can split internally.
 class MultiplePositionalRule extends PositionalRule {
-  /// The number of leading block arguments.
+  /// The number of leading collection arguments.
   ///
-  /// This and [_trailingBlocks] cannot both be positive. If every argument is
-  /// a block, this will be [_arguments.length] and [_trailingBlocks] will be 0.
-  final int _leadingBlocks;
+  /// This and [_trailingCollections] cannot both be positive. If every
+  /// argument is a collection, this will be [_arguments.length] and
+  /// [_trailingCollections] will be 0.
+  final int _leadingCollections;
 
-  /// The number of trailing block arguments.
+  /// The number of trailing collections.
   ///
-  /// This and [_leadingBlocks] cannot both be positive.
-  final int _trailingBlocks;
+  /// This and [_leadingCollections] cannot both be positive.
+  final int _trailingCollections;
 
   int get numValues {
     // Can split before any one argument, none, or all.
     var result = 2 + _arguments.length;
 
-    // When there are block arguments, there are two ways we can split on "all"
-    // arguments:
+    // When there are collection arguments, there are two ways we can split on
+    // "all" arguments:
     //
-    // - Split on just the non-block arguments, and force the block arguments
-    //   to split internally.
-    // - Split on all of them including the block arguments, and do not allow
-    //   the block arguments to split internally.
-    if (_leadingBlocks > 0 || _trailingBlocks > 0) result++;
+    // - Split on just the non-collection arguments, and force the collection
+    //   arguments to split internally.
+    // - Split on all of them including the collection arguments, and do not
+    //   allow the collection arguments to split internally.
+    if (_leadingCollections > 0 || _trailingCollections > 0) result++;
 
     return result;
   }
 
   MultiplePositionalRule(
-      Rule blockRule, this._leadingBlocks, this._trailingBlocks)
-      : super(blockRule);
+      Rule collectionRule, this._leadingCollections, this._trailingCollections)
+      : super(collectionRule);
 
   String toString() => "*Pos${super.toString()}";
 
@@ -195,24 +202,13 @@
       return chunk == _arguments[argument];
     }
 
-    // Only split before the non-block arguments. Note that we consider this
-    // case to correctly prefer this over the latter case because function
-    // block arguments always split internally. Preferring this case ensures we
-    // avoid:
-    //
-    //     function( // <-- :(
-    //         () {
-    //        ...
-    //     }),
-    //         argument,
-    //         ...
-    //         argument;
+    // Only split before the non-collection arguments.
     if (value == _arguments.length + 1) {
-      for (var i = 0; i < _leadingBlocks; i++) {
+      for (var i = 0; i < _leadingCollections; i++) {
         if (chunk == _arguments[i]) return false;
       }
 
-      for (var i = _arguments.length - _trailingBlocks;
+      for (var i = _arguments.length - _trailingCollections;
           i < _arguments.length;
           i++) {
         if (chunk == _arguments[i]) return false;
@@ -221,7 +217,7 @@
       return true;
     }
 
-    // Split before all of the arguments, even the block ones.
+    // Split before all of the arguments, even the collections.
     return true;
   }
 
@@ -229,37 +225,38 @@
     var constrained = super.constrain(value, other);
     if (constrained != null) return constrained;
 
-    if (other != _blockRule) return null;
+    if (other != _collectionRule) return null;
 
-    // If we aren't splitting any args, we can split the block.
+    // If we aren't splitting any args, we can split the collection.
     if (value == 0) return null;
 
     // Split only before the first argument.
     if (value == 1) {
-      if (_leadingBlocks > 0) {
-        // We are splitting before a block, so don't let it split internally.
+      if (_leadingCollections > 0) {
+        // We are splitting before a collection, so don't let it split
+        // internally.
         return 0;
       } else {
-        // The split is outside of the blocks so they can split or not.
+        // The split is outside of the collections so they can split or not.
         return null;
       }
     }
 
-    // Split before a single argument. If it's in the middle of the block
+    // Split before a single argument. If it's in the middle of the collection
     // arguments, don't allow them to split.
     if (value <= _arguments.length) {
       var argument = _arguments.length - value + 1;
-      if (argument < _leadingBlocks) return 0;
-      if (argument >= _arguments.length - _trailingBlocks) return 0;
+      if (argument < _leadingCollections) return 0;
+      if (argument >= _arguments.length - _trailingCollections) return 0;
 
       return null;
     }
 
-    // Only split before the non-block arguments. This case only comes into
-    // play when we do want to split the blocks, so force that here.
+    // Only split before the non-collection arguments. This case only comes into
+    // play when we do want to split the collection, so force that here.
     if (value == _arguments.length + 1) return 1;
 
-    // Split before all of the arguments, even the block ones, so don't let
+    // Split before all of the arguments, even the collection, so don't let
     // them split.
     return 0;
   }
@@ -276,7 +273,7 @@
 
   int get numValues => 3;
 
-  NamedRule(Rule blockRule) : super(blockRule);
+  NamedRule(Rule collectionRule) : super(collectionRule);
 
   void beforeArguments(Chunk chunk) {
     assert(_first == null);
@@ -300,12 +297,12 @@
     var constrained = super.constrain(value, other);
     if (constrained != null) return constrained;
 
-    if (other != _blockRule) return null;
+    if (other != _collectionRule) return null;
 
-    // If we aren't splitting any args, we can split the block.
+    // If we aren't splitting any args, we can split the collection.
     if (value == 0) return null;
 
-    // Split before all of the arguments, even the block ones, so don't let
+    // Split before all of the arguments, even the collections, so don't let
     // them split.
     return 0;
   }
diff --git a/lib/src/source_visitor.dart b/lib/src/source_visitor.dart
index 3e7c3dc..a59ff5e 100644
--- a/lib/src/source_visitor.dart
+++ b/lib/src/source_visitor.dart
@@ -1676,6 +1676,10 @@
     if (elements.isEmpty && rightBracket.precedingComments == null) {
       token(leftBracket);
       token(rightBracket);
+
+      // Clear this out in case this empty collection is in an argument list.
+      // We don't want this rule to bleed over to some other collection.
+      _nextLiteralBodyRule = null;
       return;
     }
 
diff --git a/pubspec.yaml b/pubspec.yaml
index ddc8b42..e05bba0 100644
--- a/pubspec.yaml
+++ b/pubspec.yaml
@@ -1,5 +1,5 @@
 name: dart_style
-version: 0.2.0-rc.3
+version: 0.2.0-rc.4.dev
 author: Dart Team <misc@dartlang.org>
 description: Opinionated, automatic Dart source code formatter.
 homepage: https://github.com/dart-lang/dart_style
diff --git a/test/regression/0000/0000.stmt b/test/regression/0000/0000.stmt
index ac9cf4d..8edbac4 100644
--- a/test/regression/0000/0000.stmt
+++ b/test/regression/0000/0000.stmt
@@ -41,8 +41,7 @@
         DartType type = localsHandler.substInContext(member.type);
         constructorArguments.add(potentiallyCheckOrTrustType(value, type));
       }
-    },
-        includeSuperAndInjectedMembers: true);
+    }, includeSuperAndInjectedMembers: true);
 >>> (indent 6)
       HLoopBlockInformation info = new HLoopBlockInformation(
           HLoopBlockInformation.loopType(loop),
diff --git a/test/regression/0000/0050.stmt b/test/regression/0000/0050.stmt
index d245e97..4bb1449 100644
--- a/test/regression/0000/0050.stmt
+++ b/test/regression/0000/0050.stmt
@@ -9,10 +9,8 @@
       onDone: () { print('file is now closed'); },
       onError: (e) { print(e.toString()); });
 <<<
-  inputStream
-      .transform(UTF8.decoder)
-      .transform(new LineSplitter())
-      .listen((String line) {
+  inputStream.transform(UTF8.decoder).transform(new LineSplitter()).listen(
+      (String line) {
     print('Got ${line.length} characters from stream');
   }, onDone: () {
     print('file is now closed');
diff --git a/test/regression/0000/0057.stmt b/test/regression/0000/0057.stmt
index b7598f4..282a5c7 100644
--- a/test/regression/0000/0057.stmt
+++ b/test/regression/0000/0057.stmt
@@ -4,6 +4,9 @@
 zero: 'none', one: 'one', other: 'some')} plus some text.",
 name: 'embeddedPlural2', desc: 'An embedded plural', args: [n]);
 <<<
-embeddedPlural2(n) => Intl.message("${Intl.plural(n,
+embeddedPlural2(n) => Intl.message(
+    "${Intl.plural(n,
 zero: 'none', one: 'one', other: 'some')} plus some text.",
-    name: 'embeddedPlural2', desc: 'An embedded plural', args: [n]);
\ No newline at end of file
+    name: 'embeddedPlural2',
+    desc: 'An embedded plural',
+    args: [n]);
\ No newline at end of file
diff --git a/test/regression/0300/0369.stmt b/test/regression/0300/0369.stmt
new file mode 100644
index 0000000..fcb8278
--- /dev/null
+++ b/test/regression/0300/0369.stmt
@@ -0,0 +1,26 @@
+>>>
+return log.progress("Loading asset environment", () async {
+  var graph = await entrypoint.loadPackageGraph();
+  graph = _adjustPackageGraph(graph, mode, packages);
+  var barback = new Barback(new PubPackageProvider(graph));
+  barback.log.listen(_log);
+
+  var environment = new AssetEnvironment._(graph, barback, mode,
+      watcherType, hostname, basePort, environmentConstants);
+
+  await environment._load(entrypoints: entrypoints, useDart2JS: useDart2JS);
+  return environment;
+}, fine: true);
+<<<
+return log.progress("Loading asset environment", () async {
+  var graph = await entrypoint.loadPackageGraph();
+  graph = _adjustPackageGraph(graph, mode, packages);
+  var barback = new Barback(new PubPackageProvider(graph));
+  barback.log.listen(_log);
+
+  var environment = new AssetEnvironment._(graph, barback, mode, watcherType,
+      hostname, basePort, environmentConstants);
+
+  await environment._load(entrypoints: entrypoints, useDart2JS: useDart2JS);
+  return environment;
+}, fine: true);
\ No newline at end of file
diff --git a/test/regression/other/analysis_server.unit b/test/regression/other/analysis_server.unit
index decf236..9e662a7 100644
--- a/test/regression/other/analysis_server.unit
+++ b/test/regression/other/analysis_server.unit
@@ -186,4 +186,64 @@
         toHtmlVisitor.write(impliedType.humanReadableName);
       });
     }));
+  }
+>>> (indent 2)
+  main() {
+    manager.setRoots(<String>[
+      projPath
+    ], <String>[], <String, String>{});
+  }
+<<<
+  main() {
+    manager.setRoots(<String>[projPath], <String>[], <String, String>{});
+  }
+>>> (indent 2)
+  main() {
+    server.setAnalysisRoots('0', [
+      '/foo',
+      '/bar'
+    ], [], {});
+    return pumpEventQueue(40).then((_) {
+      expect(server.statusAnalyzing, isFalse);
+    });
+  }
+<<<
+  main() {
+    server.setAnalysisRoots('0', ['/foo', '/bar'], [], {});
+    return pumpEventQueue(40).then((_) {
+      expect(server.statusAnalyzing, isFalse);
+    });
+  }
+>>> (indent 2)
+  main() {
+    return withBuilder(new DartIrBuilder(
+        DART_CONSTANT_SYSTEM,
+        element,
+        // TODO(johnniwinther): Support closure variables.
+        new Set<dart2js.Local>()), () {
+      irBuilder.buildFunctionHeader(
+          constructor.parameters.map(converter.convertElement));
+      // Visit the body directly to avoid processing the signature as
+      // expressions.
+      // Call to allow for `body == null` in case of synthesized constructors.
+      build(body);
+      return irBuilder.makeConstructorDefinition(const [], const []);
+    });
+  }
+<<<
+  main() {
+    return withBuilder(
+        new DartIrBuilder(
+            DART_CONSTANT_SYSTEM,
+            element,
+            // TODO(johnniwinther): Support closure variables.
+            new Set<dart2js.Local>()), () {
+      irBuilder.buildFunctionHeader(
+          constructor.parameters.map(converter.convertElement));
+      // Visit the body directly to avoid processing the signature as
+      // expressions.
+      // Call to allow for `body == null` in case of synthesized constructors.
+      build(body);
+      return irBuilder.makeConstructorDefinition(const [], const []);
+    });
   }
\ No newline at end of file
diff --git a/test/splitting/arguments.stmt b/test/splitting/arguments.stmt
index bffa08c..6a292dc 100644
--- a/test/splitting/arguments.stmt
+++ b/test/splitting/arguments.stmt
@@ -176,7 +176,7 @@
   'other key': value,
   'third key': value
 });
->>> non-body non-preceding argument forces all bodies to indent
+>>> non-collection non-preceding argument forces all collections to indent
 function([element, element, element, element], argument,
 {'key': value, 'other key': value, 'third key': value}, () {;});
 <<<
@@ -192,7 +192,6 @@
       'key': value,
       'other key': value,
       'third key': value
-    },
-    () {
-      ;
-    });
\ No newline at end of file
+    }, () {
+  ;
+});
\ No newline at end of file
diff --git a/test/splitting/function_arguments.stmt b/test/splitting/function_arguments.stmt
index 450b53a..3da9e71 100644
--- a/test/splitting/function_arguments.stmt
+++ b/test/splitting/function_arguments.stmt
@@ -1,13 +1,10 @@
 40 columns                              |
->>> args before and after function forces nesting
+>>> args before and after function do not force nesting
 method(first,() {fn;},third);
 <<<
-method(
-    first,
-    () {
-      fn;
-    },
-    third);
+method(first, () {
+  fn;
+}, third);
 >>> nothing but function args does not nest
 longFunctionName(() {;}, () {;}, () {;});
 <<<
@@ -168,14 +165,21 @@
     obj.inner(() {
       body;
     }));
->>> force named args to split on positional function
+>>> do not force named args to split on positional function
 function(argument, () {;},
     named: argument, another: argument);
 <<<
-function(
-    argument,
-    () {
-      ;
-    },
-    named: argument,
-    another: argument);
\ No newline at end of file
+function(argument, () {
+  ;
+}, named: argument, another: argument);
+>>> args before and after functions split independently
+longFunction(argument, argument, argument, argument, argument,
+() {;}, () {;}, argument, argument, argument, argument, argument);
+<<<
+longFunction(argument, argument,
+    argument, argument, argument, () {
+  ;
+}, () {
+  ;
+}, argument, argument, argument,
+    argument, argument);
\ No newline at end of file