Re-land "[package_config] Remove support for `.packages` files. (#2034)" (#2065)

diff --git a/pkgs/package_config/CHANGELOG.md b/pkgs/package_config/CHANGELOG.md
index 51d9b45..83953a9 100644
--- a/pkgs/package_config/CHANGELOG.md
+++ b/pkgs/package_config/CHANGELOG.md
@@ -1,5 +1,24 @@
 ## 2.3.0-wip
 
+- Removes support for the `.packages` file.
+  The Dart SDK no longer supports that file, and no new `.packages` files
+  will be generated.
+  Since the SDK requirement for this package is above 3.0.0,
+  no supporting SDK can use or generate `.packages`.
+
+- Simplifies API that no longer needs to support two separate files.
+  - Renamed `readAnyConfigFile` to `readConfigFile`, and removed
+    the `preferNewest` parameter.
+  - Same for `readAnyConfigFileUri` which becomes `readConfigFileUri`.
+  - Old functions still exists as deprecated, forwarding to the new
+    functions without the `preferNewest` argument.
+
+  Also makes `PackageConfig`, `Package` and `LanguageVersion` `@sealed` classes,
+  in preparation for making them `final` in a future update.
+
+- Adds `PackageConfig.minVersion` to complement `.maxVersion`.
+  Currently both are `2`.
+
 ## 2.2.0
 
 - Add relational operators to `LanguageVersion` with extension methods
diff --git a/pkgs/package_config/README.md b/pkgs/package_config/README.md
index 76fd3cb..4e3f0b0 100644
--- a/pkgs/package_config/README.md
+++ b/pkgs/package_config/README.md
@@ -21,6 +21,5 @@
     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,
-and will be removed in a future version of this package.
+The package no longer contains backwards compatible functionality to
+work with `.packages` files.
diff --git a/pkgs/package_config/bin/package_config_of.dart b/pkgs/package_config/bin/package_config_of.dart
new file mode 100644
index 0000000..6e8f95b
--- /dev/null
+++ b/pkgs/package_config/bin/package_config_of.dart
@@ -0,0 +1,443 @@
+#! /bin/env dart
+// Copyright (c) 2025, 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.
+
+/// Utility for checking which package configuration applies to a specific file
+/// or path.
+library;
+
+import 'dart:convert';
+import 'dart:io';
+
+import 'package:package_config/package_config.dart';
+import 'package:path/path.dart' as p;
+
+/// Output modes.
+const _printText = 0;
+const _printJsonLines = 1;
+const _printJsonList = 2;
+
+void main(List<String> args) async {
+  // Basic command line parser. No fancy.
+  var files = <String>[];
+  var stopAtPubspec = false;
+  var noParent = false;
+  var hasPrintedUsage = false;
+  var parseFlags = true;
+  var printFormat = _printText;
+  for (var arg in args) {
+    if (parseFlags && arg.startsWith('-')) {
+      switch (arg) {
+        case '-b':
+          noParent = true;
+        case '-g':
+          stopAtPubspec = false;
+        case '-h':
+          if (!hasPrintedUsage) {
+            hasPrintedUsage = true;
+            stdout.writeln(usage);
+          }
+        case '-j':
+          printFormat = _printJsonLines;
+        case '-jl':
+          printFormat = _printJsonList;
+        case '-p':
+          stopAtPubspec = true;
+        case '--':
+          parseFlags = false;
+        default:
+          stderr.writeln('Unexpected flag: $arg');
+          if (!hasPrintedUsage) {
+            hasPrintedUsage = true;
+            stderr.writeln(usage);
+          }
+      }
+    } else {
+      files.add(arg);
+    }
+  }
+  if (hasPrintedUsage) return;
+
+  /// Check current directory if no PATHs on command line.
+  if (files.isEmpty) {
+    files.add(p.current);
+  }
+
+  var loader = PackageConfigLoader(
+    stopAtPubspec: stopAtPubspec,
+    noParent: noParent,
+  );
+
+  // Collects infos if printing as single JSON value.
+  // Otherwise prints output for each file as soon as it's available.
+  var jsonList = <Map<String, Object?>>[];
+
+  for (var arg in files) {
+    var fileInfo = await _resolveInfo(arg, loader);
+    if (fileInfo == null) continue; // File does not exist, already reported.
+    if (printFormat == _printText) {
+      // Display as "readable" text.
+      print(fileInfo);
+    } else if (printFormat == _printJsonLines) {
+      // Write JSON on a single line.
+      stdout.writeln(
+        const JsonEncoder.withIndent(null).convert(fileInfo.toJson()),
+      );
+    } else {
+      // Store JSON in list, print entire list later.
+      assert(printFormat == _printJsonList);
+      jsonList.add(fileInfo.toJson());
+    }
+  }
+  if (printFormat == _printJsonList) {
+    stdout.writeln(const JsonEncoder.withIndent('  ').convert(jsonList));
+  }
+}
+
+/// Finds package information for command line provided path.
+Future<ConfigInfo?> _resolveInfo(String arg, PackageConfigLoader loader) async {
+  var path = p.normalize(arg);
+  var file = File(path);
+  if (file.existsSync()) {
+    file = file.absolute;
+    var directory = Directory(p.dirname(file.path));
+    return await _resolvePackageConfig(directory, file, loader);
+  }
+  var directory = Directory(path);
+  if (directory.existsSync()) {
+    return await _resolvePackageConfig(directory.absolute, null, loader);
+  }
+  stderr.writeln('Cannot find file or directory: $arg');
+  return null;
+}
+
+// --------------------------------------------------------------------
+// Convert package configuration information to a simple model object.
+
+/// Extract package configuration information for a file from a configuration.
+Future<ConfigInfo> _resolvePackageConfig(
+  Directory path,
+  File? file,
+  PackageConfigLoader loader,
+) async {
+  var originPath = path.path;
+  var targetPath = file?.path ?? originPath;
+  var (configPath, config) = await loader.findPackageConfig(originPath);
+  Package? package;
+  Uri? packageUri;
+  LanguageVersion? overrideVersion;
+  if (config != null) {
+    var uri = file?.uri ?? path.uri;
+    package = config.packageOf(uri);
+    if (package != null) {
+      packageUri = config.toPackageUri(uri);
+    }
+  }
+  if (file != null) {
+    overrideVersion = _readOverrideVersion(file);
+  }
+  return ConfigInfo(
+    targetPath,
+    configPath,
+    package,
+    packageUri,
+    overrideVersion,
+  );
+}
+
+/// Gathered package configuration information for [path].
+final class ConfigInfo {
+  /// Original path being resolved.
+  final String path;
+
+  /// Path to package configuration file, if any.
+  final String? configPath;
+
+  /// Package that path belongs to, if any.
+  final Package? package;
+
+  /// Package URI for [path], if it has one.
+  /// Always `null` if [package] is `null`.
+  final Uri? packageUri;
+
+  /// Language version override in file, if any.
+  final LanguageVersion? languageVersionOverride;
+
+  ConfigInfo(
+    this.path,
+    this.configPath,
+    this.package,
+    this.packageUri,
+    this.languageVersionOverride,
+  );
+
+  Map<String, Object?> toJson() {
+    return {
+      JsonKey.path: path,
+      if (configPath != null) JsonKey.configPath: configPath,
+      if (package case var package?)
+        JsonKey.package: {
+          JsonKey.name: package.name,
+          JsonKey.root: _fileUriPath(package.root),
+          if (package.languageVersion case var languageVersion?)
+            JsonKey.languageVersion: languageVersion.toString(),
+          if (packageUri case var packageUri?) ...{
+            JsonKey.packageUri: packageUri.toString(),
+            if (package.root != package.packageUriRoot)
+              JsonKey.lib: _fileUriPath(package.packageUriRoot),
+          },
+        },
+      if (languageVersionOverride case var override?)
+        JsonKey.languageVersionOverride: override.toString(),
+    };
+  }
+
+  /// Package configuration information for a path in a readable format.
+  @override
+  String toString() {
+    var buffer = StringBuffer();
+    var sep = Platform.pathSeparator;
+    var kind = path.endsWith(Platform.pathSeparator) ? 'directory' : 'file';
+    buffer.writeln('Package configuration for $kind: ${p.relative(path)}');
+    if (configPath case var configPath?) {
+      buffer.writeln('- Package configuration: ${p.relative(configPath)}');
+      if (package case var package?) {
+        buffer.writeln('- In package: ${package.name}');
+        if (package.languageVersion case var version?) {
+          buffer.writeln('  - default language version: $version');
+        }
+        var rootUri = _fileUriPath(package.root);
+        var rootPath = p.relative(Directory.fromUri(Uri.parse(rootUri)).path);
+        buffer.writeln('  - with root: $rootPath$sep');
+        if (packageUri case var packageUri?) {
+          buffer.writeln('  - Has package URI: $packageUri');
+          if (package.root != package.packageUriRoot) {
+            var libPath = p.relative(
+              Directory.fromUri(package.packageUriRoot).path,
+            );
+            buffer.writeln('    - relative to: $libPath$sep');
+          } else {
+            buffer.writeln('    - relative to root');
+          }
+        }
+      } else {
+        buffer.writeln('- Is not part of any package');
+      }
+    } else {
+      buffer.writeln('- No package configuration found');
+      assert(package == null);
+    }
+    if (languageVersionOverride case var override?) {
+      buffer.writeln('- Language version override: // @dart=$override');
+    }
+    return buffer.toString();
+  }
+
+  static String _fileUriPath(Uri uri) {
+    assert(uri.isScheme('file'));
+    return File.fromUri(uri).path;
+  }
+}
+
+// Constants for all used JSON keys to prevent mis-typing.
+extension type const JsonKey(String value) implements String {
+  static const JsonKey path = JsonKey('path');
+  static const JsonKey configPath = JsonKey('configPath');
+  static const JsonKey package = JsonKey('package');
+  static const JsonKey name = JsonKey('name');
+  static const JsonKey root = JsonKey('root');
+  static const JsonKey packageUri = JsonKey('packageUri');
+  static const JsonKey lib = JsonKey('lib');
+  static const JsonKey languageVersion = JsonKey('languageVersion');
+  static const JsonKey languageVersionOverride = JsonKey(
+    'languageVersionOverride',
+  );
+}
+
+// --------------------------------------------------------------------
+// Find language version override marker in file.
+
+/// Tries to find a language override marker in a Dart file.
+///
+/// Uses a best-effort approach to scan for a line
+/// of the form `// @dart=X.Y` before any Dart directive.
+/// Any consistently and idiomatically formatted file will be recognized
+/// correctly. A file with a multiline `/*...*/` comment where
+/// internal lines do not start with `*`, or where a Dart directive
+/// follows a `*/` on the same line, may be misinterpreted.
+LanguageVersion? _readOverrideVersion(File file) {
+  String fileContent;
+  try {
+    fileContent = file.readAsStringSync();
+  } catch (e) {
+    stderr
+      ..writeln('Error reading ${file.path} as UTF-8 text:')
+      ..writeln(e);
+    return null;
+  }
+  // Skip BOM only at start.
+  const bom = '\uFEFF';
+  var contentStart = 0;
+  // Skip BOM only at start.
+  if (fileContent.startsWith(bom)) contentStart = 1;
+  // Skip `#! ...` line only at start.
+  if (fileContent.startsWith('#!', contentStart)) {
+    // Skip until end-of-line, whether ended by `\n` or `\r\n`.
+    var endOfHashBang = fileContent.indexOf('\n', contentStart + 2);
+    if (endOfHashBang < 0) return null; // No EOL after `#!`.
+    contentStart = endOfHashBang + 1;
+  }
+  // Match lines until one that looks like a version override or not a comment.
+  for (var match in leadRegExp.allMatches(fileContent, contentStart)) {
+    if (match.namedGroup('major') case var major?) {
+      // Found `// @dart=<major>.<minor>` line.
+      var minor = match.namedGroup('minor')!;
+      return LanguageVersion(int.parse(major), int.parse(minor));
+    } else if (match.namedGroup('other') != null) {
+      // Found non-comment, so too late for language version markers.
+      break;
+    }
+  }
+  return null;
+}
+
+/// Heuristic scanner for finding leading comment lines.
+///
+/// Finds leading comments and any language version override marker
+/// within them.
+///
+/// Accepts empty lines or lines starting with `/*`, `*` or `//` as
+/// initial comment lines, and any other non-space/tab first character
+/// as a non-comment, which ends the section
+/// that can contain language overrides.
+///
+/// It's possible to construct files where that's not correct, fx.
+/// ```
+/// /* something */ import "banana.dart";
+/// // @dart=2.14
+/// ```
+/// To be absolutely certain, the code would need to properly tokenize
+/// *nested* comments, which is not a job for a RegExp.
+/// This RegExp should work for well-behaved and -formatted files.
+final leadRegExp = RegExp(
+  r'^[ \t]*'
+  r'(?:'
+  r'$' // Empty line
+  r'|'
+  r'/?\*' // Line starting with `/*` or `*`, assumed a comment continuation.
+  r'|'
+  // Line starting with `//`, and possibly followed by language override.
+  r'//(?: *@ *dart *= *(?<major>\d+) *\. *(?<minor>\d+) *$)?'
+  r'|'
+  r'(?<other>[^ \t/*])' // Any other line, assumed to end initial comments.
+  r')',
+  multiLine: true,
+);
+
+// --------------------------------------------------------------------
+// Find and load (and cache) package configurations
+
+class PackageConfigLoader {
+  /// Stop searching at the current working directory.
+  final bool noParent;
+
+  /// Stop searching if finding a `pubspec.yaml` with no package configuration.
+  final bool stopAtPubspec;
+
+  /// Cache lookup results in case someone does more lookups on the same path.
+  final Map<
+    (String path, bool stopAtPubspec),
+    (String? configPath, PackageConfig? config)
+  >
+  _packageConfigCache = {};
+
+  PackageConfigLoader({this.stopAtPubspec = false, this.noParent = false});
+
+  /// Finds a package configuration relative to [path].
+  ///
+  /// Caches result for each directory looked at.
+  /// If someone does multiple lookups in the same directory, there is no need
+  /// to find and parse the same configuration more than once.
+  Future<(String? path, PackageConfig? config)> findPackageConfig(
+    String path,
+  ) async =>
+      _packageConfigCache[(
+        path,
+        stopAtPubspec,
+      )] ??= await _findPackageConfigNoCache(path);
+
+  Future<(String? path, PackageConfig? config)> _findPackageConfigNoCache(
+    String path,
+  ) async {
+    var configPath = p.join(path, '.dart_tool', 'package_config.json');
+    var configFile = File(configPath);
+    if (configFile.existsSync()) {
+      var hasError = false;
+      var config = await loadPackageConfig(
+        configFile.absolute,
+        onError: (error) {
+          stderr.writeln(
+            'Error parsing package configuration config ($configPath):\n'
+            ' $error',
+          );
+          hasError = true;
+        },
+      );
+      return (configPath, hasError ? null : config);
+    }
+    if (stopAtPubspec) {
+      var pubspecPath = p.join(path, 'pubspec.yaml');
+      var pubspecFile = File(pubspecPath);
+      if (pubspecFile.existsSync()) {
+        stderr
+          ..writeln('Found pubspec.yaml with no .dart_tool/package_config.json')
+          ..writeln('  at $path');
+        return (null, null);
+      }
+    }
+    if (noParent && path == p.current) return (null, null);
+    var parentPath = p.dirname(path);
+    if (parentPath == path) return (null, null);
+    // Recurse on parent path.
+    return findPackageConfig(parentPath);
+  }
+}
+
+const String usage = '''
+Usage: dart package_config_of.dart [-p|-j|-jl|-h] PATH*
+
+Searches from (each) PATH for a `.dart_tool/package_config.json` file,
+loads that, and prints information about that package configuration
+and the configuration for PATH in it.
+If no PATH is given, the current directory is used.
+
+Flags:
+  -p    : Stop when finding a 'pubspec.yaml' file without a package config.
+          The default is to ignore `pubspec.yaml` files and only look for
+          `.dart_tool/package_config.json` files, which will work correctly
+          for Pub workspaces.
+  -b    : Stop if reaching the current working directory.
+          When looking for a package configuration, don't try further out
+          than the current directory.
+          Only works for starting PATHs inside the current directory.
+  -j    : Output as JSON. Emits a single JSON object for each file, on
+          a line of its own (JSON-lines format). See format below.
+          Default is to output human readable text.
+  -jl   : Emits as a single JSON list containing the JSON outputs.
+          See format of individual elements below.
+          Default is to output human readable text.
+  -h    : Print this help text. Ignore any PATHs.
+
+A JSON object written using -j or -jl has following entries:
+    "path": "PATH", normalized and ends in a `/` if a directory.
+    "configPath": path to config file. Omitted if no or invalid config.
+    "package": The package that PATH belongs to, if any. A map with:
+        "name": Package name.
+        "root": Path to package root.
+        "languageVersion": "A.B", default language version for package.
+        "packageUri": The package: URI of PATH, if one exists.
+        "lib": If package URI exists, the package URI root,
+               unless it's the same as the package root.
+    "languageVersionOverride": "A.B", if file has '//@dart=' version override.
+''';
diff --git a/pkgs/package_config/lib/package_config.dart b/pkgs/package_config/lib/package_config.dart
index 2f2a612..fc61853 100644
--- a/pkgs/package_config/lib/package_config.dart
+++ b/pkgs/package_config/lib/package_config.dart
@@ -21,20 +21,7 @@
 
 /// Reads a specific package configuration file.
 ///
-/// The file must exist and be readable.
-/// It must be either a valid `package_config.json` file
-/// or a valid `.packages` file.
-/// It is considered a `package_config.json` file if its first character
-/// is a `{`.
-///
-/// If the file is a `.packages` file (the file name is `.packages`)
-/// and [preferNewest] is true, the default, also checks if there is
-/// a `.dart_tool/package_config.json` file next
-/// to the original file, and if so, loads that instead.
-/// If [preferNewest] is set to false, a directly specified `.packages` file
-/// is loaded even if there is an available `package_config.json` file.
-/// The caller can determine this from the [PackageConfig.version]
-/// being 1 and look for a `package_config.json` file themselves.
+/// The file must exist, be readable and be a valid `package_config.json` file.
 ///
 /// If [onError] is provided, the configuration file parsing will report errors
 /// by calling that function, and then try to recover.
@@ -43,27 +30,21 @@
 /// If no [onError] is provided, errors are thrown immediately.
 Future<PackageConfig> loadPackageConfig(
   File file, {
+  void Function(Object error)? onError,
+}) => readConfigFile(file, onError ?? throwError);
+
+/// @nodoc
+@Deprecated('use loadPackageConfig instead')
+Future<PackageConfig> loadAnyPackageConfig(
+  File file, {
   bool preferNewest = true,
   void Function(Object error)? onError,
-}) => readAnyConfigFile(file, preferNewest, onError ?? throwError);
+}) => loadPackageConfig(file, onError: onError);
 
 /// Reads a specific package configuration URI.
 ///
