update lints (dart-lang/test_descriptor#45)

diff --git a/pkgs/test_descriptor/.github/workflows/test-package.yml b/pkgs/test_descriptor/.github/workflows/test-package.yml
index 38089b6..0de63d5 100644
--- a/pkgs/test_descriptor/.github/workflows/test-package.yml
+++ b/pkgs/test_descriptor/.github/workflows/test-package.yml
@@ -49,7 +49,7 @@
       matrix:
         # Add macos-latest and/or windows-latest if relevant for this package.
         os: [ubuntu-latest]
-        sdk: [2.12.0, dev]
+        sdk: [2.17.0, dev]
     steps:
       - uses: actions/checkout@93ea575cb5d8a053eaa0ac8fa3b40d7e05a33cc8
       - uses: dart-lang/setup-dart@6a218f2413a3e78e9087f638a238f6b40893203d
diff --git a/pkgs/test_descriptor/CHANGELOG.md b/pkgs/test_descriptor/CHANGELOG.md
index abe849a..aad67bf 100644
--- a/pkgs/test_descriptor/CHANGELOG.md
+++ b/pkgs/test_descriptor/CHANGELOG.md
@@ -1,3 +1,5 @@
+## 2.0.2-dev
+
 ## 2.0.1
 
 * Populate the pubspec `repository` field.
diff --git a/pkgs/test_descriptor/README.md b/pkgs/test_descriptor/README.md
index 94422ea..d50e776 100644
--- a/pkgs/test_descriptor/README.md
+++ b/pkgs/test_descriptor/README.md
@@ -21,25 +21,22 @@
 ```dart
 import 'dart:io';
 
+import 'package:test/test.dart';
 import 'package:test_descriptor/test_descriptor.dart' as d;
 
 void main() {
-  test("Directory.rename", () async {
-    await d.dir("parent", [
-      d.file("sibling", "sibling-contents"),
-      d.dir("old-name", [
-        d.file("child", "child-contents")
-      ])
+  test('Directory.rename', () async {
+    await d.dir('parent', [
+      d.file('sibling', 'sibling-contents'),
+      d.dir('old-name', [d.file('child', 'child-contents')])
     ]).create();
 
-    await new Directory("${d.sandbox}/parent/old-name")
-        .rename("${d.sandbox}/parent/new-name");
+    await Directory('${d.sandbox}/parent/old-name')
+        .rename('${d.sandbox}/parent/new-name');
 
-    await d.dir("parent", [
-      d.file("sibling", "sibling-contents"),
-      d.dir("new-name", [
-        d.file("child", "child-contents")
-      ])
+    await d.dir('parent', [
+      d.file('sibling', 'sibling-contents'),
+      d.dir('new-name', [d.file('child', 'child-contents')])
     ]).validate();
   });
 }
diff --git a/pkgs/test_descriptor/analysis_options.yaml b/pkgs/test_descriptor/analysis_options.yaml
index 200a3d4..7806fc0 100644
--- a/pkgs/test_descriptor/analysis_options.yaml
+++ b/pkgs/test_descriptor/analysis_options.yaml
@@ -1,62 +1,57 @@
+# https://dart.dev/guides/language/analysis-options
 include: package:lints/recommended.yaml
 
 analyzer:
-  strong-mode:
-    implicit-casts: false
+  language:
+    strict-casts: true
+    strict-inference: true
+    strict-raw-types: true
 
 linter:
   rules:
-    - annotate_overrides
+    - always_declare_return_types
+    - avoid_bool_literals_in_conditional_expressions
+    - avoid_catching_errors
+    - avoid_classes_with_only_static_members
     - avoid_dynamic_calls
-    - avoid_function_literals_in_foreach_calls
-    - avoid_init_to_null
-    - avoid_null_checks_in_equality_operators
-    - avoid_relative_lib_imports
+    - avoid_private_typedef_functions
+    - avoid_redundant_argument_values
     - avoid_returning_null
+    - avoid_returning_null_for_future
+    - avoid_returning_this
     - avoid_unused_constructor_parameters
-    - await_only_futures
-    - camel_case_types
+    - avoid_void_async
     - cancel_subscriptions
     - comment_references
-    - constant_identifier_names
-    - control_flow_in_finally
     - directives_ordering
-    - empty_catches
-    - empty_constructor_bodies
-    - empty_statements
-    - hash_and_equals
-    - implementation_imports
-    - iterable_contains_unrelated_type
-    - library_names
-    - library_prefixes
-    - list_remove_unrelated_type
+    - join_return_with_assignment
+    - lines_longer_than_80_chars
+    - literal_only_boolean_expressions
+    - missing_whitespace_between_adjacent_strings
     - no_adjacent_strings_in_list
-    - non_constant_identifier_names
+    - no_runtimeType_toString
     - omit_local_variable_types
     - only_throw_errors
-    - overridden_fields
     - package_api_docs
-    - package_names
-    - package_prefixed_library_names
-    - prefer_adjacent_string_concatenation
-    - prefer_collection_literals
-    - prefer_conditional_assignment
+    - prefer_asserts_in_initializer_lists
     - prefer_const_constructors
-    - prefer_final_fields
-    - prefer_generic_function_type_aliases
-    - prefer_initializing_formals
-    - prefer_interpolation_to_compose_strings
+    - prefer_const_declarations
+    - prefer_expression_function_bodies
+    - prefer_final_locals
+    - prefer_relative_imports
     - prefer_single_quotes
-    - prefer_typing_uninitialized_variables
-    - slash_for_doc_comments
+    - sort_pub_dependencies
     - test_types_in_equals
     - throw_in_finally
-    - type_init_formals
-    - unnecessary_brace_in_string_interps
-    - unnecessary_const
-    - unnecessary_getters_setters
+    - type_annotate_public_apis
+    - unawaited_futures
+    - unnecessary_await_in_return
     - unnecessary_lambdas
-    - unnecessary_new
-    - unnecessary_null_aware_assignments
+    - unnecessary_parenthesis
+    - unnecessary_raw_strings
     - unnecessary_statements
-    - unnecessary_this
+    - use_if_null_to_convert_nulls_to_bools
+    - use_raw_strings
+    - use_string_buffers
+    - use_super_parameters
+    - require_trailing_commas
diff --git a/pkgs/test_descriptor/example/example.dart b/pkgs/test_descriptor/example/example.dart
new file mode 100644
index 0000000..ef5917e
--- /dev/null
+++ b/pkgs/test_descriptor/example/example.dart
@@ -0,0 +1,25 @@
+// Copyright (c) 2022, the Dart project authors.  Please see the AUTHORS file
+// 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.
+
+import 'dart:io';
+
+import 'package:test/test.dart';
+import 'package:test_descriptor/test_descriptor.dart' as d;
+
+void main() {
+  test('Directory.rename', () async {
+    await d.dir('parent', [
+      d.file('sibling', 'sibling-contents'),
+      d.dir('old-name', [d.file('child', 'child-contents')])
+    ]).create();
+
+    await Directory('${d.sandbox}/parent/old-name')
+        .rename('${d.sandbox}/parent/new-name');
+
+    await d.dir('parent', [
+      d.file('sibling', 'sibling-contents'),
+      d.dir('new-name', [d.file('child', 'child-contents')])
+    ]).validate();
+  });
+}
diff --git a/pkgs/test_descriptor/lib/src/directory_descriptor.dart b/pkgs/test_descriptor/lib/src/directory_descriptor.dart
index e4f7b80..c4f2694 100644
--- a/pkgs/test_descriptor/lib/src/directory_descriptor.dart
+++ b/pkgs/test_descriptor/lib/src/directory_descriptor.dart
@@ -29,14 +29,13 @@
   /// [sandbox].
   Directory get io => Directory(p.join(sandbox, name));
 
-  DirectoryDescriptor(String name, Iterable<Descriptor> contents)
-      : contents = contents.toList(),
-        super(name);
+  DirectoryDescriptor(super.name, Iterable<Descriptor> contents)
+      : contents = contents.toList();
 
   /// Creates a directory descriptor named [name] that describes the physical
   /// directory at [path].
-  factory DirectoryDescriptor.fromFilesystem(String name, String path) {
-    return DirectoryDescriptor(
+  factory DirectoryDescriptor.fromFilesystem(String name, String path) =>
+      DirectoryDescriptor(
         name,
         Directory(path).listSync().map((entity) {
           // Ignore hidden files.
@@ -44,32 +43,37 @@
 
           if (entity is Directory) {
             return DirectoryDescriptor.fromFilesystem(
-                p.basename(entity.path), entity.path);
+              p.basename(entity.path),
+              entity.path,
+            );
           } else if (entity is File) {
             return FileDescriptor(
-                p.basename(entity.path), entity.readAsBytesSync());
+              p.basename(entity.path),
+              entity.readAsBytesSync(),
+            );
           }
           // Ignore broken symlinks.
           return null;
-        }).whereType<Descriptor>());
-  }
+        }).whereType<Descriptor>(),
+      );
 
   @override
   Future<void> create([String? parent]) async {
-    var fullPath = p.join(parent ?? sandbox, name);
+    final fullPath = p.join(parent ?? sandbox, name);
     await Directory(fullPath).create(recursive: true);
     await Future.wait(contents.map((entry) => entry.create(fullPath)));
   }
 
   @override
   Future<void> validate([String? parent]) async {
-    var fullPath = p.join(parent ?? sandbox, name);
+    final fullPath = p.join(parent ?? sandbox, name);
     if (!(await Directory(fullPath).exists())) {
       fail('Directory not found: "${prettyPath(fullPath)}".');
     }
 
     await waitAndReportErrors(
-        contents.map((entry) => entry.validate(fullPath)));
+      contents.map((entry) => entry.validate(fullPath)),
+    );
   }
 
   /// Treats this descriptor as a virtual filesystem and loads the binary
@@ -80,35 +84,47 @@
   Stream<List<int>> _load(String path, [String? parents]) {
     if (!p.url.isWithin('.', path)) {
       throw ArgumentError.value(
-          path, 'path', 'must be relative and beneath the base URL.');
+        path,
+        'path',
+        'must be relative and beneath the base URL.',
+      );
     }
 
-    return StreamCompleter.fromFuture(Future.sync(() {
-      var split = p.url.split(p.url.normalize(path));
-      var file = split.length == 1;
-      var matchingEntries = contents.where((entry) {
-        return entry.name == split.first &&
-            (file ? entry is FileDescriptor : entry is DirectoryDescriptor);
-      }).toList();
+    return StreamCompleter.fromFuture(
+      Future.sync(() {
+        final split = p.url.split(p.url.normalize(path));
+        final file = split.length == 1;
+        final matchingEntries = contents
+            .where(
+              (entry) =>
+                  entry.name == split.first &&
+                  (file
+                      ? entry is FileDescriptor
+                      : entry is DirectoryDescriptor),
+            )
+            .toList();
 
-      var type = file ? 'file' : 'directory';
-      var parentsAndSelf = parents == null ? name : p.url.join(parents, name);
-      if (matchingEntries.isEmpty) {
-        fail('Couldn\'t find a $type descriptor named "${split.first}" within '
-            '"$parentsAndSelf".');
-      } else if (matchingEntries.length > 1) {
-        fail('Found multiple $type descriptors named "${split.first}" within '
-            '"$parentsAndSelf".');
-      } else {
-        var remainingPath = split.sublist(1);
-        if (remainingPath.isEmpty) {
-          return (matchingEntries.first as FileDescriptor).readAsBytes();
+        final type = file ? 'file' : 'directory';
+        final parentsAndSelf =
+            parents == null ? name : p.url.join(parents, name);
+        if (matchingEntries.isEmpty) {
+          fail(
+              'Couldn\'t find a $type descriptor named "${split.first}" within '
+              '"$parentsAndSelf".');
+        } else if (matchingEntries.length > 1) {
+          fail('Found multiple $type descriptors named "${split.first}" within '
+              '"$parentsAndSelf".');
         } else {
-          return (matchingEntries.first as DirectoryDescriptor)
-              ._load(p.url.joinAll(remainingPath), parentsAndSelf);
+          final remainingPath = split.sublist(1);
+          if (remainingPath.isEmpty) {
+            return (matchingEntries.first as FileDescriptor).readAsBytes();
+          } else {
+            return (matchingEntries.first as DirectoryDescriptor)
+                ._load(p.url.joinAll(remainingPath), parentsAndSelf);
+          }
         }
-      }
-    }));
+      }),
+    );
   }
 
   @override
diff --git a/pkgs/test_descriptor/lib/src/file_descriptor.dart b/pkgs/test_descriptor/lib/src/file_descriptor.dart
index b9eae6e..2daedd5 100644
--- a/pkgs/test_descriptor/lib/src/file_descriptor.dart
+++ b/pkgs/test_descriptor/lib/src/file_descriptor.dart
@@ -33,8 +33,8 @@
   /// If [contents] isn't passed, [create] creates an empty file and [validate]
   /// verifies that the file is empty.
   ///
-  /// To match a [Matcher] against a file's binary contents, use [new
-  /// FileDescriptor.binaryMatcher] instead.
+  /// To match a [Matcher] against a file's binary contents, use
+  /// [FileDescriptor.binaryMatcher] instead.
   factory FileDescriptor(String name, Object? contents) {
     if (contents is String) return _StringFileDescriptor(name, contents);
     if (contents is List) {
@@ -57,15 +57,15 @@
       _MatcherFileDescriptor(name, matcher, isBinary: true);
 
   /// A protected constructor that's only intended for subclasses.
-  FileDescriptor.protected(String name) : super(name);
+  FileDescriptor.protected(super.name);
 
   @override
   Future<void> create([String? parent]) async {
     // Create the stream before we call [File.openWrite] because it may fail
     // fast (e.g. if this is a matcher file).
-    var file = File(p.join(parent ?? sandbox, name)).openWrite();
+    final file = File(p.join(parent ?? sandbox, name)).openWrite();
     try {
-      await readAsBytes().listen(file.add).asFuture();
+      await readAsBytes().forEach(file.add);
     } finally {
       await file.close();
     }
@@ -73,8 +73,8 @@
 
   @override
   Future<void> validate([String? parent]) async {
-    var fullPath = p.join(parent ?? sandbox, name);
-    var pretty = prettyPath(fullPath);
+    final fullPath = p.join(parent ?? sandbox, name);
+    final pretty = prettyPath(fullPath);
     if (!(await File(fullPath).exists())) {
       fail('File not found: "$pretty".');
     }
@@ -107,14 +107,14 @@
   /// The contents of this descriptor's file.
   final List<int> _contents;
 
-  _BinaryFileDescriptor(String name, this._contents) : super.protected(name);
+  _BinaryFileDescriptor(super.name, this._contents) : super.protected();
 
   @override
   Stream<List<int>> readAsBytes() => Stream.fromIterable([_contents]);
 
   @override
   Future<void> _validate(String prettPath, List<int> actualContents) async {
-    if (const IterableEquality().equals(_contents, actualContents)) return;
+    if (const IterableEquality<int>().equals(_contents, actualContents)) return;
     // TODO(nweiz): show a hex dump here if the data is small enough.
     fail('File "$prettPath" didn\'t contain the expected binary data.');
   }
@@ -124,7 +124,7 @@
   /// The contents of this descriptor's file.
   final String _contents;
 
-  _StringFileDescriptor(String name, this._contents) : super.protected(name);
+  _StringFileDescriptor(super.name, this._contents) : super.protected();
 
   @override
   Future<String> read() async => _contents;
@@ -135,20 +135,23 @@
 
   @override
   void _validate(String prettyPath, List<int> actualContents) {
-    var actualContentsText = utf8.decode(actualContents);
+    final actualContentsText = utf8.decode(actualContents);
     if (_contents == actualContentsText) return;
     fail(_textMismatchMessage(prettyPath, _contents, actualContentsText));
   }
 
   String _textMismatchMessage(
-      String prettyPath, String expected, String actual) {
+    String prettyPath,
+    String expected,
+    String actual,
+  ) {
     final expectedLines = expected.split('\n');
     final actualLines = actual.split('\n');
 
-    var results = [];
+    final results = <String>[];
 
     // Compare them line by line to see which ones match.
-    var length = math.max(expectedLines.length, actualLines.length);
+    final length = math.max(expectedLines.length, actualLines.length);
     for (var i = 0; i < length; i++) {
       if (i >= actualLines.length) {
         // Missing output.
@@ -157,8 +160,8 @@
         // Unexpected extra output.
         results.add('X ${actualLines[i]}');
       } else {
-        var expectedLine = expectedLines[i];
-        var actualLine = actualLines[i];
+        final expectedLine = expectedLines[i];
+        final actualLine = actualLines[i];
 
         if (expectedLine != actualLine) {
           // Mismatched lines.
@@ -185,9 +188,9 @@
   /// contents.
   final bool _isBinary;
 
-  _MatcherFileDescriptor(String name, this._matcher, {bool isBinary = false})
+  _MatcherFileDescriptor(super.name, this._matcher, {bool isBinary = false})
       : _isBinary = isBinary,
-        super.protected(name);
+        super.protected();
 
   @override
   Stream<List<int>> readAsBytes() =>
@@ -197,7 +200,9 @@
   Future<void> _validate(String prettyPath, List<int> actualContents) async {
     try {
       expect(
-          _isBinary ? actualContents : utf8.decode(actualContents), _matcher);
+        _isBinary ? actualContents : utf8.decode(actualContents),
+        _matcher,
+      );
     } on TestFailure catch (error) {
       fail('Invalid contents for file "$prettyPath":\n${error.message}');
     }
diff --git a/pkgs/test_descriptor/lib/src/nothing_descriptor.dart b/pkgs/test_descriptor/lib/src/nothing_descriptor.dart
index 22e7bc8..da9e2a2 100644
--- a/pkgs/test_descriptor/lib/src/nothing_descriptor.dart
+++ b/pkgs/test_descriptor/lib/src/nothing_descriptor.dart
@@ -15,15 +15,15 @@
 ///
 /// Calling [create] does nothing.
 class NothingDescriptor extends Descriptor {
-  NothingDescriptor(String name) : super(name);
+  NothingDescriptor(super.name);
 
   @override
   Future<void> create([String? parent]) async {}
 
   @override
   Future<void> validate([String? parent]) async {
-    var fullPath = p.join(parent ?? sandbox, name);
-    var pretty = prettyPath(fullPath);
+    final fullPath = p.join(parent ?? sandbox, name);
+    final pretty = prettyPath(fullPath);
     if (File(fullPath).existsSync()) {
       fail('Expected nothing to exist at "$pretty", but found a file.');
     } else if (Directory(fullPath).existsSync()) {
diff --git a/pkgs/test_descriptor/lib/src/pattern_descriptor.dart b/pkgs/test_descriptor/lib/src/pattern_descriptor.dart
index ed1b556..d55afc0 100644
--- a/pkgs/test_descriptor/lib/src/pattern_descriptor.dart
+++ b/pkgs/test_descriptor/lib/src/pattern_descriptor.dart
@@ -13,11 +13,6 @@
 import 'sandbox.dart';
 import 'utils.dart';
 
-/// A function that takes a name for a [Descriptor] and returns a [Descriptor].
-/// This is used for [PatternDescriptor]s, where the name isn't known
-/// ahead-of-time.
-typedef _EntryCreator = Descriptor Function(String name);
-
 /// A descriptor that matches filesystem entity names by [Pattern] rather than
 /// by exact [String].
 ///
@@ -29,7 +24,7 @@
 
   /// The function used to generate the [Descriptor] for filesystem entities
   /// matching [pattern].
-  final _EntryCreator _fn;
+  final Descriptor Function(String) _fn;
 
   PatternDescriptor(this.pattern, Descriptor Function(String basename) child)
       : _fn = child,
@@ -42,39 +37,44 @@
   /// `this` is considered valid.
   @override
   Future<void> validate([String? parent]) async {
-    var inSandbox = parent == null;
+    final inSandbox = parent == null;
     parent ??= sandbox;
-    var matchingEntries = await Directory(parent)
+    final matchingEntries = await Directory(parent)
         .list()
-        .map((entry) =>
-            entry is File ? entry.resolveSymbolicLinksSync() : entry.path)
+        .map(
+          (entry) =>
+              entry is File ? entry.resolveSymbolicLinksSync() : entry.path,
+        )
         .where((entry) => matchesAll(pattern, p.basename(entry)))
         .toList();
     matchingEntries.sort();
 
-    var location = inSandbox ? 'sandbox' : '"${prettyPath(parent)}"';
+    final location = inSandbox ? 'sandbox' : '"${prettyPath(parent)}"';
     if (matchingEntries.isEmpty) {
       fail('No entries found in $location matching $_patternDescription.');
     }
 
-    var results = await Future.wait(matchingEntries
-        .map((entry) {
-          var basename = p.basename(entry);
-          return runZonedGuarded(() {
-            return Result.capture(Future.sync(() async {
-              await _fn(basename).validate(parent);
-              return basename;
-            }));
-          }, (_, __) {
-            // Validate may produce multiple errors, but we ignore all but the first
-            // to avoid cluttering the user with many different errors from many
-            // different un-matched entries.
-          });
-        })
-        .whereType<Future<Result<String>>>()
-        .toList());
+    final results = await Future.wait(
+      matchingEntries
+          .map((entry) {
+            final basename = p.basename(entry);
+            return runZonedGuarded(
+                () => Result.capture(
+                      Future.sync(() async {
+                        await _fn(basename).validate(parent);
+                        return basename;
+                      }),
+                    ), (_, __) {
+              // Validate may produce multiple errors, but we ignore all but the
+              // first to avoid cluttering the user with many different errors
+              // from many different un-matched entries.
+            });
+          })
+          .whereType<Future<Result<String>>>()
+          .toList(),
+    );
 
-    var successes = results.where((result) => result.isValue).toList();
+    final successes = results.where((result) => result.isValue).toList();
     if (successes.isEmpty) {
       await waitAndReportErrors(results.map((result) => result.asFuture));
     } else if (successes.length > 1) {
@@ -91,8 +91,8 @@
     if (pattern is String) return '"$pattern"';
     if (pattern is! RegExp) return '$pattern';
 
-    var regExp = pattern as RegExp;
-    var flags = StringBuffer();
+    final regExp = pattern as RegExp;
+    final flags = StringBuffer();
     if (!regExp.isCaseSensitive) flags.write('i');
     if (regExp.isMultiLine) flags.write('m');
     return '/${regExp.pattern}/$flags';
diff --git a/pkgs/test_descriptor/lib/src/sandbox.dart b/pkgs/test_descriptor/lib/src/sandbox.dart
index 1f5847f..128b6a9 100644
--- a/pkgs/test_descriptor/lib/src/sandbox.dart
+++ b/pkgs/test_descriptor/lib/src/sandbox.dart
@@ -16,12 +16,12 @@
   if (_sandbox != null) return _sandbox!;
   // Resolve symlinks so we don't end up with inconsistent paths on Mac OS where
   // /tmp is symlinked.
-  var sandbox = _sandbox = Directory.systemTemp
+  final sandbox = _sandbox = Directory.systemTemp
       .createTempSync('dart_test_')
       .resolveSymbolicLinksSync();
 
   addTearDown(() async {
-    var sandbox = _sandbox!;
+    final sandbox = _sandbox!;
     _sandbox = null;
     await Directory(sandbox).delete(recursive: true);
   });
diff --git a/pkgs/test_descriptor/lib/src/utils.dart b/pkgs/test_descriptor/lib/src/utils.dart
index 26fd3ab..f3542bd 100644
--- a/pkgs/test_descriptor/lib/src/utils.dart
+++ b/pkgs/test_descriptor/lib/src/utils.dart
@@ -12,11 +12,16 @@
 import 'sandbox.dart';
 
 /// A UTF-8 codec that allows malformed byte sequences.
-final utf8 = const Utf8Codec(allowMalformed: true);
+const utf8 = Utf8Codec(allowMalformed: true);
 
 /// Prepends a vertical bar to [text].
-String addBar(String text) => prefixLines(text, '${glyph.verticalLine} ',
-    first: '${glyph.downEnd} ', last: '${glyph.upEnd} ', single: '| ');
+String addBar(String text) => prefixLines(
+      text,
+      '${glyph.verticalLine} ',
+      first: '${glyph.downEnd} ',
+      last: '${glyph.upEnd} ',
+      single: '| ',
+    );
 
 /// Indents [text], and adds a bullet at the beginning.
 String addBullet(String text) =>
@@ -30,18 +35,24 @@
 String describeDirectory(String name, List<Descriptor> contents) {
   if (contents.isEmpty) return name;
 
-  var buffer = StringBuffer();
+  final buffer = StringBuffer();
   buffer.writeln(name);
   for (var entry in contents.take(contents.length - 1)) {
-    var entryString = prefixLines(entry.describe(), '${glyph.verticalLine}   ',
-        first: '${glyph.teeRight}${glyph.horizontalLine}'
-            '${glyph.horizontalLine} ');
+    final entryString = prefixLines(
+      entry.describe(),
+      '${glyph.verticalLine}   ',
+      first: '${glyph.teeRight}${glyph.horizontalLine}'
+          '${glyph.horizontalLine} ',
+    );
     buffer.writeln(entryString);
   }
 
-  var lastEntryString = prefixLines(contents.last.describe(), '    ',
-      first: '${glyph.bottomLeftCorner}${glyph.horizontalLine}'
-          '${glyph.horizontalLine} ');
+  final lastEntryString = prefixLines(
+    contents.last.describe(),
+    '    ',
+    first: '${glyph.bottomLeftCorner}${glyph.horizontalLine}'
+        '${glyph.horizontalLine} ',
+  );
   buffer.write(lastEntryString);
   return buffer.toString();
 }
@@ -52,16 +63,21 @@
 /// prefixed with those instead. If [single] is passed, it's used if there's
 /// only a single line; otherwise, [first], [last], or [prefix] is used, in that
 /// order of precedence.
-String prefixLines(String text, String prefix,
-    {String? first, String? last, String? single}) {
+String prefixLines(
+  String text,
+  String prefix, {
+  String? first,
+  String? last,
+  String? single,
+}) {
   single ??= first ?? last ?? prefix;
   first ??= prefix;
   last ??= prefix;
 
-  var lines = text.split('\n');
+  final lines = text.split('\n');
   if (lines.length == 1) return '$single$text';
 
-  var buffer = StringBuffer('$first${lines.first}\n');
+  final buffer = StringBuffer('$first${lines.first}\n');
   for (var line in lines.skip(1).take(lines.length - 2)) {
     buffer.writeln('$prefix$line');
   }
@@ -90,16 +106,22 @@
 /// first using [registerException] rather than silently ignoring them.
 Future<List<T>> waitAndReportErrors<T>(Iterable<Future<T>> futures) {
   var errored = false;
-  return Future.wait(futures.map((future) {
-    // Avoid async/await so that we synchronously add error handlers for the
-    // futures to keep them from top-leveling.
-    return future.catchError((Object error, StackTrace stackTrace) {
-      if (!errored) {
-        errored = true;
-        throw error; // ignore: only_throw_errors
-      } else {
-        registerException(error, stackTrace);
-      }
-    });
-  }));
+  return Future.wait(
+    futures.map(
+      (future) =>
+          // Avoid async/await so that we synchronously add error handlers for the
+          // futures to keep them from top-leveling.
+          future.catchError(
+        // ignore: body_might_complete_normally_catch_error
+        (Object error, StackTrace stackTrace) {
+          if (!errored) {
+            errored = true;
+            throw error; // ignore: only_throw_errors
+          } else {
+            registerException(error, stackTrace);
+          }
+        },
+      ),
+    ),
+  );
 }
diff --git a/pkgs/test_descriptor/lib/test_descriptor.dart b/pkgs/test_descriptor/lib/test_descriptor.dart
index cf5e07f..b302910 100644
--- a/pkgs/test_descriptor/lib/test_descriptor.dart
+++ b/pkgs/test_descriptor/lib/test_descriptor.dart
@@ -30,9 +30,10 @@
 /// If [contents] isn't passed, [Descriptor.create] creates an empty file and
 /// [Descriptor.validate] verifies that the file is empty.
 ///
-/// To match a [Matcher] against a file's binary contents, use [new
-/// FileDescriptor.binaryMatcher] instead.
-FileDescriptor file(String name, [contents]) => FileDescriptor(name, contents);
+/// To match a [Matcher] against a file's binary contents, use
+/// [FileDescriptor.binaryMatcher] instead.
+FileDescriptor file(String name, [Object? contents]) =>
+    FileDescriptor(name, contents);
 
 /// Creates a new [DirectoryDescriptor] descriptor with [name] and [contents].
 ///
@@ -60,12 +61,14 @@
 ///
 /// [Descriptor.create] is not supported for this descriptor.
 PatternDescriptor pattern(
-        Pattern name, Descriptor Function(String basename) child) =>
+  Pattern name,
+  Descriptor Function(String basename) child,
+) =>
     PatternDescriptor(name, child);
 
 /// A convenience method for creating a [PatternDescriptor] descriptor that
 /// constructs a [FileDescriptor] descriptor.
-PatternDescriptor filePattern(Pattern name, [contents]) =>
+PatternDescriptor filePattern(Pattern name, [Object? contents]) =>
     pattern(name, (realName) => file(realName, contents));
 
 /// A convenience method for creating a [PatternDescriptor] descriptor that
diff --git a/pkgs/test_descriptor/pubspec.yaml b/pkgs/test_descriptor/pubspec.yaml
index 0f3836c..1e01da0 100644
--- a/pkgs/test_descriptor/pubspec.yaml
+++ b/pkgs/test_descriptor/pubspec.yaml
@@ -1,19 +1,17 @@
 name: test_descriptor
-version: 2.0.1
-description: An API for defining and verifying directory structures.
+version: 2.0.2-dev
+description: An API for defining and verifying files and directory structures.
 repository: https://github.com/dart-lang/test_descriptor
 
 environment:
-  sdk: '>=2.12.0 <3.0.0'
+  sdk: '>=2.17.0 <3.0.0'
 
 dependencies:
   async: ^2.5.0
   collection: ^1.15.0
-  matcher: ^0.12.10
-  meta: ^1.3.0
   path: ^1.8.0
   term_glyph: ^1.2.0
   test: ^1.16.0
 
 dev_dependencies:
-  lints: ^1.0.0
+  lints: ^2.0.0
diff --git a/pkgs/test_descriptor/test/directory_test.dart b/pkgs/test_descriptor/test/directory_test.dart
index c669be8..b0b2a22 100644
--- a/pkgs/test_descriptor/test/directory_test.dart
+++ b/pkgs/test_descriptor/test/directory_test.dart
@@ -11,7 +11,6 @@
 import 'package:path/path.dart' as p;
 import 'package:term_glyph/term_glyph.dart' as term_glyph;
 import 'package:test/test.dart';
-
 import 'package:test_descriptor/test_descriptor.dart' as d;
 
 import 'utils.dart';
@@ -28,34 +27,40 @@
         d.file('file2.txt', 'contents2')
       ]).create();
 
-      expect(File(p.join(d.sandbox, 'dir', 'file1.txt')).readAsString(),
-          completion(equals('contents1')));
-      expect(File(p.join(d.sandbox, 'dir', 'file2.txt')).readAsString(),
-          completion(equals('contents2')));
       expect(
-          File(p.join(d.sandbox, 'dir', 'subdir', 'subfile1.txt'))
-              .readAsString(),
-          completion(equals('subcontents1')));
+        File(p.join(d.sandbox, 'dir', 'file1.txt')).readAsString(),
+        completion(equals('contents1')),
+      );
       expect(
-          File(p.join(d.sandbox, 'dir', 'subdir', 'subfile2.txt'))
-              .readAsString(),
-          completion(equals('subcontents2')));
+        File(p.join(d.sandbox, 'dir', 'file2.txt')).readAsString(),
+        completion(equals('contents2')),
+      );
+      expect(
+        File(p.join(d.sandbox, 'dir', 'subdir', 'subfile1.txt')).readAsString(),
+        completion(equals('subcontents1')),
+      );
+      expect(
+        File(p.join(d.sandbox, 'dir', 'subdir', 'subfile2.txt')).readAsString(),
+        completion(equals('subcontents2')),
+      );
     });
 
     test('works if the directory already exists', () async {
       await d.dir('dir').create();
       await d.dir('dir', [d.file('name.txt', 'contents')]).create();
 
-      expect(File(p.join(d.sandbox, 'dir', 'name.txt')).readAsString(),
-          completion(equals('contents')));
+      expect(
+        File(p.join(d.sandbox, 'dir', 'name.txt')).readAsString(),
+        completion(equals('contents')),
+      );
     });
   });
 
   group('validate()', () {
     test('completes successfully if the filesystem matches the descriptor',
         () async {
-      var dirPath = p.join(d.sandbox, 'dir');
-      var subdirPath = p.join(dirPath, 'subdir');
+      final dirPath = p.join(d.sandbox, 'dir');
+      final subdirPath = p.join(dirPath, 'subdir');
       await Directory(subdirPath).create(recursive: true);
       await File(p.join(dirPath, 'file1.txt')).writeAsString('contents1');
       await File(p.join(dirPath, 'file2.txt')).writeAsString('contents2');
@@ -75,35 +80,12 @@
     });
 
     test("fails if the directory doesn't exist", () async {
-      var dirPath = p.join(d.sandbox, 'dir');
+      final dirPath = p.join(d.sandbox, 'dir');
       await Directory(dirPath).create();
       await File(p.join(dirPath, 'file1.txt')).writeAsString('contents1');
       await File(p.join(dirPath, 'file2.txt')).writeAsString('contents2');
 
       expect(
-          d.dir('dir', [
-            d.dir('subdir', [
-              d.file('subfile1.txt', 'subcontents1'),
-              d.file('subfile2.txt', 'subcontents2')
-            ]),
-            d.file('file1.txt', 'contents1'),
-            d.file('file2.txt', 'contents2')
-          ]).validate(),
-          throwsA(toString(
-              equals('Directory not found: "${p.join('dir', 'subdir')}".'))));
-    });
-
-    test('emits an error for each child that fails to validate', () async {
-      var dirPath = p.join(d.sandbox, 'dir');
-      var subdirPath = p.join(dirPath, 'subdir');
-      await Directory(subdirPath).create(recursive: true);
-      await File(p.join(dirPath, 'file1.txt')).writeAsString('contents1');
-      await File(p.join(subdirPath, 'subfile2.txt'))
-          .writeAsString('subwrongtents2');
-
-      var errors = 0;
-      var controller = StreamController<String>();
-      runZonedGuarded(() {
         d.dir('dir', [
           d.dir('subdir', [
             d.file('subfile1.txt', 'subcontents1'),
@@ -111,49 +93,94 @@
           ]),
           d.file('file1.txt', 'contents1'),
           d.file('file2.txt', 'contents2')
-        ]).validate();
-      },
-          expectAsync2((error, _) {
+        ]).validate(),
+        throwsA(
+          toString(
+            equals('Directory not found: "${p.join('dir', 'subdir')}".'),
+          ),
+        ),
+      );
+    });
+
+    test('emits an error for each child that fails to validate', () async {
+      final dirPath = p.join(d.sandbox, 'dir');
+      final subdirPath = p.join(dirPath, 'subdir');
+      await Directory(subdirPath).create(recursive: true);
+      await File(p.join(dirPath, 'file1.txt')).writeAsString('contents1');
+      await File(p.join(subdirPath, 'subfile2.txt'))
+          .writeAsString('subwrongtents2');
+
+      var errors = 0;
+      final controller = StreamController<String>();
+      runZonedGuarded(
+        () {
+          d.dir('dir', [
+            d.dir('subdir', [
+              d.file('subfile1.txt', 'subcontents1'),
+              d.file('subfile2.txt', 'subcontents2')
+            ]),
+            d.file('file1.txt', 'contents1'),
+            d.file('file2.txt', 'contents2')
+          ]).validate();
+        },
+        expectAsync2(
+          (error, _) {
             errors++;
             controller.add(error.toString());
             if (errors == 3) controller.close();
-          }, count: 3));
+          },
+          count: 3,
+        ),
+      );
 
       expect(
-          controller.stream.toList(),
-          completion(allOf([
+        controller.stream.toList(),
+        completion(
+          allOf([
             contains(
-                'File not found: "${p.join('dir', 'subdir', 'subfile1.txt')}".'),
+              'File not found: "${p.join('dir', 'subdir', 'subfile1.txt')}".',
+            ),
             contains('File not found: "${p.join('dir', 'file2.txt')}".'),
             contains(
-                startsWith('File "${p.join('dir', 'subdir', 'subfile2.txt')}" '
-                    'should contain:')),
-          ])));
+              startsWith('File "${p.join('dir', 'subdir', 'subfile2.txt')}" '
+                  'should contain:'),
+            ),
+          ]),
+        ),
+      );
     });
   });
 
   group('load()', () {
     test('loads a file', () {
-      var dir = d.dir('dir',
-          [d.file('name.txt', 'contents'), d.file('other.txt', 'wrong')]);
-      expect(utf8.decodeStream(dir.load('name.txt')),
-          completion(equals('contents')));
+      final dir = d.dir(
+        'dir',
+        [d.file('name.txt', 'contents'), d.file('other.txt', 'wrong')],
+      );
+      expect(
+        utf8.decodeStream(dir.load('name.txt')),
+        completion(equals('contents')),
+      );
     });
 
     test('loads a deeply-nested file', () {
-      var dir = d.dir('dir', [
-        d.dir('subdir',
-            [d.file('name.txt', 'subcontents'), d.file('other.txt', 'wrong')]),
+      final dir = d.dir('dir', [
+        d.dir(
+          'subdir',
+          [d.file('name.txt', 'subcontents'), d.file('other.txt', 'wrong')],
+        ),
         d.dir('otherdir', [d.file('other.txt', 'wrong')]),
         d.file('name.txt', 'contents')
       ]);
 
-      expect(utf8.decodeStream(dir.load('subdir/name.txt')),
-          completion(equals('subcontents')));
+      expect(
+        utf8.decodeStream(dir.load('subdir/name.txt')),
+        completion(equals('subcontents')),
+      );
     });
 
     test('fails to load a nested directory', () {
-      var dir = d.dir('dir', [
+      final dir = d.dir('dir', [
         d.dir('subdir', [
           d.dir('subsubdir', [d.file('name.txt', 'subcontents')])
         ]),
@@ -161,52 +188,71 @@
       ]);
 
       expect(
-          dir.load('subdir/subsubdir').toList(),
-          throwsA(toString(equals('Couldn\'t find a file descriptor named '
-              '"subsubdir" within "dir/subdir".'))));
+        dir.load('subdir/subsubdir').toList(),
+        throwsA(
+          toString(
+            equals('Couldn\'t find a file descriptor named '
+                '"subsubdir" within "dir/subdir".'),
+          ),
+        ),
+      );
     });
 
     test('fails to load an absolute path', () {
-      var dir = d.dir('dir', [d.file('name.txt', 'contents')]);
+      final dir = d.dir('dir', [d.file('name.txt', 'contents')]);
       expect(() => dir.load('/name.txt'), throwsArgumentError);
     });
 
     test("fails to load '..'", () {
-      var dir = d.dir('dir', [d.file('name.txt', 'contents')]);
+      final dir = d.dir('dir', [d.file('name.txt', 'contents')]);
       expect(() => dir.load('..'), throwsArgumentError);
     });
 
     test("fails to load a file that doesn't exist", () {
-      var dir = d.dir('dir', [
+      final dir = d.dir('dir', [
         d.dir('subdir', [d.file('name.txt', 'contents')])
       ]);
 
       expect(
-          dir.load('subdir/not-name.txt').toList(),
-          throwsA(toString(equals('Couldn\'t find a file descriptor named '
-              '"not-name.txt" within "dir/subdir".'))));
+        dir.load('subdir/not-name.txt').toList(),
+        throwsA(
+          toString(
+            equals('Couldn\'t find a file descriptor named '
+                '"not-name.txt" within "dir/subdir".'),
+          ),
+        ),
+      );
     });
 
     test('fails to load a file that exists multiple times', () {
-      var dir = d.dir('dir', [
-        d.dir('subdir',
-            [d.file('name.txt', 'contents'), d.file('name.txt', 'contents')])
+      final dir = d.dir('dir', [
+        d.dir(
+          'subdir',
+          [d.file('name.txt', 'contents'), d.file('name.txt', 'contents')],
+        )
       ]);
 
       expect(
-          dir.load('subdir/name.txt').toList(),
-          throwsA(toString(equals('Found multiple file descriptors named '
-              '"name.txt" within "dir/subdir".'))));
+        dir.load('subdir/name.txt').toList(),
+        throwsA(
+          toString(
+            equals('Found multiple file descriptors named '
+                '"name.txt" within "dir/subdir".'),
+          ),
+        ),
+      );
     });
 
     test('loads a file next to a subdirectory with the same name', () {
-      var dir = d.dir('dir', [
+      final dir = d.dir('dir', [
         d.file('name', 'contents'),
         d.dir('name', [d.file('subfile', 'contents')])
       ]);
 
       expect(
-          utf8.decodeStream(dir.load('name')), completion(equals('contents')));
+        utf8.decodeStream(dir.load('name')),
+        completion(equals('contents')),
+      );
     });
   });
 
@@ -222,18 +268,21 @@
     });
 
     test('lists the contents of the directory', () {
-      var dir = d.dir('dir',
-          [d.file('file1.txt', 'contents1'), d.file('file2.txt', 'contents2')]);
+      final dir = d.dir(
+        'dir',
+        [d.file('file1.txt', 'contents1'), d.file('file2.txt', 'contents2')],
+      );
 
       expect(
-          dir.describe(),
-          equals('dir\n'
-              '+-- file1.txt\n'
-              "'-- file2.txt"));
+        dir.describe(),
+        equals('dir\n'
+            '+-- file1.txt\n'
+            "'-- file2.txt"),
+      );
     });
 
     test('lists the contents of nested directories', () {
-      var dir = d.dir('dir', [
+      final dir = d.dir('dir', [
         d.file('file1.txt', 'contents1'),
         d.dir('subdir', [
           d.file('subfile1.txt', 'subcontents1'),
@@ -244,15 +293,16 @@
       ]);
 
       expect(
-          dir.describe(),
-          equals('dir\n'
-              '+-- file1.txt\n'
-              '+-- subdir\n'
-              '|   +-- subfile1.txt\n'
-              '|   +-- subfile2.txt\n'
-              "|   '-- subsubdir\n"
-              "|       '-- subsubfile.txt\n"
-              "'-- file2.txt"));
+        dir.describe(),
+        equals('dir\n'
+            '+-- file1.txt\n'
+            '+-- subdir\n'
+            '|   +-- subfile1.txt\n'
+            '|   +-- subfile2.txt\n'
+            "|   '-- subsubdir\n"
+            "|       '-- subsubfile.txt\n"
+            "'-- file2.txt"),
+      );
     });
 
     test('with no contents returns the directory name', () {
@@ -262,7 +312,7 @@
 
   group('fromFilesystem()', () {
     test('creates a descriptor based on the physical filesystem', () async {
-      var dir = d.dir('dir', [
+      final dir = d.dir('dir', [
         d.dir('subdir', [
           d.file('subfile1.txt', 'subcontents1'),
           d.file('subfile2.txt', 'subcontents2')
@@ -272,7 +322,7 @@
       ]);
 
       await dir.create();
-      var descriptor =
+      final descriptor =
           d.DirectoryDescriptor.fromFilesystem('dir', p.join(d.sandbox, 'dir'));
       await descriptor.create(p.join(d.sandbox, 'dir2'));
       await dir.validate(p.join(d.sandbox, 'dir2'));
@@ -288,13 +338,17 @@
         d.file('.DS_Store', 'contents2')
       ]).create();
 
-      var descriptor = d.DirectoryDescriptor.fromFilesystem(
-          'dir2', p.join(d.sandbox, 'dir'));
+      final descriptor = d.DirectoryDescriptor.fromFilesystem(
+        'dir2',
+        p.join(d.sandbox, 'dir'),
+      );
       await descriptor.create();
 
       await d.dir('dir2', [
-        d.dir('subdir',
-            [d.file('subfile1.txt', 'subcontents1'), d.nothing('.hidden')]),
+        d.dir(
+          'subdir',
+          [d.file('subfile1.txt', 'subcontents1'), d.nothing('.hidden')],
+        ),
         d.file('file1.txt', 'contents1'),
         d.nothing('.DS_Store')
       ]).validate();
diff --git a/pkgs/test_descriptor/test/file_test.dart b/pkgs/test_descriptor/test/file_test.dart
index 13b7597..26a4528 100644
--- a/pkgs/test_descriptor/test/file_test.dart
+++ b/pkgs/test_descriptor/test/file_test.dart
@@ -18,28 +18,36 @@
     test('creates a text file', () async {
       await d.file('name.txt', 'contents').create();
 
-      expect(File(p.join(d.sandbox, 'name.txt')).readAsString(),
-          completion(equals('contents')));
+      expect(
+        File(p.join(d.sandbox, 'name.txt')).readAsString(),
+        completion(equals('contents')),
+      );
     });
 
     test('creates a binary file', () async {
       await d.file('name.txt', [0, 1, 2, 3]).create();
 
-      expect(File(p.join(d.sandbox, 'name.txt')).readAsBytes(),
-          completion(equals([0, 1, 2, 3])));
+      expect(
+        File(p.join(d.sandbox, 'name.txt')).readAsBytes(),
+        completion(equals([0, 1, 2, 3])),
+      );
     });
 
     test('fails to create a matcher file', () async {
       expect(
-          d.file('name.txt', contains('foo')).create(), throwsUnsupportedError);
+        d.file('name.txt', contains('foo')).create(),
+        throwsUnsupportedError,
+      );
     });
 
     test('overwrites an existing file', () async {
       await d.file('name.txt', 'contents1').create();
       await d.file('name.txt', 'contents2').create();
 
-      expect(File(p.join(d.sandbox, 'name.txt')).readAsString(),
-          completion(equals('contents2')));
+      expect(
+        File(p.join(d.sandbox, 'name.txt')).readAsString(),
+        completion(equals('contents2')),
+      );
     });
   });
 
@@ -72,62 +80,85 @@
     test("fails if the text contents don't match", () async {
       await File(p.join(d.sandbox, 'name.txt')).writeAsString('wrong');
 
-      expect(d.file('name.txt', 'contents').validate(),
-          throwsA(toString(startsWith('File "name.txt" should contain:'))));
+      expect(
+        d.file('name.txt', 'contents').validate(),
+        throwsA(toString(startsWith('File "name.txt" should contain:'))),
+      );
     });
 
     test("fails if the binary contents don't match", () async {
       await File(p.join(d.sandbox, 'name.txt')).writeAsBytes([5, 4, 3, 2]);
 
       expect(
-          d.file('name.txt', [0, 1, 2, 3]).validate(),
-          throwsA(toString(equals(
-              'File "name.txt" didn\'t contain the expected binary data.'))));
+        d.file('name.txt', [0, 1, 2, 3]).validate(),
+        throwsA(
+          toString(
+            equals(
+              'File "name.txt" didn\'t contain the expected binary data.',
+            ),
+          ),
+        ),
+      );
     });
 
     test("fails if the text contents don't match the matcher", () async {
       await File(p.join(d.sandbox, 'name.txt')).writeAsString('wrong');
 
       expect(
-          d.file('name.txt', contains('ent')).validate(),
-          throwsA(
-              toString(startsWith('Invalid contents for file "name.txt":'))));
+        d.file('name.txt', contains('ent')).validate(),
+        throwsA(
+          toString(startsWith('Invalid contents for file "name.txt":')),
+        ),
+      );
     });
 
     test("fails if the binary contents don't match the matcher", () async {
       await File(p.join(d.sandbox, 'name.txt')).writeAsBytes([5, 4, 3, 2]);
 
       expect(
-          d.FileDescriptor.binaryMatcher('name.txt', contains(1)).validate(),
-          throwsA(
-              toString(startsWith('Invalid contents for file "name.txt":'))));
+        d.FileDescriptor.binaryMatcher('name.txt', contains(1)).validate(),
+        throwsA(
+          toString(startsWith('Invalid contents for file "name.txt":')),
+        ),
+      );
     });
 
     test("fails if invalid UTF-8 doesn't match a text matcher", () async {
       await File(p.join(d.sandbox, 'name.txt')).writeAsBytes([0xC3, 0x28]);
       expect(
-          d.file('name.txt', isEmpty).validate(),
-          throwsA(toString(allOf([
-            startsWith('Invalid contents for file "name.txt":'),
-            contains('�')
-          ]))));
+        d.file('name.txt', isEmpty).validate(),
+        throwsA(
+          toString(
+            allOf([
+              startsWith('Invalid contents for file "name.txt":'),
+              contains('�')
+            ]),
+          ),
+        ),
+      );
     });
 
     test("fails if there's no file", () {
-      expect(d.file('name.txt', 'contents').validate(),
-          throwsA(toString(equals('File not found: "name.txt".'))));
+      expect(
+        d.file('name.txt', 'contents').validate(),
+        throwsA(toString(equals('File not found: "name.txt".'))),
+      );
     });
   });
 
   group('reading', () {
     test('read() returns the contents of a text file as a string', () {
-      expect(d.file('name.txt', 'contents').read(),
-          completion(equals('contents')));
+      expect(
+        d.file('name.txt', 'contents').read(),
+        completion(equals('contents')),
+      );
     });
 
     test('read() returns the contents of a binary file as a string', () {
-      expect(d.file('name.txt', [0x68, 0x65, 0x6c, 0x6c, 0x6f]).read(),
-          completion(equals('hello')));
+      expect(
+        d.file('name.txt', [0x68, 0x65, 0x6c, 0x6c, 0x6f]).read(),
+        completion(equals('hello')),
+      );
     });
 
     test('read() fails for a matcher file', () {
@@ -136,19 +167,25 @@
 
     test('readAsBytes() returns the contents of a text file as a byte stream',
         () {
-      expect(utf8.decodeStream(d.file('name.txt', 'contents').readAsBytes()),
-          completion(equals('contents')));
+      expect(
+        utf8.decodeStream(d.file('name.txt', 'contents').readAsBytes()),
+        completion(equals('contents')),
+      );
     });
 
     test('readAsBytes() returns the contents of a binary file as a byte stream',
         () {
-      expect(byteStreamToList(d.file('name.txt', [0, 1, 2, 3]).readAsBytes()),
-          completion(equals([0, 1, 2, 3])));
+      expect(
+        byteStreamToList(d.file('name.txt', [0, 1, 2, 3]).readAsBytes()),
+        completion(equals([0, 1, 2, 3])),
+      );
     });
 
     test('readAsBytes() fails for a matcher file', () {
-      expect(d.file('name.txt', contains('hi')).readAsBytes,
-          throwsUnsupportedError);
+      expect(
+        d.file('name.txt', contains('hi')).readAsBytes,
+        throwsUnsupportedError,
+      );
     });
   });
 
diff --git a/pkgs/test_descriptor/test/nothing_test.dart b/pkgs/test_descriptor/test/nothing_test.dart
index a192087..23d91ff 100644
--- a/pkgs/test_descriptor/test/nothing_test.dart
+++ b/pkgs/test_descriptor/test/nothing_test.dart
@@ -28,25 +28,43 @@
     test("fails if there's a file", () async {
       await d.file('name.txt', 'contents').create();
       expect(
-          d.nothing('name.txt').validate(),
-          throwsA(toString(equals(
-              'Expected nothing to exist at "name.txt", but found a file.'))));
+        d.nothing('name.txt').validate(),
+        throwsA(
+          toString(
+            equals(
+              'Expected nothing to exist at "name.txt", but found a file.',
+            ),
+          ),
+        ),
+      );
     });
 
     test("fails if there's a directory", () async {
       await d.dir('dir').create();
       expect(
-          d.nothing('dir').validate(),
-          throwsA(toString(equals(
-              'Expected nothing to exist at "dir", but found a directory.'))));
+        d.nothing('dir').validate(),
+        throwsA(
+          toString(
+            equals(
+              'Expected nothing to exist at "dir", but found a directory.',
+            ),
+          ),
+        ),
+      );
     });
 
     test("fails if there's a broken link", () async {
       await Link(p.join(d.sandbox, 'link')).create('nonexistent');
       expect(
-          d.nothing('link').validate(),
-          throwsA(toString(equals(
-              'Expected nothing to exist at "link", but found a link.'))));
+        d.nothing('link').validate(),
+        throwsA(
+          toString(
+            equals(
+              'Expected nothing to exist at "link", but found a link.',
+            ),
+          ),
+        ),
+      );
     });
   });
 }
diff --git a/pkgs/test_descriptor/test/pattern_test.dart b/pkgs/test_descriptor/test/pattern_test.dart
index eb7b815..3963fc0 100644
--- a/pkgs/test_descriptor/test/pattern_test.dart
+++ b/pkgs/test_descriptor/test/pattern_test.dart
@@ -15,14 +15,14 @@
     test("succeeds if there's a file matching the pattern and the child",
         () async {
       await d.file('foo', 'blap').create();
-      await d.filePattern(RegExp(r'f..'), 'blap').validate();
+      await d.filePattern(RegExp('f..'), 'blap').validate();
     });
 
     test("succeeds if there's a directory matching the pattern and the child",
         () async {
       await d.dir('foo', [d.file('bar', 'baz')]).create();
 
-      await d.dirPattern(RegExp(r'f..'), [d.file('bar', 'baz')]).validate();
+      await d.dirPattern(RegExp('f..'), [d.file('bar', 'baz')]).validate();
     });
 
     test(
@@ -32,29 +32,35 @@
       await d.file('fee', 'blak').create();
       await d.file('faa', 'blut').create();
 
-      await d.filePattern(RegExp(r'f..'), 'blap').validate();
+      await d.filePattern(RegExp('f..'), 'blap').validate();
     });
 
     test("fails if there's no file matching the pattern", () {
       expect(
-          d.filePattern(RegExp(r'f..'), 'bar').validate(),
-          throwsA(
-              toString(equals('No entries found in sandbox matching /f../.'))));
+        d.filePattern(RegExp('f..'), 'bar').validate(),
+        throwsA(
+          toString(equals('No entries found in sandbox matching /f../.')),
+        ),
+      );
     });
 
     test("fails if there's a file matching the pattern but not the entry",
         () async {
       await d.file('foo', 'bap').create();
-      expect(d.filePattern(RegExp(r'f..'), 'bar').validate(),
-          throwsA(toString(startsWith('File "foo" should contain:'))));
+      expect(
+        d.filePattern(RegExp('f..'), 'bar').validate(),
+        throwsA(toString(startsWith('File "foo" should contain:'))),
+      );
     });
 
     test("fails if there's a dir matching the pattern but not the entry",
         () async {
       await d.dir('foo', [d.file('bar', 'bap')]).create();
 
-      expect(d.dirPattern(RegExp(r'f..'), [d.file('bar', 'baz')]).validate(),
-          throwsA(toString(startsWith('File "foo/bar" should contain:'))));
+      expect(
+        d.dirPattern(RegExp('f..'), [d.file('bar', 'baz')]).validate(),
+        throwsA(toString(startsWith('File "foo/bar" should contain:'))),
+      );
     });
 
     test(
@@ -64,9 +70,15 @@
       await d.file('fee', 'bar').create();
       await d.file('faa', 'bar').create();
       expect(
-          d.filePattern(RegExp(r'f..'), 'bar').validate(),
-          throwsA(toString(startsWith(
-              'Multiple valid entries found in sandbox matching /f../:'))));
+        d.filePattern(RegExp('f..'), 'bar').validate(),
+        throwsA(
+          toString(
+            startsWith(
+              'Multiple valid entries found in sandbox matching /f../:',
+            ),
+          ),
+        ),
+      );
     });
   });
 }
diff --git a/pkgs/test_descriptor/test/utils.dart b/pkgs/test_descriptor/test/utils.dart
index 5eb84f7..8b07e8c 100644
--- a/pkgs/test_descriptor/test/utils.dart
+++ b/pkgs/test_descriptor/test/utils.dart
@@ -5,18 +5,18 @@
 import 'package:test/test.dart';
 
 /// Converts a [Stream<List<int>>] to a flat byte future.
-Future<List<int>> byteStreamToList(Stream<List<int>> stream) {
-  return stream.fold(<int>[], (buffer, chunk) {
-    buffer.addAll(chunk);
-    return buffer;
-  });
-}
+Future<List<int>> byteStreamToList(Stream<List<int>> stream) =>
+    stream.fold(<int>[], (buffer, chunk) {
+      buffer.addAll(chunk);
+      return buffer;
+    });
 
 /// Returns a matcher that verifies that the result of calling `toString()`
 /// matches [matcher].
-Matcher toString(matcher) {
-  return predicate((object) {
-    expect(object.toString(), matcher);
-    return true;
-  }, 'toString() matches $matcher');
-}
+Matcher toString(Object? matcher) => predicate(
+      (object) {
+        expect(object.toString(), matcher);
+        return true;
+      },
+      'toString() matches $matcher',
+    );