Skip to content
Merged
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: 4 additions & 0 deletions build/CHANGELOG.md
Original file line number Diff line number Diff line change
@@ -1,3 +1,7 @@
## 4.0.1-wip

- Improvements to dartdoc.

## 4.0.0

- Remove methods and classes deprecated in `4.0.0`.
Expand Down
2 changes: 0 additions & 2 deletions build/lib/build.dart
Original file line number Diff line number Diff line change
Expand Up @@ -11,7 +11,5 @@ export 'src/file_deleting_builder.dart';
export 'src/logging.dart' show log;
export 'src/post_process_build_step.dart';
export 'src/post_process_builder.dart';
export 'src/reader.dart';
export 'src/resolver.dart';
export 'src/resource.dart';
export 'src/writer.dart';
160 changes: 69 additions & 91 deletions build/lib/src/asset_id.dart
Original file line number Diff line number Diff line change
Expand Up @@ -4,70 +4,59 @@

import 'package:path/path.dart' as p;

/// Identifies an asset within a package.
/// A file in a `build_runner` build.
class AssetId implements Comparable<AssetId> {
/// The name of the package containing this asset.
/// The package containing the file.
final String package;

/// The path to the asset relative to the root directory of [package].
/// The relative path of the file under the root of [package].
///
/// Source (i.e. read from disk) and generated (i.e. the output of a
/// `Builder`) assets all have paths. Even intermediate assets that are
/// generated and then consumed by later transformations will still have a
/// path used to identify it.
/// The path is relative and contains no parent references `..`, guaranteeing
/// that it is under [package].
///
/// Asset paths always use forward slashes as path separators, regardless of
/// the host platform. Asset paths will always be within their package, that
/// is they will never contain "../".
/// The path segment separator is `/` on all platforms.
final String path;

/// Splits [path] into its components.
/// The segments of [path].
List<String> get pathSegments => p.url.split(path);

/// The file extension of the asset, if it has one, including the ".".
/// The file extension of [path]: the portion of its "basename" from the last
/// `.` to the end, including the `.` itself.
String get extension => p.extension(path);

/// Creates a new [AssetId] at [path] within [package].
/// An [AssetId] with the specified [path] under [package].
///
/// The [path] will be normalized: any backslashes will be replaced with
/// forward slashes (regardless of host OS) and "." and ".." will be removed
/// where possible.
/// The [path] must be relative and under [package], or an [ArgumentError] is
/// thrown.
///
/// The [path] is normalized: `\` is replaced with `/`, then `.` and `..` are removed.
AssetId(this.package, String path) : path = _normalizePath(path);

/// Creates a new [AssetId] from an [uri] String.
/// Creates an [AssetId] from a [uri].
///
/// The [uri] can be relative, in which case it will be resolved relative to
/// [from]; if [uri] is relative and [from] is not specified, an
/// [ArgumentError] is thrown.
///
/// This gracefully handles `package:` or `asset:` URIs.
/// Or, [uri] can have the scheme `package` or `asset`.
///
/// Resolve a `package:` URI when creating an [AssetId] from an `import` or
/// `export` directive pointing to a package's _lib_ directory:
/// ```dart
/// AssetId assetOfDirective(UriReferencedElement element) {
/// return new AssetId.resolve(element.uri);
/// }
/// ```
/// A `package` [uri] has the form `package:$package/$path` and references
/// the specified path within the `lib/` directory of the specified package, just like
/// a `package` URI in Dart source code.
///
/// When resolving a relative URI with no scheme, specifyg the origin asset
/// ([from]) - otherwise an [ArgumentError] will be thrown.
/// ```dart
/// AssetId assetOfDirective(AssetId origin, UriReferencedElement element) {
/// return new AssetId.resolve(element.uri, from: origin);
/// }
/// ```
/// An `asset` [uri] has the form `asset:$package/$path` and references the
/// specified path within the root of the specified package.
///
/// `asset:` uris have the format '$package/$path', including the top level
/// directory.
/// If [uri] has any other scheme then [UnsupportedError] is thrown.
factory AssetId.resolve(Uri uri, {AssetId? from}) {
final resolved =
uri.hasScheme
? uri
: from != null
? from.uri.resolveUri(uri)
: (throw ArgumentError.value(
from,
'from',
'An AssetId "from" must be specified to resolve a relative '
'URI',
));
if (from == null && !uri.hasScheme) {
throw ArgumentError.value(
from,
'from',
'An AssetId `from` must be specified to resolve a relative URI.',
);
}
final resolved = uri.hasScheme ? uri : from!.uri.resolveUri(uri);
if (resolved.scheme == 'package') {
return AssetId(
resolved.pathSegments.first,
Expand All @@ -80,46 +69,48 @@ class AssetId implements Comparable<AssetId> {
);
}
throw UnsupportedError(
'Cannot resolve $uri; only "package" and "asset" schemes supported',
'Cannot resolve $uri; only "package" and "asset" schemes supported.',
);
}

/// Parses an [AssetId] string of the form "package|path/to/asset.txt".
/// Parses an [AssetId] string of the form `$package|$path`.
///
/// The [path] will be normalized: any backslashes will be replaced with
/// forward slashes (regardless of host OS) and "." and ".." will be removed
/// where possible.
factory AssetId.parse(String description) {
final parts = description.split('|');
/// If [id] does not match that form, a [FormatException] is thrown.
///
/// See [AssetId.new] for restrictions on `path` and how it will be
/// normalized.
factory AssetId.parse(String id) {
final parts = id.split('|');
if (parts.length != 2) {
throw FormatException('Could not parse "$description".');
}

if (parts[0].isEmpty) {
throw FormatException(
'Cannot have empty package name in "$description".',
);
}

if (parts[1].isEmpty) {
throw FormatException('Cannot have empty path in "$description".');
throw FormatException('Could not parse "$id".');
}

return AssetId(parts[0], parts[1]);
}

/// A `package:` URI suitable for use directly with other systems if this
/// asset is under it's package's `lib/` directory, else an `asset:` URI
/// suitable for use within build tools.
/// If [path] starts with `lib/`, the URI starting `package:` that refers to this asset.
///
/// If not, the URI `asset:$package/$path`.
late final Uri uri = _constructUri(this);

/// Deserializes an [AssetId] from [data], which must be the result of
/// calling [serialize] on an existing [AssetId].
AssetId.deserialize(List<dynamic> data)
: package = data[0] as String,
path = data[1] as String;
/// Returns an [AssetId] in [package] with path `$path$exension`.
AssetId addExtension(String extension) => AssetId(package, '$path$extension');

/// Returns an [AssetId] in [package] with [extension] removed and
/// [newExtension] appended.
AssetId changeExtension(String newExtension) =>
AssetId(package, p.withoutExtension(path) + newExtension);

/// Deserializes a `List<dynamic>` from [serialize].
AssetId.deserialize(List<dynamic> serialized)
: package = serialized[0] as String,
path = serialized[1] as String;

/// Serializes this [AssetId] to an `Object` that can be sent across isolates.
///
/// See [AssetId.deserialize].
Object serialize() => [package, path];

/// Returns `true` if [other] is an [AssetId] with the same package and path.
@override
bool operator ==(Object other) =>
other is AssetId && package == other.package && path == other.path;
Expand All @@ -134,40 +125,27 @@ class AssetId implements Comparable<AssetId> {
return path.compareTo(other.path);
}

/// Returns a new [AssetId] with the same [package] as this one and with the
/// [path] extended to include [extension].
AssetId addExtension(String extension) => AssetId(package, '$path$extension');

/// Returns a new [AssetId] with the same [package] and [path] as this one
/// but with file extension [newExtension].
AssetId changeExtension(String newExtension) =>
AssetId(package, p.withoutExtension(path) + newExtension);

/// Returns `$package|$path`, which can be converted back to an `AssetId`
/// using [AssetId.parse].
@override
String toString() => '$package|$path';

/// Serializes this [AssetId] to an object that can be sent across isolates
/// and passed to [AssetId.deserialize].
Object serialize() => [package, path];
}

String _normalizePath(String path) {
if (p.isAbsolute(path)) {
throw ArgumentError.value(path, 'Asset paths must be relative.');
}

// Normalize path separators so that they are always "/" in the AssetID.
path = path.replaceAll(r'\', '/');

// Collapse "." and "..".
final collapsed = p.posix.normalize(path);
if (collapsed.startsWith('../')) {
final result = p.posix.normalize(path);
if (result.startsWith('../')) {
throw ArgumentError.value(
path,
'Asset paths may not reach outside the package.',
'Asset paths must be within the specified the package.',
);
}
return collapsed;
return result;
}

Uri _constructUri(AssetId id) {
Expand Down
Loading
Loading