-/// The file of the URI must exist and be readable.
-/// It must be either a valid `package_config.json` file
-/// or a valid `.packages` file.
-/// It is considered a `package_config.json` file if its first
-/// non-whitespace character is a `{`.
-///
-/// If [preferNewest] is true, the default, and the file is a `.packages` file,
-/// as determined by its file name being `.packages`,
-/// first checks if there is a `.dart_tool/package_config.json` file
-/// next to the original file, and if so, loads that instead.
-/// The [file] *must not* be a `package:` URI.
-/// If [preferNewest] is set to false, a directly specified `.packages` file
-/// is loaded even if there is an available `package_config.json` file.
-/// The caller can determine this from the [PackageConfig.version]
-/// being 1 and look for a `package_config.json` file themselves.
+/// The file of the URI must exist, be readable,
+/// and be a valid `package_config.json` file.
 ///
 /// If [loader] is provided, URIs are loaded using that function.
 /// The future returned by the loader must complete with a [Uint8List]
@@ -76,7 +57,7 @@
 /// As such, it may throw any error that [loader] throws.
 ///
 /// If no [loader] is supplied, a default loader is used which
-/// only accepts `file:`,  `http:` and `https:` URIs,
+/// only accepts `file:`, `http:` and `https:` URIs,
 /// and which uses the platform file system and HTTP requests to
 /// fetch file content. The default loader never throws because
 /// of an I/O issue, as long as the location URIs are valid.
@@ -91,15 +72,21 @@
 Future<PackageConfig> loadPackageConfigUri(
   Uri file, {
   Future<Uint8List?> Function(Uri uri)? loader,
+  void Function(Object error)? onError,
+}) => readConfigFileUri(file, loader, onError ?? throwError);
+
+/// @nodoc
+@Deprecated('use loadPackageConfigUri instead')
+Future<PackageConfig> loadAnyPackageConfigUri(
+  Uri uri, {
   bool preferNewest = true,
   void Function(Object error)? onError,
-}) => readAnyConfigFileUri(file, loader, onError ?? throwError, preferNewest);
+}) => loadPackageConfigUri(uri, onError: onError);
 
 /// Finds a package configuration relative to [directory].
 ///
-/// If [directory] contains a package configuration,
-/// either a `.dart_tool/package_config.json` file or,
-/// if not, a `.packages`, then that file is loaded.
+/// If [directory] contains a `.dart_tool/package_config.json` file,
+/// then that file is loaded.
 ///
 /// If no file is found in the current directory,
 /// then the parent directories are checked recursively,
@@ -141,9 +128,8 @@
 
 /// Finds a package configuration relative to [location].
 ///
-/// If [location] contains a package configuration,
-/// either a `.dart_tool/package_config.json` file or,
-/// if not, a `.packages`, then that file is loaded.
+/// If [location] contains a `.dart_tool/package_config.json`
+/// package configuration file or, then that file is loaded.
 /// The [location] URI *must not* be a `package:` URI.
 /// It should be a hierarchical URI which is supported
 /// by [loader].
@@ -211,9 +197,6 @@
 /// If the `.dart_tool/` directory does not exist, it is created.
 /// If it cannot be created, this operation fails.
 ///
-/// Also writes a `.packages` file in [directory].
-/// This will stop happening eventually as the `.packages` file becomes
-/// discontinued.
 /// A comment is generated if `[PackageConfig.extraData]` contains a
 /// `"generator"` entry.
 Future<void> savePackageConfig(
diff --git a/pkgs/package_config/lib/src/discovery.dart b/pkgs/package_config/lib/src/discovery.dart
index a125166..7f9c23b 100644
--- a/pkgs/package_config/lib/src/discovery.dart
+++ b/pkgs/package_config/lib/src/discovery.dart
@@ -6,14 +6,12 @@
 import 'dart:typed_data';
 
 import 'errors.dart';
-import 'package_config_impl.dart';
+import 'package_config.dart';
 import 'package_config_io.dart';
 import 'package_config_json.dart';
-import 'packages_file.dart' as packages_file;
 import 'util_io.dart' show defaultLoader, pathJoin;
 
 final Uri packageConfigJsonPath = Uri(path: '.dart_tool/package_config.json');
-final Uri dotPackagesPath = Uri(path: '.packages');
 final Uri currentPath = Uri(path: '.');
 final Uri parentPath = Uri(path: '..');
 
@@ -24,16 +22,13 @@
 /// and stopping when something is found.
 ///
 /// * Check if a `.dart_tool/package_config.json` file exists in the directory.
-/// * Check if a `.packages` file exists in the directory
-///   (if `minVersion <= 1`).
-/// * Repeat these checks for the parent directories until reaching the
+/// * Repeat this check for the parent directories until reaching the
 ///   root directory if [recursive] is true.
 ///
-/// If any of these tests succeed, a `PackageConfig` class is returned.
+/// If any such a test succeeds, a `PackageConfig` class is returned.
 /// Returns `null` if no configuration was found. If a configuration
 /// is needed, then the caller can supply [PackageConfig.empty].
 ///
-/// If [minVersion] is greater than 1, `.packages` files are ignored.
 /// If [minVersion] is greater than the version read from the
 /// `package_config.json` file, it too is ignored.
 Future<PackageConfig?> findPackageConfig(
@@ -48,7 +43,6 @@
     return null;
   }
   do {
-    // Check for $cwd/.packages
     var packageConfig = await findPackageConfigInDirectory(
       directory,
       minVersion,
@@ -101,13 +95,6 @@
       var config = parsePackageConfigBytes(bytes, file, onError);
       if (config.version >= minVersion) return config;
     }
-    if (minVersion <= 1) {
-      file = location.resolveUri(dotPackagesPath);
-      bytes = await loader(file);
-      if (bytes != null) {
-        return packages_file.parse(bytes, file, onError);
-      }
-    }
     if (!recursive) break;
     var parent = location.resolveUri(parentPath);
     if (parent == location) break;
@@ -116,7 +103,7 @@
   return null;
 }
 
-/// Finds a `.packages` or `.dart_tool/package_config.json` file in [directory].
+/// Finds a `.dart_tool/package_config.json` file in [directory].
 ///
 /// Loads the file, if it is there, and returns the resulting [PackageConfig].
 /// Returns `null` if the file isn't there.
@@ -127,7 +114,6 @@
 /// a best-effort attempt is made to return a package configuration.
 /// This may be the empty package configuration.
 ///
-/// If [minVersion] is greater than 1, `.packages` files are ignored.
 /// If [minVersion] is greater than the version read from the
 /// `package_config.json` file, it too is ignored.
 Future<PackageConfig?> findPackageConfigInDirectory(
@@ -137,16 +123,10 @@
 ) async {
   var packageConfigFile = await checkForPackageConfigJsonFile(directory);
   if (packageConfigFile != null) {
-    var config = await readPackageConfigJsonFile(packageConfigFile, onError);
+    var config = await readConfigFile(packageConfigFile, onError);
     if (config.version < minVersion) return null;
     return config;
   }
-  if (minVersion <= 1) {
-    packageConfigFile = await checkForDotPackagesFile(directory);
-    if (packageConfigFile != null) {
-      return await readDotPackagesFile(packageConfigFile, onError);
-    }
-  }
   return null;
 }
 
@@ -158,9 +138,3 @@
   if (await file.exists()) return file;
   return null;
 }
-
-Future<File?> checkForDotPackagesFile(Directory directory) async {
-  var file = File(pathJoin(directory.path, '.packages'));
-  if (await file.exists()) return file;
-  return null;
-}
diff --git a/pkgs/package_config/lib/src/package_config.dart b/pkgs/package_config/lib/src/package_config.dart
index 0db7ac5..ce626e2 100644
--- a/pkgs/package_config/lib/src/package_config.dart
+++ b/pkgs/package_config/lib/src/package_config.dart
@@ -4,9 +4,11 @@
 
 import 'dart:typed_data';
 
+import 'package:meta/meta.dart' show sealed;
+
 import 'errors.dart';
-import 'package_config_impl.dart';
 import 'package_config_json.dart';
+import 'util.dart';
 
 /// A package configuration.
 ///
@@ -15,8 +17,12 @@
 /// More members may be added to this class in the future,
 /// so classes outside of this package must not implement [PackageConfig]
 /// or any subclass of it.
+@sealed
 abstract class PackageConfig {
-  /// The largest configuration version currently recognized.
+  /// The lowest configuration version currently supported.
+  static const int minVersion = 2;
+
+  /// The highest configuration version currently supported.
   static const int maxVersion = 2;
 
   /// An empty package configuration.
@@ -155,8 +161,8 @@
 
   /// The configuration version number.
   ///
-  /// Currently this is 1 or 2, where
-  /// * Version one is the `.packages` file format and
+  /// So far these have been 1 or 2, where
+  /// * Version one is the `.packages` file format, and is no longer supported.
   /// * Version two is the first `package_config.json` format.
   ///
   /// Instances of this class supports both, and the version
@@ -223,6 +229,7 @@
 }
 
 /// Configuration data for a single package.
