Better handling for binary operators in => bodies.

Treat them specially in two ways:

- Don't increase the expression nesting.
- Force the => to split if the body does.

This has the nice effect of lining up all operands, even the first:

function() =>
    expression ||
    expression ||
    expression;

Fix #434.

R=kevmoo@google.com

Review URL: https://codereview.chromium.org//1504553002 .
diff --git a/CHANGELOG.md b/CHANGELOG.md
index 28a7dd8..93ae7dd 100644
--- a/CHANGELOG.md
+++ b/CHANGELOG.md
@@ -14,6 +14,7 @@
 * Better indentation of collection literals (#421, #469).
 * Only show a hidden directory once in the output (#428).
 * Allow splitting between type and variable name (#429, #439, #454).
+* Better indentation for binary operators in `=>` bodies (#434.
 * Tweak splitting around assignment (#436, #437).
 * Indent multi-line collections in default values (#441).
 * Don't drop metadata on part directives (#443).
diff --git a/lib/src/source_visitor.dart b/lib/src/source_visitor.dart
index 0da60dd..6c9f013 100644
--- a/lib/src/source_visitor.dart
+++ b/lib/src/source_visitor.dart
@@ -185,7 +185,17 @@
 
   visitBinaryExpression(BinaryExpression node) {
     builder.startSpan();
-    builder.nestExpression();
+
+    // If a binary operator sequence appears immediately after a `=>`, don't
+    // add an extra level of nesting. Instead, let the subsequent operands line
+    // up with the first, as in:
+    //
+    //     method() =>
+    //         argument &&
+    //         argument &&
+    //         argument;
+    var isArrowBody = node.parent is ExpressionFunctionBody;
+    if (!isArrowBody) builder.nestExpression();
 
     // Start lazily so we don't force the operator to split if a line comment
     // appears before the first operand.
@@ -217,7 +227,7 @@
 
     builder.endBlockArgumentNesting();
 
-    builder.unnest();
+    if (!isArrowBody) builder.unnest();
     builder.endSpan();
     builder.endRule();
   }
@@ -715,7 +725,10 @@
     // Split after the "=>", using the rule created before the parameters
     // by _visitBody().
     split();
-    builder.endRule();
+
+    // If the body is a binary operator expression, then we want to force the
+    // split at `=>` if the operators split. See visitBinaryExpression().
+    if (node.expression is! BinaryExpression) builder.endRule();
 
     if (_isInLambda(node)) builder.endSpan();
 
@@ -725,6 +738,8 @@
     builder.endSpan();
     builder.endBlockArgumentNesting();
 
+    if (node.expression is BinaryExpression) builder.endRule();
+
     token(node.semicolon);
   }
 
diff --git a/test/regression/0000/0005.stmt b/test/regression/0000/0005.stmt
index e1d9b89..cba7a18 100644
--- a/test/regression/0000/0005.stmt
+++ b/test/regression/0000/0005.stmt
@@ -4,7 +4,8 @@
     path.isWithin(rootDirectory, directory)).toList();
 <<<
 var overlapping = _directories.keys
-    .where((directory) => path.isWithin(directory, rootDirectory) ||
+    .where((directory) =>
+        path.isWithin(directory, rootDirectory) ||
         path.isWithin(rootDirectory, directory))
     .toList();
 >>>
diff --git a/test/regression/0000/0006.stmt b/test/regression/0000/0006.stmt
index ed8ad97..e7d45c2 100644
--- a/test/regression/0000/0006.stmt
+++ b/test/regression/0000/0006.stmt
@@ -10,5 +10,6 @@
     messageMentions(id.toString()) ||
     messageMentions(path.fromUri(entry.assetId.path));
 <<<
-messageMentionsAsset(id) => messageMentions(id.toString()) ||
+messageMentionsAsset(id) =>
+    messageMentions(id.toString()) ||
     messageMentions(path.fromUri(entry.assetId.path));
\ No newline at end of file
diff --git a/test/regression/0000/0026.stmt b/test/regression/0000/0026.stmt
index b5a1c7e..4fa6c76 100644
--- a/test/regression/0000/0026.stmt
+++ b/test/regression/0000/0026.stmt
@@ -6,4 +6,4 @@
 <<<
 experimentalBootstrap = document.querySelectorAll('link').any((link) =>
     link.attributes['rel'] == 'import' &&
-        link.attributes['href'] == POLYMER_EXPERIMENTAL_HTML);
\ No newline at end of file
+    link.attributes['href'] == POLYMER_EXPERIMENTAL_HTML);
\ No newline at end of file
diff --git a/test/regression/0000/0076.unit b/test/regression/0000/0076.unit
index 0c6dd96..632b8e1 100644
--- a/test/regression/0000/0076.unit
+++ b/test/regression/0000/0076.unit
@@ -4,7 +4,8 @@
 }
 <<<
 class TokenType {
-  bool get isUserDefinableOperator => identical(lexeme, "==") ||
+  bool get isUserDefinableOperator =>
+      identical(lexeme, "==") ||
       identical(lexeme, "~") ||
       identical(lexeme, "[]") ||
       identical(lexeme, "[]=") ||
diff --git a/test/regression/0100/0130.unit b/test/regression/0100/0130.unit
index 8974b73..d63287b 100644
--- a/test/regression/0100/0130.unit
+++ b/test/regression/0100/0130.unit
@@ -4,7 +4,8 @@
           _borderWidth +
       clientY;
 <<<
-  int get screenY => window.screenTop +
+  int get screenY =>
+      window.screenTop +
       window.outerHeight -
       window.innerHeight -
       _borderWidth +
diff --git a/test/regression/0400/0434.unit b/test/regression/0400/0434.unit
new file mode 100644
index 0000000..4089412
--- /dev/null
+++ b/test/regression/0400/0434.unit
@@ -0,0 +1,20 @@
+>>> (indent 2)
+  @eventHandler
+  bool computeChangePasswordDisabled(
+          String email, String password, String newPassword) =>
+      email == null ||
+          email.isEmpty ||
+          password == null ||
+          password.isEmpty ||
+          newPassword == null ||
+          newPassword.isEmpty;
+<<<
+  @eventHandler
+  bool computeChangePasswordDisabled(
+          String email, String password, String newPassword) =>
+      email == null ||
+      email.isEmpty ||
+      password == null ||
+      password.isEmpty ||
+      newPassword == null ||
+      newPassword.isEmpty;
\ No newline at end of file
diff --git a/test/splitting/mixed.stmt b/test/splitting/mixed.stmt
index 5e641cb..33cdba4 100644
--- a/test/splitting/mixed.stmt
+++ b/test/splitting/mixed.stmt
@@ -95,14 +95,15 @@
 receiver.firstMethod().next((parameter) => longIdentifier == veryLongIdentifier);
 <<<
 receiver.firstMethod().next(
-    (parameter) => longIdentifier ==
+    (parameter) =>
+        longIdentifier ==
         veryLongIdentifier);
 >>> wrap after =>
 receiver.firstMethod().next(() => veryveryveryverylongIdentifier == veryLongIdentifier);
 <<<
 receiver.firstMethod().next(() =>
     veryveryveryverylongIdentifier ==
-        veryLongIdentifier);
+    veryLongIdentifier);
 >>> wrap at nested binary operator
 receiver.firstMethod().next(longIdentifier == veryLongIdentifier);
 <<<
@@ -197,4 +198,16 @@
 <<<
 longIdentifier +
         (longIdentifier ? 0 : 1) ==
-    identifier;
\ No newline at end of file
+    identifier;
+>>> normal indent before unsplit binary operators in => body
+veryLongFunction() => extremelyLongArgument + argument;
+<<<
+veryLongFunction() =>
+    extremelyLongArgument + argument;
+>>> no extra indent before binary operators in => body
+veryLongFunction() => longArgument + longArgument + longArgument;
+<<<
+veryLongFunction() =>
+    longArgument +
+    longArgument +
+    longArgument;
\ No newline at end of file