diff --git a/modules/playground/pubspec.yaml b/modules/playground/pubspec.yaml index 2ba589070b..f65b56dfc8 100644 --- a/modules/playground/pubspec.yaml +++ b/modules/playground/pubspec.yaml @@ -17,7 +17,7 @@ dependency_overrides: path: ../angular2_material transformers: - angular2: - ambient_directives: 'angular2/lib/src/core/directives.dart:CORE_DIRECTIVES' + ambient_directives: 'package:angular2/src/core/directives.dart#CORE_DIRECTIVES' entry_points: - web/src/gestures/index.dart - web/src/hello_world/index.dart diff --git a/modules_dart/transform/lib/src/transform/common/options.dart b/modules_dart/transform/lib/src/transform/common/options.dart index a812f43d9f..7d1ca8da8c 100644 --- a/modules_dart/transform/lib/src/transform/common/options.dart +++ b/modules_dart/transform/lib/src/transform/common/options.dart @@ -11,6 +11,7 @@ const FORMAT_CODE_PARAM = 'format_code'; const REFLECT_PROPERTIES_AS_ATTRIBUTES = 'reflect_properties_as_attributes'; // TODO(kegluenq): Remove this after 30 Nov (i/5108). const REFLECT_PROPERTIES_AS_ATTRIBUTES_OLD = 'reflectPropertiesAsAttributes'; +const AMBIENT_DIRECTIVES = 'ambient_directives'; const INIT_REFLECTOR_PARAM = 'init_reflector'; const INLINE_VIEWS_PARAM = 'inline_views'; const MIRROR_MODE_PARAM = 'mirror_mode'; @@ -39,6 +40,10 @@ class TransformerOptions { /// as attributes on DOM elements, which may aid in application debugging. final bool reflectPropertiesAsAttributes; + /// A set of directives that will be automatically passed-in to the template compiler + /// Format of an item in the list: angular2/lib/src/core/directives.dart#CORE_DIRECTIVES + final List ambientDirectives; + /// 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. @@ -59,6 +64,7 @@ class TransformerOptions { this.initReflector, this.annotationMatcher, {this.reflectPropertiesAsAttributes, + this.ambientDirectives, this.inlineViews, this.formatCode}); @@ -69,6 +75,7 @@ class TransformerOptions { List customAnnotationDescriptors: const [], bool inlineViews: false, bool reflectPropertiesAsAttributes: true, + List ambientDirectives, bool formatCode: false}) { var annotationMatcher = new AnnotationMatcher() ..addAll(customAnnotationDescriptors); @@ -78,6 +85,7 @@ class TransformerOptions { return new TransformerOptions._internal(entryPoints, entryPointGlobs, modeName, mirrorMode, initReflector, annotationMatcher, reflectPropertiesAsAttributes: reflectPropertiesAsAttributes, + ambientDirectives: ambientDirectives, inlineViews: inlineViews, formatCode: formatCode); } diff --git a/modules_dart/transform/lib/src/transform/common/options_reader.dart b/modules_dart/transform/lib/src/transform/common/options_reader.dart index e755b7bab6..7c9a0ad051 100644 --- a/modules_dart/transform/lib/src/transform/common/options_reader.dart +++ b/modules_dart/transform/lib/src/transform/common/options_reader.dart @@ -8,7 +8,7 @@ import 'options.dart'; TransformerOptions parseBarbackSettings(BarbackSettings settings) { var config = settings.configuration; _warnDeprecated(config); - var entryPoints = _readFileList(config, ENTRY_POINT_PARAM); + var entryPoints = _readStringList(config, ENTRY_POINT_PARAM); var initReflector = _readBool(config, INIT_REFLECTOR_PARAM, defaultValue: true); var reflectPropertiesAsAttributes = @@ -18,6 +18,7 @@ TransformerOptions parseBarbackSettings(BarbackSettings settings) { config, REFLECT_PROPERTIES_AS_ATTRIBUTES_OLD, defaultValue: false); } + var ambientDirectives = _readStringList(config, AMBIENT_DIRECTIVES); var formatCode = _readBool(config, FORMAT_CODE_PARAM, defaultValue: false); String mirrorModeVal = config.containsKey(MIRROR_MODE_PARAM) ? config[MIRROR_MODE_PARAM] : ''; @@ -39,6 +40,7 @@ TransformerOptions parseBarbackSettings(BarbackSettings settings) { initReflector: initReflector, customAnnotationDescriptors: _readCustomAnnotations(config), reflectPropertiesAsAttributes: reflectPropertiesAsAttributes, + ambientDirectives: ambientDirectives, inlineViews: _readBool(config, INLINE_VIEWS_PARAM, defaultValue: false), formatCode: formatCode); } @@ -51,16 +53,16 @@ bool _readBool(Map config, String paramName, {bool defaultValue}) { /// Cribbed from the polymer project. /// {@link https://github.com/dart-lang/polymer-dart} -List _readFileList(Map config, String paramName) { +List _readStringList(Map config, String paramName) { var value = config[paramName]; if (value == null) return null; - var files = []; + var result = []; bool error = false; if (value is List) { - files = value; + result = value; error = value.any((e) => e is! String); } else if (value is String) { - files = [value]; + result = [value]; error = false; } else { error = true; @@ -68,7 +70,7 @@ List _readFileList(Map config, String paramName) { if (error) { print('Invalid value for "$paramName" in the Angular 2 transformer.'); } - return files; + return result; } /// Parse the [CUSTOM_ANNOTATIONS_PARAM] options out of the transformer into diff --git a/modules_dart/transform/lib/src/transform/template_compiler/compile_data_creator.dart b/modules_dart/transform/lib/src/transform/template_compiler/compile_data_creator.dart index 77d023f054..79685dbad6 100644 --- a/modules_dart/transform/lib/src/transform/template_compiler/compile_data_creator.dart +++ b/modules_dart/transform/lib/src/transform/template_compiler/compile_data_creator.dart @@ -20,9 +20,9 @@ import 'package:barback/barback.dart'; /// The returned value wraps the [NgDepsModel] at `assetId` as well as these /// created objects. Future createCompileData( - AssetReader reader, AssetId assetId) async { + AssetReader reader, AssetId assetId, List ambientDirectives) async { return logElapsedAsync(() async { - final creator = await _CompileDataCreator.create(reader, assetId); + final creator = await _CompileDataCreator.create(reader, assetId, ambientDirectives); return creator != null ? creator.createCompileData() : null; }, operationName: 'createCompileData', assetId: assetId); } @@ -41,17 +41,18 @@ class _CompileDataCreator { final AssetReader reader; final AssetId entryPoint; final NgMeta ngMeta; + final List ambientDirectives; - _CompileDataCreator(this.reader, this.entryPoint, this.ngMeta); + _CompileDataCreator(this.reader, this.entryPoint, this.ngMeta, this.ambientDirectives); static Future<_CompileDataCreator> create( - AssetReader reader, AssetId assetId) async { + AssetReader reader, AssetId assetId, List ambientDirectives) async { if (!(await reader.hasInput(assetId))) return null; final json = await reader.readAsString(assetId); if (json == null || json.isEmpty) return null; final ngMeta = new NgMeta.fromJson(JSON.decode(json)); - return new _CompileDataCreator(reader, assetId, ngMeta); + return new _CompileDataCreator(reader, assetId, ngMeta, ambientDirectives); } NgDepsModel get ngDeps => ngMeta.ngDeps; @@ -64,6 +65,7 @@ class _CompileDataCreator { final compileData = {}; final ngMetaMap = await _extractNgMeta(); + final ambientDirectives = await _readAmbientDirectives(); for (var reflectable in ngDeps.reflectables) { if (ngMeta.types.containsKey(reflectable.name)) { @@ -71,6 +73,8 @@ class _CompileDataCreator { if (compileDirectiveMetadata.template != null) { final compileDatum = new NormalizedComponentWithViewDirectives( compileDirectiveMetadata, []); + compileDatum.directives.addAll(ambientDirectives); + for (var dep in reflectable.directives) { if (!ngMetaMap.containsKey(dep.prefix)) { logger.warning( @@ -99,6 +103,50 @@ class _CompileDataCreator { return new CompileDataResults._(ngMeta, compileData); } + Future> _readAmbientDirectives() async { + if (ambientDirectives == null) return const []; + + final res = []; + for (var ad in ambientDirectives) { + final parts = ad.split("#"); + if (parts.length != 2) { + logger.warning('The ambient directives configuration option ' + 'must be in the following format: "URI#TOKEN"'); + return const []; + } + res.addAll(await _readAmbientDirectivesFromUri(parts[0], parts[1])); + } + return res; + } + + Future> _readAmbientDirectivesFromUri(String uri, String token) async { + final metaAssetId = fromUri(toMetaExtension(uri)); + if (await reader.hasInput(metaAssetId)) { + try { + var jsonString = await reader.readAsString(metaAssetId); + if (jsonString != null && jsonString.isNotEmpty) { + var newMetadata = new NgMeta.fromJson(JSON.decode(jsonString)); + + if (newMetadata.types.containsKey(token)) { + return [newMetadata.types[token]]; + + } else if (newMetadata.aliases.containsKey(token)) { + return newMetadata.flatten(token); + + } else { + logger.warning('Could not resolve ambient directive ${token} in ${uri}', + asset: metaAssetId); + } + + } + } catch (ex, stackTrace) { + logger.warning('Failed to decode: $ex, $stackTrace', + asset: metaAssetId); + } + } + return []; + } + /// Creates a map from import prefix to the asset: uris of all `.dart` /// libraries visible from `entryPoint`, excluding `dart:` and `.ng_deps.dart` /// files it imports. Unprefixed imports have the empty string as their key. diff --git a/modules_dart/transform/lib/src/transform/template_compiler/generator.dart b/modules_dart/transform/lib/src/transform/template_compiler/generator.dart index 5a9ba0780e..ba8041dc16 100644 --- a/modules_dart/transform/lib/src/transform/template_compiler/generator.dart +++ b/modules_dart/transform/lib/src/transform/template_compiler/generator.dart @@ -26,8 +26,8 @@ import 'compile_data_creator.dart'; /// /// This method assumes a {@link DomAdapter} has been registered. Future processTemplates(AssetReader reader, AssetId assetId, - {bool reflectPropertiesAsAttributes: false}) async { - var viewDefResults = await createCompileData(reader, assetId); + {bool reflectPropertiesAsAttributes: false, List ambientDirectives}) async { + var viewDefResults = await createCompileData(reader, assetId, ambientDirectives); if (viewDefResults == null) return null; final directiveMetadatas = viewDefResults.ngMeta.types.values; if (directiveMetadatas.isNotEmpty) { diff --git a/modules_dart/transform/lib/src/transform/template_compiler/transformer.dart b/modules_dart/transform/lib/src/transform/template_compiler/transformer.dart index ab34152d62..e4825ebc42 100644 --- a/modules_dart/transform/lib/src/transform/template_compiler/transformer.dart +++ b/modules_dart/transform/lib/src/transform/template_compiler/transformer.dart @@ -37,7 +37,8 @@ class TemplateCompiler extends Transformer { var primaryId = transform.primaryInput.id; var reader = new AssetReader.fromTransform(transform); var outputs = await processTemplates(reader, primaryId, - reflectPropertiesAsAttributes: options.reflectPropertiesAsAttributes); + reflectPropertiesAsAttributes: options.reflectPropertiesAsAttributes, + ambientDirectives: options.ambientDirectives); var ngDepsCode = _emptyNgDepsContents; var templatesCode = ''; if (outputs != null) { diff --git a/modules_dart/transform/test/transform/template_compiler/all_tests.dart b/modules_dart/transform/test/transform/template_compiler/all_tests.dart index 340559e07c..62878c692c 100644 --- a/modules_dart/transform/test/transform/template_compiler/all_tests.dart +++ b/modules_dart/transform/test/transform/template_compiler/all_tests.dart @@ -74,9 +74,10 @@ void allTests() { updateReader(); }); - Future process(AssetId assetId) { + Future process(AssetId assetId, {List ambientDirectives}) { logger = new RecordingLogger(); - return log.setZoned(logger, () => processTemplates(reader, assetId)); + return log.setZoned(logger, () => processTemplates(reader, assetId, + ambientDirectives: ambientDirectives)); } // TODO(tbosch): This is just a temporary test that makes sure that the dart @@ -346,6 +347,65 @@ void allTests() { expect(didThrow).toBeFalse(); }); + + it('should include ambient directives.', () async { + fooComponentMeta.template = new CompileTemplateMetadata(template: ''); + final viewAnnotation = new AnnotationModel() + ..name = 'View' + ..isView = true; + + barNgMeta.aliases['AMBIENT'] = [barComponentMeta.type.name]; + updateReader(); + + final outputs = await process(fooAssetId, ambientDirectives: ['package:a/bar.dart#AMBIENT']); + final ngDeps = outputs.ngDeps; + expect(ngDeps).toBeNotNull(); + expect(outputs.templatesCode) + ..toBeNotNull() + ..toContain(barComponentMeta.template.template); + }); + + it('should include ambient directives when it it a list.', () async { + fooComponentMeta.template = new CompileTemplateMetadata(template: ''); + final viewAnnotation = new AnnotationModel() + ..name = 'View' + ..isView = true; + + barNgMeta.types['AMBIENT'] = barComponentMeta; + updateReader(); + + final outputs = await process(fooAssetId, ambientDirectives: ['package:a/bar.dart#AMBIENT']); + final ngDeps = outputs.ngDeps; + expect(ngDeps).toBeNotNull(); + expect(outputs.templatesCode) + ..toBeNotNull() + ..toContain(barComponentMeta.template.template); + }); + + it('should work when ambient directives config is null.', () async { + final outputs = await process(fooAssetId, ambientDirectives: null); + final ngDeps = outputs.ngDeps; + expect(ngDeps).toBeNotNull(); + }); + + it('should work when the ambient directives config is not formatted properly.', () async { + final outputs = await process(fooAssetId, ambientDirectives: ['INVALID']); + final ngDeps = outputs.ngDeps; + expect(ngDeps).toBeNotNull(); + }); + + it('should work when the file with ambient directives cannot be found.', () async { + final outputs = await process( + fooAssetId, ambientDirectives: ['package:a/invalid.dart#AMBIENT']); + final ngDeps = outputs.ngDeps; + expect(ngDeps).toBeNotNull(); + }); + + it('should work when the ambient directives token cannot be found.', () async { + final outputs = await process(fooAssetId, ambientDirectives: ['package:a/bar.dart#AMBIENT']); + final ngDeps = outputs.ngDeps; + expect(ngDeps).toBeNotNull(); + }); } void _formatThenExpectEquals(String actual, String expected) {