+@sealed
 abstract class Package {
   /// Creates a package with the provided properties.
   ///
@@ -321,6 +328,7 @@
 /// If errors during parsing are handled using an `onError` handler,
 /// then an *invalid* language version may be represented by an
 /// [InvalidLanguageVersion] object.
+@sealed
 abstract class LanguageVersion implements Comparable<LanguageVersion> {
   /// The maximal value allowed by [major] and [minor] values;
   static const int maxValue = 0x7FFFFFFF;
@@ -330,11 +338,11 @@
   ///
   /// Both [major] and [minor] must be greater than or equal to 0
   /// and less than or equal to [maxValue].
-  factory LanguageVersion(int major, int minor) {
-    RangeError.checkValueInInterval(major, 0, maxValue, 'major');
-    RangeError.checkValueInInterval(minor, 0, maxValue, 'minor');
-    return SimpleLanguageVersion(major, minor, null);
-  }
+  factory LanguageVersion(int major, int minor) => SimpleLanguageVersion(
+    RangeError.checkValueInInterval(major, 0, maxValue, 'major'),
+    RangeError.checkValueInInterval(minor, 0, maxValue, 'minor'),
+    null,
+  );
 
   /// Parses a language version string.
   ///
@@ -376,13 +384,16 @@
 
   /// Compares language versions.
   ///
-  /// Two language versions are considered equal if they have the
-  /// same major and minor version numbers.
+  /// Two language versions are equal if they have the same
+  /// major and minor version numbers.
   ///
   /// A language version is greater than another if the former's major version
   /// is greater than the latter's major version, or if they have
   /// the same major version and the former's minor version is greater than
   /// the latter's.
+  ///
+  /// Invalid language versions are ordered before all valid versions,
+  /// and are all ordered together.
   @override
   int compareTo(LanguageVersion other);
 
@@ -409,7 +420,10 @@
 ///
 /// Stored in a [Package] when the original language version string
 /// was invalid and a `onError` handler was passed to the parser
-/// which did not throw on an error.
+/// which did not throw for that error.
+/// The caller which provided the non-throwing `onError` handler
+/// should be prepared to encounter invalid values.
+@sealed
 abstract class InvalidLanguageVersion implements LanguageVersion {
   /// The value -1 for an invalid language version.
   @override
@@ -431,63 +445,49 @@
   String toString();
 }
 
-/// Relational operators for [LanguageVersion] that
-/// compare valid versions with [LanguageVersion.compareTo].
+/// Relational operators for [LanguageVersion].
 ///
-/// If either operand is an [InvalidLanguageVersion], a [StateError] is thrown.
-/// Versions should be verified as valid after parsing and before using them.
+/// Compares valid versions with [LanguageVersion.compareTo],
+/// and rejects invalid versions.
+///
+/// Versions should be verified as valid before using them.
 extension LanguageVersionRelationalOperators on LanguageVersion {
   /// Whether this language version is less than [other].
   ///
-  /// If either version being compared is an [InvalidLanguageVersion],
-  /// a [StateError] is thrown. Verify versions are valid before comparing them.
+  /// Neither version being compared must be an [InvalidLanguageVersion].
   ///
   /// For details on how valid language versions are compared,
   /// check out [LanguageVersion.compareTo].
   bool operator <(LanguageVersion other) {
-    // Throw an error if comparing as or with an invalid language version.
-    if (this is InvalidLanguageVersion) {
-      _throwThisInvalid();
-    } else if (other is InvalidLanguageVersion) {
-      _throwOtherInvalid();
-    }
-
+    // Throw an error if comparing an invalid language version.
+    if (major < 0) _throwThisInvalid();
+    if (other.major < 0) _throwOtherInvalid();
     return compareTo(other) < 0;
   }
 
   /// Whether this language version is less than or equal to [other].
   ///
-  /// If either version being compared is an [InvalidLanguageVersion],
-  /// a [StateError] is thrown. Verify versions are valid before comparing them.
+  /// Neither version being compared must be an [InvalidLanguageVersion].
   ///
   /// For details on how valid language versions are compared,
   /// check out [LanguageVersion.compareTo].
   bool operator <=(LanguageVersion other) {
-    // Throw an error if comparing as or with an invalid language version.
-    if (this is InvalidLanguageVersion) {
-      _throwThisInvalid();
-    } else if (other is InvalidLanguageVersion) {
-      _throwOtherInvalid();
-    }
-
+    // Throw an error if comparing an invalid language version.
+    if (major < 0) _throwThisInvalid();
+    if (other.major < 0) _throwOtherInvalid();
     return compareTo(other) <= 0;
   }
 
   /// Whether this language version is greater than [other].
   ///
-  /// If either version being compared is an [InvalidLanguageVersion],
-  /// a [StateError] is thrown. Verify versions are valid before comparing them.
+  /// Neither version being compared must be an [InvalidLanguageVersion].
   ///
   /// For details on how valid language versions are compared,
   /// check out [LanguageVersion.compareTo].
   bool operator >(LanguageVersion other) {
-    // Throw an error if comparing as or with an invalid language version.
-    if (this is InvalidLanguageVersion) {
-      _throwThisInvalid();
-    } else if (other is InvalidLanguageVersion) {
-      _throwOtherInvalid();
-    }
-
+    // Throw an error if comparing an invalid language version.
+    if (major < 0) _throwThisInvalid();
+    if (other.major < 0) _throwOtherInvalid();
     return compareTo(other) > 0;
   }
 
@@ -499,25 +499,705 @@
   /// For details on how valid language versions are compared,
   /// check out [LanguageVersion.compareTo].
   bool operator >=(LanguageVersion other) {
-    // Throw an error if comparing as or with an invalid language version.
-    if (this is InvalidLanguageVersion) {
-      _throwThisInvalid();
-    } else if (other is InvalidLanguageVersion) {
-      _throwOtherInvalid();
-    }
-
+    // Throw an error if comparing an invalid language version.
+    if (major < 0) _throwThisInvalid();
+    if (other.major < 0) _throwOtherInvalid();
     return compareTo(other) >= 0;
   }
 
   static Never _throwThisInvalid() =>
-      throw StateError(
-        "Can't compare an invalid language version to another language version."
-        ' Verify language versions are valid after parsing.',
+      throw UnsupportedError(
+        'Can\'t compare an invalid language version to another '
+        'language version. '
+        'Verify language versions are valid before use.',
       );
-
   static Never _throwOtherInvalid() =>
-      throw StateError(
-        "Can't compare a language version to an invalid language version."
-        ' Verify language versions are valid after parsing.',
+      throw UnsupportedError(
+        'Can\'t compare a language version to an invalid language version. '
+        'Verify language versions are valid before use.',
       );
 }
+
+// --------------------------------------------------------------------
+// Implementation of interfaces. Not exported by top-level libraries.
+
+const bool _disallowPackagesInsidePackageUriRoot = false;
+
+// Implementations of the main data types exposed by the API of this package.
+
+@sealed
+class SimplePackageConfig implements PackageConfig {
+  @override
+  final int version;
+  final Map<String, Package> _packages;
+  final PackageTree _packageTree;
+  @override
+  final Object? extraData;
+
+  factory SimplePackageConfig(
+    int version,
+    Iterable<Package> packages, [
+    Object? extraData,
+    void Function(Object error)? onError,
+  ]) {
+    onError ??= throwError;
+    var validVersion = _validateVersion(version, onError);
+    var sortedPackages = [...packages]..sort(_compareRoot);
+    var packageTree = _validatePackages(packages, sortedPackages, onError);
+    return SimplePackageConfig._(validVersion, packageTree, {
+      for (var p in packageTree.allPackages) p.name: p,
+    }, extraData);
+  }
+
+  SimplePackageConfig._(
+    this.version,
+    this._packageTree,
+    this._packages,
+    this.extraData,
+  );
+
+  /// Creates empty configuration.
+  ///
+  /// The empty configuration can be used in cases where no configuration is
+  /// found, but code expects a non-null configuration.
+  ///
+  /// The version number is [PackageConfig.maxVersion] to avoid
+  /// minimum-version filters discarding the configuration.
+  const SimplePackageConfig.empty()
+    : version = PackageConfig.maxVersion,
+      _packageTree = const EmptyPackageTree(),
+      _packages = const <String, Package>{},
+      extraData = null;
+
+  static int _validateVersion(
+    int version,
+    void Function(Object error) onError,
+  ) {
+    if (version < 0 || version > PackageConfig.maxVersion) {
+      onError(
+        PackageConfigArgumentError(
+          version,
+          'version',
+          'Must be in the range 1 to ${PackageConfig.maxVersion}',
+        ),
+      );
+      return 2; // The minimal version supporting a SimplePackageConfig.
+    }
+    return version;
+  }
+
+  static PackageTree _validatePackages(
+    Iterable<Package> originalPackages,
+    List<Package> packages,
+    void Function(Object error) onError,
+  ) {
+    var packageNames = <String>{};
+    var tree = TriePackageTree();
+    for (var originalPackage in packages) {
+      SimplePackage? newPackage;
+      if (originalPackage is! SimplePackage) {
+        // SimplePackage validates these properties.
+        newPackage = SimplePackage.validate(
+          originalPackage.name,
+          originalPackage.root,
+          originalPackage.packageUriRoot,
+          originalPackage.languageVersion,
+          originalPackage.extraData,
+          originalPackage.relativeRoot,
+          (error) {
+            if (error is PackageConfigArgumentError) {
+              onError(
+                PackageConfigArgumentError(
+                  packages,
+                  'packages',
+                  'Package ${newPackage!.name}: ${error.message}',
+                ),
+              );
+            } else {
+              onError(error);
+            }
+          },
+        );
+        if (newPackage == null) continue;
+      } else {
+        newPackage = originalPackage;
+      }
+      var name = newPackage.name;
+      if (packageNames.contains(name)) {
+        onError(
+          PackageConfigArgumentError(
+            name,
+            'packages',
+            "Duplicate package name '$name'",
+          ),
+        );
+        continue;
+      }
+      packageNames.add(name);
+      tree.add(newPackage, (error) {
+        if (error is ConflictException) {
+          // There is a conflict with an existing package.
+          var existingPackage = error.existingPackage;
+          switch (error.conflictType) {
+            case ConflictType.sameRoots:
+              onError(
+                PackageConfigArgumentError(
+                  originalPackages,
+                  'packages',
+                  'Packages ${newPackage!.name} and ${existingPackage.name} '
+                      'have the same root directory: ${newPackage.root}.\n',
+                ),
+              );
+              break;
+            case ConflictType.interleaving:
+              // The new package is inside the package URI root of the existing
+              // package.
+              onError(
+                PackageConfigArgumentError(
+                  originalPackages,
+                  'packages',
+                  'Package ${newPackage!.name} is inside the root of '
+                      'package ${existingPackage.name}, and the package root '
+                      'of ${existingPackage.name} is inside the root of '
+                      '${newPackage.name}.\n'
+                      '${existingPackage.name} package root: '
+                      '${existingPackage.packageUriRoot}\n'
+                      '${newPackage.name} root: ${newPackage.root}\n',
+                ),
+              );
+              break;
+            case ConflictType.insidePackageRoot:
+              onError(
+                PackageConfigArgumentError(
+                  originalPackages,
+                  'packages',
+                  'Package ${newPackage!.name} is inside the package root of '
+                      'package ${existingPackage.name}.\n'
+                      '${existingPackage.name} package root: '
+                      '${existingPackage.packageUriRoot}\n'
+                      '${newPackage.name} root: ${newPackage.root}\n',
+                ),
+              );
+              break;
+          }
+        } else {
+          // Any other error.
+          onError(error);
+        }
+      });
+    }
+    return tree;
+  }
+
+  @override
+  Iterable<Package> get packages => _packages.values;
+
+  @override
+  Package? operator [](String packageName) => _packages[packageName];
+
+  @override
+  Package? packageOf(Uri file) => _packageTree.packageOf(file);
+
+  @override
+  Uri? resolve(Uri packageUri) {
+    var packageName = checkValidPackageUri(packageUri, 'packageUri');
+    return _packages[packageName]?.packageUriRoot.resolveUri(
+      Uri(path: packageUri.path.substring(packageName.length + 1)),
+    );
+  }
+
+  @override
+  Uri? toPackageUri(Uri nonPackageUri) {
+    if (nonPackageUri.isScheme('package')) {
+      throw PackageConfigArgumentError(
+        nonPackageUri,
+        'nonPackageUri',
+        'Must not be a package URI',
+      );
+    }
+    if (nonPackageUri.hasQuery || nonPackageUri.hasFragment) {
+      throw PackageConfigArgumentError(
+        nonPackageUri,
+        'nonPackageUri',
+        'Must not have query or fragment part',
+      );
+    }
+    // Find package that file belongs to.
+    var package = _packageTree.packageOf(nonPackageUri);
+    if (package == null) return null;
+    // Check if it is inside the package URI root.
+    var path = nonPackageUri.toString();
+    var root = package.packageUriRoot.toString();
+    if (_beginsWith(package.root.toString().length, root, path)) {
+      var rest = path.substring(root.length);
+      return Uri(scheme: 'package', path: '${package.name}/$rest');
+    }
+    return null;
+  }
+}
+
+/// Configuration data for a single package.
+@sealed
+class SimplePackage implements Package {
+  @override
+  final String name;
+  @override
+  final Uri root;
+  @override
+  final Uri packageUriRoot;
+  @override
+  final LanguageVersion? languageVersion;
+  @override
+  final Object? extraData;
+  @override
+  final bool relativeRoot;
+
+  SimplePackage._(
+    this.name,
+    this.root,
+    this.packageUriRoot,
+    this.languageVersion,
+    this.extraData,
+    this.relativeRoot,
+  );
+
+  /// Creates a [SimplePackage] with the provided content.
+  ///
+  /// The provided arguments must be valid.
+  ///
+  /// If the arguments are invalid then the error is reported by
+  /// calling [onError], then the erroneous entry is ignored.
+  ///
+  /// If [onError] is provided, the user is expected to be able to handle
+  /// errors themselves. An invalid [languageVersion] string
+  /// will be replaced with the string `"invalid"`. This allows
+  /// users to detect the difference between an absent version and
+  /// an invalid one.
+  ///
+  /// Returns `null` if the input is invalid and an approximately valid package
+  /// cannot be salvaged from the input.
+  static SimplePackage? validate(
+    String name,
+    Uri root,
+    Uri? packageUriRoot,
+    LanguageVersion? languageVersion,
+    Object? extraData,
+    bool relativeRoot,
+    void Function(Object error) onError,
+  ) {
+    var fatalError = false;
+    var invalidIndex = checkPackageName(name);
+    if (invalidIndex >= 0) {
+      onError(
+        PackageConfigFormatException(
+          'Not a valid package name',
+          name,
+          invalidIndex,
+        ),
+      );
+      fatalError = true;
+    }
+    if (root.isScheme('package')) {
+      onError(
+        PackageConfigArgumentError(
+          '$root',
+          'root',
+          'Must not be a package URI',
+        ),
+      );
+      fatalError = true;
+    } else if (!isAbsoluteDirectoryUri(root)) {
+      onError(
+        PackageConfigArgumentError(
+          '$root',
+          'root',
+          'In package $name: Not an absolute URI with no query or fragment '
+              'with a path ending in /',
+        ),
+      );
+      // Try to recover. If the URI has a scheme,
+      // then ensure that the path ends with `/`.
+      if (!root.hasScheme) {
+        fatalError = true;
+      } else if (!root.path.endsWith('/')) {
+        root = root.replace(path: '${root.path}/');
+      }
+    }
+    if (packageUriRoot == null) {
+      packageUriRoot = root;
+    } else if (!fatalError) {
+      packageUriRoot = root.resolveUri(packageUriRoot);
+      if (!isAbsoluteDirectoryUri(packageUriRoot)) {
+        onError(
+          PackageConfigArgumentError(
+            packageUriRoot,
+            'packageUriRoot',
+            'In package $name: Not an absolute URI with no query or fragment '
+                'with a path ending in /',
+          ),
+        );
+        packageUriRoot = root;
+      } else if (!isUriPrefix(root, packageUriRoot)) {
+        onError(
+          PackageConfigArgumentError(
+            packageUriRoot,
+            'packageUriRoot',
+            'The package URI root is not below the package root',
+          ),
+        );
+        packageUriRoot = root;
+      }
+    }
+    if (fatalError) return null;
+    return SimplePackage._(
+      name,
+      root,
+      packageUriRoot,
+      languageVersion,
+      extraData,
+      relativeRoot,
+    );
+  }
+}
+
+/// Checks whether [source] is a valid Dart language version string.
+///
+/// The format is (as RegExp) `^(0|[1-9]\d+)\.(0|[1-9]\d+)$`.
+///
+/// Reports a format exception by calling [onError] if the format isn't correct,
+/// or if the numbers are too large (at most 32-bit signed integers).
+LanguageVersion parseLanguageVersion(
+  String source,
+  void Function(Object error) onError,
+) {
+  var index = 0;
+  // Reads a positive decimal numeral. Returns the value of the numeral,
+  // or a negative number in case of an error.
+  // Starts at [index] and increments the index to the position after
+  // the numeral.
+  // It is an error if the numeral value is greater than 0x7FFFFFFFF.
+  // It is a recoverable error if the numeral starts with leading zeros.
+  int readNumeral() {
+    const maxValue = 0x7FFFFFFF;
+    if (index == source.length) {
+      onError(PackageConfigFormatException('Missing number', source, index));
+      return -1;
+    }
+    var start = index;
+
+    var char = source.codeUnitAt(index);
+    var digit = char ^ 0x30;
+    if (digit > 9) {
+      onError(PackageConfigFormatException('Missing number', source, index));
+      return -1;
+    }
+    var firstDigit = digit;
+    var value = 0;
+    do {
+      value = value * 10 + digit;
+      if (value > maxValue) {
+        onError(
+          PackageConfigFormatException('Number too large', source, start),
+        );
+        return -1;
+      }
+      index++;
+      if (index == source.length) break;
+      char = source.codeUnitAt(index);
+      digit = char ^ 0x30;
+    } while (digit <= 9);
+    if (firstDigit == 0 && index > start + 1) {
+      onError(
+        PackageConfigFormatException('Leading zero not allowed', source, start),
+      );
+    }
+    return value;
+  }
+
+  var major = readNumeral();
+  if (major < 0) {
+    return SimpleInvalidLanguageVersion(source);
+  }
+  if (index == source.length || source.codeUnitAt(index) != $dot) {
+    onError(PackageConfigFormatException("Missing '.'", source, index));
+    return SimpleInvalidLanguageVersion(source);
+  }
+  index++;
+  var minor = readNumeral();
+  if (minor < 0) {
+    return SimpleInvalidLanguageVersion(source);
+  }
+  if (index != source.length) {
+    onError(
+      PackageConfigFormatException(
+        'Unexpected trailing character',
+        source,
+        index,
+      ),
+    );
+    return SimpleInvalidLanguageVersion(source);
+  }
+  return SimpleLanguageVersion(major, minor, source);
+}
+
+@sealed
+class SimpleLanguageVersion implements LanguageVersion {
+  @override
+  final int major;
+  @override
+  final int minor;
+
+  /// A cache for `toString`, pre-filled with source if created by parsing.
+  ///
+  /// Also used by [SimpleInvalidLanguageVersion] for its invalid source
+  /// or a suitably invalid `toString` value.
+  String? _source;
+
+  SimpleLanguageVersion(this.major, this.minor, this._source);
+
+  @override
+  bool operator ==(Object other) =>
+      other is LanguageVersion && major == other.major && minor == other.minor;
+
+  @override
+  int get hashCode => (major * 17 ^ minor * 37) & 0x3FFFFFFF;
+
+  @override
+  String toString() => _source ??= '$major.$minor';
+
+  @override
+  int compareTo(LanguageVersion other) {
+    var result = major - other.major;
+    if (result != 0) return result;
+    return minor - other.minor;
+  }
+}
+
+@sealed
+class SimpleInvalidLanguageVersion extends SimpleLanguageVersion
+    implements InvalidLanguageVersion {
+  SimpleInvalidLanguageVersion(String source) : super(-1, -1, source);
+
+  @override
+  int get hashCode => identityHashCode(this);
+  @override
+  bool operator ==(Object other) => identical(this, other);
+}
+
+abstract class PackageTree {
+  Iterable<Package> get allPackages;
+  SimplePackage? packageOf(Uri file);
+}
+
+class _PackageTrieNode {
+  SimplePackage? package;
+
+  /// Indexed by path segment.
+  Map<String, _PackageTrieNode> map = {};
+}
+
+/// Packages of a package configuration ordered by root path.
+///
+/// A package has a root path and a package root path, where the latter
+/// contains the files exposed by `package:` URIs.
+///
+/// A package is said to be inside another package if the root path URI of
+/// the latter is a prefix of the root path URI of the former.
+///
+/// No two packages of a package may have the same root path.
+/// The package root path of a package must not be inside another package's
+/// root path.
+/// Entire other packages are allowed inside a package's root.
+class TriePackageTree implements PackageTree {
+  /// Indexed by URI scheme.
+  final Map<String, _PackageTrieNode> _map = {};
+
+  /// A list of all packages.
+  final List<SimplePackage> _packages = [];
+
+  @override
+  Iterable<Package> get allPackages sync* {
+    for (var package in _packages) {
+      yield package;
+    }
+  }
+
+  bool _checkConflict(
+    _PackageTrieNode node,
+    SimplePackage newPackage,
+    void Function(Object error) onError,
+  ) {
+    var existingPackage = node.package;
+    if (existingPackage != null) {
+      // Trying to add package that is inside the existing package.
+      // 1) If it's an exact match it's not allowed (i.e. the roots can't be
+      //    the same).
+      if (newPackage.root.path.length == existingPackage.root.path.length) {
+        onError(
+          ConflictException(
+            newPackage,
+            existingPackage,
+            ConflictType.sameRoots,
+          ),
+        );
+        return true;
+      }
+      // 2) The existing package has a packageUriRoot thats inside the
+      //    root of the new package.
+      if (_beginsWith(
+        0,
+        newPackage.root.toString(),
+        existingPackage.packageUriRoot.toString(),
+      )) {
+        onError(
+          ConflictException(
+            newPackage,
+            existingPackage,
+            ConflictType.interleaving,
+          ),
+        );
+        return true;
+      }
+
+      // For internal reasons we allow this (for now). One should still never do
+      // it though.
+      // 3) The new package is inside the packageUriRoot of existing package.
+      if (_disallowPackagesInsidePackageUriRoot) {
+        if (_beginsWith(
+          0,
+          existingPackage.packageUriRoot.toString(),
+          newPackage.root.toString(),
+        )) {
+          onError(
+            ConflictException(
+              newPackage,
+              existingPackage,
+              ConflictType.insidePackageRoot,
+            ),
+          );
+          return true;
+        }
+      }
+    }
+    return false;
+  }
+
+  /// Tries to add `newPackage` to the tree.
+  ///
+  /// Reports a [ConflictException] if the added package conflicts with an
+  /// existing package.
+  /// It conflicts if its root or package root is the same as an existing
+  /// package's root or package root, is between the two, or if it's inside the
+  /// package root of an existing package.
+  ///
+  /// If a conflict is detected between [newPackage] and a previous package,
+  /// then [onError] is called with a [ConflictException] object
+  /// and the [newPackage] is not added to the tree.
+  ///
+  /// The packages are added in order of their root path.
+  void add(SimplePackage newPackage, void Function(Object error) onError) {
+    var root = newPackage.root;
+    var node = _map[root.scheme] ??= _PackageTrieNode();
+    if (_checkConflict(node, newPackage, onError)) return;
+    var segments = root.pathSegments;
+    // Notice that we're skipping the last segment as it's always the empty
+    // string because roots are directories.
+    for (var i = 0; i < segments.length - 1; i++) {
+      var path = segments[i];
+      node = node.map[path] ??= _PackageTrieNode();
+      if (_checkConflict(node, newPackage, onError)) return;
+    }
+    node.package = newPackage;
+    _packages.add(newPackage);
+  }
+
+  bool _isMatch(
+    String path,
+    _PackageTrieNode node,
+    List<SimplePackage> potential,
+  ) {
+    var currentPackage = node.package;
+    if (currentPackage != null) {
+      var currentPackageRootLength = currentPackage.root.toString().length;
+      if (path.length == currentPackageRootLength) return true;
+      var currentPackageUriRoot = currentPackage.packageUriRoot.toString();
+      // Is [file] inside the package root of [currentPackage]?
+      if (currentPackageUriRoot.length == currentPackageRootLength ||
+          _beginsWith(currentPackageRootLength, currentPackageUriRoot, path)) {
+        return true;
+      }
+      potential.add(currentPackage);
+    }
+    return false;
+  }
+
+  @override
+  SimplePackage? packageOf(Uri file) {
+    var currentTrieNode = _map[file.scheme];
+    if (currentTrieNode == null) return null;
+    var path = file.toString();
+    var potential = <SimplePackage>[];
+    if (_isMatch(path, currentTrieNode, potential)) {
+      return currentTrieNode.package;
+    }
+    var segments = file.pathSegments;
+
+    for (var i = 0; i < segments.length - 1; i++) {
+      var segment = segments[i];
+      currentTrieNode = currentTrieNode!.map[segment];
+      if (currentTrieNode == null) break;
+      if (_isMatch(path, currentTrieNode, potential)) {
+        return currentTrieNode.package;
+      }
+    }
+    if (potential.isEmpty) return null;
+    return potential.last;
+  }
+}
+
+class EmptyPackageTree implements PackageTree {
+  const EmptyPackageTree();
+
+  @override
+  Iterable<Package> get allPackages => const Iterable<Package>.empty();
+
+  @override
+  SimplePackage? packageOf(Uri file) => null;
+}
+
+/// Checks whether [longerPath] begins with [parentPath].
+///
+/// Skips checking the [start] first characters which are assumed to
+/// already have been matched.
+bool _beginsWith(int start, String parentPath, String longerPath) {
+  if (longerPath.length < parentPath.length) return false;
+  for (var i = start; i < parentPath.length; i++) {
+    if (longerPath.codeUnitAt(i) != parentPath.codeUnitAt(i)) return false;
+  }
+  return true;
+}
+
+enum ConflictType { sameRoots, interleaving, insidePackageRoot }
+
+/// Conflict between packages added to the same configuration.
+///
+/// The [package] conflicts with [existingPackage] if it has
+/// the same root path or the package URI root path
+/// of [existingPackage] is inside the root path of [package].
+class ConflictException {
+  /// The existing package that [package] conflicts with.
+  final SimplePackage existingPackage;
+
+  /// The package that could not be added without a conflict.
+  final SimplePackage package;
+
+  /// Whether the conflict is with the package URI root of [existingPackage].
+  final ConflictType conflictType;
+
+  /// Creates a root conflict between [package] and [existingPackage].
+  ConflictException(this.package, this.existingPackage, this.conflictType);
+}
+
+/// Used for sorting packages by root path.
+int _compareRoot(Package p1, Package p2) =>
+    p1.root.toString().compareTo(p2.root.toString());
diff --git a/pkgs/package_config/lib/src/package_config_impl.dart b/pkgs/package_config/lib/src/package_config_impl.dart
deleted file mode 100644
index a33ccf8..0000000
--- a/pkgs/package_config/lib/src/package_config_impl.dart
+++ /dev/null
@@ -1,685 +0,0 @@
-// Copyright (c) 2019, the Dart project authors.  Please see the AUTHORS file
-// for details. All rights reserved. Use of this source code is governed by a
-// BSD-style license that can be found in the LICENSE file.
-
-import 'errors.dart';
-import 'package_config.dart';
-import 'util.dart';
-
-export 'package_config.dart';
-
-const bool _disallowPackagesInsidePackageUriRoot = false;
-
-// Implementations of the main data types exposed by the API of this package.
-
-class SimplePackageConfig implements PackageConfig {
-  @override
-  final int version;
-  final Map<String, Package> _packages;
-  final PackageTree _packageTree;
-  @override
-  final Object? extraData;
-
-  factory SimplePackageConfig(
-    int version,
-    Iterable<Package> packages, [
-    Object? extraData,
-    void Function(Object error)? onError,
-  ]) {
-    onError ??= throwError;
-    var validVersion = _validateVersion(version, onError);
-    var sortedPackages = [...packages]..sort(_compareRoot);
-    var packageTree = _validatePackages(packages, sortedPackages, onError);
-    return SimplePackageConfig._(validVersion, packageTree, {
-      for (var p in packageTree.allPackages) p.name: p,
-    }, extraData);
-  }
-
-  SimplePackageConfig._(
-    this.version,
-    this._packageTree,
-    this._packages,
-    this.extraData,
-  );
-
-  /// Creates empty configuration.
-  ///
-  /// The empty configuration can be used in cases where no configuration is
-  /// found, but code expects a non-null configuration.
-  ///
-  /// The version number is [PackageConfig.maxVersion] to avoid
-  /// minimum-version filters discarding the configuration.
-  const SimplePackageConfig.empty()
-    : version = PackageConfig.maxVersion,
-      _packageTree = const EmptyPackageTree(),
-      _packages = const <String, Package>{},
-      extraData = null;
-
-  static int _validateVersion(
-    int version,
-    void Function(Object error) onError,
-  ) {
-    if (version < 0 || version > PackageConfig.maxVersion) {
-      onError(
-        PackageConfigArgumentError(
-          version,
-          'version',
-          'Must be in the range 1 to ${PackageConfig.maxVersion}',
-        ),
-      );
-      return 2; // The minimal version supporting a SimplePackageConfig.
-    }
-    return version;
-  }
-
-  static PackageTree _validatePackages(
-    Iterable<Package> originalPackages,
-    List<Package> packages,
-    void Function(Object error) onError,
-  ) {
-    var packageNames = <String>{};
-    var tree = TriePackageTree();
-    for (var originalPackage in packages) {
-      SimplePackage? newPackage;
-      if (originalPackage is! SimplePackage) {
-        // SimplePackage validates these properties.
-        newPackage = SimplePackage.validate(
-          originalPackage.name,
-          originalPackage.root,
-          originalPackage.packageUriRoot,
-          originalPackage.languageVersion,
-          originalPackage.extraData,
-          originalPackage.relativeRoot,
-          (error) {
-            if (error is PackageConfigArgumentError) {
-              onError(
-                PackageConfigArgumentError(
-                  packages,
-                  'packages',
-                  'Package ${newPackage!.name}: ${error.message}',
-                ),
-              );
-            } else {
-              onError(error);
-            }
-          },
-        );
-        if (newPackage == null) continue;
-      } else {
-        newPackage = originalPackage;
-      }
-      var name = newPackage.name;
-      if (packageNames.contains(name)) {
-        onError(
-          PackageConfigArgumentError(
-            name,
-            'packages',
-            "Duplicate package name '$name'",
-          ),
-        );
-        continue;
-      }
-      packageNames.add(name);
-      tree.add(newPackage, (error) {
-        if (error is ConflictException) {
-          // There is a conflict with an existing package.
-          var existingPackage = error.existingPackage;
-          switch (error.conflictType) {
-            case ConflictType.sameRoots:
-              onError(
-                PackageConfigArgumentError(
-                  originalPackages,
-                  'packages',
-                  'Packages ${newPackage!.name} and ${existingPackage.name} '
-                      'have the same root directory: ${newPackage.root}.\n',
-                ),
-              );
-              break;
-            case ConflictType.interleaving:
-              // The new package is inside the package URI root of the existing
-              // package.
-              onError(
-                PackageConfigArgumentError(
-                  originalPackages,
-                  'packages',
-                  'Package ${newPackage!.name} is inside the root of '
-                      'package ${existingPackage.name}, and the package root '
-                      'of ${existingPackage.name} is inside the root of '
-                      '${newPackage.name}.\n'
-                      '${existingPackage.name} package root: '
-                      '${existingPackage.packageUriRoot}\n'
-                      '${newPackage.name} root: ${newPackage.root}\n',
-                ),
-              );
-              break;
-            case ConflictType.insidePackageRoot:
-              onError(
-                PackageConfigArgumentError(
-                  originalPackages,
-                  'packages',
-                  'Package ${newPackage!.name} is inside the package root of '
-                      'package ${existingPackage.name}.\n'
-                      '${existingPackage.name} package root: '
-                      '${existingPackage.packageUriRoot}\n'
-                      '${newPackage.name} root: ${newPackage.root}\n',
-                ),
-              );
-              break;
-          }
-        } else {
-          // Any other error.
-          onError(error);
-        }
-      });
-    }
-    return tree;
-  }
-
-  @override
-  Iterable<Package> get packages => _packages.values;
-
-  @override
-  Package? operator [](String packageName) => _packages[packageName];
-
-  @override
-  Package? packageOf(Uri file) => _packageTree.packageOf(file);
-
-  @override
-  Uri? resolve(Uri packageUri) {
-    var packageName = checkValidPackageUri(packageUri, 'packageUri');
-    return _packages[packageName]?.packageUriRoot.resolveUri(
-      Uri(path: packageUri.path.substring(packageName.length + 1)),
-    );
-  }
-
-  @override
-  Uri? toPackageUri(Uri nonPackageUri) {
-    if (nonPackageUri.isScheme('package')) {
-      throw PackageConfigArgumentError(
-        nonPackageUri,
-        'nonPackageUri',
-        'Must not be a package URI',
-      );
-    }
-    if (nonPackageUri.hasQuery || nonPackageUri.hasFragment) {
-      throw PackageConfigArgumentError(
-        nonPackageUri,
-        'nonPackageUri',
-        'Must not have query or fragment part',
-      );
-    }
-    // Find package that file belongs to.
-    var package = _packageTree.packageOf(nonPackageUri);
-    if (package == null) return null;
-    // Check if it is inside the package URI root.
-    var path = nonPackageUri.toString();
-    var root = package.packageUriRoot.toString();
-    if (_beginsWith(package.root.toString().length, root, path)) {
-      var rest = path.substring(root.length);
-      return Uri(scheme: 'package', path: '${package.name}/$rest');
-    }
-    return null;
-  }
-}
-
-/// Configuration data for a single package.
-class SimplePackage implements Package {
-  @override
-  final String name;
-  @override
-  final Uri root;
-  @override
-  final Uri packageUriRoot;
-  @override
-  final LanguageVersion? languageVersion;
-  @override
-  final Object? extraData;
-  @override
-  final bool relativeRoot;
-
-  SimplePackage._(
-    this.name,
-    this.root,
-    this.packageUriRoot,
-    this.languageVersion,
-    this.extraData,
-    this.relativeRoot,
-  );
-
-  /// Creates a [SimplePackage] with the provided content.
-  ///
-  /// The provided arguments must be valid.
-  ///
-  /// If the arguments are invalid then the error is reported by
-  /// calling [onError], then the erroneous entry is ignored.
-  ///
-  /// If [onError] is provided, the user is expected to be able to handle
-  /// errors themselves. An invalid [languageVersion] string
-  /// will be replaced with the string `"invalid"`. This allows
-  /// users to detect the difference between an absent version and
-  /// an invalid one.
-  ///
-  /// Returns `null` if the input is invalid and an approximately valid package
-  /// cannot be salvaged from the input.
-  static SimplePackage? validate(
-    String name,
-    Uri root,
-    Uri? packageUriRoot,
-    LanguageVersion? languageVersion,
-    Object? extraData,
-    bool relativeRoot,
-    void Function(Object error) onError,
-  ) {
-    var fatalError = false;
-    var invalidIndex = checkPackageName(name);
-    if (invalidIndex >= 0) {
-      onError(
-        PackageConfigFormatException(
-          'Not a valid package name',
-          name,
-          invalidIndex,
-        ),
-      );
-      fatalError = true;
-    }
-    if (root.isScheme('package')) {
-      onError(
-        PackageConfigArgumentError(
-          '$root',
-          'root',
-          'Must not be a package URI',
-        ),
-      );
-      fatalError = true;
-    } else if (!isAbsoluteDirectoryUri(root)) {
-      onError(
-        PackageConfigArgumentError(
-          '$root',
-          'root',
-          'In package $name: Not an absolute URI with no query or fragment '
-              'with a path ending in /',
-        ),
-      );
-      // Try to recover. If the URI has a scheme,
-      // then ensure that the path ends with `/`.
-      if (!root.hasScheme) {
-        fatalError = true;
-      } else if (!root.path.endsWith('/')) {
-        root = root.replace(path: '${root.path}/');
-      }
-    }
-    if (packageUriRoot == null) {
-      packageUriRoot = root;
-    } else if (!fatalError) {
-      packageUriRoot = root.resolveUri(packageUriRoot);
-      if (!isAbsoluteDirectoryUri(packageUriRoot)) {
-        onError(
-          PackageConfigArgumentError(
-            packageUriRoot,
-            'packageUriRoot',
-            'In package $name: Not an absolute URI with no query or fragment '
-                'with a path ending in /',
-          ),
-        );
-        packageUriRoot = root;
-      } else if (!isUriPrefix(root, packageUriRoot)) {
-        onError(
-          PackageConfigArgumentError(
-            packageUriRoot,
-            'packageUriRoot',
-            'The package URI root is not below the package root',
-          ),
-        );
-        packageUriRoot = root;
-      }
-    }
-    if (fatalError) return null;
-    return SimplePackage._(
-      name,
-      root,
-      packageUriRoot,
-      languageVersion,
-      extraData,
-      relativeRoot,
-    );
-  }
-}
-
-/// Checks whether [source] is a valid Dart language version string.
-///
-/// The format is (as RegExp) `^(0|[1-9]\d+)\.(0|[1-9]\d+)$`.
-///
-/// 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 Function(Object error) onError,
-) {
-  var index = 0;
-  // Reads a positive decimal numeral. Returns the value of the numeral,
-  // or a negative number in case of an error.
-  // Starts at [index] and increments the index to the position after
-  // the numeral.
-  // It is an error if the numeral value is greater than 0x7FFFFFFFF.
-  // It is a recoverable error if the numeral starts with leading zeros.
-  int readNumeral() {
-    const maxValue = 0x7FFFFFFF;
-    if (index == source!.length) {
-      onError(PackageConfigFormatException('Missing number', source, index));
-      return -1;
-    }
-    var start = index;
-
-    var char = source.codeUnitAt(index);
-    var digit = char ^ 0x30;
-    if (digit > 9) {
-      onError(PackageConfigFormatException('Missing number', source, index));
-      return -1;
-    }
-    var firstDigit = digit;
-    var value = 0;
-    do {
-      value = value * 10 + digit;
-      if (value > maxValue) {
-        onError(
-          PackageConfigFormatException('Number too large', source, start),
-        );
-        return -1;
-      }
-      index++;
-      if (index == source.length) break;
-      char = source.codeUnitAt(index);
-      digit = char ^ 0x30;
-    } while (digit <= 9);
-    if (firstDigit == 0 && index > start + 1) {
-      onError(
-        PackageConfigFormatException('Leading zero not allowed', source, start),
-      );
-    }
-    return value;
-  }
-
-  var major = readNumeral();
-  if (major < 0) {
-    return SimpleInvalidLanguageVersion(source);
-  }
-  if (index == source!.length || source.codeUnitAt(index) != $dot) {
-    onError(PackageConfigFormatException("Missing '.'", source, index));
-    return SimpleInvalidLanguageVersion(source);
-  }
-  index++;
-  var minor = readNumeral();
-  if (minor < 0) {
-    return SimpleInvalidLanguageVersion(source);
-  }
-  if (index != source.length) {
-    onError(
-      PackageConfigFormatException(
-        'Unexpected trailing character',
-        source,
-        index,
-      ),
-    );
-    return SimpleInvalidLanguageVersion(source);
-  }
-  return SimpleLanguageVersion(major, minor, source);
-}
-
-abstract class _SimpleLanguageVersionBase implements LanguageVersion {
-  @override
-  int compareTo(LanguageVersion other) {
-    var result = major.compareTo(other.major);
-    if (result != 0) return result;
-    return minor.compareTo(other.minor);
-  }
-}
-
-class SimpleLanguageVersion extends _SimpleLanguageVersionBase {
-  @override
-  final int major;
-  @override
-  final int minor;
-  String? _source;
-  SimpleLanguageVersion(this.major, this.minor, this._source);
-
-  @override
-  bool operator ==(Object other) =>
-      other is LanguageVersion && major == other.major && minor == other.minor;
-
-  @override
-  int get hashCode => (major * 17 ^ minor * 37) & 0x3FFFFFFF;
-
-  @override
-  String toString() => _source ??= '$major.$minor';
-}
-
-class SimpleInvalidLanguageVersion extends _SimpleLanguageVersionBase
-    implements InvalidLanguageVersion {
-  final String? _source;
-  SimpleInvalidLanguageVersion(this._source);
-  @override
-  int get major => -1;
-  @override
-  int get minor => -1;
-
-  @override
-  String toString() => _source!;
-}
-
-abstract class PackageTree {
-  Iterable<Package> get allPackages;
-  SimplePackage? packageOf(Uri file);
-}
-
-class _PackageTrieNode {
-  SimplePackage? package;
-
-  /// Indexed by path segment.
-  Map<String, _PackageTrieNode> map = {};
-}
-
-/// Packages of a package configuration ordered by root path.
-///
-/// A package has a root path and a package root path, where the latter
-/// contains the files exposed by `package:` URIs.
-///
-/// A package is said to be inside another package if the root path URI of
-/// the latter is a prefix of the root path URI of the former.
-///
-/// No two packages of a package may have the same root path.
-/// The package root path of a package must not be inside another package's
-/// root path.
-/// Entire other packages are allowed inside a package's root.
-class TriePackageTree implements PackageTree {
-  /// Indexed by URI scheme.
-  final Map<String, _PackageTrieNode> _map = {};
-
-  /// A list of all packages.
-  final List<SimplePackage> _packages = [];
-
-  @override
-  Iterable<Package> get allPackages sync* {
-    for (var package in _packages) {
-      yield package;
-    }
-  }
-
-  bool _checkConflict(
-    _PackageTrieNode node,
-    SimplePackage newPackage,
-    void Function(Object error) onError,
-  ) {
-    var existingPackage = node.package;
-    if (existingPackage != null) {
-      // Trying to add package that is inside the existing package.
-      // 1) If it's an exact match it's not allowed (i.e. the roots can't be
-      //    the same).
-      if (newPackage.root.path.length == existingPackage.root.path.length) {
-        onError(
-          ConflictException(
-            newPackage,
-            existingPackage,
-            ConflictType.sameRoots,
-          ),
-        );
-        return true;
-      }
-      // 2) The existing package has a packageUriRoot thats inside the
-      //    root of the new package.
-      if (_beginsWith(
-        0,
-        newPackage.root.toString(),
-        existingPackage.packageUriRoot.toString(),
-      )) {
-        onError(
-          ConflictException(
-            newPackage,
-            existingPackage,
-            ConflictType.interleaving,
-          ),
-        );
-        return true;
-      }
-
-      // For internal reasons we allow this (for now). One should still never do
-      // it though.
-      // 3) The new package is inside the packageUriRoot of existing package.
-      if (_disallowPackagesInsidePackageUriRoot) {
-        if (_beginsWith(
-          0,
-          existingPackage.packageUriRoot.toString(),
-          newPackage.root.toString(),
-        )) {
-          onError(
-            ConflictException(
-              newPackage,
-              existingPackage,
-              ConflictType.insidePackageRoot,
-            ),
-          );
-          return true;
-        }
-      }
-    }
-    return false;
-  }
-
-  /// Tries to add `newPackage` to the tree.
-  ///
-  /// Reports a [ConflictException] if the added package conflicts with an
-  /// existing package.
-  /// It conflicts if its root or package root is the same as an existing
-  /// package's root or package root, is between the two, or if it's inside the
-  /// package root of an existing package.
-  ///
-  /// If a conflict is detected between [newPackage] and a previous package,
-  /// then [onError] is called with a [ConflictException] object
-  /// and the [newPackage] is not added to the tree.
-  ///
-  /// The packages are added in order of their root path.
-  void add(SimplePackage newPackage, void Function(Object error) onError) {
-    var root = newPackage.root;
-    var node = _map[root.scheme] ??= _PackageTrieNode();
-    if (_checkConflict(node, newPackage, onError)) return;
-    var segments = root.pathSegments;
-    // Notice that we're skipping the last segment as it's always the empty
-    // string because roots are directories.
-    for (var i = 0; i < segments.length - 1; i++) {
-      var path = segments[i];
-      node = node.map[path] ??= _PackageTrieNode();
-      if (_checkConflict(node, newPackage, onError)) return;
-    }
-    node.package = newPackage;
-    _packages.add(newPackage);
-  }
-
-  bool _isMatch(
-    String path,
-    _PackageTrieNode node,
-    List<SimplePackage> potential,
-  ) {
-    var currentPackage = node.package;
-    if (currentPackage != null) {
-      var currentPackageRootLength = currentPackage.root.toString().length;
-      if (path.length == currentPackageRootLength) return true;
-      var currentPackageUriRoot = currentPackage.packageUriRoot.toString();
-      // Is [file] inside the package root of [currentPackage]?
-      if (currentPackageUriRoot.length == currentPackageRootLength ||
-          _beginsWith(currentPackageRootLength, currentPackageUriRoot, path)) {
-        return true;
-      }
-      potential.add(currentPackage);
-    }
-    return false;
-  }
-
-  @override
-  SimplePackage? packageOf(Uri file) {
-    var currentTrieNode = _map[file.scheme];
-    if (currentTrieNode == null) return null;
-    var path = file.toString();
-    var potential = <SimplePackage>[];
-    if (_isMatch(path, currentTrieNode, potential)) {
-      return currentTrieNode.package;
-    }
-    var segments = file.pathSegments;
-
-    for (var i = 0; i < segments.length - 1; i++) {
-      var segment = segments[i];
-      currentTrieNode = currentTrieNode!.map[segment];
-      if (currentTrieNode == null) break;
-      if (_isMatch(path, currentTrieNode, potential)) {
-        return currentTrieNode.package;
-      }
-    }
-    if (potential.isEmpty) return null;
-    return potential.last;
-  }
-}
-
-class EmptyPackageTree implements PackageTree {
-  const EmptyPackageTree();
-
-  @override
-  Iterable<Package> get allPackages => const Iterable<Package>.empty();
-
-  @override
-  SimplePackage? packageOf(Uri file) => null;
-}
-
-/// Checks whether [longerPath] begins with [parentPath].
-///
-/// Skips checking the [start] first characters which are assumed to
-/// already have been matched.
-bool _beginsWith(int start, String parentPath, String longerPath) {
-  if (longerPath.length < parentPath.length) return false;
-  for (var i = start; i < parentPath.length; i++) {
-    if (longerPath.codeUnitAt(i) != parentPath.codeUnitAt(i)) return false;
-  }
-  return true;
-}
-
-enum ConflictType { sameRoots, interleaving, insidePackageRoot }
-
-/// Conflict between packages added to the same configuration.
-///
-/// The [package] conflicts with [existingPackage] if it has
-/// the same root path or the package URI root path
-/// of [existingPackage] is inside the root path of [package].
-class ConflictException {
-  /// The existing package that [package] conflicts with.
-  final SimplePackage existingPackage;
-
-  /// The package that could not be added without a conflict.
-  final SimplePackage package;
-
-  /// Whether the conflict is with the package URI root of [existingPackage].
-  final ConflictType conflictType;
-
-  /// Creates a root conflict between [package] and [existingPackage].
-  ConflictException(this.package, this.existingPackage, this.conflictType);
-}
-
-/// Used for sorting packages by root path.
-int _compareRoot(Package p1, Package p2) =>
-    p1.root.toString().compareTo(p2.root.toString());
diff --git a/pkgs/package_config/lib/src/package_config_io.dart b/pkgs/package_config/lib/src/package_config_io.dart
index bcbe31b..8b91a1f 100644
--- a/pkgs/package_config/lib/src/package_config_io.dart
+++ b/pkgs/package_config/lib/src/package_config_io.dart
@@ -9,10 +9,8 @@
 import 'dart:typed_data';
 
 import 'errors.dart';
-import 'package_config_impl.dart';
+import 'package_config.dart';
 import 'package_config_json.dart';
-import 'packages_file.dart' as packages_file;
-import 'util.dart';
 import 'util_io.dart';
 
 /// Name of directory where Dart tools store their configuration.
@@ -25,52 +23,28 @@
 /// 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
-/// a version two `package_config.json` file.
-///
-/// If the [file] is a `.packages` file and [preferNewest] is true,
-/// first checks whether there is an adjacent `.dart_tool/package_config.json`
-/// file, and if so, reads that instead.
-/// If [preferNewest] is false, the specified file is loaded even if it is
-/// a `.packages` file and there is an available `package_config.json` file.
-///
 /// The file must exist and be a normal file.
-Future<PackageConfig> readAnyConfigFile(
+Future<PackageConfig> readConfigFile(
   File file,
-  bool preferNewest,
   void Function(Object error) onError,
 ) async {
-  if (preferNewest && fileName(file.path) == packagesFileName) {
-    var alternateFile = File(
-      pathJoin(dirName(file.path), dartToolDirName, packageConfigFileName),
-    );
-    if (alternateFile.existsSync()) {
-      return await readPackageConfigJsonFile(alternateFile, onError);
-    }
-  }
   Uint8List bytes;
   try {
     bytes = await file.readAsBytes();
-  } catch (e) {
-    onError(e);
+  } catch (error) {
+    onError(error);
     return const SimplePackageConfig.empty();
   }
-  return parseAnyConfigFile(bytes, file.uri, onError);
+  return parsePackageConfigBytes(bytes, file.uri, onError);
 }
 
-/// Like [readAnyConfigFile] but uses a URI and an optional loader.
-Future<PackageConfig> readAnyConfigFileUri(
+/// Like [readConfigFile] but uses a URI and an optional loader.
+Future<PackageConfig> readConfigFileUri(
   Uri file,
   Future<Uint8List?> Function(Uri uri)? loader,
   void Function(Object error) onError,
-  bool preferNewest,
 ) async {
   if (file.isScheme('package')) {
     throw PackageConfigArgumentError(
@@ -81,23 +55,11 @@
   }
   if (loader == null) {
     if (file.isScheme('file')) {
-      return await readAnyConfigFile(File.fromUri(file), preferNewest, onError);
+      return await readConfigFile(File.fromUri(file), onError);
     }
     loader = defaultLoader;
   }
-  if (preferNewest && file.pathSegments.last == packagesFileName) {
-    var alternateFile = file.resolve('$dartToolDirName/$packageConfigFileName');
-    Uint8List? bytes;
-    try {
-      bytes = await loader(alternateFile);
-    } catch (e) {
-      onError(e);
-      return const SimplePackageConfig.empty();
-    }
-    if (bytes != null) {
-      return parsePackageConfigBytes(bytes, alternateFile, onError);
-    }
-  }
+
   Uint8List? bytes;
   try {
     bytes = await loader(file);
@@ -115,54 +77,9 @@
     );
     return const SimplePackageConfig.empty();
   }
-  return parseAnyConfigFile(bytes, file, onError);
-}
-
-/// Parses a `.packages` or `package_config.json` file's contents.
-///
-/// Assumes it's a JSON file if the first non-whitespace character
-/// is `{`, otherwise assumes it's a `.packages` file.
-PackageConfig parseAnyConfigFile(
-  Uint8List bytes,
-  Uri file,
-  void Function(Object error) onError,
-) {
-  var firstChar = firstNonWhitespaceChar(bytes);
-  if (firstChar != $lbrace) {
-    // Definitely not a JSON object, probably a .packages.
-    return packages_file.parse(bytes, file, onError);
-  }
   return parsePackageConfigBytes(bytes, file, onError);
 }
 
-Future<PackageConfig> readPackageConfigJsonFile(
-  File file,
-  void Function(Object error) onError,
-) async {
-  Uint8List bytes;
-  try {
-    bytes = await file.readAsBytes();
-  } catch (error) {
-    onError(error);
-    return const SimplePackageConfig.empty();
-  }
-  return parsePackageConfigBytes(bytes, file.uri, onError);
-}
-
-Future<PackageConfig> readDotPackagesFile(
-  File file,
-  void Function(Object error) onError,
-) async {
-  Uint8List bytes;
-  try {
-    bytes = await file.readAsBytes();
-  } catch (error) {
-    onError(error);
-    return const SimplePackageConfig.empty();
-  }
-  return packages_file.parse(bytes, file.uri, onError);
-}
-
 Future<void> writePackageConfigJsonFile(
   PackageConfig config,
   Directory targetDirectory,
@@ -175,14 +92,5 @@
 
   var sink = file.openWrite(encoding: utf8);
   writePackageConfigJsonUtf8(config, baseUri, sink);
-  var doneJson = sink.close();
-
-  // Write .packages too.
-  file = File(pathJoin(targetDirectory.path, packagesFileName));
-  baseUri = file.uri;
-  sink = file.openWrite(encoding: utf8);
-  writeDotPackages(config, baseUri, sink);
-  var donePackages = sink.close();
-
-  await Future.wait([doneJson, donePackages]);
+  await sink.close();
 }
diff --git a/pkgs/package_config/lib/src/package_config_json.dart b/pkgs/package_config/lib/src/package_config_json.dart
index 6198abf..ee818ea 100644
--- a/pkgs/package_config/lib/src/package_config_json.dart
+++ b/pkgs/package_config/lib/src/package_config_json.dart
@@ -8,8 +8,7 @@
 import 'dart:typed_data';
 
 import 'errors.dart';
-import 'package_config_impl.dart';
-import 'packages_file.dart' as packages_file;
+import 'package_config.dart';
 import 'util.dart';
 
 const String _configVersionKey = 'configVersion';
@@ -26,10 +25,6 @@
   _languageVersionKey,
 ];
 
-const String _generatedKey = 'generated';
-const String _generatorKey = 'generator';
-const String _generatorVersionKey = 'generatorVersion';
-
 final _jsonUtf8Decoder = json.fuse(utf8).decoder;
 
 PackageConfig parsePackageConfigBytes(
@@ -167,7 +162,7 @@
     }
 
     LanguageVersion? version;
-    if (languageVersion != null) {
+    if (languageVersion case var languageVersion?) {
       version = parseLanguageVersion(languageVersion, onError);
     } else if (hasVersion) {
       version = SimpleInvalidLanguageVersion('invalid');
@@ -297,24 +292,6 @@
       ],
     };
 
-void writeDotPackages(PackageConfig config, Uri baseUri, StringSink output) {
-  var extraData = config.extraData;
-  // Write .packages too.
-  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 is String ? " $generatorVersion" : ""}"
-          "${generated is String ? " on $generated" : ""}.";
-    }
-  }
-  packages_file.write(output, config, baseUri: baseUri, comment: comment);
-}
-
 /// If "extraData" is a JSON map, then return it, otherwise return null.
 ///
 /// If the value contains any of the [reservedNames] for the current context,
diff --git a/pkgs/package_config/lib/src/packages_file.dart b/pkgs/package_config/lib/src/packages_file.dart
deleted file mode 100644
index 6a977a0..0000000
--- a/pkgs/package_config/lib/src/packages_file.dart
+++ /dev/null
@@ -1,248 +0,0 @@
-// Copyright (c) 2019, the Dart project authors.  Please see the AUTHORS file
-// for details. All rights reserved. Use of this source code is governed by a
-// BSD-style license that can be found in the LICENSE file.
-
-import 'errors.dart';
-import 'package_config_impl.dart';
-import 'util.dart';
-
-/// The language version prior to the release of language versioning.
-///
-/// This is the default language version used by all packages from a
-/// `.packages` file.
-final LanguageVersion _languageVersion = LanguageVersion(2, 7);
-
-/// Parses a `.packages` file into a [PackageConfig].
-///
-/// 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.
-///
-/// Returns a simple package configuration where each package's
-/// [Package.packageUriRoot] is the same as its [Package.root]
-/// and it has no [Package.languageVersion].
-PackageConfig parse(
-  List<int> source,
-  Uri baseLocation,
-  void Function(Object error) onError,
-) {
-  if (baseLocation.isScheme('package')) {
-    onError(
-      PackageConfigArgumentError(
-        baseLocation,
-        'baseLocation',
-        'Must not be a package: URI',
-      ),
-    );
-    return PackageConfig.empty;
-  }
-  var index = 0;
-  var packages = <Package>[];
-  var packageNames = <String>{};
-  while (index < source.length) {
-    var ignoreLine = false;
-    var start = index;
-    var separatorIndex = -1;
-    var end = source.length;
-    var char = source[index++];
-    if (char == $cr || char == $lf) {
-      continue;
-    }
-    if (char == $colon) {
-      onError(
-        PackageConfigFormatException('Missing package name', source, index - 1),
-      );
-      ignoreLine = true; // Ignore if package name is invalid.
-    } else {
-      ignoreLine = char == $hash; // Ignore if comment.
-    }
-    var queryStart = -1;
-    var fragmentStart = -1;
-    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;
-      } else if (char == $question && queryStart < 0 && fragmentStart < 0) {
-        queryStart = index - 1;
-      } else if (char == $hash && fragmentStart < 0) {
-        fragmentStart = index - 1;
-      }
-    }
-    if (ignoreLine) continue;
-    if (separatorIndex < 0) {
-      onError(
-        PackageConfigFormatException("No ':' on line", source, index - 1),
-      );
-      continue;
-    }
-    var packageName = String.fromCharCodes(source, start, separatorIndex);
-    var invalidIndex = checkPackageName(packageName);
-    if (invalidIndex >= 0) {
-      onError(
-        PackageConfigFormatException(
-          'Not a valid package name',
-          source,
-          start + invalidIndex,
-        ),
-      );
-      continue;
-    }
-    if (queryStart >= 0) {
-      onError(
-        PackageConfigFormatException(
-          'Location URI must not have query',
-          source,
-          queryStart,
-        ),
-      );
-      end = queryStart;
-    } else if (fragmentStart >= 0) {
-      onError(
-        PackageConfigFormatException(
-          'Location URI must not have fragment',
-          source,
-          fragmentStart,
-        ),
-      );
-      end = fragmentStart;
-    }
-    var packageValue = String.fromCharCodes(source, separatorIndex + 1, end);
-    Uri packageLocation;
-    try {
-      packageLocation = Uri.parse(packageValue);
-    } on FormatException catch (e) {
-      onError(PackageConfigFormatException.from(e));
-      continue;
-    }
-    var relativeRoot = !hasAbsolutePath(packageLocation);
-    packageLocation = baseLocation.resolveUri(packageLocation);
-    if (packageLocation.isScheme('package')) {
-      onError(
-        PackageConfigFormatException(
-          'Package URI as location for package',
-          source,
-          separatorIndex + 1,
-        ),
-      );
-      continue;
-    }
-    var path = packageLocation.path;
-    if (!path.endsWith('/')) {
-      path += '/';
-      packageLocation = packageLocation.replace(path: path);
-    }
-    if (packageNames.contains(packageName)) {
-      onError(
-        PackageConfigFormatException(
-          'Same package name occurred more than once',
-          source,
-          start,
-        ),
-      );
-      continue;
-    }
-    var rootUri = packageLocation;
-    if (path.endsWith('/lib/')) {
-      // Assume default Pub package layout. Include package itself in root.
-      rootUri = packageLocation.replace(
-        path: path.substring(0, path.length - 4),
-      );
-    }
-    var package = SimplePackage.validate(
-      packageName,
-      rootUri,
-      packageLocation,
-      _languageVersion,
-      null,
-      relativeRoot,
-      (error) {
-        if (error is ArgumentError) {
-          onError(
-            PackageConfigFormatException(error.message.toString(), source),
-          );
-        } else {
-          onError(error);
-        }
-      },
-    );
-    if (package != null) {
-      packages.add(package);
-      packageNames.add(packageName);
-    }
-  }
-  return SimplePackageConfig(1, packages, null, onError);
-}
-
-/// Writes the configuration 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.
-void write(
-  StringSink output,
-  PackageConfig config, {
-  Uri? baseUri,
-  String? comment,
-}) {
-  if (baseUri != null && !baseUri.isAbsolute) {
-    throw PackageConfigArgumentError(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();
-  }
-  for (var package in config.packages) {
-    var packageName = package.name;
-    var uri = package.packageUriRoot;
-    // Validate packageName.
-    if (!isValidPackageName(packageName)) {
-      throw PackageConfigArgumentError(
-        config,
-        'config',
-        '"$packageName" is not a valid package name',
-      );
-    }
-    if (uri.scheme == 'package') {
-      throw PackageConfigArgumentError(
-        config,
-        'config',
-        'Package location must not be a package URI: $uri',
-      );
-    }
-    output.write(packageName);
-    output.write(':');
-    // If baseUri is provided, make the URI relative to baseUri.
-    if (baseUri != null) {
-      uri = relativizeUri(uri, baseUri)!;
-    }
-    if (!uri.path.endsWith('/')) {
-      uri = uri.replace(path: '${uri.path}/');
-    }
-    output.write(uri);
-    output.writeln();
-  }
-}
diff --git a/pkgs/package_config/lib/src/util.dart b/pkgs/package_config/lib/src/util.dart
index cebd3c8..ce112b5 100644
--- a/pkgs/package_config/lib/src/util.dart
+++ b/pkgs/package_config/lib/src/util.dart
@@ -13,11 +13,6 @@
     r"                                 !  $ &'()*+,-. 0123456789 ; =  "
     r'@ABCDEFGHIJKLMNOPQRSTUVWXYZ    _ abcdefghijklmnopqrstuvwxyz   ~ ';
 
-/// Tests whether something is a valid Dart package name.
-bool isValidPackageName(String string) {
-  return checkPackageName(string) < 0;
-}
-
 /// Check if a string is a valid package name.
 ///
 /// Valid package names contain only characters in [_validPackageNameCharacters]
@@ -148,20 +143,7 @@
   return path.toString().startsWith(prefix.toString());
 }
 
