Refactor to use enhanced enums for Output and Show. (#1116)

Also some other minor clean-up while I was at it:

- Remove unneeded library tags.
- Fix some raw typed collections.
- Use list element where it makes sense.
- Remove unneeded .whereType<Rule>() calls. (They were to filter out
  null, but the rule field is non-nullable now.)
diff --git a/benchmark/benchmark.dart b/benchmark/benchmark.dart
index feab837..6c79038 100644
--- a/benchmark/benchmark.dart
+++ b/benchmark/benchmark.dart
@@ -2,8 +2,6 @@
 // for details. All rights reserved. Use of this source code is governed by a
 // BSD-style license that can be found in the LICENSE file.
 
-library dart_style.benchmark.benchmark;
-
 import 'dart:io';
 
 import 'package:dart_style/dart_style.dart';
diff --git a/lib/src/cli/format_command.dart b/lib/src/cli/format_command.dart
index ad8b126..a05e240 100644
--- a/lib/src/cli/format_command.dart
+++ b/lib/src/cli/format_command.dart
@@ -87,20 +87,16 @@
       usageException('Cannot print a summary with JSON output.');
     }
 
-    int pageWidth;
-    try {
-      pageWidth = int.parse(argResults['line-length']);
-    } on FormatException catch (_) {
-      usageException('--line-length must be an integer, was '
-          '"${argResults['line-length']}".');
-    }
+    var pageWidth = int.tryParse(argResults['line-length']) ??
+        usageException('--line-length must be an integer, was '
+            '"${argResults['line-length']}".');
 
