blob: 364df75b176d6f159f9a9f66121a2511fce949bb [file] [log] [blame]
// 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 'dart:typed_data';
import 'errors.dart';
import "package_config_impl.dart";
import 'package_config_json.dart';
/// A package configuration.
///
/// Associates configuration data to packages and files in packages.
///
/// 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.
static const int maxVersion = 2;
/// An empty package configuration.
///
/// A package configuration with no available packages.
/// Is used as a default value where a package configuration
/// is expected, but none have been specified or found.
static const PackageConfig empty = SimplePackageConfig.empty();
/// Creats a package configuration with the provided available [packages].
///
/// The packages must be valid packages (valid package name, valid
/// absolute directory URIs, valid language version, if any),
/// and there must not be two packages with the same name.
///
/// The package's root ([Package.rootUri]) and package-root
/// ([Package.packageUriRoot]) paths must satisfy a number of constraints
/// We say that one path (which we know ends with a `/` charater)
/// is inside another path, if the latter path is a prefix of the former path,
/// including the two paths being the same.
///
/// * No package's root must be the same as another package's root.
/// * The package-root of a package must be inside the pacakge's root.
/// * If one package's package-root is inside another package's root,
/// then the latter package's package root must not be inside the former
/// package's root. (No getting between a package and its package root!)
/// This also disallows a package's root being the same as another
/// package's package root.
///
/// If supplied, the [extraData] will be available as the
/// [PackageConfig.extraData] of the created configuration.
///
/// The version of the resulting configuration is always [maxVersion].
factory PackageConfig(Iterable<Package> packages, {dynamic extraData}) =>
SimplePackageConfig(maxVersion, packages, extraData);
/// Parses a package configuration file.
///
/// The [bytes] must be an UTF-8 encoded JSON object
/// containing a valid package configuration.
///
/// The [baseUri] is used as the base for resolving relative
/// URI references in the configuration file. If the configuration
/// has been read from a file, the [baseUri] can be the URI of that
/// file, or of the directory it occurs in.
///
/// If [onError] is provided, errors found during parsing or building
/// the configuration are reported by calling [onError] instead of
/// throwing, and parser makes a *best effort* attempt to continue
/// despite the error. The input must still be valid JSON.
/// The result may be a [PackageConfig.empty] if there is no way to
/// extract useful information from the bytes.
static PackageConfig parseBytes(Uint8List bytes, Uri baseUri,
{void onError(Object error)}) =>
parsePackageConfigBytes(bytes, baseUri, onError ?? throwError);
/// Parses a package configuration file.
///
/// The [configuration] must be a JSON object
/// containing a valid package configuration.
///
/// The [baseUri] is used as the base for resolving relative
/// URI references in the configuration file. If the configuration
/// has been read from a file, the [baseUri] can be the URI of that
/// file, or of the directory it occurs in.
///
/// If [onError] is provided, errors found during parsing or building
/// the configuration are reported by calling [onError] instead of
/// throwing, and parser makes a *best effort* attempt to continue
/// despite the error. The input must still be valid JSON.
/// The result may be a [PackageConfig.empty] if there is no way to
/// extract useful information from the bytes.
static PackageConfig parseString(String configuration, Uri baseUri,
{void onError(Object error)}) =>
parsePackageConfigString(configuration, baseUri, onError ?? throwError);
/// Parses the JSON data of a package configuration file.
///
/// The [configuration] must be a JSON-like Dart data structure,
/// like the one provided by parsing JSON text using `dart:convert`,
/// containing a valid package configuration.
///
/// The [baseUri] is used as the base for resolving relative
/// URI references in the configuration file. If the configuration
/// has been read from a file, the [baseUri] can be the URI of that
/// file, or of the directory it occurs in.
///
/// If [onError] is provided, errors found during parsing or building
/// the configuration are reported by calling [onError] instead of
/// throwing, and parser makes a *best effort* attempt to continue
/// despite the error. The input must still be valid JSON.
/// The result may be a [PackageConfig.empty] if there is no way to
/// extract useful information from the bytes.
static PackageConfig parseJson(dynamic jsonData, Uri baseUri,
{void onError(Object error)}) =>
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<Uint8List> output,
[Uri /*?*/ baseUri]) {
writePackageConfigJsonUtf8(configuration, baseUri, output);
}
/// Writes a configuration JSON text 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 writeString(PackageConfig configuration, StringSink output,
[Uri /*?*/ baseUri]) {
writePackageConfigJsonString(configuration, baseUri, output);
}
/// Converts a configuration to a JSON-like data structure.
///
/// If [baseUri] is provided, URI references in the generated data
/// will be made relative to [baseUri] where possible.
static Map<String, dynamic> toJson(PackageConfig configuration,
[Uri /*?*/ baseUri]) =>
packageConfigToJson(configuration, baseUri);
/// The configuration version number.
///
/// Currently this is 1 or 2, where
/// * Version one is the `.packages` file format and
/// * Version two is the first `package_config.json` format.
///
/// Instances of this class supports both, and the version
/// is only useful for detecting which kind of file the configuration
/// was read from.
int get version;
/// All the available packages of this configuration.
///
/// No two of these packages have the same name,
/// and no two [Package.root] directories overlap.
Iterable<Package> get packages;
/// Look up a package by name.
///
/// Returns the [Package] fron [packages] with [packageName] as
/// [Package.name]. Returns `null` if the package is not available in the
/// current configuration.
Package /*?*/ operator [](String packageName);
/// Provides the associated package for a specific [file] (or directory).
///
/// Returns a [Package] which contains the [file]'s path, if any.
/// That is, the [Package.rootUri] directory is a parent directory
/// of the [file]'s location.
///
/// Returns `null` if the file does not belong to any package.
Package /*?*/ packageOf(Uri file);
/// Resolves a `package:` URI to a non-package URI
///
/// The [packageUri] must be a valid package URI. That means:
/// * A URI with `package` as scheme,
/// * with no authority part (`package://...`),
/// * with a path starting with a valid package name followed by a slash, and
/// * with no query or fragment part.
///
/// Throws an [ArgumentError] (which also implements [PackageConfigError])
/// if the package URI is not valid.
///
/// Returns `null` if the package name of [packageUri] is not available
/// in this package configuration.
/// Returns the remaining path of the package URI resolved relative to the
/// [Package.packageUriRoot] of the corresponding package.
Uri /*?*/ resolve(Uri packageUri);
/// The package URI which resolves to [nonPackageUri].
///
/// The [nonPackageUri] must not have any query or fragment part,
/// and it must not have `package` as scheme.
/// Throws an [ArgumentError] (which also implements [PackageConfigError])
/// if the non-package URI is not valid.
///
/// Returns a package URI which [resolve] will convert to [nonPackageUri],
/// if any such URI exists. Returns `null` if no such package URI exists.
Uri /*?*/ toPackageUri(Uri nonPackageUri);
/// Extra data associated with the package configuration.
///
/// The data may be in any format, depending on who introduced it.
/// The standard `packjage_config.json` file storage will only store
/// JSON-like list/map data structures.
dynamic get extraData;
}
/// Configuration data for a single package.
abstract class Package {
/// Creates a package with the provided properties.
///
/// The [name] must be a valid package name.
/// The [root] must be an absolute directory URI, meaning an absolute URI
/// with no query or fragment path and a path starting and ending with `/`.
/// The [packageUriRoot], if provided, must be either an absolute
/// directory URI or a relative URI reference which is then resolved
/// relative to [root]. It must then also be a subdirectory of [root],
/// or the same directory.
/// If [languageVersion] is supplied, it must be a valid Dart language
/// version, which means two decimal integer literals separated by a `.`,
/// where the integer literals have no leading zeros unless they are
/// a single zero digit.
/// 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,
dynamic extraData}) =>
SimplePackage.validate(
name, root, packageUriRoot, languageVersion, extraData, throwError);
/// The package-name of the package.
String get name;
/// The location of the root of the package.
///
/// Is always an absolute URI with no query or fragment parts,
/// and with a path ending in `/`.
///
/// All files in the [rootUri] directory are considered
/// part of the package for purposes where that that matters.
Uri get root;
/// The root of the files available through `package:` URIs.
///
/// A `package:` URI with [name] as the package name is
/// resolved relative to this location.
///
/// Is always an absolute URI with no query or fragment part
/// with a path ending in `/`,
/// and with a location which is a subdirectory
/// of the [root], or the same as the [root].
Uri get packageUriRoot;
/// The default language version associated with this package.
///
/// Each package may have a default language version associated,
/// which is the language version used to parse and compile
/// Dart files in the package.
/// A package version is defined by two non-negative numbers,
/// the *major* and *minor* version numbers.
LanguageVersion /*?*/ get languageVersion;
/// Extra data associated with the specific package.
///
/// The data may be in any format, depending on who introduced it.
/// The standard `packjage_config.json` file storage will only store
/// JSON-like list/map data structures.
dynamic get extraData;
}
/// A language version.
///
/// A language version is represented by two non-negative integers,
/// the [major] and [minor] version numbers.
///
/// 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<LanguageVersion> {
/// The maximal value allowed by [major] and [minor] values;
static const int maxValue = 0x7FFFFFFF;
factory LanguageVersion(int major, int minor) {
RangeError.checkValueInInterval(major, 0, maxValue, "major");
RangeError.checkValueInInterval(minor, 0, maxValue, "major");
return SimpleLanguageVersion(major, minor, null);
}
/// Parses a language version string.
///
/// A valid language version string has the form
///
/// > *decimalNumber* `.` *decimalNumber*
///
/// where a *decimalNumber* is a non-empty sequence of decimal digits
/// with no unnecessary leading zeros (the decimal number only starts
/// with a zero digit if that digit is the entire number).
/// No spaces are allowed in the string.
///
/// If the [source] is valid then it is parsed into a valid
/// [LanguageVersion] object.
/// If not, then the [onError] is called with a [FormatException].
/// 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 onError(Object error)}) =>
parseLanguageVersion(source, onError ?? throwError);
/// The major language version.
///
/// A non-negative integer less than 2<sup>31</sup>.
///
/// The value is negative for objects representing *invalid* language
/// versions ([InvalidLanguageVersion]).
int get major;
/// The minor language version.
///
/// A non-negative integer less than 2<sup>31</sup>.
///
/// The value is negative for objects representing *invalid* language
/// versions ([InvalidLanguageVersion]).
int get minor;
/// Compares language versions.
///
/// Two language versions are considered equal if they have the
/// same major and minor version numbers.
///
/// A language version is greater then 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.
int compareTo(LanguageVersion other);
/// Valid language versions with the same [major] and [minor] values are
/// equal.
///
/// Invalid language versions ([InvalidLanguageVersion]) are not equal to
/// any other object.
bool operator ==(Object other);
int get hashCode;
/// A string representation of the language version.
///
/// A valid language version is represented as
/// `"${version.major}.${version.minor}"`.
String toString();
}
/// An *invalid* language version.
///
/// Stored in a [Package] when the orginal 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 value -1 for an invalid language version.
int get major;
/// The value -1 for an invalid language version.
int get minor;
/// An invalid language version is only equal to itself.
bool operator ==(Object other);
int get hashCode;
/// The original invalid version string.
String toString();
}