-/// Finds the first non-JSON-whitespace character in a file.
-///
-/// Used to heuristically detect whether a file is a JSON file or an .ini file.
-int firstNonWhitespaceChar(List<int> bytes) {
-  for (var i = 0; i < bytes.length; i++) {
-    var char = bytes[i];
-    if (char != 0x20 && char != 0x09 && char != 0x0a && char != 0x0d) {
-      return char;
-    }
-  }
-  return -1;
-}
-
-/// Appends a trailing `/` if the path doesn't end with one.
+/// Appends a trailing `/` if the [path] ends in a non-`/` character.
 String trailingSlash(String path) {
   if (path.isEmpty || path.endsWith('/')) return path;
   return '$path/';
@@ -253,26 +235,8 @@
 }
 
 // Character constants used by this package.
-/// "Line feed" control character.
-const int $lf = 0x0a;
-
-/// "Carriage return" control character.
-const int $cr = 0x0d;
-
 /// Space character.
 const int $space = 0x20;
 
-/// Character `#`.
-const int $hash = 0x23;
-
 /// Character `.`.
 const int $dot = 0x2e;
-
-/// Character `:`.
-const int $colon = 0x3a;
-
-/// Character `?`.
-const int $question = 0x3f;
-
-/// Character `{`.
-const int $lbrace = 0x7b;
diff --git a/pkgs/package_config/pubspec.yaml b/pkgs/package_config/pubspec.yaml
index 5ff9c53..f81f5db 100644
--- a/pkgs/package_config/pubspec.yaml
+++ b/pkgs/package_config/pubspec.yaml
@@ -8,8 +8,9 @@
   sdk: ^3.7.0
 
 dependencies:
