Skip to content
Merged
Show file tree
Hide file tree
Changes from 1 commit
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