Add tests, cover more expression types, and a few other tweaks.
diff --git a/lib/src/source_visitor.dart b/lib/src/source_visitor.dart
index 565a645..b856b9a 100644
--- a/lib/src/source_visitor.dart
+++ b/lib/src/source_visitor.dart
@@ -191,6 +191,10 @@
   final Map<Token, Rule> _blockRules = {};
   final Map<Token, Chunk> _blockPreviousChunks = {};
 
+  /// Comments and new lines attached to tokens added here are suppressed
+  /// from the output.
+  Set<Token> _suppressPrecedingCommentsAndNewLines = {};
+
   /// Initialize a newly created visitor to write source code representing
   /// the visited nodes to the given [writer].
   SourceVisitor(this._formatter, this._lineInfo, this._source) {
@@ -1205,7 +1209,7 @@
     writePrecedingCommentsAndNewlines(cascade.target.beginToken);
     _suppressPrecedingCommentsAndNewLines.add(cascade.target.beginToken);
 
-    final newTarget = astFactory.parenthesizedExpression(
+    var newTarget = astFactory.parenthesizedExpression(
         Token(TokenType.OPEN_PAREN, 0)
           ..previous = statement.beginToken.previous
           ..next = cascade.target.beginToken,
@@ -1222,8 +1226,8 @@
   }
 
   void _removeCascade(ExpressionStatement statement) {
-    final CascadeExpression cascade = statement.expression;
-    final subexpression = cascade.cascadeSections.single;
+    var cascade = statement.expression as CascadeExpression;
+    var subexpression = cascade.cascadeSections.single;
     builder.nestExpression();
 
     if (subexpression is AssignmentExpression) {
@@ -1256,9 +1260,9 @@
       // And similarly for PropertyAccess expressions.
       visit(_insertCascadeTargetIntoExpression(subexpression, cascade.target));
     } else {
-      throw StateError(
+      throw UnsupportedError(
           '--fix-single-cascade-statements: subexpression of cascade '
-          '"$cascade" has unhandled type ${subexpression.runtimeType}');
+          '"$cascade" has unsupported type ${subexpression.runtimeType}.');
     }
 
     token(statement.semicolon);
@@ -1272,36 +1276,42 @@
   /// expression. Callers must visit the nested expression themselves
   /// if-and-only-if this method returns false.
   bool _fixSingleCascadeStatement(ExpressionStatement statement) {
-    final CascadeExpression cascade = statement.expression;
+    var cascade = statement.expression as CascadeExpression;
     if (cascade.cascadeSections.length != 1) return false;
 
-    final subexpression = cascade.cascadeSections.single;
-
-    if (cascade.target is AsExpression ||
-        cascade.target is AwaitExpression ||
-        cascade.target is BinaryExpression ||
-        cascade.target is PrefixExpression) {
+    var target = cascade.target;
+    if (target is AsExpression ||
+        target is AwaitExpression ||
+        target is BinaryExpression ||
+        target is ConditionalExpression ||
+        target is IsExpression ||
+        target is PostfixExpression ||
+        target is PrefixExpression) {
       // In these cases, the cascade target needs to be parenthesized before
       // removing the cascade, otherwise the semantics will change.
       _fixCascadeByParenthesizingTarget(statement);
       return true;
-    } else if (cascade.target is IndexExpression ||
-        cascade.target is InstanceCreationExpression ||
-        cascade.target is MethodInvocation ||
-        cascade.target is ParenthesizedExpression ||
-        cascade.target is PrefixedIdentifier ||
-        cascade.target is PropertyAccess ||
-        cascade.target is SimpleIdentifier) {
+    } else if (target is BooleanLiteral ||
+        target is FunctionExpression ||
+        target is IndexExpression ||
+        target is InstanceCreationExpression ||
+        target is IntegerLiteral ||
+        target is ListLiteral ||
+        target is NullLiteral ||
+        target is MethodInvocation ||
+        target is ParenthesizedExpression ||
+        target is PrefixedIdentifier ||
+        target is PropertyAccess ||
+        target is SimpleIdentifier ||
+        target is StringLiteral ||
+        target is ThisExpression) {
       // OK to simply remove the cascade.
       _removeCascade(statement);
       return true;
     } else {
-      // Refuse to attempt fixing cases which haven't been well-tested.
-      // To fix this error, add the type to one of the above lists, after
-      // checking whether parentheses are needed for this case or not.
-      throw StateError(
-          '--fix-single-cascade-statements: TARGET of cascade "$cascade" '
-          'has unhandled type ${cascade.target.runtimeType}');
+      // If we get here, some new syntax was added to the language that the fix
+      // does not yet support. Leave it as is.
+      return false;
     }
   }
 
@@ -3532,10 +3542,6 @@
     if (after != null) after();
   }
 
-  /// Comments and new lines attached to tokens added here will be suppressed
-  /// from the output.
-  Set<Token> _suppressPrecedingCommentsAndNewLines = {};
-
   /// Writes all formatted whitespace and comments that appear before [token].
   bool writePrecedingCommentsAndNewlines(Token token) {
     var comment = token.precedingComments;
@@ -3550,15 +3556,9 @@
       return false;
     }
 
-    // Hack: Suppress comments from indicated tokens.
+    // If the token's comments are being moved by a fix, do not write them here.
     if (_suppressPrecedingCommentsAndNewLines.contains(token)) return false;
 
-    if (token.previous == null) {
-      throw StateError(
-          'Writing comments preceding `$token` but previous token is null; '
-          'next token is ${token.next}.');
-    }
-
     var previousLine = _endLine(token.previous);
     var tokenLine = _startLine(token);
 
diff --git a/test/fix_test.dart b/test/fix_test.dart
index 0cf3964..787f9c0 100644
--- a/test/fix_test.dart
+++ b/test/fix_test.dart
@@ -18,4 +18,6 @@
   testFile("fixes/function_typedefs.unit", [StyleFix.functionTypedefs]);
   testFile("fixes/optional_const.unit", [StyleFix.optionalConst]);
   testFile("fixes/optional_new.stmt", [StyleFix.optionalNew]);
+  testFile("fixes/single_cascade_statements.stmt",
+      [StyleFix.singleCascadeStatements]);
 }
diff --git a/test/fixes/single_cascade_statements.stmt b/test/fixes/single_cascade_statements.stmt
new file mode 100644
index 0000000..3ffe5a0
--- /dev/null
+++ b/test/fixes/single_cascade_statements.stmt
@@ -0,0 +1,137 @@
+40 columns                              |
+>>>
+obj..method();
+<<<
+obj.method();
+>>>
+obj..getter;
+<<<
+obj.getter;
+>>>
+obj..setter = 3;
+<<<
+obj.setter = 3;
+>>>
+obj..[subscript] = 3;
+<<<
+obj[subscript] = 3;
+>>> targets that don't need parentheses
+{
+  null..method();
+  123..method();
+  true..method();
+  "str"..method();
+  "str$interp"..method();
+  [1]..method();
+  () {}..method();
+
+  this..method();
+  new C()..method();
+  const C()..method();
+  foo()..method();
+  foo.bar()..method();
+  foo?.bar()..method();
+  super.foo()..method();
+}
+<<<
+{
+  null.method();
+  123.method();
+  true.method();
+  "str".method();
+  "str$interp".method();
+  [1].method();
+  () {}.method();
+
+  this.method();
+  new C().method();
+  const C().method();
+  foo().method();
+  foo.bar().method();
+  foo?.bar().method();
+  super.foo().method();
+}
+>>> targets that do need parentheses
+{
+  a as C..method();
+  a is C..method();
+  c ? a : b..method();
+  a ?? b..method();
+  a && b..method();
+  a || b..method();
+  a == b..method();
+  a != b..method();
+  a < b..method();
+  a > b..method();
+  a <= b..method();
+  a >= b..method();
+  a ^ b..method();
+  a | b..method();
+  a << b..method();
+  a >> b..method();
+  a + b..method();
+  a - b..method();
+  a * b..method();
+  a / b..method();
+  a ~/ b..method();
+  -a..method();
+  !a..method();
+  ~a..method();
+  a++..method();
+  a--..method();
+
+  foo() async {
+    await a..method();
+  }
+}
+<<<
+{
+  (a as C).method();
+  (a is C).method();
+  (c ? a : b).method();
+  (a ?? b).method();
+  (a && b).method();
+  (a || b).method();
+  (a == b).method();
+  (a != b).method();
+  (a < b).method();
+  (a > b).method();
+  (a <= b).method();
+  (a >= b).method();
+  (a ^ b).method();
+  (a | b).method();
+  (a << b).method();
+  (a >> b).method();
+  (a + b).method();
+  (a - b).method();
+  (a * b).method();
+  (a / b).method();
+  (a ~/ b).method();
+  (-a).method();
+  (!a).method();
+  (~a).method();
+  (a++).method();
+  (a--).method();
+
+  foo() async {
+    (await a).method();
+  }
+}
+>>> unaffected expressions
+{
+  foo..bar()..method();
+  a = b..method();
+  a += b..method();
+  () => a..method();
+  throw a..method();
+}
+<<<
+{
+  foo
+    ..bar()
+    ..method();
+  a = b..method();
+  a += b..method();
+  () => a..method();
+  throw a..method();
+}
\ No newline at end of file