+  meta: ^1.15.0
   path: ^1.8.0
 
 dev_dependencies:
-  dart_flutter_team_lints: ^3.0.0
+  dart_flutter_team_lints: ^3.4.0
   test: ^1.16.0
diff --git a/pkgs/package_config/test/bench.dart b/pkgs/package_config/test/bench.dart
index 1d0ab1d..ba7408e 100644
--- a/pkgs/package_config/test/bench.dart
+++ b/pkgs/package_config/test/bench.dart
@@ -3,7 +3,6 @@
 // BSD-style license that can be found in the LICENSE file.
 
 import 'dart:convert';
-import 'dart:typed_data';
 
 import 'package:package_config/src/errors.dart';
 import 'package:package_config/src/package_config_json.dart';
@@ -31,8 +30,7 @@
   sb.writeln('}');
   var stopwatch = Stopwatch()..start();
   var config = parsePackageConfigBytes(
-    // ignore: unnecessary_cast
-    utf8.encode(sb.toString()) as Uint8List,
+    utf8.encode(sb.toString()),
     Uri.parse('file:///tmp/.dart_tool/file.dart'),
     throwError,
   );
diff --git a/pkgs/package_config/test/discovery_test.dart b/pkgs/package_config/test/discovery_test.dart
index 1e76e1e..529afcd 100644
--- a/pkgs/package_config/test/discovery_test.dart
+++ b/pkgs/package_config/test/discovery_test.dart
@@ -78,7 +78,7 @@
       },
     );
 
