Recognize URIs starting with `dart-macro+`.
Such URIs belong to the same package as the
the rest of the URI would, but are not `package:` URIs,
and do not have a corresponding `package:` URI.
In this way, they behave like files inside the package,
but outside of `lib/`, even if they are `dart-macro+package:`
URIs.
diff --git a/CHANGELOG.md b/CHANGELOG.md
index a682bc3..95a9c9c 100644
--- a/CHANGELOG.md
+++ b/CHANGELOG.md
@@ -1,6 +1,8 @@
-## 2.1.1-wip
+## 2.2.0-wip
- Require Dart 3.2
+- Add support for `dart-macro+` scheme prefixes.
+ Recognizes these as belonging to the same package as their unprefixed URIs.
## 2.1.0
diff --git a/lib/src/package_config.dart b/lib/src/package_config.dart
index c00ac67..2225834 100644
--- a/lib/src/package_config.dart
+++ b/lib/src/package_config.dart
@@ -166,9 +166,13 @@
/// Provides the associated package for a specific [file] (or directory).
///
- /// Returns a [Package] which contains the [file]'s path, if any.
+ /// If the [file] is a `package:` URI, it must be a *valid* `package:` URI,
+ /// and the package with that URI's package name is returned, if one exists.
+ ///
+ /// Otherwise returns a [Package] which contains the [file]'s path.
/// That is, the [Package.root] directory is a parent directory
- /// of the [file]'s location.
+ /// of the [file]'s location, based on the root URI being a prefix of this
+ /// [file] URI.
///
/// Returns `null` if the file does not belong to any package.
Package? packageOf(Uri file);
@@ -190,7 +194,7 @@
/// [Package.packageUriRoot] of the corresponding package.
Uri? resolve(Uri packageUri);
- /// The package URI which resolves to [nonPackageUri].
+ /// The package URI which resolves to [nonPackageUri], if any.
///
/// The [nonPackageUri] must not have any query or fragment part,
/// and it must not have `package` as scheme.
diff --git a/lib/src/package_config_impl.dart b/lib/src/package_config_impl.dart
index d8e8d49..b5205eb 100644
--- a/lib/src/package_config_impl.dart
+++ b/lib/src/package_config_impl.dart
@@ -142,7 +142,16 @@
Package? operator [](String packageName) => _packages[packageName];
@override
- Package? packageOf(Uri file) => _packageTree.packageOf(file);
+ Package? packageOf(Uri uri) {
+ if (uri.isScheme('package') || uri.isScheme('dart-macro+package')) {
+ // Directly specifies its package.
+ var packageName = checkValidPackageUri(uri, 'uri', allowMacro: true);
+ return _packages[packageName];
+ }
+
+ /// Try finding a prefix in the package-config's `rootUri`s.
+ return _packageTree.packageOf(uri);
+ }
@override
Uri? resolve(Uri packageUri) {
@@ -161,14 +170,22 @@
throw PackageConfigArgumentError(nonPackageUri, 'nonPackageUri',
'Must not have query or fragment part');
}
+ if (nonPackageUri.scheme.startsWith(dartMacroSchemePrefix)) {
+ // A macro-generated file has no `package:` URI.
+ return null;
+ }
// 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.
+ // Check if it is inside the package URI root (usually `lib/`).
var path = nonPackageUri.toString();
- var root = package.packageUriRoot.toString();
- if (_beginsWith(package.root.toString().length, root, path)) {
- var rest = path.substring(root.length);
+ var packageRoot = package.root.toString();
+ var packageUriRoot = package.packageUriRoot.toString();
+ assert(packageUriRoot.startsWith(packageRoot));
+ // This prefix was correctly matched by `packageOf`.
+ var matchedPrefix = packageRoot.length;
+ if (_beginsWith(matchedPrefix, packageUriRoot, matchedPrefix, path)) {
+ var rest = path.substring(packageUriRoot.length);
return Uri(scheme: 'package', path: '${package.name}/$rest');
}
return null;
@@ -428,7 +445,7 @@
}
// 2) The existing package has a packageUriRoot thats inside the
// root of the new package.
- if (_beginsWith(0, newPackage.root.toString(),
+ if (_beginsWith(0, newPackage.root.toString(), 0,
existingPackage.packageUriRoot.toString())) {
onError(ConflictException(
newPackage, existingPackage, ConflictType.interleaving));
@@ -439,7 +456,7 @@
// it thouh.
// 3) The new package is inside the packageUriRoot of existing package.
if (_disallowPackagesInsidePackageUriRoot) {
- if (_beginsWith(0, existingPackage.packageUriRoot.toString(),
+ if (_beginsWith(0, existingPackage.packageUriRoot.toString(), 0,
newPackage.root.toString())) {
onError(ConflictException(
newPackage, existingPackage, ConflictType.insidePackageRoot));
@@ -479,16 +496,18 @@
_packages.add(newPackage);
}
- bool _isMatch(
- String path, _PackageTrieNode node, List<SimplePackage> potential) {
+ /// Matches `path.substring(offset)` against the strings of `node`.
+ bool _isMatch(String path, int pathStart, _PackageTrieNode node,
+ List<SimplePackage> potential) {
var currentPackage = node.package;
if (currentPackage != null) {
var currentPackageRootLength = currentPackage.root.toString().length;
- if (path.length == currentPackageRootLength) return true;
+ if (path.length - pathStart == currentPackageRootLength) return true;
var currentPackageUriRoot = currentPackage.packageUriRoot.toString();
// Is [file] inside the package root of [currentPackage]?
if (currentPackageUriRoot.length == currentPackageRootLength ||
- _beginsWith(currentPackageRootLength, currentPackageUriRoot, path)) {
+ _beginsWith(currentPackageRootLength, currentPackageUriRoot,
+ currentPackageRootLength + pathStart, path)) {
return true;
}
potential.add(currentPackage);
@@ -498,11 +517,18 @@
@override
SimplePackage? packageOf(Uri file) {
- var currentTrieNode = _map[file.scheme];
+ var scheme = file.scheme;
+ var pathStart = 0;
+ if (scheme.startsWith(dartMacroSchemePrefix)) {
+ scheme = schemeFromMacroScheme(scheme);
+ pathStart = dartMacroSchemePrefix.length;
+ }
+ var currentTrieNode = _map[scheme];
if (currentTrieNode == null) return null;
var path = file.toString();
+ assert(pathStart == 0 || path.startsWith(dartMacroSchemePrefix));
var potential = <SimplePackage>[];
- if (_isMatch(path, currentTrieNode, potential)) {
+ if (_isMatch(path, pathStart, currentTrieNode, potential)) {
return currentTrieNode.package;
}
var segments = file.pathSegments;
@@ -511,7 +537,7 @@
var segment = segments[i];
currentTrieNode = currentTrieNode!.map[segment];
if (currentTrieNode == null) break;
- if (_isMatch(path, currentTrieNode, potential)) {
+ if (_isMatch(path, pathStart, currentTrieNode, potential)) {
return currentTrieNode.package;
}
}
@@ -532,12 +558,15 @@
/// 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;
+/// Skips checking the [parentStart] first characters which are assumed to
+/// already have been matched up to [longerStart].
+bool _beginsWith(
+ int parentStart, String parentPath, int longerStart, String longerPath) {
+ if (longerPath.length - longerStart < parentPath.length - parentStart) {
+ return false;
+ }
+ for (var i = parentStart, j = longerStart; i < parentPath.length; i++, j++) {
+ if (longerPath.codeUnitAt(j) != parentPath.codeUnitAt(i)) return false;
}
return true;
}
diff --git a/lib/src/packages_file.dart b/lib/src/packages_file.dart
index f84db10..6590b03 100644
--- a/lib/src/packages_file.dart
+++ b/lib/src/packages_file.dart
@@ -107,6 +107,13 @@
'Package URI as location for package', source, separatorIndex + 1));
continue;
}
+ if (packageLocation.scheme.startsWith(dartMacroSchemePrefix)) {
+ onError(PackageConfigFormatException(
+ 'Macro-generated URI as location for package',
+ source,
+ separatorIndex + 1));
+ continue;
+ }
var path = packageLocation.path;
if (!path.endsWith('/')) {
path += '/';
@@ -120,6 +127,8 @@
var rootUri = packageLocation;
if (path.endsWith('/lib/')) {
// Assume default Pub package layout. Include package itself in root.
+ // TODO(lrn): Stop doing this. Expect the package file to add a root
+ // if it wants a root.
rootUri =
packageLocation.replace(path: path.substring(0, path.length - 4));
}
diff --git a/lib/src/util.dart b/lib/src/util.dart
index 3bf1bec..97129be 100644
--- a/lib/src/util.dart
+++ b/lib/src/util.dart
@@ -40,14 +40,39 @@
return -1;
}
+/// Dart macro generated augmentation file scheme for a package library.
+const dartMacroSchemePrefix = 'dart-macro+';
+
+/// Dart macro generated augmentation file scheme for a package library.
+const dartMacroPackageScheme = 'dart-macro+package';
+
+/// Dart macro generated augmentation file scheme for a file library.
+const dartMacroFileScheme = 'dart-macro+file';
+
+Map<String, String> _macroSchemeTranslation = {
+ dartMacroPackageScheme: 'package',
+ dartMacroFileScheme: 'file',
+};
+
+/// Convert a `dart-macro+` scheme to the underlying scheme.
+String schemeFromMacroScheme(String macroScheme) {
+ assert(macroScheme.startsWith(dartMacroSchemePrefix));
+ return _macroSchemeTranslation[macroScheme] ??=
+ macroScheme.substring(dartMacroSchemePrefix.length);
+}
+
/// Validate that a [Uri] is a valid `package:` URI.
///
/// Used to validate user input.
///
/// Returns the package name extracted from the package URI,
/// which is the path segment between `package:` and the first `/`.
-String checkValidPackageUri(Uri packageUri, String name) {
- if (packageUri.scheme != 'package') {
+///
+/// If [allowMacro] is `true`, the scheme may also be `dart-macro+package`.
+String checkValidPackageUri(Uri packageUri, String name,
+ {bool allowMacro = false}) {
+ if (!packageUri.isScheme('package') &&
+ !(allowMacro && packageUri.isScheme(dartMacroPackageScheme))) {
throw PackageConfigArgumentError(packageUri, name, 'Not a package: URI');
}
if (packageUri.hasAuthority) {
@@ -81,7 +106,7 @@
if (badIndex >= 0) {
if (packageName.isEmpty) {
throw PackageConfigArgumentError(
- packageUri, name, 'Package names mus be non-empty');
+ packageUri, name, 'Package names must be non-empty');
}
if (badIndex == packageName.length) {
throw PackageConfigArgumentError(packageUri, name,
diff --git a/pubspec.yaml b/pubspec.yaml
index 6ac7f54..51df8bb 100644
--- a/pubspec.yaml
+++ b/pubspec.yaml
@@ -1,5 +1,5 @@
name: package_config
-version: 2.1.1-wip
+version: 2.2.0-wip
description: Support for reading and writing Dart Package Configuration files.
repository: https://github.com/dart-lang/package_config
diff --git a/test/package_config_impl_test.dart b/test/package_config_impl_test.dart
index 87d1fd4..f683ba1 100644
--- a/test/package_config_impl_test.dart
+++ b/test/package_config_impl_test.dart
@@ -183,6 +183,63 @@
'extra': 'data',
});
});
+
+ group('macro-generated', () {
+ const macroPrefix = 'dart-macro+';
+ var config = PackageConfig([
+ Package('foo', Uri.parse('file:///pkg/foo/'),
+ packageUriRoot: Uri.parse('file:///pkg/foo/lib/'),
+ languageVersion: LanguageVersion(3, 6)),
+ Package('bar', Uri.parse('file:///pkg/bar/'),
+ packageUriRoot: Uri.parse('file:///pkg/bar/lib/')),
+ ]);
+ test('package URI', () {
+ var uri = Uri.parse('${macroPrefix}package:foo/bar.dart');
+ // The macro-package-URI belongs to the package
+ // of the underlying `package:` URI.
+ var packageOf = config.packageOf(uri);
+ expect(packageOf, isNotNull);
+ expect(packageOf!.name, 'foo');
+
+ // The URI is not a package URI.
+ expect(() => config.resolve(uri), throwsArgumentError);
+
+ // And it has no corresponding package URI.
+ var toPackageUri = config.toPackageUri(uri);
+ expect(toPackageUri, null);
+ });
+
+ test('non-package URI outside lib/', () {
+ var uri = Uri.parse('${macroPrefix}file:///pkg/foo/tool/tool.dart');
+
+ var packageOf = config.packageOf(uri);
+ expect(packageOf, isNotNull);
+ expect(packageOf!.name, 'foo');
+
+ // The URI is not a package URI.
+ expect(() => config.resolve(uri), throwsArgumentError);
+
+ // And it has no corresponding package URI.
+ var toPackageUri = config.toPackageUri(uri);
+ expect(toPackageUri, null);
+ });
+
+ test('non-package URI inside lib/', () {
+ var uri = Uri.parse('${macroPrefix}file:///pkg/foo/lib/file.dart');
+
+ var packageOf = config.packageOf(uri);
+ expect(packageOf, isNotNull);
+ expect(packageOf!.name, 'foo');
+
+ // The URI is not a package URI.
+ expect(() => config.resolve(uri), throwsArgumentError);
+
+ // And it has no corresponding package URI, even if it
+ // "belongs" inside `lib/`.
+ var toPackageUri = config.toPackageUri(uri);
+ expect(toPackageUri, null);
+ });
+ });
}
final Matcher throwsPackageConfigError = throwsA(isA<PackageConfigError>());