diff --git a/modules/angular2/src/forms/form_builder.ts b/modules/angular2/src/forms/form_builder.ts index 3448d44ecb..50a5d04e2f 100644 --- a/modules/angular2/src/forms/form_builder.ts +++ b/modules/angular2/src/forms/form_builder.ts @@ -1,3 +1,4 @@ +import {Injectable} from 'angular2/di'; import {StringMapWrapper, ListWrapper, List} from 'angular2/src/facade/collection'; import {isPresent, isArray} from 'angular2/src/facade/lang'; import * as modelModule from './model'; @@ -67,6 +68,7 @@ import * as modelModule from './model'; * * ``` */ +@Injectable() export class FormBuilder { group(controlsConfig: StringMap, extra: StringMap = null): modelModule.ControlGroup { diff --git a/modules/angular2/src/transform/common/names.dart b/modules/angular2/src/transform/common/names.dart index 2386031b18..66d506c080 100644 --- a/modules/angular2/src/transform/common/names.dart +++ b/modules/angular2/src/transform/common/names.dart @@ -6,6 +6,9 @@ const REFLECTOR_VAR_NAME = 'reflector'; const TRANSFORM_DYNAMIC_MODE = 'transform_dynamic'; const DEPS_EXTENSION = '.ng_deps.dart'; const META_EXTENSION = '.ng_meta.json'; +// TODO(sigmund): consider merging into .ng_meta by generating local metadata +// upfront (rather than extracting it from ng_deps). +const ALIAS_EXTENSION = '.aliases.json'; const REFLECTION_CAPABILITIES_NAME = 'ReflectionCapabilities'; const REGISTER_TYPE_METHOD_NAME = 'registerType'; const REGISTER_GETTERS_METHOD_NAME = 'registerGetters'; @@ -20,6 +23,10 @@ String toMetaExtension(String uri) => String toDepsExtension(String uri) => _toExtension(uri, const [META_EXTENSION, '.dart'], DEPS_EXTENSION); +/// Returns `uri` with its extension updated to [ALIAS_EXTENSION]. +String toAliasExtension(String uri) => + _toExtension(uri, const [DEPS_EXTENSION, '.dart'], ALIAS_EXTENSION); + /// Returns `uri` with its extension updated to `toExtension` if its /// extension is currently in `fromExtension`. String _toExtension( diff --git a/modules/angular2/src/transform/common/ng_meta.dart b/modules/angular2/src/transform/common/ng_meta.dart new file mode 100644 index 0000000000..eca4f97e50 --- /dev/null +++ b/modules/angular2/src/transform/common/ng_meta.dart @@ -0,0 +1,88 @@ +library angular2.transform.common.ng_meta; + +import 'package:angular2/src/render/api.dart'; +import 'package:angular2/src/render/dom/convert.dart'; +import 'logging.dart'; + +/// Metadata about directives and directive aliases. +/// +/// [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. +/// +/// Instances of this class are serialized into `.aliases.json` and +/// `.ng_meta.json` files as intermediate assets to make the compilation process +/// easier. +class NgMeta { + /// Directive metadata for each type annotated as a directive. + final Map types; + + /// List of other types and names associated with a given name. + final Map> aliases; + + NgMeta(this.types, this.aliases); + + NgMeta.empty() : this({}, {}); + + bool get isEmpty => types.isEmpty && aliases.isEmpty; + + /// Parse from the serialized form produced by [toJson]. + factory NgMeta.fromJson(Map json) { + var types = {}; + var aliases = {}; + for (var key in json.keys) { + var entry = json[key]; + if (entry['kind'] == 'type') { + types[key] = directiveMetadataFromMap(entry['value']); + } else if (entry['kind'] == 'alias') { + aliases[key] = entry['value']; + } + } + return new NgMeta(types, aliases); + } + + /// Serialized representation of this instance. + Map toJson() { + var result = {}; + types.forEach((k, v) { + result[k] = {'kind': 'type', 'value': directiveMetadataToMap(v)}; + }); + + aliases.forEach((k, v) { + result[k] = {'kind': 'alias', 'value': v}; + }); + return result; + } + + /// Merge into this instance all information from [other]. + void addAll(NgMeta other) { + types.addAll(other.types); + aliases.addAll(other.aliases); + } + + /// Returns the metadata for every type associated with the given [alias]. + List flatten(String alias) { + var result = []; + var seen = new Set(); + helper(name) { + if (!seen.add(name)) { + logger.warning('Circular alias dependency for "$name".'); + return; + } + if (types.containsKey(name)) { + result.add(types[name]); + } else if (aliases.containsKey(name)) { + aliases[name].forEach(helper); + } else { + logger.warning('Unknown alias: "$name".'); + } + } + helper(alias); + return result; + } +} diff --git a/modules/angular2/src/transform/directive_metadata_extractor/extractor.dart b/modules/angular2/src/transform/directive_metadata_extractor/extractor.dart index 67803e9bdb..667ea97395 100644 --- a/modules/angular2/src/transform/directive_metadata_extractor/extractor.dart +++ b/modules/angular2/src/transform/directive_metadata_extractor/extractor.dart @@ -1,38 +1,49 @@ library angular2.transform.directive_metadata_extractor.extractor; import 'dart:async'; +import 'dart:convert'; import 'package:analyzer/analyzer.dart'; -import 'package:angular2/src/render/api.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/ng_deps.dart'; +import 'package:angular2/src/transform/common/ng_meta.dart'; import 'package:barback/barback.dart'; import 'package:code_transformers/assets.dart'; -/// Returns a map from a class name (that is, its `Identifier` stringified) -/// to [DirectiveMetadata] for all `Directive`-annotated classes visible -/// in a file importing `entryPoint`. That is, this includes all -/// `Directive` annotated classes in `entryPoint` and any assets which it -/// `export`s. -/// Returns `null` if there are no `Directive`-annotated classes in -/// `entryPoint`. -Future> extractDirectiveMetadata( - AssetReader reader, AssetId entryPoint) async { +/// Returns [NgMeta] associated with [entryPoint]. +/// +/// This includes entries for every `Directive`-annotated classes and +/// constants that match the directive-aliases pattern, which are visible in a +/// file importing `entryPoint`. That is, this includes all `Directive` +/// annotated public classes in `entryPoint`, all `DirectiveAlias` annotated +/// public variables, and any assets which it `export`s. Returns an empty +/// [NgMeta] if there are no `Directive`-annotated classes in `entryPoint`. +Future extractDirectiveMetadata( + AssetReader reader, AssetId entryPoint) { return _extractDirectiveMetadataRecursive(reader, entryPoint); } var _nullFuture = new Future.value(null); -Future> _extractDirectiveMetadataRecursive( +Future _extractDirectiveMetadataRecursive( AssetReader reader, AssetId entryPoint) async { - if (!(await reader.hasInput(entryPoint))) return null; + var ngMeta = new NgMeta.empty(); + if (!(await reader.hasInput(entryPoint))) return ngMeta; var ngDeps = await NgDeps.parse(reader, entryPoint); - var baseMap = _metadataMapFromNgDeps(ngDeps); + _extractMetadata(ngDeps, ngMeta); - return Future.wait(ngDeps.exports.map((export) { + var aliasesFile = + new AssetId(entryPoint.package, toAliasExtension(entryPoint.path)); + + if (await reader.hasInput(aliasesFile)) { + ngMeta.addAll(new NgMeta.fromJson( + JSON.decode(await reader.readAsString(aliasesFile)))); + } + + await Future.wait(ngDeps.exports.map((export) { var uri = stringLiteralToString(export.uri); if (uri.startsWith('dart:')) return _nullFuture; @@ -41,25 +52,16 @@ Future> _extractDirectiveMetadataRecursive( errorOnAbsolute: false); if (assetId == entryPoint) return _nullFuture; return _extractDirectiveMetadataRecursive(reader, assetId) - .then((exportMap) { - if (exportMap != null) { - if (baseMap == null) { - baseMap = exportMap; - } else { - baseMap.addAll(exportMap); - } - } - }); - })).then((_) => baseMap); + .then(ngMeta.addAll); + })); + return ngMeta; } -Map _metadataMapFromNgDeps(NgDeps ngDeps) { - if (ngDeps == null || ngDeps.registeredTypes.isEmpty) return null; - var retVal = {}; - ngDeps.registeredTypes.forEach((rType) { - if (rType.directiveMetadata != null) { - retVal['${rType.typeName}'] = rType.directiveMetadata; - } +// TODO(sigmund): rather than having to parse it from generated code. we should +// be able to produce directly all information we need for ngMeta. +void _extractMetadata(NgDeps ngDeps, NgMeta ngMeta) { + if (ngDeps == null) return; + ngDeps.registeredTypes.forEach((type) { + ngMeta.types[type.typeName.name] = type.directiveMetadata; }); - return retVal; } diff --git a/modules/angular2/src/transform/directive_metadata_extractor/transformer.dart b/modules/angular2/src/transform/directive_metadata_extractor/transformer.dart index cfc4bab328..b7762e9815 100644 --- a/modules/angular2/src/transform/directive_metadata_extractor/transformer.dart +++ b/modules/angular2/src/transform/directive_metadata_extractor/transformer.dart @@ -3,7 +3,6 @@ library angular2.transform.directive_metadata_extractor.transformer; import 'dart:async'; import 'dart:convert'; -import 'package:angular2/src/render/dom/convert.dart'; 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'; @@ -29,14 +28,10 @@ class DirectiveMetadataExtractor extends Transformer { var reader = new AssetReader.fromTransform(transform); var fromAssetId = transform.primaryInput.id; - var metadataMap = await extractDirectiveMetadata(reader, fromAssetId); - if (metadataMap != null) { - var jsonMap = {}; - metadataMap.forEach((k, v) { - jsonMap[k] = directiveMetadataToMap(v); - }); + var ngMeta = await extractDirectiveMetadata(reader, fromAssetId); + if (ngMeta != null && !ngMeta.isEmpty) { transform.addOutput(new Asset.fromString( - _outputAssetId(fromAssetId), _encoder.convert(jsonMap))); + _outputAssetId(fromAssetId), _encoder.convert(ngMeta.toJson()))); } }, errorMessage: 'Extracting ng metadata failed.'); } diff --git a/modules/angular2/src/transform/directive_processor/rewriter.dart b/modules/angular2/src/transform/directive_processor/rewriter.dart index af47bdc54c..aaad2888c0 100644 --- a/modules/angular2/src/transform/directive_processor/rewriter.dart +++ b/modules/angular2/src/transform/directive_processor/rewriter.dart @@ -11,6 +11,7 @@ import 'package:angular2/src/transform/common/interface_matcher.dart'; import 'package:angular2/src/transform/common/logging.dart'; import 'package:angular2/src/transform/common/names.dart'; import 'package:angular2/src/transform/common/xhr_impl.dart'; +import 'package:angular2/src/transform/common/ng_meta.dart'; import 'package:barback/barback.dart' show AssetId; import 'package:path/path.dart' as path; @@ -23,14 +24,19 @@ import 'visitors.dart'; /// 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 createNgDeps( - AssetReader reader, AssetId assetId, AnnotationMatcher annotationMatcher, +Future createNgDeps(AssetReader reader, AssetId assetId, + AnnotationMatcher annotationMatcher, NgMeta ngMeta, {bool inlineViews}) async { // TODO(kegluneq): Shortcut if we can determine that there are no // [Directive]s present, taking into account `export`s. var writer = new AsyncStringWriter(); - var visitor = new CreateNgDepsVisitor(writer, assetId, - new XhrImpl(reader, assetId), annotationMatcher, _interfaceMatcher, + var visitor = new CreateNgDepsVisitor( + writer, + assetId, + new XhrImpl(reader, assetId), + annotationMatcher, + _interfaceMatcher, + ngMeta, inlineViews: inlineViews); var code = await reader.readAsString(assetId); parseCompilationUnit(code, name: assetId.path).accept(visitor); @@ -49,10 +55,19 @@ InterfaceMatcher _interfaceMatcher = new InterfaceMatcher(); /// associated .ng_deps.dart file. class CreateNgDepsVisitor extends Object with SimpleAstVisitor { final AsyncStringWriter writer; + + /// Output ngMeta information about aliases. + // TODO(sigmund): add more to ngMeta. Currently this only contains aliasing + // information, but we could produce here all the metadata we need and avoid + // parsing the ngdeps files later. + final NgMeta ngMeta; + /// Whether an Angular 2 `Injectable` has been found. bool _foundNgInjectable = false; + /// Whether this library `imports` or `exports` any non-'dart:' libraries. bool _usesNonLangLibs = false; + /// Whether we have written an import of base file /// (the file we are processing). bool _wroteBaseLibImport = false; @@ -66,8 +81,13 @@ class CreateNgDepsVisitor extends Object with SimpleAstVisitor { /// The assetId for the file which we are parsing. final AssetId assetId; - CreateNgDepsVisitor(AsyncStringWriter writer, AssetId assetId, XHR xhr, - AnnotationMatcher annotationMatcher, InterfaceMatcher interfaceMatcher, + CreateNgDepsVisitor( + AsyncStringWriter writer, + AssetId assetId, + XHR xhr, + AnnotationMatcher annotationMatcher, + InterfaceMatcher interfaceMatcher, + this.ngMeta, {bool inlineViews}) : writer = writer, _copyVisitor = new ToSourceVisitor(writer), @@ -223,6 +243,29 @@ class CreateNgDepsVisitor extends Object with SimpleAstVisitor { return null; } + @override + Object visitTopLevelVariableDeclaration(TopLevelVariableDeclaration node) { + // We process any top-level declaration that fits the directive-alias + // declaration pattern. Ideally we would use an annotation on the field to + // help us filter out only what's needed, but unfortunately TypeScript + // doesn't support decorators on variable declarations (see + // angular/angular#1747 and angular/ts2dart#249 for context). + outer: for (var variable in node.variables.variables) { + var initializer = variable.initializer; + if (initializer != null && initializer is ListLiteral) { + var otherNames = []; + for (var exp in initializer.elements) { + // Only simple identifiers are supported for now. + // TODO(sigmund): add support for prefixes (see issue #3232). + if (exp is! SimpleIdentifier) continue outer; + otherNames.add(exp.name); + } + ngMeta.aliases[variable.name.name] = otherNames; + } + } + return null; + } + Object _nodeToSource(AstNode node) { if (node == null) return null; return node.accept(_copyVisitor); diff --git a/modules/angular2/src/transform/directive_processor/transformer.dart b/modules/angular2/src/transform/directive_processor/transformer.dart index 008f701dab..41796a4621 100644 --- a/modules/angular2/src/transform/directive_processor/transformer.dart +++ b/modules/angular2/src/transform/directive_processor/transformer.dart @@ -1,11 +1,13 @@ library angular2.transform.directive_processor.transformer; import 'dart:async'; +import 'dart:convert'; 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'; @@ -32,8 +34,9 @@ class DirectiveProcessor extends Transformer { await log.initZoned(transform, () async { var asset = transform.primaryInput; var reader = new AssetReader.fromTransform(transform); + var ngMeta = new NgMeta.empty(); var ngDepsSrc = await createNgDeps( - reader, asset.id, options.annotationMatcher, + reader, asset.id, options.annotationMatcher, ngMeta, inlineViews: options.inlineViews); if (ngDepsSrc != null && ngDepsSrc.isNotEmpty) { var ngDepsAssetId = @@ -44,6 +47,12 @@ class DirectiveProcessor extends Transformer { } transform.addOutput(new Asset.fromString(ngDepsAssetId, ngDepsSrc)); } + if (!ngMeta.isEmpty) { + var ngAliasesId = + transform.primaryInput.id.changeExtension(ALIAS_EXTENSION); + transform.addOutput(new Asset.fromString(ngAliasesId, + new JsonEncoder.withIndent(" ").convert(ngMeta.toJson()))); + } }, errorMessage: 'Processing ng directives failed.'); } } diff --git a/modules/angular2/src/transform/template_compiler/view_definition_creator.dart b/modules/angular2/src/transform/template_compiler/view_definition_creator.dart index 8b1230c239..4fe0dd936c 100644 --- a/modules/angular2/src/transform/template_compiler/view_definition_creator.dart +++ b/modules/angular2/src/transform/template_compiler/view_definition_creator.dart @@ -5,11 +5,11 @@ import 'dart:convert'; import 'package:analyzer/analyzer.dart'; import 'package:angular2/src/render/api.dart'; -import 'package:angular2/src/render/dom/convert.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/ng_deps.dart'; +import 'package:angular2/src/transform/common/ng_meta.dart'; import 'package:barback/barback.dart'; import 'package:code_transformers/assets.dart'; @@ -60,18 +60,20 @@ class _ViewDefinitionCreator { var ngDeps = await ngDepsFuture; var retVal = {}; - var visitor = new _TemplateExtractVisitor(await _createMetadataMap()); + var visitor = new _TemplateExtractVisitor(await _extractNgMeta()); ngDeps.registeredTypes.forEach((rType) { visitor.reset(); rType.annotations.accept(visitor); if (visitor.viewDef != null) { + // Note: we use '' because the current file maps to the default prefix. + var ngMeta = visitor._metadataMap['']; var typeName = '${rType.typeName}'; var hostMetadata = null; - if (visitor._metadataMap.containsKey(typeName)) { - hostMetadata = visitor._metadataMap[typeName]; + if (ngMeta.types.containsKey(typeName)) { + hostMetadata = ngMeta.types[typeName]; visitor.viewDef.componentId = hostMetadata.id; } else { - logger.error('Missing component "$typeName" in metadata map', + logger.warning('Missing component "$typeName" in metadata map', asset: entryPoint); visitor.viewDef.componentId = _getComponentId(entryPoint, typeName); } @@ -91,7 +93,7 @@ class _ViewDefinitionCreator { var ngDeps = await ngDepsFuture; var importAssetToPrefix = {}; - // Include the `.ng_meta.dart` file associated with `entryPoint`. + // Include the `.ng_meta.json` file associated with `entryPoint`. importAssetToPrefix[new AssetId( entryPoint.package, toMetaExtension(entryPoint.path))] = null; @@ -137,24 +139,24 @@ class _ViewDefinitionCreator { /// ...... /// } /// ``` - Future> _createMetadataMap() async { + Future> _extractNgMeta() async { var importAssetToPrefix = await _createImportAssetToPrefixMap(); - var retVal = {}; + var retVal = {}; for (var importAssetId in importAssetToPrefix.keys) { + var prefix = importAssetToPrefix[importAssetId]; + if (prefix == null) prefix = ''; + var ngMeta = retVal.putIfAbsent(prefix, () => new NgMeta.empty()); var metaAssetId = new AssetId( importAssetId.package, toMetaExtension(importAssetId.path)); if (await reader.hasInput(metaAssetId)) { try { - var json = await reader.readAsString(metaAssetId); - var jsonMap = JSON.decode(json); - jsonMap.forEach((className, metaDataMap) { - var prefixStr = importAssetToPrefix[importAssetId]; - var key = prefixStr != null ? '$prefixStr.$className' : className; - var value = directiveMetadataFromMap(metaDataMap) - ..id = _getComponentId(importAssetId, className); - retVal[key] = value; + var json = JSON.decode(await reader.readAsString(metaAssetId)); + var newMetadata = new NgMeta.fromJson(json); + newMetadata.types.forEach((className, metadata) { + metadata.id = _getComponentId(importAssetId, className); }); + ngMeta.addAll(newMetadata); } catch (ex, stackTrace) { logger.warning('Failed to decode: $ex, $stackTrace', asset: metaAssetId); @@ -169,7 +171,7 @@ class _ViewDefinitionCreator { /// [RegisterType] object and pulling out [ViewDefinition] information. class _TemplateExtractVisitor extends Object with RecursiveAstVisitor { ViewDefinition viewDef = null; - final Map _metadataMap; + final Map _metadataMap; final ConstantEvaluator _evaluator = new ConstantEvaluator(); _TemplateExtractVisitor(this._metadataMap); @@ -237,28 +239,35 @@ class _TemplateExtractVisitor extends Object with RecursiveAstVisitor { if (viewDef == null) return; if (node is! ListLiteral) { - logger.error( - 'Angular 2 currently only supports list literals as values for' - ' "directives". Source: $node'); + logger.error('Angular 2 currently only supports list literals as values ' + 'for "directives". Source: $node'); return; } var directiveList = (node as ListLiteral); for (var node in directiveList.elements) { - if (node is! SimpleIdentifier && node is! PrefixedIdentifier) { + var ngMeta; + var name; + if (node is SimpleIdentifier) { + ngMeta = _metadataMap['']; + name = node.name; + } else if (node is PrefixedIdentifier) { + ngMeta = _metadataMap[node.prefix.name]; + name = node.name; + } else { logger.error( 'Angular 2 currently only supports simple and prefixed identifiers ' 'as values for "directives". Source: $node'); return; } - var name = '$node'; - if (_metadataMap.containsKey(name)) { - viewDef.directives.add(_metadataMap[name]); + if (ngMeta.types.containsKey(name)) { + viewDef.directives.add(ngMeta.types[name]); + } else if (ngMeta.aliases.containsKey(name)) { + viewDef.directives.addAll(ngMeta.flatten(name)); } else { - logger.warning('Could not find Directive entry for $name. ' - 'Please be aware that reusable, pre-defined lists of Directives ' - '(aka "directive aliases") are not yet supported and will cause ' - 'your application to misbehave. ' - 'See https://github.com/angular/angular/issues/1747 for details.'); + logger.warning('Could not find Directive entry for $node. ' + 'Please be aware that Dart transformers have limited support for ' + 'reusable, pre-defined lists of Directives (aka ' + '"directive aliases"). See https://goo.gl/d8XPt0 for details.'); } } } diff --git a/modules/angular2/test/transform/common/ng_meta_test.dart b/modules/angular2/test/transform/common/ng_meta_test.dart new file mode 100644 index 0000000000..04bc84c7af --- /dev/null +++ b/modules/angular2/test/transform/common/ng_meta_test.dart @@ -0,0 +1,101 @@ +library angular2.test.transform.common.annotation_matcher_test; + +import 'package:angular2/src/render/api.dart'; +import 'package:angular2/src/transform/common/ng_meta.dart'; +import 'package:guinness/guinness.dart'; + +main() => allTests(); + +void allTests() { + var mockData = [ + new DirectiveMetadata(id: 'm1'), + new DirectiveMetadata(id: 'm2'), + new DirectiveMetadata(id: 'm3'), + new DirectiveMetadata(id: 'm4') + ]; + + it('should allow empty data.', () { + var ngMeta = new NgMeta.empty(); + expect(ngMeta.isEmpty).toBeTrue(); + }); + + describe('serialization', () { + it('should parse empty data correctly.', () { + var ngMeta = new NgMeta.fromJson({}); + expect(ngMeta.isEmpty).toBeTrue(); + }); + + it('should be lossless', () { + var a = new NgMeta.empty(); + a.types['T0'] = mockData[0]; + a.types['T1'] = mockData[1]; + a.types['T2'] = mockData[2]; + a.types['T3'] = mockData[3]; + a.aliases['a1'] = ['T1']; + a.aliases['a2'] = ['a1']; + a.aliases['a3'] = ['T3', 'a2']; + a.aliases['a4'] = ['a3', 'T3']; + _checkSimilar(a, new NgMeta.fromJson(a.toJson())); + }); + }); + + describe('flatten', () { + it('should include recursive aliases.', () { + var a = new NgMeta.empty(); + a.types['T0'] = mockData[0]; + a.types['T1'] = mockData[1]; + a.types['T2'] = mockData[2]; + a.types['T3'] = mockData[3]; + a.aliases['a1'] = ['T1']; + a.aliases['a2'] = ['a1']; + a.aliases['a3'] = ['T3', 'a2']; + a.aliases['a4'] = ['a3', 'T0']; + expect(a.flatten('a4')).toEqual([mockData[3], mockData[1], mockData[0]]); + }); + + it('should detect cycles.', () { + var a = new NgMeta.empty(); + a.types['T0'] = mockData[0]; + a.aliases['a1'] = ['T0', 'a1']; + a.aliases['a2'] = ['a1']; + expect(a.flatten('a1')).toEqual([mockData[0]]); + }); + }); + + describe('merge', () { + it('should merge all types on addAll', () { + var a = new NgMeta.empty(); + var b = new NgMeta.empty(); + a.types['T0'] = mockData[0]; + b.types['T1'] = mockData[1]; + a.addAll(b); + expect(a.types).toContain('T1'); + expect(a.types['T1']).toEqual(mockData[1]); + }); + + it('should merge all aliases on addAll', () { + var a = new NgMeta.empty(); + var b = new NgMeta.empty(); + a.aliases['a'] = ['x']; + b.aliases['b'] = ['y']; + a.addAll(b); + expect(a.aliases).toContain('b'); + expect(a.aliases['b']).toEqual(['y']); + }); + }); +} + +_checkSimilar(NgMeta a, NgMeta b) { + expect(a.types.length).toEqual(b.types.length); + expect(a.aliases.length).toEqual(b.aliases.length); + for (var k in a.types.keys) { + expect(b.types).toContain(k); + var at = a.types[k]; + var bt = b.types[k]; + expect(at.id).toEqual(bt.id); + } + for (var k in a.aliases.keys) { + expect(b.aliases).toContain(k); + expect(b.aliases[k]).toEqual(a.aliases[k]); + } +} diff --git a/modules/angular2/test/transform/directive_metadata_extractor/all_tests.dart b/modules/angular2/test/transform/directive_metadata_extractor/all_tests.dart index 7dc20858a1..6fd07d734e 100644 --- a/modules/angular2/test/transform/directive_metadata_extractor/all_tests.dart +++ b/modules/angular2/test/transform/directive_metadata_extractor/all_tests.dart @@ -37,9 +37,12 @@ void allTests() { }); it('should parse compile children values', () async { - var ngDeps = await NgDeps.parse(reader, new AssetId('a', - 'directive_metadata_extractor/' - 'directive_metadata_files/compile_children.ng_deps.dart')); + var ngDeps = await NgDeps.parse( + reader, + new AssetId( + 'a', + 'directive_metadata_extractor/' + 'directive_metadata_files/compile_children.ng_deps.dart')); var it = ngDeps.registeredTypes.iterator; // Unset value defaults to `true`. @@ -122,71 +125,108 @@ void allTests() { it('should fail when a class is annotated with multiple Directives.', () async { - var ngDeps = await NgDeps.parse(reader, new AssetId('a', - 'directive_metadata_extractor/' - 'directive_metadata_files/too_many_directives.ng_deps.dart')); - expect(() => ngDeps.registeredTypes.first.directiveMetadata).toThrowWith( - anInstanceOf: PrintLoggerError); + var ngDeps = await NgDeps.parse( + reader, + new AssetId( + 'a', + 'directive_metadata_extractor/' + 'directive_metadata_files/too_many_directives.ng_deps.dart')); + expect(() => ngDeps.registeredTypes.first.directiveMetadata) + .toThrowWith(anInstanceOf: PrintLoggerError); }); }); describe('extractMetadata', () { it('should generate `DirectiveMetadata` from .ng_deps.dart files.', () async { - var extracted = await extractDirectiveMetadata(reader, new AssetId( - 'a', 'directive_metadata_extractor/simple_files/foo.ng_deps.dart')); - expect(extracted).toContain('FooComponent'); + var extracted = await extractDirectiveMetadata( + reader, + new AssetId('a', + 'directive_metadata_extractor/simple_files/foo.ng_deps.dart')); + expect(extracted.types).toContain('FooComponent'); - var extractedMeta = extracted['FooComponent']; + var extractedMeta = extracted.types['FooComponent']; expect(extractedMeta.selector).toEqual('[foo]'); }); - it('should generate `DirectiveMetadata` from .ng_deps.dart files that use ' - 'automatic adjacent string concatenation.', () async { - var extracted = await extractDirectiveMetadata(reader, new AssetId('a', - 'directive_metadata_extractor/adjacent_strings_files/' - 'foo.ng_deps.dart')); - expect(extracted).toContain('FooComponent'); + it( + 'should generate `DirectiveMetadata` from .ng_deps.dart files that use ' + 'automatic adjacent string concatenation.', + () async { + var extracted = await extractDirectiveMetadata( + reader, + new AssetId( + 'a', + 'directive_metadata_extractor/adjacent_strings_files/' + 'foo.ng_deps.dart')); + expect(extracted.types).toContain('FooComponent'); - var extractedMeta = extracted['FooComponent']; + var extractedMeta = extracted.types['FooComponent']; expect(extractedMeta.selector).toEqual('[foo]'); }); it('should include `DirectiveMetadata` from exported files.', () async { - var extracted = await extractDirectiveMetadata(reader, new AssetId( - 'a', 'directive_metadata_extractor/export_files/foo.ng_deps.dart')); - expect(extracted).toContain('FooComponent'); - expect(extracted).toContain('BarComponent'); + var extracted = await extractDirectiveMetadata( + reader, + new AssetId('a', + 'directive_metadata_extractor/export_files/foo.ng_deps.dart')); + expect(extracted.types).toContain('FooComponent'); + expect(extracted.types).toContain('BarComponent'); - expect(extracted['FooComponent'].selector).toEqual('[foo]'); - expect(extracted['BarComponent'].selector).toEqual('[bar]'); + 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 extractDirectiveMetadata(reader, new AssetId('a', - 'directive_metadata_extractor/recursive_export_files/foo.ng_deps.dart')); - expect(extracted).toContain('FooComponent'); - expect(extracted).toContain('BarComponent'); - expect(extracted).toContain('BazComponent'); + var extracted = await extractDirectiveMetadata( + reader, + new AssetId('a', + 'directive_metadata_extractor/recursive_export_files/foo.ng_deps.dart')); + expect(extracted.types).toContain('FooComponent'); + expect(extracted.types).toContain('BarComponent'); + expect(extracted.types).toContain('BazComponent'); - expect(extracted['FooComponent'].selector).toEqual('[foo]'); - expect(extracted['BarComponent'].selector).toEqual('[bar]'); - expect(extracted['BazComponent'].selector).toEqual('[baz]'); + expect(extracted.types['FooComponent'].selector).toEqual('[foo]'); + expect(extracted.types['BarComponent'].selector).toEqual('[bar]'); + expect(extracted.types['BazComponent'].selector).toEqual('[baz]'); }); - it('should include `DirectiveMetadata` from exported files ' - 'expressed as absolute uris', () async { - reader.addAsset(new AssetId('bar', 'lib/bar.ng_deps.dart'), readFile( - 'directive_metadata_extractor/absolute_export_files/bar.ng_deps.dart')); + it( + 'should include `DirectiveMetadata` from exported files ' + 'expressed as absolute uris', + () async { + reader.addAsset( + new AssetId('bar', 'lib/bar.ng_deps.dart'), + readFile( + 'directive_metadata_extractor/absolute_export_files/bar.ng_deps.dart')); - var extracted = await extractDirectiveMetadata(reader, new AssetId('a', - 'directive_metadata_extractor/absolute_export_files/foo.ng_deps.dart')); - expect(extracted).toContain('FooComponent'); - expect(extracted).toContain('BarComponent'); + var extracted = await extractDirectiveMetadata( + reader, + new AssetId('a', + 'directive_metadata_extractor/absolute_export_files/foo.ng_deps.dart')); + expect(extracted.types).toContain('FooComponent'); + expect(extracted.types).toContain('BarComponent'); - expect(extracted['FooComponent'].selector).toEqual('[foo]'); - expect(extracted['BarComponent'].selector).toEqual('[bar]'); + expect(extracted.types['FooComponent'].selector).toEqual('[foo]'); + expect(extracted.types['BarComponent'].selector).toEqual('[bar]'); + }); + + it('should include directive aliases', () async { + reader.addAsset( + new AssetId('bar', 'lib/bar.ng_deps.dart'), + readFile( + 'directive_metadata_extractor/directive_aliases_files/bar.ng_deps.dart')); + + var extracted = await extractDirectiveMetadata( + reader, + new AssetId('a', + 'directive_metadata_extractor/directive_aliases_files/foo.ng_deps.dart')); + expect(extracted.aliases).toContain('alias1'); + expect(extracted.aliases).toContain('alias2'); + expect(extracted.aliases['alias1']).toContain('BarComponent'); + expect(extracted.aliases['alias2']).toContain('FooComponent'); + expect(extracted.aliases['alias2']).toContain('alias1'); }); }); } diff --git a/modules/angular2/test/transform/directive_metadata_extractor/directive_aliases_files/bar.aliases.json b/modules/angular2/test/transform/directive_metadata_extractor/directive_aliases_files/bar.aliases.json new file mode 100644 index 0000000000..3db1b66e68 --- /dev/null +++ b/modules/angular2/test/transform/directive_metadata_extractor/directive_aliases_files/bar.aliases.json @@ -0,0 +1,8 @@ +{ + "alias1": { + "kind": "alias", + "value": [ + "BarComponent" + ] + } +} diff --git a/modules/angular2/test/transform/directive_metadata_extractor/directive_aliases_files/bar.ng_deps.dart b/modules/angular2/test/transform/directive_metadata_extractor/directive_aliases_files/bar.ng_deps.dart new file mode 100644 index 0000000000..381592661d --- /dev/null +++ b/modules/angular2/test/transform/directive_metadata_extractor/directive_aliases_files/bar.ng_deps.dart @@ -0,0 +1,16 @@ +library foo.ng_deps.dart; + +import 'bar.dart'; +import 'package:angular2/src/core/annotations/annotations.dart'; + +var _visited = false; +void initReflector(reflector) { + if (_visited) return; + _visited = true; + reflector + ..registerType(BarComponent, { + 'factory': () => new BarComponent(), + 'parameters': const [], + 'annotations': const [const Component(selector: '[bar]')] + }); +} diff --git a/modules/angular2/test/transform/directive_metadata_extractor/directive_aliases_files/foo.aliases.json b/modules/angular2/test/transform/directive_metadata_extractor/directive_aliases_files/foo.aliases.json new file mode 100644 index 0000000000..3e205d4acc --- /dev/null +++ b/modules/angular2/test/transform/directive_metadata_extractor/directive_aliases_files/foo.aliases.json @@ -0,0 +1,9 @@ +{ + "alias2": { + "kind": "alias", + "value": [ + "FooComponent", + "alias1" + ] + } +} diff --git a/modules/angular2/test/transform/directive_metadata_extractor/directive_aliases_files/foo.ng_deps.dart b/modules/angular2/test/transform/directive_metadata_extractor/directive_aliases_files/foo.ng_deps.dart new file mode 100644 index 0000000000..57e2c54e79 --- /dev/null +++ b/modules/angular2/test/transform/directive_metadata_extractor/directive_aliases_files/foo.ng_deps.dart @@ -0,0 +1,20 @@ +library foo.ng_deps.dart; + +import 'foo.dart'; +import 'package:angular2/src/core/annotations/annotations.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, { + 'factory': () => new FooComponent(), + 'parameters': const [], + 'annotations': const [const Component(selector: '[foo]')] + }); + i0.initReflector(reflector); +} diff --git a/modules/angular2/test/transform/directive_processor/all_tests.dart b/modules/angular2/test/transform/directive_processor/all_tests.dart index 7b42eb0f7f..830e6ea151 100644 --- a/modules/angular2/test/transform/directive_processor/all_tests.dart +++ b/modules/angular2/test/transform/directive_processor/all_tests.dart @@ -1,10 +1,13 @@ library angular2.test.transform.directive_processor.all_tests; +import 'dart:convert'; + import 'package:barback/barback.dart'; import 'package:angular2/src/transform/directive_processor/rewriter.dart'; import 'package:angular2/src/transform/common/annotation_matcher.dart'; 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/ng_meta.dart'; import 'package:code_transformers/messages/build_logger.dart'; import 'package:dart_style/dart_style.dart'; import 'package:guinness/guinness.dart'; @@ -18,45 +21,50 @@ main() { } void allTests() { - _testNgDeps('should preserve parameter annotations as const instances.', + _testProcessor('should preserve parameter annotations as const instances.', 'parameter_metadata/soup.dart'); - _testNgDeps('should recognize custom annotations with package: imports', + _testProcessor('should recognize custom annotations with package: imports', 'custom_metadata/package_soup.dart', customDescriptors: [ const ClassDescriptor('Soup', 'package:soup/soup.dart', superClass: 'Component'), ]); - _testNgDeps('should recognize custom annotations with relative imports', + _testProcessor('should recognize custom annotations with relative imports', 'custom_metadata/relative_soup.dart', assetId: new AssetId('soup', 'lib/relative_soup.dart'), customDescriptors: [ - const ClassDescriptor('Soup', 'package:soup/annotations/soup.dart', - superClass: 'Component'), - ]); + const ClassDescriptor('Soup', 'package:soup/annotations/soup.dart', + superClass: 'Component'), + ]); - _testNgDeps('Requires the specified import.', 'custom_metadata/bad_soup.dart', + _testProcessor( + 'Requires the specified import.', 'custom_metadata/bad_soup.dart', customDescriptors: [ const ClassDescriptor('Soup', 'package:soup/soup.dart', superClass: 'Component'), ]); - _testNgDeps( + _testProcessor( 'should inline `templateUrl` values.', 'url_expression_files/hello.dart'); var absoluteReader = new TestAssetReader(); - absoluteReader.addAsset(new AssetId('other_package', 'lib/template.html'), + absoluteReader.addAsset( + new AssetId('other_package', 'lib/template.html'), readFile( 'directive_processor/absolute_url_expression_files/template.html')); - absoluteReader.addAsset(new AssetId('other_package', 'lib/template.css'), + absoluteReader.addAsset( + new AssetId('other_package', 'lib/template.css'), readFile( 'directive_processor/absolute_url_expression_files/template.css')); - _testNgDeps('should inline `templateUrl` and `styleUrls` values expressed as' - ' absolute urls.', 'absolute_url_expression_files/hello.dart', + _testProcessor( + 'should inline `templateUrl` and `styleUrls` values expressed' + ' as absolute urls.', + 'absolute_url_expression_files/hello.dart', reader: absoluteReader); - _testNgDeps( + _testProcessor( 'should inline multiple `styleUrls` values expressed as absolute urls.', 'multiple_style_urls_files/hello.dart'); @@ -64,40 +72,44 @@ void allTests() { readFile('directive_processor/multiple_style_urls_files/template.html')); absoluteReader.addAsset(new AssetId('a', 'lib/template.css'), readFile('directive_processor/multiple_style_urls_files/template.css')); - absoluteReader.addAsset(new AssetId('a', 'lib/template_other.css'), readFile( - 'directive_processor/multiple_style_urls_files/template_other.css')); - _testNgDeps( + absoluteReader.addAsset( + new AssetId('a', 'lib/template_other.css'), + readFile( + 'directive_processor/multiple_style_urls_files/template_other.css')); + _testProcessor( 'shouldn\'t inline multiple `styleUrls` values expressed as absolute ' - 'urls.', 'multiple_style_urls_not_inlined_files/hello.dart', - inlineViews: false, reader: absoluteReader); + 'urls.', + 'multiple_style_urls_not_inlined_files/hello.dart', + inlineViews: false, + reader: absoluteReader); - _testNgDeps('should inline `templateUrl`s expressed as adjacent strings.', + _testProcessor('should inline `templateUrl`s expressed as adjacent strings.', 'split_url_expression_files/hello.dart'); - _testNgDeps('should report implemented types as `interfaces`.', + _testProcessor('should report implemented types as `interfaces`.', 'interfaces_files/soup.dart'); - _testNgDeps('should not include transitively implemented types.', + _testProcessor('should not include transitively implemented types.', 'interface_chain_files/soup.dart'); - _testNgDeps('should not include superclasses in `interfaces`.', + _testProcessor('should not include superclasses in `interfaces`.', 'superclass_files/soup.dart'); - _testNgDeps( + _testProcessor( 'should populate `lifecycle` when lifecycle interfaces are present.', 'interface_lifecycle_files/soup.dart'); - _testNgDeps('should populate multiple `lifecycle` values when necessary.', + _testProcessor('should populate multiple `lifecycle` values when necessary.', 'multiple_interface_lifecycle_files/soup.dart'); - _testNgDeps( + _testProcessor( 'should populate `lifecycle` when lifecycle superclass is present.', 'superclass_lifecycle_files/soup.dart'); - _testNgDeps('should populate `lifecycle` with prefix when necessary.', + _testProcessor('should populate `lifecycle` with prefix when necessary.', 'prefixed_interface_lifecycle_files/soup.dart'); - _testNgDeps( + _testProcessor( 'should not throw/hang on invalid urls', 'invalid_url_files/hello.dart', expectedLogs: [ 'ERROR: Uri /bad/absolute/url.html not supported from angular2|test/' @@ -110,13 +122,20 @@ void allTests() { 'test/transform/directive_processor/invalid_url_files/hello.dart' ]); - _testNgDeps('should find and register static functions.', + _testProcessor('should find and register static functions.', 'static_function_files/hello.dart'); + + _testProcessor('should find direcive aliases patterns.', + 'directive_aliases_files/hello.dart', + reader: absoluteReader); } -void _testNgDeps(String name, String inputPath, - {List customDescriptors: const [], AssetId assetId, - AssetReader reader, List expectedLogs, bool inlineViews: true, +void _testProcessor(String name, String inputPath, + {List customDescriptors: const [], + AssetId assetId, + AssetReader reader, + List expectedLogs, + bool inlineViews: true, bool isolate: false}) { var testFn = isolate ? iit : it; testFn(name, () async { @@ -130,20 +149,33 @@ void _testNgDeps(String name, String inputPath, reader.addAsset(assetId, await reader.readAsString(inputId)); inputId = assetId; } - var expectedPath = path.join(path.dirname(inputPath), 'expected', + var expectedNgDepsPath = path.join(path.dirname(inputPath), 'expected', path.basename(inputPath).replaceFirst('.dart', '.ng_deps.dart')); - var expectedId = _assetIdForPath(expectedPath); + var expectedNgDepsId = _assetIdForPath(expectedNgDepsPath); + + var expectedAliasesPath = path.join(path.dirname(inputPath), 'expected', + path.basename(inputPath).replaceFirst('.dart', '.aliases.json')); + var expectedAliasesId = _assetIdForPath(expectedAliasesPath); var annotationMatcher = new AnnotationMatcher() ..addAll(customDescriptors); - var output = await createNgDeps(reader, inputId, annotationMatcher, + var ngMeta = new NgMeta.empty(); + var output = await createNgDeps( + reader, inputId, annotationMatcher, ngMeta, inlineViews: inlineViews); if (output == null) { - expect(await reader.hasInput(expectedId)).toBeFalse(); + expect(await reader.hasInput(expectedNgDepsId)).toBeFalse(); } else { - var input = await reader.readAsString(expectedId); + var input = await reader.readAsString(expectedNgDepsId); expect(formatter.format(output)).toEqual(formatter.format(input)); } + if (ngMeta.isEmpty) { + expect(await reader.hasInput(expectedAliasesId)).toBeFalse(); + } else { + var expectedJson = await reader.readAsString(expectedAliasesId); + expect(new JsonEncoder.withIndent(' ').convert(ngMeta.toJson())) + .toEqual(expectedJson.trim()); + } }); if (expectedLogs != null) { diff --git a/modules/angular2/test/transform/directive_processor/directive_aliases_files/a.dart b/modules/angular2/test/transform/directive_processor/directive_aliases_files/a.dart new file mode 100644 index 0000000000..c6691d2b6b --- /dev/null +++ b/modules/angular2/test/transform/directive_processor/directive_aliases_files/a.dart @@ -0,0 +1,3 @@ +class Bar {} + +const alias3 = const [Bar]; diff --git a/modules/angular2/test/transform/directive_processor/directive_aliases_files/b.dart b/modules/angular2/test/transform/directive_processor/directive_aliases_files/b.dart new file mode 100644 index 0000000000..f1e00d1589 --- /dev/null +++ b/modules/angular2/test/transform/directive_processor/directive_aliases_files/b.dart @@ -0,0 +1 @@ +class Baz {} diff --git a/modules/angular2/test/transform/directive_processor/directive_aliases_files/expected/hello.aliases.json b/modules/angular2/test/transform/directive_processor/directive_aliases_files/expected/hello.aliases.json new file mode 100644 index 0000000000..6cc3f59507 --- /dev/null +++ b/modules/angular2/test/transform/directive_processor/directive_aliases_files/expected/hello.aliases.json @@ -0,0 +1,15 @@ +{ + "alias1": { + "kind": "alias", + "value": [ + "HelloCmp" + ] + }, + "alias2": { + "kind": "alias", + "value": [ + "HelloCmp", + "Foo" + ] + } +} diff --git a/modules/angular2/test/transform/directive_processor/directive_aliases_files/expected/hello.ng_deps.dart b/modules/angular2/test/transform/directive_processor/directive_aliases_files/expected/hello.ng_deps.dart new file mode 100644 index 0000000000..250990a806 --- /dev/null +++ b/modules/angular2/test/transform/directive_processor/directive_aliases_files/expected/hello.ng_deps.dart @@ -0,0 +1,27 @@ +library examples.src.hello_world.absolute_url_expression_files.ng_deps.dart; + +import 'hello.dart'; +export 'hello.dart'; +import 'package:angular2/src/reflection/reflection.dart' as _ngRef; +import 'package:angular2/angular2.dart' + show bootstrap, Component, Directive, View, NgElement; +export 'a.dart' show alias3; +import 'b.dart' as b; + +var _visited = false; +void initReflector() { + if (_visited) return; + _visited = true; + _ngRef.reflector + ..registerType(HelloCmp, { + 'factory': () => new HelloCmp(), + 'parameters': const [], + 'annotations': const [ + const Component(selector: 'hello-app'), + const View( + template: r'''{{greeting}}''', + templateUrl: r'template.html', + styles: const [r'''.greeting { .color: blue; }''',]) + ] + }); +} diff --git a/modules/angular2/test/transform/directive_processor/directive_aliases_files/hello.dart b/modules/angular2/test/transform/directive_processor/directive_aliases_files/hello.dart new file mode 100644 index 0000000000..6f08ea5db7 --- /dev/null +++ b/modules/angular2/test/transform/directive_processor/directive_aliases_files/hello.dart @@ -0,0 +1,20 @@ +library examples.src.hello_world.absolute_url_expression_files; + +import 'package:angular2/angular2.dart' + show bootstrap, Component, Directive, View, NgElement; +export 'a.dart' show alias3; +import 'b.dart' as b; + +@Component(selector: 'hello-app') +@View(templateUrl: 'template.html', styleUrls: const ['template.css']) +class HelloCmp {} + +class Foo {} + +// valid +const alias1 = const [HelloCmp]; +// valid, even though it includes things that are not components +const alias2 = const [HelloCmp, Foo]; + +// Prefixed names are not supported +const alias4 = const [b.Baz]; diff --git a/modules/angular2/test/transform/directive_processor/directive_aliases_files/template.css b/modules/angular2/test/transform/directive_processor/directive_aliases_files/template.css new file mode 100644 index 0000000000..c48477652e --- /dev/null +++ b/modules/angular2/test/transform/directive_processor/directive_aliases_files/template.css @@ -0,0 +1 @@ +.greeting { .color: blue; } \ No newline at end of file diff --git a/modules/angular2/test/transform/directive_processor/directive_aliases_files/template.html b/modules/angular2/test/transform/directive_processor/directive_aliases_files/template.html new file mode 100644 index 0000000000..d75013393f --- /dev/null +++ b/modules/angular2/test/transform/directive_processor/directive_aliases_files/template.html @@ -0,0 +1 @@ +{{greeting}} \ No newline at end of file diff --git a/modules/angular2/test/transform/integration/simple_annotation_files/expected/bar.ng_meta.json b/modules/angular2/test/transform/integration/simple_annotation_files/expected/bar.ng_meta.json index 775a7253b0..e1b930e04b 100644 --- a/modules/angular2/test/transform/integration/simple_annotation_files/expected/bar.ng_meta.json +++ b/modules/angular2/test/transform/integration/simple_annotation_files/expected/bar.ng_meta.json @@ -1,23 +1,26 @@ { "MyComponent": { - "id": "MyComponent", - "selector": "[soup]", - "compileChildren": true, - "hostProperties": {}, - "hostListeners": {}, - "hostActions": {}, - "hostAttributes": {}, - "properties": [], - "readAttributes": [], - "type": 1, - "exportAs": null, - "callOnDestroy": false, - "callOnCheck": false, - "callOnInit": false, - "callOnChange": false, - "callOnAllChangesDone": false, - "events": [], - "changeDetection": null, - "version": 1 + "kind": "type", + "value": { + "id": "MyComponent", + "selector": "[soup]", + "compileChildren": true, + "hostProperties": {}, + "hostListeners": {}, + "hostActions": {}, + "hostAttributes": {}, + "properties": [], + "readAttributes": [], + "type": 1, + "exportAs": null, + "callOnDestroy": false, + "callOnCheck": false, + "callOnInit": false, + "callOnChange": false, + "callOnAllChangesDone": false, + "events": [], + "changeDetection": null, + "version": 1 + } } -} \ No newline at end of file +} diff --git a/modules/angular2/test/transform/template_compiler/all_tests.dart b/modules/angular2/test/transform/template_compiler/all_tests.dart index 2a3804d0d1..d327ceeaab 100644 --- a/modules/angular2/test/transform/template_compiler/all_tests.dart +++ b/modules/angular2/test/transform/template_compiler/all_tests.dart @@ -36,6 +36,21 @@ void changeDetectorTests() { var output = await (process(new AssetId('a', inputPath))); expect(output).toContain('notifyOnBinding'); }); + + it('should include directives mentioned in directive aliases.', () async { + // Input 2 is the same as input1, but contains the directive aliases + // inlined. + var input1Path = + 'template_compiler/directive_aliases_files/hello1.ng_deps.dart'; + var input2Path = + 'template_compiler/directive_aliases_files/hello2.ng_deps.dart'; + // Except for the directive argument in the View annotation, the generated + // change detectors are identical. + var output1 = (await process(new AssetId('a', input1Path))).replaceFirst( + 'directives: const [alias1]', 'directives: const [GoodbyeCmp]'); + var output2 = await process(new AssetId('a', input2Path)); + _formatThenExpectEquals(output1, output2); + }); } void noChangeDetectorTests() { diff --git a/modules/angular2/test/transform/template_compiler/directive_aliases_files/hello1.ng_deps.dart b/modules/angular2/test/transform/template_compiler/directive_aliases_files/hello1.ng_deps.dart new file mode 100644 index 0000000000..3498b23d83 --- /dev/null +++ b/modules/angular2/test/transform/template_compiler/directive_aliases_files/hello1.ng_deps.dart @@ -0,0 +1,28 @@ +library examples.hello_world.index_common_dart.ng_deps.dart; + +import 'hello.dart'; +import 'package:angular2/angular2.dart' + show bootstrap, Component, Directive, View, NgElement; + +var _visited = false; +void initReflector(reflector) { + if (_visited) return; + _visited = true; + reflector + ..registerType(HelloCmp, { + 'factory': () => new HelloCmp(), + 'parameters': const [const []], + 'annotations': const [ + const Component(selector: 'hello-app'), + const View(template: 'goodbye-app', directives: const [alias1]) + ] + }) + ..registerType(GoodbyeCmp, { + 'factory': () => new GoodbyeCmp(), + 'parameters': const [const []], + 'annotations': const [ + const Component(selector: 'goodbye-app'), + const View(template: 'Goodbye') + ] + }); +} diff --git a/modules/angular2/test/transform/template_compiler/directive_aliases_files/hello1.ng_meta.json b/modules/angular2/test/transform/template_compiler/directive_aliases_files/hello1.ng_meta.json new file mode 100644 index 0000000000..8eed07aa6f --- /dev/null +++ b/modules/angular2/test/transform/template_compiler/directive_aliases_files/hello1.ng_meta.json @@ -0,0 +1,35 @@ +{ + "HelloCmp": + { + "kind": "type", + "value": { + "id":"HelloCmp", + "selector":"hello-app", + "compileChildren":true, + "host":{}, + "properties":[], + "readAttributes":[], + "type":1, + "version":1 + } + }, + "GoodbyeCmp":{ + "kind": "type", + "value": { + "id":"GoodbyeCmp", + "selector":"goodbye-app", + "compileChildren":true, + "host":{}, + "properties":[], + "readAttributes":[], + "type":1, + "version":1 + } + }, + "aliases1":{ + "kind": "alias", + "value": [ + "GoodbyeCmp" + ] + } +} diff --git a/modules/angular2/test/transform/template_compiler/directive_aliases_files/hello2.ng_deps.dart b/modules/angular2/test/transform/template_compiler/directive_aliases_files/hello2.ng_deps.dart new file mode 100644 index 0000000000..4b53eab40b --- /dev/null +++ b/modules/angular2/test/transform/template_compiler/directive_aliases_files/hello2.ng_deps.dart @@ -0,0 +1,28 @@ +library examples.hello_world.index_common_dart.ng_deps.dart; + +import 'hello.dart'; +import 'package:angular2/angular2.dart' + show bootstrap, Component, Directive, View, NgElement; + +var _visited = false; +void initReflector(reflector) { + if (_visited) return; + _visited = true; + reflector + ..registerType(HelloCmp, { + 'factory': () => new HelloCmp(), + 'parameters': const [const []], + 'annotations': const [ + const Component(selector: 'hello-app'), + const View(template: 'goodbye-app', directives: const [GoodbyeCmp]) + ] + }) + ..registerType(GoodbyeCmp, { + 'factory': () => new GoodbyeCmp(), + 'parameters': const [const []], + 'annotations': const [ + const Component(selector: 'goodbye-app'), + const View(template: 'Goodbye') + ] + }); +} diff --git a/modules/angular2/test/transform/template_compiler/directive_aliases_files/hello2.ng_meta.json b/modules/angular2/test/transform/template_compiler/directive_aliases_files/hello2.ng_meta.json new file mode 100644 index 0000000000..9d87d70423 --- /dev/null +++ b/modules/angular2/test/transform/template_compiler/directive_aliases_files/hello2.ng_meta.json @@ -0,0 +1,29 @@ +{ + "HelloCmp": + { + "kind": "type", + "value": { + "id":"HelloCmp", + "selector":"hello-app", + "compileChildren":true, + "host":{}, + "properties":[], + "readAttributes":[], + "type":1, + "version":1 + } + }, + "GoodbyeCmp":{ + "kind": "type", + "value": { + "id":"GoodbyeCmp", + "selector":"goodbye-app", + "compileChildren":true, + "host":{}, + "properties":[], + "readAttributes":[], + "type":1, + "version":1 + } + } +} diff --git a/modules/angular2/test/transform/template_compiler/duplicate_files/hello.ng_meta.json b/modules/angular2/test/transform/template_compiler/duplicate_files/hello.ng_meta.json index eab28548e9..6631f51651 100644 --- a/modules/angular2/test/transform/template_compiler/duplicate_files/hello.ng_meta.json +++ b/modules/angular2/test/transform/template_compiler/duplicate_files/hello.ng_meta.json @@ -1,13 +1,16 @@ { "HelloCmp": { - "id":"HelloCmp", - "selector":"hello-app", - "compileChildren":true, - "host":{}, - "properties":[], - "readAttributes":[], - "type":1, - "version":1 + "kind": "type", + "value": { + "id":"HelloCmp", + "selector":"hello-app", + "compileChildren":true, + "host":{}, + "properties":[], + "readAttributes":[], + "type":1, + "version":1 + } } -} \ No newline at end of file +} diff --git a/modules/angular2/test/transform/template_compiler/inline_expression_files/hello.ng_meta.json b/modules/angular2/test/transform/template_compiler/inline_expression_files/hello.ng_meta.json index eab28548e9..6631f51651 100644 --- a/modules/angular2/test/transform/template_compiler/inline_expression_files/hello.ng_meta.json +++ b/modules/angular2/test/transform/template_compiler/inline_expression_files/hello.ng_meta.json @@ -1,13 +1,16 @@ { "HelloCmp": { - "id":"HelloCmp", - "selector":"hello-app", - "compileChildren":true, - "host":{}, - "properties":[], - "readAttributes":[], - "type":1, - "version":1 + "kind": "type", + "value": { + "id":"HelloCmp", + "selector":"hello-app", + "compileChildren":true, + "host":{}, + "properties":[], + "readAttributes":[], + "type":1, + "version":1 + } } -} \ No newline at end of file +} diff --git a/modules/angular2/test/transform/template_compiler/inline_method_files/hello.ng_meta.json b/modules/angular2/test/transform/template_compiler/inline_method_files/hello.ng_meta.json index eab28548e9..6631f51651 100644 --- a/modules/angular2/test/transform/template_compiler/inline_method_files/hello.ng_meta.json +++ b/modules/angular2/test/transform/template_compiler/inline_method_files/hello.ng_meta.json @@ -1,13 +1,16 @@ { "HelloCmp": { - "id":"HelloCmp", - "selector":"hello-app", - "compileChildren":true, - "host":{}, - "properties":[], - "readAttributes":[], - "type":1, - "version":1 + "kind": "type", + "value": { + "id":"HelloCmp", + "selector":"hello-app", + "compileChildren":true, + "host":{}, + "properties":[], + "readAttributes":[], + "type":1, + "version":1 + } } -} \ No newline at end of file +} diff --git a/modules/angular2/test/transform/template_compiler/ng_for_files/hello.ng_meta.json b/modules/angular2/test/transform/template_compiler/ng_for_files/hello.ng_meta.json index eab28548e9..6631f51651 100644 --- a/modules/angular2/test/transform/template_compiler/ng_for_files/hello.ng_meta.json +++ b/modules/angular2/test/transform/template_compiler/ng_for_files/hello.ng_meta.json @@ -1,13 +1,16 @@ { "HelloCmp": { - "id":"HelloCmp", - "selector":"hello-app", - "compileChildren":true, - "host":{}, - "properties":[], - "readAttributes":[], - "type":1, - "version":1 + "kind": "type", + "value": { + "id":"HelloCmp", + "selector":"hello-app", + "compileChildren":true, + "host":{}, + "properties":[], + "readAttributes":[], + "type":1, + "version":1 + } } -} \ No newline at end of file +} diff --git a/modules/angular2/test/transform/template_compiler/one_directive_files/hello.ng_meta.json b/modules/angular2/test/transform/template_compiler/one_directive_files/hello.ng_meta.json index 6bb779796b..9d87d70423 100644 --- a/modules/angular2/test/transform/template_compiler/one_directive_files/hello.ng_meta.json +++ b/modules/angular2/test/transform/template_compiler/one_directive_files/hello.ng_meta.json @@ -1,23 +1,29 @@ { "HelloCmp": { - "id":"HelloCmp", - "selector":"hello-app", - "compileChildren":true, - "host":{}, - "properties":[], - "readAttributes":[], - "type":1, - "version":1 + "kind": "type", + "value": { + "id":"HelloCmp", + "selector":"hello-app", + "compileChildren":true, + "host":{}, + "properties":[], + "readAttributes":[], + "type":1, + "version":1 + } }, "GoodbyeCmp":{ - "id":"GoodbyeCmp", - "selector":"goodbye-app", - "compileChildren":true, - "host":{}, - "properties":[], - "readAttributes":[], - "type":1, - "version":1 + "kind": "type", + "value": { + "id":"GoodbyeCmp", + "selector":"goodbye-app", + "compileChildren":true, + "host":{}, + "properties":[], + "readAttributes":[], + "type":1, + "version":1 + } } -} \ No newline at end of file +} diff --git a/modules/angular2/test/transform/template_compiler/url_expression_files/hello.ng_meta.json b/modules/angular2/test/transform/template_compiler/url_expression_files/hello.ng_meta.json index eab28548e9..6631f51651 100644 --- a/modules/angular2/test/transform/template_compiler/url_expression_files/hello.ng_meta.json +++ b/modules/angular2/test/transform/template_compiler/url_expression_files/hello.ng_meta.json @@ -1,13 +1,16 @@ { "HelloCmp": { - "id":"HelloCmp", - "selector":"hello-app", - "compileChildren":true, - "host":{}, - "properties":[], - "readAttributes":[], - "type":1, - "version":1 + "kind": "type", + "value": { + "id":"HelloCmp", + "selector":"hello-app", + "compileChildren":true, + "host":{}, + "properties":[], + "readAttributes":[], + "type":1, + "version":1 + } } -} \ No newline at end of file +} diff --git a/modules/angular2/test/transform/template_compiler/url_method_files/hello.ng_meta.json b/modules/angular2/test/transform/template_compiler/url_method_files/hello.ng_meta.json index eab28548e9..6631f51651 100644 --- a/modules/angular2/test/transform/template_compiler/url_method_files/hello.ng_meta.json +++ b/modules/angular2/test/transform/template_compiler/url_method_files/hello.ng_meta.json @@ -1,13 +1,16 @@ { "HelloCmp": { - "id":"HelloCmp", - "selector":"hello-app", - "compileChildren":true, - "host":{}, - "properties":[], - "readAttributes":[], - "type":1, - "version":1 + "kind": "type", + "value": { + "id":"HelloCmp", + "selector":"hello-app", + "compileChildren":true, + "host":{}, + "properties":[], + "readAttributes":[], + "type":1, + "version":1 + } } -} \ No newline at end of file +} diff --git a/modules/angular2/test/transform/template_compiler/with_prefix_files/goodbye.ng_meta.json b/modules/angular2/test/transform/template_compiler/with_prefix_files/goodbye.ng_meta.json index 56cf16b9e3..8fb195bb46 100644 --- a/modules/angular2/test/transform/template_compiler/with_prefix_files/goodbye.ng_meta.json +++ b/modules/angular2/test/transform/template_compiler/with_prefix_files/goodbye.ng_meta.json @@ -1,12 +1,15 @@ { "GoodbyeCmp":{ - "id":"GoodbyeCmp", - "selector":"goodbye-app", - "compileChildren":true, - "host":{}, - "properties":[], - "readAttributes":[], - "type":1, - "version":1 + "kind": "type", + "value": { + "id":"GoodbyeCmp", + "selector":"goodbye-app", + "compileChildren":true, + "host":{}, + "properties":[], + "readAttributes":[], + "type":1, + "version":1 + } } -} \ No newline at end of file +} diff --git a/modules/angular2/test/transform/template_compiler/with_prefix_files/hello.ng_meta.json b/modules/angular2/test/transform/template_compiler/with_prefix_files/hello.ng_meta.json index eab28548e9..6631f51651 100644 --- a/modules/angular2/test/transform/template_compiler/with_prefix_files/hello.ng_meta.json +++ b/modules/angular2/test/transform/template_compiler/with_prefix_files/hello.ng_meta.json @@ -1,13 +1,16 @@ { "HelloCmp": { - "id":"HelloCmp", - "selector":"hello-app", - "compileChildren":true, - "host":{}, - "properties":[], - "readAttributes":[], - "type":1, - "version":1 + "kind": "type", + "value": { + "id":"HelloCmp", + "selector":"hello-app", + "compileChildren":true, + "host":{}, + "properties":[], + "readAttributes":[], + "type":1, + "version":1 + } } -} \ No newline at end of file +} diff --git a/modules/angular2/test/transform/template_compiler/with_prefix_files/ng2_prefix.ng_meta.json b/modules/angular2/test/transform/template_compiler/with_prefix_files/ng2_prefix.ng_meta.json index 7766355a52..49ef8f2006 100644 --- a/modules/angular2/test/transform/template_compiler/with_prefix_files/ng2_prefix.ng_meta.json +++ b/modules/angular2/test/transform/template_compiler/with_prefix_files/ng2_prefix.ng_meta.json @@ -1,12 +1,15 @@ { "MyApp":{ - "id":"MyApp", - "selector":"my-app", - "compileChildren":true, - "host":{}, - "properties":[], - "readAttributes":[], - "type":1, - "version":1 + "kind": "type", + "value": { + "id":"MyApp", + "selector":"my-app", + "compileChildren":true, + "host":{}, + "properties":[], + "readAttributes":[], + "type":1, + "version":1 + } } } diff --git a/modules/angular2/test/transform/transform.server.spec.dart b/modules/angular2/test/transform/transform.server.spec.dart index 7ca7a770ba..6e92b193b1 100644 --- a/modules/angular2/test/transform/transform.server.spec.dart +++ b/modules/angular2/test/transform/transform.server.spec.dart @@ -5,6 +5,7 @@ import 'package:unittest/unittest.dart' hide expect; import 'package:unittest/vm_config.dart'; import 'common/async_string_writer_tests.dart' as asyncStringWriter; +import 'common/ng_meta_test.dart' as ngMetaTest; import 'bind_generator/all_tests.dart' as bindGenerator; import 'deferred_rewriter/all_tests.dart' as deferredRewriter; import 'directive_linker/all_tests.dart' as directiveLinker; @@ -17,6 +18,7 @@ import 'template_compiler/all_tests.dart' as templateCompiler; main() { useVMConfiguration(); describe('AsyncStringWriter', asyncStringWriter.allTests); + describe('NgMeta', ngMetaTest.allTests); describe('Bind Generator', bindGenerator.allTests); describe('Directive Linker', directiveLinker.allTests); describe('Directive Metadata Extractor', directiveMeta.allTests); diff --git a/modules/examples/pubspec.yaml b/modules/examples/pubspec.yaml index 842a7c4b34..376cb55597 100644 --- a/modules/examples/pubspec.yaml +++ b/modules/examples/pubspec.yaml @@ -30,13 +30,10 @@ transformers: - web/src/key_events/index.dart - web/src/sourcemap/index.dart - web/src/todo/index.dart - # These entrypoints are disabled until nested-directives are supported - # by transformers (issue #1747): - # web/src/model_driven_forms/index.dart - # web/src/order_management/index.dart - # web/src/person_management/index.dart - # web/src/template_driven_forms/index.dart - # + - web/src/model_driven_forms/index.dart + - web/src/order_management/index.dart + - web/src/person_management/index.dart + - web/src/template_driven_forms/index.dart # These entrypoints are disabled until cross-package urls are working (issue #2982) # - web/src/material/button/index.dart # - web/src/material/checkbox/index.dart @@ -57,13 +54,10 @@ transformers: - web/src/key_events/index.dart - web/src/sourcemap/index.dart - web/src/todo/index.dart - # These entrypoints are disabled until nested-directives are supported - # by transformers (issue #1747): - # web/src/model_driven_forms/index.dart - # web/src/order_management/index.dart - # web/src/person_management/index.dart - # web/src/template_driven_forms/index.dart - # + - web/src/model_driven_forms/index.dart + - web/src/order_management/index.dart + - web/src/person_management/index.dart + - web/src/template_driven_forms/index.dart # These entrypoints are disabled until cross-package urls are working (issue #2982) # - web/src/material/button/index.dart # - web/src/material/checkbox/index.dart diff --git a/modules/examples/src/order_management/index.ts b/modules/examples/src/order_management/index.ts index 9e7ff89d1f..cf6bb2b81f 100644 --- a/modules/examples/src/order_management/index.ts +++ b/modules/examples/src/order_management/index.ts @@ -13,6 +13,8 @@ import { EventEmitter } from 'angular2/bootstrap'; +import {Injectable} from 'angular2/di'; + import {formDirectives} from 'angular2/forms'; import {ListWrapper} from 'angular2/src/facade/collection'; @@ -44,6 +46,7 @@ class Order { // ---- services var _nextId = 1000; +@Injectable() class DataService { orderItems: OrderItem[]; orders: Order[]; diff --git a/modules/examples/src/person_management/index.ts b/modules/examples/src/person_management/index.ts index 7d7eedbdff..3bff36ff89 100644 --- a/modules/examples/src/person_management/index.ts +++ b/modules/examples/src/person_management/index.ts @@ -12,6 +12,8 @@ import { Binding } from 'angular2/bootstrap'; +import {Injectable} from 'angular2/di'; + import {formDirectives} from 'angular2/forms'; import {RegExpWrapper, print, isPresent, CONST_EXPR} from 'angular2/src/facade/lang'; @@ -50,6 +52,7 @@ class Person { // ---- services +@Injectable() class DataService { currentPerson: Person; persons: Person[]; diff --git a/tools/broccoli/trees/dart_tree.ts b/tools/broccoli/trees/dart_tree.ts index 8e06574a7e..1b1c523ec9 100644 --- a/tools/broccoli/trees/dart_tree.ts +++ b/tools/broccoli/trees/dart_tree.ts @@ -51,7 +51,8 @@ function getSourceTree() { translateBuiltins: true, }); // Native sources, dart only examples, etc. - var dartSrcs = modulesFunnel(['**/*.dart', '**/*.ng_meta.json', '**/css/**']); + var dartSrcs = + modulesFunnel(['**/*.dart', '**/*.ng_meta.json', '**/*.aliases.json', '**/css/**']); return mergeTrees([transpiled, dartSrcs]); }