-    // Finds .packages if no package_config.json.
+    // Does not find .packages if no package_config.json.
     fileTest(
       '.packages',
       {
@@ -87,9 +87,8 @@
         'packages': {'shouldNotBeFound': <Object, Object>{}},
       },
       (Directory directory) async {
-        var config = (await findPackageConfig(directory))!;
-        expect(config.version, 1); // Found .packages file.
-        validatePackagesFile(config, directory);
+        var config = await findPackageConfig(directory);
+        expect(config, null);
       },
     );
 
@@ -99,7 +98,7 @@
       {
         '.packages': packagesFile,
         '.dart_tool': {'package_config.json': packageConfigFile},
-        'subdir': {'script.dart': 'main(){}'},
+        'subdir': {'.packages': packagesFile, 'script.dart': 'main(){}'},
       },
       (Directory directory) async {
         var config = (await findPackageConfig(subdir(directory, 'subdir/')))!;
@@ -108,25 +107,11 @@
       },
     );
 
-    // Finds .packages in super-directory.
-    fileTest(
-      '.packages recursive',
-      {
-        '.packages': packagesFile,
-        'subdir': {'script.dart': 'main(){}'},
-      },
-      (Directory directory) async {
-        var config = (await findPackageConfig(subdir(directory, 'subdir/')))!;
-        expect(config.version, 1);
-        validatePackagesFile(config, directory);
-      },
-    );
-
     // Does not find a packages/ directory, and returns null if nothing found.
     fileTest(
       'package directory packages not supported',
       {
-        'packages': {'foo': <String, dynamic>{}},
+        'packages': {'foo': <String, Object?>{}},
       },
       (Directory directory) async {
         var config = await findPackageConfig(directory);
@@ -135,20 +120,8 @@
     );
 
     group('throws', () {
-      fileTest('invalid .packages', {'.packages': 'not a .packages file'}, (
-        Directory directory,
-      ) {
-        expect(findPackageConfig(directory), throwsA(isA<FormatException>()));
-      });
-
-      fileTest('invalid .packages as JSON', {'.packages': packageConfigFile}, (
-        Directory directory,
-      ) {
-        expect(findPackageConfig(directory), throwsA(isA<FormatException>()));
-      });
-
       fileTest(
-        'invalid .packages',
+        'invalid package config not JSON',
         {
           '.dart_tool': {'package_config.json': 'not a JSON file'},
         },
@@ -158,7 +131,7 @@
       );
 
       fileTest(
-        'invalid .packages as INI',
+        'invalid package config as INI',
         {
           '.dart_tool': {'package_config.json': packagesFile},
         },
@@ -166,39 +139,25 @@
           expect(findPackageConfig(directory), throwsA(isA<FormatException>()));
         },
       );
+
+      fileTest(
+        'indirectly through .packages',
+        {
+          '.packages': packagesFile,
+          '.dart_tool': {'package_config.json': packageConfigFile},
+        },
+        (Directory directory) async {
+          // A .packages file in the directory of a .dart_tool/package_config.json
+          // used to automatically redirect to the package_config.json.
+          var file = dirFile(directory, '.packages');
+          expect(loadPackageConfig(file), throwsA(isA<FormatException>()));
+        },
+      );
     });
 
     group('handles error', () {
-      fileTest('invalid .packages', {'.packages': 'not a .packages file'}, (
-        Directory directory,
-      ) async {
-        var hadError = false;
-        await findPackageConfig(
-          directory,
-          onError: expectAsync1((error) {
-            hadError = true;
-            expect(error, isA<FormatException>());
-          }, max: -1),
-        );
-        expect(hadError, true);
-      });
-
-      fileTest('invalid .packages as JSON', {'.packages': packageConfigFile}, (
-        Directory directory,
-      ) async {
-        var hadError = false;
-        await findPackageConfig(
-          directory,
-          onError: expectAsync1((error) {
-            hadError = true;
-            expect(error, isA<FormatException>());
-          }, max: -1),
-        );
-        expect(hadError, true);
-      });
-
       fileTest(
-        'invalid package_config not JSON',
+        'invalid package config not JSON',
         {
           '.dart_tool': {'package_config.json': 'not a JSON file'},
         },
@@ -244,8 +203,8 @@
       },
     );
 
-    // Finds package_config.json in super-directory, with .packages in
-    // subdir and minVersion > 1.
+    // Finds package_config.json in super-directory.
+    // (Even with `.packages` in search directory.)
     fileTest(
       'package_config.json recursive .packages ignored',
       {
@@ -280,20 +239,6 @@
         expect(config.version, 2);
         validatePackagesFile(config, directory);
       });
-      fileTest('indirectly through .packages', files, (
-        Directory directory,
-      ) async {
-        var file = dirFile(directory, '.packages');
-        var config = await loadPackageConfig(file);
-        expect(config.version, 2);
-        validatePackagesFile(config, directory);
-      });
-      fileTest('prefer .packages', files, (Directory directory) async {
-        var file = dirFile(directory, '.packages');
-        var config = await loadPackageConfig(file, preferNewest: false);
-        expect(config.version, 1);
-        validatePackagesFile(config, directory);
-      });
     });
 
     fileTest(
@@ -323,26 +268,15 @@
       },
     );
 
