Avoid needless indentation on chained if-else elements. (#822)

* Avoid needless indentation on chained if-else elements.

Fix #813.

* Use collection for!

I also went ahead and pub upgraded, which brought in some lints, which
I fixed.
diff --git a/CHANGELOG.md b/CHANGELOG.md
index 65250ae..e31a726 100644
--- a/CHANGELOG.md
+++ b/CHANGELOG.md
@@ -2,6 +2,7 @@
 
 * Better indentation of function expressions inside trailing comma argument
   lists. (Thanks a14@!)
+* Avoid needless indentation on chained if-else elements (#813).
 
 # 1.2.7
 
diff --git a/lib/src/call_chain_visitor.dart b/lib/src/call_chain_visitor.dart
index 936beee..7546e22 100644
--- a/lib/src/call_chain_visitor.dart
+++ b/lib/src/call_chain_visitor.dart
@@ -108,7 +108,9 @@
       //         .property
       //         .method()[1][2];
       var call = expression;
-      while (call is IndexExpression) call = (call as IndexExpression).target;
+      while (call is IndexExpression) {
+        call = (call as IndexExpression).target;
+      }
 
       if (SourceVisitor.looksLikeStaticCall(call)) {
         // Don't include things that look like static method or constructor
@@ -180,7 +182,9 @@
     }
 
     if (blockCalls != null) {
-      for (var blockCall in blockCalls) calls.remove(blockCall);
+      for (var blockCall in blockCalls) {
+        calls.remove(blockCall);
+      }
     }
 
     if (hangingCall != null) {
diff --git a/lib/src/line_splitting/solve_state.dart b/lib/src/line_splitting/solve_state.dart
index 0ae7a70..734b1e0 100644
--- a/lib/src/line_splitting/solve_state.dart
+++ b/lib/src/line_splitting/solve_state.dart
@@ -420,7 +420,9 @@
     });
 
     // Add the costs for the spans containing splits.
-    for (var span in splitSpans) cost += span.cost;
+    for (var span in splitSpans) {
+      cost += span.cost;
+    }
 
     // Finish the last line.
     endLine(_splitter.chunks.length);
diff --git a/lib/src/source_visitor.dart b/lib/src/source_visitor.dart
index 96f60bc..47230c6 100644
--- a/lib/src/source_visitor.dart
+++ b/lib/src/source_visitor.dart
@@ -1517,15 +1517,14 @@
   }
 
   void visitIfElement(IfElement node) {
-    // Wrap the whole thing in a single rule. If a split happens inside the
-    // condition or the then clause, we want the then and else clauses to split.
-    builder.startRule();
-
-    token(node.ifKeyword);
-    space();
-    token(node.leftParenthesis);
-    visit(node.condition);
-    token(node.rightParenthesis);
+    // Treat a chain of if-else elements as a single unit so that we don't
+    // unnecessarily indent each subsequent section of the chain.
+    var ifElements = [
+      for (CollectionElement thisNode = node;
+          thisNode is IfElement;
+          thisNode = (thisNode as IfElement).elseElement)
+        thisNode as IfElement
+    ];
 
     // If the body of the then or else branch is a spread of a collection
     // literal, then we want to format those collections more like blocks than
@@ -1551,66 +1550,81 @@
     //
     // To do that, if we see that either clause is a spread collection, we
     // create a single rule and force both collections to use it.
-    var thenSpreadBracket = _findSpreadCollectionBracket(node.thenElement);
-    var elseSpreadBracket = _findSpreadCollectionBracket(node.elseElement);
-
-    if (thenSpreadBracket != null || elseSpreadBracket != null) {
-      var spreadRule = Rule();
-      if (thenSpreadBracket != null) {
-        beforeBlock(thenSpreadBracket, spreadRule, null);
-      }
-
-      if (elseSpreadBracket != null) {
-        beforeBlock(elseSpreadBracket, spreadRule, null);
+    var spreadRule = Rule();
+    var spreadBrackets = <CollectionElement, Token>{};
+    for (var element in ifElements) {
+      var spreadBracket = _findSpreadCollectionBracket(element.thenElement);
+      if (spreadBracket != null) {
+        spreadBrackets[element] = spreadBracket;
+        beforeBlock(spreadBracket, spreadRule, null);
       }
     }
 
-    builder.nestExpression(indent: 2, now: true);
-
-    // Treat a spread of a collection literal like a block in an if statement
-    // and don't split after the "else".
-    if (thenSpreadBracket != null) {
-      space();
-    } else {
-      split();
-
-      // If the then clause is a non-spread collection or lambda, make sure the
-      // body is indented.
-      builder.startBlockArgumentNesting();
+    var elseSpreadBracket =
+        _findSpreadCollectionBracket(ifElements.last.elseElement);
+    if (elseSpreadBracket != null) {
+      spreadBrackets[ifElements.last.elseElement] = elseSpreadBracket;
+      beforeBlock(elseSpreadBracket, spreadRule, null);
     }
 
-    visit(node.thenElement);
-
-    if (thenSpreadBracket == null) builder.endBlockArgumentNesting();
-    builder.unnest();
-
-    if (node.elseElement != null) {
-      if (thenSpreadBracket != null) {
-        space();
-      } else {
-        split();
-      }
-
-      token(node.elseKeyword);
-
+    visitChild(CollectionElement element, CollectionElement child) {
       builder.nestExpression(indent: 2, now: true);
 
-      if (elseSpreadBracket != null) {
+      // Treat a spread of a collection literal like a block in an if statement
+      // and don't split after the "else".
+      var isSpread = spreadBrackets.containsKey(element);
+      if (isSpread) {
         space();
       } else {
         split();
 
-        // If the else clause is a non-spread collection or lambda, make sure
-        // the body is indented.
+        // If the then clause is a non-spread collection or lambda, make sure the
+        // body is indented.
         builder.startBlockArgumentNesting();
       }
 
-      visit(node.elseElement);
+      visit(child);
 
-      if (elseSpreadBracket == null) builder.endBlockArgumentNesting();
+      if (!isSpread) builder.endBlockArgumentNesting();
       builder.unnest();
     }
 
+    // Wrap the whole thing in a single rule. If a split happens inside the
+    // condition or the then clause, we want the then and else clauses to split.
+    builder.startRule();
+
+    for (var element in ifElements) {
+      // The condition.
+      token(element.ifKeyword);
+      space();
+      token(element.leftParenthesis);
+      visit(element.condition);
+      token(element.rightParenthesis);
+
+      visitChild(element, element.thenElement);
+
+      // Handle this element's "else" keyword and prepare to write the element,
+      // but don't write it. It will either be the next element in [ifElements]
+      // or the final else element handled after the loop.
+      if (element.elseElement != null) {
+        if (spreadBrackets.containsKey(element)) {
+          space();
+        } else {
+          split();
+        }
+
+        token(node.elseKeyword);
+
+        // If there is another if element in the chain, put a space between
+        // it and this "else".
+        if (element != ifElements.last) space();
+      }
+    }
+
+    // Handle the final trailing else if there is one.
+    var lastElse = ifElements.last.elseElement;
+    if (lastElse != null) visitChild(lastElse, lastElse);
+
     builder.endRule();
   }
 
diff --git a/pubspec.lock b/pubspec.lock
index 3b394d1..374e069 100644
--- a/pubspec.lock
+++ b/pubspec.lock
@@ -14,14 +14,14 @@
       name: args
       url: "https://pub.dartlang.org"
     source: hosted
-    version: "1.5.1"
+    version: "1.5.2"
   async:
     dependency: transitive
     description:
       name: async
       url: "https://pub.dartlang.org"
     source: hosted
-    version: "2.1.0"
+    version: "2.2.0"
   boolean_selector:
     dependency: transitive
     description:
@@ -105,14 +105,14 @@
       name: http
       url: "https://pub.dartlang.org"
     source: hosted
-    version: "0.12.0+1"
+    version: "0.12.0+2"
   http_multi_server:
     dependency: transitive
     description:
       name: http_multi_server
       url: "https://pub.dartlang.org"
     source: hosted
-    version: "2.0.5"
+    version: "2.1.0"
   http_parser:
     dependency: transitive
     description:
@@ -140,7 +140,7 @@
       name: json_rpc_2
       url: "https://pub.dartlang.org"
     source: hosted
-    version: "2.0.9"
+    version: "2.1.0"
   kernel:
     dependency: transitive
     description:
@@ -168,7 +168,7 @@
       name: mime
       url: "https://pub.dartlang.org"
     source: hosted
-    version: "0.9.6+2"
+    version: "0.9.6+3"
   multi_server_socket:
     dependency: transitive
     description:
@@ -210,7 +210,7 @@
       name: pedantic
       url: "https://pub.dartlang.org"
     source: hosted
-    version: "1.5.0"
+    version: "1.7.0"
   pool:
     dependency: transitive
     description:
@@ -231,7 +231,7 @@
       name: shelf
       url: "https://pub.dartlang.org"
     source: hosted
-    version: "0.7.4"
+    version: "0.7.5"
   shelf_packages_handler:
     dependency: transitive
     description:
@@ -252,7 +252,7 @@
       name: shelf_web_socket
       url: "https://pub.dartlang.org"
     source: hosted
-    version: "0.2.2+5"
+    version: "0.2.3"
   source_map_stack_trace:
     dependency: transitive
     description:
@@ -287,7 +287,7 @@
       name: stream_channel
       url: "https://pub.dartlang.org"
     source: hosted
-    version: "1.7.0"
+    version: "2.0.0"
   string_scanner:
     dependency: transitive
     description:
@@ -308,21 +308,21 @@
       name: test
       url: "https://pub.dartlang.org"
     source: hosted
-    version: "1.6.0"
+    version: "1.6.4"
   test_api:
     dependency: transitive
     description:
       name: test_api
       url: "https://pub.dartlang.org"
     source: hosted
-    version: "0.2.3"
+    version: "0.2.6"
   test_core:
     dependency: transitive
     description:
       name: test_core
       url: "https://pub.dartlang.org"
     source: hosted
-    version: "0.2.2"
+    version: "0.2.6"
   test_descriptor:
     dependency: "direct dev"
     description:
@@ -350,7 +350,7 @@
       name: vm_service_client
       url: "https://pub.dartlang.org"
     source: hosted
-    version: "0.2.6"
+    version: "0.2.6+2"
   watcher:
     dependency: transitive
     description:
@@ -364,7 +364,7 @@
       name: web_socket_channel
       url: "https://pub.dartlang.org"
     source: hosted
-    version: "1.0.12"
+    version: "1.0.13"
   yaml:
     dependency: "direct dev"
     description:
@@ -373,4 +373,4 @@
     source: hosted
     version: "2.1.15"
 sdks:
-  dart: ">=2.1.1-dev.0.0 <3.0.0"
+  dart: ">=2.3.0 <3.0.0"
diff --git a/pubspec.yaml b/pubspec.yaml
index b1d58e5..1cfc78c 100644
--- a/pubspec.yaml
+++ b/pubspec.yaml
@@ -8,7 +8,7 @@
 homepage: https://github.com/dart-lang/dart_style
 
 environment:
-  sdk: '>=2.1.0 <3.0.0'
+  sdk: '>=2.3.0 <3.0.0'
 
 dependencies:
   analyzer: '>=0.36.3 <0.37.0'
diff --git a/test/splitting/list_collection_if.stmt b/test/splitting/list_collection_if.stmt
index 7f272a7..36e44c2 100644
--- a/test/splitting/list_collection_if.stmt
+++ b/test/splitting/list_collection_if.stmt
@@ -214,4 +214,103 @@
   if (veryLongCondition +
       thatNeedsToSplit)
     thing
+];
+>>> chained if-else
+var list = [if (condition1) thing1 else if (condition2) thing2];
+<<<
+var list = [
+  if (condition1)
+    thing1
+  else if (condition2)
+    thing2
+];
+>>> chained if-else with else at end
+var list = [if (condition1) thing1 else if (condition2) thing2 else thing3];
+<<<
+var list = [
+  if (condition1)
+    thing1
+  else if (condition2)
+    thing2
+  else
+    thing3
+];
+>>> long chained if-else
+var list = [if (condition1) thing1 else if (condition2) thing2 else if (condition3) thing3];
+<<<
+var list = [
+  if (condition1)
+    thing1
+  else if (condition2)
+    thing2
+  else if (condition3)
+    thing3
+];
+>>> long chained if-else with else at end
+var list = [if (condition1) thing1 else if (condition2) thing2 else if (condition3) thing3 else thing4];
+<<<
+var list = [
+  if (condition1)
+    thing1
+  else if (condition2)
+    thing2
+  else if (condition3)
+    thing3
+  else
+    thing4
+];
+>>> chained if-else with spread and unspread collections
+var list = [
+  if (condition1) ...[
+    spreadList
+  ] else if (condition2)
+    [notSpread]
+  else if (condition3)
+    thing1
+  else ...{
+    spreadSet
+  }
+];
+<<<
+var list = [
+  if (condition1) ...[
+    spreadList
+  ] else if (condition2)
+    [notSpread]
+  else if (condition3)
+    thing1
+  else ...{
+    spreadSet
+  }
+];
+>>> nested chained if-else
+var list = [
+  if (condition1)
+    if (condition2) a else b
+  else if (condition4)
+    if (condition5) c
+    else if (condition6) d else e
+  else if (condition7)
+    if (condition8) ...[f]
+    else if (condition9) [g] else ...{h}
+];
+<<<
+var list = [
+  if (condition1)
+    if (condition2) a else b
+  else if (condition4)
+    if (condition5)
+      c
+    else if (condition6)
+      d
+    else
+      e
+  else if (condition7)
+    if (condition8) ...[
+      f
+    ] else if (condition9)
+      [g]
+    else ...{
+      h
+    }
 ];
\ No newline at end of file