-    int indent;
-    try {
-      indent = int.parse(argResults['indent']);
-      if (indent < 0 || indent.toInt() != indent) throw FormatException();
-    } on FormatException catch (_) {
-      usageException('--indent must be a non-negative integer, was '
+    var indent = int.tryParse(argResults['indent']) ??
+        usageException('--indent must be an integer, was '
+            '"${argResults['indent']}".');
+
+    if (indent < 0) {
+      usageException('--indent must be non-negative, was '
           '"${argResults['indent']}".');
     }
 
diff --git a/lib/src/cli/formatter_options.dart b/lib/src/cli/formatter_options.dart
index c5ca1ce..2887029 100644
--- a/lib/src/cli/formatter_options.dart
+++ b/lib/src/cli/formatter_options.dart
@@ -2,8 +2,6 @@
 // for details. All rights reserved. Use of this source code is governed by a
 // BSD-style license that can be found in the LICENSE file.
 
-library dart_style.src.formatter_options;
-
 import 'dart:io';
 
 import '../source_code.dart';
diff --git a/lib/src/cli/output.dart b/lib/src/cli/output.dart
index f7d9bd7..9851268 100644
--- a/lib/src/cli/output.dart
+++ b/lib/src/cli/output.dart
@@ -7,36 +7,26 @@
 import '../source_code.dart';
 
 /// Where formatted code results should go.
-class Output {
+enum Output {
   /// Overwrite files on disc.
-  static const Output write = _WriteOutput();
+  write,
 
   /// Print the code to the terminal as human-friendly text.
-  static const Output show = _ShowOutput();
+  show,
 
   /// Print the code to the terminal as JSON.
-  static const Output json = _JsonOutput();
+  json,
 
   /// Do nothing. (Used when the user just wants the list of files that would
   /// be changed.)
-  static const Output none = Output._();
-
-  const Output._();
+  none;
 
   /// Write the file to disc.
   ///
   /// If stdin is being formatted, then [file] is `null`.
-  bool writeFile(File? file, String displayPath, SourceCode result) => false;
-
-  /// Print the file to the terminal in some way.
-  void showFile(String path, SourceCode result) {}
-}
-
-class _WriteOutput extends Output {
-  const _WriteOutput() : super._();
-
-  @override
   bool writeFile(File? file, String displayPath, SourceCode result) {
+    if (this != Output.write) return false;
+
     try {
       file!.writeAsStringSync(result.text);
     } on FileSystemException catch (err) {
@@ -46,35 +36,35 @@
 
     return true;
   }
-}
 
-class _ShowOutput extends Output {
-  const _ShowOutput() : super._();
-
-  @override
+  /// Print the file to the terminal in some way.
   void showFile(String path, SourceCode result) {
-    // Don't add an extra newline.
-    stdout.write(result.text);
-  }
-}
+    switch (this) {
+      case Output.show:
+        // Don't add an extra newline.
+        stdout.write(result.text);
+        break;
 
-class _JsonOutput extends Output {
-  const _JsonOutput() : super._();
+      case Output.json:
+        // TODO(rnystrom): Put an empty selection in here to remain compatible with
+        // the old formatter. Since there's no way to pass a selection on the
+        // command line, this will never be used, which is why it's hard-coded to
+        // -1, -1. If we add support for passing in a selection, put the real
+        // result here.
+        print(jsonEncode({
+          'path': path,
+          'source': result.text,
+          'selection': {
+            'offset': result.selectionStart ?? -1,
+            'length': result.selectionLength ?? -1
+          }
+        }));
+        break;
 
-  @override
-  void showFile(String path, SourceCode result) {
-    // TODO(rnystrom): Put an empty selection in here to remain compatible with
-    // the old formatter. Since there's no way to pass a selection on the
-    // command line, this will never be used, which is why it's hard-coded to
-    // -1, -1. If we add support for passing in a selection, put the real
-    // result here.
-    print(jsonEncode({
-      'path': path,
-      'source': result.text,
-      'selection': {
-        'offset': result.selectionStart ?? -1,
-        'length': result.selectionLength ?? -1
-      }
-    }));
+      case Output.write:
+      case Output.none:
+        // Do nothing.
+        break;
+    }
   }
 }
diff --git a/lib/src/cli/show.dart b/lib/src/cli/show.dart
index e3a8863..3343433 100644
--- a/lib/src/cli/show.dart
+++ b/lib/src/cli/show.dart
@@ -4,26 +4,24 @@
 import 'package:path/path.dart' as p;
 
 /// Which file paths should be printed.
-abstract class Show {
+enum Show {
   /// No files.
-  static const Show none = _NoneShow();
+  none,
 
   /// All traversed files.
-  static const Show all = _AllShow();
+  all,
 
   /// Only files whose formatting changed.
-  static const Show changed = _ChangedShow();
+  changed,
 
   /// The legacy dartfmt output style when not overwriting files.
-  static const Show legacy = _LegacyShow();
+  legacy,
 
   /// The legacy dartfmt output style when overwriting files.
-  static const Show overwrite = _OverwriteShow();
+  overwrite,
 
   /// The legacy dartfmt output style in "--dry-run".
-  static const Show dryRun = _DryRunShow();
-
-  const Show._();
+  dryRun;
 
   /// The display path to show for [file] which is in [directory].
   ///
@@ -31,22 +29,65 @@
   /// name is printed separately. The new CLI only prints file paths, so this
   /// includes the root directory to disambiguate which directory the file is
   /// in.
-  String displayPath(String directory, String file) => p.normalize(file);
+  String displayPath(String directory, String file) {
+    switch (this) {
+      case Show.legacy:
+      case Show.overwrite:
+      case Show.dryRun:
+        return p.relative(file, from: directory);
+
+      default:
+        return p.normalize(file);
+    }
+  }
 
   /// Describes a file that was processed.
   ///
   /// Returns whether or not this file should be displayed.
-  bool file(String path, {required bool changed, required bool overwritten}) =>
-      true;
+  bool file(String path, {required bool changed, required bool overwritten}) {
+    switch (this) {
+      case Show.all:
+      case Show.overwrite:
+        if (changed) {
+          _showFileChange(path, overwritten: overwritten);
+        } else {
+          print('Unchanged $path');
+        }
+        return true;
+
+      case Show.changed:
+        if (changed) _showFileChange(path, overwritten: overwritten);
+        return changed;
+
+      case Show.dryRun:
+        if (changed) print(path);
+        return true;
+
+      default:
+        return true;
+    }
+  }
 
   /// Describes the directory whose contents are about to be processed.
-  void directory(String path) {}
+  void directory(String path) {
+    if (this == Show.legacy || this == Show.overwrite) {
+      print('Formatting directory $directory:');
+    }
+  }
 
   /// Describes the symlink at [path] that wasn't followed.
-  void skippedLink(String path) {}
+  void skippedLink(String path) {
+    if (this == Show.legacy || this == Show.overwrite) {
+      print('Skipping link $path');
+    }
+  }
 
   /// Describes the hidden [path] that wasn't processed.
-  void hiddenPath(String path) {}
+  void hiddenPath(String path) {
+    if (this == Show.legacy || this == Show.overwrite) {
+      print('Skipping hidden path $path');
+    }
+  }
 
   void _showFileChange(String path, {required bool overwritten}) {
     if (overwritten) {
@@ -56,77 +97,3 @@
     }
   }
 }
-
-mixin _ShowFileMixin on Show {
-  @override
-  bool file(String path, {required bool changed, required bool overwritten}) {
-    if (changed) {
-      _showFileChange(path, overwritten: overwritten);
-    } else {
-      print('Unchanged $path');
-    }
-
-    return true;
-  }
-}
-
-mixin _LegacyMixin on Show {
-  @override
-  String displayPath(String directory, String file) =>
-      p.relative(file, from: directory);
-
-  @override
-  void directory(String directory) {
-    print('Formatting directory $directory:');
-  }
-
-  @override
-  void skippedLink(String path) {
-    print('Skipping link $path');
-  }
-
-  @override
-  void hiddenPath(String path) {
-    print('Skipping hidden path $path');
-  }
-}
-
-class _NoneShow extends Show {
-  const _NoneShow() : super._();
-}
-
-class _AllShow extends Show with _ShowFileMixin {
-  const _AllShow() : super._();
-}
-
-class _ChangedShow extends Show {
-  const _ChangedShow() : super._();
-
-  @override
-  bool file(String path, {required bool changed, required bool overwritten}) {
-    if (changed) _showFileChange(path, overwritten: overwritten);
-    return changed;
-  }
-}
-
-class _LegacyShow extends Show with _LegacyMixin {
-  const _LegacyShow() : super._();
-}
-
-class _OverwriteShow extends Show with _ShowFileMixin, _LegacyMixin {
-  const _OverwriteShow() : super._();
-}
-
-class _DryRunShow extends Show {
-  const _DryRunShow() : super._();
-
-  @override
-  String displayPath(String directory, String file) =>
-      p.relative(file, from: directory);
-
-  @override
-  bool file(String path, {required bool changed, required bool overwritten}) {
-    if (changed) print(path);
-    return true;
-  }
-}
diff --git a/lib/src/debug.dart b/lib/src/debug.dart
index b9b2938..567e442 100644
--- a/lib/src/debug.dart
+++ b/lib/src/debug.dart
@@ -7,7 +7,6 @@
 
 import 'chunk.dart';
 import 'line_splitting/rule_set.dart';
-import 'rule/rule.dart';
 
 /// Set this to `true` to turn on diagnostic output while building chunks.
 bool traceChunkBuilder = false;
@@ -189,12 +188,12 @@
 
 /// Shows all of the constraints between the rules used by [chunks].
 void dumpConstraints(List<Chunk> chunks) {
-  var rules = chunks.map((chunk) => chunk.rule).whereType<Rule>().toSet();
+  var rules = chunks.map((chunk) => chunk.rule).toSet();
 
   for (var rule in rules) {
-    var constrainedValues = [];
+    var constrainedValues = <String>[];
     for (var value = 0; value < rule.numValues; value++) {
-      var constraints = [];
+      var constraints = <String>[];
       for (var other in rules) {
         if (rule == other) continue;
 
diff --git a/lib/src/exceptions.dart b/lib/src/exceptions.dart
index 86488a4..93809e4 100644
--- a/lib/src/exceptions.dart
+++ b/lib/src/exceptions.dart
@@ -2,8 +2,6 @@
 // for details. All rights reserved. Use of this source code is governed by a
 // BSD-style license that can be found in the LICENSE file.
 
-library dart_style.src.formatter_exception;
-
 import 'package:analyzer/error/error.dart';
 import 'package:source_span/source_span.dart';
 
diff --git a/lib/src/fast_hash.dart b/lib/src/fast_hash.dart
index beea29d..30ebf68 100644
--- a/lib/src/fast_hash.dart
+++ b/lib/src/fast_hash.dart
@@ -2,8 +2,6 @@
 // for details. All rights reserved. Use of this source code is governed by a
 // BSD-style license that can be found in the LICENSE file.
 
-library dart_style.src.fast_hash;
-
 /// A mixin for classes with identity equality that need to be frequently
 /// hashed.
 abstract class FastHash {
diff --git a/lib/src/line_splitting/line_splitter.dart b/lib/src/line_splitting/line_splitter.dart
index 62bec03..9c7c5f3 100644
--- a/lib/src/line_splitting/line_splitter.dart
+++ b/lib/src/line_splitting/line_splitter.dart
@@ -119,11 +119,8 @@
   /// page width.
   LineSplitter(this.writer, this.chunks, this.blockIndentation)
       : // Collect the set of rules that we need to select values for.
-        rules = chunks
-            .map((chunk) => chunk.rule)
-            .whereType<Rule>()
-            .toSet()
-            .toList(growable: false) {
+        rules =
+            chunks.map((chunk) => chunk.rule).toSet().toList(growable: false) {
     _queue.bindSplitter(this);
 
     // Store the rule's index in the rule so we can get from a chunk to a rule
@@ -146,9 +143,8 @@
   /// indentation of each line.
   SplitSet apply() {
     // Start with a completely unbound, unsplit solution.
-    _queue.add(SolveState(this, RuleSet(rules.length)));
-
-    SolveState? bestSolution;
+    var bestSolution = SolveState(this, RuleSet(rules.length));
+    _queue.add(bestSolution);
 
     var attempts = 0;
     while (_queue.isNotEmpty) {
@@ -177,11 +173,11 @@
 
     if (debug.traceSplitter) {
       debug.log('$bestSolution (winner)');
-      debug.dumpLines(chunks, bestSolution!.splits);
+      debug.dumpLines(chunks, bestSolution.splits);
       debug.log();
     }
 
-    return bestSolution!.splits;
+    return bestSolution.splits;
   }
 
   void enqueue(SolveState state) {
diff --git a/lib/src/line_splitting/rule_set.dart b/lib/src/line_splitting/rule_set.dart
index 5a85cc0..0f6ef8e 100644
--- a/lib/src/line_splitting/rule_set.dart
+++ b/lib/src/line_splitting/rule_set.dart
@@ -157,13 +157,9 @@
 
   @override
   String toString() {
-    var result = [];
-    for (var i = 0; i < _columns.length; i++) {
-      if (_columns[i] != -1) {
-        result.add('$i:${_columns[i]}');
-      }
-    }
-
-    return result.join(' ');
+    return [
+      for (var i = 0; i < _columns.length; i++)
+        if (_columns[i] != -1) '$i:${_columns[i]}'
+    ].join(' ');
   }
 }
diff --git a/lib/src/line_splitting/solve_state.dart b/lib/src/line_splitting/solve_state.dart
index 888ca34..8fb320b 100644
--- a/lib/src/line_splitting/solve_state.dart
+++ b/lib/src/line_splitting/solve_state.dart
@@ -118,14 +118,11 @@
 
   /// Returns `true` if this state is a better solution to use as the final
   /// result than [other].
-  bool isBetterThan(SolveState? other) {
+  bool isBetterThan(SolveState other) {
     // If this state contains an unbound rule that we know can't be left
     // unsplit, we can't pick this as a solution.
     if (!_isComplete) return false;
 
-    // Anything is better than nothing.
-    if (other == null) return true;
-
     // Prefer the solution that fits the most in the page.
     if (overflowChars != other.overflowChars) {
       return overflowChars < other.overflowChars;
diff --git a/lib/src/nesting_builder.dart b/lib/src/nesting_builder.dart
index e8a4c41..9204a0d 100644
--- a/lib/src/nesting_builder.dart
+++ b/lib/src/nesting_builder.dart
@@ -2,8 +2,6 @@
 // for details. All rights reserved. Use of this source code is governed by a
 // BSD-style license that can be found in the LICENSE file.
 
-library dart_style.src.nesting_builder;
-
 import 'constants.dart';
 import 'nesting_level.dart';
 
diff --git a/lib/src/nesting_level.dart b/lib/src/nesting_level.dart
index 79d3237..ba029a3 100644
--- a/lib/src/nesting_level.dart
+++ b/lib/src/nesting_level.dart
@@ -2,8 +2,6 @@
 // for details. All rights reserved. Use of this source code is governed by a
 // BSD-style license that can be found in the LICENSE file.
 
-library dart_style.src.nesting_level;
-
 import 'fast_hash.dart';
 
 /// A single level of expression nesting.
diff --git a/lib/src/rule/combinator.dart b/lib/src/rule/combinator.dart
index aa83527..c452947 100644
--- a/lib/src/rule/combinator.dart
+++ b/lib/src/rule/combinator.dart
@@ -2,8 +2,6 @@
 // for details. All rights reserved. Use of this source code is governed by a
 // BSD-style license that can be found in the LICENSE file.
 
-library dart_style.src.rule.combinator;
-
 import '../chunk.dart';
 import 'rule.dart';
 
diff --git a/lib/src/rule/type_argument.dart b/lib/src/rule/type_argument.dart
index e4c30ef..4cc9eb7 100644
--- a/lib/src/rule/type_argument.dart
+++ b/lib/src/rule/type_argument.dart
@@ -2,8 +2,6 @@
 // for details. All rights reserved. Use of this source code is governed by a
 // BSD-style license that can be found in the LICENSE file.
 
-library dart_style.src.rule.type_argument;
-
 import '../chunk.dart';
 import '../constants.dart';
 import 'rule.dart';
diff --git a/lib/src/source_code.dart b/lib/src/source_code.dart
index 87c5aa5..02f0acf 100644
--- a/lib/src/source_code.dart
+++ b/lib/src/source_code.dart
@@ -2,8 +2,6 @@
 // for details. All rights reserved. Use of this source code is governed by a
 // BSD-style license that can be found in the LICENSE file.
 
-library dart_style.src.source_code;
-
 /// Describes a chunk of source code that is to be formatted or has been
 /// formatted.
 class SourceCode {
diff --git a/pubspec.lock b/pubspec.lock
index b716bf4..1351485 100644
--- a/pubspec.lock
+++ b/pubspec.lock
@@ -7,21 +7,21 @@
       name: _fe_analyzer_shared
       url: "https://pub.dartlang.org"
     source: hosted
-    version: "39.0.0"
+    version: "40.0.0"
   analyzer:
     dependency: "direct main"
     description:
       name: analyzer
       url: "https://pub.dartlang.org"
     source: hosted
-    version: "4.0.0"
+    version: "4.1.0"
   args:
     dependency: "direct main"
     description:
       name: args
       url: "https://pub.dartlang.org"
     source: hosted
-    version: "2.3.0"
+    version: "2.3.1"
   async:
     dependency: transitive
     description:
@@ -36,13 +36,6 @@
       url: "https://pub.dartlang.org"
     source: hosted
     version: "2.1.0"
-  charcode:
-    dependency: transitive
-    description:
-      name: charcode
-      url: "https://pub.dartlang.org"
-    source: hosted
-    version: "1.3.1"
   cli_util:
     dependency: transitive
     description:
@@ -63,21 +56,21 @@
       name: convert
       url: "https://pub.dartlang.org"
     source: hosted
-    version: "3.0.1"
+    version: "3.0.2"
   coverage:
     dependency: transitive
     description:
       name: coverage
       url: "https://pub.dartlang.org"
     source: hosted
-    version: "1.2.0"
+    version: "1.3.2"
   crypto:
     dependency: transitive
     description:
       name: crypto
       url: "https://pub.dartlang.org"
     source: hosted
-    version: "3.0.1"
+    version: "3.0.2"
   file:
     dependency: transitive
     description:
@@ -91,7 +84,7 @@
       name: frontend_server_client
       url: "https://pub.dartlang.org"
     source: hosted
-    version: "2.1.2"
+    version: "2.1.3"
   glob:
     dependency: transitive
     description:
@@ -119,7 +112,7 @@
       name: http_parser
       url: "https://pub.dartlang.org"
     source: hosted
-    version: "4.0.0"
+    version: "4.0.1"
   io:
     dependency: transitive
     description:
@@ -161,14 +154,14 @@
       name: meta
       url: "https://pub.dartlang.org"
     source: hosted
-    version: "1.7.0"
+    version: "1.8.0"
   mime:
     dependency: transitive
     description:
       name: mime
       url: "https://pub.dartlang.org"
     source: hosted
-    version: "1.0.1"
+    version: "1.0.2"
   node_preamble:
     dependency: transitive
     description:
@@ -189,7 +182,7 @@
       name: path
       url: "https://pub.dartlang.org"
     source: hosted
-    version: "1.8.1"
+    version: "1.8.2"
   pool:
     dependency: transitive
     description:
@@ -273,7 +266,7 @@
       name: string_scanner
       url: "https://pub.dartlang.org"
     source: hosted
-    version: "1.1.0"
+    version: "1.1.1"
   term_glyph:
     dependency: transitive
     description:
@@ -287,7 +280,7 @@
       name: test
       url: "https://pub.dartlang.org"
     source: hosted
-    version: "1.21.0"
+    version: "1.21.1"
   test_api:
     dependency: transitive
     description:
@@ -322,14 +315,14 @@
       name: typed_data
       url: "https://pub.dartlang.org"
     source: hosted
-    version: "1.3.0"
+    version: "1.3.1"
   vm_service:
     dependency: transitive
     description:
       name: vm_service
       url: "https://pub.dartlang.org"
     source: hosted
-    version: "8.2.2"
+    version: "8.3.0"
   watcher:
     dependency: transitive
     description:
@@ -343,20 +336,20 @@
       name: web_socket_channel
       url: "https://pub.dartlang.org"
     source: hosted
-    version: "2.1.0"
+    version: "2.2.0"
   webkit_inspection_protocol:
     dependency: transitive
     description:
       name: webkit_inspection_protocol
       url: "https://pub.dartlang.org"
     source: hosted
-    version: "1.0.0"
+    version: "1.1.0"
   yaml:
     dependency: "direct dev"
     description:
       name: yaml
       url: "https://pub.dartlang.org"
     source: hosted
-    version: "3.1.0"
+    version: "3.1.1"
 sdks:
-  dart: ">=2.16.0 <3.0.0"
+  dart: ">=2.17.0 <3.0.0"
diff --git a/pubspec.yaml b/pubspec.yaml
index f795619..766cde8 100644
--- a/pubspec.yaml
+++ b/pubspec.yaml
@@ -6,7 +6,7 @@
   Provides an API and a CLI tool.
 repository: https://github.com/dart-lang/dart_style
 environment:
-  sdk: ">=2.12.0 <3.0.0"
+  sdk: ">=2.17.0 <3.0.0"
 
 dependencies:
   analyzer: '>=3.3.1 <5.0.0'
diff --git a/test/command_line_test.dart b/test/command_line_test.dart
index 0596e51..56eba6a 100644
--- a/test/command_line_test.dart
+++ b/test/command_line_test.dart
@@ -2,8 +2,6 @@
 // for details. All rights reserved. Use of this source code is governed by a
 // BSD-style license that can be found in the LICENSE file.
 
-library dart_style.test.command_line;
-
 import 'dart:convert';
 
 import 'package:path/path.dart' as p;
diff --git a/test/io_test.dart b/test/io_test.dart
index c4b1b8a..49851a5 100644
--- a/test/io_test.dart
+++ b/test/io_test.dart
@@ -2,8 +2,6 @@
 // for details. All rights reserved. Use of this source code is governed by a
 // BSD-style license that can be found in the LICENSE file.
 
-library dart_style.test.io;
-
 import 'dart:io';
 
 import 'package:dart_style/src/cli/formatter_options.dart';
diff --git a/test/source_code_test.dart b/test/source_code_test.dart
index 3b4b1e3..9f41c54 100644
--- a/test/source_code_test.dart
+++ b/test/source_code_test.dart
@@ -2,8 +2,6 @@
 // for details. All rights reserved. Use of this source code is governed by a
 // BSD-style license that can be found in the LICENSE file.
 
-library dart_style.test.source_code_test;
-
 import 'package:dart_style/dart_style.dart';
 import 'package:test/test.dart';