Merge pull request #904 from dart-lang/split-nested-control-flow

For split control flow elements that contain other ones.
diff --git a/CHANGELOG.md b/CHANGELOG.md
index ebb360b..9265c5e 100644
--- a/CHANGELOG.md
+++ b/CHANGELOG.md
@@ -3,7 +3,8 @@
 * Add `--fix-single-cascade-statements`.
 * Correctly handle `var` in `--fix-function-typedefs` (#826).
 * Preserve leading indentation in fixed doc comments (#821).
-* Add `--verbose` to hide advanced options in `--help` output
+* Add `--verbose` to hide advanced options in `--help` output.
+* Split outer nested control flow elements (#869).
 
 # 1.3.3
 
diff --git a/lib/src/source_visitor.dart b/lib/src/source_visitor.dart
index ffd7a0f..caeffe9 100644
--- a/lib/src/source_visitor.dart
+++ b/lib/src/source_visitor.dart
@@ -117,6 +117,9 @@
     return false;
   }
 
+  static bool _isControlFlowElement(AstNode node) =>
+      node is IfElement || node is ForElement;
+
   /// The builder for the block that is currently being visited.
   ChunkBuilder builder;
 
@@ -1561,6 +1564,11 @@
 
     if (!isSpreadBody) builder.endBlockArgumentNesting();
     builder.unnest();
+
+    // If a control flow element is nested inside another, force the outer one
+    // to split.
+    if (_isControlFlowElement(node.body)) builder.forceRules();
+
     builder.endRule();
   }
 
@@ -1867,7 +1875,6 @@
       beforeBlock(elseSpreadBracket, spreadRule, null);
     }
 
-    @override
     void visitChild(CollectionElement element, CollectionElement child) {
       builder.nestExpression(indent: 2, now: true);
 
@@ -1894,6 +1901,7 @@
     // condition or the then clause, we want the then and else clauses to split.
     builder.startRule();
 
+    var hasInnerControlFlow = false;
     for (var element in ifElements) {
       // The condition.
       token(element.ifKeyword);
@@ -1903,6 +1911,9 @@
       token(element.rightParenthesis);
 
       visitChild(element, element.thenElement);
+      if (_isControlFlowElement(element.thenElement)) {
+        hasInnerControlFlow = true;
+      }
 
       // 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]
@@ -1924,8 +1935,17 @@
 
     // Handle the final trailing else if there is one.
     var lastElse = ifElements.last.elseElement;
-    if (lastElse != null) visitChild(lastElse, lastElse);
+    if (lastElse != null) {
+      visitChild(lastElse, lastElse);
 
+      if (_isControlFlowElement(lastElse)) {
+        hasInnerControlFlow = true;
+      }
+    }
+
+    // If a control flow element is nested inside another, force the outer one
+    // to split.
+    if (hasInnerControlFlow) builder.forceRules();
     builder.endRule();
   }
 
diff --git a/test/regression/0800/0869.stmt b/test/regression/0800/0869.stmt
new file mode 100644
index 0000000..bd474dc
--- /dev/null
+++ b/test/regression/0800/0869.stmt
@@ -0,0 +1,9 @@
+>>>
+var failed = [
+  for (var e in _restResultEvents) if (!e.isSuccess) e.message,
+];
+<<<
+var failed = [
+  for (var e in _restResultEvents)
+    if (!e.isSuccess) e.message,
+];
\ No newline at end of file
diff --git a/test/splitting/list_collection_for.stmt b/test/splitting/list_collection_for.stmt
index b569861..6d84800 100644
--- a/test/splitting/list_collection_for.stmt
+++ b/test/splitting/list_collection_for.stmt
@@ -72,15 +72,27 @@
       body;
     }
 ];
->>> nested for doesn't split if it fits
+>>> split if child is for
 var l = [for (a in b) for (c in d) t];
 <<<
-var l = [for (a in b) for (c in d) t];
->>> split collection before nested for
-var list = [for (a in b) for (c in d) longThing];
+var l = [
+  for (a in b)
+    for (c in d) t
+];
+>>> split if child is if
+var list = [
+  for (var a in b) if (c) d
+];
 <<<
 var list = [
-  for (a in b) for (c in d) longThing
+  for (var a in b)
+    if (c) d
+];
+>>> split collection before body
+var list = [for (a in b) longThingThatIsLong];
+<<<
+var list = [
+  for (a in b) longThingThatIsLong
 ];
 >>> just split outer for
 var list = [for (a in b) for (c in d) longThingThatIsLong];
@@ -89,6 +101,30 @@
   for (a in b)
     for (c in d) longThingThatIsLong
 ];
+>>> nested list inside for element
+var list = [for (var a in b) [c]];
+<<<
+var list = [
+  for (var a in b) [c]
+];
+>>> nested spread list inside for element
+var list = [for (var a in b) ...[c]];
+<<<
+var list = [
+  for (var a in b) ...[c]
+];
+>>> nested if inside list
+var l = [for (var a in b) [if (c) d]];
+<<<
+var l = [
+  for (var a in b) [if (c) d]
+];
+>>> nested for inside list
+var l = [for (a in b) [for (c in d) e]];
+<<<
+var l = [
+  for (a in b) [for (c in d) e]
+];
 >>> split in for-in type
 var list = [for (LongGenericTypeName<TypeArg, AnotherTypeArgument> a in b) body];
 <<<
diff --git a/test/splitting/list_collection_if.stmt b/test/splitting/list_collection_if.stmt
index 00d9cb3..467d2f8 100644
--- a/test/splitting/list_collection_if.stmt
+++ b/test/splitting/list_collection_if.stmt
@@ -190,15 +190,27 @@
       body;
     }
 ];
