Skip to content
This repository has been archived by the owner on Jan 14, 2025. It is now read-only.

Recognize URIs starting with dart-macro+. #148

Closed
wants to merge 1 commit into from
Closed
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
4 changes: 3 additions & 1 deletion CHANGELOG.md
Original file line number Diff line number Diff line change
@@ -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

Expand Down
10 changes: 7 additions & 3 deletions lib/src/package_config.dart
Original file line number Diff line number Diff line change
Expand Up @@ -166,9 +166,13 @@ abstract class PackageConfig {

/// 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);
Expand All @@ -190,7 +194,7 @@ abstract class PackageConfig {
/// [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.
Expand Down
69 changes: 49 additions & 20 deletions lib/src/package_config_impl.dart
Original file line number Diff line number Diff line change
Expand Up @@ -142,7 +142,16 @@ class SimplePackageConfig implements PackageConfig {
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) {
Expand All @@ -161,14 +170,22 @@ class SimplePackageConfig implements PackageConfig {
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;
Expand Down Expand Up @@ -428,7 +445,7 @@ class TriePackageTree implements PackageTree {
}
// 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));
Expand All @@ -439,7 +456,7 @@ class TriePackageTree implements PackageTree {
// 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));
Expand Down Expand Up @@ -479,16 +496,18 @@ class TriePackageTree implements PackageTree {
_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);
Expand All @@ -498,11 +517,18 @@ class TriePackageTree implements PackageTree {

@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;
Expand All @@ -511,7 +537,7 @@ class TriePackageTree implements PackageTree {
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;
}
}
Expand All @@ -532,12 +558,15 @@ class EmptyPackageTree implements PackageTree {

/// 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;
}
Expand Down
9 changes: 9 additions & 0 deletions lib/src/packages_file.dart
Original file line number Diff line number Diff line change
Expand Up @@ -107,6 +107,13 @@ PackageConfig parse(
'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 += '/';
Expand All @@ -120,6 +127,8 @@ PackageConfig parse(
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));
}
Expand Down
31 changes: 28 additions & 3 deletions lib/src/util.dart
Original file line number Diff line number Diff line change
Expand Up @@ -40,14 +40,39 @@ int checkPackageName(String string) {
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) {
Expand Down Expand Up @@ -81,7 +106,7 @@ String checkValidPackageUri(Uri packageUri, String name) {
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,
Expand Down
2 changes: 1 addition & 1 deletion pubspec.yaml
Original file line number Diff line number Diff line change
@@ -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

Expand Down
57 changes: 57 additions & 0 deletions test/package_config_impl_test.dart
Original file line number Diff line number Diff line change
Expand Up @@ -183,6 +183,63 @@ void main() {
'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>());