Migrate to null safety (#93)

* Migrate non-deprecated libraries to null safety.

* Major version increment, removing deprecated APIs.

This is the null safe, non-deprecated API for package_config.json
file manipulation.

Also address #86.
diff --git a/CHANGELOG.md b/CHANGELOG.md
index 6dcae80..32ff3f1 100644
--- a/CHANGELOG.md
+++ b/CHANGELOG.md
@@ -1,3 +1,8 @@
+## 2.0.0
+
+- Migrate to null safety.
+- Remove legacy APIs.
+
 ## 1.9.3
 
 - Fix `Package` constructor not accepting relative `packageUriRoot`.
diff --git a/README.md b/README.md
index b47a682..1ad4b41 100644
--- a/README.md
+++ b/README.md
@@ -7,11 +7,12 @@
 The primary libraries are
 * `package_config.dart`:
     Defines the `PackageConfig` class and other types needed to use
-    package configurations.
+    package configurations, and provides functions to find, read and
+    write package configuration files.
 
-* `package_config_discovery.dart`:
-    Provides functions for reading configurations from files,
-    and writing them back out.
+* `package_config_types.dart`:
+    Just the `PackageConfig` class and other types needed to use
+    package configurations. This library does not depend on `dart:io`.
 
 The package includes deprecated backwards compatible functionality to
 work with the `.packages` file. This functionality will not be maintained,
diff --git a/analysis_options.yaml b/analysis_options.yaml
index 66639ec..a785408 100644
--- a/analysis_options.yaml
+++ b/analysis_options.yaml
@@ -6,6 +6,5 @@
 analyzer:
   errors:
     annotate_overrides: ignore
-    curly_braces_in_flow_control_structures: ignore
     prefer_single_quotes: ignore
     use_function_type_syntax_for_parameters: ignore
diff --git a/lib/discovery.dart b/lib/discovery.dart
deleted file mode 100644
index a72bb12..0000000
--- a/lib/discovery.dart
+++ /dev/null
@@ -1,226 +0,0 @@
-// Copyright (c) 2015, 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.
-
-@Deprecated("Use the package_config.json based API")
-library package_config.discovery;
-
-import "dart:io";
-import "dart:typed_data" show Uint8List;
-
-import "package:path/path.dart" as path;
-
-import "packages.dart";
-import "packages_file.dart" as pkgfile show parse;
-import "src/packages_impl.dart";
-import "src/packages_io_impl.dart";
-
-/// Reads a package resolution file and creates a [Packages] object from it.
-///
-/// The [packagesFile] must exist and be loadable.
-/// Currently that means the URI must have a `file`, `http` or `https` scheme,
-/// and that the file can be loaded and its contents parsed correctly.
-///
-/// If the [loader] is provided, it is used to fetch non-`file` URIs, and
-/// it can support other schemes or set up more complex HTTP requests.
-///
-/// This function can be used to load an explicitly configured package
-/// resolution file, for example one specified using a `--packages`
-/// command-line parameter.
-Future<Packages> loadPackagesFile(Uri packagesFile,
-    {Future<List<int>> loader(Uri uri)}) async {
-  Packages parseBytes(List<int> bytes) {
-    return MapPackages(pkgfile.parse(bytes, packagesFile));
-  }
-
-  if (packagesFile.scheme == "file") {
-    return parseBytes(await File.fromUri(packagesFile).readAsBytes());
-  }
-  if (loader == null) {
-    return parseBytes(await _httpGet(packagesFile));
-  }
-  return parseBytes(await loader(packagesFile));
-}
-
-/// Create a [Packages] object for a package directory.
-///
-/// The [packagesDir] URI should refer to a directory.
-/// Package names are resolved as relative to sub-directories of the
-/// package directory.
-///
-/// This function can be used for explicitly configured package directories,
-/// for example one specified using a `--package-root` comand-line parameter.
-Packages getPackagesDirectory(Uri packagesDir) {
-  if (packagesDir.scheme == "file") {
-    return FilePackagesDirectoryPackages(Directory.fromUri(packagesDir));
-  }
-  if (!packagesDir.path.endsWith('/')) {
-    packagesDir = packagesDir.replace(path: packagesDir.path + '/');
-  }
-  return NonFilePackagesDirectoryPackages(packagesDir);
-}
-
-/// Discover the package configuration for a Dart script.
-///
-/// The [baseUri] points to either the Dart script or its directory.
-/// A package resolution strategy is found by going through the following steps,
-/// and stopping when something is found.
-///
-/// * Check if a `.packages` file exists in the same directory.
-/// * If `baseUri`'s scheme is not `file`, then assume a `packages` directory
-///   in the same directory, and resolve packages relative to that.
-/// * If `baseUri`'s scheme *is* `file`:
-///   * Check if a `packages` directory exists.
-///   * Otherwise check each successive parent directory of `baseUri` for a
-///     `.packages` file.
-///
-/// If any of these tests succeed, a `Packages` class is returned.
-/// Returns the constant [noPackages] if no resolution strategy is found.
-///
-/// This function currently only supports `file`, `http` and `https` URIs.
-/// It needs to be able to load a `.packages` file from the URI, so only
-/// recognized schemes are accepted.
-///
-/// To support other schemes, or more complex HTTP requests,
-/// an optional [loader] function can be supplied.
-/// It's called to load the `.packages` file for a non-`file` scheme.
-/// The loader function returns the *contents* of the file
-/// identified by the URI it's given.
-/// The content should be a UTF-8 encoded `.packages` file, and must return an
-/// error future if loading fails for any reason.
-Future<Packages> findPackages(Uri baseUri,
-    {Future<List<int>> loader(Uri unsupportedUri)}) {
-  if (baseUri.scheme == "file") {
-    return Future<Packages>.sync(() => findPackagesFromFile(baseUri));
-  } else if (loader != null) {
-    return findPackagesFromNonFile(baseUri, loader: loader);
-  } else if (baseUri.scheme == "http" || baseUri.scheme == "https") {
-    return findPackagesFromNonFile(baseUri, loader: _httpGet);
-  } else {
-    return Future<Packages>.value(Packages.noPackages);
-  }
-}
-
-/// Find the location of the package resolution file/directory for a Dart file.
-///
-/// Checks for a `.packages` file in the [workingDirectory].
-/// If not found, checks for a `packages` directory in the same directory.
-/// If still not found, starts checking parent directories for
-/// `.packages` until reaching the root directory.
-///
-/// Returns a [File] object of a `.packages` file if one is found, or a
-/// [Directory] object for the `packages/` directory if that is found.
-FileSystemEntity _findPackagesFile(String workingDirectory) {
-  var dir = Directory(workingDirectory);
-  if (!dir.isAbsolute) dir = dir.absolute;
-  if (!dir.existsSync()) {
-    throw ArgumentError.value(
-        workingDirectory, "workingDirectory", "Directory does not exist.");
-  }
-  File checkForConfigFile(Directory directory) {
-    assert(directory.isAbsolute);
-    var file = File(path.join(directory.path, ".packages"));
-    if (file.existsSync()) return file;
-    return null;
-  }
-
-  // Check for $cwd/.packages
-  var packagesCfgFile = checkForConfigFile(dir);
-  if (packagesCfgFile != null) return packagesCfgFile;
-  // Check for $cwd/packages/
-  var packagesDir = Directory(path.join(dir.path, "packages"));
-  if (packagesDir.existsSync()) return packagesDir;
-  // Check for cwd(/..)+/.packages
-  var parentDir = dir.parent;
-  while (parentDir.path != dir.path) {
-    packagesCfgFile = checkForConfigFile(parentDir);
-    if (packagesCfgFile != null) break;
-    dir = parentDir;
-    parentDir = dir.parent;
-  }
-  return packagesCfgFile;
-}
-
-/// Finds a package resolution strategy for a local Dart script.
-///
-/// The [fileBaseUri] points to either a Dart script or the directory of the
-/// script. The `fileBaseUri` must be a `file:` URI.
-///
-/// This function first tries to locate a `.packages` file in the `fileBaseUri`
-/// directory. If that is not found, it instead checks for the presence of
-/// a `packages/` directory in the same place.
-/// If that also fails, it starts checking parent directories for a `.packages`
-/// file, and stops if it finds it.
-/// Otherwise it gives up and returns [Packages.noPackages].
-Packages findPackagesFromFile(Uri fileBaseUri) {
-  var baseDirectoryUri = fileBaseUri;
-  if (!fileBaseUri.path.endsWith('/')) {
-    baseDirectoryUri = baseDirectoryUri.resolve(".");
-  }
-  var baseDirectoryPath = baseDirectoryUri.toFilePath();
-  var location = _findPackagesFile(baseDirectoryPath);
-  if (location == null) return Packages.noPackages;
-  if (location is File) {
-    var fileBytes = location.readAsBytesSync();
-    var map = pkgfile.parse(fileBytes, Uri.file(location.path));
-    return MapPackages(map);
-  }
-  assert(location is Directory);
-  return FilePackagesDirectoryPackages(location);
-}
-
-/// Finds a package resolution strategy for a Dart script.
-///
-/// The [nonFileUri] points to either a Dart script or the directory of the
-/// script.
-/// The [nonFileUri] should not be a `file:` URI since the algorithm for
-/// finding a package resolution strategy is more elaborate for `file:` URIs.
-/// In that case, use [findPackagesFromFile].
-///
-/// This function first tries to locate a `.packages` file in the [nonFileUri]
-/// directory. If that is not found, it instead assumes a `packages/` directory
-/// in the same place.
-///
-/// By default, this function only works for `http:` and `https:` URIs.
-/// To support other schemes, a loader must be provided, which is used to
-/// try to load the `.packages` file. The loader should return the contents
-/// of the requested `.packages` file as bytes, which will be assumed to be
-/// UTF-8 encoded.
-Future<Packages> findPackagesFromNonFile(Uri nonFileUri,
-    {Future<List<int>> loader(Uri name)}) async {
-  loader ??= _httpGet;
-  var packagesFileUri = nonFileUri.resolve(".packages");
-
-  try {
-    var fileBytes = await loader(packagesFileUri);
-    var map = pkgfile.parse(fileBytes, packagesFileUri);
-    return MapPackages(map);
-  } catch (_) {
-    // Didn't manage to load ".packages". Assume a "packages/" directory.
-    var packagesDirectoryUri = nonFileUri.resolve("packages/");
-    return NonFilePackagesDirectoryPackages(packagesDirectoryUri);
-  }
-}
-
-/// Fetches a file over http.
-Future<List<int>> _httpGet(Uri uri) async {
-  var client = HttpClient();
-  var request = await client.getUrl(uri);
-  var response = await request.close();
-  if (response.statusCode != HttpStatus.ok) {
-    throw HttpException('${response.statusCode} ${response.reasonPhrase}',
-        uri: uri);
-  }
-  var splitContent = await response.toList();
-  var totalLength = 0;
-  for (var list in splitContent) {
-    totalLength += list.length;
-  }
-  var result = Uint8List(totalLength);
-  var offset = 0;
-  for (var contentPart in splitContent) {
-    result.setRange(offset, offset + contentPart.length, contentPart);
-    offset += contentPart.length;
-  }
-  return result;
-}
diff --git a/lib/discovery_analysis.dart b/lib/discovery_analysis.dart
deleted file mode 100644
index 2af0729..0000000
--- a/lib/discovery_analysis.dart
+++ /dev/null
@@ -1,167 +0,0 @@
-// Copyright (c) 2015, 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.
-
-/// Analyse a directory structure and find packages resolvers for each
-/// sub-directory.
-///
-/// The resolvers are generally the same that would be found by using
-/// the `discovery.dart` library on each sub-directory in turn,
-/// but more efficiently and with some heuristics for directories that
-/// wouldn't otherwise have a package resolution strategy, or that are
-/// determined to be "package directories" themselves.
-@Deprecated("Use the package_config.json based API")
-library package_config.discovery_analysis;
-
-import "dart:collection" show HashMap;
-import "dart:io" show File, Directory;
-
-import "package:path/path.dart" as path;
-
-import "packages.dart";
-import "packages_file.dart" as pkgfile;
-import "src/packages_impl.dart";
-import "src/packages_io_impl.dart";
-
-/// Associates a [Packages] package resolution strategy with a directory.
-///
-/// The package resolution applies to the directory and any sub-directory
-/// that doesn't have its own overriding child [PackageContext].
-abstract class PackageContext {
-  /// The directory that introduced the [packages] resolver.
-  Directory get directory;
-
-  /// A [Packages] resolver that applies to the directory.
-  ///
-  /// Introduced either by a `.packages` file or a `packages/` directory.
-  Packages get packages;
-
-  /// Child contexts that apply to sub-directories of [directory].
-  List<PackageContext> get children;
-
-  /// Look up the [PackageContext] that applies to a specific directory.
-  ///
-  /// The directory must be inside [directory].
-  PackageContext operator [](Directory directory);
-
-  /// A map from directory to package resolver.
-  ///
-  /// Has an entry for this package context and for each child context
-  /// contained in this one.
-  Map<Directory, Packages> asMap();
-
-  /// Analyze [directory] and sub-directories for package resolution strategies.
-  ///
-  /// Returns a mapping from sub-directories to [Packages] objects.
-  ///
-  /// The analysis assumes that there are no `.packages` files in a parent
-  /// directory of `directory`. If there is, its corresponding `Packages` object
-  /// should be provided as `root`.
-  static PackageContext findAll(Directory directory,
-      {Packages root = Packages.noPackages}) {
-    if (!directory.existsSync()) {
-      throw ArgumentError("Directory not found: $directory");
-    }
-    var contexts = <PackageContext>[];
-    void findRoots(Directory directory) {
-      Packages packages;
-      List<PackageContext> oldContexts;
-      var packagesFile = File(path.join(directory.path, ".packages"));
-      if (packagesFile.existsSync()) {
-        packages = _loadPackagesFile(packagesFile);
-        oldContexts = contexts;
-        contexts = [];
-      } else {
-        var packagesDir = Directory(path.join(directory.path, "packages"));
-        if (packagesDir.existsSync()) {
-          packages = FilePackagesDirectoryPackages(packagesDir);
-          oldContexts = contexts;
-          contexts = [];
-        }
-      }
-      for (var entry in directory.listSync()) {
-        if (entry is Directory) {
-          if (packages == null || !entry.path.endsWith("/packages")) {
-            findRoots(entry);
-          }
-        }
-      }
-      if (packages != null) {
-        oldContexts.add(_PackageContext(directory, packages, contexts));
-        contexts = oldContexts;
-      }
-    }
-
-    findRoots(directory);
-    // If the root is not itself context root, add a the wrapper context.
-    if (contexts.length == 1 && contexts[0].directory == directory) {
-      return contexts[0];
-    }
-    return _PackageContext(directory, root, contexts);
-  }
-}
-
-class _PackageContext implements PackageContext {
-  final Directory directory;
-  final Packages packages;
-  final List<PackageContext> children;
-  _PackageContext(this.directory, this.packages, List<PackageContext> children)
-      : children = List<PackageContext>.unmodifiable(children);
-
-  Map<Directory, Packages> asMap() {
-    var result = HashMap<Directory, Packages>();
-    void recurse(_PackageContext current) {
-      result[current.directory] = current.packages;
-      for (var child in current.children) {
-        recurse(child);
-      }
-    }
-
-    recurse(this);
-    return result;
-  }
-
-  PackageContext operator [](Directory directory) {
-    var path = directory.path;
-    if (!path.startsWith(this.directory.path)) {
-      throw ArgumentError("Not inside $path: $directory");
-    }
-    var current = this;
-    // The current path is know to agree with directory until deltaIndex.
-    var deltaIndex = current.directory.path.length;
-    List children = current.children;
-    var i = 0;
-    while (i < children.length) {
-      // TODO(lrn): Sort children and use binary search.
-      _PackageContext child = children[i];
-      var childPath = child.directory.path;
-      if (_stringsAgree(path, childPath, deltaIndex, childPath.length)) {
-        deltaIndex = childPath.length;
-        if (deltaIndex == path.length) {
-          return child;
-        }
-        current = child;
-        children = current.children;
-        i = 0;
-        continue;
-      }
-      i++;
-    }
-    return current;
-  }
-
-  static bool _stringsAgree(String a, String b, int start, int end) {
-    if (a.length < end || b.length < end) return false;
-    for (var i = start; i < end; i++) {
-      if (a.codeUnitAt(i) != b.codeUnitAt(i)) return false;
-    }
-    return true;
-  }
-}
-
-Packages _loadPackagesFile(File file) {
-  var uri = Uri.file(file.path);
-  var bytes = file.readAsBytesSync();
-  var map = pkgfile.parse(bytes, uri);
-  return MapPackages(map);
-}
diff --git a/lib/package_config.dart b/lib/package_config.dart
index 1113ac8..3dfd8ef 100644
--- a/lib/package_config.dart
+++ b/lib/package_config.dart
@@ -3,8 +3,11 @@
 // BSD-style license that can be found in the LICENSE file.
 
 /// A package configuration is a way to assign file paths to package URIs,
-/// and vice-versa,
-library package_config.package_config_discovery;
+/// and vice-versa.
+///
+/// This package provides functionality to find, read and write package
+/// configurations in the [specified format](https://github.com/dart-lang/language/blob/master/accepted/future-releases/language-versioning/package-config-file-v2.md).
+library package_config.package_config;
 
 import "dart:io" show File, Directory;
 import "dart:typed_data" show Uint8List;
@@ -39,7 +42,7 @@
 /// a valid configuration from the invalid configuration file.
 /// If no [onError] is provided, errors are thrown immediately.
 Future<PackageConfig> loadPackageConfig(File file,
-        {bool preferNewest = true, void onError(Object error)}) =>
+        {bool preferNewest = true, void onError(Object error)?}) =>
     readAnyConfigFile(file, preferNewest, onError ?? throwError);
 
 /// Reads a specific package configuration URI.