-    fileTest('.packages', {'.packages': packagesFile}, (
+    fileTest('.packages cannot be loaded', {'.packages': packagesFile}, (
       Directory directory,
     ) async {
       var file = dirFile(directory, '.packages');
-      var config = await loadPackageConfig(file);
-      expect(config.version, 1);
-      validatePackagesFile(config, directory);
+      expect(loadPackageConfig(file), throwsFormatException);
     });
 
-    fileTest('.packages non-default name', {'pheldagriff': packagesFile}, (
-      Directory directory,
-    ) async {
-      var file = dirFile(directory, 'pheldagriff');
-      var config = await loadPackageConfig(file);
-      expect(config.version, 1);
-      validatePackagesFile(config, directory);
-    });
-
-    fileTest('no config found', {}, (Directory directory) {
-      var file = dirFile(directory, 'anyname');
+    fileTest('no config file found', {}, (Directory directory) {
+      var file = dirFile(directory, 'any_name');
       expect(
         () => loadPackageConfig(file),
         throwsA(isA<FileSystemException>()),
@@ -350,7 +284,7 @@
     });
 
     fileTest('no config found, handled', {}, (Directory directory) async {
-      var file = dirFile(directory, 'anyname');
+      var file = dirFile(directory, 'any_name');
       var hadError = false;
       await loadPackageConfig(
         file,
@@ -362,33 +296,10 @@
       expect(hadError, true);
     });
 
-    fileTest('specified file syntax error', {'anyname': 'syntax error'}, (
+    fileTest('specified file syntax error', {'any_name': 'syntax error'}, (
       Directory directory,
     ) {
-      var file = dirFile(directory, 'anyname');
-      expect(() => loadPackageConfig(file), throwsFormatException);
-    });
-
-    // Find package_config.json in subdir even if initial file syntax error.
-    fileTest(
-      'specified file syntax onError',
-      {
-        '.packages': 'syntax error',
-        '.dart_tool': {'package_config.json': packageConfigFile},
-      },
-      (Directory directory) async {
-        var file = dirFile(directory, '.packages');
-        var config = await loadPackageConfig(file);
-        expect(config.version, 2);
-        validatePackagesFile(config, directory);
-      },
-    );
-
-    // A file starting with `{` is a package_config.json file.
-    fileTest('file syntax error with {', {'.packages': '{syntax error'}, (
-      Directory directory,
-    ) {
-      var file = dirFile(directory, '.packages');
+      var file = dirFile(directory, 'any_name');
       expect(() => loadPackageConfig(file), throwsFormatException);
     });
   });
diff --git a/pkgs/package_config/test/discovery_uri_test.dart b/pkgs/package_config/test/discovery_uri_test.dart
index e81d023..2d056a7 100644
--- a/pkgs/package_config/test/discovery_uri_test.dart
+++ b/pkgs/package_config/test/discovery_uri_test.dart
@@ -65,7 +65,7 @@
       {
         '.packages': 'invalid .packages file',
         'script.dart': 'main(){}',
-        'packages': {'shouldNotBeFound': <String, dynamic>{}},
+        'packages': {'shouldNotBeFound': <String, Object?>{}},
         '.dart_tool': {'package_config.json': packageConfigFile},
       },
       (directory, loader) async {
@@ -75,21 +75,6 @@
       },
     );
 
-    // Finds .packages if no package_config.json.
-    loaderTest(
-      '.packages',
-      {
-        '.packages': packagesFile,
-        'script.dart': 'main(){}',
-        'packages': {'shouldNotBeFound': <String, dynamic>{}},
-      },
-      (directory, loader) async {
-        var config = (await findPackageConfigUri(directory, loader: loader))!;
-        expect(config.version, 1); // Found .packages file.
-        validatePackagesFile(config, directory);
-      },
-    );
-
     // Finds package_config.json in super-directory.
     loaderTest(
       'package_config.json recursive',
@@ -109,21 +94,21 @@
       },
     );
 
-    // Finds .packages in super-directory.
+    // Does not find a .packages file.
     loaderTest(
-      '.packages recursive',
+      'Not .packages',
       {
         '.packages': packagesFile,
-        'subdir': {'script.dart': 'main(){}'},
+        'script.dart': 'main(){}',
+        'packages': {'shouldNotBeFound': <String, Object?>{}},
       },
       (directory, loader) async {
-        var config =
-            (await findPackageConfigUri(
-              directory.resolve('subdir/'),
-              loader: loader,
-            ))!;
-        expect(config.version, 1);
-        validatePackagesFile(config, directory);
+        var config = await findPackageConfigUri(
+          recurse: false,
+          directory,
+          loader: loader,
+        );
+        expect(config, null);
       },
     );
 
@@ -131,93 +116,13 @@
     loaderTest(
       'package directory packages not supported',
       {
-        'packages': {'foo': <String, dynamic>{}},
+        'packages': {'foo': <String, Object?>{}},
       },
       (Uri directory, loader) async {
         var config = await findPackageConfigUri(directory, loader: loader);
         expect(config, null);
       },
     );
-
-    loaderTest('invalid .packages', {'.packages': 'not a .packages file'}, (
-      Uri directory,
-      loader,
-    ) {
-      expect(
-        () => findPackageConfigUri(directory, loader: loader),
-        throwsA(isA<FormatException>()),
-      );
-    });
-
-    loaderTest('invalid .packages as JSON', {'.packages': packageConfigFile}, (
-      Uri directory,
-      loader,
-    ) {
-      expect(
-        () => findPackageConfigUri(directory, loader: loader),
-        throwsA(isA<FormatException>()),
-      );
-    });
-
-    loaderTest(
-      'invalid .packages',
-      {
-        '.dart_tool': {'package_config.json': 'not a JSON file'},
-      },
-      (Uri directory, loader) {
-        expect(
-          () => findPackageConfigUri(directory, loader: loader),
-          throwsA(isA<FormatException>()),
-        );
-      },
-    );
-
-    loaderTest(
-      'invalid .packages as INI',
-      {
-        '.dart_tool': {'package_config.json': packagesFile},
-      },
-      (Uri directory, loader) {
-        expect(
-          () => findPackageConfigUri(directory, loader: loader),
-          throwsA(isA<FormatException>()),
-        );
-      },
-    );
-
-    // Does not find .packages if no package_config.json and minVersion > 1.
-    loaderTest(
-      '.packages ignored',
-      {'.packages': packagesFile, 'script.dart': 'main(){}'},
-      (directory, loader) async {
-        var config = await findPackageConfigUri(
-          directory,
-          minVersion: 2,
-          loader: loader,
-        );
-        expect(config, null);
-      },
-    );
-
-    // Finds package_config.json in super-directory, with .packages in
-    // subdir and minVersion > 1.
-    loaderTest(
-      'package_config.json recursive ignores .packages',
-      {
-        '.dart_tool': {'package_config.json': packageConfigFile},
-        'subdir': {'.packages': packagesFile, 'script.dart': 'main(){}'},
-      },
-      (directory, loader) async {
-        var config =
-            (await findPackageConfigUri(
-              directory.resolve('subdir/'),
-              minVersion: 2,
-              loader: loader,
-            ))!;
-        expect(config.version, 2);
-        validatePackagesFile(config, directory);
-      },
-    );
   });
 
   group('loadPackageConfig', () {
@@ -237,10 +142,17 @@
         Uri directory,
         loader,
       ) async {
+        // Is no longer supported.
         var file = directory.resolve('.packages');
-        var config = await loadPackageConfigUri(file, loader: loader);
-        expect(config.version, 2);
-        validatePackagesFile(config, directory);
+        var hadError = false;
+        await loadPackageConfigUri(
+          file,
+          loader: loader,
+          onError: (_) {
+            hadError = true;
+          },
+        );
+        expect(hadError, true);
       });
     });
 
