refactor(dart/transform): Compose NgDepsModel & NgMeta phases

Link `NgDeps` & `NgMeta` data in the same phase to avoid unnecessary asset
creation & reading.

Remove `NgMeta#exports` and instead use `NgDeps#exports` to link `NgMeta` files
in `ng_meta_linker.dart`.
This commit is contained in:
Tim Blasi 2015-10-01 11:05:43 -07:00
parent 75187d605b
commit 4ac29621f4
35 changed files with 821 additions and 783 deletions

View File

@ -8,16 +8,17 @@ import 'package:path/path.dart' as path;
/// If `fromAbsolute` is specified, `importPath` may be a relative path,
/// otherwise it is expected to be absolute.
String writeImportUri(String importPath, {String prefix, String fromAbsolute}) {
var urlResolver = const TransformerUrlResolver();
var codegenImportPath;
var resolver = const TransformerUrlResolver();
var importUri = resolver.toAssetScheme(Uri.parse(importPath));
var importUri =
toAssetScheme(Uri.parse(urlResolver.resolve(fromAbsolute, importPath)));
if (_canPackageImport(importUri) ||
fromAbsolute == null ||
fromAbsolute.isEmpty) {
codegenImportPath = _toPackageImport(importUri);
} else {
var moduleUri = resolver.toAssetScheme(Uri.parse(fromAbsolute));
var moduleUri = toAssetScheme(Uri.parse(fromAbsolute));
if (_canImportRelative(importUri, from: moduleUri)) {
codegenImportPath = path.url.relative(importUri.toString(),
from: path.dirname(moduleUri.toString()));

View File

@ -2,74 +2,123 @@ library angular2.transform.common.ng_meta;
import 'package:angular2/src/core/compiler/directive_metadata.dart';
import 'logging.dart';
import 'model/ng_deps_model.pb.dart';
import 'url_resolver.dart' show isDartCoreUri;
/// Metadata about directives and directive aliases.
/// Metadata about directives, directive aliases, and injectable values.
///
/// [NgMeta] is used in three stages of the transformation process. First we
/// store directive aliases exported from a single file in an [NgMeta] instance.
/// Later we use another [NgMeta] instance to store more information about a
/// single file, including both directive aliases and directives extracted from
/// the corresponding `.ng_deps.dart` file. Further down the compilation
/// process, the template compiler needs to reason about the namespace of import
/// prefixes, so it will combine multple [NgMeta] instances together if they
/// were imported into a file with the same prefix.
/// [NgMeta] is used in three stages of the transformation process:
///
/// Instances of this class are serialized into `.aliases.json` and
/// `.ng_meta.json` files as intermediate assets to make the compilation process
/// easier.
/// First we store directive aliases and types exported directly (that is, not
/// via an `export` statement) from a single file in an [NgMeta] instance.
///
/// In the second phase, we perform two actions:
/// 1. Incorporate all the data from [NgMeta] instances created by all
/// files `exported` by the original file, such that all aliases and types
/// visible when importing the original file are stored in its associated
/// [NgMeta] instance.
/// 2. Use the [NgDepsModel] to write Dart code registering all injectable
/// values with the Angular 2 runtime reflection system.
///
/// Further down the compilation process, the template compiler needs to reason
/// about the namespace of import prefixes, so it will combine multiple [NgMeta]
/// instances together if they were imported into a file with the same prefix.
///
/// Instances of this class are serialized into `.ng_meta.json` files as
/// intermediate assets to make the compilation process easier.
class NgMeta {
static const _ALIAS_VALUE = 'alias';
static const _KIND_KEY = 'kind';
static const _NG_DEPS_KEY = 'ngDeps';
static const _TYPE_VALUE = 'type';
static const _VALUE_KEY = 'value';
/// Directive metadata for each type annotated as a directive.
final Map<String, CompileDirectiveMetadata> types;
/// List of other types and names associated with a given name.
final Map<String, List<String>> aliases;
/// TODO(kegluneq): Once merged with NgDepsModel, use its exports.
final List<String> exports;
// The NgDeps generated from
final NgDepsModel ngDeps;
NgMeta(this.types, this.aliases, this.exports);
NgMeta(
{Map<String, CompileDirectiveMetadata> types,
Map<String, List<String>> aliases,
this.ngDeps: null})
: this.types = types != null ? types : {},
this.aliases = aliases != null ? aliases : {};
NgMeta.empty() : this({}, {}, []);
NgMeta.empty() : this();
bool get isEmpty => types.isEmpty && aliases.isEmpty && exports.isEmpty;
// `model` can be an `ImportModel` or `ExportModel`.
static bool _isDartImport(dynamic model) => isDartCoreUri(model.uri);
bool get isNgDepsEmpty {
if (ngDeps == null) return true;
// If this file imports only dart: libraries and does not define any
// reflectables of its own, it doesn't need a .ng_deps.dart file.
if (ngDeps.reflectables == null || ngDeps.reflectables.isEmpty) {
if ((ngDeps.imports == null || ngDeps.imports.every(_isDartImport)) &&
(ngDeps.exports == null || ngDeps.exports.every(_isDartImport))) {
return true;
}
}
return false;
}
bool get isEmpty => types.isEmpty && aliases.isEmpty && isNgDepsEmpty;
/// Parse from the serialized form produced by [toJson].
factory NgMeta.fromJson(Map json) {
var exports = <String>[];
var types = {};
var aliases = {};
var ngDeps = null;
final types = {};
final aliases = {};
for (var key in json.keys) {
if (key == '__exports__') {
exports = json[key];
if (key == _NG_DEPS_KEY) {
var ngDepsJsonMap = json[key];
if (ngDepsJsonMap == null) continue;
if (ngDepsJsonMap is! Map) {
logger.warning(
'Unexpected value $ngDepsJsonMap for key "$key" in NgMeta.');
continue;
}
ngDeps = new NgDepsModel()..mergeFromJsonMap(ngDepsJsonMap);
} else {
var entry = json[key];
if (entry['kind'] == 'type') {
types[key] = CompileDirectiveMetadata.fromJson(entry['value']);
} else if (entry['kind'] == 'alias') {
aliases[key] = entry['value'];
if (entry is! Map) {
logger.warning('Unexpected value $entry for key "$key" in NgMeta.');
continue;
}
if (entry[_KIND_KEY] == _TYPE_VALUE) {
types[key] = CompileDirectiveMetadata.fromJson(entry[_VALUE_KEY]);
} else if (entry[_KIND_KEY] == _ALIAS_VALUE) {
aliases[key] = entry[_VALUE_KEY];
}
}
}
return new NgMeta(types, aliases, exports);
return new NgMeta(types: types, aliases: aliases, ngDeps: ngDeps);
}
/// Serialized representation of this instance.
Map toJson() {
Map toJson({bool withNgDeps: true}) {
var result = {};
result['__exports__'] = exports;
if (withNgDeps) {
result[_NG_DEPS_KEY] = isNgDepsEmpty ? null : ngDeps.writeToJsonMap();
}
types.forEach((k, v) {
result[k] = {'kind': 'type', 'value': v.toJson()};
result[k] = {_KIND_KEY: _TYPE_VALUE, _VALUE_KEY: v.toJson()};
});
aliases.forEach((k, v) {
result[k] = {'kind': 'alias', 'value': v};
result[k] = {_KIND_KEY: _ALIAS_VALUE, _VALUE_KEY: v};
});
return result;
}
/// Merge into this instance all information from [other].
/// This does not include `exports`.
/// This does not include `ngDeps`.
void addAll(NgMeta other) {
types.addAll(other.types);
aliases.addAll(other.aliases);

View File

@ -5,9 +5,6 @@ import 'package:glob/glob.dart';
import 'annotation_matcher.dart';
import 'mirror_mode.dart';
/// See `optimizationPhases` below for an explanation.
const DEFAULT_OPTIMIZATION_PHASES = 5;
const CUSTOM_ANNOTATIONS_PARAM = 'custom_annotations';
const ENTRY_POINT_PARAM = 'entry_points';
const FORMAT_CODE_PARAM = 'format_code';
@ -41,15 +38,6 @@ class TransformerOptions {
final bool reflectPropertiesAsAttributes;
/// The number of phases to spend optimizing output size.
/// Each additional phase adds time to the transformation but may decrease
/// final output size. There is a limit beyond which this will no longer
/// decrease size, that is, setting this to 20 may not decrease size any
/// more than setting it to 10, but you will still pay an additional
/// penalty in transformation time.
/// The "correct" number of phases varies with the structure of the app.
final int optimizationPhases;
/// Whether to format generated code.
/// Code that is only modified will never be formatted because doing so may
/// invalidate the source maps generated by `dart2js` and/or other tools.
@ -62,7 +50,6 @@ class TransformerOptions {
this.mirrorMode,
this.initReflector,
this.annotationMatcher,
this.optimizationPhases,
{this.reflectPropertiesAsAttributes,
this.formatCode});
@ -71,24 +58,16 @@ class TransformerOptions {
MirrorMode mirrorMode: MirrorMode.none,
bool initReflector: true,
List<ClassDescriptor> customAnnotationDescriptors: const [],
int optimizationPhases: DEFAULT_OPTIMIZATION_PHASES,
bool inlineViews: true,
bool reflectPropertiesAsAttributes: true,
bool formatCode: false}) {
var annotationMatcher = new AnnotationMatcher()
..addAll(customAnnotationDescriptors);
optimizationPhases = optimizationPhases.isNegative ? 0 : optimizationPhases;
var entryPointGlobs = entryPoints != null
? entryPoints.map((path) => new Glob(path)).toList(growable: false)
: null;
return new TransformerOptions._internal(
entryPoints,
entryPointGlobs,
modeName,
mirrorMode,
initReflector,
annotationMatcher,
optimizationPhases,
return new TransformerOptions._internal(entryPoints, entryPointGlobs,
modeName, mirrorMode, initReflector, annotationMatcher,
reflectPropertiesAsAttributes: reflectPropertiesAsAttributes,
formatCode: formatCode);
}

View File

@ -28,14 +28,11 @@ TransformerOptions parseBarbackSettings(BarbackSettings settings) {
mirrorMode = MirrorMode.none;
break;
}
var optimizationPhases = _readInt(config, OPTIMIZATION_PHASES_PARAM,
defaultValue: DEFAULT_OPTIMIZATION_PHASES);
return new TransformerOptions(entryPoints,
modeName: settings.mode.name,
mirrorMode: mirrorMode,
initReflector: initReflector,
customAnnotationDescriptors: _readCustomAnnotations(config),
optimizationPhases: optimizationPhases,
reflectPropertiesAsAttributes: reflectPropertiesAsAttributes,
formatCode: formatCode);
}
@ -68,18 +65,6 @@ List<String> _readFileList(Map config, String paramName) {
return files;
}
int _readInt(Map config, String paramName, {int defaultValue: null}) {
if (!config.containsKey(paramName)) return defaultValue;
var value = config[paramName];
if (value is String) {
value = int.parse(value);
}
if (value is! int) {
throw new ArgumentError.value(value, paramName, 'Expected an integer');
}
return value;
}
/// Parse the [CUSTOM_ANNOTATIONS_PARAM] options out of the transformer into
/// [ClassDescriptor]s.
List<ClassDescriptor> _readCustomAnnotations(Map config) {

View File

@ -1,57 +1,83 @@
library angular2.transform.template_compiler.url_resolver;
import 'package:angular2/src/core/compiler/url_resolver.dart';
import 'package:angular2/src/core/services.dart';
import 'package:barback/barback.dart';
class TransformerUrlResolver implements UrlResolver {
const TransformerUrlResolver();
@override
String resolve(String baseUrl, String url) {
if (url == null) throw new ArgumentError.notNull('url');
Uri uri = Uri.parse(url);
if (!uri.isAbsolute) {
if (baseUrl == null) throw new ArgumentError.notNull('baseUrl');
if (baseUrl.isEmpty) throw new ArgumentError.value(
'(empty string)', 'baseUrl');
uri = Uri.parse(baseUrl).resolveUri(uri);
}
return toAssetScheme(uri).toString();
}
}
/// Converts `absoluteUri` to use the 'asset' scheme used in the Angular 2
/// template compiler.
///
/// The `scheme` of `absoluteUri` is expected to be either 'package' or
/// 'asset'.
Uri toAssetScheme(Uri absoluteUri) {
if (absoluteUri == null) return null;
String toAssetUri(AssetId assetId) {
if (assetId == null) throw new ArgumentError.notNull('assetId');
return 'asset:${assetId.package}/${assetId.path}';
}
if (!absoluteUri.isAbsolute) {
throw new ArgumentError.value(
absoluteUri, 'absoluteUri', 'Value passed must be an absolute uri');
}
if (absoluteUri.scheme == 'asset') {
if (absoluteUri.pathSegments.length < 3) {
throw new FormatException(
'An asset: URI must have at least 3 path '
'segments, for example '
'asset:<package-name>/<first-level-dir>/<path-to-dart-file>.',
absoluteUri);
}
return absoluteUri;
}
if (absoluteUri.scheme != 'package') {
throw new ArgumentError.value(
absoluteUri, 'absoluteUri', 'Unsupported URI scheme encountered');
}
AssetId fromUri(String assetUri) {
if (assetUri == null) throw new ArgumentError.notNull('assetUri');
if (assetUri.isEmpty) throw new ArgumentError.value(
'(empty string)', 'assetUri');
var uri = toAssetScheme(Uri.parse(assetUri));
return new AssetId(
uri.pathSegments.first, uri.pathSegments.skip(1).join('/'));
}
if (absoluteUri.pathSegments.length < 2) {
/// Converts `absoluteUri` to use the 'asset' scheme used in the Angular 2
/// template compiler.
///
/// The `scheme` of `absoluteUri` is expected to be either 'package' or
/// 'asset'.
Uri toAssetScheme(Uri absoluteUri) {
if (absoluteUri == null) throw new ArgumentError.notNull('absoluteUri');
if (!absoluteUri.isAbsolute) {
throw new ArgumentError.value(
absoluteUri, 'absoluteUri', 'Value passed must be an absolute uri');
}
if (absoluteUri.scheme == 'asset') {
if (absoluteUri.pathSegments.length < 3) {
throw new FormatException(
'A package: URI must have at least 2 path '
'An asset: URI must have at least 3 path '
'segments, for example '
'package:<package-name>/<path-to-dart-file>',
'asset:<package-name>/<first-level-dir>/<path-to-dart-file>.',
absoluteUri);
}
var pathSegments = absoluteUri.pathSegments.toList()..insert(1, 'lib');
return new Uri(scheme: 'asset', pathSegments: pathSegments);
return absoluteUri;
}
if (absoluteUri.scheme != 'package') {
throw new FormatException(
'Unsupported URI scheme "${absoluteUri.scheme}" encountered.',
absoluteUri);
}
if (absoluteUri.pathSegments.length < 2) {
throw new FormatException(
'A package: URI must have at least 2 path '
'segments, for example '
'package:<package-name>/<path-to-dart-file>',
absoluteUri);
}
var pathSegments = absoluteUri.pathSegments.toList()..insert(1, 'lib');
return new Uri(scheme: 'asset', pathSegments: pathSegments);
}
bool isDartCoreUri(String uri) {
if (uri == null) throw new ArgumentError.notNull('uri');
if (uri.isEmpty) throw new ArgumentError.value('(empty string)', 'uri');
return uri.startsWith('dart:');
}

View File

@ -3,20 +3,25 @@ library angular2.transform.template_compiler.xhr_impl;
import 'dart:async';
import 'package:angular2/src/core/compiler/xhr.dart' show XHR;
import 'package:angular2/src/transform/common/asset_reader.dart';
import 'package:barback/barback.dart';
import 'package:angular2/src/transform/common/logging.dart';
import 'package:angular2/src/transform/common/url_resolver.dart';
/// Transformer-specific implementation of XHR that is backed by an
/// [AssetReader].
///
/// This implementation expects urls using the asset: scheme.
/// See [src/transform/common/url_resolver.dart] for a way to convert package:
/// and relative urls to asset: urls.
class XhrImpl implements XHR {
final AssetReader _reader;
XhrImpl(this._reader);
Future<String> get(String url) async {
final uri = Uri.parse(url);
if (uri.scheme != 'asset') {
throw new FormatException('Unsupported uri encountered: $uri', url);
final assetId = fromUri(url);
if (!url.startsWith('asset:')) {
logger.warning('XhrImpl received unexpected url: $url');
}
final assetId =
new AssetId(uri.pathSegments.first, uri.pathSegments.skip(1).join('/'));
if (!await _reader.hasInput(assetId)) {
throw new ArgumentError.value('Could not read asset at uri $url', 'url');

View File

@ -1,118 +0,0 @@
library angular2.transform.directive_linker.linker;
import 'dart:async';
import 'package:angular2/src/transform/common/asset_reader.dart';
import 'package:angular2/src/transform/common/logging.dart';
import 'package:angular2/src/transform/common/names.dart';
import 'package:angular2/src/transform/common/model/import_export_model.pb.dart';
import 'package:angular2/src/transform/common/model/ng_deps_model.pb.dart';
import 'package:barback/barback.dart';
import 'package:code_transformers/assets.dart';
/// Checks the `.ng_deps.json` file represented by `entryPoint` and
/// determines whether it is necessary to the functioning of the Angular 2
/// Dart app.
///
/// An `.ng_deps.json` file is not necessary if:
/// 1. It does not register any `@Injectable` types with the system.
/// 2. It does not import any libraries whose `.ng_deps.json` files register
/// any `@Injectable` types with the system.
///
/// Since `@Directive` and `@Component` inherit from `@Injectable`, we know
/// we will not miss processing any classes annotated with those tags.
Future<bool> isNecessary(AssetReader reader, AssetId entryPoint) async {
if (!(await reader.hasInput(entryPoint))) return false;
var jsonString = await reader.readAsString(entryPoint);
if (jsonString == null || jsonString.isEmpty) return false;
var ngDepsModel = new NgDepsModel.fromJson(jsonString);
if (ngDepsModel.reflectables != null &&
ngDepsModel.reflectables.isNotEmpty) return true;
// We do not register any @Injectables, do we call any dependencies?
var linkedDepsMap = await _processNgImports(reader, entryPoint, ngDepsModel);
return linkedDepsMap.isNotEmpty;
}
/// Modifies the [NgDepsModel] represented by `entryPoint` to import its
/// dependencies' associated `.ng_deps.dart` files.
///
/// For example, if entry_point.ng_deps.dart imports dependency.dart, this
/// will check if dependency.ng_deps.json exists. If it does, we add an import
/// to dependency.ng_deps.dart to the entry_point [NgDepsModel] and set
/// `isNgDeps` to `true` to signify that it is a dependency on which we need to
/// call `initReflector`.
Future<NgDepsModel> linkNgDeps(AssetReader reader, AssetId entryPoint) async {
if (!(await reader.hasInput(entryPoint))) return null;
var jsonString = await reader.readAsString(entryPoint);
if (jsonString.isEmpty) return null;
var ngDepsModel = new NgDepsModel.fromJson(jsonString);
var linkedDepsMap = await _processNgImports(reader, entryPoint, ngDepsModel);
if (linkedDepsMap.isEmpty) {
// We are not calling `initReflector` on any other libraries, but we still
// return the model to ensure it is written to code.
// TODO(kegluneq): Continue using the protobuf format after this phase.
return ngDepsModel;
}
for (var i = ngDepsModel.imports.length - 1; i >= 0; --i) {
var import = ngDepsModel.imports[i];
if (linkedDepsMap.containsKey(import.uri)) {
var linkedModel = new ImportModel()
..isNgDeps = true
..uri = toDepsExtension(linkedDepsMap[import.uri])
..prefix = 'i$i';
// TODO(kegluneq): Preserve combinators?
ngDepsModel.imports.insert(i + 1, linkedModel);
}
}
for (var i = 0, iLen = ngDepsModel.exports.length; i < iLen; ++i) {
var export = ngDepsModel.exports[i];
if (linkedDepsMap.containsKey(export.uri)) {
var linkedModel = new ImportModel()
..isNgDeps = true
..uri = toDepsExtension(linkedDepsMap[export.uri])
..prefix = 'i${ngDepsModel.imports.length}';
// TODO(kegluneq): Preserve combinators?
ngDepsModel.imports.add(linkedModel);
}
}
return ngDepsModel;
}
bool _isNotDartDirective(dynamic model) {
return !model.uri.startsWith('dart:');
}
/// Maps the `uri` of each input [ImportModel] or [ExportModel] to its
/// associated `.ng_deps.json` file, if one exists.
Future<Map<String, String>> _processNgImports(
AssetReader reader, AssetId ngJsonAsset, NgDepsModel model) {
final nullFuture = new Future.value(null);
final importsAndExports = new List.from(model.imports)..addAll(model.exports);
final retVal = <String, String>{};
final entryPoint =
new AssetId(ngJsonAsset.package, toDepsExtension(ngJsonAsset.path));
return Future
.wait(
importsAndExports.where(_isNotDartDirective).map((dynamic directive) {
// The uri of the import or export with .dart replaced with .ng_deps.json.
// This is the json file containing Angular 2 codegen info, if one exists.
var linkedJsonUri = toJsonExtension(directive.uri);
var spanArg = null;
var linkedNgJsonAsset = uriToAssetId(
entryPoint, linkedJsonUri, logger, spanArg,
errorOnAbsolute: false);
if (linkedNgJsonAsset == ngJsonAsset) return nullFuture;
return reader.hasInput(linkedNgJsonAsset).then((hasInput) {
if (hasInput) {
retVal[directive.uri] = linkedJsonUri;
}
}, onError: (_) => null);
}))
.then((_) => retVal);
}

View File

@ -1,84 +0,0 @@
library angular2.transform.directive_linker.transformer;
import 'dart:async';
import 'package:angular2/src/transform/common/asset_reader.dart';
import 'package:angular2/src/transform/common/code/ng_deps_code.dart';
import 'package:angular2/src/transform/common/formatter.dart';
import 'package:angular2/src/transform/common/logging.dart' as log;
import 'package:angular2/src/transform/common/names.dart';
import 'package:barback/barback.dart';
import 'linker.dart';
/// Transformer responsible for processing `.ng_deps.json` files created by
/// {@link DirectiveProcessor} and ensuring that each imports its dependencies'
/// .ng_deps.dart files.
class DirectiveLinker extends Transformer implements DeclaringTransformer {
DirectiveLinker();
@override
bool isPrimary(AssetId id) => id.path.endsWith(DEPS_JSON_EXTENSION);
@override
declareOutputs(DeclaringTransform transform) {
// TODO(kegluenq): We should consume this, but doing so causes barback to
// incorrectly determine what assets are available in this phase.
// transform.consumePrimary();
transform.declareOutput(_depsAssetId(transform.primaryId));
}
@override
Future apply(Transform transform) async {
await log.initZoned(transform, () async {
var reader = new AssetReader.fromTransform(transform);
var primaryId = transform.primaryInput.id;
var ngDepsModel = await linkNgDeps(reader, primaryId);
// See above
// transform.consumePrimary();
var outputAssetId = _depsAssetId(primaryId);
if (ngDepsModel != null) {
var buf = new StringBuffer();
var writer = new NgDepsWriter(buf);
writer.writeNgDepsModel(ngDepsModel);
var formattedCode = formatter.format('$buf', uri: primaryId.path);
transform.addOutput(new Asset.fromString(outputAssetId, formattedCode));
} else {
transform.addOutput(new Asset.fromString(outputAssetId, ''));
}
});
}
}
AssetId _depsAssetId(AssetId primaryId) =>
new AssetId(primaryId.package, toDepsExtension(primaryId.path));
/// Transformer responsible for removing unnecessary `.ng_deps.json` files
/// created by {@link DirectiveProcessor}.
class EmptyNgDepsRemover extends Transformer implements DeclaringTransformer {
EmptyNgDepsRemover();
@override
bool isPrimary(AssetId id) => id.path.endsWith(DEPS_JSON_EXTENSION);
/// We occasionally consume the primary input, but that depends on the
/// contents of the file, so we conservatively declare that we both consume
/// and output the asset. This prevents barback from making any assumptions
/// about the existence of the assets until after the transformer has run.
@override
declareOutputs(DeclaringTransform transform) {
transform.consumePrimary();
transform.declareOutput(transform.primaryId);
}
@override
Future apply(Transform transform) async {
await log.initZoned(transform, () async {
var reader = new AssetReader.fromTransform(transform);
transform.consumePrimary();
if ((await isNecessary(reader, transform.primaryInput.id))) {
transform.addOutput(transform.primaryInput);
}
});
}
}

View File

@ -1,78 +0,0 @@
library angular2.transform.directive_metadata_linker.linker;
import 'dart:async';
import 'dart:convert';
import 'package:angular2/src/transform/common/asset_reader.dart';
import 'package:angular2/src/transform/common/logging.dart';
import 'package:angular2/src/transform/common/names.dart';
import 'package:angular2/src/transform/common/ng_meta.dart';
import 'package:barback/barback.dart';
import 'package:code_transformers/assets.dart';
import 'package:path/path.dart' as path;
/// Returns [NgMeta] associated with [entryPoint] combined with the [NgMeta] of
/// all files `export`ed from the original file.
///
/// This includes entries for every `Directive`-annotated class and
/// constants that match the directive-aliases pattern.
///
/// There are entries for each of these which is visible from a file importing
/// the original .dart file that produced `entryPoint`. That is, this includes
/// all `Directive` annotated public classes in that file, all `DirectiveAlias`
/// annotated public variables, and any of those entries which are visible from
/// files which the .dart file `export`ed.
///
/// Returns an empty [NgMeta] if there are no `Directive`-annotated classes or
/// `DirectiveAlias` annotated constants in `entryPoint`.
Future<NgMeta> linkDirectiveMetadata(AssetReader reader, AssetId entryPoint) {
return _linkDirectiveMetadataRecursive(
reader, entryPoint, new Set<AssetId>());
}
final _nullFuture = new Future.value(null);
// TODO(kegluneq): Don't reinvent the wheel? Centalize?
AssetId _fromPackageUri(String packageUri) {
var pathParts = path.url.split(packageUri);
return new AssetId(pathParts[0].substring('package:'.length),
'lib/${pathParts.getRange(1, pathParts.length).join('/')}');
}
Future<NgMeta> _linkDirectiveMetadataRecursive(
AssetReader reader, AssetId entryPoint, Set<AssetId> seen) async {
if (entryPoint == null) {
return new NgMeta.empty();
}
// Break cycles, if they exist.
if (seen.contains(entryPoint)) return _nullFuture;
seen.add(entryPoint);
if (!(await reader.hasInput(entryPoint))) return new NgMeta.empty();
var ngMetaJson = await reader.readAsString(entryPoint);
if (ngMetaJson == null || ngMetaJson.isEmpty) return new NgMeta.empty();
var ngMeta = new NgMeta.fromJson(JSON.decode(ngMetaJson));
if (ngMeta.exports == null) return ngMeta;
// Recursively add NgMeta files from `exports`.
return Future.wait(ngMeta.exports.map((uri) {
if (uri.startsWith('dart:')) return _nullFuture;
var metaUri = toMetaExtension(uri);
var assetId;
if (uri.startsWith('package:')) {
assetId = _fromPackageUri(metaUri);
} else {
assetId = uriToAssetId(entryPoint, metaUri, logger, null /* span */,
errorOnAbsolute: false);
}
return _linkDirectiveMetadataRecursive(reader, assetId, seen)
.then((exportedNgMeta) {
if (exportedNgMeta != null) {
ngMeta.addAll(exportedNgMeta);
}
});
})).then((_) => ngMeta);
}

View File

@ -0,0 +1,88 @@
library angular2.transform.directive_metadata_linker.ng_deps_linker;
import 'dart:async';
import 'package:angular2/src/core/services.dart';
import 'package:angular2/src/transform/common/asset_reader.dart';
import 'package:angular2/src/transform/common/logging.dart';
import 'package:angular2/src/transform/common/names.dart';
import 'package:angular2/src/transform/common/model/import_export_model.pb.dart';
import 'package:angular2/src/transform/common/model/ng_deps_model.pb.dart';
import 'package:angular2/src/transform/common/url_resolver.dart';
import 'package:barback/barback.dart';
/// Modifies the [NgDepsModel] represented by `entryPoint` to import its
/// dependencies' associated `.ng_deps.dart` files.
///
/// For example, if entry_point.ng_deps.dart imports dependency.dart, this
/// will check if dependency.ng_meta.json exists. If it does, we add an import
/// to dependency.ng_deps.dart to the entry_point [NgDepsModel] and set
/// `isNgDeps` to `true` to signify that it is a dependency on which we need to
/// call `initReflector`.
Future<NgDepsModel> linkNgDeps(NgDepsModel ngDepsModel, AssetReader reader,
AssetId entryPoint, UrlResolver resolver) async {
if (ngDepsModel == null) return null;
var linkedDepsMap =
await _processNgImports(ngDepsModel, reader, entryPoint, resolver);
if (linkedDepsMap.isEmpty) {
// We are not calling `initReflector` on any other libraries, but we still
// return the model to ensure it is written to code.
// TODO(kegluneq): Continue using the protobuf format after this phase.
return ngDepsModel;
}
for (var i = ngDepsModel.imports.length - 1; i >= 0; --i) {
var import = ngDepsModel.imports[i];
if (linkedDepsMap.containsKey(import.uri)) {
var linkedModel = new ImportModel()
..isNgDeps = true
..uri = toDepsExtension(import.uri)
..prefix = 'i$i';
// TODO(kegluneq): Preserve combinators?
ngDepsModel.imports.insert(i + 1, linkedModel);
}
}
for (var i = 0, iLen = ngDepsModel.exports.length; i < iLen; ++i) {
var export = ngDepsModel.exports[i];
if (linkedDepsMap.containsKey(export.uri)) {
var linkedModel = new ImportModel()
..isNgDeps = true
..uri = toDepsExtension(export.uri)
..prefix = 'i${ngDepsModel.imports.length}';
// TODO(kegluneq): Preserve combinators?
ngDepsModel.imports.add(linkedModel);
}
}
return ngDepsModel;
}
bool _isNotDartDirective(dynamic model) => !isDartCoreUri(model.uri);
/// Maps the `uri` of each input [ImportModel] or [ExportModel] to its
/// associated `.ng_deps.json` file, if one exists.
Future<Map<String, String>> _processNgImports(NgDepsModel model,
AssetReader reader, AssetId assetId, UrlResolver resolver) async {
final importsAndExports = new List.from(model.imports)..addAll(model.exports);
final retVal = <String, String>{};
final assetUri = toAssetUri(assetId);
return Future
.wait(
importsAndExports.where(_isNotDartDirective).map((dynamic directive) {
// The uri of the import or export with .dart replaced with .ng_meta.json.
// This is the json file containing Angular 2 codegen info, if one exists.
var linkedJsonUri =
resolver.resolve(assetUri, toMetaExtension(directive.uri));
return reader.hasInput(fromUri(linkedJsonUri)).then((hasInput) {
if (hasInput) {
retVal[directive.uri] = linkedJsonUri;
}
}, onError: (err, stack) {
logger.warning('Error while looking for $linkedJsonUri. '
'Message: $err\n'
'Stack: $stack');
});
}))
.then((_) => retVal);
}

View File

@ -0,0 +1,88 @@
library angular2.transform.directive_metadata_linker.linker;
import 'dart:async';
import 'dart:convert';
import 'package:angular2/src/transform/common/asset_reader.dart';
import 'package:angular2/src/transform/common/logging.dart';
import 'package:angular2/src/transform/common/names.dart';
import 'package:angular2/src/transform/common/ng_meta.dart';
import 'package:angular2/src/transform/common/url_resolver.dart';
import 'package:barback/barback.dart';
import 'ng_deps_linker.dart';
/// Returns [NgMeta] associated with [entryPoint] combined with the [NgMeta] of
/// all files `export`ed from the original file.
///
/// This includes entries for every `Directive`-annotated class and
/// constants that match the directive-aliases pattern.
///
/// There are entries for each of these which is visible from a file importing
/// the original .dart file that produced `entryPoint`. That is, this includes
/// all `Directive` annotated public classes in that file, all `DirectiveAlias`
/// annotated public variables, and any of those entries which are visible from
/// files which the .dart file `export`ed.
///
/// Returns an empty [NgMeta] if there are no `Directive`-annotated classes or
/// `DirectiveAlias` annotated constants in `entryPoint`.
Future<NgMeta> linkDirectiveMetadata(
AssetReader reader, AssetId entryPoint) async {
var ngMeta = await _readNgMeta(reader, entryPoint);
if (ngMeta == null || ngMeta.isEmpty) return null;
await Future.wait([
linkNgDeps(ngMeta.ngDeps, reader, entryPoint, _urlResolver),
_linkDirectiveMetadataRecursive(
ngMeta, reader, entryPoint, new Set<String>())
]);
return ngMeta;
}
Future<NgMeta> _readNgMeta(AssetReader reader, AssetId ngMetaAssetId) async {
if (!(await reader.hasInput(ngMetaAssetId))) return null;
var ngMetaJson = await reader.readAsString(ngMetaAssetId);
if (ngMetaJson == null || ngMetaJson.isEmpty) return null;
return new NgMeta.fromJson(JSON.decode(ngMetaJson));
}
final _urlResolver = const TransformerUrlResolver();
Future<NgMeta> _linkDirectiveMetadataRecursive(NgMeta ngMeta,
AssetReader reader, AssetId assetId, Set<String> seen) async {
if (ngMeta == null ||
ngMeta.ngDeps == null ||
ngMeta.ngDeps.exports == null) {
return ngMeta;
}
var assetUri = toAssetUri(assetId);
return Future
.wait(ngMeta.ngDeps.exports
.where((export) => !isDartCoreUri(export.uri))
.map((export) =>
_urlResolver.resolve(assetUri, toMetaExtension(export.uri)))
.where((uri) => !seen.contains(uri))
.map((uri) async {
seen.add(uri);
try {
final exportAssetId = fromUri(uri);
if (await reader.hasInput(exportAssetId)) {
var exportNgMetaJson = await reader.readAsString(exportAssetId);
if (exportNgMetaJson == null) return null;
var exportNgMeta = new NgMeta.fromJson(JSON.decode(exportNgMetaJson));
await _linkDirectiveMetadataRecursive(
exportNgMeta, reader, exportAssetId, seen);
if (exportNgMeta != null) {
ngMeta.addAll(exportNgMeta);
}
}
} catch (err, st) {
// Log and continue.
logger.warning('Failed to fetch $uri. Message: $err.\n$st');
}
}))
.then((_) => ngMeta);
}

View File

@ -4,11 +4,13 @@ import 'dart:async';
import 'dart:convert';
import 'package:angular2/src/transform/common/asset_reader.dart';
import 'package:angular2/src/transform/common/code/ng_deps_code.dart';
import 'package:angular2/src/transform/common/formatter.dart';
import 'package:angular2/src/transform/common/logging.dart' as log;
import 'package:angular2/src/transform/common/names.dart';
import 'package:barback/barback.dart';
import 'linker.dart';
import 'ng_meta_linker.dart';
/// Transformer responsible for processing .ng_meta.json files created by
/// {@link DirectiveProcessor} and "linking" them.
@ -31,6 +33,7 @@ class DirectiveMetadataLinker extends Transformer
// incorrectly determine what assets are available in this phase.
// transform.consumePrimary();
transform.declareOutput(transform.primaryId);
transform.declareOutput(_depsAssetId(transform.primaryId));
}
@override
@ -42,15 +45,32 @@ class DirectiveMetadataLinker extends Transformer
new AssetReader.fromTransform(transform), primaryId).then((ngMeta) {
// See above
// transform.consumePrimary();
if (ngMeta != null && !ngMeta.isEmpty) {
transform.addOutput(new Asset.fromString(
primaryId, _encoder.convert(ngMeta.toJson())));
} else {
// Not outputting an asset could confuse barback, so output an
// empty one.
transform.addOutput(transform.primaryInput);
if (ngMeta != null) {
if (!ngMeta.types.isEmpty || !ngMeta.aliases.isEmpty) {
transform.addOutput(new Asset.fromString(
primaryId, _encoder.convert(ngMeta.toJson(withNgDeps: false))));
} else {
// Not outputting an asset could confuse barback.
transform.addOutput(new Asset.fromString(primaryId, ''));
}
var depsAssetId = _depsAssetId(primaryId);
if (!ngMeta.isNgDepsEmpty) {
var buf = new StringBuffer();
var writer = new NgDepsWriter(buf);
writer.writeNgDepsModel(ngMeta.ngDeps);
var formattedCode =
formatter.format(buf.toString(), uri: depsAssetId.path);
transform
.addOutput(new Asset.fromString(depsAssetId, formattedCode));
} else {
transform.addOutput(new Asset.fromString(depsAssetId, ''));
}
}
});
});
}
}
AssetId _depsAssetId(AssetId primaryId) =>
new AssetId(primaryId.package, toDepsExtension(primaryId.path));

View File

@ -9,7 +9,6 @@ import 'package:angular2/src/transform/common/code/ng_deps_code.dart';
import 'package:angular2/src/transform/common/directive_metadata_reader.dart';
import 'package:angular2/src/transform/common/interface_matcher.dart';
import 'package:angular2/src/transform/common/logging.dart';
import 'package:angular2/src/transform/common/model/ng_deps_model.pb.dart';
import 'package:angular2/src/transform/common/ng_compiler.dart';
import 'package:angular2/src/transform/common/ng_meta.dart';
import 'package:barback/barback.dart' show AssetId;
@ -17,26 +16,20 @@ import 'package:angular2/src/core/compiler/template_compiler.dart';
import 'inliner.dart';
/// Generates a file registering all Angular 2 `Directive`s found in `code` in
/// ngDeps format [TODO(kegluneq): documentation reference needed]. `assetId` is
/// the id of the asset containing `code`.
///
/// If no Angular 2 `Directive`s are found in `code`, returns the empty
/// string unless `forceGenerate` is true, in which case an empty ngDeps
/// file is created.
Future<NgDepsModel> createNgDeps(AssetReader reader, AssetId assetId,
AnnotationMatcher annotationMatcher, NgMeta ngMeta) async {
/// Generates an instance of [NgMeta] describing the file at `assetId`.
Future<NgMeta> createNgDeps(AssetReader reader, AssetId assetId,
AnnotationMatcher annotationMatcher) async {
// TODO(kegluneq): Shortcut if we can determine that there are no
// [Directive]s present, taking into account `export`s.
var codeWithParts = await inlineParts(reader, assetId);
if (codeWithParts == null || codeWithParts.isEmpty) return null;
var parsedCode =
parseCompilationUnit(codeWithParts, name: '${assetId.path} and parts');
var ngDepsVisitor = new NgDepsVisitor(assetId, annotationMatcher);
parsedCode.accept(ngDepsVisitor);
var ngDepsModel = ngDepsVisitor.model;
var ngMeta = new NgMeta(ngDeps: ngDepsVisitor.model);
var templateCompiler = createTemplateCompiler(reader);
var ngMetaVisitor = new _NgMetaVisitor(
@ -44,21 +37,9 @@ Future<NgDepsModel> createNgDeps(AssetReader reader, AssetId assetId,
parsedCode.accept(ngMetaVisitor);
await ngMetaVisitor.whenDone();
// If this file imports only dart: libraries and does not define any
// reflectables of its own, it doesn't need a .ng_deps.dart file.
if (ngDepsModel.reflectables == null || ngDepsModel.reflectables.isEmpty) {
if (ngDepsModel.imports.every(_isDartImport) &&
ngDepsModel.exports.every(_isDartImport)) {
return null;
}
}
return ngDepsModel;
return ngMeta;
}
// `model` can be an [ImportModel] or [ExportModel].
bool _isDartImport(dynamic model) => model.uri.startsWith('dart:');
// TODO(kegluneq): Allow the caller to provide an InterfaceMatcher.
final _interfaceMatcher = new InterfaceMatcher();
@ -96,12 +77,6 @@ class _NgMetaVisitor extends Object with SimpleAstVisitor<Object> {
return node.declarations.accept(this);
}
@override
Object visitExportDirective(ExportDirective node) {
ngMeta.exports.add(stringLiteralToString(node.uri));
return null;
}
@override
Object visitClassDeclaration(ClassDeclaration node) {
_normalizations.add(_reader

View File

@ -8,7 +8,6 @@ import 'package:angular2/src/transform/common/asset_reader.dart';
import 'package:angular2/src/transform/common/logging.dart' as log;
import 'package:angular2/src/transform/common/names.dart';
import 'package:angular2/src/transform/common/options.dart';
import 'package:angular2/src/transform/common/ng_meta.dart';
import 'package:barback/barback.dart';
import 'rewriter.dart';
@ -37,7 +36,6 @@ class DirectiveProcessor extends Transformer implements DeclaringTransformer {
@override
declareOutputs(DeclaringTransform transform) {
transform.declareOutput(_ngMetaAssetId(transform.primaryId));
transform.declareOutput(_ngDepsAssetId(transform.primaryId));
}
@override
@ -46,20 +44,13 @@ class DirectiveProcessor extends Transformer implements DeclaringTransformer {
await log.initZoned(transform, () async {
var primaryId = transform.primaryInput.id;
var reader = new AssetReader.fromTransform(transform);
var ngMeta = new NgMeta.empty();
var ngDepsModel = await createNgDeps(
reader, primaryId, options.annotationMatcher, ngMeta);
// TODO(kegluneq): Combine NgDepsModel with NgMeta in a single .json file.
if (ngDepsModel != null) {
var ngDepsAssetId = _ngDepsAssetId(primaryId);
transform.addOutput(new Asset.fromString(
ngDepsAssetId, _encoder.convert(ngDepsModel.writeToJsonMap())));
}
var metaOutputId = _ngMetaAssetId(primaryId);
if (!ngMeta.isEmpty) {
transform.addOutput(new Asset.fromString(
metaOutputId, _encoder.convert(ngMeta.toJson())));
var ngMeta =
await createNgDeps(reader, primaryId, options.annotationMatcher);
if (ngMeta == null || ngMeta.isEmpty) {
return;
}
transform.addOutput(new Asset.fromString(
_ngMetaAssetId(primaryId), _encoder.convert(ngMeta.toJson())));
});
}
}
@ -68,8 +59,3 @@ AssetId _ngMetaAssetId(AssetId primaryInputId) {
return new AssetId(
primaryInputId.package, toMetaExtension(primaryInputId.path));
}
AssetId _ngDepsAssetId(AssetId primaryInputId) {
return new AssetId(
primaryInputId.package, toJsonExtension(primaryInputId.path));
}

View File

@ -30,7 +30,8 @@ class CompileDataResults {
NormalizedComponentWithViewDirectives> viewDefinitions;
final List<CompileDirectiveMetadata> directiveMetadatas;
CompileDataResults._(this.ngDeps, this.viewDefinitions, this.directiveMetadatas);
CompileDataResults._(
this.ngDeps, this.viewDefinitions, this.directiveMetadatas);
}
// TODO(kegluenq): Improve this test.
@ -86,9 +87,7 @@ class _CompileDataCreator {
Future<Map<AssetId, String>> _createImportAssetToPrefixMap() async {
var ngDeps = await ngDepsFuture;
var importAssetToPrefix = <AssetId, String>{
entryPoint: null
};
var importAssetToPrefix = <AssetId, String>{entryPoint: null};
for (ImportDirective node in ngDeps.imports) {
var uri = stringLiteralToString(node.uri);
@ -144,12 +143,15 @@ class _CompileDataCreator {
importAssetId.package, toMetaExtension(importAssetId.path));
if (await reader.hasInput(metaAssetId)) {
try {
var json = JSON.decode(await reader.readAsString(metaAssetId));
var newMetadata = new NgMeta.fromJson(json);
if (importAssetId == entryPoint) {
this.directiveMetadatas.addAll(newMetadata.types.values);
var jsonString = await reader.readAsString(metaAssetId);
if (jsonString != null && jsonString.isNotEmpty) {
var json = JSON.decode(jsonString);
var newMetadata = new NgMeta.fromJson(json);
if (importAssetId == entryPoint) {
this.directiveMetadatas.addAll(newMetadata.types.values);
}
ngMeta.addAll(newMetadata);
}
ngMeta.addAll(newMetadata);
} catch (ex, stackTrace) {
logger.warning('Failed to decode: $ex, $stackTrace',
asset: metaAssetId);

View File

@ -4,7 +4,6 @@ import 'package:barback/barback.dart';
import 'package:dart_style/dart_style.dart';
import 'deferred_rewriter/transformer.dart';
import 'directive_linker/transformer.dart';
import 'directive_metadata_linker/transformer.dart';
import 'directive_processor/transformer.dart';
import 'bind_generator/transformer.dart';
@ -30,14 +29,8 @@ class AngularTransformerGroup extends TransformerGroup {
[new ReflectionRemover(options)],
[new DirectiveProcessor(options)]
];
phases.addAll(new List.generate(
options.optimizationPhases, (_) => [new EmptyNgDepsRemover()]));
phases.addAll([
[
new DeferredRewriter(options),
new DirectiveLinker(),
new DirectiveMetadataLinker()
],
[new DeferredRewriter(options), new DirectiveMetadataLinker()],
[new BindGenerator(options)],
[new TemplateCompiler(options)],
[new StylesheetCompiler()],

View File

@ -0,0 +1,43 @@
library angular2.test.transform.common.ng_meta_helper;
import 'package:angular2/src/core/compiler/directive_metadata.dart';
import 'package:angular2/src/core/change_detection/change_detection.dart';
import 'package:angular2/src/core/metadata/view.dart' show ViewEncapsulation;
export 'package:angular2/src/core/compiler/directive_metadata.dart';
export 'package:angular2/src/core/change_detection/change_detection.dart';
export 'package:angular2/src/core/metadata/view.dart' show ViewEncapsulation;
export 'package:angular2/src/transform/common/model/ng_deps_model.pb.dart';
export 'package:angular2/src/transform/common/ng_meta.dart';
CompileDirectiveMetadata createComponentMetadataForTest(
{String name: 'TestMetadata',
moduleUrl: 'asset:angular2/test/test.dart',
selector: '[test]',
String template: 'Test'}) {
return createDirectiveMetadataForTest(
name: name,
moduleUrl: moduleUrl,
selector: selector,
template: new CompileTemplateMetadata(
encapsulation: ViewEncapsulation.Emulated, template: template));
}
CompileDirectiveMetadata createDirectiveMetadataForTest(
{String name: 'TestMetadata',
String moduleUrl: 'asset:angular2/test/test.dart',
String selector: '[test]',
CompileTemplateMetadata template: null}) {
return CompileDirectiveMetadata.create(
type: new CompileTypeMetadata(name: name, moduleUrl: moduleUrl),
isComponent: false,
dynamicLoadable: true,
selector: selector,
exportAs: null,
changeDetection: ChangeDetectionStrategy.Default,
inputs: [],
outputs: [],
host: {},
lifecycleHooks: [],
template: template);
}

View File

@ -0,0 +1,166 @@
library angular2.test.transform.common.url_resolver_tests;
import 'package:angular2/src/transform/common/url_resolver.dart';
import 'package:barback/barback.dart';
import 'package:guinness/guinness.dart';
main() => allTests();
void allTests() {
var urlResolver = const TransformerUrlResolver();
describe('toAssetUri', () {
it('should convert `AssetId`s to asset: uris', () {
var assetId = new AssetId('test_package', 'lib/src/impl.dart');
expect(toAssetUri(assetId))
.toEqual('asset:test_package/lib/src/impl.dart');
});
it('should throw if passed a null AssetId', () {
expect(() => toAssetUri(null)).toThrowWith(anInstanceOf: ArgumentError);
});
});
describe('fromUri', () {
it('should convert asset: `uri`s to `AssetId`s', () {
expect(fromUri('asset:test_package/lib/src/impl.dart'))
.toEqual(new AssetId('test_package', 'lib/src/impl.dart'));
});
it('should convert package: `uri`s to `AssetId`s', () {
expect(fromUri('package:test_package/src/impl.dart'))
.toEqual(new AssetId('test_package', 'lib/src/impl.dart'));
});
it('should throw if passed a null uri', () {
expect(() => fromUri(null)).toThrowWith(anInstanceOf: ArgumentError);
});
it('should throw if passed an empty uri', () {
expect(() => fromUri('')).toThrowWith(anInstanceOf: ArgumentError);
});
});
describe('isDartCoreUri', () {
it('should detect dart: uris', () {
expect(isDartCoreUri('dart:core')).toBeTrue();
expect(isDartCoreUri('dart:convert')).toBeTrue();
expect(isDartCoreUri('package:angular2/angular2.dart')).toBeFalse();
expect(isDartCoreUri('asset:angular2/lib/angular2.dart')).toBeFalse();
});
it('should throw if passed a null uri', () {
expect(() => isDartCoreUri(null))
.toThrowWith(anInstanceOf: ArgumentError);
});
it('should throw if passed an empty uri', () {
expect(() => isDartCoreUri('')).toThrowWith(anInstanceOf: ArgumentError);
});
});
describe('toAssetScheme', () {
it('should throw for relative `Uri`s', () {
expect(() => toAssetScheme(Uri.parse('/lib/src/file.dart')))
.toThrowWith(anInstanceOf: ArgumentError);
});
it('should convert package: `Uri`s to asset:', () {
expect(toAssetScheme(Uri.parse('package:angular2/angular2.dart')))
.toEqual(Uri.parse('asset:angular2/lib/angular2.dart'));
});
it('should throw for package: `Uri`s which are too short', () {
expect(() => toAssetScheme(Uri.parse('package:angular2')))
.toThrowWith(anInstanceOf: FormatException);
});
it('should convert asset: `Uri`s to asset:', () {
expect(toAssetScheme(Uri.parse('asset:angular2/lib/angular2.dart')))
.toEqual(Uri.parse('asset:angular2/lib/angular2.dart'));
});
it('should throw for asset: `Uri`s which are too short', () {
expect(() => toAssetScheme(Uri.parse('asset:angular2')))
.toThrowWith(anInstanceOf: FormatException);
expect(() => toAssetScheme(Uri.parse('asset:angular2/lib')))
.toThrowWith(anInstanceOf: FormatException);
});
it('should throw for unsupported schemes', () {
expect(() => toAssetScheme(Uri.parse('file:///angular2')))
.toThrowWith(anInstanceOf: FormatException);
});
it('should throw if passed a null uri', () {
expect(() => toAssetScheme(null))
.toThrowWith(anInstanceOf: ArgumentError);
});
});
describe('resolve', () {
it('should resolve package: uris to asset: uris', () {
expect(urlResolver.resolve('', 'package:angular2/angular2.dart'))
.toEqual('asset:angular2/lib/angular2.dart');
});
it('should ignore baseUrl for absolute uris', () {
expect(urlResolver.resolve(null, 'package:angular2/angular2.dart'))
.toEqual('asset:angular2/lib/angular2.dart');
expect(urlResolver.resolve(null, 'asset:angular2/lib/angular2.dart'))
.toEqual('asset:angular2/lib/angular2.dart');
});
it('should resolve asset: uris to asset: uris', () {
expect(urlResolver.resolve('', 'asset:angular2/lib/angular2.dart'))
.toEqual('asset:angular2/lib/angular2.dart');
});
it('should resolve relative uris when baseUrl is package: uri', () {
expect(urlResolver.resolve('package:angular2/angular2.dart',
'src/transform/transformer.dart'))
.toEqual('asset:angular2/lib/src/transform/transformer.dart');
});
it('should resolve relative uris when baseUrl is asset: uri', () {
expect(urlResolver.resolve('asset:angular2/lib/angular2.dart',
'src/transform/transformer.dart'))
.toEqual('asset:angular2/lib/src/transform/transformer.dart');
});
it('should normalize uris', () {
expect(urlResolver.resolve('asset:angular2/lib/angular2.dart',
'src/transform/../transform/transformer.dart'))
.toEqual('asset:angular2/lib/src/transform/transformer.dart');
expect(urlResolver.resolve('asset:angular2/lib/src/../angular2.dart',
'src/transform/transformer.dart'))
.toEqual('asset:angular2/lib/src/transform/transformer.dart');
});
it('should throw if passed a null uri', () {
expect(() => urlResolver.resolve('package:angular2/angular2.dart', null))
.toThrowWith(anInstanceOf: ArgumentError);
});
it('should gracefully handle an empty uri', () {
expect(urlResolver.resolve('package:angular2/angular2.dart', ''))
.toEqual('asset:angular2/lib/angular2.dart');
});
it('should throw if passed a relative uri and a null baseUri', () {
expect(() => urlResolver.resolve(null, 'angular2/angular2.dart'))
.toThrowWith(anInstanceOf: ArgumentError);
});
it('should throw if passed a relative uri and an empty baseUri', () {
expect(() => urlResolver.resolve('', 'angular2/angular2.dart'))
.toThrowWith(anInstanceOf: ArgumentError);
});
it('should throw if the resolved uri is relative', () {
expect(() => urlResolver.resolve('/angular2/', 'angular2.dart'))
.toThrowWith(anInstanceOf: ArgumentError);
});
});
}

View File

@ -1,137 +0,0 @@
library angular2.test.transform.directive_linker.all_tests;
import 'package:barback/barback.dart';
import 'package:angular2/src/transform/common/model/annotation_model.pb.dart';
import 'package:angular2/src/transform/common/model/import_export_model.pb.dart';
import 'package:angular2/src/transform/common/model/ng_deps_model.pb.dart';
import 'package:angular2/src/transform/common/model/reflection_info_model.pb.dart';
import 'package:angular2/src/transform/directive_linker/linker.dart';
import 'package:dart_style/dart_style.dart';
import 'package:guinness/guinness.dart';
import '../common/read_file.dart';
var formatter = new DartFormatter();
main() => allTests();
void allTests() {
var reader;
beforeEach(() {
reader = new TestAssetReader();
});
it('should chain imported dependencies.', () async {
var fooModel = new NgDepsModel()
..libraryUri = 'test.foo'
..imports.add(new ImportModel()
..uri = 'bar.dart'
..prefix = 'dep');
var barModel = new NgDepsModel()..libraryUri = 'test.bar';
var fooAssetId = new AssetId('a', 'lib/foo.ng_deps.json');
reader
..addAsset(fooAssetId, fooModel.writeToJson())
..addAsset(
new AssetId('a', 'lib/bar.ng_deps.json'), barModel.writeToJson());
var linked = await linkNgDeps(reader, fooAssetId);
expect(linked).toBeNotNull();
var linkedImport =
linked.imports.firstWhere((i) => i.uri.endsWith('bar.ng_deps.dart'));
expect(linkedImport).toBeNotNull();
expect(linkedImport.isNgDeps).toBeTrue();
expect(linkedImport.prefix.startsWith('i')).toBeTrue();
});
it('should chain exported dependencies.', () async {
var fooModel = new NgDepsModel()
..libraryUri = 'test.foo'
..exports.add(new ExportModel()..uri = 'bar.dart');
var barModel = new NgDepsModel()..libraryUri = 'test.bar';
var fooAssetId = new AssetId('a', 'lib/foo.ng_deps.json');
reader
..addAsset(fooAssetId, fooModel.writeToJson())
..addAsset(
new AssetId('a', 'lib/bar.ng_deps.json'), barModel.writeToJson());
var linked = await linkNgDeps(reader, fooAssetId);
expect(linked).toBeNotNull();
var linkedImport =
linked.imports.firstWhere((i) => i.uri.endsWith('bar.ng_deps.dart'));
expect(linkedImport).toBeNotNull();
expect(linkedImport.isNgDeps).toBeTrue();
expect(linkedImport.prefix.startsWith('i')).toBeTrue();
});
describe('isNecessary', () {
it('should drop deps that do no registration and do not import.', () async {
var fooModel = new NgDepsModel()..libraryUri = 'test.foo';
var fooAssetId = new AssetId('a', 'lib/foo.ng_deps.json');
reader.addAsset(fooAssetId, fooModel.writeToJson());
expect(await isNecessary(reader, fooAssetId)).toBeFalse();
});
it('should retain deps that import other deps.', () async {
var fooModel = new NgDepsModel()
..libraryUri = 'test.foo'
..imports.add(new ImportModel()..uri = 'bar.dart');
var barModel = new NgDepsModel()..libraryUri = 'test.bar';
var fooAssetId = new AssetId('a', 'lib/foo.ng_deps.json');
reader
..addAsset(fooAssetId, fooModel.writeToJson())
..addAsset(
new AssetId('a', 'lib/bar.ng_deps.json'), barModel.writeToJson());
expect(await isNecessary(reader, fooAssetId)).toBeTrue();
});
it('should retain deps that export other deps.', () async {
var fooModel = new NgDepsModel()
..libraryUri = 'test.foo'
..exports.add(new ExportModel()..uri = 'bar.dart');
var barModel = new NgDepsModel()..libraryUri = 'test.bar';
var fooAssetId = new AssetId('a', 'lib/foo.ng_deps.json');
reader
..addAsset(fooAssetId, fooModel.writeToJson())
..addAsset(
new AssetId('a', 'lib/bar.ng_deps.json'), barModel.writeToJson());
expect(await isNecessary(reader, fooAssetId)).toBeTrue();
});
it('should retain deps that register injectable types.', () async {
var fooModel = new NgDepsModel()
..libraryUri = 'test.foo'
..reflectables.add(new ReflectionInfoModel()
..name = 'MyInjectable'
..annotations.add(new AnnotationModel()
..name = 'Injectable'
..isInjectable = true));
var fooAssetId = new AssetId('a', 'lib/foo.ng_deps.json');
reader.addAsset(fooAssetId, fooModel.writeToJson());
expect(await isNecessary(reader, fooAssetId)).toBeTrue();
});
it('should retain deps that register injectable functions.', () async {
var fooModel = new NgDepsModel()
..libraryUri = 'test.foo'
..reflectables.add(new ReflectionInfoModel()
..name = 'injectableFunction'
..isFunction = true
..annotations.add(new AnnotationModel()
..name = 'Injectable'
..isInjectable = true));
var fooAssetId = new AssetId('a', 'lib/foo.ng_deps.json');
reader.addAsset(fooAssetId, fooModel.writeToJson());
expect(await isNecessary(reader, fooAssetId)).toBeTrue();
});
});
}

View File

@ -1,19 +0,0 @@
library foo.ng_deps.dart;
import 'bar.dart';
import 'package:angular2/src/core/metadata.dart';
export 'baz.dart';
import 'baz.ng_deps.dart' as i0;
var _visited = false;
void initReflector(reflector) {
if (_visited) return;
_visited = true;
reflector
..registerType(
BarComponent,
new ReflectionInfo(const [const Component(selector: '[bar]')], const [],
() => new BarComponent()));
i0.initReflector(reflector);
}

View File

@ -1,17 +0,0 @@
library foo.ng_deps.dart;
import 'baz.dart';
import 'package:angular2/src/core/metadata.dart';
export 'foo.dart';
var _visited = false;
void initReflector(reflector) {
if (_visited) return;
_visited = true;
reflector
..registerType(
BazComponent,
new ReflectionInfo(const [const Component(selector: '[baz]')], const [],
() => new BazComponent()));
}

View File

@ -1,19 +0,0 @@
library foo.ng_deps.dart;
import 'foo.dart';
import 'package:angular2/src/core/metadata.dart';
export 'bar.dart';
import 'bar.ng_deps.dart' as i0;
var _visited = false;
void initReflector(reflector) {
if (_visited) return;
_visited = true;
reflector
..registerType(
FooComponent,
new ReflectionInfo(const [const Component(selector: '[foo]')], const [],
() => new FooComponent()));
i0.initReflector(reflector);
}

View File

@ -1,17 +1,14 @@
library angular2.test.transform.directive_metadata_linker.all_tests;
import 'dart:async';
import 'package:angular2/src/core/render/api.dart';
import 'package:angular2/src/core/change_detection/change_detection.dart';
import 'package:angular2/src/transform/common/directive_metadata_reader.dart';
import 'package:angular2/src/transform/common/logging.dart';
import 'package:angular2/src/transform/common/ng_deps.dart';
import 'package:angular2/src/transform/directive_metadata_linker/'
'linker.dart';
import 'dart:convert';
import 'package:angular2/src/transform/common/model/import_export_model.pb.dart';
import 'package:angular2/src/transform/directive_metadata_linker/ng_meta_linker.dart';
import 'package:barback/barback.dart';
import 'package:dart_style/dart_style.dart';
import 'package:guinness/guinness.dart';
import '../common/ng_meta_helper.dart';
import '../common/read_file.dart';
var formatter = new DartFormatter();
@ -20,59 +17,148 @@ main() => allTests();
void allTests() {
TestAssetReader reader = null;
final moduleBase = 'asset:a';
var fooNgMeta, fooAssetId;
var barNgMeta, barAssetId;
var bazNgMeta, bazAssetId;
/// Call after making changes to `fooNgMeta`, `barNgMeta`, or `bazNgMeta` and
/// before trying to read them from `reader`.
final updateReader = () => reader
..addAsset(fooAssetId, JSON.encode(fooNgMeta.toJson()))
..addAsset(barAssetId, JSON.encode(barNgMeta.toJson()))
..addAsset(bazAssetId, JSON.encode(bazNgMeta.toJson()));
beforeEach(() {
reader = new TestAssetReader();
// Establish some test NgMeta objects with one Component each.
var fooName = 'FooComponent';
var fooComponentMeta = createComponentMetadataForTest(
name: fooName,
moduleUrl: '$moduleBase/export_cycle_files/foo.dart',
selector: '[foo]',
template: 'Foo');
fooNgMeta = new NgMeta(ngDeps: new NgDepsModel());
fooNgMeta.types[fooName] = fooComponentMeta;
var barName = 'BarComponent';
var barComponentMeta = createComponentMetadataForTest(
name: barName,
moduleUrl: '$moduleBase/export_cycle_files/bar.dart',
selector: '[bar]',
template: 'Bar');
barNgMeta = new NgMeta(ngDeps: new NgDepsModel());
barNgMeta.types[barName] = barComponentMeta;
var bazName = 'BazComponent';
var bazComponentMeta = createComponentMetadataForTest(
name: bazName,
moduleUrl: '$moduleBase/export_cycle_files/baz.dart',
selector: '[baz]',
template: 'Baz');
bazNgMeta = new NgMeta(ngDeps: new NgDepsModel());
barNgMeta.types[bazName] = bazComponentMeta;
fooAssetId = new AssetId('a', 'lib/foo.ng_meta.json');
barAssetId = new AssetId('a', 'lib/bar.ng_meta.json');
bazAssetId = new AssetId('a', 'lib/baz.ng_meta.json');
updateReader();
});
it('should include `DirectiveMetadata` from exported files.', () async {
var extracted = await linkDirectiveMetadata(
reader,
new AssetId(
'a', 'directive_metadata_linker/export_files/foo.ng_meta.json'));
expect(extracted.types).toContain('FooComponent');
expect(extracted.types).toContain('BarComponent');
describe('NgMeta linker', () {
it('should include `DirectiveMetadata` from exported files.', () async {
fooNgMeta.ngDeps.exports.add(new ExportModel()..uri = 'bar.dart');
updateReader();
expect(extracted.types['FooComponent'].selector).toEqual('[foo]');
expect(extracted.types['BarComponent'].selector).toEqual('[bar]');
var extracted = await linkDirectiveMetadata(reader, fooAssetId);
expect(extracted.types).toContain('FooComponent');
expect(extracted.types).toContain('BarComponent');
expect(extracted.types['FooComponent'].selector).toEqual('[foo]');
expect(extracted.types['BarComponent'].selector).toEqual('[bar]');
});
it('should include `DirectiveMetadata` recursively from exported files.',
() async {
fooNgMeta.ngDeps.exports.add(new ExportModel()..uri = 'bar.dart');
barNgMeta.ngDeps.exports.add(new ExportModel()..uri = 'baz.dart');
updateReader();
var extracted = await linkDirectiveMetadata(reader, fooAssetId);
expect(extracted.types).toContain('FooComponent');
expect(extracted.types).toContain('BarComponent');
expect(extracted.types).toContain('BazComponent');
expect(extracted.types['FooComponent'].selector).toEqual('[foo]');
expect(extracted.types['BarComponent'].selector).toEqual('[bar]');
expect(extracted.types['BazComponent'].selector).toEqual('[baz]');
});
it('should handle `DirectiveMetadata` export cycles gracefully.', () async {
fooNgMeta.ngDeps.exports.add(new ExportModel()..uri = 'bar.dart');
barNgMeta.ngDeps.exports.add(new ExportModel()..uri = 'baz.dart');
bazNgMeta.ngDeps.exports.add(new ExportModel()..uri = 'foo.dart');
updateReader();
var extracted = await linkDirectiveMetadata(reader, bazAssetId);
expect(extracted.types).toContain('FooComponent');
expect(extracted.types).toContain('BarComponent');
expect(extracted.types).toContain('BazComponent');
});
it(
'should include `DirectiveMetadata` from exported files '
'expressed as absolute uris', () async {
fooNgMeta.ngDeps.exports
.add(new ExportModel()..uri = 'package:bar/bar.dart');
updateReader();
reader.addAsset(new AssetId('bar', 'lib/bar.ng_meta.json'),
JSON.encode(barNgMeta.toJson()));
var extracted = await linkDirectiveMetadata(reader, fooAssetId);
expect(extracted.types).toContain('FooComponent');
expect(extracted.types).toContain('BarComponent');
expect(extracted.types['FooComponent'].selector).toEqual('[foo]');
expect(extracted.types['BarComponent'].selector).toEqual('[bar]');
});
});
it('should include `DirectiveMetadata` recursively from exported files.',
() async {
var extracted = await linkDirectiveMetadata(
reader,
new AssetId('a',
'directive_metadata_linker/recursive_export_files/foo.ng_meta.json'));
expect(extracted.types).toContain('FooComponent');
expect(extracted.types).toContain('BarComponent');
expect(extracted.types).toContain('BazComponent');
describe('NgDeps linker', () {
it('should chain imported dependencies.', () async {
fooNgMeta.ngDeps
..libraryUri = 'test.foo'
..imports.add(new ImportModel()
..uri = 'bar.dart'
..prefix = 'dep');
barNgMeta.ngDeps.libraryUri = 'test.bar';
updateReader();
expect(extracted.types['FooComponent'].selector).toEqual('[foo]');
expect(extracted.types['BarComponent'].selector).toEqual('[bar]');
expect(extracted.types['BazComponent'].selector).toEqual('[baz]');
});
var linked = (await linkDirectiveMetadata(reader, fooAssetId)).ngDeps;
expect(linked).toBeNotNull();
var linkedImport =
linked.imports.firstWhere((i) => i.uri.endsWith('bar.ng_deps.dart'));
expect(linkedImport).toBeNotNull();
expect(linkedImport.isNgDeps).toBeTrue();
expect(linkedImport.prefix.startsWith('i')).toBeTrue();
});
it('should handle `DirectiveMetadata` export cycles gracefully.', () async {
var extracted = await linkDirectiveMetadata(
reader,
new AssetId('a',
'directive_metadata_linker/export_cycle_files/baz.ng_meta.json'));
expect(extracted.types).toContain('FooComponent');
expect(extracted.types).toContain('BarComponent');
expect(extracted.types).toContain('BazComponent');
});
it('should chain exported dependencies.', () async {
fooNgMeta.ngDeps
..libraryUri = 'test.foo'
..exports.add(new ExportModel()..uri = 'bar.dart');
barNgMeta.ngDeps.libraryUri = 'test.bar';
updateReader();
it(
'should include `DirectiveMetadata` from exported files '
'expressed as absolute uris', () async {
var extracted = await linkDirectiveMetadata(
reader,
new AssetId('a',
'directive_metadata_linker/absolute_export_files/foo.ng_meta.json'));
expect(extracted.types).toContain('FooComponent');
expect(extracted.types).toContain('BarComponent');
expect(extracted.types['FooComponent'].selector).toEqual('[foo]');
expect(extracted.types['BarComponent'].selector).toEqual('[bar]');
var linked = (await linkDirectiveMetadata(reader, fooAssetId)).ngDeps;
expect(linked).toBeNotNull();
var linkedImport =
linked.imports.firstWhere((i) => i.uri.endsWith('bar.ng_deps.dart'));
expect(linkedImport).toBeNotNull();
expect(linkedImport.isNgDeps).toBeTrue();
expect(linkedImport.prefix.startsWith('i')).toBeTrue();
});
});
}

View File

@ -41,7 +41,7 @@ Expect _expectSelector(ReflectionInfoModel model) {
void allTests() {
it('should preserve parameter annotations.', () async {
var model = await _testCreateModel('parameter_metadata/soup.dart');
var model = (await _testCreateModel('parameter_metadata/soup.dart')).ngDeps;
expect(model.reflectables.length).toBe(1);
var reflectable = model.reflectables.first;
expect(reflectable.parameters.length).toBe(2);
@ -59,7 +59,8 @@ void allTests() {
});
describe('part support', () {
var modelFuture = _testCreateModel('part_files/main.dart');
var modelFuture = _testCreateModel('part_files/main.dart')
.then((ngMeta) => ngMeta != null ? ngMeta.ngDeps : null);
it('should include directives from the part.', () async {
var model = await modelFuture;
@ -79,56 +80,59 @@ void allTests() {
});
it('should handle multiple `part` directives.', () async {
var model = await _testCreateModel('multiple_part_files/main.dart');
var model =
(await _testCreateModel('multiple_part_files/main.dart')).ngDeps;
expect(model.reflectables.length).toEqual(3);
_expectSelector(model.reflectables.first).toEqual("'[part1]'");
_expectSelector(model.reflectables[1]).toEqual("'[part2]'");
_expectSelector(model.reflectables[2]).toEqual("'[main]'");
});
it('should not generate .ng_deps.dart for `part` files.', () async {
var model = await _testCreateModel('part_files/part.dart');
expect(model).toBeNull();
it('should not generate anything for `part` files.', () async {
expect(await _testCreateModel('part_files/part.dart')).toBeNull();
});
});
describe('custom annotations', () {
it('should be recognized from package: imports', () async {
var model =
var ngMeta =
await _testCreateModel('custom_metadata/package_soup.dart', customDescriptors:
[
const ClassDescriptor('Soup', 'package:soup/soup.dart',
superClass: 'Component')
]);
var model = ngMeta.ngDeps;
expect(model.reflectables.length).toEqual(1);
expect(model.reflectables.first.name).toEqual('PackageSoup');
});
it('should be recognized from relative imports', () async {
var model = await _testCreateModel('custom_metadata/relative_soup.dart',
var ngMeta = await _testCreateModel('custom_metadata/relative_soup.dart',
assetId: new AssetId('soup', 'lib/relative_soup.dart'),
customDescriptors: [
const ClassDescriptor('Soup', 'package:soup/annotations/soup.dart',
superClass: 'Component')
]);
var model = ngMeta.ngDeps;
expect(model.reflectables.length).toEqual(1);
expect(model.reflectables.first.name).toEqual('RelativeSoup');
});
it('should ignore annotations that are not imported', () async {
var model =
var ngMeta =
await _testCreateModel('custom_metadata/bad_soup.dart', customDescriptors:
[
const ClassDescriptor('Soup', 'package:soup/soup.dart',
superClass: 'Component')
]);
expect(model).toBeNull();
expect(ngMeta.ngDeps == null || ngMeta.ngDeps.reflectables.isEmpty)
.toBeTrue();
});
});
describe('interfaces', () {
it('should include implemented types', () async {
var model = await _testCreateModel('interfaces_files/soup.dart');
var model = (await _testCreateModel('interfaces_files/soup.dart')).ngDeps;
expect(model.reflectables.first.interfaces).toBeNotNull();
expect(model.reflectables.first.interfaces.isNotEmpty).toBeTrue();
@ -139,7 +143,8 @@ void allTests() {
});
it('should not include transitively implemented types', () async {
var model = await _testCreateModel('interface_chain_files/soup.dart');
var model =
(await _testCreateModel('interface_chain_files/soup.dart')).ngDeps;
expect(model.reflectables.first.interfaces).toBeNotNull();
expect(model.reflectables.first.interfaces.isNotEmpty).toBeTrue();
@ -152,15 +157,15 @@ void allTests() {
});
it('should not include superclasses.', () async {
var model = await _testCreateModel('superclass_files/soup.dart');
var model = (await _testCreateModel('superclass_files/soup.dart')).ngDeps;
var interfaces = model.reflectables.first.interfaces;
expect(interfaces == null || interfaces.isEmpty).toBeTrue();
});
it('should populate multiple `lifecycle` values when necessary.', () async {
var model = await _testCreateModel(
'multiple_interface_lifecycle_files/soup.dart');
var model = (await _testCreateModel(
'multiple_interface_lifecycle_files/soup.dart')).ngDeps;
expect(model.reflectables.first.interfaces).toBeNotNull();
expect(model.reflectables.first.interfaces.isNotEmpty).toBeTrue();
@ -174,15 +179,16 @@ void allTests() {
it('should not populate `lifecycle` when lifecycle superclass is present.',
() async {
var model =
await _testCreateModel('superclass_lifecycle_files/soup.dart');
(await _testCreateModel('superclass_lifecycle_files/soup.dart'))
.ngDeps;
var interfaces = model.reflectables.first.interfaces;
expect(interfaces == null || interfaces.isEmpty).toBeTrue();
});
it('should populate `lifecycle` with prefix when necessary.', () async {
var model = await _testCreateModel(
'prefixed_interface_lifecycle_files/soup.dart');
var model = (await _testCreateModel(
'prefixed_interface_lifecycle_files/soup.dart')).ngDeps;
expect(model.reflectables.first.interfaces).toBeNotNull();
expect(model.reflectables.first.interfaces.isNotEmpty).toBeTrue();
expect(model.reflectables.first.interfaces
@ -193,7 +199,8 @@ void allTests() {
describe('property metadata', () {
it('should be recorded on fields', () async {
var model = await _testCreateModel('prop_metadata_files/fields.dart');
var model =
(await _testCreateModel('prop_metadata_files/fields.dart')).ngDeps;
expect(model.reflectables.first.propertyMetadata).toBeNotNull();
expect(model.reflectables.first.propertyMetadata.isNotEmpty).toBeTrue();
@ -205,7 +212,8 @@ void allTests() {
});
it('should be recorded on getters', () async {
var model = await _testCreateModel('prop_metadata_files/getters.dart');
var model =
(await _testCreateModel('prop_metadata_files/getters.dart')).ngDeps;
expect(model.reflectables.first.propertyMetadata).toBeNotNull();
expect(model.reflectables.first.propertyMetadata.isNotEmpty).toBeTrue();
@ -221,7 +229,8 @@ void allTests() {
it('should gracefully handle const instances of annotations', () async {
// Regression test for i/4481
var model = await _testCreateModel('prop_metadata_files/override.dart');
var model =
(await _testCreateModel('prop_metadata_files/override.dart')).ngDeps;
expect(model.reflectables.first.propertyMetadata).toBeNotNull();
expect(model.reflectables.first.propertyMetadata.isNotEmpty).toBeTrue();
@ -240,7 +249,8 @@ void allTests() {
});
it('should be recorded on setters', () async {
var model = await _testCreateModel('prop_metadata_files/setters.dart');
var model =
(await _testCreateModel('prop_metadata_files/setters.dart')).ngDeps;
expect(model.reflectables.first.propertyMetadata).toBeNotNull();
expect(model.reflectables.first.propertyMetadata.isNotEmpty).toBeTrue();
@ -253,8 +263,8 @@ void allTests() {
it('should be coalesced when getters and setters have the same name',
() async {
var model = await _testCreateModel(
'prop_metadata_files/getters_and_setters.dart');
var model = (await _testCreateModel(
'prop_metadata_files/getters_and_setters.dart')).ngDeps;
expect(model.reflectables.first.propertyMetadata).toBeNotNull();
expect(model.reflectables.first.propertyMetadata.length).toBe(1);
@ -271,8 +281,7 @@ void allTests() {
it('should not throw/hang on invalid urls', () async {
var logger = new RecordingLogger();
var model =
await _testCreateModel('invalid_url_files/hello.dart', logger: logger);
await _testCreateModel('invalid_url_files/hello.dart', logger: logger);
expect(logger.hasErrors).toBeTrue();
expect(logger.logs)
..toContain('ERROR: ERROR: Invalid argument (url): '
@ -280,7 +289,8 @@ void allTests() {
});
it('should find and register static functions.', () async {
var model = await _testCreateModel('static_function_files/hello.dart');
var model =
(await _testCreateModel('static_function_files/hello.dart')).ngDeps;
var functionReflectable =
model.reflectables.firstWhere((i) => i.isFunction, orElse: () => null);
@ -295,9 +305,7 @@ void allTests() {
});
it('should find direcive aliases patterns.', () async {
var ngMeta = new NgMeta.empty();
await _testCreateModel('directive_aliases_files/hello.dart',
ngMeta: ngMeta);
var ngMeta = await _testCreateModel('directive_aliases_files/hello.dart');
expect(ngMeta.aliases).toContain('alias1');
expect(ngMeta.aliases['alias1']).toContain('HelloCmp');
@ -307,8 +315,7 @@ void allTests() {
});
it('should include hooks for implemented types (single)', () async {
var ngMeta = new NgMeta.empty();
await _testCreateModel('interfaces_files/soup.dart', ngMeta: ngMeta);
var ngMeta = await _testCreateModel('interfaces_files/soup.dart');
expect(ngMeta.types.isNotEmpty).toBeTrue();
expect(ngMeta.types['ChangingSoupComponent']).toBeNotNull();
@ -318,9 +325,8 @@ void allTests() {
});
it('should include hooks for implemented types (many)', () async {
var ngMeta = new NgMeta.empty();
await _testCreateModel('multiple_interface_lifecycle_files/soup.dart',
ngMeta: ngMeta);
var ngMeta = await _testCreateModel(
'multiple_interface_lifecycle_files/soup.dart');
expect(ngMeta.types.isNotEmpty).toBeTrue();
expect(ngMeta.types['MultiSoupComponent']).toBeNotNull();
@ -335,9 +341,9 @@ void allTests() {
fakeReader
..addAsset(new AssetId('other_package', 'lib/template.html'), '')
..addAsset(new AssetId('other_package', 'lib/template.css'), '');
var ngMeta = new NgMeta.empty();
await _testCreateModel('absolute_url_expression_files/hello.dart',
ngMeta: ngMeta, reader: fakeReader);
var ngMeta = await _testCreateModel(
'absolute_url_expression_files/hello.dart',
reader: fakeReader);
expect(ngMeta.types.isNotEmpty).toBeTrue();
expect(ngMeta.types['HelloCmp']).toBeNotNull();
@ -346,9 +352,7 @@ void allTests() {
it('should populate all provided values for Components & Directives',
() async {
var ngMeta = new NgMeta.empty();
await _testCreateModel('unusual_component_files/hello.dart',
ngMeta: ngMeta);
var ngMeta = await _testCreateModel('unusual_component_files/hello.dart');
expect(ngMeta.types.isNotEmpty).toBeTrue();
@ -382,8 +386,7 @@ void allTests() {
});
it('should include hooks for implemented types (single)', () async {
var ngMeta = new NgMeta.empty();
await _testCreateModel('interfaces_files/soup.dart', ngMeta: ngMeta);
var ngMeta = await _testCreateModel('interfaces_files/soup.dart');
expect(ngMeta.types.isNotEmpty).toBeTrue();
expect(ngMeta.types['ChangingSoupComponent']).toBeNotNull();
@ -393,9 +396,8 @@ void allTests() {
});
it('should include hooks for implemented types (many)', () async {
var ngMeta = new NgMeta.empty();
await _testCreateModel('multiple_interface_lifecycle_files/soup.dart',
ngMeta: ngMeta);
var ngMeta = await _testCreateModel(
'multiple_interface_lifecycle_files/soup.dart');
expect(ngMeta.types.isNotEmpty).toBeTrue();
expect(ngMeta.types['MultiSoupComponent']).toBeNotNull();
@ -410,9 +412,9 @@ void allTests() {
fakeReader
..addAsset(new AssetId('other_package', 'lib/template.html'), '')
..addAsset(new AssetId('other_package', 'lib/template.css'), '');
var ngMeta = new NgMeta.empty();
await _testCreateModel('absolute_url_expression_files/hello.dart',
ngMeta: ngMeta, reader: fakeReader);
var ngMeta = await _testCreateModel(
'absolute_url_expression_files/hello.dart',
reader: fakeReader);
expect(ngMeta.types.isNotEmpty).toBeTrue();
expect(ngMeta.types['HelloCmp']).toBeNotNull();
@ -423,12 +425,11 @@ void allTests() {
});
}
Future<NgDepsModel> _testCreateModel(String inputPath,
Future<NgMeta> _testCreateModel(String inputPath,
{List<AnnotationDescriptor> customDescriptors: const [],
AssetId assetId,
AssetReader reader,
BuildLogger logger,
NgMeta ngMeta}) {
BuildLogger logger}) {
if (logger == null) logger = new RecordingLogger();
return log.setZoned(logger, () async {
var inputId = _assetIdForPath(inputPath);
@ -439,12 +440,9 @@ Future<NgDepsModel> _testCreateModel(String inputPath,
reader.addAsset(assetId, await reader.readAsString(inputId));
inputId = assetId;
}
if (ngMeta == null) {
ngMeta = new NgMeta.empty();
}
var annotationMatcher = new AnnotationMatcher()..addAll(customDescriptors);
return createNgDeps(reader, inputId, annotationMatcher, ngMeta);
return createNgDeps(reader, inputId, annotationMatcher);
});
}

View File

@ -114,24 +114,25 @@ void allTests() {
new IntegrationTestConfig(
'should handle Directive depenedencies declared on a View.',
inputs: {
'a|web/index.dart': 'directive_dep_files/index.dart',
'a|web/foo.dart': 'directive_dep_files/foo.dart',
'a|web/bar.dart': 'directive_dep_files/bar.dart'
},
'a|web/index.dart': 'directive_dep_files/index.dart',
'a|web/foo.dart': 'directive_dep_files/foo.dart',
'a|web/bar.dart': 'directive_dep_files/bar.dart'
},
outputs: {
'a|web/bar.ng_deps.dart': 'directive_dep_files/expected/bar.ng_deps.dart'
}),
'a|web/bar.ng_deps.dart': 'directive_dep_files/expected/bar.ng_deps.dart'
}),
new IntegrationTestConfig(
'should handle chained Directive dependencies declared on a View.',
inputs: {
'a|web/index.dart': 'directive_chain_files/index.dart',
'a|web/foo.dart': 'directive_chain_files/foo.dart',
'a|web/bar.dart': 'directive_chain_files/bar.dart',
'a|web/baz.dart': 'directive_chain_files/baz.dart'
},
'a|web/index.dart': 'directive_chain_files/index.dart',
'a|web/foo.dart': 'directive_chain_files/foo.dart',
'a|web/bar.dart': 'directive_chain_files/bar.dart',
'a|web/baz.dart': 'directive_chain_files/baz.dart'
},
outputs: {
'a|web/bar.ng_deps.dart': 'directive_chain_files/expected/bar.ng_deps.dart'
})
'a|web/bar.ng_deps.dart':
'directive_chain_files/expected/bar.ng_deps.dart'
})
];
var cache = {};
@ -160,8 +161,9 @@ void allTests() {
],
config.assetPathToInputPath,
config.assetPathToExpectedOutputPath,
[],
StringFormatter.noNewlinesOrSurroundingWhitespace);
[]);
//,
// StringFormatter.noNewlinesOrSurroundingWhitespace);
}
}
}

View File

@ -5,6 +5,7 @@ import 'bar.template.dart' as _templates;
import 'bar.dart';
import 'package:angular2/src/core/reflection/reflection.dart' as _ngRef;
import 'package:angular2/src/core/metadata.dart';
import 'package:angular2/src/core/metadata.ng_deps.dart' as i0;
import 'baz.dart';
import 'baz.ng_deps.dart' as i1;
export 'bar.dart';
@ -21,5 +22,6 @@ void initReflector() {
const View(directives: [Foo], template: 'foo'),
_templates.HostMyComponentTemplate
], const [], () => new MyComponent()));
i0.initReflector();
i1.initReflector();
}

View File

@ -5,6 +5,7 @@ import 'bar.template.dart' as _templates;
import 'bar.dart';
import 'package:angular2/src/core/reflection/reflection.dart' as _ngRef;
import 'package:angular2/src/core/metadata.dart';
import 'package:angular2/src/core/metadata.ng_deps.dart' as i0;
import 'foo.dart' as prefix;
import 'foo.ng_deps.dart' as i1;
export 'bar.dart';
@ -21,5 +22,6 @@ void initReflector() {
const View(directives: [prefix.Foo], template: 'foo'),
_templates.HostMyComponentTemplate
], const [], () => new MyComponent()));
i0.initReflector();
i1.initReflector();
}

View File

@ -5,6 +5,7 @@ import 'bar.template.dart' as _templates;
import 'bar.dart';
import 'package:angular2/src/core/reflection/reflection.dart' as _ngRef;
import 'package:angular2/src/core/metadata.dart';
import 'package:angular2/src/core/metadata.ng_deps.dart' as i0;
export 'bar.dart';
var _visited = false;
@ -23,4 +24,5 @@ void initReflector() {
], const [], () => new MyComponent()))
..registerGetters(
{'eventName1': (o) => o.eventName1, 'eventName2': (o) => o.eventName2});
i0.initReflector();
}

View File

@ -5,6 +5,7 @@ import 'bar.template.dart' as _templates;
import 'bar.dart';
import 'package:angular2/src/core/reflection/reflection.dart' as _ngRef;
import 'package:angular2/src/core/metadata.dart';
import 'package:angular2/src/core/metadata.ng_deps.dart' as i0;
import 'foo.dart';
export 'bar.dart';
@ -22,4 +23,5 @@ void initReflector() {
], const [
const [MyContext]
], (MyContext c) => new MyComponent(c)));
i0.initReflector();
}

View File

@ -5,6 +5,7 @@ import 'bar.template.dart' as _templates;
import 'bar.dart';
import 'package:angular2/src/core/reflection/reflection.dart' as _ngRef;
import 'package:angular2/src/core/metadata.dart';
import 'package:angular2/src/core/metadata.ng_deps.dart' as i0;
export 'bar.dart';
var _visited = false;
@ -19,4 +20,5 @@ void initReflector() {
const View(template: ''),
_templates.HostMyComponentTemplate
], const [], () => new MyComponent()));
i0.initReflector();
}

View File

@ -4,6 +4,7 @@ import 'index.dart';
import 'package:angular2/src/core/reflection/reflection.dart' as _ngRef;
import 'package:angular2/bootstrap_static.dart';
import 'index.ng_deps.dart' as ngStaticInit;
import 'index.ng_deps.dart' as i1;
import 'package:angular2/src/core/reflection/reflection.dart';
import 'bar.dart';
import 'bar.ng_deps.dart' as i3;
@ -13,5 +14,6 @@ var _visited = false;
void initReflector() {
if (_visited) return;
_visited = true;
i1.initReflector();
i3.initReflector();
}

View File

@ -5,6 +5,7 @@ import 'bar.template.dart' as _templates;
import 'bar.dart';
import 'package:angular2/src/core/reflection/reflection.dart' as _ngRef;
import 'package:angular2/src/core/metadata.dart';
import 'package:angular2/src/core/metadata.ng_deps.dart' as i0;
export 'bar.dart';
var _visited = false;
@ -19,4 +20,5 @@ void initReflector() {
const View(template: ''),
_templates.HostMyComponentTemplate
], const [], () => new MyComponent()));
i0.initReflector();
}

View File

@ -5,6 +5,7 @@ import 'bar.template.dart' as _templates;
import 'bar.dart';
import 'package:angular2/src/core/reflection/reflection.dart' as _ngRef;
import 'package:angular2/src/core/metadata.dart';
import 'package:angular2/src/core/metadata.ng_deps.dart' as i0;
export 'bar.dart';
var _visited = false;
@ -19,4 +20,5 @@ void initReflector() {
const View(template: 'Salad: {{myNum}} is awesome'),
_templates.HostMyComponentTemplate
], const [], () => new MyComponent()));
i0.initReflector();
}

View File

@ -5,6 +5,7 @@ import 'bar.template.dart' as _templates;
import 'bar.dart';
import 'package:angular2/src/core/reflection/reflection.dart' as _ngRef;
import 'package:angular2/src/core/metadata.dart';
import 'package:angular2/src/core/metadata.ng_deps.dart' as i0;
import 'foo.dart' as prefix;
export 'bar.dart';
@ -27,4 +28,5 @@ void initReflector() {
],
(prefix.MyContext c, String inValue) =>
new MyComponent(c, inValue)));
i0.initReflector();
}

View File

@ -6,9 +6,9 @@ import 'package:unittest/vm_config.dart';
import 'common/async_string_writer_tests.dart' as asyncStringWriter;
import 'common/ng_meta_test.dart' as ngMetaTest;
import 'common/url_resolver_tests.dart' as urlResolver;
import 'bind_generator/all_tests.dart' as bindGenerator;
import 'deferred_rewriter/all_tests.dart' as deferredRewriter;
import 'directive_linker/all_tests.dart' as directiveLinker;
import 'directive_metadata_linker/all_tests.dart' as directiveMeta;
import 'directive_processor/all_tests.dart' as directiveProcessor;
import 'inliner_for_test/all_tests.dart' as inliner;
@ -22,7 +22,6 @@ main() {
describe('AsyncStringWriter', asyncStringWriter.allTests);
describe('NgMeta', ngMetaTest.allTests);
describe('Bind Generator', bindGenerator.allTests);
describe('Directive Linker', directiveLinker.allTests);
describe('Directive Metadata Linker', directiveMeta.allTests);
describe('Directive Processor', directiveProcessor.allTests);
describe('Inliner For Test', inliner.allTests);
@ -30,6 +29,7 @@ main() {
describe('Template Compiler', templateCompiler.allTests);
describe('Deferred Rewriter', deferredRewriter.allTests);
describe('Stylesheet Compiler', stylesheetCompiler.allTests);
describe('Url Resolver', urlResolver.allTests);
// NOTE(kegluneq): These use `code_transformers#testPhases`, which is not
// designed to work with `guinness`.
group('Transformer Pipeline', integration.allTests);