@@ -84,9 +87,9 @@
 /// a valid configuration from the invalid configuration file.
 /// If no [onError] is provided, errors are thrown immediately.
 Future<PackageConfig> loadPackageConfigUri(Uri file,
-        {Future<Uint8List /*?*/ > loader(Uri uri) /*?*/,
+        {Future<Uint8List?> loader(Uri uri)?,
         bool preferNewest = true,
-        void onError(Object error)}) =>
+        void onError(Object error)?}) =>
     readAnyConfigFileUri(file, loader, onError ?? throwError, preferNewest);
 
 /// Finds a package configuration relative to [directory].
@@ -109,8 +112,8 @@
 /// If no [onError] is provided, errors are thrown immediately.
 ///
 /// Returns `null` if no configuration file is found.
-Future<PackageConfig> findPackageConfig(Directory directory,
-        {bool recurse = true, void onError(Object error)}) =>
+Future<PackageConfig?> findPackageConfig(Directory directory,
+        {bool recurse = true, void onError(Object error)?}) =>
     discover.findPackageConfig(directory, recurse, onError ?? throwError);
 
 /// Finds a package configuration relative to [location].
@@ -153,10 +156,10 @@
 /// If no [onError] is provided, errors are thrown immediately.
 ///
 /// Returns `null` if no configuration file is found.
-Future<PackageConfig> findPackageConfigUri(Uri location,
+Future<PackageConfig?> findPackageConfigUri(Uri location,
         {bool recurse = true,
-        Future<Uint8List /*?*/ > loader(Uri uri),
-        void onError(Object error)}) =>
+        Future<Uint8List?> loader(Uri uri)?,
+        void onError(Object error)?}) =>
     discover.findPackageConfigUri(
         location, loader, onError ?? throwError, recurse);
 
diff --git a/lib/packages.dart b/lib/packages.dart
deleted file mode 100644
index 203f32f..0000000
--- a/lib/packages.dart
+++ /dev/null
@@ -1,96 +0,0 @@
-// Copyright (c) 2015, 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.
-
-@Deprecated("Use the package_config.json based API")
-library package_config.packages;
-
-import "src/packages_impl.dart";
-
-/// A package resolution strategy.
-///
-/// Allows converting a `package:` URI to a different kind of URI.
-///
-/// May also allow listing the available packages and converting
-/// to a `Map<String, Uri>` that gives the base location of each available
-/// package. In some cases there is no way to find the available packages,
-/// in which case [packages] and [asMap] will throw if used.
-/// One such case is if the packages are resolved relative to a
-/// `packages/` directory available over HTTP.
-@Deprecated("Use the package_config.json based API")
-abstract class Packages {
-  /// A [Packages] resolver containing no packages.
-  ///
-  /// This constant object is returned by [find] above if no
-  /// package resolution strategy is found.
-  static const Packages noPackages = NoPackages();
-
-  /// Resolve a package URI into a non-package URI.
-  ///
-  /// Translates a `package:` URI, according to the package resolution
-  /// strategy, into a URI that can be loaded.
-  /// By default, only `file`, `http` and `https` URIs are returned.
-  /// Custom `Packages` objects may return other URIs.
-  ///
-  /// If resolution fails because a package with the requested package name
-  /// is not available, the [notFound] function is called.
-  /// If no `notFound` function is provided, it defaults to throwing an error.
-  ///
-  /// The [packageUri] must be a valid package URI.
-  Uri resolve(Uri packageUri, {Uri notFound(Uri packageUri)});
-
-  /// Return the names of the available packages.
-  ///
-  /// Returns an iterable that allows iterating the names of available packages.
-  ///
-  /// Some `Packages` objects are unable to find the package names,
-  /// and getting `packages` from such a `Packages` object will throw.
-  Iterable<String> get packages;
-
-  /// Retrieve metadata associated with a package.
-  ///
-  /// Metadata have string keys and values, and are looked up by key.
-  ///
-  /// Returns `null` if the argument is not a valid package name,
-  /// or if the package is not one of the packages configured by
-  /// this packages object, or if the package does not have associated
-  /// metadata with the provided [key].
-  ///
-  /// Not all `Packages` objects can support metadata.
-  /// Those will always return `null`.
-  String packageMetadata(String packageName, String key);
-
-  /// Retrieve metadata associated with a library.
-  ///
-  /// If [libraryUri] is a `package:` URI, the returned value
-  /// is the same that would be returned by [packageMetadata] with
-  /// the package's name and the same key.
-  ///
-  /// If [libraryUri] is not a `package:` URI, and this [Packages]
-  /// object has a [defaultPackageName], then the [key] is looked
-  /// up on the default package instead.
-  ///
-  /// Otherwise the result is `null`.
-  String libraryMetadata(Uri libraryUri, String key);
-
-  /// Return the names-to-base-URI mapping of the available packages.
-  ///
-  /// Returns a map from package name to a base URI.
-  /// The [resolve] method will resolve a package URI with a specific package
-  /// name to a path extending the base URI that this map gives for that
-  /// package name.
-  ///
-  /// Some `Packages` objects are unable to find the package names,
-  /// and calling `asMap` on such a `Packages` object will throw.
-  Map<String, Uri> asMap();
-
-  /// The name of the "default package".
-  ///
-  /// A default package is a package that *non-package* libraries
-  /// may be considered part of for some purposes.
-  ///
-  /// The value is `null` if there is no default package.
-  /// Not all implementations of [Packages] supports a default package,
-  /// and will always have a `null` value for those.
-  String get defaultPackageName;
-}
diff --git a/lib/packages_file.dart b/lib/packages_file.dart
deleted file mode 100644
index ef0b0b3..0000000
--- a/lib/packages_file.dart
+++ /dev/null
@@ -1,232 +0,0 @@
-// Copyright (c) 2015, 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.
-
-@Deprecated("Use the package_config.json based API")
-library package_config.packages_file;
-
-import "package:charcode/ascii.dart";
-
-import "src/util.dart" show isValidPackageName;
-
-/// Parses a `.packages` file into a map from package name to base URI.
-///
-/// The [source] is the byte content of a `.packages` file, assumed to be
-/// UTF-8 encoded. In practice, all significant parts of the file must be ASCII,
-/// so Latin-1 or Windows-1252 encoding will also work fine.
-///
-/// If the file content is available as a string, its [String.codeUnits] can
-/// be used as the `source` argument of this function.
-///
-/// The [baseLocation] is used as a base URI to resolve all relative
-/// URI references against.
-/// If the content was read from a file, `baseLocation` should be the
-/// location of that file.
-///
-/// If [allowDefaultPackage] is set to true, an entry with an empty package name
-/// is accepted. This entry does not correspond to a package, but instead
-/// represents a *default package* which non-package libraries may be considered
-/// part of in some cases. The value of that entry must be a valid package name.
-///
-/// Returns a simple mapping from package name to package location.
-/// If default package is allowed, the map maps the empty string to the default package's name.
-Map<String, Uri> parse(List<int> source, Uri baseLocation,
-    {bool allowDefaultPackage = false}) {
-  var index = 0;
-  var result = <String, Uri>{};
-  while (index < source.length) {
-    var isComment = false;
-    var start = index;
-    var separatorIndex = -1;
-    var end = source.length;
-    var char = source[index++];
-    if (char == $cr || char == $lf) {
-      continue;
-    }
-    if (char == $colon) {
-      if (!allowDefaultPackage) {
-        throw FormatException("Missing package name", source, index - 1);
-      }
-      separatorIndex = index - 1;
-    }
-    isComment = char == $hash;
-    while (index < source.length) {
-      char = source[index++];
-      if (char == $colon && separatorIndex < 0) {
-        separatorIndex = index - 1;
-      } else if (char == $cr || char == $lf) {
-        end = index - 1;
-        break;
-      }
-    }
-    if (isComment) continue;
-    if (separatorIndex < 0) {
-      throw FormatException("No ':' on line", source, index - 1);
-    }
-    var packageName = String.fromCharCodes(source, start, separatorIndex);
-    if (packageName.isEmpty
-        ? !allowDefaultPackage
-        : !isValidPackageName(packageName)) {
-      throw FormatException("Not a valid package name", packageName, 0);
-    }
-    var packageValue = String.fromCharCodes(source, separatorIndex + 1, end);
-    Uri packageLocation;
-    if (packageName.isEmpty) {
-      if (!isValidPackageName(packageValue)) {
-        throw FormatException(
-            "Default package entry value is not a valid package name");
-      }
-      packageLocation = Uri(path: packageValue);
-    } else {
-      packageLocation = baseLocation.resolve(packageValue);
-      if (!packageLocation.path.endsWith('/')) {
-        packageLocation =
-            packageLocation.replace(path: packageLocation.path + "/");
-      }
-    }
-    if (result.containsKey(packageName)) {
-      if (packageName.isEmpty) {
-        throw FormatException(
-            "More than one default package entry", source, start);
-      }
-      throw FormatException("Same package name occured twice", source, start);
-    }
-    result[packageName] = packageLocation;
-  }
-  return result;
-}
-
-/// Writes the mapping to a [StringSink].
-///
-/// If [comment] is provided, the output will contain this comment
-/// with `# ` in front of each line.
-/// Lines are defined as ending in line feed (`'\n'`). If the final
-/// line of the comment doesn't end in a line feed, one will be added.
-///
-/// If [baseUri] is provided, package locations will be made relative
-/// to the base URI, if possible, before writing.
-///
-/// If [allowDefaultPackage] is `true`, the [packageMapping] may contain an
-/// empty string mapping to the _default package name_.
-///
-/// All the keys of [packageMapping] must be valid package names,
-/// and the values must be URIs that do not have the `package:` scheme.
-void write(StringSink output, Map<String, Uri> packageMapping,
-    {Uri baseUri, String comment, bool allowDefaultPackage = false}) {
-  ArgumentError.checkNotNull(allowDefaultPackage, 'allowDefaultPackage');
-
-  if (baseUri != null && !baseUri.isAbsolute) {
-    throw ArgumentError.value(baseUri, "baseUri", "Must be absolute");
-  }
-
-  if (comment != null) {
-    var lines = comment.split('\n');
-    if (lines.last.isEmpty) lines.removeLast();
-    for (var commentLine in lines) {
-      output.write('# ');
-      output.writeln(commentLine);
-    }
-  } else {
-    output.write("# generated by package:package_config at ");
-    output.write(DateTime.now());
-    output.writeln();
-  }
-
-  packageMapping.forEach((String packageName, Uri uri) {
-    // If [packageName] is empty then [uri] is the _default package name_.
-    if (allowDefaultPackage && packageName.isEmpty) {
-      final defaultPackageName = uri.toString();
-      if (!isValidPackageName(defaultPackageName)) {
-        throw ArgumentError.value(
-          defaultPackageName,
-          'defaultPackageName',
-          '"$defaultPackageName" is not a valid package name',
-        );
-      }
-      output.write(':');
-      output.write(defaultPackageName);
-      output.writeln();
-      return;
-    }
-    // Validate packageName.
-    if (!isValidPackageName(packageName)) {
-      throw ArgumentError('"$packageName" is not a valid package name');
-    }
-    if (uri.scheme == "package") {
-      throw ArgumentError.value(
-          "Package location must not be a package: URI", uri.toString());
-    }
-    output.write(packageName);
-    output.write(':');
-    // If baseUri provided, make uri relative.
-    if (baseUri != null) {
-      uri = _relativize(uri, baseUri);
-    }
-    if (!uri.path.endsWith('/')) {
-      uri = uri.replace(path: uri.path + '/');
-    }
-    output.write(uri);
-    output.writeln();
-  });
-}
-
-/// Attempts to return a relative URI for [uri].
-///
-/// The result URI satisfies `baseUri.resolveUri(result) == uri`,
-/// but may be relative.
-/// The `baseUri` must be absolute.
-Uri _relativize(Uri uri, Uri baseUri) {
-  assert(baseUri.isAbsolute);
-  if (uri.hasQuery || uri.hasFragment) {
-    uri = Uri(
-        scheme: uri.scheme,
-        userInfo: uri.hasAuthority ? uri.userInfo : null,
-        host: uri.hasAuthority ? uri.host : null,
-        port: uri.hasAuthority ? uri.port : null,
-        path: uri.path);
-  }
-
-  // Already relative. We assume the caller knows what they are doing.
-  if (!uri.isAbsolute) return uri;
-
-  if (baseUri.scheme != uri.scheme) {
-    return uri;
-  }
-
-  // If authority differs, we could remove the scheme, but it's not worth it.
-  if (uri.hasAuthority != baseUri.hasAuthority) return uri;
-  if (uri.hasAuthority) {
-    if (uri.userInfo != baseUri.userInfo ||
-        uri.host.toLowerCase() != baseUri.host.toLowerCase() ||
-        uri.port != baseUri.port) {
-      return uri;
-    }
-  }
-
-  baseUri = baseUri.normalizePath();
-  var base = baseUri.pathSegments.toList();
-  if (base.isNotEmpty) {
-    base = List<String>.from(base)..removeLast();
-  }
-  uri = uri.normalizePath();
-  var target = uri.pathSegments.toList();
-  if (target.isNotEmpty && target.last.isEmpty) target.removeLast();
-  var index = 0;
-  while (index < base.length && index < target.length) {
-    if (base[index] != target[index]) {
-      break;
-    }
-    index++;
-  }
-  if (index == base.length) {
-    if (index == target.length) {
-      return Uri(path: "./");
-    }
-    return Uri(path: target.skip(index).join('/'));
-  } else if (index > 0) {
-    return Uri(
-        path: '../' * (base.length - index) + target.skip(index).join('/'));
-  } else {
-    return uri;
-  }
-}
diff --git a/lib/src/discovery.dart b/lib/src/discovery.dart
index 8ac6a01..a3e01d7 100644
--- a/lib/src/discovery.dart
+++ b/lib/src/discovery.dart
@@ -32,7 +32,7 @@
 /// If any of these tests succeed, a `PackageConfig` class is returned.
 /// Returns `null` if no configuration was found. If a configuration
 /// is needed, then the caller can supply [PackageConfig.empty].
