Split empty catch blocks with finally clauses.

Fix #1029
diff --git a/CHANGELOG.md b/CHANGELOG.md
index 387b186..a5c022f 100644
--- a/CHANGELOG.md
+++ b/CHANGELOG.md
@@ -5,6 +5,7 @@
 * Always split cascades with multiple sections (#1006).
 * Don't indent cascades farther than their receiver method chains.
 * Optimize line splitting cascades (#811).
+* Split empty catch blocks with finally clauses (#1029).
 
 # 2.0.1
 
diff --git a/lib/src/argument_list_visitor.dart b/lib/src/argument_list_visitor.dart
index a22864f..429d5bc 100644
--- a/lib/src/argument_list_visitor.dart
+++ b/lib/src/argument_list_visitor.dart
@@ -316,7 +316,7 @@
   final int _trailingBlocks;
 
   /// The rule used to split the bodies of all block arguments.
-  Rule? get blockRule => _blockRule;
+  Rule get blockRule => _blockRule!;
   Rule? _blockRule;
 
   /// The most recent chunk that split before an argument.
@@ -446,7 +446,7 @@
       rule.disableSplitOnInnerRules();
 
       // Tell it to use the rule we've already created.
-      visitor.beforeBlock(argumentBlock, blockRule!, previousSplit);
+      visitor.beforeBlock(argumentBlock, blockRule, previousSplit);
     } else if (_allArguments.length > 1) {
       // Edge case: Only bump the nesting if there are multiple arguments. This
       // lets us avoid spurious indentation in cases like:
diff --git a/lib/src/nesting_level.dart b/lib/src/nesting_level.dart
index acbdfe3..3e2c5f8 100644
--- a/lib/src/nesting_level.dart
+++ b/lib/src/nesting_level.dart
@@ -25,7 +25,7 @@
   /// The nesting level surrounding this one, or `null` if this is represents
   /// top level code in a block.
   NestingLevel? get parent => _parent;
-  NestingLevel? _parent;
+  final NestingLevel? _parent;
 
   /// The number of characters that this nesting level is indented relative to
   /// the containing level.
@@ -42,7 +42,9 @@
 
   bool get isNested => _parent != null;
 
-  NestingLevel() : indent = 0;
+  NestingLevel()
+      : _parent = null,
+        indent = 0;
 
   NestingLevel._(this._parent, this.indent);
 
diff --git a/lib/src/source_visitor.dart b/lib/src/source_visitor.dart
index 9b0517d..c97f0b5 100644
--- a/lib/src/source_visitor.dart
+++ b/lib/src/source_visitor.dart
@@ -478,19 +478,7 @@
     // else is always split.
     if (_isEmptyCollection(node.statements, node.rightBracket)) {
       token(node.leftBracket);
-
-      // Force a split when used as the then body of an if with an else:
-      //
-      //     if (condition) {
-      //     } else ...
-      if (node.parent is IfStatement) {
-        var ifStatement = node.parent as IfStatement;
-        if (ifStatement.elseStatement != null &&
-            ifStatement.thenStatement == node) {
-          newline();
-        }
-      }
-
+      if (_splitEmptyBlock(node)) newline();
       token(node.rightBracket);
       return;
     }
@@ -2034,7 +2022,6 @@
     token(node.rightParenthesis);
     builder.unnest();
 
-    @override
     void visitClause(Statement clause) {
       if (clause is Block || clause is IfStatement) {
         space();
@@ -3417,6 +3404,31 @@
   bool _isEmptyCollection(Iterable<AstNode> nodes, Token rightBracket) =>
       nodes.isEmpty && rightBracket.precedingComments == null;
 
+  /// Whether [node] should be forced to split even if completely empty.
+  ///
+  /// Most empty blocks format as `{}` but in a couple of cases where there is
+  /// a subsequent block, we split the previous one.
+  bool _splitEmptyBlock(Block node) {
+    // Force a split when used as the then body of an if with an else:
+    //
+    //     if (condition) {
+    //     } else ...
+    if (node.parent is IfStatement) {
+      var ifStatement = node.parent as IfStatement;
+      return ifStatement.elseStatement != null &&
+          ifStatement.thenStatement == node;
+    }
+
+    // Force a split in an empty catch if there is a finally:
+    if (node.parent is CatchClause && node.parent!.parent is TryStatement) {
+      var tryStatement = node.parent!.parent as TryStatement;
+      return tryStatement.finallyBlock != null &&
+          tryStatement.catchClauses.any((clause) => clause.body == node);
+    }
+
+    return false;
+  }
+
   /// If [node] is a spread of a non-empty collection literal, then this
   /// returns the token for the opening bracket of the collection, as in:
   ///
diff --git a/test/regression/1000/1029.unit b/test/regression/1000/1029.unit
new file mode 100644
index 0000000..8dda931
--- /dev/null
+++ b/test/regression/1000/1029.unit
@@ -0,0 +1,17 @@
+>>>
+void main() {
+  try {
+    doSomething();
+  } on Exception catch (e) {} finally {
+    cleanupSomething();
+  }
+}
+<<<
+void main() {
+  try {
+    doSomething();
+  } on Exception catch (e) {
+  } finally {
+    cleanupSomething();
+  }
+}
\ No newline at end of file
diff --git a/test/splitting/statements.stmt b/test/splitting/statements.stmt
index 13e9aea..dde279c 100644
--- a/test/splitting/statements.stmt
+++ b/test/splitting/statements.stmt
@@ -70,4 +70,33 @@
 }
 <<<
 if (condition) {
-} else {}
\ No newline at end of file
+} else {}
+>>> split empty catch if there is a finally
+try {;} catch (err) {} finally {;}
+<<<
+try {
+  ;
+} catch (err) {
+} finally {
+  ;
+}
+>>> split empty on if there is a finally
+try {;} on Exception {} finally {;}
+<<<
+try {
+  ;
+} on Exception {
+} finally {
+  ;
+}
+>>> split all empty catches if there is a finally
+try {;} catch (err1) {} catch (err2) {} catch (err3) {} finally {;}
+<<<
+try {
+  ;
+} catch (err1) {
+} catch (err2) {
+} catch (err3) {
+} finally {
+  ;
+}
\ No newline at end of file