Support messages in assert().

Fix #411.

R=kevmoo@google.com, paulberry@google.com

Review URL: https://codereview.chromium.org//1589823004 .
diff --git a/CHANGELOG.md b/CHANGELOG.md
index b2611b9..3e419b4 100644
--- a/CHANGELOG.md
+++ b/CHANGELOG.md
@@ -1,5 +1,6 @@
 # 0.2.3
 
+* Support messages in assert() (#411).
 * Don't put spaces around magic generic method annotation comments (#477).
 * Always put member metadata annotations on their own line (#483).
 * Indent functions in named argument lists with non-functions (#478).
diff --git a/lib/src/argument_list_visitor.dart b/lib/src/argument_list_visitor.dart
index 3ffa4c0..e292c77 100644
--- a/lib/src/argument_list_visitor.dart
+++ b/lib/src/argument_list_visitor.dart
@@ -20,7 +20,15 @@
 class ArgumentListVisitor {
   final SourceVisitor _visitor;
 
-  final ArgumentList _node;
+  /// The "(" before the argument list.
+  final Token _leftParenthesis;
+
+  /// The ")" after the argument list.
+  final Token _rightParenthesis;
+
+  /// All of the arguments, positional, named, and functions, in the argument
+  /// list.
+  final List<Expression> _allArguments;
 
   /// The normal arguments preceding any block function arguments.
   final ArgumentSublist _arguments;
@@ -37,7 +45,7 @@
 
   /// Returns `true` if there is only a single positional argument.
   bool get _isSingle =>
-      _node.arguments.length == 1 && _node.arguments.single is! NamedExpression;
+      _allArguments.length == 1 && _allArguments.single is! NamedExpression;
 
   /// Whether this argument list has any collection or block function arguments.
   // TODO(rnystrom): Returning true based on collections is non-optimal. It
@@ -48,12 +56,21 @@
       _arguments._collections.isNotEmpty || _functions != null;
 
   factory ArgumentListVisitor(SourceVisitor visitor, ArgumentList node) {
+    return new ArgumentListVisitor.forArguments(
+        visitor, node.leftParenthesis, node.rightParenthesis, node.arguments);
+  }
+
+  factory ArgumentListVisitor.forArguments(
+      SourceVisitor visitor,
+      Token leftParenthesis,
+      Token rightParenthesis,
+      List<Expression> arguments) {
     // Look for a single contiguous range of block function arguments.
     var functionsStart;
     var functionsEnd;
 
-    for (var i = 0; i < node.arguments.length; i++) {
-      var argument = node.arguments[i];
+    for (var i = 0; i < arguments.length; i++) {
+      var argument = arguments[i];
       if (_isBlockFunction(argument)) {
         if (functionsStart == null) functionsStart = i;
 
@@ -88,33 +105,47 @@
     //     }
     //         another: argument);
     if (functionsStart != null &&
-        node.arguments[0] is NamedExpression &&
-        (functionsStart > 0 || functionsEnd < node.arguments.length)) {
+        arguments[0] is NamedExpression &&
+        (functionsStart > 0 || functionsEnd < arguments.length)) {
       functionsStart = null;
     }
 
     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,
+          leftParenthesis,
+          rightParenthesis,
+          arguments,
+          new ArgumentSublist(arguments, arguments),
+          null,
+          null);
     }
 
     // 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();
+    var argumentsBefore = arguments.take(functionsStart).toList();
+    var functions = arguments.sublist(functionsStart, functionsEnd);
+    var argumentsAfter = arguments.skip(functionsEnd).toList();
 
     return new ArgumentListVisitor._(
         visitor,
-        node,
-        new ArgumentSublist(node.arguments, argumentsBefore),
+        leftParenthesis,
+        rightParenthesis,
+        arguments,
+        new ArgumentSublist(arguments, argumentsBefore),
         functions,
-        new ArgumentSublist(node.arguments, argumentsAfter));
+        new ArgumentSublist(arguments, argumentsAfter));
   }
 
-  ArgumentListVisitor._(this._visitor, this._node, this._arguments,
-      this._functions, this._argumentsAfterFunctions);
+  ArgumentListVisitor._(
+      this._visitor,
+      this._leftParenthesis,
+      this._rightParenthesis,
+      this._allArguments,
+      this._arguments,
+      this._functions,
+      this._argumentsAfterFunctions);
 
   /// Builds chunks for the argument list.
   void visit() {
@@ -126,7 +157,7 @@
     // them.
     _visitor.builder.nestExpression();
     _visitor.builder.startSpan();
-    _visitor.token(_node.leftParenthesis);
+    _visitor.token(_leftParenthesis);
 
     _arguments.visit(_visitor);
 
@@ -138,7 +169,7 @@
       // 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) {
+      if (_functions.first == _allArguments.first) {
         _visitor.soloZeroSplit();
       } else {
         _visitor.soloSplit();
@@ -150,7 +181,7 @@
         _visitor.visit(argument);
 
         // Write the trailing comma.
-        if (argument != _node.arguments.last) {
+        if (argument != _allArguments.last) {
           _visitor.token(argument.endToken.next);
         }
       }
@@ -160,7 +191,7 @@
       _visitor.builder.endSpan();
     }
 
-    _visitor.token(_node.rightParenthesis);
+    _visitor.token(_rightParenthesis);
 
     _visitor.builder.unnest();
 
@@ -401,8 +432,7 @@
     }
 
     if (argument is NamedExpression) {
-      visitor.visitNamedArgument(
-          argument as NamedExpression, rule as NamedRule);
+      visitor.visitNamedArgument(argument, rule as NamedRule);
     } else {
       visitor.visit(argument);
     }
diff --git a/lib/src/source_visitor.dart b/lib/src/source_visitor.dart
index cbe3448..529e420 100644
--- a/lib/src/source_visitor.dart
+++ b/lib/src/source_visitor.dart
@@ -161,10 +161,13 @@
   visitAssertStatement(AssertStatement node) {
     _simpleStatement(node, () {
       token(node.assertKeyword);
-      token(node.leftParenthesis);
-      soloZeroSplit();
-      visit(node.condition);
-      token(node.rightParenthesis);
+
+      var arguments = [node.condition];
+      if (node.message != null) arguments.add(node.message);
+
+      var visitor = new ArgumentListVisitor.forArguments(
+          this, node.leftParenthesis, node.rightParenthesis, arguments);
+      visitor.visit();
     });
   }
 
diff --git a/test/splitting/statements.stmt b/test/splitting/statements.stmt
index b140854..add34c2 100644
--- a/test/splitting/statements.stmt
+++ b/test/splitting/statements.stmt
@@ -8,6 +8,26 @@
 <<<
 assert(
     "some very long string that wraps");
+>>> single-line assert with message
+assert(true, "blah");
+<<<
+assert(true, "blah");
+>>> split assert with message before both
+assert(true, "looong string that wraps");
+<<<
+assert(
+    true, "looong string that wraps");
+>>> split assert with message after first
+assert(veryLongCondition, "long string that wraps");
+<<<
+assert(veryLongCondition,
+    "long string that wraps");
+>>> split assert with message at both
+assert(veryVeryVeryVeryVeryLongCondition, "long string that wraps");
+<<<
+assert(
+    veryVeryVeryVeryVeryLongCondition,
+    "long string that wraps");
 >>> split in do-while condition
 do {} while ("some long string that wraps");
 <<<