Allow cascades with repeated method names to be one line.

BUG=
R=rnystrom@google.com

Review URL: https://codereview.appspot.com/212600043
diff --git a/CHANGELOG.md b/CHANGELOG.md
index 16bf414..9043ff1 100644
--- a/CHANGELOG.md
+++ b/CHANGELOG.md
@@ -1,6 +1,7 @@
 # 0.1.8-dev
 
 * Update to latest args.
+* Allow cascades with repeated method names to be one line.
 
 # 0.1.7
 
diff --git a/lib/src/source_visitor.dart b/lib/src/source_visitor.dart
index ec9ca70..bbeeafd 100644
--- a/lib/src/source_visitor.dart
+++ b/lib/src/source_visitor.dart
@@ -368,22 +368,41 @@
 
     _writer.indent();
 
-    // If there are multiple cascades, they always get their own line, even if
-    // they would fit.
-    if (node.cascadeSections.length > 1) {
-      newline();
-      visitNodes(node.cascadeSections, between: newline);
-    } else {
+    // If the cascade sections have consistent names they can be broken
+    // normally otherwise they always get their own line.
+    if (_allowInlineCascade(node.cascadeSections)) {
       _writer.startMultisplit();
       _writer.multisplit();
       visitNodes(node.cascadeSections, between: _writer.multisplit);
-
       _writer.endMultisplit();
+    } else {
+      newline();
+      visitNodes(node.cascadeSections, between: newline);
     }
 
     _writer.unindent();
   }
 
+  /// Whether a cascade should be allowed to be inline as opposed to one
+  /// expression per line.
+  bool _allowInlineCascade(List<Expression> sections) {
+    if (sections.length < 2) return true;
+
+    var name;
+    // We could be more forgiving about what constitutes sections with
+    // consistent names but for now we require all sections to have the same
+    // method name.
+    for (var expression in sections) {      
+      if (expression is! MethodInvocation) return false;
+      if (name == null) {
+        name = expression.methodName.name;
+      } else if (name != expression.methodName.name) {
+        return false;
+      }
+    }   
+    return true;
+  }
+
   visitCatchClause(CatchClause node) {
     token(node.onKeyword, after: space);
     visit(node.exceptionType);
diff --git a/test/whitespace/cascades.stmt b/test/whitespace/cascades.stmt
index 1088331..d8a0589 100644
--- a/test/whitespace/cascades.stmt
+++ b/test/whitespace/cascades.stmt
@@ -9,13 +9,26 @@
 "foo"
   ..toString(
       argument, argument, argument);
->>> multiple cascades get their own line
-"foo"..toString()..toString();
+>>> multiple cascades get the same line when the method names are the same
+list
+  ..add("baz")
+  ..add("bar");
 <<<
-"foo"
-  ..toString()
+list..add("baz")..add("bar");
+>>> cascades indent contained blocks (and force multi-line) multiple cascades get their own line when method names are different
+foo..fooBar()..toString();
+<<<
+foo
+  ..fooBar()
   ..toString();
+>>> cascaded setters are always multi-line even with the same name
+foo..baz = 3..baz=5;
+<<<
+foo
+  ..baz = 3
+  ..baz = 5;
 >>> cascades indent contained blocks (and force multi-line)
+
 "foo"..toString(() {body;});
 <<<
 "foo"
diff --git a/test/whitespace/functions.unit b/test/whitespace/functions.unit
index e80e5b5..b79e171 100644
--- a/test/whitespace/functions.unit
+++ b/test/whitespace/functions.unit
@@ -36,12 +36,12 @@
   y() async {}
   var z = () async {};
 }
->>> dartbug.com/16384
-fish() => []..add(1)..add(2);
-<<<
+>>>
 fish() => []
   ..add(1)
   ..add(2);
+<<<
+fish() => []..add(1)..add(2);
 >>>
 fish() => []..add(1);
 <<<