@@ -271,26 +183,6 @@
       },
     );
 
-    loaderTest('.packages', {'.packages': packagesFile}, (
-      Uri directory,
-      loader,
-    ) async {
-      var file = directory.resolve('.packages');
-      var config = await loadPackageConfigUri(file, loader: loader);
-      expect(config.version, 1);
-      validatePackagesFile(config, directory);
-    });
-
-    loaderTest('.packages non-default name', {'pheldagriff': packagesFile}, (
-      Uri directory,
-      loader,
-    ) async {
-      var file = directory.resolve('pheldagriff');
-      var config = await loadPackageConfigUri(file, loader: loader);
-      expect(config.version, 1);
-      validatePackagesFile(config, directory);
-    });
-
     loaderTest('no config found', {}, (Uri directory, loader) {
       var file = directory.resolve('anyname');
       expect(
@@ -344,7 +236,7 @@
       expect(hadError, true);
     });
 
-    // Don't look for package_config.json if original file not named .packages.
+    // Don't look for package_config.json if original name or file are bad.
     loaderTest(
       'specified file syntax error with alternative',
       {
diff --git a/pkgs/package_config/test/package_config_impl_test.dart b/pkgs/package_config/test/package_config_impl_test.dart
index 0ad399e..e68c31d 100644
--- a/pkgs/package_config/test/package_config_impl_test.dart
+++ b/pkgs/package_config/test/package_config_impl_test.dart
@@ -138,11 +138,11 @@
       ) {
         expect(version == otherVersion, identical(version, otherVersion));
 
-        expect(() => version < otherVersion, throwsA(isA<StateError>()));
-        expect(() => version <= otherVersion, throwsA(isA<StateError>()));
+        expect(() => version < otherVersion, throwsA(isA<UnsupportedError>()));
+        expect(() => version <= otherVersion, throwsA(isA<UnsupportedError>()));
 
-        expect(() => version > otherVersion, throwsA(isA<StateError>()));
-        expect(() => version >= otherVersion, throwsA(isA<StateError>()));
+        expect(() => version > otherVersion, throwsA(isA<UnsupportedError>()));
+        expect(() => version >= otherVersion, throwsA(isA<UnsupportedError>()));
       }
 
       var validVersion = LanguageVersion(3, 5);
diff --git a/pkgs/package_config/test/parse_test.dart b/pkgs/package_config/test/parse_test.dart
index d5c7325..adeeaa8 100644
--- a/pkgs/package_config/test/parse_test.dart
+++ b/pkgs/package_config/test/parse_test.dart
@@ -8,89 +8,11 @@
 import 'package:package_config/package_config_types.dart';
 import 'package:package_config/src/errors.dart';
 import 'package:package_config/src/package_config_json.dart';
-import 'package:package_config/src/packages_file.dart' as packages;
 import 'package:test/test.dart';
 
 import 'src/util.dart';
 
 void main() {
-  group('.packages', () {
-    test('valid', () {
-      var packagesFile =
-          '# Generated by pub yadda yadda\n'
-          'foo:file:///foo/lib/\n'
-          'bar:/bar/lib/\n'
-          'baz:lib/\n';
-      var result = packages.parse(
-        utf8.encode(packagesFile),
-        Uri.parse('file:///tmp/file.dart'),
-        throwError,
-      );
-      expect(result.version, 1);
-      expect({for (var p in result.packages) p.name}, {'foo', 'bar', 'baz'});
-      expect(
-        result.resolve(pkg('foo', 'foo.dart')),
-        Uri.parse('file:///foo/lib/foo.dart'),
-      );
-      expect(
-        result.resolve(pkg('bar', 'bar.dart')),
-        Uri.parse('file:///bar/lib/bar.dart'),
-      );
-      expect(
-        result.resolve(pkg('baz', 'baz.dart')),
-        Uri.parse('file:///tmp/lib/baz.dart'),
-      );
-
-      var foo = result['foo']!;
-      expect(foo, isNotNull);
-      expect(foo.root, Uri.parse('file:///foo/'));
-      expect(foo.packageUriRoot, Uri.parse('file:///foo/lib/'));
-      expect(foo.languageVersion, LanguageVersion(2, 7));
-      expect(foo.relativeRoot, false);
-    });
-
-    test('valid empty', () {
-      var packagesFile = '# Generated by pub yadda yadda\n';
-      var result = packages.parse(
-        utf8.encode(packagesFile),
-        Uri.file('/tmp/file.dart'),
-        throwError,
-      );
-      expect(result.version, 1);
-      expect({for (var p in result.packages) p.name}, <String>{});
-    });
-
-    group('invalid', () {
-      var baseFile = Uri.file('/tmp/file.dart');
-      void testThrows(String name, String content) {
-        test(name, () {
-          expect(
-            () => packages.parse(utf8.encode(content), baseFile, throwError),
-            throwsA(isA<FormatException>()),
-          );
-        });
-        test('$name, handle error', () {
-          var hadError = false;
-          packages.parse(utf8.encode(content), baseFile, (error) {
-            hadError = true;
-            expect(error, isA<FormatException>());
-          });
-          expect(hadError, true);
-        });
-      }
-
-      testThrows('repeated package name', 'foo:lib/\nfoo:lib\n');
-      testThrows('no colon', 'foo\n');
-      testThrows('empty package name', ':lib/\n');
-      testThrows('dot only package name', '.:lib/\n');
-      testThrows('dot only package name', '..:lib/\n');
-      testThrows('invalid package name character', 'f\\o:lib/\n');
-      testThrows('package URI', 'foo:package:bar/lib/');
-      testThrows('location with query', 'f\\o:lib/?\n');
-      testThrows('location with fragment', 'f\\o:lib/#\n');
-    });
-  });
-
   group('package_config.json', () {
     test('valid', () {
       var packageConfigFile = '''
@@ -410,7 +332,7 @@
         String containsString,
       ) {
         test(name, () {
-          dynamic exception;
+          Object? exception;
           try {
             parsePackageConfigBytes(
               utf8.encode(source),
diff --git a/pkgs/package_config/test/src/util.dart b/pkgs/package_config/test/src/util.dart
index 780ee80..0253e91 100644
--- a/pkgs/package_config/test/src/util.dart
+++ b/pkgs/package_config/test/src/util.dart
@@ -48,8 +48,7 @@
       if (value is! Map<String, Object?>) return null;
       value = value[parts[i]];
     }
-    // ignore: unnecessary_cast
-    if (value is String) return utf8.encode(value) as Uint8List;
+    if (value is String) return utf8.encode(value);
     return null;
   }