-Future<PackageConfig /*?*/ > findPackageConfig(
+Future<PackageConfig?> findPackageConfig(
     Directory baseDirectory, bool recursive, void onError(Object error)) async {
   var directory = baseDirectory;
   if (!directory.isAbsolute) directory = directory.absolute;
@@ -53,10 +53,10 @@
 }
 
 /// Similar to [findPackageConfig] but based on a URI.
-Future<PackageConfig /*?*/ > findPackageConfigUri(
+Future<PackageConfig?> findPackageConfigUri(
     Uri location,
-    Future<Uint8List /*?*/ > loader(Uri uri) /*?*/,
-    void onError(Object error) /*?*/,
+    Future<Uint8List?> loader(Uri uri)?,
+    void onError(Object error),
     bool recursive) async {
   if (location.isScheme("package")) {
     onError(PackageConfigArgumentError(
@@ -102,7 +102,7 @@
 /// If [onError] is supplied, parsing errors are reported using that, and
 /// a best-effort attempt is made to return a package configuration.
 /// This may be the empty package configuration.
-Future<PackageConfig /*?*/ > findPackagConfigInDirectory(
+Future<PackageConfig?> findPackagConfigInDirectory(
     Directory directory, void onError(Object error)) async {
   var packageConfigFile = await checkForPackageConfigJsonFile(directory);
   if (packageConfigFile != null) {
@@ -115,7 +115,7 @@
   return null;
 }
 
-Future<File> /*?*/ checkForPackageConfigJsonFile(Directory directory) async {
+Future<File?> checkForPackageConfigJsonFile(Directory directory) async {
   assert(directory.isAbsolute);
   var file =
       File(pathJoin(directory.path, ".dart_tool", "package_config.json"));
@@ -123,7 +123,7 @@
   return null;
 }
 
-Future<File /*?*/ > checkForDotPackagesFile(Directory directory) async {
+Future<File?> checkForDotPackagesFile(Directory directory) async {
   var file = File(pathJoin(directory.path, ".packages"));
   if (await file.exists()) return file;
   return null;
diff --git a/lib/src/errors.dart b/lib/src/errors.dart
index c973617..f351571 100644
--- a/lib/src/errors.dart
+++ b/lib/src/errors.dart
@@ -12,7 +12,7 @@
 
 class PackageConfigArgumentError extends ArgumentError
     implements PackageConfigError {
-  PackageConfigArgumentError(Object /*?*/ value, String name, String message)
+  PackageConfigArgumentError(Object? value, String name, String message)
       : super.value(value, name, message);
 
   PackageConfigArgumentError.from(ArgumentError error)
@@ -21,8 +21,7 @@
 
 class PackageConfigFormatException extends FormatException
     implements PackageConfigError {
-  PackageConfigFormatException(String message, Object /*?*/ source,
-      [int /*?*/ offset])
+  PackageConfigFormatException(String message, Object? source, [int? offset])
       : super(message, source, offset);
 
   PackageConfigFormatException.from(FormatException exception)
@@ -30,4 +29,4 @@
 }
 
 /// The default `onError` handler.
-void /*Never*/ throwError(Object error) => throw error;
+Never throwError(Object error) => throw error;
diff --git a/lib/src/package_config.dart b/lib/src/package_config.dart
index 30c758a..63d01ea 100644
--- a/lib/src/package_config.dart
+++ b/lib/src/package_config.dart
@@ -50,7 +50,7 @@
   /// [PackageConfig.extraData] of the created configuration.
   ///
   /// The version of the resulting configuration is always [maxVersion].
-  factory PackageConfig(Iterable<Package> packages, {dynamic extraData}) =>
+  factory PackageConfig(Iterable<Package> packages, {Object? extraData}) =>
       SimplePackageConfig(maxVersion, packages, extraData);
 
   /// Parses a package configuration file.
@@ -67,10 +67,10 @@
   /// the configuration are reported by calling [onError] instead of
   /// throwing, and parser makes a *best effort* attempt to continue
   /// despite the error. The input must still be valid JSON.
-  /// The result may be a [PackageConfig.empty] if there is no way to
+  /// The result may be [PackageConfig.empty] if there is no way to
   /// extract useful information from the bytes.
   static PackageConfig parseBytes(Uint8List bytes, Uri baseUri,
-          {void onError(Object error)}) =>
+          {void onError(Object error)?}) =>
       parsePackageConfigBytes(bytes, baseUri, onError ?? throwError);
 
   /// Parses a package configuration file.
@@ -87,10 +87,10 @@
   /// the configuration are reported by calling [onError] instead of
   /// throwing, and parser makes a *best effort* attempt to continue
   /// despite the error. The input must still be valid JSON.
-  /// The result may be a [PackageConfig.empty] if there is no way to
+  /// The result may be [PackageConfig.empty] if there is no way to
   /// extract useful information from the bytes.
   static PackageConfig parseString(String configuration, Uri baseUri,
-          {void onError(Object error)}) =>
+          {void onError(Object error)?}) =>
       parsePackageConfigString(configuration, baseUri, onError ?? throwError);
 
   /// Parses the JSON data of a package configuration file.
@@ -108,10 +108,10 @@
   /// the configuration are reported by calling [onError] instead of
   /// throwing, and parser makes a *best effort* attempt to continue
   /// despite the error. The input must still be valid JSON.
-  /// The result may be a [PackageConfig.empty] if there is no way to
+  /// The result may be [PackageConfig.empty] if there is no way to
   /// extract useful information from the bytes.
-  static PackageConfig parseJson(dynamic jsonData, Uri baseUri,
-          {void onError(Object error)}) =>
+  static PackageConfig parseJson(Object? jsonData, Uri baseUri,
+          {void onError(Object error)?}) =>
       parsePackageConfigJson(jsonData, baseUri, onError ?? throwError);
 
   /// Writes a configuration file for this configuration on [output].
@@ -119,7 +119,7 @@
   /// If [baseUri] is provided, URI references in the generated file
   /// will be made relative to [baseUri] where possible.
   static void writeBytes(PackageConfig configuration, Sink<Uint8List> output,
-      [Uri /*?*/ baseUri]) {
+      [Uri? baseUri]) {
     writePackageConfigJsonUtf8(configuration, baseUri, output);
   }
 
@@ -128,7 +128,7 @@
   /// If [baseUri] is provided, URI references in the generated file
   /// will be made relative to [baseUri] where possible.
   static void writeString(PackageConfig configuration, StringSink output,
-      [Uri /*?*/ baseUri]) {
+      [Uri? baseUri]) {
     writePackageConfigJsonString(configuration, baseUri, output);
   }
 
@@ -136,8 +136,8 @@
   ///
   /// If [baseUri] is provided, URI references in the generated data
   /// will be made relative to [baseUri] where possible.
-  static Map<String, dynamic> toJson(PackageConfig configuration,
-          [Uri /*?*/ baseUri]) =>
+  static Map<String, Object?> toJson(PackageConfig configuration,
+          [Uri? baseUri]) =>
       packageConfigToJson(configuration, baseUri);
 
   /// The configuration version number.
@@ -162,7 +162,7 @@
   /// Returns the [Package] fron [packages] with [packageName] as
   /// [Package.name]. Returns `null` if the package is not available in the
   /// current configuration.
-  Package /*?*/ operator [](String packageName);
+  Package? operator [](String packageName);
 
   /// Provides the associated package for a specific [file] (or directory).
   ///
@@ -171,7 +171,7 @@
   /// of the [file]'s location.
   ///
   /// Returns `null` if the file does not belong to any package.
-  Package /*?*/ packageOf(Uri file);
+  Package? packageOf(Uri file);
 
   /// Resolves a `package:` URI to a non-package URI
   ///
@@ -188,7 +188,7 @@
   /// in this package configuration.
   /// Returns the remaining path of the package URI resolved relative to the
   /// [Package.packageUriRoot] of the corresponding package.
-  Uri /*?*/ resolve(Uri packageUri);
+  Uri? resolve(Uri packageUri);
 
   /// The package URI which resolves to [nonPackageUri].
   ///
@@ -199,14 +199,14 @@
   ///
   /// Returns a package URI which [resolve] will convert to [nonPackageUri],
   /// if any such URI exists. Returns `null` if no such package URI exists.
-  Uri /*?*/ toPackageUri(Uri nonPackageUri);
+  Uri? toPackageUri(Uri nonPackageUri);
 
   /// Extra data associated with the package configuration.
   ///
   /// The data may be in any format, depending on who introduced it.
   /// The standard `packjage_config.json` file storage will only store
   /// JSON-like list/map data structures.
-  dynamic get extraData;
+  Object? get extraData;
 }
 
 /// Configuration data for a single package.
@@ -227,11 +227,11 @@
   /// If [extraData] is supplied, it will be available as the
   /// [Package.extraData] of the created package.
   factory Package(String name, Uri root,
-          {Uri /*?*/ packageUriRoot,
-          LanguageVersion /*?*/ languageVersion,
-          dynamic extraData}) =>
+          {Uri? packageUriRoot,
+          LanguageVersion? languageVersion,
+          Object? extraData}) =>
       SimplePackage.validate(
-          name, root, packageUriRoot, languageVersion, extraData, throwError);
+          name, root, packageUriRoot, languageVersion, extraData, throwError)!;
 
   /// The package-name of the package.
   String get name;
@@ -263,14 +263,18 @@
   /// Dart files in the package.
   /// A package version is defined by two non-negative numbers,
   /// the *major* and *minor* version numbers.
-  LanguageVersion /*?*/ get languageVersion;
+  ///
+  /// A package may have no language version associated with it
+  /// in the package configuration, in which case tools should
+  /// use a default behavior for the package.
+  LanguageVersion? get languageVersion;
 
   /// Extra data associated with the specific package.
   ///
   /// The data may be in any format, depending on who introduced it.
-  /// The standard `packjage_config.json` file storage will only store
+  /// The standard `package_config.json` file storage will only store
   /// JSON-like list/map data structures.
-  dynamic get extraData;
+  Object? get extraData;
 }
 
 /// A language version.
@@ -307,7 +311,7 @@
   /// If [onError] is not supplied, it defaults to throwing the exception.
   /// If the call does not throw, then an [InvalidLanguageVersion] is returned
   /// containing the original [source].
-  static LanguageVersion parse(String source, {void onError(Object error)}) =>
+  static LanguageVersion parse(String source, {void onError(Object error)?}) =>
       parseLanguageVersion(source, onError ?? throwError);
 
   /// The major language version.
diff --git a/lib/src/package_config_impl.dart b/lib/src/package_config_impl.dart
index 9e23af0..5c6b7f7 100644
--- a/lib/src/package_config_impl.dart
+++ b/lib/src/package_config_impl.dart
@@ -14,10 +14,10 @@
   final int version;
   final Map<String, Package> _packages;
   final PackageTree _packageTree;
-  final dynamic extraData;
+  final Object? extraData;
 
   factory SimplePackageConfig(int version, Iterable<Package> packages,
-      [dynamic extraData, void onError(Object error)]) {
+      [Object? extraData, void onError(Object error)?]) {
     onError ??= throwError;
     var validVersion = _validateVersion(version, onError);
     var sortedPackages = [...packages]..sort(_compareRoot);
@@ -53,11 +53,7 @@
     var packageNames = <String>{};
     var tree = MutablePackageTree();
     for (var originalPackage in packages) {
-      if (originalPackage == null) {
-        onError(ArgumentError.notNull("element of packages"));
-        continue;
-      }
-      SimplePackage package;
+      SimplePackage? package;
       if (originalPackage is! SimplePackage) {
         // SimplePackage validates these properties.
         package = SimplePackage.validate(
@@ -68,7 +64,7 @@
             originalPackage.extraData, (error) {
           if (error is PackageConfigArgumentError) {
             onError(PackageConfigArgumentError(packages, "packages",
-                "Package ${package.name}: ${error.message}"));
+                "Package ${package!.name}: ${error.message}"));
           } else {
             onError(error);
           }
@@ -92,7 +88,7 @@
             onError(PackageConfigArgumentError(
                 originalPackages,
                 "packages",
-                "Packages ${package.name} and ${existingPackage.name} "
+                "Packages ${package!.name} and ${existingPackage.name} "
                     "have the same root directory: ${package.root}.\n"));
           } else {
             assert(error.isPackageRootConflict);
@@ -100,7 +96,7 @@
             onError(PackageConfigArgumentError(
                 originalPackages,
                 "packages",
-                "Package ${package.name} is inside the package URI root of "
+                "Package ${package!.name} is inside the package URI root of "
                     "package ${existingPackage.name}.\n"
                     "${existingPackage.name} URI root: "
                     "${existingPackage.packageUriRoot}\n"
@@ -117,7 +113,7 @@
 
   Iterable<Package> get packages => _packages.values;
 
-  Package /*?*/ operator [](String packageName) => _packages[packageName];
+  Package? operator [](String packageName) => _packages[packageName];
 
   /// Provides the associated package for a specific [file] (or directory).
   ///
@@ -125,15 +121,15 @@
   /// That is, the [Package.rootUri] directory is a parent directory
   /// of the [file]'s location.
   /// Returns `null` if the file does not belong to any package.
-  Package /*?*/ packageOf(Uri file) => _packageTree.packageOf(file);
+  Package? packageOf(Uri file) => _packageTree.packageOf(file);
 
-  Uri /*?*/ resolve(Uri packageUri) {
+  Uri? resolve(Uri packageUri) {
     var packageName = checkValidPackageUri(packageUri, "packageUri");
-    return _packages[packageName]?.packageUriRoot?.resolveUri(
+    return _packages[packageName]?.packageUriRoot.resolveUri(
         Uri(path: packageUri.path.substring(packageName.length + 1)));
   }
 
-  Uri /*?*/ toPackageUri(Uri nonPackageUri) {
+  Uri? toPackageUri(Uri nonPackageUri) {
     if (nonPackageUri.isScheme("package")) {
       throw PackageConfigArgumentError(
           nonPackageUri, "nonPackageUri", "Must not be a package URI");
@@ -161,8 +157,8 @@
   final String name;
   final Uri root;
   final Uri packageUriRoot;
-  final LanguageVersion /*?*/ languageVersion;
-  final dynamic extraData;
+  final LanguageVersion? languageVersion;
+  final Object? extraData;
 
   SimplePackage._(this.name, this.root, this.packageUriRoot,
       this.languageVersion, this.extraData);
@@ -182,12 +178,12 @@
   ///
   /// Returns `null` if the input is invalid and an approximately valid package
   /// cannot be salvaged from the input.
-  static SimplePackage /*?*/ validate(
+  static SimplePackage? validate(
       String name,
       Uri root,
-      Uri packageUriRoot,
-      LanguageVersion /*?*/ languageVersion,
-      dynamic extraData,
+      Uri? packageUriRoot,
+      LanguageVersion? languageVersion,
+      Object? extraData,
       void onError(Object error)) {
     var fatalError = false;
     var invalidIndex = checkPackageName(name);
@@ -244,7 +240,7 @@
 /// Reports a format exception on [onError] if not, or if the numbers
 /// are too large (at most 32-bit signed integers).
 LanguageVersion parseLanguageVersion(
-    String source, void onError(Object error)) {
+    String? source, void onError(Object error)) {
   var index = 0;
   // Reads a positive decimal numeral. Returns the value of the numeral,
   // or a negative number in case of an error.
@@ -254,7 +250,7 @@
   // It is a recoverable error if the numeral starts with leading zeros.
   int readNumeral() {
     const maxValue = 0x7FFFFFFF;
-    if (index == source.length) {
+    if (index == source!.length) {
       onError(PackageConfigFormatException("Missing number", source, index));
       return -1;
     }
@@ -291,7 +287,7 @@
   if (major < 0) {
     return SimpleInvalidLanguageVersion(source);
   }
-  if (index == source.length || source.codeUnitAt(index) != $dot) {
+  if (index == source!.length || source.codeUnitAt(index) != $dot) {
     onError(PackageConfigFormatException("Missing '.'", source, index));
     return SimpleInvalidLanguageVersion(source);
   }
@@ -319,7 +315,7 @@
 class SimpleLanguageVersion extends _SimpleLanguageVersionBase {
   final int major;
   final int minor;
-  String /*?*/ _source;
+  String? _source;
   SimpleLanguageVersion(this.major, this.minor, this._source);
 
   bool operator ==(Object other) =>
@@ -332,17 +328,17 @@
 
 class SimpleInvalidLanguageVersion extends _SimpleLanguageVersionBase
     implements InvalidLanguageVersion {
-  final String _source;
+  final String? _source;
   SimpleInvalidLanguageVersion(this._source);
   int get major => -1;
   int get minor => -1;
 
-  String toString() => _source;
+  String toString() => _source!;
 }
 
 abstract class PackageTree {
   Iterable<Package> get allPackages;
-  SimplePackage /*?*/ packageOf(Uri file);
+  SimplePackage? packageOf(Uri file);
 }
 
 /// Packages of a package configuration ordered by root path.
@@ -373,12 +369,17 @@
   /// Indexed by [Package.name].
   /// If a package has no nested packages (which is most often the case),
   /// there is no tree object associated with it.
-  Map<String, MutablePackageTree /*?*/ > /*?*/ _packageChildren;
+  Map<String, MutablePackageTree>? _packageChildren;
 
   Iterable<Package> get allPackages sync* {
-    for (var package in packages) yield package;
-    if (_packageChildren != null) {
-      for (var tree in _packageChildren.values) yield* tree.allPackages;
+    for (var package in packages) {
+      yield package;
+    }
+    var children = _packageChildren;
+    if (children != null) {
+      for (var tree in children.values) {
+        yield* tree.allPackages;
+      }
     }
   }
 
@@ -424,7 +425,7 @@
     packages.add(package);
   }
 
-  SimplePackage /*?*/ packageOf(Uri file) {
+  SimplePackage? packageOf(Uri file) {
     return findPackageOf(0, file.toString());
   }
 
@@ -434,7 +435,7 @@
   ///
   /// Assumes the first [start] characters of path agrees with all
   /// the packages at this level of the tree.
-  SimplePackage /*?*/ findPackageOf(int start, String path) {
+  SimplePackage? findPackageOf(int start, String path) {
     for (var childPackage in packages) {
       var childPath = childPackage.root.toString();
       if (_beginsWith(start, childPath, path)) {
@@ -447,14 +448,9 @@
             _beginsWith(childPathLength, uriRoot, path)) {
           return childPackage;
         }
-        // Otherwise add [package] as child of [childPackage].
-        // TODO(lrn): When NNBD comes, convert to:
-        // return _packageChildren?[childPackage.name]
-        //     ?.packageOf(childPathLength, path) ?? childPackage;
-        if (_packageChildren == null) return childPackage;
-        var childTree = _packageChildren[childPackage.name];
-        if (childTree == null) return childPackage;
-        return childTree.findPackageOf(childPathLength, path) ?? childPackage;
+        return _packageChildren?[childPackage.name]
+                ?.findPackageOf(childPathLength, path) ??
+            childPackage;
       }
     }
     return null;
@@ -474,7 +470,7 @@
 
   Iterable<Package> get allPackages => const Iterable<Package>.empty();
 
-  SimplePackage packageOf(Uri file) => null;
+  SimplePackage? packageOf(Uri file) => null;
 }
 
 /// Checks whether [longerPath] begins with [parentPath].
diff --git a/lib/src/package_config_io.dart b/lib/src/package_config_io.dart
index bfbb1e3..d3e59be 100644
--- a/lib/src/package_config_io.dart
+++ b/lib/src/package_config_io.dart
@@ -15,6 +15,21 @@
 import "util.dart";
 import "util_io.dart";
 
+/// Name of directory where Dart tools store their configuration.
+///
+/// Directory is created in the package root directory.
+const dartToolDirName = ".dart_tool";
+
+/// Name of file containing new package configuration data.
+///
+/// File is stored in the dart tool directory.
+const packageConfigFileName = "package_config.json";
+
+/// Name of file containing legacy package configuration data.
+///
+/// File is stored in the package root directory.
+const packagesFileName = ".packages";
+
 /// Reads a package configuration file.
 ///
 /// Detects whether the [file] is a version one `.packages` file or
@@ -29,9 +44,9 @@
 /// The file must exist and be a normal file.
 Future<PackageConfig> readAnyConfigFile(
     File file, bool preferNewest, void onError(Object error)) async {
-  if (preferNewest && fileName(file.path) == ".packages") {
-    var alternateFile =
-        File(pathJoin(dirName(file.path), ".dart_tool", "package_config.json"));
+  if (preferNewest && fileName(file.path) == packagesFileName) {
+    var alternateFile = File(
+        pathJoin(dirName(file.path), dartToolDirName, packageConfigFileName));
     if (alternateFile.existsSync()) {
       return await readPackageConfigJsonFile(alternateFile, onError);
     }
@@ -49,7 +64,7 @@
 /// Like [readAnyConfigFile] but uses a URI and an optional loader.
 Future<PackageConfig> readAnyConfigFileUri(
     Uri file,
-    Future<Uint8List /*?*/ > loader(Uri uri) /*?*/,
+    Future<Uint8List?> loader(Uri uri)?,
     void onError(Object error),
     bool preferNewest) async {
   if (file.isScheme("package")) {
@@ -62,9 +77,9 @@
     }
     loader = defaultLoader;
   }
-  if (preferNewest && file.pathSegments.last == ".packages") {
-    var alternateFile = file.resolve(".dart_tool/package_config.json");
-    Uint8List /*?*/ bytes;
+  if (preferNewest && file.pathSegments.last == packagesFileName) {
+    var alternateFile = file.resolve("$dartToolDirName/$packageConfigFileName");
+    Uint8List? bytes;
     try {
       bytes = await loader(alternateFile);
     } catch (e) {
@@ -75,7 +90,7 @@
       return parsePackageConfigBytes(bytes, alternateFile, onError);
     }
   }
-  Uint8List /*?*/ bytes;
+  Uint8List? bytes;
   try {
     bytes = await loader(file);
   } catch (e) {
@@ -131,15 +146,17 @@
 Future<void> writePackageConfigJsonFile(
     PackageConfig config, Directory targetDirectory) async {
   // Write .dart_tool/package_config.json first.
-  var file =
-      File(pathJoin(targetDirectory.path, ".dart_tool", "package_config.json"));
+  var dartToolDir = Directory(pathJoin(targetDirectory.path, dartToolDirName));
+  await dartToolDir.create(recursive: true);
+  var file = File(pathJoin(dartToolDir.path, packageConfigFileName));
   var baseUri = file.uri;
+
   var sink = file.openWrite(encoding: utf8);
   writePackageConfigJsonUtf8(config, baseUri, sink);
   var doneJson = sink.close();
 
   // Write .packages too.
-  file = File(pathJoin(targetDirectory.path, ".packages"));
+  file = File(pathJoin(targetDirectory.path, packagesFileName));
   baseUri = file.uri;
   sink = file.openWrite(encoding: utf8);
   writeDotPackages(config, baseUri, sink);
diff --git a/lib/src/package_config_json.dart b/lib/src/package_config_json.dart
index 27abf50..25b04c4 100644
--- a/lib/src/package_config_json.dart
+++ b/lib/src/package_config_json.dart
@@ -59,10 +59,10 @@
 
 /// Creates a [PackageConfig] from a parsed JSON-like object structure.
 ///
-/// The [json] argument must be a JSON object (`Map<String, dynamic>`)
+/// The [json] argument must be a JSON object (`Map<String, Object?>`)
 /// containing a `"configVersion"` entry with an integer value in the range
 /// 1 to [PackageConfig.maxVersion],
-/// and with a `"packages"` entry which is a JSON array (`List<dynamic>`)
+/// and with a `"packages"` entry which is a JSON array (`List<Object?>`)
 /// containing JSON objects which each has the following properties:
 ///
 /// * `"name"`: The package name as a string.
@@ -80,7 +80,7 @@
 /// The [baseLocation] is used as base URI to resolve the "rootUri"
 /// URI referencestring.
 PackageConfig parsePackageConfigJson(
-    dynamic json, Uri baseLocation, void onError(Object error)) {
+    Object? json, Uri baseLocation, void onError(Object error)) {
   if (!baseLocation.hasScheme || baseLocation.isScheme("package")) {
     throw PackageConfigArgumentError(baseLocation.toString(), "baseLocation",
         "Must be an absolute non-package: URI");
@@ -97,10 +97,10 @@
     return "object";
   }
 
-  T checkType<T>(dynamic value, String name, [String /*?*/ packageName]) {
+  T? checkType<T>(Object? value, String name, [String? packageName]) {
     if (value is T) return value;
-    // The only types we are called with are [int], [String], [List<dynamic>]
-    // and Map<String, dynamic>. Recognize which to give a better error message.
+    // The only types we are called with are [int], [String], [List<Object?>]
+    // and Map<String, Object?>. Recognize which to give a better error message.
     var message =
         "$name${packageName != null ? " of package $packageName" : ""}"
         " is not a JSON ${typeName<T>()}";
@@ -108,12 +108,12 @@
     return null;
   }
 
-  Package /*?*/ parsePackage(Map<String, dynamic> entry) {
-    String /*?*/ name;
-    String /*?*/ rootUri;
-    String /*?*/ packageUri;
-    String /*?*/ languageVersion;
-    Map<String, dynamic> /*?*/ extraData;
+  Package? parsePackage(Map<String, Object?> entry) {
+    String? name;
+    String? rootUri;
+    String? packageUri;
+    String? languageVersion;
+    Map<String, Object?>? extraData;
     var hasName = false;
     var hasRoot = false;
     var hasVersion = false;
@@ -146,22 +146,22 @@
       onError(PackageConfigFormatException("Missing rootUri entry", entry));
     }
     if (name == null || rootUri == null) return null;
-    var root = baseLocation.resolve(rootUri);
+    var root = baseLocation.resolve(rootUri!);
     if (!root.path.endsWith("/")) root = root.replace(path: root.path + "/");
     var packageRoot = root;
-    if (packageUri != null) packageRoot = root.resolve(packageUri);
+    if (packageUri != null) packageRoot = root.resolve(packageUri!);
     if (!packageRoot.path.endsWith("/")) {
       packageRoot = packageRoot.replace(path: packageRoot.path + "/");
     }
 
-    LanguageVersion /*?*/ version;
+    LanguageVersion? version;
     if (languageVersion != null) {
       version = parseLanguageVersion(languageVersion, onError);
     } else if (hasVersion) {
       version = SimpleInvalidLanguageVersion("invalid");
     }
 
-    return SimplePackage.validate(name, root, packageRoot, version, extraData,
+    return SimplePackage.validate(name!, root, packageRoot, version, extraData,
         (error) {
       if (error is ArgumentError) {
         onError(
@@ -172,22 +172,22 @@
     });
   }
 
-  var map = checkType<Map<String, dynamic>>(json, "value");
+  var map = checkType<Map<String, Object?>>(json, "value");
   if (map == null) return const SimplePackageConfig.empty();
-  Map<String, dynamic> /*?*/ extraData;
-  List<Package> /*?*/ packageList;
-  int /*?*/ configVersion;
+  Map<String, Object?>? extraData;
+  List<Package>? packageList;
+  int? configVersion;
   map.forEach((key, value) {
     switch (key) {
       case _configVersionKey:
         configVersion = checkType<int>(value, _configVersionKey) ?? 2;
         break;
       case _packagesKey:
-        var packageArray = checkType<List<dynamic>>(value, _packagesKey) ?? [];
+        var packageArray = checkType<List<Object?>>(value, _packagesKey) ?? [];
         var packages = <Package>[];
         for (var package in packageArray) {
           var packageMap =
-              checkType<Map<String, dynamic>>(package, "package entry");
+              checkType<Map<String, Object?>>(package, "package entry");
           if (packageMap != null) {
             var entry = parsePackage(packageMap);
             if (entry != null) {
@@ -210,7 +210,7 @@
     onError(PackageConfigFormatException("Missing packages list", json));
     packageList = [];
   }
-  return SimplePackageConfig(configVersion, packageList, extraData, (error) {
+  return SimplePackageConfig(configVersion!, packageList!, extraData, (error) {
     if (error is ArgumentError) {
       onError(PackageConfigFormatException(error.message, error.invalidValue));
     } else {
@@ -222,26 +222,26 @@
 final _jsonUtf8Encoder = JsonUtf8Encoder("  ");
 
 void writePackageConfigJsonUtf8(
-    PackageConfig config, Uri baseUri, Sink<List<int>> output) {
+    PackageConfig config, Uri? baseUri, Sink<List<int>> output) {
   // Can be optimized.
   var data = packageConfigToJson(config, baseUri);
   output.add(_jsonUtf8Encoder.convert(data) as Uint8List);
 }
 
 void writePackageConfigJsonString(
-    PackageConfig config, Uri baseUri, StringSink output) {
+    PackageConfig config, Uri? baseUri, StringSink output) {
   // Can be optimized.
   var data = packageConfigToJson(config, baseUri);
   output.write(JsonEncoder.withIndent("  ").convert(data) as Uint8List);
 }
 
-Map<String, dynamic> packageConfigToJson(PackageConfig config, Uri baseUri) =>
-    <String, dynamic>{
+Map<String, Object?> packageConfigToJson(PackageConfig config, Uri? baseUri) =>
+    <String, Object?>{
       ...?_extractExtraData(config.extraData, _topNames),
       _configVersionKey: PackageConfig.maxVersion,
       _packagesKey: [
         for (var package in config.packages)
-          <String, dynamic>{
+          <String, Object?>{
             _nameKey: package.name,
             _rootUriKey: relativizeUri(package.root, baseUri).toString(),
             if (package.root != package.packageUriRoot)
@@ -259,15 +259,15 @@
 void writeDotPackages(PackageConfig config, Uri baseUri, StringSink output) {
   var extraData = config.extraData;
   // Write .packages too.
-  String /*?*/ comment;
-  if (extraData != null) {
-    String /*?*/ generator = extraData[_generatorKey];
-    if (generator != null) {
-      String /*?*/ generated = extraData[_generatedKey];
-      String /*?*/ generatorVersion = extraData[_generatorVersionKey];
+  String? comment;
+  if (extraData is Map<String, Object?>) {
+    var generator = extraData[_generatorKey];
+    if (generator is String) {
+      var generated = extraData[_generatedKey];
+      var generatorVersion = extraData[_generatorVersionKey];
       comment = "Generated by $generator"
-          "${generatorVersion != null ? " $generatorVersion" : ""}"
-          "${generated != null ? " on $generated" : ""}.";
+          "${generatorVersion is String ? " $generatorVersion" : ""}"
+          "${generated is String ? " on $generated" : ""}.";
     }
   }
   packages_file.write(output, config, baseUri: baseUri, comment: comment);
@@ -277,20 +277,21 @@
 ///
 /// If the value contains any of the [reservedNames] for the current context,
 /// entries with that name in the extra data are dropped.
-Map<String, dynamic> /*?*/ _extractExtraData(
-    dynamic data, Iterable<String> reservedNames) {
-  if (data is Map<String, dynamic>) {
+Map<String, Object?>? _extractExtraData(
+    Object? data, Iterable<String> reservedNames) {
+  if (data is Map<String, Object?>) {
     if (data.isEmpty) return null;
     for (var name in reservedNames) {
       if (data.containsKey(name)) {
-        data = {
+        var filteredData = {
           for (var key in data.keys)
             if (!reservedNames.contains(key)) key: data[key]
         };
-        if (data.isEmpty) return null;
-        for (var value in data.values) {
+        if (filteredData.isEmpty) return null;
+        for (var value in filteredData.values) {
           if (!_validateJson(value)) return null;
         }
+        return filteredData;
       }
     }
     return data;
@@ -299,13 +300,13 @@
 }
 
 /// Checks that the object is a valid JSON-like data structure.
-bool _validateJson(dynamic object) {
+bool _validateJson(Object? object) {
   if (object == null || true == object || false == object) return true;
   if (object is num || object is String) return true;
-  if (object is List<dynamic>) {
+  if (object is List<Object?>) {
     return object.every(_validateJson);
   }
-  if (object is Map<String, dynamic>) {
+  if (object is Map<String, Object?>) {
     return object.values.every(_validateJson);
   }
   return false;
diff --git a/lib/src/packages_file.dart b/lib/src/packages_file.dart
index 184b0dd..071d548 100644
--- a/lib/src/packages_file.dart
+++ b/lib/src/packages_file.dart
@@ -154,7 +154,7 @@
 /// All the keys of [packageMapping] must be valid package names,
 /// and the values must be URIs that do not have the `package:` scheme.
 void write(StringSink output, PackageConfig config,
-    {Uri baseUri, String comment}) {
+    {Uri? baseUri, String? comment}) {
   if (baseUri != null && !baseUri.isAbsolute) {
     throw PackageConfigArgumentError(baseUri, "baseUri", "Must be absolute");
   }
@@ -187,7 +187,7 @@
     output.write(':');
     // If baseUri is provided, make the URI relative to baseUri.
     if (baseUri != null) {
-      uri = relativizeUri(uri, baseUri);
+      uri = relativizeUri(uri, baseUri)!;
     }
     if (!uri.path.endsWith('/')) {
       uri = uri.replace(path: uri.path + '/');
diff --git a/lib/src/packages_impl.dart b/lib/src/packages_impl.dart
deleted file mode 100644
index 19f1039..0000000
--- a/lib/src/packages_impl.dart
+++ /dev/null
@@ -1,128 +0,0 @@
-// Copyright (c) 2015, 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.
-
-/// Implementations of [Packages] that may be used in either server or browser
-/// based applications. For implementations that can only run in the browser,
-/// see [package_config.packages_io_impl].
-@Deprecated("Use the package_config.json based API")
-library package_config.packages_impl;
-
-import "dart:collection" show UnmodifiableMapView;
-
-import "../packages.dart";
-import "util.dart" show checkValidPackageUri;
-
-/// A [Packages] null-object.
-class NoPackages implements Packages {
-  const NoPackages();
-
-  Uri resolve(Uri packageUri, {Uri notFound(Uri packageUri)}) {
-    var packageName = checkValidPackageUri(packageUri, "packageUri");
-    if (notFound != null) return notFound(packageUri);
-    throw ArgumentError.value(
-        packageUri, "packageUri", 'No package named "$packageName"');
-  }
-
-  Iterable<String> get packages => Iterable<String>.empty();
-
-  Map<String, Uri> asMap() => const <String, Uri>{};
-
-  String get defaultPackageName => null;
-
-  String packageMetadata(String packageName, String key) => null;
-
-  String libraryMetadata(Uri libraryUri, String key) => null;
-}
-
-/// Base class for [Packages] implementations.
-///
-/// This class implements the [resolve] method in terms of a private
-/// member
-abstract class PackagesBase implements Packages {
-  Uri resolve(Uri packageUri, {Uri notFound(Uri packageUri)}) {
-    packageUri = packageUri.normalizePath();
-    var packageName = checkValidPackageUri(packageUri, "packageUri");
-    var packageBase = getBase(packageName);
-    if (packageBase == null) {
-      if (notFound != null) return notFound(packageUri);
-      throw ArgumentError.value(
-          packageUri, "packageUri", 'No package named "$packageName"');
-    }
-    var packagePath = packageUri.path.substring(packageName.length + 1);
-    return packageBase.resolve(packagePath);
-  }
-
-  /// Find a base location for a package name.
-  ///
-  /// Returns `null` if no package exists with that name, and that can be
-  /// determined.
-  Uri getBase(String packageName);
-
-  String get defaultPackageName => null;
-
-  String packageMetadata(String packageName, String key) => null;
-
-  String libraryMetadata(Uri libraryUri, String key) => null;
-}
-
-/// A [Packages] implementation based on an existing map.
-class MapPackages extends PackagesBase {
-  final Map<String, Uri> _mapping;
-  MapPackages(this._mapping);
-
-  Uri getBase(String packageName) =>
-      packageName.isEmpty ? null : _mapping[packageName];
-
-  Iterable<String> get packages => _mapping.keys;
-
-  Map<String, Uri> asMap() => UnmodifiableMapView<String, Uri>(_mapping);
-
-  String get defaultPackageName => _mapping[""]?.toString();
-
-  String packageMetadata(String packageName, String key) {
-    if (packageName.isEmpty) return null;
-    var uri = _mapping[packageName];
-    if (uri == null || !uri.hasFragment) return null;
-    // This can be optimized, either by caching the map or by
-    // parsing incrementally instead of parsing the entire fragment.
-    return Uri.splitQueryString(uri.fragment)[key];
-  }
-
-  String libraryMetadata(Uri libraryUri, String key) {
-    if (libraryUri.isScheme("package")) {
-      return packageMetadata(libraryUri.pathSegments.first, key);
-    }
-    var defaultPackageNameUri = _mapping[""];
-    if (defaultPackageNameUri != null) {
-      return packageMetadata(defaultPackageNameUri.toString(), key);
-    }
-    return null;
-  }
-}
-
-/// A [Packages] implementation based on a remote (e.g., HTTP) directory.
-///
-/// There is no way to detect which packages exist short of trying to use
-/// them. You can't necessarily check whether a directory exists,
-/// except by checking for a know file in the directory.
-class NonFilePackagesDirectoryPackages extends PackagesBase {
-  final Uri _packageBase;
-  NonFilePackagesDirectoryPackages(this._packageBase);
-
-  Uri getBase(String packageName) => _packageBase.resolve("$packageName/");
-
-  Error _failListingPackages() {
-    return UnsupportedError(
-        "Cannot list packages for a ${_packageBase.scheme}: "
-        "based package root");
-  }
-
-  Iterable<String> get packages {
-    throw _failListingPackages();
-  }
-
-  Map<String, Uri> asMap() {
-    throw _failListingPackages();
-  }
-}
diff --git a/lib/src/packages_io_impl.dart b/lib/src/packages_io_impl.dart
deleted file mode 100644
index c623f4d..0000000
--- a/lib/src/packages_io_impl.dart
+++ /dev/null
@@ -1,46 +0,0 @@
-// Copyright (c) 2015, 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.
-
-/// Implementations of [Packages] that can only be used in server based
-/// applications.
-@Deprecated("Use the package_config.json based API")
-library package_config.packages_io_impl;
-
-import "dart:collection" show UnmodifiableMapView;
-import "dart:io" show Directory;
-
-import "packages_impl.dart";
-
-import "util_io.dart";
-
-/// A [Packages] implementation based on a local directory.
-class FilePackagesDirectoryPackages extends PackagesBase {
-  final Directory _packageDir;
-  final Map<String, Uri> _packageToBaseUriMap = <String, Uri>{};
-
-  FilePackagesDirectoryPackages(this._packageDir);
-
-  Uri getBase(String packageName) {
-    return _packageToBaseUriMap.putIfAbsent(packageName, () {
-      return Uri.file(pathJoin(_packageDir.path, packageName, '.'));
-    });
-  }
-
-  Iterable<String> _listPackageNames() {
-    return _packageDir
-        .listSync()
-        .whereType<Directory>()
-        .map((e) => fileName(e.path));
-  }
-
-  Iterable<String> get packages => _listPackageNames();
-
-  Map<String, Uri> asMap() {
-    var result = <String, Uri>{};
-    for (var packageName in _listPackageNames()) {
-      result[packageName] = getBase(packageName);
-    }
-    return UnmodifiableMapView<String, Uri>(result);
-  }
-}
diff --git a/lib/src/util.dart b/lib/src/util.dart
index 50b140f..9b263ae 100644
--- a/lib/src/util.dart
+++ b/lib/src/util.dart
@@ -153,10 +153,10 @@
 /// `baseUri.resolveUri(result) == uri`,
 ///
 /// The `baseUri` must be absolute.
-Uri relativizeUri(Uri uri, Uri /*?*/ baseUri) {
+Uri? relativizeUri(Uri? uri, Uri? baseUri) {
   if (baseUri == null) return uri;
   assert(baseUri.isAbsolute);
-  if (uri.hasQuery || uri.hasFragment) {
+  if (uri!.hasQuery || uri.hasFragment) {
     uri = Uri(
         scheme: uri.scheme,
         userInfo: uri.hasAuthority ? uri.userInfo : null,
diff --git a/lib/src/util_io.dart b/lib/src/util_io.dart
index 2aa8c94..8e5f0b8 100644
--- a/lib/src/util_io.dart
+++ b/lib/src/util_io.dart
@@ -9,7 +9,7 @@
 import 'dart:io';
 import 'dart:typed_data';
 
-Future<Uint8List> defaultLoader(Uri uri) async {
+Future<Uint8List?> defaultLoader(Uri uri) async {
   if (uri.isScheme("file")) {
     var file = File.fromUri(uri);
     try {
@@ -24,7 +24,7 @@
   throw UnsupportedError("Default URI unsupported scheme: $uri");
 }
 
-Future<Uint8List /*?*/ > _httpGet(Uri uri) async {
+Future<Uint8List?> _httpGet(Uri uri) async {
   assert(uri.isScheme("http") || uri.isScheme("https"));
   var client = HttpClient();
   var request = await client.getUrl(uri);
@@ -45,7 +45,7 @@
   }
   var result = Uint8List(totalLength);
   var offset = 0;
-  for (Uint8List contentPart in splitContent) {
+  for (var contentPart in splitContent as Iterable<Uint8List>) {
     result.setRange(offset, offset + contentPart.length, contentPart);
     offset += contentPart.length;
   }
@@ -80,7 +80,7 @@
 ///
 /// If a part ends with a path separator, then no extra separator is
 /// inserted.
-String pathJoin(String part1, String part2, [String part3]) {
+String pathJoin(String part1, String part2, [String? part3]) {
   var separator = Platform.pathSeparator;
   var separator1 = part1.endsWith(separator) ? "" : separator;
   if (part3 == null) {
diff --git a/pubspec.yaml b/pubspec.yaml
index b7d5969..05c5266 100644
--- a/pubspec.yaml
+++ b/pubspec.yaml
@@ -1,20 +1,55 @@
 name: package_config
-version: 1.9.3
+version: 2.0.0-dev
 description: Support for working with Package Configuration files.
 homepage: https://github.com/dart-lang/package_config
 
 environment:
-  sdk: '>=2.7.0 <3.0.0'
+  sdk: '>=2.12.0-0 <3.0.0'
 
 dependencies:
-  path: ^1.6.4
-  charcode: ^1.1.0
+  path: ^1.8.0-nullsafety.3
 
 dev_dependencies:
-  test: ^1.6.4
-  matcher: ^0.12.5
-  pedantic: ^1.9.0
+  build_resolvers: ^1.10.0
+  build_runner: ^1.10.0
+  build_runner_core: ^1.10.0
+  build_test: ^1.3.0
+  build_web_compilers: ^2.15.0
+  pedantic: ^1.10.0-nullsafety.3
+  test: ^1.16.0-nullsafety.4
 
-  build_runner: ^1.0.0
-  build_web_compilers: ^2.0.0
-  build_test: ^0.10.0
+dependency_overrides:
+  analyzer:
+    git:
+      url: git://github.com/dart-lang/sdk.git
+      path: pkg/analyzer
+  build_resolvers:
+    git:
+      url: git://github.com/dart-lang/build.git
+      path: build_resolvers
+  build_runner:
+    git:
+      url: git://github.com/dart-lang/build.git
+      path: build_runner
+  build_runner_core:
+    git:
+      url: git://github.com/dart-lang/build.git
+      path: build_runner_core
+  build_test:
+    git:
+      url: git://github.com/dart-lang/build.git
+      path: build_test
+  coverage:
+    git: git://github.com/dart-lang/coverage.git
+  test:
+    git:
+      url: git://github.com/dart-lang/test.git
+      path: pkgs/test
+  test_api:
+    git:
+      url: git://github.com/dart-lang/test.git
+      path: pkgs/test_api
+  test_core:
+    git:
+      url: git://github.com/dart-lang/test.git
+      path: pkgs/test_core
diff --git a/test/discovery_test.dart b/test/discovery_test.dart
index 5cbc992..df2374d 100644
--- a/test/discovery_test.dart
+++ b/test/discovery_test.dart
@@ -63,7 +63,7 @@
         "package_config.json": packageConfigFile,
       }
     }, (Directory directory) async {
-      var config = await findPackageConfig(directory);
+      var config = (await findPackageConfig(directory))!;
       expect(config.version, 2); // Found package_config.json file.
       validatePackagesFile(config, directory);
     });
@@ -74,7 +74,7 @@
       "script.dart": "main(){}",
       "packages": {"shouldNotBeFound": {}}
     }, (Directory directory) async {
-      var config = await findPackageConfig(directory);
+      var config = (await findPackageConfig(directory))!;
       expect(config.version, 1); // Found .packages file.
       validatePackagesFile(config, directory);
     });
@@ -89,7 +89,7 @@
         "script.dart": "main(){}",
       }
     }, (Directory directory) async {
-      var config = await findPackageConfig(subdir(directory, "subdir/"));
+      var config = (await findPackageConfig(subdir(directory, "subdir/")))!;
       expect(config.version, 2);
       validatePackagesFile(config, directory);
     });
diff --git a/test/discovery_uri_test.dart b/test/discovery_uri_test.dart
index 23c02d7..a895279 100644
--- a/test/discovery_uri_test.dart
+++ b/test/discovery_uri_test.dart
@@ -61,7 +61,7 @@
         "package_config.json": packageConfigFile,
       }
     }, (directory, loader) async {
-      var config = await findPackageConfigUri(directory, loader: loader);
+      var config = (await findPackageConfigUri(directory, loader: loader))!;
       expect(config.version, 2); // Found package_config.json file.
       validatePackagesFile(config, directory);
     });
@@ -72,7 +72,7 @@
       "script.dart": "main(){}",
       "packages": {"shouldNotBeFound": {}}
     }, (directory, loader) async {
-      var config = await findPackageConfigUri(directory, loader: loader);
+      var config = (await findPackageConfigUri(directory, loader: loader))!;
       expect(config.version, 1); // Found .packages file.
       validatePackagesFile(config, directory);
     });
@@ -87,8 +87,8 @@
         "script.dart": "main(){}",
       }
     }, (directory, loader) async {
-      var config = await findPackageConfigUri(directory.resolve("subdir/"),
-          loader: loader);
+      var config = (await findPackageConfigUri(directory.resolve("subdir/"),
+          loader: loader))!;
       expect(config.version, 2);
       validatePackagesFile(config, directory);
     });
diff --git a/test/legacy/all.dart b/test/legacy/all.dart
deleted file mode 100644
index 22e2e4f..0000000
--- a/test/legacy/all.dart
+++ /dev/null
@@ -1,20 +0,0 @@
-// Copyright (c) 2015, 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.
-
-@deprecated
-library package_config.all_test;
-
-import "package:test/test.dart";
-
-import "discovery_analysis_test.dart" as discovery_analysis;
-import "discovery_test.dart" as discovery;
-import "parse_test.dart" as parse;
-import "parse_write_test.dart" as parse_write;
-
-void main() {
-  group("parse:", parse.main);
-  group("discovery:", discovery.main);
-  group("discovery-analysis:", discovery_analysis.main);
-  group("parse/write:", parse_write.main);
-}
diff --git a/test/legacy/discovery_analysis_test.dart b/test/legacy/discovery_analysis_test.dart
deleted file mode 100644
index 7554d85..0000000
--- a/test/legacy/discovery_analysis_test.dart
+++ /dev/null
@@ -1,127 +0,0 @@
-// Copyright (c) 2015, 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.
-
-@deprecated
-@TestOn('vm')
-library package_config.discovery_analysis_test;
-
-import "dart:io";
-
-import "package:package_config/discovery_analysis.dart";
-import "package:package_config/packages.dart";
-import "package:path/path.dart" as path;
-import "package:test/test.dart";
-
-void main() {
-  fileTest("basic", {
-    ".packages": packagesFile,
-    "foo": {".packages": packagesFile},
-    "bar": {
-      "packages": {"foo": {}, "bar": {}, "baz": {}}
-    },
-    "baz": {}
-  }, (Directory directory) {
-    var dirUri = Uri.directory(directory.path);
-    var ctx = PackageContext.findAll(directory);
-    var root = ctx[directory];
-    expect(root, same(ctx));
-    validatePackagesFile(root.packages, dirUri);
-    var fooDir = sub(directory, "foo");
-    var foo = ctx[fooDir];
-    expect(identical(root, foo), isFalse);
-    validatePackagesFile(foo.packages, dirUri.resolve("foo/"));
-    var barDir = sub(directory, "bar");
-    var bar = ctx[sub(directory, "bar")];
-    validatePackagesDir(bar.packages, dirUri.resolve("bar/"));
-    var barbar = ctx[sub(barDir, "bar")];
-    expect(barbar, same(bar)); // inherited.
-    var baz = ctx[sub(directory, "baz")];
-    expect(baz, same(root)); // inherited.
-
-    var map = ctx.asMap();
-    expect(map.keys.map((dir) => dir.path),
-        unorderedEquals([directory.path, fooDir.path, barDir.path]));
-    return null;
-  });
-}
-
-Directory sub(Directory parent, String dirName) {
-  return Directory(path.join(parent.path, dirName));
-}
-
-const packagesFile = """
-# A comment
-foo:file:///dart/packages/foo/
-bar:http://example.com/dart/packages/bar/
-baz:packages/baz/
-""";
-
-void validatePackagesFile(Packages resolver, Uri location) {
-  expect(resolver, isNotNull);
-  expect(resolver.resolve(pkg("foo", "bar/baz")),
-      equals(Uri.parse("file:///dart/packages/foo/bar/baz")));
-  expect(resolver.resolve(pkg("bar", "baz/qux")),
-      equals(Uri.parse("http://example.com/dart/packages/bar/baz/qux")));
-  expect(resolver.resolve(pkg("baz", "qux/foo")),
-      equals(location.resolve("packages/baz/qux/foo")));
-  expect(resolver.packages, unorderedEquals(["foo", "bar", "baz"]));
-}
-
-void validatePackagesDir(Packages resolver, Uri location) {
-  // Expect three packages: foo, bar and baz
-  expect(resolver, isNotNull);
-  expect(resolver.resolve(pkg("foo", "bar/baz")),
-      equals(location.resolve("packages/foo/bar/baz")));
-  expect(resolver.resolve(pkg("bar", "baz/qux")),
-      equals(location.resolve("packages/bar/baz/qux")));
-  expect(resolver.resolve(pkg("baz", "qux/foo")),
-      equals(location.resolve("packages/baz/qux/foo")));
-  if (location.scheme == "file") {
-    expect(resolver.packages, unorderedEquals(["foo", "bar", "baz"]));
-  } else {
-    expect(() => resolver.packages, throwsUnsupportedError);
-  }
-}
-
-Uri pkg(String packageName, String packagePath) {
-  var path;
-  if (packagePath.startsWith('/')) {
-    path = "$packageName$packagePath";
-  } else {
-    path = "$packageName/$packagePath";
-  }
-  return Uri(scheme: "package", path: path);
-}
-
-/// Create a directory structure from [description] and run [fileTest].
-///
-/// Description is a map, each key is a file entry. If the value is a map,
-/// it's a sub-dir, otherwise it's a file and the value is the content
-/// as a string.
-void fileTest(
-    String name, Map description, Future fileTest(Directory directory)) {
-  group("file-test", () {
-    var tempDir = Directory.systemTemp.createTempSync("file-test");
-    setUp(() {
-      _createFiles(tempDir, description);
-    });
-    tearDown(() {
-      tempDir.deleteSync(recursive: true);
-    });
-    test(name, () => fileTest(tempDir));
-  });
-}
-
-void _createFiles(Directory target, Map description) {
-  description.forEach((name, content) {
-    if (content is Map) {
-      var subDir = Directory(path.join(target.path, name));
-      subDir.createSync();
-      _createFiles(subDir, content);
-    } else {
-      var file = File(path.join(target.path, name));
-      file.writeAsStringSync(content, flush: true);
-    }
-  });
-}
diff --git a/test/legacy/discovery_test.dart b/test/legacy/discovery_test.dart
deleted file mode 100644
index 72874c8..0000000
--- a/test/legacy/discovery_test.dart
+++ /dev/null
@@ -1,329 +0,0 @@
-// Copyright (c) 2015, 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.
-
-@deprecated
-@TestOn('vm')
-library package_config.discovery_test;
-
-import "dart:async";
-import "dart:io";
-import "package:test/test.dart";
-import "package:package_config/packages.dart";
-import "package:package_config/discovery.dart";
-import "package:path/path.dart" as path;
-
-const packagesFile = """
-# A comment
-foo:file:///dart/packages/foo/
-bar:http://example.com/dart/packages/bar/
-baz:packages/baz/
-""";
-
-void validatePackagesFile(Packages resolver, Uri location) {
-  expect(resolver, isNotNull);
-  expect(resolver.resolve(pkg("foo", "bar/baz")),
-      equals(Uri.parse("file:///dart/packages/foo/bar/baz")));
-  expect(resolver.resolve(pkg("bar", "baz/qux")),
-      equals(Uri.parse("http://example.com/dart/packages/bar/baz/qux")));
-  expect(resolver.resolve(pkg("baz", "qux/foo")),
-      equals(location.resolve("packages/baz/qux/foo")));
-  expect(resolver.packages, unorderedEquals(["foo", "bar", "baz"]));
-}
-
-void validatePackagesDir(Packages resolver, Uri location) {
-  // Expect three packages: foo, bar and baz
-  expect(resolver, isNotNull);
-  expect(resolver.resolve(pkg("foo", "bar/baz")),
-      equals(location.resolve("packages/foo/bar/baz")));
-  expect(resolver.resolve(pkg("bar", "baz/qux")),
-      equals(location.resolve("packages/bar/baz/qux")));
-  expect(resolver.resolve(pkg("baz", "qux/foo")),
-      equals(location.resolve("packages/baz/qux/foo")));
-  if (location.scheme == "file") {
-    expect(resolver.packages, unorderedEquals(["foo", "bar", "baz"]));
-  } else {
-    expect(() => resolver.packages, throwsUnsupportedError);
-  }
-}
-
-Uri pkg(String packageName, String packagePath) {
-  var path;
-  if (packagePath.startsWith('/')) {
-    path = "$packageName$packagePath";
-  } else {
-    path = "$packageName/$packagePath";
-  }
-  return Uri(scheme: "package", path: path);
-}
-
-void main() {
-  generalTest(".packages", {
-    ".packages": packagesFile,
-    "script.dart": "main(){}",
-    "packages": {"shouldNotBeFound": {}}
-  }, (Uri location) async {
-    Packages resolver;
-    resolver = await findPackages(location);
-    validatePackagesFile(resolver, location);
-    resolver = await findPackages(location.resolve("script.dart"));
-    validatePackagesFile(resolver, location);
-    var specificDiscovery = (location.scheme == "file")
-        ? findPackagesFromFile
-        : findPackagesFromNonFile;
-    resolver = await specificDiscovery(location);
-    validatePackagesFile(resolver, location);
-    resolver = await specificDiscovery(location.resolve("script.dart"));
-    validatePackagesFile(resolver, location);
-  });
-
-  generalTest("packages/", {
-    "packages": {"foo": {}, "bar": {}, "baz": {}},
-    "script.dart": "main(){}"
-  }, (Uri location) async {
-    Packages resolver;
-    var isFile = (location.scheme == "file");
-    resolver = await findPackages(location);
-    validatePackagesDir(resolver, location);
-    resolver = await findPackages(location.resolve("script.dart"));
-    validatePackagesDir(resolver, location);
-    var specificDiscovery =
-        isFile ? findPackagesFromFile : findPackagesFromNonFile;
-    resolver = await specificDiscovery(location);
-    validatePackagesDir(resolver, location);
-    resolver = await specificDiscovery(location.resolve("script.dart"));
-    validatePackagesDir(resolver, location);
-  });
-
-  generalTest("underscore packages", {
-    "packages": {"_foo": {}}
-  }, (Uri location) async {
-    var resolver = await findPackages(location);
-    expect(resolver.resolve(pkg("_foo", "foo.dart")),
-        equals(location.resolve("packages/_foo/foo.dart")));
-  });
-
-  fileTest(".packages recursive", {
-    ".packages": packagesFile,
-    "subdir": {"script.dart": "main(){}"}
-  }, (Uri location) async {
-    Packages resolver;
-    resolver = await findPackages(location.resolve("subdir/"));
-    validatePackagesFile(resolver, location);
-    resolver = await findPackages(location.resolve("subdir/script.dart"));
-    validatePackagesFile(resolver, location);
-    resolver = await findPackagesFromFile(location.resolve("subdir/"));
-    validatePackagesFile(resolver, location);
-    resolver =
-        await findPackagesFromFile(location.resolve("subdir/script.dart"));
-    validatePackagesFile(resolver, location);
-  });
-
-  httpTest(".packages not recursive", {
-    ".packages": packagesFile,
-    "subdir": {"script.dart": "main(){}"}
-  }, (Uri location) async {
-    Packages resolver;
-    var subdir = location.resolve("subdir/");
-    resolver = await findPackages(subdir);
-    validatePackagesDir(resolver, subdir);
-    resolver = await findPackages(subdir.resolve("script.dart"));
-    validatePackagesDir(resolver, subdir);
-    resolver = await findPackagesFromNonFile(subdir);
-    validatePackagesDir(resolver, subdir);
-    resolver = await findPackagesFromNonFile(subdir.resolve("script.dart"));
-    validatePackagesDir(resolver, subdir);
-  });
-
-  fileTest("no packages", {"script.dart": "main(){}"}, (Uri location) async {
-    // A file: location with no .packages or packages returns
-    // Packages.noPackages.
-    Packages resolver;
-    resolver = await findPackages(location);
-    expect(resolver, same(Packages.noPackages));
-    resolver = await findPackages(location.resolve("script.dart"));
-    expect(resolver, same(Packages.noPackages));
-    resolver = findPackagesFromFile(location);
-    expect(resolver, same(Packages.noPackages));
-    resolver = findPackagesFromFile(location.resolve("script.dart"));
-    expect(resolver, same(Packages.noPackages));
-  });
-
-  httpTest("no packages", {"script.dart": "main(){}"}, (Uri location) async {
-    // A non-file: location with no .packages or packages/:
-    // Assumes a packages dir exists, and resolves relative to that.
-    Packages resolver;
-    resolver = await findPackages(location);
-    validatePackagesDir(resolver, location);
-    resolver = await findPackages(location.resolve("script.dart"));
-    validatePackagesDir(resolver, location);
-    resolver = await findPackagesFromNonFile(location);
-    validatePackagesDir(resolver, location);
-    resolver = await findPackagesFromNonFile(location.resolve("script.dart"));
-    validatePackagesDir(resolver, location);
-  });
-
-  test(".packages w/ loader", () async {
-    var location = Uri.parse("krutch://example.com/path/");
-    Future<List<int>> loader(Uri file) async {
-      if (file.path.endsWith(".packages")) {
-        return packagesFile.codeUnits;
-      }
-      throw "not found";
-    }
-
-    // A non-file: location with no .packages or packages/:
-    // Assumes a packages dir exists, and resolves relative to that.
-    Packages resolver;
-    resolver = await findPackages(location, loader: loader);
-    validatePackagesFile(resolver, location);
-    resolver =
-        await findPackages(location.resolve("script.dart"), loader: loader);
-    validatePackagesFile(resolver, location);
-    resolver = await findPackagesFromNonFile(location, loader: loader);
-    validatePackagesFile(resolver, location);
-    resolver = await findPackagesFromNonFile(location.resolve("script.dart"),
-        loader: loader);
-    validatePackagesFile(resolver, location);
-  });
-
-  test("no packages w/ loader", () async {
-    var location = Uri.parse("krutch://example.com/path/");
-    Future<List<int>> loader(Uri file) async {
-      throw "not found";
-    }
-
-    // A non-file: location with no .packages or packages/:
-    // Assumes a packages dir exists, and resolves relative to that.
-    Packages resolver;
-    resolver = await findPackages(location, loader: loader);
-    validatePackagesDir(resolver, location);
-    resolver =
-        await findPackages(location.resolve("script.dart"), loader: loader);
-    validatePackagesDir(resolver, location);
-    resolver = await findPackagesFromNonFile(location, loader: loader);
-    validatePackagesDir(resolver, location);
-    resolver = await findPackagesFromNonFile(location.resolve("script.dart"),
-        loader: loader);
-    validatePackagesDir(resolver, location);
-  });
-
-  generalTest("loadPackagesFile", {".packages": packagesFile},
-      (Uri directory) async {
-    var file = directory.resolve(".packages");
-    var resolver = await loadPackagesFile(file);
-    validatePackagesFile(resolver, file);
-  });
-
-  generalTest(
-      "loadPackagesFile non-default name", {"pheldagriff": packagesFile},
-      (Uri directory) async {
-    var file = directory.resolve("pheldagriff");
-    var resolver = await loadPackagesFile(file);
-    validatePackagesFile(resolver, file);
-  });
-
-  test("loadPackagesFile w/ loader", () async {
-    Future<List<int>> loader(Uri uri) async => packagesFile.codeUnits;
-    var file = Uri.parse("krutz://example.com/.packages");
-    var resolver = await loadPackagesFile(file, loader: loader);
-    validatePackagesFile(resolver, file);
-  });
-
-  generalTest("loadPackagesFile not found", {}, (Uri directory) async {
-    var file = directory.resolve(".packages");
-    expect(
-        loadPackagesFile(file),
-        throwsA(anyOf(
-            TypeMatcher<FileSystemException>(), TypeMatcher<HttpException>())));
-  });
-
-  generalTest("loadPackagesFile syntax error", {".packages": "syntax error"},
-      (Uri directory) async {
-    var file = directory.resolve(".packages");
-    expect(loadPackagesFile(file), throwsFormatException);
-  });
-
-  generalTest("getPackagesDir", {
-    "packages": {"foo": {}, "bar": {}, "baz": {}}
-  }, (Uri directory) async {
-    var packages = directory.resolve("packages/");
-    var resolver = getPackagesDirectory(packages);
-    var resolved = resolver.resolve(pkg("foo", "flip/flop"));
-    expect(resolved, packages.resolve("foo/flip/flop"));
-  });
-}
-
-/// Create a directory structure from [description] and run [fileTest].
-///
-/// Description is a map, each key is a file entry. If the value is a map,
-/// it's a sub-dir, otherwise it's a file and the value is the content
-/// as a string.
-void fileTest(String name, Map description, Future fileTest(Uri directory)) {
-  group("file-test", () {
-    var tempDir = Directory.systemTemp.createTempSync("file-test");
-    setUp(() {
-      _createFiles(tempDir, description);
-    });
-    tearDown(() {
-      tempDir.deleteSync(recursive: true);
-    });
-    test(name, () => fileTest(Uri.file(path.join(tempDir.path, "."))));
-  });
-}
-
-/// HTTP-server the directory structure from [description] and run [htpTest].
-///
-/// Description is a map, each key is a file entry. If the value is a map,
-/// it's a sub-dir, otherwise it's a file and the value is the content
-/// as a string.
-void httpTest(String name, Map description, Future httpTest(Uri directory)) {
-  group("http-test", () {
-    var serverSub;
-    var uri;
-    setUp(() {
-      return HttpServer.bind(InternetAddress.loopbackIPv4, 0).then((server) {
-        uri = Uri(
-            scheme: "http", host: "127.0.0.1", port: server.port, path: "/");
-        serverSub = server.listen((HttpRequest request) {
-          // No error handling.
-          var path = request.uri.path;
-          if (path.startsWith('/')) path = path.substring(1);
-          if (path.endsWith('/')) path = path.substring(0, path.length - 1);
-          var parts = path.split('/');
-          dynamic fileOrDir = description;
-          for (var i = 0; i < parts.length; i++) {
-            fileOrDir = fileOrDir[parts[i]];
-            if (fileOrDir == null) {
-              request.response.statusCode = 404;
-              request.response.close();
-              return;
-            }
-          }
-          request.response.write(fileOrDir);
-          request.response.close();
-        });
-      });
-    });
-    tearDown(() => serverSub.cancel());
-    test(name, () => httpTest(uri));
-  });
-}
-
-void generalTest(String name, Map description, Future action(Uri location)) {
-  fileTest(name, description, action);
-  httpTest(name, description, action);
-}
-
-void _createFiles(Directory target, Map description) {
-  description.forEach((name, content) {
-    if (content is Map) {
-      var subDir = Directory(path.join(target.path, name));
-      subDir.createSync();
-      _createFiles(subDir, content);
-    } else {
-      var file = File(path.join(target.path, name));
-      file.writeAsStringSync(content, flush: true);
-    }
-  });
-}
diff --git a/test/legacy/parse_test.dart b/test/legacy/parse_test.dart
deleted file mode 100644
index b9cf1f8..0000000
--- a/test/legacy/parse_test.dart
+++ /dev/null
@@ -1,246 +0,0 @@
-// Copyright (c) 2015, 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.
-
-@deprecated
-library package_config.parse_test;
-
-import "package:package_config/packages.dart";
-import "package:package_config/packages_file.dart" show parse;
-import "package:package_config/src/packages_impl.dart";
-import "package:test/test.dart";
-
-void main() {
-  var base = Uri.parse("file:///one/two/three/packages.map");
-  test("empty", () {
-    var packages = doParse(emptySample, base);
-    expect(packages.asMap(), isEmpty);
-  });
-  test("comment only", () {
-    var packages = doParse(commentOnlySample, base);
-    expect(packages.asMap(), isEmpty);
-  });
-  test("empty lines only", () {
-    var packages = doParse(emptyLinesSample, base);
-    expect(packages.asMap(), isEmpty);
-  });
-
-  test("empty lines only", () {
-    var packages = doParse(emptyLinesSample, base);
-    expect(packages.asMap(), isEmpty);
-  });
-
-  test("single", () {
-    var packages = doParse(singleRelativeSample, base);
-    expect(packages.packages.toList(), equals(["foo"]));
-    expect(packages.resolve(Uri.parse("package:foo/bar/baz.dart")),
-        equals(base.resolve("../test/").resolve("bar/baz.dart")));
-  });
-
-  test("single no slash", () {
-    var packages = doParse(singleRelativeSampleNoSlash, base);
-    expect(packages.packages.toList(), equals(["foo"]));
-    expect(packages.resolve(Uri.parse("package:foo/bar/baz.dart")),
-        equals(base.resolve("../test/").resolve("bar/baz.dart")));
-  });
-
-  test("single no newline", () {
-    var packages = doParse(singleRelativeSampleNoNewline, base);
-    expect(packages.packages.toList(), equals(["foo"]));
-    expect(packages.resolve(Uri.parse("package:foo/bar/baz.dart")),
-        equals(base.resolve("../test/").resolve("bar/baz.dart")));
-  });
-
-  test("single absolute authority", () {
-    var packages = doParse(singleAbsoluteSample, base);
-    expect(packages.packages.toList(), equals(["foo"]));
-    expect(packages.resolve(Uri.parse("package:foo/bar/baz.dart")),
-        equals(Uri.parse("http://example.com/some/where/bar/baz.dart")));
-  });
-
-  test("single empty path", () {
-    var packages = doParse(singleEmptyPathSample, base);
-    expect(packages.packages.toList(), equals(["foo"]));
-    expect(packages.resolve(Uri.parse("package:foo/bar/baz.dart")),
-        equals(base.replace(path: "${base.path}/bar/baz.dart")));
-  });
-
-  test("single absolute path", () {
-    var packages = doParse(singleAbsolutePathSample, base);
-    expect(packages.packages.toList(), equals(["foo"]));
-    expect(packages.resolve(Uri.parse("package:foo/bar/baz.dart")),
-        equals(base.replace(path: "/test/bar/baz.dart")));
-  });
-
-  test("multiple", () {
-    var packages = doParse(multiRelativeSample, base);
-    expect(packages.packages.toList()..sort(), equals(["bar", "foo"]));
-    expect(packages.resolve(Uri.parse("package:foo/bar/baz.dart")),
-        equals(base.resolve("../test/").resolve("bar/baz.dart")));
-    expect(packages.resolve(Uri.parse("package:bar/foo/baz.dart")),
-        equals(base.resolve("../test2/").resolve("foo/baz.dart")));
-  });
-
-  test("dot-dot 1", () {
-    var packages = doParse(singleRelativeSample, base);
-    expect(packages.packages.toList(), equals(["foo"]));
-    expect(packages.resolve(Uri.parse("package:foo/qux/../bar/baz.dart")),
-        equals(base.resolve("../test/").resolve("bar/baz.dart")));
-  });
-
-  test("all valid chars can be used in URI segment", () {
-    var packages = doParse(allValidCharsSample, base);
-    expect(packages.packages.toList(), equals([allValidChars]));
-    expect(packages.resolve(Uri.parse("package:$allValidChars/bar/baz.dart")),
-        equals(base.resolve("../test/").resolve("bar/baz.dart")));
-  });
-
-  test("no invalid chars accepted", () {
-    var map = {};
-    for (var i = 0; i < allValidChars.length; i++) {
-      map[allValidChars.codeUnitAt(i)] = true;
-    }
-    for (var i = 0; i <= 255; i++) {
-      if (map[i] == true) continue;
-      var char = String.fromCharCode(i);
-      expect(() => doParse("x${char}x:x", null),
-          anyOf(throwsNoSuchMethodError, throwsFormatException));
-    }
-  });
-
-  test("no escapes", () {
-    expect(() => doParse("x%41x:x", base), throwsFormatException);
-  });
-
-  test("same name twice", () {
-    expect(
-        () => doParse(singleRelativeSample * 2, base), throwsFormatException);
-  });
-
-  test("disallow default package", () {
-    expect(() => doParse(":foo", base, allowDefaultPackage: false),
-        throwsFormatException);
-  });
-
-  test("allow default package", () {
-    var packages = doParse(":foo", base, allowDefaultPackage: true);
-    expect(packages.defaultPackageName, "foo");
-  });
-
-  test("allow default package name with dot", () {
-    var packages = doParse(":foo.bar", base, allowDefaultPackage: true);
-    expect(packages.defaultPackageName, "foo.bar");
-  });
-
-  test("not two default packages", () {
-    expect(() => doParse(":foo\n:bar", base, allowDefaultPackage: true),
-        throwsFormatException);
-  });
-
-  test("default package invalid package name", () {
-    // Not a valid *package name*.
-    expect(() => doParse(":foo/bar", base, allowDefaultPackage: true),
-        throwsFormatException);
-  });
-
-  group("metadata", () {
-    var packages = doParse(
-        ":foo\n"
-        "foo:foo#metafoo=1\n"
-        "bar:bar#metabar=2\n"
-        "baz:baz\n"
-        "qux:qux#metaqux1=3&metaqux2=4\n",
-        base,
-        allowDefaultPackage: true);
-    test("non-existing", () {
-      // non-package name.
-      expect(packages.packageMetadata("///", "f"), null);
-      expect(packages.packageMetadata("", "f"), null);
-      // unconfigured package name.
-      expect(packages.packageMetadata("absent", "f"), null);
-      // package name without that metadata
-      expect(packages.packageMetadata("foo", "notfoo"), null);
-    });
-    test("lookup", () {
-      expect(packages.packageMetadata("foo", "metafoo"), "1");
-      expect(packages.packageMetadata("bar", "metabar"), "2");
-      expect(packages.packageMetadata("qux", "metaqux1"), "3");
-      expect(packages.packageMetadata("qux", "metaqux2"), "4");
-    });
-    test("by library URI", () {
-      expect(
-          packages.libraryMetadata(
-              Uri.parse("package:foo/index.dart"), "metafoo"),
-          "1");
-      expect(
-          packages.libraryMetadata(
-              Uri.parse("package:bar/index.dart"), "metabar"),
-          "2");
-      expect(
-          packages.libraryMetadata(
-              Uri.parse("package:qux/index.dart"), "metaqux1"),
-          "3");
-      expect(
-          packages.libraryMetadata(
-              Uri.parse("package:qux/index.dart"), "metaqux2"),
-          "4");
-    });
-    test("by default package", () {
-      expect(
-          packages.libraryMetadata(
-              Uri.parse("file:///whatever.dart"), "metafoo"),
-          "1");
-    });
-  });
-
-  for (var invalidSample in invalid) {
-    test("invalid '$invalidSample'", () {
-      var result;
-      try {
-        result = doParse(invalidSample, base);
-      } on FormatException {
-        // expected
-        return;
-      }
-      fail("Resolved to $result");
-    });
-  }
-}
-
-Packages doParse(String sample, Uri baseUri,
-    {bool allowDefaultPackage = false}) {
-  var map = parse(sample.codeUnits, baseUri,
-      allowDefaultPackage: allowDefaultPackage);
-  return MapPackages(map);
-}
-
-// Valid samples.
-var emptySample = "";
-var commentOnlySample = "# comment only\n";
-var emptyLinesSample = "\n\n\r\n";
-var singleRelativeSample = "foo:../test/\n";
-var singleRelativeSampleNoSlash = "foo:../test\n";
-var singleRelativeSampleNoNewline = "foo:../test/";
-var singleAbsoluteSample = "foo:http://example.com/some/where/\n";
-var singleEmptyPathSample = "foo:\n";
-var singleAbsolutePathSample = "foo:/test/\n";
-var multiRelativeSample = "foo:../test/\nbar:../test2/\n";
-// All valid path segment characters in an URI.
-var allValidChars = r"!$&'()*+,-.0123456789;="
-    r"@ABCDEFGHIJKLMNOPQRSTUVWXYZ_abcdefghijklmnopqrstuvwxyz~";
-
-var allValidCharsSample = "${allValidChars}:../test/\n";
-
-// Invalid samples.
-var invalid = [
-  ":baz.dart", // empty.
-  "foobar=baz.dart", // no colon (but an equals, which is not the same)
-  ".:../test/", // dot segment
-  "..:../test/", // dot-dot segment
-  "...:../test/", // dot-dot-dot segment
-  "foo/bar:../test/", // slash in name
-  "/foo:../test/", // slash at start of name
-  "?:../test/", // invalid characters.
-  "[:../test/", // invalid characters.
-  "x#:../test/", // invalid characters.
-];
diff --git a/test/legacy/parse_write_test.dart b/test/legacy/parse_write_test.dart
deleted file mode 100644
index a51ced1..0000000
--- a/test/legacy/parse_write_test.dart
+++ /dev/null
@@ -1,133 +0,0 @@
-// Copyright (c) 2015, 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.
-
-@deprecated
-library package_config.parse_write_test;
-
-import "dart:convert" show utf8;
-import "package:package_config/packages_file.dart";
-import "package:test/test.dart";
-
-void main() {
-  void testBase(baseDirString) {
-    var baseDir = Uri.parse(baseDirString);
-    group("${baseDir.scheme} base", () {
-      var packagesFile = baseDir.resolve(".packages");
-
-      void roundTripTest(String name, Map<String, Uri> map) {
-        group(name, () {
-          test("write with no baseUri", () {
-            var content = writeToString(map).codeUnits;
-            var resultMap = parse(content, packagesFile);
-            expect(resultMap, map);
-          });
-
-          test("write with base directory", () {
-            var content = writeToString(map, baseUri: baseDir).codeUnits;
-            var resultMap = parse(content, packagesFile);
-            expect(resultMap, map);
-          });
-
-          test("write with base .packages file", () {
-            var content = writeToString(map, baseUri: packagesFile).codeUnits;
-            var resultMap = parse(content, packagesFile);
-            expect(resultMap, map);
-          });
-
-          test("write with defaultPackageName", () {
-            var content = writeToString(
-              {'': Uri.parse('my_pkg')}..addAll(map),
-              allowDefaultPackage: true,
-            ).codeUnits;
-            var resultMap = parse(
-              content,
-              packagesFile,
-              allowDefaultPackage: true,
-            );
-            expect(resultMap[''].toString(), 'my_pkg');
-            expect(
-              resultMap,
-              {'': Uri.parse('my_pkg')}..addAll(map),
-            );
-          });
-
-          test("write with defaultPackageName (utf8)", () {
-            var content = utf8.encode(writeToString(
-              {'': Uri.parse('my_pkg')}..addAll(map),
-              allowDefaultPackage: true,
-            ));
-            var resultMap = parse(
-              content,
-              packagesFile,
-              allowDefaultPackage: true,
-            );
-            expect(resultMap[''].toString(), 'my_pkg');
-            expect(
-              resultMap,
-              {'': Uri.parse('my_pkg')}..addAll(map),
-            );
-          });
-        });
-      }
-
-      var lowerDir = baseDir.resolve("path3/path4/");
-      var higherDir = baseDir.resolve("../");
-      var parallelDir = baseDir.resolve("../path3/");
-      var rootDir = baseDir.resolve("/");
-      var fileDir = Uri.parse("file:///path1/part2/");
-      var httpDir = Uri.parse("http://example.com/path1/path2/");
-      var otherDir = Uri.parse("other:/path1/path2/");
-
-      roundTripTest("empty", {});
-      roundTripTest("lower directory", {"foo": lowerDir});
-      roundTripTest("higher directory", {"foo": higherDir});
-      roundTripTest("parallel directory", {"foo": parallelDir});
-      roundTripTest("same directory", {"foo": baseDir});
-      roundTripTest("root directory", {"foo": rootDir});
-      roundTripTest("file directory", {"foo": fileDir});
-      roundTripTest("http directory", {"foo": httpDir});
-      roundTripTest("other scheme directory", {"foo": otherDir});
-      roundTripTest("multiple same-type directories",
-          {"foo": lowerDir, "bar": higherDir, "baz": parallelDir});
-      roundTripTest("multiple scheme directories",
-          {"foo": fileDir, "bar": httpDir, "baz": otherDir});
-      roundTripTest("multiple scheme directories and mutliple same type", {
-        "foo": fileDir,
-        "bar": httpDir,
-        "baz": otherDir,
-        "qux": lowerDir,
-        "hip": higherDir,
-        "dep": parallelDir
-      });
-    });
-  }
-
-  testBase("file:///base1/base2/");
-  testBase("http://example.com/base1/base2/");
-  testBase("other:/base1/base2/");
-
-  // Check that writing adds the comment.
-  test("write preserves comment", () {
-    var comment = "comment line 1\ncomment line 2\ncomment line 3";
-    var result = writeToString({}, comment: comment);
-    // Comment with "# " before each line and "\n" after last.
-    var expectedComment =
-        "# comment line 1\n# comment line 2\n# comment line 3\n";
-    expect(result, startsWith(expectedComment));
-  });
-}
-
-String writeToString(
-  Map<String, Uri> map, {
-  Uri baseUri,
-  String comment,
-  bool allowDefaultPackage = false,
-}) {
-  var buffer = StringBuffer();
-  write(buffer, map,
-      baseUri: baseUri,
-      comment: comment,
-      allowDefaultPackage: allowDefaultPackage);
-  return buffer.toString();
-}
diff --git a/test/parse_test.dart b/test/parse_test.dart
index 59a7e71..385be36 100644
--- a/test/parse_test.dart
+++ b/test/parse_test.dart
@@ -32,7 +32,7 @@
       expect(result.resolve(pkg("baz", "baz.dart")),
           Uri.parse("file:///tmp/lib/baz.dart"));
 
-      var foo = result["foo"];
+      var foo = result["foo"]!;
       expect(foo, isNotNull);
       expect(foo.root, Uri.parse("file:///foo/"));
       expect(foo.packageUriRoot, Uri.parse("file:///foo/lib/"));
@@ -111,8 +111,10 @@
           "other": [42]
         }
         """;
-      var config = parsePackageConfigBytes(utf8.encode(packageConfigFile),
-          Uri.parse("file:///tmp/.dart_tool/file.dart"), throwError);
+      var config = parsePackageConfigBytes(
+          utf8.encode(packageConfigFile) as Uint8List,
+          Uri.parse("file:///tmp/.dart_tool/file.dart"),
+          throwError);
       expect(config.version, 2);
       expect({for (var p in config.packages) p.name},
           {"foo", "bar", "baz", "noslash"});
@@ -124,28 +126,28 @@
       expect(config.resolve(pkg("baz", "baz.dart")),
           Uri.parse("file:///tmp/lib/baz.dart"));
 
-      var foo = config["foo"];
+      var foo = config["foo"]!;
       expect(foo, isNotNull);
       expect(foo.root, Uri.parse("file:///foo/"));
       expect(foo.packageUriRoot, Uri.parse("file:///foo/lib/"));
       expect(foo.languageVersion, LanguageVersion(2, 5));
       expect(foo.extraData, {"nonstandard": true});
 
-      var bar = config["bar"];
+      var bar = config["bar"]!;
       expect(bar, isNotNull);
       expect(bar.root, Uri.parse("file:///bar/"));
       expect(bar.packageUriRoot, Uri.parse("file:///bar/lib/"));
       expect(bar.languageVersion, LanguageVersion(9999, 9999));
       expect(bar.extraData, null);
 
-      var baz = config["baz"];
+      var baz = config["baz"]!;
       expect(baz, isNotNull);
       expect(baz.root, Uri.parse("file:///tmp/"));
       expect(baz.packageUriRoot, Uri.parse("file:///tmp/lib/"));
       expect(baz.languageVersion, null);
 
       // No slash after root or package root. One is inserted.
-      var noslash = config["noslash"];
+      var noslash = config["noslash"]!;
       expect(noslash, isNotNull);
       expect(noslash.root, Uri.parse("file:///tmp/noslash/"));
       expect(noslash.packageUriRoot, Uri.parse("file:///tmp/noslash/lib/"));
@@ -185,8 +187,10 @@
           "configVersion": 2
         }
         """;
-      var config = parsePackageConfigBytes(utf8.encode(packageConfigFile),
-          Uri.parse("file:///tmp/.dart_tool/file.dart"), throwError);
+      var config = parsePackageConfigBytes(
+          utf8.encode(packageConfigFile) as Uint8List,
+          Uri.parse("file:///tmp/.dart_tool/file.dart"),
+          throwError);
       expect(config.version, 2);
       expect({for (var p in config.packages) p.name}, {"foo", "bar", "baz"});
 
@@ -209,8 +213,10 @@
     var name = '"name":"foo"';
     var root = '"rootUri":"/foo/"';
     test("minimal", () {
-      var config = parsePackageConfigBytes(utf8.encode("{$cfg,$pkgs}"),
-          Uri.parse("file:///tmp/.dart_tool/file.dart"), throwError);
+      var config = parsePackageConfigBytes(
+          utf8.encode("{$cfg,$pkgs}") as Uint8List,
+          Uri.parse("file:///tmp/.dart_tool/file.dart"),
+          throwError);
       expect(config.version, 2);
       expect(config.packages, isEmpty);
     });
@@ -218,7 +224,7 @@
       // A package must have a name and a rootUri, the remaining properties
       // are optional.
       var config = parsePackageConfigBytes(
-          utf8.encode('{$cfg,"packages":[{$name,$root}]}'),
+          utf8.encode('{$cfg,"packages":[{$name,$root}]}') as Uint8List,
           Uri.parse("file:///tmp/.dart_tool/file.dart"),
           throwError);
       expect(config.version, 2);
@@ -235,17 +241,17 @@
           {"name": "qux", "rootUri": "/foo/qux/", "packageUri": "lib/"},
         ]
       }));
-      var config = parsePackageConfigBytes(configBytes,
+      var config = parsePackageConfigBytes(configBytes as Uint8List,
           Uri.parse("file:///tmp/.dart_tool/file.dart"), throwError);
       expect(config.version, 2);
-      expect(config.packageOf(Uri.parse("file:///foo/lala/lala.dart")).name,
+      expect(config.packageOf(Uri.parse("file:///foo/lala/lala.dart"))!.name,
           "foo");
-      expect(
-          config.packageOf(Uri.parse("file:///foo/bar/lala.dart")).name, "bar");
-      expect(config.packageOf(Uri.parse("file:///foo/bar/baz/lala.dart")).name,
+      expect(config.packageOf(Uri.parse("file:///foo/bar/lala.dart"))!.name,
+          "bar");
+      expect(config.packageOf(Uri.parse("file:///foo/bar/baz/lala.dart"))!.name,
           "baz");
-      expect(
-          config.packageOf(Uri.parse("file:///foo/qux/lala.dart")).name, "qux");
+      expect(config.packageOf(Uri.parse("file:///foo/qux/lala.dart"))!.name,
+          "qux");
       expect(config.toPackageUri(Uri.parse("file:///foo/lib/diz")),
           Uri.parse("package:foo/diz"));
       expect(config.toPackageUri(Uri.parse("file:///foo/bar/lib/diz")),
@@ -260,7 +266,7 @@
       void testThrows(String name, String source) {
         test(name, () {
           expect(
-              () => parsePackageConfigBytes(utf8.encode(source),
+              () => parsePackageConfigBytes(utf8.encode(source) as Uint8List,
                   Uri.parse("file:///tmp/.dart_tool/file.dart"), throwError),
               throwsA(TypeMatcher<FormatException>()));
         });
@@ -386,7 +392,7 @@
         for (var package in config.packages) {
           var name = package.name;
           test("package $name", () {
-            var expectedPackage = expected[name];
+            var expectedPackage = expected[name]!;
             expect(expectedPackage, isNotNull);
             expect(package.root, expectedPackage.root, reason: "root");
             expect(package.packageUriRoot, expectedPackage.packageUriRoot,
diff --git a/test/src/util.dart b/test/src/util.dart
index 6e689b7..2b262e1 100644
--- a/test/src/util.dart
+++ b/test/src/util.dart
@@ -34,18 +34,18 @@
 /// it's a subdirectory, otherwise it's a file and the value is the content
 /// as a string.
 void loaderTest(String name, Map<String, Object> description,
-    void loaderTest(Uri root, Future<Uint8List> loader(Uri uri))) {
+    void loaderTest(Uri root, Future<Uint8List?> loader(Uri uri))) {
   var root = Uri(scheme: "test", path: "/");
-  Future<Uint8List> loader(Uri uri) async {
+  Future<Uint8List?> loader(Uri uri) async {
     var path = uri.path;
     if (!uri.isScheme("test") || !path.startsWith("/")) return null;
     var parts = path.split("/");
-    dynamic value = description;
+    Object? value = description;
     for (var i = 1; i < parts.length; i++) {
-      if (value is! Map<String, dynamic>) return null;
+      if (value is! Map<String, Object?>) return null;
       value = value[parts[i]];
     }
-    if (value is String) return utf8.encode(value);
+    if (value is String) return utf8.encode(value) as Uint8List;
     return null;
   }
 
diff --git a/test/src/util_io.dart b/test/src/util_io.dart
index d05618a..37deee9 100644
--- a/test/src/util_io.dart
+++ b/test/src/util_io.dart
@@ -35,20 +35,20 @@
 /// with the content as description.
 /// Otherwise the content should be a string,
 /// which is written to the file as UTF-8.
-Directory createTestFiles(Map<String, Object> description) {
-  var target = Directory.systemTemp.createTempSync("pkgcfgtest");
-  _createFiles(target, description);
-  return target;
-}
+// Directory createTestFiles(Map<String, Object> description) {
+//   var target = Directory.systemTemp.createTempSync("pkgcfgtest");
+//   _createFiles(target, description);
+//   return target;
+// }
 
 // Creates temporary files in the target directory.
-void _createFiles(Directory target, Map<Object, Object> description) {
+void _createFiles(Directory target, Map<Object?, Object?> description) {
   description.forEach((name, content) {
     var entryName = pathJoin(target.path, "$name");
-    if (content is Map<Object, Object>) {
+    if (content is Map<Object?, Object?>) {
       _createFiles(Directory(entryName)..createSync(), content);
     } else {
-      File(entryName).writeAsStringSync(content, flush: true);
+      File(entryName).writeAsStringSync(content as String, flush: true);
     }
   });
 }