->>> nested if doesn't split if it fits
+>>> split if child is if
 var list = [if (c) if (d) thing];
 <<<
-var list = [if (c) if (d) thing];
->>> split collection before nested if
-var list = [if (c) if (d) fairlyLongThingHere];
-<<<
 var list = [
-  if (c) if (d) fairlyLongThingHere
+  if (c)
+    if (d) thing
+];
+>>> split if child is for
+var list = [
+  if (a) for (var b in c) thing
+];
+<<<
+var list = [
+  if (a)
+    for (var b in c) thing
+];
+>>> split collection before body
+var list = [if (c) longThingHereThatIsLong];
+<<<
+var list = [
+  if (c) longThingHereThatIsLong
 ];
 >>> just split outer if
 var list = [if (condition) if (another) longThingHereThatIsLong];
@@ -207,6 +219,32 @@
   if (condition)
     if (another) longThingHereThatIsLong
 ];
+>>> nested list inside if element
+var list = [if (a) [b]];
+<<<
+var list = [
+  if (a) [b]
+];
+>>> nested spread list inside if element
+var list = [if (a) ...[b]];
+<<<
+var list = [
+  if (a) ...[b]
+];
+>>> nested if inside list
+var list = [if (a) [if (b) c]];
+<<<
+var list = [
+  if (a) [if (b) c]
+];
+>>> nested for inside list
+var l = [
+  if (a) [for (var b in c) d]
+];
+<<<
+var l = [
+  if (a) [for (var b in c) d]
+];
 >>> split inside condition
 var list = [if (veryLongCondition + thatNeedsToSplit) thing];
 <<<
diff --git a/test/splitting/map_collection_for.stmt b/test/splitting/map_collection_for.stmt
index eed802e..ed3448f 100644
--- a/test/splitting/map_collection_for.stmt
+++ b/test/splitting/map_collection_for.stmt
@@ -72,15 +72,18 @@
       body;
     }
 };
->>> nested for doesn't split if it fits
+>>> nested for splits outer
 ({for (a in b) for (c in d) 1: 1});
 <<<
-({for (a in b) for (c in d) 1: 1});
->>> split collection before nested for
-var map = {for (a in b) for (c in d) 1: longThing};
+({
+  for (a in b)
+    for (c in d) 1: 1
+});
+>>> split collection before body
+var map = {for (a in b) longThingThatIsLong};
 <<<
 var map = {
-  for (a in b) for (c in d) 1: longThing
+  for (a in b) longThingThatIsLong
 };
 >>> just split outer for
 var map = {for (a in b) for (c in d) 1: longThingThatIsLong};
diff --git a/test/splitting/map_collection_if.stmt b/test/splitting/map_collection_if.stmt
index ef4758a..3b01be5 100644
--- a/test/splitting/map_collection_if.stmt
+++ b/test/splitting/map_collection_if.stmt
@@ -168,15 +168,18 @@
       body;
     }
 };
->>> nested if doesn't split if it fits
+>>> nested if splits outer
 var map = {if (c) if (d) thing: 1};
 <<<
-var map = {if (c) if (d) thing: 1};
->>> split collection before nested if
-var map = {if (c) if (d) fairlyLongThingHere: 1};
-<<<
 var map = {
-  if (c) if (d) fairlyLongThingHere: 1
+  if (c)
+    if (d) thing: 1
+};
+>>> split collection before body
+var map = {if (c) fairlyLongThingHere: 1};
+<<<
+var map = {
+  if (c) fairlyLongThingHere: 1
 };
 >>> just split outer if
 var map = {if (condition) if (another) longThingThatIsLong: 1};
diff --git a/test/splitting/set_collection_for.stmt b/test/splitting/set_collection_for.stmt
index 4d2a399..007b4fc 100644
--- a/test/splitting/set_collection_for.stmt
+++ b/test/splitting/set_collection_for.stmt
@@ -72,15 +72,18 @@
       body;
     }
 };
->>> nested for doesn't split if it fits
+>>> nested for splits outer for
 var s = {for (a in b) for (c in d) t};
 <<<
-var s = {for (a in b) for (c in d) t};
->>> split collection before nested for
-var set = {for (a in b) for (c in d) longThing};
+var s = {
+  for (a in b)
+    for (c in d) t
+};
+>>> split collection before body
+var set = {for (a in b) longThingThatIsLong};
 <<<
 var set = {
-  for (a in b) for (c in d) longThing
+  for (a in b) longThingThatIsLong
 };
 >>> just split outer for
 var set = {for (a in b) for (c in d) longThingThatIsLong};
diff --git a/test/splitting/set_collection_if.stmt b/test/splitting/set_collection_if.stmt
index 86375ad..75f722c 100644
--- a/test/splitting/set_collection_if.stmt
+++ b/test/splitting/set_collection_if.stmt
@@ -168,15 +168,18 @@
       body;
     }
 };
->>> nested if doesn't split if it fits
+>>> nested if splits outer
 var set = {if (c) if (d) thing};
 <<<
-var set = {if (c) if (d) thing};
->>> split collection before nested if
-var set = {if (c) if (d) fairlyLongThingHere};
-<<<
 var set = {
-  if (c) if (d) fairlyLongThingHere
+  if (c)
+    if (d) thing
+};
+>>> split collection before body
+var set = {if (c) longThingHereThatIsLong};
+<<<
+var set = {
+  if (c) longThingHereThatIsLong
 };
 >>> just split outer if
 var set = {if (condition) if (another) longThingHereThatIsLong};