From 91e70f0f0eb860a14d5d35882d9f2ae2fb8d76f8 Mon Sep 17 00:00:00 2001 From: "Lasse R.H. Nielsen" Date: Thu, 6 Mar 2025 16:08:14 +0100 Subject: [PATCH 1/8] Remove uspport for `.packages` files. Simplify (braking) API which no longer needs to support two files. Since this is breaking anyway, add `final` to classes, and make comparison operators on `LanguageVersion` instance methods. Add tool to query package configuration for a file or directory: `bin/package_config_of.dart`. --- pkgs/package_config/CHANGELOG.md | 24 +- pkgs/package_config/README.md | 5 +- pkgs/package_config/lib/package_config.dart | 62 +- .../lib/package_config_types.dart | 1 - pkgs/package_config/lib/src/discovery.dart | 34 +- .../lib/src/package_config.dart | 700 ++++++++++++++++-- .../lib/src/package_config_impl.dart | 568 -------------- .../lib/src/package_config_io.dart | 91 +-- .../lib/src/package_config_json.dart | 24 +- .../package_config/lib/src/packages_file.dart | 193 ----- pkgs/package_config/pubspec.yaml | 4 +- pkgs/package_config/test/discovery_test.dart | 133 +--- .../test/discovery_uri_test.dart | 113 +-- pkgs/package_config/test/parse_test.dart | 64 -- 14 files changed, 749 insertions(+), 1267 deletions(-) delete mode 100644 pkgs/package_config/lib/src/package_config_impl.dart delete mode 100644 pkgs/package_config/lib/src/packages_file.dart diff --git a/pkgs/package_config/CHANGELOG.md b/pkgs/package_config/CHANGELOG.md index ff6e78ea6..db5070783 100644 --- a/pkgs/package_config/CHANGELOG.md +++ b/pkgs/package_config/CHANGELOG.md @@ -1,10 +1,24 @@ -## 2.2.0 +## 3.0.0 -- Add relational operators to `LanguageVersion` with extension methods - exported under `LanguageVersionRelationalOperators`. +- Removes support for the `.packages` file. + The Dart SDK no longer supports that file, and no new `.packages` files + will be generated. -- Include correct parameter names in errors when validating - the `major` and `minor` versions in the `LanguageVersion.new` constructor. +- **Breaking change** + 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`. + + Also makes `PackageConfig`, `Package` and `LanguageVersion` final classes. + +- Adds `PackageConfig.minVersion` to complement `.maxVersion`. + Currently both are `2`. + +- Adds relational operators to `LanguageVersion`. + +- Includes correct parameter names in errors when validating + the `major` and `minor` versions in the `LanguageVersion()` constructor. ## 2.1.1 diff --git a/pkgs/package_config/README.md b/pkgs/package_config/README.md index 76fd3cbed..4e3f0b0c7 100644 --- a/pkgs/package_config/README.md +++ b/pkgs/package_config/README.md @@ -21,6 +21,5 @@ The primary libraries of this package are 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/lib/package_config.dart b/pkgs/package_config/lib/package_config.dart index 074c97707..0c01d6d24 100644 --- a/pkgs/package_config/lib/package_config.dart +++ b/pkgs/package_config/lib/package_config.dart @@ -21,20 +21,7 @@ export 'package_config_types.dart'; /// 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. @@ -42,26 +29,19 @@ export 'package_config_types.dart'; /// a valid configuration from the invalid configuration file. /// If no [onError] is provided, errors are thrown immediately. Future loadPackageConfig(File file, + {void Function(Object error)? onError}) => + readConfigFile(file, onError ?? throwError); + +/// @nodoc +@Deprecated('use loadPackageConfig instead') +Future 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] @@ -88,15 +68,19 @@ Future loadPackageConfig(File file, /// If no [onError] is provided, errors are thrown immediately. Future loadPackageConfigUri(Uri file, {Future Function(Uri uri)? loader, - bool preferNewest = true, void Function(Object error)? onError}) => - readAnyConfigFileUri(file, loader, onError ?? throwError, preferNewest); + readConfigFileUri(file, loader, onError ?? throwError); + +/// @nodoc +@Deprecated('use loadPackageConfigUri instead') +Future loadAnyPackageConfigUri(Uri uri, + {bool preferNewest = true, void Function(Object error)? onError}) => + 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, @@ -129,9 +113,8 @@ Future findPackageConfig(Directory directory, /// 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]. @@ -189,9 +172,6 @@ Future findPackageConfigUri(Uri location, /// 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 savePackageConfig( diff --git a/pkgs/package_config/lib/package_config_types.dart b/pkgs/package_config/lib/package_config_types.dart index 5c2c3413c..913f744f6 100644 --- a/pkgs/package_config/lib/package_config_types.dart +++ b/pkgs/package_config/lib/package_config_types.dart @@ -17,6 +17,5 @@ export 'src/package_config.dart' show InvalidLanguageVersion, LanguageVersion, - LanguageVersionRelationalOperators, Package, PackageConfig; diff --git a/pkgs/package_config/lib/src/discovery.dart b/pkgs/package_config/lib/src/discovery.dart index b67841099..e02ce1ec0 100644 --- a/pkgs/package_config/lib/src/discovery.dart +++ b/pkgs/package_config/lib/src/discovery.dart @@ -6,14 +6,12 @@ import 'dart:io'; 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 @@ final Uri parentPath = Uri(path: '..'); /// 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 findPackageConfig(Directory baseDirectory, @@ -44,7 +39,6 @@ Future findPackageConfig(Directory baseDirectory, return null; } do { - // Check for $cwd/.packages var packageConfig = await findPackageConfigInDirectory(directory, minVersion, onError); if (packageConfig != null) return packageConfig; @@ -87,13 +81,6 @@ Future findPackageConfigUri( 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; @@ -102,7 +89,7 @@ Future findPackageConfigUri( 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. @@ -113,7 +100,6 @@ Future findPackageConfigUri( /// 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 findPackageConfigInDirectory(Directory directory, @@ -124,12 +110,6 @@ Future findPackageConfigInDirectory(Directory directory, 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; } @@ -140,9 +120,3 @@ Future checkForPackageConfigJsonFile(Directory directory) async { if (await file.exists()) return file; return null; } - -Future 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 707e85703..7ef3637ea 100644 --- a/pkgs/package_config/lib/src/package_config.dart +++ b/pkgs/package_config/lib/src/package_config.dart @@ -5,8 +5,8 @@ import 'dart:typed_data'; import 'errors.dart'; -import 'package_config_impl.dart'; import 'package_config_json.dart'; +import 'util.dart'; /// A package configuration. /// @@ -15,8 +15,11 @@ import 'package_config_json.dart'; /// 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. -abstract class PackageConfig { - /// The largest configuration version currently recognized. +abstract final class PackageConfig { + /// 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. @@ -142,8 +145,8 @@ abstract class PackageConfig { /// 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 @@ -210,7 +213,7 @@ abstract class PackageConfig { } /// Configuration data for a single package. -abstract class Package { +abstract final class Package { /// Creates a package with the provided properties. /// /// The [name] must be a valid package name. @@ -298,7 +301,7 @@ abstract class Package { /// If errors during parsing are handled using an `onError` handler, /// then an *invalid* language version may be represented by an /// [InvalidLanguageVersion] object. -abstract class LanguageVersion implements Comparable { +abstract final class LanguageVersion implements Comparable { /// The maximal value allowed by [major] and [minor] values; static const int maxValue = 0x7FFFFFFF; @@ -352,16 +355,53 @@ abstract class LanguageVersion implements Comparable { /// 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); + /// 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. + /// + /// For details on how valid language versions are compared, + /// check out [LanguageVersion.compareTo]. + bool operator <(LanguageVersion other); + + /// 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. + /// + /// For details on how valid language versions are compared, + /// check out [LanguageVersion.compareTo]. + bool operator <=(LanguageVersion other); + + /// Whether this language version is greater than [other]. + /// + /// 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); + + /// Whether this language version is greater than or equal to [other]. + /// + /// 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); + /// Valid language versions with the same [major] and [minor] values are /// equal. /// @@ -386,7 +426,9 @@ abstract class LanguageVersion implements Comparable { /// 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. -abstract class InvalidLanguageVersion implements LanguageVersion { +/// The caller which provided the `onError` handler which was called +/// should be prepared to encounter invalid values. +abstract final class InvalidLanguageVersion implements LanguageVersion { /// The value -1 for an invalid language version. @override int get major; @@ -407,63 +449,399 @@ abstract class InvalidLanguageVersion implements LanguageVersion { String toString(); } -/// Relational operators for [LanguageVersion] that -/// compare valid versions with [LanguageVersion.compareTo]. +// -------------------------------------------------------------------- +// Implementation of interfaces. + +const bool _disallowPackagesInsidePackageUriRoot = false; + +// Implementations of the main data types exposed by the API of this package. + +final class SimplePackageConfig implements PackageConfig { + @override + final int version; + final Map _packages; + final PackageTree _packageTree; + @override + final Object? extraData; + + factory SimplePackageConfig(int version, Iterable 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 {}, + 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 originalPackages, + List packages, void Function(Object error) onError) { + var packageNames = {}; + 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 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. +final 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+)$`. /// -/// If either operand is an [InvalidLanguageVersion], a [StateError] is thrown. -/// Versions should be verified as valid after parsing and before using them. -extension LanguageVersionRelationalOperators on LanguageVersion { +/// 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 final class _SimpleLanguageVersionBase implements LanguageVersion { + @override + int compareTo(LanguageVersion other) { + var result = major - other.major; + if (result != 0) return result; + return minor - other.minor; + } +} + +final 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'; + /// 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]. + @override 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 with an invalid language version. + if (other is InvalidLanguageVersion) _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]. + @override 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 with an invalid language version. + if (other is InvalidLanguageVersion) _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]. + @override 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(); - } - + if (other is InvalidLanguageVersion) _throwOtherInvalid(); return compareTo(other) > 0; } @@ -474,22 +852,242 @@ extension LanguageVersionRelationalOperators on LanguageVersion { /// /// For details on how valid language versions are compared, /// check out [LanguageVersion.compareTo]. + @override 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 with an invalid language version. + if (other is InvalidLanguageVersion) _throwOtherInvalid(); return compareTo(other) >= 0; } + static Never _throwOtherInvalid() => throw StateError( + 'Can\'t compare a language version to an invalid language version. ' + 'Verify language versions are valid after parsing.'); +} + +final class SimpleInvalidLanguageVersion extends _SimpleLanguageVersionBase + implements InvalidLanguageVersion { + final String? _source; + SimpleInvalidLanguageVersion(this._source); + @override + int get major => -1; + @override + int get minor => -1; + + @override + bool operator <(LanguageVersion other) { + _throwThisInvalid(); + } + + @override + bool operator <=(LanguageVersion other) { + _throwThisInvalid(); + } + + @override + bool operator >(LanguageVersion other) { + _throwThisInvalid(); + } + + @override + bool operator >=(LanguageVersion other) { + _throwThisInvalid(); + } + + @override + String toString() => _source!; + static Never _throwThisInvalid() => throw StateError( 'Can\'t compare an invalid language version to another language version. ' 'Verify language versions are valid after parsing.'); +} - static Never _throwOtherInvalid() => throw StateError( - 'Can\'t compare a language version to an invalid language version. ' - 'Verify language versions are valid after parsing.'); +abstract class PackageTree { + Iterable get allPackages; + SimplePackage? packageOf(Uri file); +} + +class _PackageTrieNode { + SimplePackage? package; + + /// Indexed by path segment. + Map 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 _map = {}; + + /// A list of all packages. + final List _packages = []; + + @override + Iterable 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 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 = []; + 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 get allPackages => const Iterable.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 865e99a8e..000000000 --- a/pkgs/package_config/lib/src/package_config_impl.dart +++ /dev/null @@ -1,568 +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 _packages; - final PackageTree _packageTree; - @override - final Object? extraData; - - factory SimplePackageConfig(int version, Iterable 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 {}, - 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 originalPackages, - List packages, void Function(Object error) onError) { - var packageNames = {}; - 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 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 get allPackages; - SimplePackage? packageOf(Uri file); -} - -class _PackageTrieNode { - SimplePackage? package; - - /// Indexed by path segment. - Map 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 _map = {}; - - /// A list of all packages. - final List _packages = []; - - @override - Iterable 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 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 = []; - 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 get allPackages => const Iterable.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 8c5773b2b..19cae38c3 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:io'; 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,32 +23,11 @@ const dartToolDirName = '.dart_tool'; /// 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 readAnyConfigFile( - 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); - } - } +Future readConfigFile( + File file, void Function(Object error) onError) async { Uint8List bytes; try { bytes = await file.readAsBytes(); @@ -58,38 +35,25 @@ Future readAnyConfigFile( onError(e); 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 readAnyConfigFileUri( +/// Like [readConfigFile] but uses a URI and an optional loader. +Future readConfigFileUri( Uri file, Future Function(Uri uri)? loader, - void Function(Object error) onError, - bool preferNewest) async { + void Function(Object error) onError) async { if (file.isScheme('package')) { throw PackageConfigArgumentError( file, 'file', 'Must not be a package: URI'); } 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); @@ -102,20 +66,6 @@ Future readAnyConfigFileUri( file.toString(), 'file', 'File cannot be read')); 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); } @@ -131,18 +81,6 @@ Future readPackageConfigJsonFile( return parsePackageConfigBytes(bytes, file.uri, onError); } -Future 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 writePackageConfigJsonFile( PackageConfig config, Directory targetDirectory) async { // Write .dart_tool/package_config.json first. @@ -153,14 +91,5 @@ Future writePackageConfigJsonFile( 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 65560a0f0..afc18cb4e 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:convert'; 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 @@ const List _packageNames = [ _languageVersionKey ]; -const String _generatedKey = 'generated'; -const String _generatorKey = 'generator'; -const String _generatorVersionKey = 'generatorVersion'; - final _jsonUtf8Decoder = json.fuse(utf8).decoder; PackageConfig parsePackageConfigBytes( @@ -264,23 +259,6 @@ Map packageConfigToJson(PackageConfig config, Uri? baseUri) => ], }; -void writeDotPackages(PackageConfig config, Uri baseUri, StringSink output) { - var extraData = config.extraData; - // Write .packages too. - String? comment; - if (extraData is Map) { - 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 bf68f2c88..000000000 --- a/pkgs/package_config/lib/src/packages_file.dart +++ /dev/null @@ -1,193 +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 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 = []; - var packageNames = {}; - 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/pubspec.yaml b/pkgs/package_config/pubspec.yaml index b29ed0b65..c0a081ae6 100644 --- a/pkgs/package_config/pubspec.yaml +++ b/pkgs/package_config/pubspec.yaml @@ -1,5 +1,5 @@ name: package_config -version: 2.2.0 +version: 3.0.0-wip description: Support for reading and writing Dart Package Configuration files. repository: https://github.com/dart-lang/tools/tree/main/pkgs/package_config issue_tracker: https://github.com/dart-lang/tools/labels/package%3Apackage_config @@ -11,5 +11,5 @@ dependencies: path: ^1.8.0 dev_dependencies: - dart_flutter_team_lints: ^3.0.0 + dart_flutter_team_lints: ^3.1.0 test: ^1.16.0 diff --git a/pkgs/package_config/test/discovery_test.dart b/pkgs/package_config/test/discovery_test.dart index 6d1b65529..dbe691efc 100644 --- a/pkgs/package_config/test/discovery_test.dart +++ b/pkgs/package_config/test/discovery_test.dart @@ -69,15 +69,14 @@ void main() { validatePackagesFile(config, directory); }); - // Finds .packages if no package_config.json. + // Does not find .packages if no package_config.json. fileTest('.packages', { '.packages': packagesFile, 'script.dart': 'main(){}', 'packages': {'shouldNotBeFound': {}} }, (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); }); // Finds package_config.json in super-directory. @@ -87,6 +86,7 @@ void main() { 'package_config.json': packageConfigFile, }, 'subdir': { + '.packages': packagesFile, 'script.dart': 'main(){}', } }, (Directory directory) async { @@ -95,16 +95,6 @@ void main() { validatePackagesFile(config, directory); }); - // 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': { @@ -116,19 +106,7 @@ void main() { }); group('throws', () { - fileTest('invalid .packages', { - '.packages': 'not a .packages file', - }, (Directory directory) { - expect(findPackageConfig(directory), throwsA(isA())); - }); - - fileTest('invalid .packages as JSON', { - '.packages': packageConfigFile, - }, (Directory directory) { - expect(findPackageConfig(directory), throwsA(isA())); - }); - - fileTest('invalid .packages', { + fileTest('invalid package config not JSON', { '.dart_tool': { 'package_config.json': 'not a JSON file', } @@ -136,41 +114,29 @@ void main() { expect(findPackageConfig(directory), throwsA(isA())); }); - fileTest('invalid .packages as INI', { + fileTest('invalid package config as INI', { '.dart_tool': { 'package_config.json': packagesFile, } }, (Directory directory) { expect(findPackageConfig(directory), throwsA(isA())); }); - }); - - 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()); - }, max: -1)); - expect(hadError, true); - }); - fileTest('invalid .packages as JSON', { - '.packages': packageConfigFile, + fileTest('indirectly through .packages', { + '.packages': packagesFile, + '.dart_tool': { + 'package_config.json': packageConfigFile, + }, }, (Directory directory) async { - var hadError = false; - await findPackageConfig(directory, - onError: expectAsync1((error) { - hadError = true; - expect(error, isA()); - }, max: -1)); - expect(hadError, true); + // 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())); }); + }); - fileTest('invalid package_config not JSON', { + group('handles error', () { + fileTest('invalid package config not JSON', { '.dart_tool': { 'package_config.json': 'not a JSON file', } @@ -208,8 +174,8 @@ void main() { expect(config, null); }); - // 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', { '.dart_tool': { 'package_config.json': packageConfigFile, @@ -242,19 +208,6 @@ void main() { 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('package_config.json non-default name', { @@ -280,32 +233,21 @@ void main() { validatePackagesFile(config, directory); }); - fileTest('.packages', { + 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())); }); 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, onError: expectAsync1((error) { @@ -316,30 +258,9 @@ void main() { }); fileTest('specified file syntax error', { - 'anyname': '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', + 'any_name': '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 542bf0a65..f436a295a 100644 --- a/pkgs/package_config/test/discovery_uri_test.dart +++ b/pkgs/package_config/test/discovery_uri_test.dart @@ -66,17 +66,6 @@ void main() { validatePackagesFile(config, directory); }); - // Finds .packages if no package_config.json. - loaderTest('.packages', { - '.packages': packagesFile, - 'script.dart': 'main(){}', - 'packages': {'shouldNotBeFound': {}} - }, (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', { '.packages': packagesFile, @@ -93,15 +82,15 @@ void main() { validatePackagesFile(config, directory); }); - // Finds .packages in super-directory. - loaderTest('.packages recursive', { + // Does not find a .packages file. + loaderTest('Not .packages', { '.packages': packagesFile, - 'subdir': {'script.dart': 'main(){}'} + 'script.dart': 'main(){}', + 'packages': {'shouldNotBeFound': {}} }, (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); }); // Does not find a packages/ directory, and returns null if nothing found. @@ -113,65 +102,6 @@ void main() { 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())); - }); - - loaderTest('invalid .packages as JSON', { - '.packages': packageConfigFile, - }, (Uri directory, loader) { - expect(() => findPackageConfigUri(directory, loader: loader), - throwsA(isA())); - }); - - loaderTest('invalid .packages', { - '.dart_tool': { - 'package_config.json': 'not a JSON file', - } - }, (Uri directory, loader) { - expect(() => findPackageConfigUri(directory, loader: loader), - throwsA(isA())); - }); - - loaderTest('invalid .packages as INI', { - '.dart_tool': { - 'package_config.json': packagesFile, - } - }, (Uri directory, loader) { - expect(() => findPackageConfigUri(directory, loader: loader), - throwsA(isA())); - }); - - // 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', () { @@ -191,10 +121,13 @@ void main() { }); loaderTest('indirectly through .packages', files, (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); }); }); @@ -221,24 +154,6 @@ void main() { validatePackagesFile(config, directory); }); - 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(() => loadPackageConfigUri(file, loader: loader), @@ -280,7 +195,7 @@ void main() { 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', { 'anyname': 'syntax error', '.dart_tool': { diff --git a/pkgs/package_config/test/parse_test.dart b/pkgs/package_config/test/parse_test.dart index a92b9bfcc..8555b086c 100644 --- a/pkgs/package_config/test/parse_test.dart +++ b/pkgs/package_config/test/parse_test.dart @@ -8,75 +8,11 @@ import 'dart:typed_data'; 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}, {}); - }); - - 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())); - }); - test('$name, handle error', () { - var hadError = false; - packages.parse(utf8.encode(content), baseFile, (error) { - hadError = true; - expect(error, isA()); - }); - 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 = ''' From b50dfdbf6c47bd00aec3f2b5635a490ce36c5536 Mon Sep 17 00:00:00 2001 From: "Lasse R.H. Nielsen" Date: Thu, 6 Mar 2025 16:23:24 +0100 Subject: [PATCH 2/8] Remember -wip on changelog version. --- pkgs/package_config/CHANGELOG.md | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/pkgs/package_config/CHANGELOG.md b/pkgs/package_config/CHANGELOG.md index db5070783..775b6699b 100644 --- a/pkgs/package_config/CHANGELOG.md +++ b/pkgs/package_config/CHANGELOG.md @@ -1,4 +1,4 @@ -## 3.0.0 +## 3.0.0-wip - Removes support for the `.packages` file. The Dart SDK no longer supports that file, and no new `.packages` files From 0586a0b7b84a233d8d3ca09384fc6b753ee31bab Mon Sep 17 00:00:00 2001 From: "Lasse R.H. Nielsen" Date: Tue, 11 Mar 2025 13:41:00 +0100 Subject: [PATCH 3/8] Remove support for `.packages` files. Simplify API which no longer needs to support two files. (Replaces two existing functions with different ones. The old methods are retained as deprecated, and will be removed in a future release.) Adds `@sealed` to model classes in anticipation of making them `final` or `sealed` classes in the future. Add tool to query the package configuration for a file or directory: `bin/package_config_of.dart PATH`. --- pkgs/package_config/CHANGELOG.md | 21 +- pkgs/package_config/lib/package_config.dart | 4 +- .../lib/package_config_types.dart | 1 + .../lib/src/package_config.dart | 214 ++++++++---------- pkgs/package_config/pubspec.yaml | 7 +- pkgs/package_config/test/discovery_test.dart | 2 +- .../test/discovery_uri_test.dart | 6 +- .../test/package_config_impl_test.dart | 8 +- pkgs/package_config/test/parse_test.dart | 2 +- 9 files changed, 119 insertions(+), 146 deletions(-) diff --git a/pkgs/package_config/CHANGELOG.md b/pkgs/package_config/CHANGELOG.md index 775b6699b..83953a9d1 100644 --- a/pkgs/package_config/CHANGELOG.md +++ b/pkgs/package_config/CHANGELOG.md @@ -1,24 +1,31 @@ -## 3.0.0-wip +## 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`. -- **Breaking change** - Simplifies API that no longer needs to support two separate files. +- 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` final classes. + 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`. -- Adds relational operators to `LanguageVersion`. +## 2.2.0 -- Includes correct parameter names in errors when validating - the `major` and `minor` versions in the `LanguageVersion()` constructor. +- Add relational operators to `LanguageVersion` with extension methods + exported under `LanguageVersionRelationalOperators`. + +- Include correct parameter names in errors when validating + the `major` and `minor` versions in the `LanguageVersion.new` constructor. ## 2.1.1 diff --git a/pkgs/package_config/lib/package_config.dart b/pkgs/package_config/lib/package_config.dart index 0c01d6d24..955bed4e1 100644 --- a/pkgs/package_config/lib/package_config.dart +++ b/pkgs/package_config/lib/package_config.dart @@ -41,7 +41,7 @@ Future loadAnyPackageConfig(File file, /// Reads a specific package configuration URI. /// /// The file of the URI must exist, be readable, -/// and be a valid `package_config.json` file. +/// 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] @@ -54,7 +54,7 @@ Future loadAnyPackageConfig(File file, /// 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. diff --git a/pkgs/package_config/lib/package_config_types.dart b/pkgs/package_config/lib/package_config_types.dart index 913f744f6..5c2c3413c 100644 --- a/pkgs/package_config/lib/package_config_types.dart +++ b/pkgs/package_config/lib/package_config_types.dart @@ -17,5 +17,6 @@ export 'src/package_config.dart' show InvalidLanguageVersion, LanguageVersion, + LanguageVersionRelationalOperators, Package, PackageConfig; diff --git a/pkgs/package_config/lib/src/package_config.dart b/pkgs/package_config/lib/src/package_config.dart index 7ef3637ea..2e57d299a 100644 --- a/pkgs/package_config/lib/src/package_config.dart +++ b/pkgs/package_config/lib/src/package_config.dart @@ -4,6 +4,8 @@ import 'dart:typed_data'; +import 'package:meta/meta.dart' show sealed; + import 'errors.dart'; import 'package_config_json.dart'; import 'util.dart'; @@ -15,7 +17,8 @@ import 'util.dart'; /// 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. -abstract final class PackageConfig { +@sealed +abstract class PackageConfig { /// The lowest configuration version currently supported. static const int minVersion = 2; @@ -213,7 +216,8 @@ abstract final class PackageConfig { } /// Configuration data for a single package. -abstract final class Package { +@sealed +abstract class Package { /// Creates a package with the provided properties. /// /// The [name] must be a valid package name. @@ -301,7 +305,8 @@ abstract final class Package { /// If errors during parsing are handled using an `onError` handler, /// then an *invalid* language version may be represented by an /// [InvalidLanguageVersion] object. -abstract final class LanguageVersion implements Comparable { +@sealed +abstract class LanguageVersion implements Comparable { /// The maximal value allowed by [major] and [minor] values; static const int maxValue = 0x7FFFFFFF; @@ -368,40 +373,6 @@ abstract final class LanguageVersion implements Comparable { @override int compareTo(LanguageVersion other); - /// 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. - /// - /// For details on how valid language versions are compared, - /// check out [LanguageVersion.compareTo]. - bool operator <(LanguageVersion other); - - /// 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. - /// - /// For details on how valid language versions are compared, - /// check out [LanguageVersion.compareTo]. - bool operator <=(LanguageVersion other); - - /// Whether this language version is greater than [other]. - /// - /// 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); - - /// Whether this language version is greater than or equal to [other]. - /// - /// 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); - /// Valid language versions with the same [major] and [minor] values are /// equal. /// @@ -428,7 +399,8 @@ abstract final class LanguageVersion implements Comparable { /// which did not throw on an error. /// The caller which provided the `onError` handler which was called /// should be prepared to encounter invalid values. -abstract final class InvalidLanguageVersion implements LanguageVersion { +@sealed +abstract class InvalidLanguageVersion implements LanguageVersion { /// The value -1 for an invalid language version. @override int get major; @@ -449,14 +421,83 @@ abstract final class InvalidLanguageVersion implements LanguageVersion { String toString(); } +/// Relational operators for [LanguageVersion]. +/// +/// 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]. + /// + /// 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 an invalid language version. + if (this is InvalidLanguageVersion) _throwThisInvalid(); + if (other is InvalidLanguageVersion) _throwOtherInvalid(); + return compareTo(other) < 0; + } + + /// Whether this language version is less than or equal to [other]. + /// + /// 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 an invalid language version. + if (this is InvalidLanguageVersion) _throwThisInvalid(); + if (other is InvalidLanguageVersion) _throwOtherInvalid(); + return compareTo(other) <= 0; + } + + /// Whether this language version is greater than [other]. + /// + /// 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 an invalid language version. + if (this is InvalidLanguageVersion) _throwThisInvalid(); + if (other is InvalidLanguageVersion) _throwOtherInvalid(); + return compareTo(other) > 0; + } + + /// Whether this language version is greater than or equal to [other]. + /// + /// If either version being compared is an [InvalidLanguageVersion], + /// a [StateError] is thrown. Verify versions are valid before comparing them. + /// + /// For details on how valid language versions are compared, + /// check out [LanguageVersion.compareTo]. + bool operator >=(LanguageVersion other) { + // Throw an error if comparing an invalid language version. + if (this is InvalidLanguageVersion) _throwThisInvalid(); + if (other is InvalidLanguageVersion) _throwOtherInvalid(); + return compareTo(other) >= 0; + } + + static Never _throwThisInvalid() => throw UnsupportedError( + 'Can\'t compare an invalid language version to another language version. ' + 'Verify language versions are valid before use.'); + static Never _throwOtherInvalid() => throw UnsupportedError( + 'Can\'t compare a language version to an invalid language version. ' + 'Verify language versions are valid before use.'); +} + // -------------------------------------------------------------------- -// Implementation of interfaces. +// 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. -final class SimplePackageConfig implements PackageConfig { +@sealed +class SimplePackageConfig implements PackageConfig { @override final int version; final Map _packages; @@ -620,7 +661,8 @@ final class SimplePackageConfig implements PackageConfig { } /// Configuration data for a single package. -final class SimplePackage implements Package { +@sealed +class SimplePackage implements Package { @override final String name; @override @@ -779,7 +821,8 @@ LanguageVersion parseLanguageVersion( return SimpleLanguageVersion(major, minor, source); } -abstract final class _SimpleLanguageVersionBase implements LanguageVersion { +@sealed +abstract class _SimpleLanguageVersionBase implements LanguageVersion { @override int compareTo(LanguageVersion other) { var result = major - other.major; @@ -788,7 +831,8 @@ abstract final class _SimpleLanguageVersionBase implements LanguageVersion { } } -final class SimpleLanguageVersion extends _SimpleLanguageVersionBase { +@sealed +class SimpleLanguageVersion extends _SimpleLanguageVersionBase { @override final int major; @override @@ -805,66 +849,10 @@ final class SimpleLanguageVersion extends _SimpleLanguageVersionBase { @override String toString() => _source ??= '$major.$minor'; - - /// Whether this language version is less than [other]. - /// - /// Neither version being compared must be an [InvalidLanguageVersion]. - /// - /// For details on how valid language versions are compared, - /// check out [LanguageVersion.compareTo]. - @override - bool operator <(LanguageVersion other) { - // Throw an error if comparing with an invalid language version. - if (other is InvalidLanguageVersion) _throwOtherInvalid(); - - return compareTo(other) < 0; - } - - /// Whether this language version is less than or equal to [other]. - /// - /// Neither version being compared must be an [InvalidLanguageVersion]. - /// - /// For details on how valid language versions are compared, - /// check out [LanguageVersion.compareTo]. - @override - bool operator <=(LanguageVersion other) { - // Throw an error if comparing with an invalid language version. - if (other is InvalidLanguageVersion) _throwOtherInvalid(); - return compareTo(other) <= 0; - } - - /// Whether this language version is greater than [other]. - /// - /// Neither version being compared must be an [InvalidLanguageVersion]. - /// - /// For details on how valid language versions are compared, - /// check out [LanguageVersion.compareTo]. - @override - bool operator >(LanguageVersion other) { - if (other is InvalidLanguageVersion) _throwOtherInvalid(); - return compareTo(other) > 0; - } - - /// Whether this language version is greater than or equal to [other]. - /// - /// If either version being compared is an [InvalidLanguageVersion], - /// a [StateError] is thrown. Verify versions are valid before comparing them. - /// - /// For details on how valid language versions are compared, - /// check out [LanguageVersion.compareTo]. - @override - bool operator >=(LanguageVersion other) { - // Throw an error if comparing with an invalid language version. - if (other is InvalidLanguageVersion) _throwOtherInvalid(); - return compareTo(other) >= 0; - } - - static Never _throwOtherInvalid() => throw StateError( - 'Can\'t compare a language version to an invalid language version. ' - 'Verify language versions are valid after parsing.'); } -final class SimpleInvalidLanguageVersion extends _SimpleLanguageVersionBase +@sealed +class SimpleInvalidLanguageVersion extends _SimpleLanguageVersionBase implements InvalidLanguageVersion { final String? _source; SimpleInvalidLanguageVersion(this._source); @@ -873,32 +861,8 @@ final class SimpleInvalidLanguageVersion extends _SimpleLanguageVersionBase @override int get minor => -1; - @override - bool operator <(LanguageVersion other) { - _throwThisInvalid(); - } - - @override - bool operator <=(LanguageVersion other) { - _throwThisInvalid(); - } - - @override - bool operator >(LanguageVersion other) { - _throwThisInvalid(); - } - - @override - bool operator >=(LanguageVersion other) { - _throwThisInvalid(); - } - @override String toString() => _source!; - - static Never _throwThisInvalid() => throw StateError( - 'Can\'t compare an invalid language version to another language version. ' - 'Verify language versions are valid after parsing.'); } abstract class PackageTree { diff --git a/pkgs/package_config/pubspec.yaml b/pkgs/package_config/pubspec.yaml index c0a081ae6..f81f5dbcc 100644 --- a/pkgs/package_config/pubspec.yaml +++ b/pkgs/package_config/pubspec.yaml @@ -1,15 +1,16 @@ name: package_config -version: 3.0.0-wip +version: 2.3.0-wip description: Support for reading and writing Dart Package Configuration files. repository: https://github.com/dart-lang/tools/tree/main/pkgs/package_config issue_tracker: https://github.com/dart-lang/tools/labels/package%3Apackage_config environment: - sdk: ^3.4.0 + sdk: ^3.7.0 dependencies: + meta: ^1.15.0 path: ^1.8.0 dev_dependencies: - dart_flutter_team_lints: ^3.1.0 + dart_flutter_team_lints: ^3.4.0 test: ^1.16.0 diff --git a/pkgs/package_config/test/discovery_test.dart b/pkgs/package_config/test/discovery_test.dart index dbe691efc..3ecbfad14 100644 --- a/pkgs/package_config/test/discovery_test.dart +++ b/pkgs/package_config/test/discovery_test.dart @@ -98,7 +98,7 @@ void main() { // Does not find a packages/ directory, and returns null if nothing found. fileTest('package directory packages not supported', { 'packages': { - 'foo': {}, + 'foo': {}, } }, (Directory directory) async { var config = await findPackageConfig(directory); diff --git a/pkgs/package_config/test/discovery_uri_test.dart b/pkgs/package_config/test/discovery_uri_test.dart index f436a295a..b16eca83c 100644 --- a/pkgs/package_config/test/discovery_uri_test.dart +++ b/pkgs/package_config/test/discovery_uri_test.dart @@ -56,7 +56,7 @@ void main() { loaderTest('package_config.json', { '.packages': 'invalid .packages file', 'script.dart': 'main(){}', - 'packages': {'shouldNotBeFound': {}}, + 'packages': {'shouldNotBeFound': {}}, '.dart_tool': { 'package_config.json': packageConfigFile, } @@ -86,7 +86,7 @@ void main() { loaderTest('Not .packages', { '.packages': packagesFile, 'script.dart': 'main(){}', - 'packages': {'shouldNotBeFound': {}} + 'packages': {'shouldNotBeFound': {}} }, (directory, loader) async { var config = await findPackageConfigUri(recurse: false, directory, loader: loader); @@ -96,7 +96,7 @@ void main() { // Does not find a packages/ directory, and returns null if nothing found. loaderTest('package directory packages not supported', { 'packages': { - 'foo': {}, + 'foo': {}, } }, (Uri directory, loader) async { var config = await findPackageConfigUri(directory, loader: loader); diff --git a/pkgs/package_config/test/package_config_impl_test.dart b/pkgs/package_config/test/package_config_impl_test.dart index fff82fa5e..ce02ef95a 100644 --- a/pkgs/package_config/test/package_config_impl_test.dart +++ b/pkgs/package_config/test/package_config_impl_test.dart @@ -133,11 +133,11 @@ void main() { ) { expect(version == otherVersion, identical(version, otherVersion)); - expect(() => version < otherVersion, throwsA(isA())); - expect(() => version <= otherVersion, throwsA(isA())); + expect(() => version < otherVersion, throwsA(isA())); + expect(() => version <= otherVersion, throwsA(isA())); - expect(() => version > otherVersion, throwsA(isA())); - expect(() => version >= otherVersion, throwsA(isA())); + expect(() => version > otherVersion, throwsA(isA())); + expect(() => version >= otherVersion, throwsA(isA())); } var validVersion = LanguageVersion(3, 5); diff --git a/pkgs/package_config/test/parse_test.dart b/pkgs/package_config/test/parse_test.dart index 8555b086c..1a057d11a 100644 --- a/pkgs/package_config/test/parse_test.dart +++ b/pkgs/package_config/test/parse_test.dart @@ -270,7 +270,7 @@ void main() { void testThrowsContains( String name, String source, String containsString) { test(name, () { - dynamic exception; + Object? exception; try { parsePackageConfigBytes( // ignore: unnecessary_cast From e946009395e0b8deed145081c0cab67325ffc82f Mon Sep 17 00:00:00 2001 From: "Lasse R.H. Nielsen" Date: Tue, 11 Mar 2025 13:49:06 +0100 Subject: [PATCH 4/8] New format. --- pkgs/package_config/lib/package_config.dart | 86 ++- pkgs/package_config/lib/src/discovery.dart | 56 +- pkgs/package_config/lib/src/errors.dart | 17 +- .../lib/src/package_config.dart | 366 ++++++++---- .../lib/src/package_config_io.dart | 33 +- .../lib/src/package_config_json.dart | 95 ++- pkgs/package_config/lib/src/util.dart | 55 +- pkgs/package_config/test/bench.dart | 6 +- pkgs/package_config/test/discovery_test.dart | 337 ++++++----- .../test/discovery_uri_test.dart | 286 +++++---- .../test/package_config_impl_test.dart | 115 ++-- pkgs/package_config/test/parse_test.dart | 542 +++++++++++------- pkgs/package_config/test/src/util_io.dart | 7 +- 13 files changed, 1271 insertions(+), 730 deletions(-) diff --git a/pkgs/package_config/lib/package_config.dart b/pkgs/package_config/lib/package_config.dart index 955bed4e1..fc618539d 100644 --- a/pkgs/package_config/lib/package_config.dart +++ b/pkgs/package_config/lib/package_config.dart @@ -28,15 +28,18 @@ export 'package_config_types.dart'; /// The returned package configuration is a *best effort* attempt to create /// a valid configuration from the invalid configuration file. /// If no [onError] is provided, errors are thrown immediately. -Future loadPackageConfig(File file, - {void Function(Object error)? onError}) => - readConfigFile(file, onError ?? throwError); +Future loadPackageConfig( + File file, { + void Function(Object error)? onError, +}) => readConfigFile(file, onError ?? throwError); /// @nodoc @Deprecated('use loadPackageConfig instead') -Future loadAnyPackageConfig(File file, - {bool preferNewest = true, void Function(Object error)? onError}) => - loadPackageConfig(file, onError: onError); +Future loadAnyPackageConfig( + File file, { + bool preferNewest = true, + void Function(Object error)? onError, +}) => loadPackageConfig(file, onError: onError); /// Reads a specific package configuration URI. /// @@ -66,16 +69,19 @@ Future loadAnyPackageConfig(File file, /// The returned package configuration is a *best effort* attempt to create /// a valid configuration from the invalid configuration file. /// If no [onError] is provided, errors are thrown immediately. -Future loadPackageConfigUri(Uri file, - {Future Function(Uri uri)? loader, - void Function(Object error)? onError}) => - readConfigFileUri(file, loader, onError ?? throwError); +Future loadPackageConfigUri( + Uri file, { + Future Function(Uri uri)? loader, + void Function(Object error)? onError, +}) => readConfigFileUri(file, loader, onError ?? throwError); /// @nodoc @Deprecated('use loadPackageConfigUri instead') -Future loadAnyPackageConfigUri(Uri uri, - {bool preferNewest = true, void Function(Object error)? onError}) => - loadPackageConfigUri(uri, onError: onError); +Future loadAnyPackageConfigUri( + Uri uri, { + bool preferNewest = true, + void Function(Object error)? onError, +}) => loadPackageConfigUri(uri, onError: onError); /// Finds a package configuration relative to [directory]. /// @@ -99,16 +105,25 @@ Future loadAnyPackageConfigUri(Uri uri, /// any lower-version configuration files are ignored in the search. /// /// Returns `null` if no configuration file is found. -Future findPackageConfig(Directory directory, - {bool recurse = true, - void Function(Object error)? onError, - int minVersion = 1}) { +Future findPackageConfig( + Directory directory, { + bool recurse = true, + void Function(Object error)? onError, + int minVersion = 1, +}) { if (minVersion > PackageConfig.maxVersion) { - throw ArgumentError.value(minVersion, 'minVersion', - 'Maximum known version is ${PackageConfig.maxVersion}'); + throw ArgumentError.value( + minVersion, + 'minVersion', + 'Maximum known version is ${PackageConfig.maxVersion}', + ); } return discover.findPackageConfig( - directory, minVersion, recurse, onError ?? throwError); + directory, + minVersion, + recurse, + onError ?? throwError, + ); } /// Finds a package configuration relative to [location]. @@ -153,17 +168,27 @@ Future findPackageConfig(Directory directory, /// any lower-version configuration files are ignored in the search. /// /// Returns `null` if no configuration file is found. -Future findPackageConfigUri(Uri location, - {bool recurse = true, - int minVersion = 1, - Future Function(Uri uri)? loader, - void Function(Object error)? onError}) { +Future findPackageConfigUri( + Uri location, { + bool recurse = true, + int minVersion = 1, + Future Function(Uri uri)? loader, + void Function(Object error)? onError, +}) { if (minVersion > PackageConfig.maxVersion) { - throw ArgumentError.value(minVersion, 'minVersion', - 'Maximum known version is ${PackageConfig.maxVersion}'); + throw ArgumentError.value( + minVersion, + 'minVersion', + 'Maximum known version is ${PackageConfig.maxVersion}', + ); } return discover.findPackageConfigUri( - location, minVersion, loader, onError ?? throwError, recurse); + location, + minVersion, + loader, + onError ?? throwError, + recurse, + ); } /// Writes a package configuration to the provided directory. @@ -175,5 +200,6 @@ Future findPackageConfigUri(Uri location, /// A comment is generated if `[PackageConfig.extraData]` contains a /// `"generator"` entry. Future savePackageConfig( - PackageConfig configuration, Directory directory) => - writePackageConfigJsonFile(configuration, directory); + PackageConfig configuration, + Directory directory, +) => writePackageConfigJsonFile(configuration, directory); diff --git a/pkgs/package_config/lib/src/discovery.dart b/pkgs/package_config/lib/src/discovery.dart index e02ce1ec0..4e9e22dd1 100644 --- a/pkgs/package_config/lib/src/discovery.dart +++ b/pkgs/package_config/lib/src/discovery.dart @@ -31,16 +31,23 @@ final Uri parentPath = Uri(path: '..'); /// /// If [minVersion] is greater than the version read from the /// `package_config.json` file, it too is ignored. -Future findPackageConfig(Directory baseDirectory, - int minVersion, bool recursive, void Function(Object error) onError) async { +Future findPackageConfig( + Directory baseDirectory, + int minVersion, + bool recursive, + void Function(Object error) onError, +) async { var directory = baseDirectory; if (!directory.isAbsolute) directory = directory.absolute; if (!await directory.exists()) { return null; } do { - var packageConfig = - await findPackageConfigInDirectory(directory, minVersion, onError); + var packageConfig = await findPackageConfigInDirectory( + directory, + minVersion, + onError, + ); if (packageConfig != null) return packageConfig; if (!recursive) break; // Check in parent directories. @@ -53,23 +60,30 @@ Future findPackageConfig(Directory baseDirectory, /// Similar to [findPackageConfig] but based on a URI. Future findPackageConfigUri( - Uri location, - int minVersion, - Future Function(Uri uri)? loader, - void Function(Object error) onError, - bool recursive) async { + Uri location, + int minVersion, + Future Function(Uri uri)? loader, + void Function(Object error) onError, + bool recursive, +) async { if (location.isScheme('package')) { - onError(PackageConfigArgumentError( - location, 'location', 'Must not be a package: URI')); + onError( + PackageConfigArgumentError( + location, + 'location', + 'Must not be a package: URI', + ), + ); return null; } if (loader == null) { if (location.isScheme('file')) { return findPackageConfig( - Directory.fromUri(location.resolveUri(currentPath)), - minVersion, - recursive, - onError); + Directory.fromUri(location.resolveUri(currentPath)), + minVersion, + recursive, + onError, + ); } loader = defaultLoader; } @@ -102,8 +116,11 @@ Future findPackageConfigUri( /// /// If [minVersion] is greater than the version read from the /// `package_config.json` file, it too is ignored. -Future findPackageConfigInDirectory(Directory directory, - int minVersion, void Function(Object error) onError) async { +Future findPackageConfigInDirectory( + Directory directory, + int minVersion, + void Function(Object error) onError, +) async { var packageConfigFile = await checkForPackageConfigJsonFile(directory); if (packageConfigFile != null) { var config = await readPackageConfigJsonFile(packageConfigFile, onError); @@ -115,8 +132,9 @@ Future findPackageConfigInDirectory(Directory directory, Future checkForPackageConfigJsonFile(Directory directory) async { assert(directory.isAbsolute); - var file = - File(pathJoin(directory.path, '.dart_tool', 'package_config.json')); + var file = File( + pathJoin(directory.path, '.dart_tool', 'package_config.json'), + ); if (await file.exists()) return file; return null; } diff --git a/pkgs/package_config/lib/src/errors.dart b/pkgs/package_config/lib/src/errors.dart index a66fef7f3..ba6ad5d38 100644 --- a/pkgs/package_config/lib/src/errors.dart +++ b/pkgs/package_config/lib/src/errors.dart @@ -13,20 +13,25 @@ abstract class PackageConfigError { class PackageConfigArgumentError extends ArgumentError implements PackageConfigError { PackageConfigArgumentError( - Object? super.value, String super.name, String super.message) - : super.value(); + Object? super.value, + String super.name, + String super.message, + ) : super.value(); PackageConfigArgumentError.from(ArgumentError error) - : super.value(error.invalidValue, error.name, error.message); + : super.value(error.invalidValue, error.name, error.message); } class PackageConfigFormatException extends FormatException implements PackageConfigError { - PackageConfigFormatException(super.message, Object? super.source, - [super.offset]); + PackageConfigFormatException( + super.message, + Object? super.source, [ + super.offset, + ]); PackageConfigFormatException.from(FormatException exception) - : super(exception.message, exception.source, exception.offset); + : super(exception.message, exception.source, exception.offset); } /// The default `onError` handler. diff --git a/pkgs/package_config/lib/src/package_config.dart b/pkgs/package_config/lib/src/package_config.dart index 2e57d299a..2dc035da3 100644 --- a/pkgs/package_config/lib/src/package_config.dart +++ b/pkgs/package_config/lib/src/package_config.dart @@ -75,9 +75,11 @@ abstract class PackageConfig { /// despite the error. The input must still be valid JSON. /// The result may be [PackageConfig.empty] if there is no way to /// extract useful information from the bytes. - static PackageConfig parseBytes(Uint8List bytes, Uri baseUri, - {void Function(Object error)? onError}) => - parsePackageConfigBytes(bytes, baseUri, onError ?? throwError); + static PackageConfig parseBytes( + Uint8List bytes, + Uri baseUri, { + void Function(Object error)? onError, + }) => parsePackageConfigBytes(bytes, baseUri, onError ?? throwError); /// Parses a package configuration file. /// @@ -95,9 +97,11 @@ abstract class PackageConfig { /// despite the error. The input must still be valid JSON. /// The result may be [PackageConfig.empty] if there is no way to /// extract useful information from the bytes. - static PackageConfig parseString(String configuration, Uri baseUri, - {void Function(Object error)? onError}) => - parsePackageConfigString(configuration, baseUri, onError ?? throwError); + static PackageConfig parseString( + String configuration, + Uri baseUri, { + void Function(Object error)? onError, + }) => parsePackageConfigString(configuration, baseUri, onError ?? throwError); /// Parses the JSON data of a package configuration file. /// @@ -116,16 +120,21 @@ abstract class PackageConfig { /// despite the error. The input must still be valid JSON. /// The result may be [PackageConfig.empty] if there is no way to /// extract useful information from the bytes. - static PackageConfig parseJson(Object? jsonData, Uri baseUri, - {void Function(Object error)? onError}) => - parsePackageConfigJson(jsonData, baseUri, onError ?? throwError); + static PackageConfig parseJson( + Object? jsonData, + Uri baseUri, { + void Function(Object error)? onError, + }) => parsePackageConfigJson(jsonData, baseUri, onError ?? throwError); /// Writes a configuration file for this configuration on [output]. /// /// If [baseUri] is provided, URI references in the generated file /// will be made relative to [baseUri] where possible. - static void writeBytes(PackageConfig configuration, Sink output, - [Uri? baseUri]) { + static void writeBytes( + PackageConfig configuration, + Sink output, [ + Uri? baseUri, + ]) { writePackageConfigJsonUtf8(configuration, baseUri, output); } @@ -133,8 +142,11 @@ abstract class PackageConfig { /// /// If [baseUri] is provided, URI references in the generated file /// will be made relative to [baseUri] where possible. - static void writeString(PackageConfig configuration, StringSink output, - [Uri? baseUri]) { + static void writeString( + PackageConfig configuration, + StringSink output, [ + Uri? baseUri, + ]) { writePackageConfigJsonString(configuration, baseUri, output); } @@ -142,9 +154,10 @@ abstract class PackageConfig { /// /// If [baseUri] is provided, URI references in the generated data /// will be made relative to [baseUri] where possible. - static Map toJson(PackageConfig configuration, - [Uri? baseUri]) => - packageConfigToJson(configuration, baseUri); + static Map toJson( + PackageConfig configuration, [ + Uri? baseUri, + ]) => packageConfigToJson(configuration, baseUri); /// The configuration version number. /// @@ -238,13 +251,23 @@ abstract class Package { /// /// If [extraData] is supplied, it will be available as the /// [Package.extraData] of the created package. - factory Package(String name, Uri root, - {Uri? packageUriRoot, - LanguageVersion? languageVersion, - Object? extraData, - bool relativeRoot = true}) => - SimplePackage.validate(name, root, packageUriRoot, languageVersion, - extraData, relativeRoot, throwError)!; + factory Package( + String name, + Uri root, { + Uri? packageUriRoot, + LanguageVersion? languageVersion, + Object? extraData, + bool relativeRoot = true, + }) => + SimplePackage.validate( + name, + root, + packageUriRoot, + languageVersion, + extraData, + relativeRoot, + throwError, + )!; /// The package-name of the package. String get name; @@ -338,9 +361,10 @@ abstract class LanguageVersion implements Comparable { /// If [onError] is not supplied, it defaults to throwing the exception. /// If the call does not throw, then an [InvalidLanguageVersion] is returned /// containing the original [source]. - static LanguageVersion parse(String source, - {void Function(Object error)? onError}) => - parseLanguageVersion(source, onError ?? throwError); + static LanguageVersion parse( + String source, { + void Function(Object error)? onError, + }) => parseLanguageVersion(source, onError ?? throwError); /// The major language version. /// @@ -481,12 +505,17 @@ extension LanguageVersionRelationalOperators on LanguageVersion { return compareTo(other) >= 0; } - static Never _throwThisInvalid() => throw UnsupportedError( - 'Can\'t compare an invalid language version to another language version. ' - 'Verify language versions are valid before use.'); - static Never _throwOtherInvalid() => throw UnsupportedError( - 'Can\'t compare a language version to an invalid language version. ' - 'Verify language versions are valid before use.'); + static Never _throwThisInvalid() => + throw UnsupportedError( + 'Can\'t compare an invalid language version to another ' + 'language version. ' + 'Verify language versions are valid before use.', + ); + static Never _throwOtherInvalid() => + throw UnsupportedError( + 'Can\'t compare a language version to an invalid language version. ' + 'Verify language versions are valid before use.', + ); } // -------------------------------------------------------------------- @@ -505,18 +534,27 @@ class SimplePackageConfig implements PackageConfig { @override final Object? extraData; - factory SimplePackageConfig(int version, Iterable packages, - [Object? extraData, void Function(Object error)? onError]) { + factory SimplePackageConfig( + int version, + Iterable 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); + return SimplePackageConfig._(validVersion, packageTree, { + for (var p in packageTree.allPackages) p.name: p, + }, extraData); } SimplePackageConfig._( - this.version, this._packageTree, this._packages, this.extraData); + this.version, + this._packageTree, + this._packages, + this.extraData, + ); /// Creates empty configuration. /// @@ -526,23 +564,33 @@ class SimplePackageConfig implements PackageConfig { /// 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 {}, - extraData = null; + : version = PackageConfig.maxVersion, + _packageTree = const EmptyPackageTree(), + _packages = const {}, + extraData = null; static int _validateVersion( - int version, void Function(Object error) onError) { + 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}')); + 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 originalPackages, - List packages, void Function(Object error) onError) { + static PackageTree _validatePackages( + Iterable originalPackages, + List packages, + void Function(Object error) onError, + ) { var packageNames = {}; var tree = TriePackageTree(); for (var originalPackage in packages) { @@ -550,27 +598,39 @@ class SimplePackageConfig implements PackageConfig { 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); - } - }); + 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'")); + onError( + PackageConfigArgumentError( + name, + 'packages', + "Duplicate package name '$name'", + ), + ); continue; } packageNames.add(name); @@ -580,16 +640,20 @@ class SimplePackageConfig implements PackageConfig { var existingPackage = error.existingPackage; switch (error.conflictType) { case ConflictType.sameRoots: - onError(PackageConfigArgumentError( + onError( + PackageConfigArgumentError( originalPackages, 'packages', 'Packages ${newPackage!.name} and ${existingPackage.name} ' - 'have the same root directory: ${newPackage.root}.\n')); + '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( + onError( + PackageConfigArgumentError( originalPackages, 'packages', 'Package ${newPackage!.name} is inside the root of ' @@ -598,17 +662,22 @@ class SimplePackageConfig implements PackageConfig { '${newPackage.name}.\n' '${existingPackage.name} package root: ' '${existingPackage.packageUriRoot}\n' - '${newPackage.name} root: ${newPackage.root}\n')); + '${newPackage.name} root: ${newPackage.root}\n', + ), + ); break; case ConflictType.insidePackageRoot: - onError(PackageConfigArgumentError( + 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')); + '${newPackage.name} root: ${newPackage.root}\n', + ), + ); break; } } else { @@ -633,18 +702,25 @@ class SimplePackageConfig implements PackageConfig { Uri? resolve(Uri packageUri) { var packageName = checkValidPackageUri(packageUri, 'packageUri'); return _packages[packageName]?.packageUriRoot.resolveUri( - Uri(path: packageUri.path.substring(packageName.length + 1))); + 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'); + nonPackageUri, + 'nonPackageUri', + 'Must not be a package URI', + ); } if (nonPackageUri.hasQuery || nonPackageUri.hasFragment) { - throw PackageConfigArgumentError(nonPackageUri, 'nonPackageUri', - 'Must not have query or fragment part'); + throw PackageConfigArgumentError( + nonPackageUri, + 'nonPackageUri', + 'Must not have query or fragment part', + ); } // Find package that file belongs to. var package = _packageTree.packageOf(nonPackageUri); @@ -676,8 +752,14 @@ class SimplePackage implements Package { @override final bool relativeRoot; - SimplePackage._(this.name, this.root, this.packageUriRoot, - this.languageVersion, this.extraData, this.relativeRoot); + SimplePackage._( + this.name, + this.root, + this.packageUriRoot, + this.languageVersion, + this.extraData, + this.relativeRoot, + ); /// Creates a [SimplePackage] with the provided content. /// @@ -695,30 +777,44 @@ class SimplePackage implements Package { /// 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) { + 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)); + 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')); + onError( + PackageConfigArgumentError( + '$root', + 'root', + 'Must not be a package URI', + ), + ); fatalError = true; } else if (!isAbsoluteDirectoryUri(root)) { - onError(PackageConfigArgumentError( + onError( + PackageConfigArgumentError( '$root', 'root', 'In package $name: Not an absolute URI with no query or fragment ' - 'with a path ending in /')); + 'with a path ending in /', + ), + ); // Try to recover. If the URI has a scheme, // then ensure that the path ends with `/`. if (!root.hasScheme) { @@ -732,21 +828,35 @@ class SimplePackage implements Package { } else if (!fatalError) { packageUriRoot = root.resolveUri(packageUriRoot); if (!isAbsoluteDirectoryUri(packageUriRoot)) { - onError(PackageConfigArgumentError( + onError( + PackageConfigArgumentError( packageUriRoot, 'packageUriRoot', 'In package $name: Not an absolute URI with no query or fragment ' - 'with a path ending in /')); + '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')); + 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); + name, + root, + packageUriRoot, + languageVersion, + extraData, + relativeRoot, + ); } } @@ -757,7 +867,9 @@ class SimplePackage implements Package { /// 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) { + 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. @@ -785,7 +897,8 @@ LanguageVersion parseLanguageVersion( value = value * 10 + digit; if (value > maxValue) { onError( - PackageConfigFormatException('Number too large', source, start)); + PackageConfigFormatException('Number too large', source, start), + ); return -1; } index++; @@ -794,8 +907,9 @@ LanguageVersion parseLanguageVersion( digit = char ^ 0x30; } while (digit <= 9); if (firstDigit == 0 && index > start + 1) { - onError(PackageConfigFormatException( - 'Leading zero not allowed', source, start)); + onError( + PackageConfigFormatException('Leading zero not allowed', source, start), + ); } return value; } @@ -814,8 +928,13 @@ LanguageVersion parseLanguageVersion( return SimpleInvalidLanguageVersion(source); } if (index != source.length) { - onError(PackageConfigFormatException( - 'Unexpected trailing character', source, index)); + onError( + PackageConfigFormatException( + 'Unexpected trailing character', + source, + index, + ), + ); return SimpleInvalidLanguageVersion(source); } return SimpleLanguageVersion(major, minor, source); @@ -903,24 +1022,40 @@ class TriePackageTree implements PackageTree { } } - bool _checkConflict(_PackageTrieNode node, SimplePackage newPackage, - void Function(Object error) onError) { + 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)); + 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)); + if (_beginsWith( + 0, + newPackage.root.toString(), + existingPackage.packageUriRoot.toString(), + )) { + onError( + ConflictException( + newPackage, + existingPackage, + ConflictType.interleaving, + ), + ); return true; } @@ -928,10 +1063,18 @@ class TriePackageTree implements PackageTree { // 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)); + if (_beginsWith( + 0, + existingPackage.packageUriRoot.toString(), + newPackage.root.toString(), + )) { + onError( + ConflictException( + newPackage, + existingPackage, + ConflictType.insidePackageRoot, + ), + ); return true; } } @@ -969,7 +1112,10 @@ class TriePackageTree implements PackageTree { } bool _isMatch( - String path, _PackageTrieNode node, List potential) { + String path, + _PackageTrieNode node, + List potential, + ) { var currentPackage = node.package; if (currentPackage != null) { var currentPackageRootLength = currentPackage.root.toString().length; diff --git a/pkgs/package_config/lib/src/package_config_io.dart b/pkgs/package_config/lib/src/package_config_io.dart index 19cae38c3..26307c07b 100644 --- a/pkgs/package_config/lib/src/package_config_io.dart +++ b/pkgs/package_config/lib/src/package_config_io.dart @@ -27,7 +27,9 @@ const packageConfigFileName = 'package_config.json'; /// /// The file must exist and be a normal file. Future readConfigFile( - File file, void Function(Object error) onError) async { + File file, + void Function(Object error) onError, +) async { Uint8List bytes; try { bytes = await file.readAsBytes(); @@ -40,12 +42,16 @@ Future readConfigFile( /// Like [readConfigFile] but uses a URI and an optional loader. Future readConfigFileUri( - Uri file, - Future Function(Uri uri)? loader, - void Function(Object error) onError) async { + Uri file, + Future Function(Uri uri)? loader, + void Function(Object error) onError, +) async { if (file.isScheme('package')) { throw PackageConfigArgumentError( - file, 'file', 'Must not be a package: URI'); + file, + 'file', + 'Must not be a package: URI', + ); } if (loader == null) { if (file.isScheme('file')) { @@ -62,15 +68,22 @@ Future readConfigFileUri( return const SimplePackageConfig.empty(); } if (bytes == null) { - onError(PackageConfigArgumentError( - file.toString(), 'file', 'File cannot be read')); + onError( + PackageConfigArgumentError( + file.toString(), + 'file', + 'File cannot be read', + ), + ); return const SimplePackageConfig.empty(); } return parsePackageConfigBytes(bytes, file, onError); } Future readPackageConfigJsonFile( - File file, void Function(Object error) onError) async { + File file, + void Function(Object error) onError, +) async { Uint8List bytes; try { bytes = await file.readAsBytes(); @@ -82,7 +95,9 @@ Future readPackageConfigJsonFile( } Future writePackageConfigJsonFile( - PackageConfig config, Directory targetDirectory) async { + PackageConfig config, + Directory targetDirectory, +) async { // Write .dart_tool/package_config.json first. var dartToolDir = Directory(pathJoin(targetDirectory.path, dartToolDirName)); await dartToolDir.create(recursive: true); diff --git a/pkgs/package_config/lib/src/package_config_json.dart b/pkgs/package_config/lib/src/package_config_json.dart index afc18cb4e..306af2c0d 100644 --- a/pkgs/package_config/lib/src/package_config_json.dart +++ b/pkgs/package_config/lib/src/package_config_json.dart @@ -22,13 +22,16 @@ const List _packageNames = [ _nameKey, _rootUriKey, _packageUriKey, - _languageVersionKey + _languageVersionKey, ]; final _jsonUtf8Decoder = json.fuse(utf8).decoder; PackageConfig parsePackageConfigBytes( - Uint8List bytes, Uri file, void Function(Object error) onError) { + Uint8List bytes, + Uri file, + void Function(Object error) onError, +) { // TODO(lrn): Make this simpler. Maybe parse directly from bytes. Object? jsonObject; try { @@ -41,7 +44,10 @@ PackageConfig parsePackageConfigBytes( } PackageConfig parsePackageConfigString( - String source, Uri file, void Function(Object error) onError) { + String source, + Uri file, + void Function(Object error) onError, +) { Object? jsonObject; try { jsonObject = jsonDecode(source); @@ -73,10 +79,16 @@ PackageConfig parsePackageConfigString( /// The [baseLocation] is used as base URI to resolve the "rootUri" /// URI reference string. PackageConfig parsePackageConfigJson( - Object? json, Uri baseLocation, void Function(Object error) onError) { + Object? json, + Uri baseLocation, + void Function(Object error) onError, +) { if (!baseLocation.hasScheme || baseLocation.isScheme('package')) { - throw PackageConfigArgumentError(baseLocation.toString(), 'baseLocation', - 'Must be an absolute non-package: URI'); + throw PackageConfigArgumentError( + baseLocation.toString(), + 'baseLocation', + 'Must be an absolute non-package: URI', + ); } if (!baseLocation.path.endsWith('/')) { @@ -157,16 +169,25 @@ PackageConfig parsePackageConfigJson( } return SimplePackage.validate( - name!, root, packageRoot, version, extraData, relativeRoot, (error) { - if (error is ArgumentError) { - onError( - PackageConfigFormatException( - error.message.toString(), error.invalidValue), - ); - } else { - onError(error); - } - }); + name!, + root, + packageRoot, + version, + extraData, + relativeRoot, + (error) { + if (error is ArgumentError) { + onError( + PackageConfigFormatException( + error.message.toString(), + error.invalidValue, + ), + ); + } else { + onError(error); + } + }, + ); } var map = checkType>(json, 'value'); @@ -183,8 +204,10 @@ PackageConfig parsePackageConfigJson( var packageArray = checkType>(value, _packagesKey) ?? []; var packages = []; for (var package in packageArray) { - var packageMap = - checkType>(package, 'package entry'); + var packageMap = checkType>( + package, + 'package entry', + ); if (packageMap != null) { var entry = parsePackage(packageMap); if (entry != null) { @@ -211,7 +234,9 @@ PackageConfig parsePackageConfigJson( if (error is ArgumentError) { onError( PackageConfigFormatException( - error.message.toString(), error.invalidValue), + error.message.toString(), + error.invalidValue, + ), ); } else { onError(error); @@ -222,14 +247,20 @@ PackageConfig parsePackageConfigJson( final _jsonUtf8Encoder = JsonUtf8Encoder(' '); void writePackageConfigJsonUtf8( - PackageConfig config, Uri? baseUri, Sink> output) { + PackageConfig config, + Uri? baseUri, + Sink> output, +) { // Can be optimized. var data = packageConfigToJson(config, baseUri); output.add(_jsonUtf8Encoder.convert(data) as Uint8List); } void writePackageConfigJsonString( - PackageConfig config, Uri? baseUri, StringSink output) { + PackageConfig config, + Uri? baseUri, + StringSink output, +) { // Can be optimized. var data = packageConfigToJson(config, baseUri); output.write(const JsonEncoder.withIndent(' ').convert(data)); @@ -243,19 +274,21 @@ Map packageConfigToJson(PackageConfig config, Uri? baseUri) => for (var package in config.packages) { _nameKey: package.name, - _rootUriKey: trailingSlash((package.relativeRoot - ? relativizeUri(package.root, baseUri) - : package.root) - .toString()), + _rootUriKey: trailingSlash( + (package.relativeRoot + ? relativizeUri(package.root, baseUri) + : package.root) + .toString(), + ), if (package.root != package.packageUriRoot) _packageUriKey: trailingSlash( - relativizeUri(package.packageUriRoot, package.root) - .toString()), + relativizeUri(package.packageUriRoot, package.root).toString(), + ), if (package.languageVersion != null && package.languageVersion is! InvalidLanguageVersion) _languageVersionKey: package.languageVersion.toString(), ...?_extractExtraData(package.extraData, _packageNames), - } + }, ], }; @@ -264,14 +297,16 @@ Map packageConfigToJson(PackageConfig config, Uri? baseUri) => /// If the value contains any of the [reservedNames] for the current context, /// entries with that name in the extra data are dropped. Map? _extractExtraData( - Object? data, Iterable reservedNames) { + Object? data, + Iterable reservedNames, +) { if (data is Map) { if (data.isEmpty) return null; for (var name in reservedNames) { if (data.containsKey(name)) { var filteredData = { for (var key in data.keys) - if (!reservedNames.contains(key)) key: data[key] + if (!reservedNames.contains(key)) key: data[key], }; if (filteredData.isEmpty) return null; for (var value in filteredData.values) { diff --git a/pkgs/package_config/lib/src/util.dart b/pkgs/package_config/lib/src/util.dart index 4f0210cda..cebd3c8c0 100644 --- a/pkgs/package_config/lib/src/util.dart +++ b/pkgs/package_config/lib/src/util.dart @@ -52,12 +52,18 @@ String checkValidPackageUri(Uri packageUri, String name) { } if (packageUri.hasAuthority) { throw PackageConfigArgumentError( - packageUri, name, 'Package URIs must not have a host part'); + packageUri, + name, + 'Package URIs must not have a host part', + ); } if (packageUri.hasQuery) { // A query makes no sense if resolved to a file: URI. throw PackageConfigArgumentError( - packageUri, name, 'Package URIs must not have a query part'); + packageUri, + name, + 'Package URIs must not have a query part', + ); } if (packageUri.hasFragment) { // We could leave the fragment after the URL when resolving, @@ -65,27 +71,42 @@ String checkValidPackageUri(Uri packageUri, String name) { // "package:foo/foo.dart#2" were considered different libraries. // Keep the syntax open in case we ever get multiple libraries in one file. throw PackageConfigArgumentError( - packageUri, name, 'Package URIs must not have a fragment part'); + packageUri, + name, + 'Package URIs must not have a fragment part', + ); } if (packageUri.path.startsWith('/')) { throw PackageConfigArgumentError( - packageUri, name, "Package URIs must not start with a '/'"); + packageUri, + name, + "Package URIs must not start with a '/'", + ); } var firstSlash = packageUri.path.indexOf('/'); if (firstSlash == -1) { - throw PackageConfigArgumentError(packageUri, name, - "Package URIs must start with the package name followed by a '/'"); + throw PackageConfigArgumentError( + packageUri, + name, + "Package URIs must start with the package name followed by a '/'", + ); } var packageName = packageUri.path.substring(0, firstSlash); var badIndex = checkPackageName(packageName); if (badIndex >= 0) { if (packageName.isEmpty) { throw PackageConfigArgumentError( - packageUri, name, 'Package names mus be non-empty'); + packageUri, + name, + 'Package names mus be non-empty', + ); } if (badIndex == packageName.length) { - throw PackageConfigArgumentError(packageUri, name, - "Package names must contain at least one non-'.' character"); + throw PackageConfigArgumentError( + packageUri, + name, + "Package names must contain at least one non-'.' character", + ); } assert(badIndex < packageName.length); var badCharCode = packageName.codeUnitAt(badIndex); @@ -95,7 +116,10 @@ String checkValidPackageUri(Uri packageUri, String name) { badChar = "'${packageName[badIndex]}' ($badChar)"; } throw PackageConfigArgumentError( - packageUri, name, 'Package names must not contain $badChar'); + packageUri, + name, + 'Package names must not contain $badChar', + ); } return packageName; } @@ -173,11 +197,12 @@ Uri? relativizeUri(Uri? uri, Uri? baseUri) { assert(baseUri.isAbsolute); if (uri!.hasQuery || uri.hasFragment) { uri = Uri( - scheme: uri.scheme, - userInfo: uri.hasAuthority ? uri.userInfo : null, - host: uri.hasAuthority ? uri.host : null, - port: uri.hasAuthority ? uri.port : null, - path: uri.path); + scheme: uri.scheme, + userInfo: uri.hasAuthority ? uri.userInfo : null, + host: uri.hasAuthority ? uri.host : null, + port: uri.hasAuthority ? uri.port : null, + path: uri.path, + ); } // Already relative. We assume the caller knows what they are doing. diff --git a/pkgs/package_config/test/bench.dart b/pkgs/package_config/test/bench.dart index 8428481f7..1d0ab1da3 100644 --- a/pkgs/package_config/test/bench.dart +++ b/pkgs/package_config/test/bench.dart @@ -48,8 +48,10 @@ void bench(final int size, final bool doPrint) { final lookup = stopwatch.elapsedMilliseconds; if (doPrint) { - print('Read file with $size packages in $read ms, ' - 'looked up all packages in $lookup ms'); + print( + 'Read file with $size packages in $read ms, ' + 'looked up all packages in $lookup ms', + ); } } diff --git a/pkgs/package_config/test/discovery_test.dart b/pkgs/package_config/test/discovery_test.dart index 3ecbfad14..529afcdeb 100644 --- a/pkgs/package_config/test/discovery_test.dart +++ b/pkgs/package_config/test/discovery_test.dart @@ -43,153 +43,184 @@ const packageConfigFile = ''' void validatePackagesFile(PackageConfig resolver, Directory directory) { expect(resolver, isNotNull); - expect(resolver.resolve(pkg('foo', 'bar/baz')), - equals(Uri.parse('file:///dart/packages/foo/bar/baz'))); - expect(resolver.resolve(pkg('bar', 'baz/qux')), - equals(Uri.parse('file:///dart/packages/bar/baz/qux'))); - expect(resolver.resolve(pkg('baz', 'qux/foo')), - equals(Uri.directory(directory.path).resolve('packages/baz/qux/foo'))); - expect([for (var p in resolver.packages) p.name], - unorderedEquals(['foo', 'bar', 'baz'])); + expect( + resolver.resolve(pkg('foo', 'bar/baz')), + equals(Uri.parse('file:///dart/packages/foo/bar/baz')), + ); + expect( + resolver.resolve(pkg('bar', 'baz/qux')), + equals(Uri.parse('file:///dart/packages/bar/baz/qux')), + ); + expect( + resolver.resolve(pkg('baz', 'qux/foo')), + equals(Uri.directory(directory.path).resolve('packages/baz/qux/foo')), + ); + expect([ + for (var p in resolver.packages) p.name, + ], unorderedEquals(['foo', 'bar', 'baz'])); } void main() { group('findPackages', () { // Finds package_config.json if there. - fileTest('package_config.json', { - '.packages': 'invalid .packages file', - 'script.dart': 'main(){}', - 'packages': {'shouldNotBeFound': {}}, - '.dart_tool': { - 'package_config.json': packageConfigFile, - } - }, (Directory directory) async { - var config = (await findPackageConfig(directory))!; - expect(config.version, 2); // Found package_config.json file. - validatePackagesFile(config, directory); - }); + fileTest( + 'package_config.json', + { + '.packages': 'invalid .packages file', + 'script.dart': 'main(){}', + 'packages': {'shouldNotBeFound': {}}, + '.dart_tool': {'package_config.json': packageConfigFile}, + }, + (Directory directory) async { + var config = (await findPackageConfig(directory))!; + expect(config.version, 2); // Found package_config.json file. + validatePackagesFile(config, directory); + }, + ); // Does not find .packages if no package_config.json. - fileTest('.packages', { - '.packages': packagesFile, - 'script.dart': 'main(){}', - 'packages': {'shouldNotBeFound': {}} - }, (Directory directory) async { - var config = await findPackageConfig(directory); - expect(config, null); - }); + fileTest( + '.packages', + { + '.packages': packagesFile, + 'script.dart': 'main(){}', + 'packages': {'shouldNotBeFound': {}}, + }, + (Directory directory) async { + var config = await findPackageConfig(directory); + expect(config, null); + }, + ); // Finds package_config.json in super-directory. - fileTest('package_config.json recursive', { - '.packages': packagesFile, - '.dart_tool': { - 'package_config.json': packageConfigFile, - }, - 'subdir': { + fileTest( + 'package_config.json recursive', + { '.packages': packagesFile, - 'script.dart': 'main(){}', - } - }, (Directory directory) async { - var config = (await findPackageConfig(subdir(directory, 'subdir/')))!; - expect(config.version, 2); - validatePackagesFile(config, directory); - }); + '.dart_tool': {'package_config.json': packageConfigFile}, + 'subdir': {'.packages': packagesFile, 'script.dart': 'main(){}'}, + }, + (Directory directory) async { + var config = (await findPackageConfig(subdir(directory, 'subdir/')))!; + expect(config.version, 2); + validatePackagesFile(config, directory); + }, + ); // Does not find a packages/ directory, and returns null if nothing found. - fileTest('package directory packages not supported', { - 'packages': { - 'foo': {}, - } - }, (Directory directory) async { - var config = await findPackageConfig(directory); - expect(config, null); - }); + fileTest( + 'package directory packages not supported', + { + 'packages': {'foo': {}}, + }, + (Directory directory) async { + var config = await findPackageConfig(directory); + expect(config, null); + }, + ); group('throws', () { - fileTest('invalid package config not JSON', { - '.dart_tool': { - 'package_config.json': 'not a JSON file', - } - }, (Directory directory) { - expect(findPackageConfig(directory), throwsA(isA())); - }); + fileTest( + 'invalid package config not JSON', + { + '.dart_tool': {'package_config.json': 'not a JSON file'}, + }, + (Directory directory) { + expect(findPackageConfig(directory), throwsA(isA())); + }, + ); - fileTest('invalid package config as INI', { - '.dart_tool': { - 'package_config.json': packagesFile, - } - }, (Directory directory) { - expect(findPackageConfig(directory), throwsA(isA())); - }); + fileTest( + 'invalid package config as INI', + { + '.dart_tool': {'package_config.json': packagesFile}, + }, + (Directory directory) { + expect(findPackageConfig(directory), throwsA(isA())); + }, + ); - fileTest('indirectly through .packages', { - '.packages': packagesFile, - '.dart_tool': { - 'package_config.json': packageConfigFile, + 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())); - }); + (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())); + }, + ); }); group('handles error', () { - fileTest('invalid package config not JSON', { - '.dart_tool': { - 'package_config.json': 'not a JSON file', - } - }, (Directory directory) async { - var hadError = false; - await findPackageConfig(directory, + fileTest( + 'invalid package config not JSON', + { + '.dart_tool': {'package_config.json': 'not a JSON file'}, + }, + (Directory directory) async { + var hadError = false; + await findPackageConfig( + directory, onError: expectAsync1((error) { hadError = true; expect(error, isA()); - }, max: -1)); - expect(hadError, true); - }); + }, max: -1), + ); + expect(hadError, true); + }, + ); - fileTest('invalid package config as INI', { - '.dart_tool': { - 'package_config.json': packagesFile, - } - }, (Directory directory) async { - var hadError = false; - await findPackageConfig(directory, + fileTest( + 'invalid package config as INI', + { + '.dart_tool': {'package_config.json': packagesFile}, + }, + (Directory directory) async { + var hadError = false; + await findPackageConfig( + directory, onError: expectAsync1((error) { hadError = true; expect(error, isA()); - }, max: -1)); - expect(hadError, true); - }); + }, max: -1), + ); + expect(hadError, true); + }, + ); }); // Does not find .packages if no package_config.json and minVersion > 1. - fileTest('.packages ignored', { - '.packages': packagesFile, - 'script.dart': 'main(){}' - }, (Directory directory) async { - var config = await findPackageConfig(directory, minVersion: 2); - expect(config, null); - }); + fileTest( + '.packages ignored', + {'.packages': packagesFile, 'script.dart': 'main(){}'}, + (Directory directory) async { + var config = await findPackageConfig(directory, minVersion: 2); + expect(config, null); + }, + ); // Finds package_config.json in super-directory. // (Even with `.packages` in search directory.) - fileTest('package_config.json recursive .packages ignored', { - '.dart_tool': { - 'package_config.json': packageConfigFile, + fileTest( + 'package_config.json recursive .packages ignored', + { + '.dart_tool': {'package_config.json': packageConfigFile}, + 'subdir': {'.packages': packagesFile, 'script.dart': 'main(){}'}, }, - 'subdir': { - '.packages': packagesFile, - 'script.dart': 'main(){}', - } - }, (Directory directory) async { - var config = (await findPackageConfig(subdir(directory, 'subdir/'), - minVersion: 2))!; - expect(config.version, 2); - validatePackagesFile(config, directory); - }); + (Directory directory) async { + var config = + (await findPackageConfig( + subdir(directory, 'subdir/'), + minVersion: 2, + ))!; + expect(config.version, 2); + validatePackagesFile(config, directory); + }, + ); }); group('loadPackageConfig', () { @@ -197,45 +228,49 @@ void main() { group('package_config.json', () { var files = { '.packages': packagesFile, - '.dart_tool': { - 'package_config.json': packageConfigFile, - }, + '.dart_tool': {'package_config.json': packageConfigFile}, }; fileTest('directly', files, (Directory directory) async { - var file = - dirFile(subdir(directory, '.dart_tool'), 'package_config.json'); + var file = dirFile( + subdir(directory, '.dart_tool'), + 'package_config.json', + ); var config = await loadPackageConfig(file); expect(config.version, 2); validatePackagesFile(config, directory); }); }); - fileTest('package_config.json non-default name', { - '.packages': packagesFile, - 'subdir': { - 'pheldagriff': packageConfigFile, + fileTest( + 'package_config.json non-default name', + { + '.packages': packagesFile, + 'subdir': {'pheldagriff': packageConfigFile}, }, - }, (Directory directory) async { - var file = dirFile(directory, 'subdir/pheldagriff'); - var config = await loadPackageConfig(file); - expect(config.version, 2); - validatePackagesFile(config, directory); - }); + (Directory directory) async { + var file = dirFile(directory, 'subdir/pheldagriff'); + var config = await loadPackageConfig(file); + expect(config.version, 2); + validatePackagesFile(config, directory); + }, + ); - fileTest('package_config.json named .packages', { - 'subdir': { - '.packages': packageConfigFile, + fileTest( + 'package_config.json named .packages', + { + 'subdir': {'.packages': packageConfigFile}, }, - }, (Directory directory) async { - var file = dirFile(directory, 'subdir/.packages'); - var config = await loadPackageConfig(file); - expect(config.version, 2); - validatePackagesFile(config, directory); - }); + (Directory directory) async { + var file = dirFile(directory, 'subdir/.packages'); + var config = await loadPackageConfig(file); + expect(config.version, 2); + validatePackagesFile(config, directory); + }, + ); - fileTest('.packages cannot be loaded', { - '.packages': packagesFile, - }, (Directory directory) async { + fileTest('.packages cannot be loaded', {'.packages': packagesFile}, ( + Directory directory, + ) async { var file = dirFile(directory, '.packages'); expect(loadPackageConfig(file), throwsFormatException); }); @@ -243,23 +278,27 @@ void main() { fileTest('no config file found', {}, (Directory directory) { var file = dirFile(directory, 'any_name'); expect( - () => loadPackageConfig(file), throwsA(isA())); + () => loadPackageConfig(file), + throwsA(isA()), + ); }); fileTest('no config found, handled', {}, (Directory directory) async { var file = dirFile(directory, 'any_name'); var hadError = false; - await loadPackageConfig(file, - onError: expectAsync1((error) { - hadError = true; - expect(error, isA()); - }, max: -1)); + await loadPackageConfig( + file, + onError: expectAsync1((error) { + hadError = true; + expect(error, isA()); + }, max: -1), + ); expect(hadError, true); }); - fileTest('specified file syntax error', { - 'any_name': 'syntax error', - }, (Directory directory) { + fileTest('specified file syntax error', {'any_name': 'syntax error'}, ( + Directory directory, + ) { 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 b16eca83c..2d056a762 100644 --- a/pkgs/package_config/test/discovery_uri_test.dart +++ b/pkgs/package_config/test/discovery_uri_test.dart @@ -40,68 +40,89 @@ const packageConfigFile = ''' void validatePackagesFile(PackageConfig resolver, Uri directory) { expect(resolver, isNotNull); - expect(resolver.resolve(pkg('foo', 'bar/baz')), - equals(Uri.parse('file:///dart/packages/foo/bar/baz'))); - expect(resolver.resolve(pkg('bar', 'baz/qux')), - equals(directory.resolve('/dart/packages/bar/baz/qux'))); - expect(resolver.resolve(pkg('baz', 'qux/foo')), - equals(directory.resolve('packages/baz/qux/foo'))); - expect([for (var p in resolver.packages) p.name], - unorderedEquals(['foo', 'bar', 'baz'])); + expect( + resolver.resolve(pkg('foo', 'bar/baz')), + equals(Uri.parse('file:///dart/packages/foo/bar/baz')), + ); + expect( + resolver.resolve(pkg('bar', 'baz/qux')), + equals(directory.resolve('/dart/packages/bar/baz/qux')), + ); + expect( + resolver.resolve(pkg('baz', 'qux/foo')), + equals(directory.resolve('packages/baz/qux/foo')), + ); + expect([ + for (var p in resolver.packages) p.name, + ], unorderedEquals(['foo', 'bar', 'baz'])); } void main() { group('findPackages', () { // Finds package_config.json if there. - loaderTest('package_config.json', { - '.packages': 'invalid .packages file', - 'script.dart': 'main(){}', - 'packages': {'shouldNotBeFound': {}}, - '.dart_tool': { - 'package_config.json': packageConfigFile, - } - }, (directory, loader) async { - var config = (await findPackageConfigUri(directory, loader: loader))!; - expect(config.version, 2); // Found package_config.json file. - validatePackagesFile(config, directory); - }); + loaderTest( + 'package_config.json', + { + '.packages': 'invalid .packages file', + 'script.dart': 'main(){}', + 'packages': {'shouldNotBeFound': {}}, + '.dart_tool': {'package_config.json': packageConfigFile}, + }, + (directory, loader) async { + var config = (await findPackageConfigUri(directory, loader: loader))!; + expect(config.version, 2); // Found package_config.json file. + validatePackagesFile(config, directory); + }, + ); // Finds package_config.json in super-directory. - loaderTest('package_config.json recursive', { - '.packages': packagesFile, - '.dart_tool': { - 'package_config.json': packageConfigFile, + loaderTest( + 'package_config.json recursive', + { + '.packages': packagesFile, + '.dart_tool': {'package_config.json': packageConfigFile}, + 'subdir': {'script.dart': 'main(){}'}, }, - 'subdir': { - 'script.dart': 'main(){}', - } - }, (directory, loader) async { - var config = (await findPackageConfigUri(directory.resolve('subdir/'), - loader: loader))!; - expect(config.version, 2); - validatePackagesFile(config, directory); - }); + (directory, loader) async { + var config = + (await findPackageConfigUri( + directory.resolve('subdir/'), + loader: loader, + ))!; + expect(config.version, 2); + validatePackagesFile(config, directory); + }, + ); // Does not find a .packages file. - loaderTest('Not .packages', { - '.packages': packagesFile, - 'script.dart': 'main(){}', - 'packages': {'shouldNotBeFound': {}} - }, (directory, loader) async { - var config = - await findPackageConfigUri(recurse: false, directory, loader: loader); - expect(config, null); - }); + loaderTest( + 'Not .packages', + { + '.packages': packagesFile, + 'script.dart': 'main(){}', + 'packages': {'shouldNotBeFound': {}}, + }, + (directory, loader) async { + var config = await findPackageConfigUri( + recurse: false, + directory, + loader: loader, + ); + expect(config, null); + }, + ); // Does not find a packages/ directory, and returns null if nothing found. - loaderTest('package directory packages not supported', { - 'packages': { - 'foo': {}, - } - }, (Uri directory, loader) async { - var config = await findPackageConfigUri(directory, loader: loader); - expect(config, null); - }); + loaderTest( + 'package directory packages not supported', + { + 'packages': {'foo': {}}, + }, + (Uri directory, loader) async { + var config = await findPackageConfigUri(directory, loader: loader); + expect(config, null); + }, + ); }); group('loadPackageConfig', () { @@ -109,9 +130,7 @@ void main() { group('package_config.json', () { var files = { '.packages': packagesFile, - '.dart_tool': { - 'package_config.json': packageConfigFile, - }, + '.dart_tool': {'package_config.json': packageConfigFile}, }; loaderTest('directly', files, (Uri directory, loader) async { var file = directory.resolve('.dart_tool/package_config.json'); @@ -119,106 +138,135 @@ void main() { expect(config.version, 2); validatePackagesFile(config, directory); }); - loaderTest('indirectly through .packages', files, - (Uri directory, loader) async { + loaderTest('indirectly through .packages', files, ( + Uri directory, + loader, + ) async { // Is no longer supported. var file = directory.resolve('.packages'); var hadError = false; - await loadPackageConfigUri(file, loader: loader, onError: (_) { - hadError = true; - }); + await loadPackageConfigUri( + file, + loader: loader, + onError: (_) { + hadError = true; + }, + ); expect(hadError, true); }); }); - loaderTest('package_config.json non-default name', { - '.packages': packagesFile, - 'subdir': { - 'pheldagriff': packageConfigFile, + loaderTest( + 'package_config.json non-default name', + { + '.packages': packagesFile, + 'subdir': {'pheldagriff': packageConfigFile}, }, - }, (Uri directory, loader) async { - var file = directory.resolve('subdir/pheldagriff'); - var config = await loadPackageConfigUri(file, loader: loader); - expect(config.version, 2); - validatePackagesFile(config, directory); - }); + (Uri directory, loader) async { + var file = directory.resolve('subdir/pheldagriff'); + var config = await loadPackageConfigUri(file, loader: loader); + expect(config.version, 2); + validatePackagesFile(config, directory); + }, + ); - loaderTest('package_config.json named .packages', { - 'subdir': { - '.packages': packageConfigFile, + loaderTest( + 'package_config.json named .packages', + { + 'subdir': {'.packages': packageConfigFile}, }, - }, (Uri directory, loader) async { - var file = directory.resolve('subdir/.packages'); - var config = await loadPackageConfigUri(file, loader: loader); - expect(config.version, 2); - validatePackagesFile(config, directory); - }); + (Uri directory, loader) async { + var file = directory.resolve('subdir/.packages'); + var config = await loadPackageConfigUri(file, loader: loader); + expect(config.version, 2); + validatePackagesFile(config, directory); + }, + ); loaderTest('no config found', {}, (Uri directory, loader) { var file = directory.resolve('anyname'); - expect(() => loadPackageConfigUri(file, loader: loader), - throwsA(isA())); + expect( + () => loadPackageConfigUri(file, loader: loader), + throwsA(isA()), + ); }); - loaderTest('no config found, handle error', {}, - (Uri directory, loader) async { + loaderTest('no config found, handle error', {}, ( + Uri directory, + loader, + ) async { var file = directory.resolve('anyname'); var hadError = false; - await loadPackageConfigUri(file, - loader: loader, - onError: expectAsync1((error) { - hadError = true; - expect(error, isA()); - }, max: -1)); + await loadPackageConfigUri( + file, + loader: loader, + onError: expectAsync1((error) { + hadError = true; + expect(error, isA()); + }, max: -1), + ); expect(hadError, true); }); - loaderTest('specified file syntax error', { - 'anyname': 'syntax error', - }, (Uri directory, loader) { + loaderTest('specified file syntax error', {'anyname': 'syntax error'}, ( + Uri directory, + loader, + ) { var file = directory.resolve('anyname'); - expect(() => loadPackageConfigUri(file, loader: loader), - throwsFormatException); + expect( + () => loadPackageConfigUri(file, loader: loader), + throwsFormatException, + ); }); - loaderTest('specified file syntax onError', { - 'anyname': 'syntax error', - }, (directory, loader) async { + loaderTest('specified file syntax onError', {'anyname': 'syntax error'}, ( + directory, + loader, + ) async { var file = directory.resolve('anyname'); var hadError = false; - await loadPackageConfigUri(file, - loader: loader, - onError: expectAsync1((error) { - hadError = true; - expect(error, isA()); - }, max: -1)); + await loadPackageConfigUri( + file, + loader: loader, + onError: expectAsync1((error) { + hadError = true; + expect(error, isA()); + }, max: -1), + ); expect(hadError, true); }); // Don't look for package_config.json if original name or file are bad. - loaderTest('specified file syntax error with alternative', { - 'anyname': 'syntax error', - '.dart_tool': { - 'package_config.json': packageConfigFile, + loaderTest( + 'specified file syntax error with alternative', + { + 'anyname': 'syntax error', + '.dart_tool': {'package_config.json': packageConfigFile}, }, - }, (directory, loader) async { - var file = directory.resolve('anyname'); - expect(() => loadPackageConfigUri(file, loader: loader), - throwsFormatException); - }); + (directory, loader) async { + var file = directory.resolve('anyname'); + expect( + () => loadPackageConfigUri(file, loader: loader), + throwsFormatException, + ); + }, + ); // A file starting with `{` is a package_config.json file. - loaderTest('file syntax error with {', { - '.packages': '{syntax error', - }, (directory, loader) async { + loaderTest('file syntax error with {', {'.packages': '{syntax error'}, ( + directory, + loader, + ) async { var file = directory.resolve('.packages'); var hadError = false; - await loadPackageConfigUri(file, - loader: loader, - onError: expectAsync1((error) { - hadError = true; - expect(error, isA()); - }, max: -1)); + await loadPackageConfigUri( + file, + loader: loader, + onError: expectAsync1((error) { + hadError = true; + expect(error, isA()); + }, max: -1), + ); expect(hadError, true); }); }); diff --git a/pkgs/package_config/test/package_config_impl_test.dart b/pkgs/package_config/test/package_config_impl_test.dart index ce02ef95a..e68c31df3 100644 --- a/pkgs/package_config/test/package_config_impl_test.dart +++ b/pkgs/package_config/test/package_config_impl_test.dart @@ -21,22 +21,20 @@ void main() { test('negative major', () { expect( - () => LanguageVersion(-1, 1), - throwsA(isA().having( - (e) => e.name, - 'message', - contains('major'), - ))); + () => LanguageVersion(-1, 1), + throwsA( + isA().having((e) => e.name, 'message', contains('major')), + ), + ); }); test('negative minor', () { expect( - () => LanguageVersion(1, -1), - throwsA(isA().having( - (e) => e.name, - 'message', - contains('minor'), - ))); + () => LanguageVersion(1, -1), + throwsA( + isA().having((e) => e.name, 'message', contains('minor')), + ), + ); }); test('minimal parse', () { @@ -47,13 +45,18 @@ void main() { void failParse(String name, String input) { test('$name - error', () { - expect(() => LanguageVersion.parse(input), - throwsA(isA())); + expect( + () => LanguageVersion.parse(input), + throwsA(isA()), + ); expect(() => LanguageVersion.parse(input), throwsFormatException); var failed = false; - var actual = LanguageVersion.parse(input, onError: (_) { - failed = true; - }); + var actual = LanguageVersion.parse( + input, + onError: (_) { + failed = true; + }, + ); expect(failed, true); expect(actual, isA()); }); @@ -101,7 +104,9 @@ void main() { /// Test that the relational comparisons between two valid versions /// match the results of `compareTo`. void testComparisons( - LanguageVersion version, LanguageVersion otherVersion) { + LanguageVersion version, + LanguageVersion otherVersion, + ) { expect(version == otherVersion, version.compareTo(otherVersion) == 0); expect(version < otherVersion, version.compareTo(otherVersion) < 0); @@ -164,11 +169,14 @@ void main() { test('absolute package root', () { var version = LanguageVersion(1, 1); var absolute = root.resolve('foo/bar/'); - var package = Package('name', root, - packageUriRoot: absolute, - relativeRoot: false, - languageVersion: version, - extraData: unique); + var package = Package( + 'name', + root, + packageUriRoot: absolute, + relativeRoot: false, + languageVersion: version, + extraData: unique, + ); expect(package.name, 'name'); expect(package.root, root); expect(package.packageUriRoot, absolute); @@ -180,8 +188,13 @@ void main() { test('relative package root', () { var relative = Uri.parse('foo/bar/'); var absolute = root.resolveUri(relative); - var package = Package('name', root, - packageUriRoot: relative, relativeRoot: true, extraData: unique); + var package = Package( + 'name', + root, + packageUriRoot: relative, + relativeRoot: true, + extraData: unique, + ); expect(package.name, 'name'); expect(package.root, root); expect(package.packageUriRoot, absolute); @@ -198,22 +211,30 @@ void main() { test('Invalid root, not absolute', () { expect( - () => Package('name', Uri.parse('/foo/')), throwsPackageConfigError); + () => Package('name', Uri.parse('/foo/')), + throwsPackageConfigError, + ); }); test('Invalid root, not ending in slash', () { - expect(() => Package('name', Uri.parse('file:///foo')), - throwsPackageConfigError); + expect( + () => Package('name', Uri.parse('file:///foo')), + throwsPackageConfigError, + ); }); test('invalid package root, not ending in slash', () { - expect(() => Package('name', root, packageUriRoot: Uri.parse('foo')), - throwsPackageConfigError); + expect( + () => Package('name', root, packageUriRoot: Uri.parse('foo')), + throwsPackageConfigError, + ); }); test('invalid package root, not inside root', () { - expect(() => Package('name', root, packageUriRoot: Uri.parse('../baz/')), - throwsPackageConfigError); + expect( + () => Package('name', root, packageUriRoot: Uri.parse('../baz/')), + throwsPackageConfigError, + ); }); }); @@ -238,19 +259,26 @@ void main() { }); }); test('writeString', () { - var config = PackageConfig([ - Package('foo', Uri.parse('file:///pkg/foo/'), + var config = PackageConfig( + [ + Package( + 'foo', + Uri.parse('file:///pkg/foo/'), packageUriRoot: Uri.parse('file:///pkg/foo/lib/'), relativeRoot: false, languageVersion: LanguageVersion(2, 4), - extraData: {'foo': 'foo!'}), - Package('bar', Uri.parse('file:///pkg/bar/'), + extraData: {'foo': 'foo!'}, + ), + Package( + 'bar', + Uri.parse('file:///pkg/bar/'), packageUriRoot: Uri.parse('file:///pkg/bar/lib/'), relativeRoot: true, - extraData: {'bar': 'bar!'}), - ], extraData: { - 'extra': 'data' - }); + extraData: {'bar': 'bar!'}, + ), + ], + extraData: {'extra': 'data'}, + ); var buffer = StringBuffer(); PackageConfig.writeString(config, buffer, Uri.parse('file:///pkg/')); var text = buffer.toString(); @@ -265,12 +293,7 @@ void main() { 'languageVersion': '2.4', 'foo': 'foo!', }, - { - 'name': 'bar', - 'rootUri': 'bar/', - 'packageUri': 'lib/', - 'bar': 'bar!', - }, + {'name': 'bar', 'rootUri': 'bar/', 'packageUri': 'lib/', 'bar': 'bar!'}, ]), 'extra': 'data', }); diff --git a/pkgs/package_config/test/parse_test.dart b/pkgs/package_config/test/parse_test.dart index 1a057d11a..0550a73c6 100644 --- a/pkgs/package_config/test/parse_test.dart +++ b/pkgs/package_config/test/parse_test.dart @@ -48,20 +48,29 @@ void main() { } '''; var config = parsePackageConfigBytes( - // ignore: unnecessary_cast - utf8.encode(packageConfigFile) as Uint8List, - Uri.parse('file:///tmp/.dart_tool/file.dart'), - throwError); + // ignore: unnecessary_cast + utf8.encode(packageConfigFile) as Uint8List, + Uri.parse('file:///tmp/.dart_tool/file.dart'), + throwError, + ); expect(config.version, 2); - expect({for (var p in config.packages) p.name}, - {'foo', 'bar', 'baz', 'noslash'}); + expect( + {for (var p in config.packages) p.name}, + {'foo', 'bar', 'baz', 'noslash'}, + ); - expect(config.resolve(pkg('foo', 'foo.dart')), - Uri.parse('file:///foo/lib/foo.dart')); - expect(config.resolve(pkg('bar', 'bar.dart')), - Uri.parse('file:///bar/lib/bar.dart')); - expect(config.resolve(pkg('baz', 'baz.dart')), - Uri.parse('file:///tmp/lib/baz.dart')); + expect( + config.resolve(pkg('foo', 'foo.dart')), + Uri.parse('file:///foo/lib/foo.dart'), + ); + expect( + config.resolve(pkg('bar', 'bar.dart')), + Uri.parse('file:///bar/lib/bar.dart'), + ); + expect( + config.resolve(pkg('baz', 'baz.dart')), + Uri.parse('file:///tmp/lib/baz.dart'), + ); var foo = config['foo']!; expect(foo, isNotNull); @@ -96,7 +105,7 @@ void main() { expect(config.extraData, { 'generator': 'pub', - 'other': [42] + 'other': [42], }); }); @@ -129,22 +138,29 @@ void main() { } '''; var config = parsePackageConfigBytes( - // ignore: unnecessary_cast - utf8.encode(packageConfigFile) as Uint8List, - Uri.parse('file:///tmp/.dart_tool/file.dart'), - throwError); + // ignore: unnecessary_cast + utf8.encode(packageConfigFile) as Uint8List, + Uri.parse('file:///tmp/.dart_tool/file.dart'), + throwError, + ); expect(config.version, 2); expect({for (var p in config.packages) p.name}, {'foo', 'bar', 'baz'}); - expect(config.resolve(pkg('foo', 'foo.dart')), - Uri.parse('file:///foo/lib/foo.dart')); - expect(config.resolve(pkg('bar', 'bar.dart')), - Uri.parse('file:///bar/lib/bar.dart')); - expect(config.resolve(pkg('baz', 'baz.dart')), - Uri.parse('file:///tmp/lib/baz.dart')); + expect( + config.resolve(pkg('foo', 'foo.dart')), + Uri.parse('file:///foo/lib/foo.dart'), + ); + expect( + config.resolve(pkg('bar', 'bar.dart')), + Uri.parse('file:///bar/lib/bar.dart'), + ); + expect( + config.resolve(pkg('baz', 'baz.dart')), + Uri.parse('file:///tmp/lib/baz.dart'), + ); expect(config.extraData, { 'generator': 'pub', - 'other': [42] + 'other': [42], }); }); @@ -156,10 +172,11 @@ void main() { var root = '"rootUri":"/foo/"'; test('minimal', () { var config = parsePackageConfigBytes( - // ignore: unnecessary_cast - utf8.encode('{$cfg,$pkgs}') as Uint8List, - Uri.parse('file:///tmp/.dart_tool/file.dart'), - throwError); + // ignore: unnecessary_cast + utf8.encode('{$cfg,$pkgs}') as Uint8List, + Uri.parse('file:///tmp/.dart_tool/file.dart'), + throwError, + ); expect(config.version, 2); expect(config.packages, isEmpty); }); @@ -167,108 +184,161 @@ void main() { // A package must have a name and a rootUri, the remaining properties // are optional. var config = parsePackageConfigBytes( - // ignore: unnecessary_cast - utf8.encode('{$cfg,"packages":[{$name,$root}]}') as Uint8List, - Uri.parse('file:///tmp/.dart_tool/file.dart'), - throwError); + // ignore: unnecessary_cast + utf8.encode('{$cfg,"packages":[{$name,$root}]}') as Uint8List, + Uri.parse('file:///tmp/.dart_tool/file.dart'), + throwError, + ); expect(config.version, 2); expect(config.packages.first.name, 'foo'); }); test('nested packages', () { - var configBytes = utf8.encode(json.encode({ - 'configVersion': 2, - 'packages': [ - {'name': 'foo', 'rootUri': '/foo/', 'packageUri': 'lib/'}, - {'name': 'bar', 'rootUri': '/foo/bar/', 'packageUri': 'lib/'}, - {'name': 'baz', 'rootUri': '/foo/bar/baz/', 'packageUri': 'lib/'}, - {'name': 'qux', 'rootUri': '/foo/qux/', 'packageUri': 'lib/'}, - ] - })); + var configBytes = utf8.encode( + json.encode({ + 'configVersion': 2, + 'packages': [ + {'name': 'foo', 'rootUri': '/foo/', 'packageUri': 'lib/'}, + {'name': 'bar', 'rootUri': '/foo/bar/', 'packageUri': 'lib/'}, + {'name': 'baz', 'rootUri': '/foo/bar/baz/', 'packageUri': 'lib/'}, + {'name': 'qux', 'rootUri': '/foo/qux/', 'packageUri': 'lib/'}, + ], + }), + ); // ignore: unnecessary_cast - var config = parsePackageConfigBytes(configBytes as Uint8List, - Uri.parse('file:///tmp/.dart_tool/file.dart'), throwError); + var config = parsePackageConfigBytes( + configBytes as Uint8List, + Uri.parse('file:///tmp/.dart_tool/file.dart'), + throwError, + ); expect(config.version, 2); - expect(config.packageOf(Uri.parse('file:///foo/lala/lala.dart'))!.name, - 'foo'); - expect(config.packageOf(Uri.parse('file:///foo/bar/lala.dart'))!.name, - 'bar'); - expect(config.packageOf(Uri.parse('file:///foo/bar/baz/lala.dart'))!.name, - 'baz'); - expect(config.packageOf(Uri.parse('file:///foo/qux/lala.dart'))!.name, - 'qux'); - expect(config.toPackageUri(Uri.parse('file:///foo/lib/diz')), - Uri.parse('package:foo/diz')); - expect(config.toPackageUri(Uri.parse('file:///foo/bar/lib/diz')), - Uri.parse('package:bar/diz')); - expect(config.toPackageUri(Uri.parse('file:///foo/bar/baz/lib/diz')), - Uri.parse('package:baz/diz')); - expect(config.toPackageUri(Uri.parse('file:///foo/qux/lib/diz')), - Uri.parse('package:qux/diz')); + expect( + config.packageOf(Uri.parse('file:///foo/lala/lala.dart'))!.name, + 'foo', + ); + expect( + config.packageOf(Uri.parse('file:///foo/bar/lala.dart'))!.name, + 'bar', + ); + expect( + config.packageOf(Uri.parse('file:///foo/bar/baz/lala.dart'))!.name, + 'baz', + ); + expect( + config.packageOf(Uri.parse('file:///foo/qux/lala.dart'))!.name, + 'qux', + ); + expect( + config.toPackageUri(Uri.parse('file:///foo/lib/diz')), + Uri.parse('package:foo/diz'), + ); + expect( + config.toPackageUri(Uri.parse('file:///foo/bar/lib/diz')), + Uri.parse('package:bar/diz'), + ); + expect( + config.toPackageUri(Uri.parse('file:///foo/bar/baz/lib/diz')), + Uri.parse('package:baz/diz'), + ); + expect( + config.toPackageUri(Uri.parse('file:///foo/qux/lib/diz')), + Uri.parse('package:qux/diz'), + ); }); test('nested packages 2', () { - var configBytes = utf8.encode(json.encode({ - 'configVersion': 2, - 'packages': [ - {'name': 'foo', 'rootUri': '/', 'packageUri': 'lib/'}, - {'name': 'bar', 'rootUri': '/bar/', 'packageUri': 'lib/'}, - {'name': 'baz', 'rootUri': '/bar/baz/', 'packageUri': 'lib/'}, - {'name': 'qux', 'rootUri': '/qux/', 'packageUri': 'lib/'}, - ] - })); + var configBytes = utf8.encode( + json.encode({ + 'configVersion': 2, + 'packages': [ + {'name': 'foo', 'rootUri': '/', 'packageUri': 'lib/'}, + {'name': 'bar', 'rootUri': '/bar/', 'packageUri': 'lib/'}, + {'name': 'baz', 'rootUri': '/bar/baz/', 'packageUri': 'lib/'}, + {'name': 'qux', 'rootUri': '/qux/', 'packageUri': 'lib/'}, + ], + }), + ); // ignore: unnecessary_cast - var config = parsePackageConfigBytes(configBytes as Uint8List, - Uri.parse('file:///tmp/.dart_tool/file.dart'), throwError); + var config = parsePackageConfigBytes( + configBytes as Uint8List, + Uri.parse('file:///tmp/.dart_tool/file.dart'), + throwError, + ); expect(config.version, 2); expect( - config.packageOf(Uri.parse('file:///lala/lala.dart'))!.name, 'foo'); + config.packageOf(Uri.parse('file:///lala/lala.dart'))!.name, + 'foo', + ); expect(config.packageOf(Uri.parse('file:///bar/lala.dart'))!.name, 'bar'); - expect(config.packageOf(Uri.parse('file:///bar/baz/lala.dart'))!.name, - 'baz'); + expect( + config.packageOf(Uri.parse('file:///bar/baz/lala.dart'))!.name, + 'baz', + ); expect(config.packageOf(Uri.parse('file:///qux/lala.dart'))!.name, 'qux'); - expect(config.toPackageUri(Uri.parse('file:///lib/diz')), - Uri.parse('package:foo/diz')); - expect(config.toPackageUri(Uri.parse('file:///bar/lib/diz')), - Uri.parse('package:bar/diz')); - expect(config.toPackageUri(Uri.parse('file:///bar/baz/lib/diz')), - Uri.parse('package:baz/diz')); - expect(config.toPackageUri(Uri.parse('file:///qux/lib/diz')), - Uri.parse('package:qux/diz')); + expect( + config.toPackageUri(Uri.parse('file:///lib/diz')), + Uri.parse('package:foo/diz'), + ); + expect( + config.toPackageUri(Uri.parse('file:///bar/lib/diz')), + Uri.parse('package:bar/diz'), + ); + expect( + config.toPackageUri(Uri.parse('file:///bar/baz/lib/diz')), + Uri.parse('package:baz/diz'), + ); + expect( + config.toPackageUri(Uri.parse('file:///qux/lib/diz')), + Uri.parse('package:qux/diz'), + ); }); test('packageOf is case sensitive on windows', () { - var configBytes = utf8.encode(json.encode({ - 'configVersion': 2, - 'packages': [ - {'name': 'foo', 'rootUri': 'file:///C:/Foo/', 'packageUri': 'lib/'}, - ] - })); + var configBytes = utf8.encode( + json.encode({ + 'configVersion': 2, + 'packages': [ + {'name': 'foo', 'rootUri': 'file:///C:/Foo/', 'packageUri': 'lib/'}, + ], + }), + ); var config = parsePackageConfigBytes( - // ignore: unnecessary_cast - configBytes as Uint8List, - Uri.parse('file:///C:/tmp/.dart_tool/file.dart'), - throwError); + // ignore: unnecessary_cast + configBytes as Uint8List, + Uri.parse('file:///C:/tmp/.dart_tool/file.dart'), + throwError, + ); expect(config.version, 2); expect( - config.packageOf(Uri.parse('file:///C:/foo/lala/lala.dart')), null); - expect(config.packageOf(Uri.parse('file:///C:/Foo/lala/lala.dart'))!.name, - 'foo'); + config.packageOf(Uri.parse('file:///C:/foo/lala/lala.dart')), + null, + ); + expect( + config.packageOf(Uri.parse('file:///C:/Foo/lala/lala.dart'))!.name, + 'foo', + ); }); group('invalid', () { void testThrows(String name, String source) { test(name, () { expect( - // ignore: unnecessary_cast - () => parsePackageConfigBytes(utf8.encode(source) as Uint8List, - Uri.parse('file:///tmp/.dart_tool/file.dart'), throwError), - throwsA(isA())); + // ignore: unnecessary_cast + () => parsePackageConfigBytes( + utf8.encode(source) as Uint8List, + Uri.parse('file:///tmp/.dart_tool/file.dart'), + throwError, + ), + throwsA(isA()), + ); }); } void testThrowsContains( - String name, String source, String containsString) { + String name, + String source, + String containsString, + ) { test(name, () { Object? exception; try { @@ -315,11 +385,17 @@ void main() { testThrows('one-dot', '{$cfg,"packages":[{"name":".",$root}]}'); testThrows('two-dot', '{$cfg,"packages":[{"name":"..",$root}]}'); testThrows( - "invalid char '\\'", '{$cfg,"packages":[{"name":"\\",$root}]}'); + "invalid char '\\'", + '{$cfg,"packages":[{"name":"\\",$root}]}', + ); testThrows( - "invalid char ':'", '{$cfg,"packages":[{"name":":",$root}]}'); + "invalid char ':'", + '{$cfg,"packages":[{"name":":",$root}]}', + ); testThrows( - "invalid char ' '", '{$cfg,"packages":[{"name":" ",$root}]}'); + "invalid char ' '", + '{$cfg,"packages":[{"name":" ",$root}]}', + ); }); testThrows('no root', '{$cfg,"packages":[{$name}]}'); @@ -329,92 +405,142 @@ void main() { testThrows('object', '{$cfg,"packages":[{$name,"rootUri":{}}]}'); testThrows('fragment', '{$cfg,"packages":[{$name,"rootUri":"x/#"}]}'); testThrows('query', '{$cfg,"packages":[{$name,"rootUri":"x/?"}]}'); - testThrows('package-URI', - '{$cfg,"packages":[{$name,"rootUri":"package:x/x/"}]}'); + testThrows( + 'package-URI', + '{$cfg,"packages":[{$name,"rootUri":"package:x/x/"}]}', + ); }); group('package-URI root:', () { testThrows( - 'null', '{$cfg,"packages":[{$name,$root,"packageUri":null}]}'); + 'null', + '{$cfg,"packages":[{$name,$root,"packageUri":null}]}', + ); testThrows('num', '{$cfg,"packages":[{$name,$root,"packageUri":1}]}'); testThrows( - 'object', '{$cfg,"packages":[{$name,$root,"packageUri":{}}]}'); - testThrows('fragment', - '{$cfg,"packages":[{$name,$root,"packageUri":"x/#"}]}'); + 'object', + '{$cfg,"packages":[{$name,$root,"packageUri":{}}]}', + ); + testThrows( + 'fragment', + '{$cfg,"packages":[{$name,$root,"packageUri":"x/#"}]}', + ); + testThrows( + 'query', + '{$cfg,"packages":[{$name,$root,"packageUri":"x/?"}]}', + ); + testThrows( + 'package: URI', + '{$cfg,"packages":[{$name,$root,"packageUri":"package:x/x/"}]}', + ); testThrows( - 'query', '{$cfg,"packages":[{$name,$root,"packageUri":"x/?"}]}'); - testThrows('package: URI', - '{$cfg,"packages":[{$name,$root,"packageUri":"package:x/x/"}]}'); - testThrows('not inside root', - '{$cfg,"packages":[{$name,$root,"packageUri":"../other/"}]}'); + 'not inside root', + '{$cfg,"packages":[{$name,$root,"packageUri":"../other/"}]}', + ); }); group('language version', () { - testThrows('null', - '{$cfg,"packages":[{$name,$root,"languageVersion":null}]}'); testThrows( - 'num', '{$cfg,"packages":[{$name,$root,"languageVersion":1}]}'); - testThrows('object', - '{$cfg,"packages":[{$name,$root,"languageVersion":{}}]}'); - testThrows('empty', - '{$cfg,"packages":[{$name,$root,"languageVersion":""}]}'); - testThrows('non number.number', - '{$cfg,"packages":[{$name,$root,"languageVersion":"x.1"}]}'); - testThrows('number.non number', - '{$cfg,"packages":[{$name,$root,"languageVersion":"1.x"}]}'); - testThrows('non number', - '{$cfg,"packages":[{$name,$root,"languageVersion":"x"}]}'); - testThrows('one number', - '{$cfg,"packages":[{$name,$root,"languageVersion":"1"}]}'); - testThrows('three numbers', - '{$cfg,"packages":[{$name,$root,"languageVersion":"1.2.3"}]}'); - testThrows('leading zero first', - '{$cfg,"packages":[{$name,$root,"languageVersion":"01.1"}]}'); - testThrows('leading zero second', - '{$cfg,"packages":[{$name,$root,"languageVersion":"1.01"}]}'); - testThrows('trailing-', - '{$cfg,"packages":[{$name,$root,"languageVersion":"1.1-1"}]}'); - testThrows('trailing+', - '{$cfg,"packages":[{$name,$root,"languageVersion":"1.1+1"}]}'); + 'null', + '{$cfg,"packages":[{$name,$root,"languageVersion":null}]}', + ); + testThrows( + 'num', + '{$cfg,"packages":[{$name,$root,"languageVersion":1}]}', + ); + testThrows( + 'object', + '{$cfg,"packages":[{$name,$root,"languageVersion":{}}]}', + ); + testThrows( + 'empty', + '{$cfg,"packages":[{$name,$root,"languageVersion":""}]}', + ); + testThrows( + 'non number.number', + '{$cfg,"packages":[{$name,$root,"languageVersion":"x.1"}]}', + ); + testThrows( + 'number.non number', + '{$cfg,"packages":[{$name,$root,"languageVersion":"1.x"}]}', + ); + testThrows( + 'non number', + '{$cfg,"packages":[{$name,$root,"languageVersion":"x"}]}', + ); + testThrows( + 'one number', + '{$cfg,"packages":[{$name,$root,"languageVersion":"1"}]}', + ); + testThrows( + 'three numbers', + '{$cfg,"packages":[{$name,$root,"languageVersion":"1.2.3"}]}', + ); + testThrows( + 'leading zero first', + '{$cfg,"packages":[{$name,$root,"languageVersion":"01.1"}]}', + ); + testThrows( + 'leading zero second', + '{$cfg,"packages":[{$name,$root,"languageVersion":"1.01"}]}', + ); + testThrows( + 'trailing-', + '{$cfg,"packages":[{$name,$root,"languageVersion":"1.1-1"}]}', + ); + testThrows( + 'trailing+', + '{$cfg,"packages":[{$name,$root,"languageVersion":"1.1+1"}]}', + ); }); }); - testThrows('duplicate package name', - '{$cfg,"packages":[{$name,$root},{$name,"rootUri":"/other/"}]}'); + testThrows( + 'duplicate package name', + '{$cfg,"packages":[{$name,$root},{$name,"rootUri":"/other/"}]}', + ); testThrowsContains( - // The roots of foo and bar are the same. - 'same roots', - '{$cfg,"packages":[{$name,$root},{"name":"bar",$root}]}', - 'the same root directory'); + // The roots of foo and bar are the same. + 'same roots', + '{$cfg,"packages":[{$name,$root},{"name":"bar",$root}]}', + 'the same root directory', + ); testThrowsContains( - // The roots of foo and bar are the same. - 'same roots 2', - '{$cfg,"packages":[{$name,"rootUri":"/"},{"name":"bar","rootUri":"/"}]}', - 'the same root directory'); + // The roots of foo and bar are the same. + 'same roots 2', + '{$cfg,"packages":[{$name,"rootUri":"/"},{"name":"bar","rootUri":"/"}]}', + 'the same root directory', + ); testThrowsContains( - // The root of bar is inside the root of foo, - // but the package root of foo is inside the root of bar. - 'between root and lib', - '{$cfg,"packages":[' - '{"name":"foo","rootUri":"/foo/","packageUri":"bar/lib/"},' - '{"name":"bar","rootUri":"/foo/bar/","packageUri":"baz/lib"}]}', - 'package root of foo is inside the root of bar'); + // The root of bar is inside the root of foo, + // but the package root of foo is inside the root of bar. + 'between root and lib', + '{$cfg,"packages":[' + '{"name":"foo","rootUri":"/foo/","packageUri":"bar/lib/"},' + '{"name":"bar","rootUri":"/foo/bar/","packageUri":"baz/lib"}]}', + 'package root of foo is inside the root of bar', + ); // This shouldn't be allowed, but for internal reasons it is. test('package inside package root', () { var config = parsePackageConfigBytes( - // ignore: unnecessary_cast - utf8.encode( - '{$cfg,"packages":[' - '{"name":"foo","rootUri":"/foo/","packageUri":"lib/"},' - '{"name":"bar","rootUri":"/foo/lib/bar/","packageUri":"lib"}]}', - ) as Uint8List, - Uri.parse('file:///tmp/.dart_tool/file.dart'), - throwError); + // ignore: unnecessary_cast + utf8.encode( + '{$cfg,"packages":[' + '{"name":"foo","rootUri":"/foo/","packageUri":"lib/"},' + '{"name":"bar","rootUri":"/foo/lib/bar/","packageUri":"lib"}]}', + ) + as Uint8List, + Uri.parse('file:///tmp/.dart_tool/file.dart'), + throwError, + ); + expect( + config + .packageOf(Uri.parse('file:///foo/lib/bar/lib/lala.dart'))! + .name, + 'foo', + ); // why not bar? expect( - config - .packageOf(Uri.parse('file:///foo/lib/bar/lib/lala.dart'))! - .name, - 'foo'); // why not bar? - expect(config.toPackageUri(Uri.parse('file:///foo/lib/bar/lib/diz')), - Uri.parse('package:foo/bar/lib/diz')); // why not package:bar/diz? + config.toPackageUri(Uri.parse('file:///foo/lib/bar/lib/diz')), + Uri.parse('package:foo/bar/lib/diz'), + ); // why not package:bar/diz? }); }); }); @@ -434,10 +560,16 @@ void main() { var expectedPackage = expected[name]!; expect(expectedPackage, isNotNull); expect(package.root, expectedPackage.root, reason: 'root'); - expect(package.packageUriRoot, expectedPackage.packageUriRoot, - reason: 'package root'); - expect(package.languageVersion, expectedPackage.languageVersion, - reason: 'languageVersion'); + expect( + package.packageUriRoot, + expectedPackage.packageUriRoot, + reason: 'package root', + ); + expect( + package.languageVersion, + expectedPackage.languageVersion, + reason: 'languageVersion', + ); }); } }); @@ -455,34 +587,58 @@ void main() { '''; var baseUri = Uri.parse('file:///start/'); var config = PackageConfig([ - Package('foo', Uri.parse('file:///start/foo/'), - packageUriRoot: Uri.parse('file:///start/foo/bar/'), - languageVersion: LanguageVersion(1, 2)) + Package( + 'foo', + Uri.parse('file:///start/foo/'), + packageUriRoot: Uri.parse('file:///start/foo/bar/'), + languageVersion: LanguageVersion(1, 2), + ), ]); testConfig( - 'string', PackageConfig.parseString(configText, baseUri), config); + 'string', + PackageConfig.parseString(configText, baseUri), + config, + ); testConfig( - 'bytes', - PackageConfig.parseBytes( - Uint8List.fromList(configText.codeUnits), baseUri), - config); - testConfig('json', PackageConfig.parseJson(jsonDecode(configText), baseUri), - config); + 'bytes', + PackageConfig.parseBytes( + Uint8List.fromList(configText.codeUnits), + baseUri, + ), + config, + ); + testConfig( + 'json', + PackageConfig.parseJson(jsonDecode(configText), baseUri), + config, + ); baseUri = Uri.parse('file:///start2/'); config = PackageConfig([ - Package('foo', Uri.parse('file:///start2/foo/'), - packageUriRoot: Uri.parse('file:///start2/foo/bar/'), - languageVersion: LanguageVersion(1, 2)) + Package( + 'foo', + Uri.parse('file:///start2/foo/'), + packageUriRoot: Uri.parse('file:///start2/foo/bar/'), + languageVersion: LanguageVersion(1, 2), + ), ]); testConfig( - 'string2', PackageConfig.parseString(configText, baseUri), config); + 'string2', + PackageConfig.parseString(configText, baseUri), + config, + ); + testConfig( + 'bytes2', + PackageConfig.parseBytes( + Uint8List.fromList(configText.codeUnits), + baseUri, + ), + config, + ); testConfig( - 'bytes2', - PackageConfig.parseBytes( - Uint8List.fromList(configText.codeUnits), baseUri), - config); - testConfig('json2', - PackageConfig.parseJson(jsonDecode(configText), baseUri), config); + 'json2', + PackageConfig.parseJson(jsonDecode(configText), baseUri), + config, + ); }); } diff --git a/pkgs/package_config/test/src/util_io.dart b/pkgs/package_config/test/src/util_io.dart index e032556f4..40bd231b7 100644 --- a/pkgs/package_config/test/src/util_io.dart +++ b/pkgs/package_config/test/src/util_io.dart @@ -13,8 +13,11 @@ import 'package:test/test.dart'; /// it's a subdirectory, otherwise it's a file and the value is the content /// as a string. /// Introduces a group to hold the [setUp]/[tearDown] logic. -void fileTest(String name, Map description, - void Function(Directory directory) fileTest) { +void fileTest( + String name, + Map description, + void Function(Directory directory) fileTest, +) { group('file-test', () { var tempDir = Directory.systemTemp.createTempSync('pkgcfgtest'); setUp(() { From 1b387ca19a55ad9cfa7bc2c4ff719d5041670e04 Mon Sep 17 00:00:00 2001 From: "Lasse R.H. Nielsen" Date: Tue, 11 Mar 2025 15:35:54 +0100 Subject: [PATCH 5/8] Remove unused utility functions. --- pkgs/package_config/lib/src/discovery.dart | 2 +- .../lib/src/package_config.dart | 78 +++++++++---------- .../lib/src/package_config_io.dart | 18 +---- .../lib/src/package_config_json.dart | 2 +- pkgs/package_config/lib/src/util.dart | 38 +-------- pkgs/package_config/test/bench.dart | 4 +- pkgs/package_config/test/parse_test.dart | 37 ++++----- pkgs/package_config/test/src/util.dart | 3 +- 8 files changed, 59 insertions(+), 123 deletions(-) diff --git a/pkgs/package_config/lib/src/discovery.dart b/pkgs/package_config/lib/src/discovery.dart index 4e9e22dd1..7f9c23ba4 100644 --- a/pkgs/package_config/lib/src/discovery.dart +++ b/pkgs/package_config/lib/src/discovery.dart @@ -123,7 +123,7 @@ Future findPackageConfigInDirectory( ) 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; } diff --git a/pkgs/package_config/lib/src/package_config.dart b/pkgs/package_config/lib/src/package_config.dart index 2dc035da3..8a711eacd 100644 --- a/pkgs/package_config/lib/src/package_config.dart +++ b/pkgs/package_config/lib/src/package_config.dart @@ -338,11 +338,11 @@ abstract class LanguageVersion implements Comparable { /// /// 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. /// @@ -420,8 +420,8 @@ abstract class LanguageVersion implements Comparable { /// /// 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. -/// The caller which provided the `onError` handler which was called +/// 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 { @@ -460,8 +460,8 @@ extension LanguageVersionRelationalOperators on LanguageVersion { /// check out [LanguageVersion.compareTo]. bool operator <(LanguageVersion other) { // Throw an error if comparing an invalid language version. - if (this is InvalidLanguageVersion) _throwThisInvalid(); - if (other is InvalidLanguageVersion) _throwOtherInvalid(); + if (major < 0) _throwThisInvalid(); + if (other.major < 0) _throwOtherInvalid(); return compareTo(other) < 0; } @@ -473,8 +473,8 @@ extension LanguageVersionRelationalOperators on LanguageVersion { /// check out [LanguageVersion.compareTo]. bool operator <=(LanguageVersion other) { // Throw an error if comparing an invalid language version. - if (this is InvalidLanguageVersion) _throwThisInvalid(); - if (other is InvalidLanguageVersion) _throwOtherInvalid(); + if (major < 0) _throwThisInvalid(); + if (other.major < 0) _throwOtherInvalid(); return compareTo(other) <= 0; } @@ -486,8 +486,8 @@ extension LanguageVersionRelationalOperators on LanguageVersion { /// check out [LanguageVersion.compareTo]. bool operator >(LanguageVersion other) { // Throw an error if comparing an invalid language version. - if (this is InvalidLanguageVersion) _throwThisInvalid(); - if (other is InvalidLanguageVersion) _throwOtherInvalid(); + if (major < 0) _throwThisInvalid(); + if (other.major < 0) _throwOtherInvalid(); return compareTo(other) > 0; } @@ -500,8 +500,8 @@ extension LanguageVersionRelationalOperators on LanguageVersion { /// check out [LanguageVersion.compareTo]. bool operator >=(LanguageVersion other) { // Throw an error if comparing an invalid language version. - if (this is InvalidLanguageVersion) _throwThisInvalid(); - if (other is InvalidLanguageVersion) _throwOtherInvalid(); + if (major < 0) _throwThisInvalid(); + if (other.major < 0) _throwOtherInvalid(); return compareTo(other) >= 0; } @@ -864,10 +864,10 @@ class SimplePackage implements Package { /// /// 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). +/// 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, + String source, void Function(Object error) onError, ) { var index = 0; @@ -879,7 +879,7 @@ LanguageVersion parseLanguageVersion( // It is a recoverable error if the numeral starts with leading zeros. int readNumeral() { const maxValue = 0x7FFFFFFF; - if (index == source!.length) { + if (index == source.length) { onError(PackageConfigFormatException('Missing number', source, index)); return -1; } @@ -918,7 +918,7 @@ LanguageVersion parseLanguageVersion( if (major < 0) { return SimpleInvalidLanguageVersion(source); } - if (index == source!.length || source.codeUnitAt(index) != $dot) { + if (index == source.length || source.codeUnitAt(index) != $dot) { onError(PackageConfigFormatException("Missing '.'", source, index)); return SimpleInvalidLanguageVersion(source); } @@ -941,22 +941,18 @@ LanguageVersion parseLanguageVersion( } @sealed -abstract class _SimpleLanguageVersionBase implements LanguageVersion { - @override - int compareTo(LanguageVersion other) { - var result = major - other.major; - if (result != 0) return result; - return minor - other.minor; - } -} - -@sealed -class SimpleLanguageVersion extends _SimpleLanguageVersionBase { +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 @@ -968,20 +964,24 @@ class SimpleLanguageVersion extends _SimpleLanguageVersionBase { @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 _SimpleLanguageVersionBase +class SimpleInvalidLanguageVersion extends SimpleLanguageVersion implements InvalidLanguageVersion { - final String? _source; - SimpleInvalidLanguageVersion(this._source); - @override - int get major => -1; - @override - int get minor => -1; + SimpleInvalidLanguageVersion(String source) : super(-1, -1, source); @override - String toString() => _source!; + int get hashCode => identityHashCode(this); + @override + bool operator ==(Object other) => identical(this, other); } abstract class PackageTree { diff --git a/pkgs/package_config/lib/src/package_config_io.dart b/pkgs/package_config/lib/src/package_config_io.dart index 26307c07b..8b91a1f31 100644 --- a/pkgs/package_config/lib/src/package_config_io.dart +++ b/pkgs/package_config/lib/src/package_config_io.dart @@ -33,8 +33,8 @@ Future readConfigFile( Uint8List bytes; try { bytes = await file.readAsBytes(); - } catch (e) { - onError(e); + } catch (error) { + onError(error); return const SimplePackageConfig.empty(); } return parsePackageConfigBytes(bytes, file.uri, onError); @@ -80,20 +80,6 @@ Future readConfigFileUri( return parsePackageConfigBytes(bytes, file, onError); } -Future 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 writePackageConfigJsonFile( PackageConfig config, Directory targetDirectory, diff --git a/pkgs/package_config/lib/src/package_config_json.dart b/pkgs/package_config/lib/src/package_config_json.dart index 306af2c0d..ee818ea13 100644 --- a/pkgs/package_config/lib/src/package_config_json.dart +++ b/pkgs/package_config/lib/src/package_config_json.dart @@ -162,7 +162,7 @@ PackageConfig parsePackageConfigJson( } LanguageVersion? version; - if (languageVersion != null) { + if (languageVersion case var languageVersion?) { version = parseLanguageVersion(languageVersion, onError); } else if (hasVersion) { version = SimpleInvalidLanguageVersion('invalid'); diff --git a/pkgs/package_config/lib/src/util.dart b/pkgs/package_config/lib/src/util.dart index cebd3c8c0..ce112b571 100644 --- a/pkgs/package_config/lib/src/util.dart +++ b/pkgs/package_config/lib/src/util.dart @@ -13,11 +13,6 @@ const String _validPackageNameCharacters = 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 @@ bool isUriPrefix(Uri prefix, Uri path) { 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 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 @@ Uri? relativizeUri(Uri? uri, Uri? baseUri) { } // 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/test/bench.dart b/pkgs/package_config/test/bench.dart index 1d0ab1da3..ba7408edd 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 @@ void bench(final int size, final bool doPrint) { 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/parse_test.dart b/pkgs/package_config/test/parse_test.dart index 0550a73c6..adeeaa8cd 100644 --- a/pkgs/package_config/test/parse_test.dart +++ b/pkgs/package_config/test/parse_test.dart @@ -48,8 +48,7 @@ void main() { } '''; var config = parsePackageConfigBytes( - // ignore: unnecessary_cast - utf8.encode(packageConfigFile) as Uint8List, + utf8.encode(packageConfigFile), Uri.parse('file:///tmp/.dart_tool/file.dart'), throwError, ); @@ -138,8 +137,7 @@ void main() { } '''; var config = parsePackageConfigBytes( - // ignore: unnecessary_cast - utf8.encode(packageConfigFile) as Uint8List, + utf8.encode(packageConfigFile), Uri.parse('file:///tmp/.dart_tool/file.dart'), throwError, ); @@ -172,8 +170,7 @@ void main() { var root = '"rootUri":"/foo/"'; test('minimal', () { var config = parsePackageConfigBytes( - // ignore: unnecessary_cast - utf8.encode('{$cfg,$pkgs}') as Uint8List, + utf8.encode('{$cfg,$pkgs}'), Uri.parse('file:///tmp/.dart_tool/file.dart'), throwError, ); @@ -184,8 +181,7 @@ void main() { // A package must have a name and a rootUri, the remaining properties // are optional. var config = parsePackageConfigBytes( - // ignore: unnecessary_cast - utf8.encode('{$cfg,"packages":[{$name,$root}]}') as Uint8List, + utf8.encode('{$cfg,"packages":[{$name,$root}]}'), Uri.parse('file:///tmp/.dart_tool/file.dart'), throwError, ); @@ -205,9 +201,8 @@ void main() { ], }), ); - // ignore: unnecessary_cast var config = parsePackageConfigBytes( - configBytes as Uint8List, + configBytes, Uri.parse('file:///tmp/.dart_tool/file.dart'), throwError, ); @@ -258,9 +253,8 @@ void main() { ], }), ); - // ignore: unnecessary_cast var config = parsePackageConfigBytes( - configBytes as Uint8List, + configBytes, Uri.parse('file:///tmp/.dart_tool/file.dart'), throwError, ); @@ -303,8 +297,7 @@ void main() { }), ); var config = parsePackageConfigBytes( - // ignore: unnecessary_cast - configBytes as Uint8List, + configBytes, Uri.parse('file:///C:/tmp/.dart_tool/file.dart'), throwError, ); @@ -323,9 +316,8 @@ void main() { void testThrows(String name, String source) { test(name, () { expect( - // ignore: unnecessary_cast () => parsePackageConfigBytes( - utf8.encode(source) as Uint8List, + utf8.encode(source), Uri.parse('file:///tmp/.dart_tool/file.dart'), throwError, ), @@ -343,8 +335,7 @@ void main() { Object? exception; try { parsePackageConfigBytes( - // ignore: unnecessary_cast - utf8.encode(source) as Uint8List, + utf8.encode(source), Uri.parse('file:///tmp/.dart_tool/file.dart'), throwError, ); @@ -521,13 +512,11 @@ void main() { // This shouldn't be allowed, but for internal reasons it is. test('package inside package root', () { var config = parsePackageConfigBytes( - // ignore: unnecessary_cast utf8.encode( - '{$cfg,"packages":[' - '{"name":"foo","rootUri":"/foo/","packageUri":"lib/"},' - '{"name":"bar","rootUri":"/foo/lib/bar/","packageUri":"lib"}]}', - ) - as Uint8List, + '{$cfg,"packages":[' + '{"name":"foo","rootUri":"/foo/","packageUri":"lib/"},' + '{"name":"bar","rootUri":"/foo/lib/bar/","packageUri":"lib"}]}', + ), Uri.parse('file:///tmp/.dart_tool/file.dart'), throwError, ); diff --git a/pkgs/package_config/test/src/util.dart b/pkgs/package_config/test/src/util.dart index 780ee80dc..0253e914e 100644 --- a/pkgs/package_config/test/src/util.dart +++ b/pkgs/package_config/test/src/util.dart @@ -48,8 +48,7 @@ void loaderTest( if (value is! Map) 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; } From 6fff9b8fd99f4e5482254de686c8cc90f5369b9e Mon Sep 17 00:00:00 2001 From: "Lasse R.H. Nielsen" Date: Tue, 11 Mar 2025 16:19:04 +0100 Subject: [PATCH 6/8] And format again. --- pkgs/package_config/lib/src/package_config.dart | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/pkgs/package_config/lib/src/package_config.dart b/pkgs/package_config/lib/src/package_config.dart index 8a711eacd..ce626e212 100644 --- a/pkgs/package_config/lib/src/package_config.dart +++ b/pkgs/package_config/lib/src/package_config.dart @@ -948,7 +948,7 @@ class SimpleLanguageVersion implements LanguageVersion { 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; From 15ca3f26acc4cf513a25297fe8705bb1a52e0e27 Mon Sep 17 00:00:00 2001 From: "Lasse R.H. Nielsen" Date: Tue, 11 Mar 2025 16:21:24 +0100 Subject: [PATCH 7/8] Add file in bin/. --- .../package_config/bin/package_config_of.dart | 443 ++++++++++++++++++ 1 file changed, 443 insertions(+) create mode 100644 pkgs/package_config/bin/package_config_of.dart 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 000000000..6e8f95b7f --- /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 args) async { + // Basic command line parser. No fancy. + var files = []; + 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 = >[]; + + 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 _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 _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 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=.` 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 *= *(?\d+) *\. *(?\d+) *$)?' + r'|' + r'(?[^ \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. +'''; From 05ec976eecc5eedc14ed43db4674fed42db7e664 Mon Sep 17 00:00:00 2001 From: "Lasse R.H. Nielsen" Date: Tue, 11 Mar 2025 16:23:36 +0100 Subject: [PATCH 8/8] Update workflow to use SDK 3.7 --- .github/workflows/package_config.yaml | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/.github/workflows/package_config.yaml b/.github/workflows/package_config.yaml index 29d739eca..1bd55582e 100644 --- a/.github/workflows/package_config.yaml +++ b/.github/workflows/package_config.yaml @@ -54,7 +54,7 @@ jobs: fail-fast: false matrix: os: [ubuntu-latest, windows-latest] - sdk: [3.4, dev] + sdk: [3.7, dev] steps: - uses: actions/checkout@11bd71901bbe5b1630ceea73d27597364c9af683 - uses: dart-lang/setup-dart@e630b99d28a3b71860378cafdc2a067c71107f94