feat(core): add support for ambient directives to dart transformers

Closes #5129
This commit is contained in:
vsavkin 2015-11-05 09:24:58 -08:00 committed by Victor Savkin
parent adc0e32cff
commit 4909feddde
7 changed files with 136 additions and 17 deletions

View File

@ -17,7 +17,7 @@ dependency_overrides:
path: ../angular2_material path: ../angular2_material
transformers: transformers:
- angular2: - angular2:
ambient_directives: 'angular2/lib/src/core/directives.dart:CORE_DIRECTIVES' ambient_directives: 'package:angular2/src/core/directives.dart#CORE_DIRECTIVES'
entry_points: entry_points:
- web/src/gestures/index.dart - web/src/gestures/index.dart
- web/src/hello_world/index.dart - web/src/hello_world/index.dart

View File

@ -11,6 +11,7 @@ const FORMAT_CODE_PARAM = 'format_code';
const REFLECT_PROPERTIES_AS_ATTRIBUTES = 'reflect_properties_as_attributes'; const REFLECT_PROPERTIES_AS_ATTRIBUTES = 'reflect_properties_as_attributes';
// TODO(kegluenq): Remove this after 30 Nov (i/5108). // TODO(kegluenq): Remove this after 30 Nov (i/5108).
const REFLECT_PROPERTIES_AS_ATTRIBUTES_OLD = 'reflectPropertiesAsAttributes'; const REFLECT_PROPERTIES_AS_ATTRIBUTES_OLD = 'reflectPropertiesAsAttributes';
const AMBIENT_DIRECTIVES = 'ambient_directives';
const INIT_REFLECTOR_PARAM = 'init_reflector'; const INIT_REFLECTOR_PARAM = 'init_reflector';
const INLINE_VIEWS_PARAM = 'inline_views'; const INLINE_VIEWS_PARAM = 'inline_views';
const MIRROR_MODE_PARAM = 'mirror_mode'; const MIRROR_MODE_PARAM = 'mirror_mode';
@ -39,6 +40,10 @@ class TransformerOptions {
/// as attributes on DOM elements, which may aid in application debugging. /// as attributes on DOM elements, which may aid in application debugging.
final bool reflectPropertiesAsAttributes; 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<String> ambientDirectives;
/// Whether to format generated code. /// Whether to format generated code.
/// Code that is only modified will never be formatted because doing so may /// Code that is only modified will never be formatted because doing so may
/// invalidate the source maps generated by `dart2js` and/or other tools. /// invalidate the source maps generated by `dart2js` and/or other tools.
@ -59,6 +64,7 @@ class TransformerOptions {
this.initReflector, this.initReflector,
this.annotationMatcher, this.annotationMatcher,
{this.reflectPropertiesAsAttributes, {this.reflectPropertiesAsAttributes,
this.ambientDirectives,
this.inlineViews, this.inlineViews,
this.formatCode}); this.formatCode});
@ -69,6 +75,7 @@ class TransformerOptions {
List<ClassDescriptor> customAnnotationDescriptors: const [], List<ClassDescriptor> customAnnotationDescriptors: const [],
bool inlineViews: false, bool inlineViews: false,
bool reflectPropertiesAsAttributes: true, bool reflectPropertiesAsAttributes: true,
List<String> ambientDirectives,
bool formatCode: false}) { bool formatCode: false}) {
var annotationMatcher = new AnnotationMatcher() var annotationMatcher = new AnnotationMatcher()
..addAll(customAnnotationDescriptors); ..addAll(customAnnotationDescriptors);
@ -78,6 +85,7 @@ class TransformerOptions {
return new TransformerOptions._internal(entryPoints, entryPointGlobs, return new TransformerOptions._internal(entryPoints, entryPointGlobs,
modeName, mirrorMode, initReflector, annotationMatcher, modeName, mirrorMode, initReflector, annotationMatcher,
reflectPropertiesAsAttributes: reflectPropertiesAsAttributes, reflectPropertiesAsAttributes: reflectPropertiesAsAttributes,
ambientDirectives: ambientDirectives,
inlineViews: inlineViews, inlineViews: inlineViews,
formatCode: formatCode); formatCode: formatCode);
} }

View File

@ -8,7 +8,7 @@ import 'options.dart';
TransformerOptions parseBarbackSettings(BarbackSettings settings) { TransformerOptions parseBarbackSettings(BarbackSettings settings) {
var config = settings.configuration; var config = settings.configuration;
_warnDeprecated(config); _warnDeprecated(config);
var entryPoints = _readFileList(config, ENTRY_POINT_PARAM); var entryPoints = _readStringList(config, ENTRY_POINT_PARAM);
var initReflector = var initReflector =
_readBool(config, INIT_REFLECTOR_PARAM, defaultValue: true); _readBool(config, INIT_REFLECTOR_PARAM, defaultValue: true);
var reflectPropertiesAsAttributes = var reflectPropertiesAsAttributes =
@ -18,6 +18,7 @@ TransformerOptions parseBarbackSettings(BarbackSettings settings) {
config, REFLECT_PROPERTIES_AS_ATTRIBUTES_OLD, config, REFLECT_PROPERTIES_AS_ATTRIBUTES_OLD,
defaultValue: false); defaultValue: false);
} }
var ambientDirectives = _readStringList(config, AMBIENT_DIRECTIVES);
var formatCode = _readBool(config, FORMAT_CODE_PARAM, defaultValue: false); var formatCode = _readBool(config, FORMAT_CODE_PARAM, defaultValue: false);
String mirrorModeVal = String mirrorModeVal =
config.containsKey(MIRROR_MODE_PARAM) ? config[MIRROR_MODE_PARAM] : ''; config.containsKey(MIRROR_MODE_PARAM) ? config[MIRROR_MODE_PARAM] : '';
@ -39,6 +40,7 @@ TransformerOptions parseBarbackSettings(BarbackSettings settings) {
initReflector: initReflector, initReflector: initReflector,
customAnnotationDescriptors: _readCustomAnnotations(config), customAnnotationDescriptors: _readCustomAnnotations(config),
reflectPropertiesAsAttributes: reflectPropertiesAsAttributes, reflectPropertiesAsAttributes: reflectPropertiesAsAttributes,
ambientDirectives: ambientDirectives,
inlineViews: _readBool(config, INLINE_VIEWS_PARAM, defaultValue: false), inlineViews: _readBool(config, INLINE_VIEWS_PARAM, defaultValue: false),
formatCode: formatCode); formatCode: formatCode);
} }
@ -51,16 +53,16 @@ bool _readBool(Map config, String paramName, {bool defaultValue}) {
/// Cribbed from the polymer project. /// Cribbed from the polymer project.
/// {@link https://github.com/dart-lang/polymer-dart} /// {@link https://github.com/dart-lang/polymer-dart}
List<String> _readFileList(Map config, String paramName) { List<String> _readStringList(Map config, String paramName) {
var value = config[paramName]; var value = config[paramName];
if (value == null) return null; if (value == null) return null;
var files = []; var result = [];
bool error = false; bool error = false;
if (value is List) { if (value is List) {
files = value; result = value;
error = value.any((e) => e is! String); error = value.any((e) => e is! String);
} else if (value is String) { } else if (value is String) {
files = [value]; result = [value];
error = false; error = false;
} else { } else {
error = true; error = true;
@ -68,7 +70,7 @@ List<String> _readFileList(Map config, String paramName) {
if (error) { if (error) {
print('Invalid value for "$paramName" in the Angular 2 transformer.'); 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 /// Parse the [CUSTOM_ANNOTATIONS_PARAM] options out of the transformer into

View File

@ -20,9 +20,9 @@ import 'package:barback/barback.dart';
/// The returned value wraps the [NgDepsModel] at `assetId` as well as these /// The returned value wraps the [NgDepsModel] at `assetId` as well as these
/// created objects. /// created objects.
Future<CompileDataResults> createCompileData( Future<CompileDataResults> createCompileData(
AssetReader reader, AssetId assetId) async { AssetReader reader, AssetId assetId, List<String> ambientDirectives) async {
return logElapsedAsync(() 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; return creator != null ? creator.createCompileData() : null;
}, operationName: 'createCompileData', assetId: assetId); }, operationName: 'createCompileData', assetId: assetId);
} }
@ -41,17 +41,18 @@ class _CompileDataCreator {
final AssetReader reader; final AssetReader reader;
final AssetId entryPoint; final AssetId entryPoint;
final NgMeta ngMeta; final NgMeta ngMeta;
final List<String> ambientDirectives;
_CompileDataCreator(this.reader, this.entryPoint, this.ngMeta); _CompileDataCreator(this.reader, this.entryPoint, this.ngMeta, this.ambientDirectives);
static Future<_CompileDataCreator> create( static Future<_CompileDataCreator> create(
AssetReader reader, AssetId assetId) async { AssetReader reader, AssetId assetId, List<String> ambientDirectives) async {
if (!(await reader.hasInput(assetId))) return null; if (!(await reader.hasInput(assetId))) return null;
final json = await reader.readAsString(assetId); final json = await reader.readAsString(assetId);
if (json == null || json.isEmpty) return null; if (json == null || json.isEmpty) return null;
final ngMeta = new NgMeta.fromJson(JSON.decode(json)); 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; NgDepsModel get ngDeps => ngMeta.ngDeps;
@ -64,6 +65,7 @@ class _CompileDataCreator {
final compileData = final compileData =
<ReflectionInfoModel, NormalizedComponentWithViewDirectives>{}; <ReflectionInfoModel, NormalizedComponentWithViewDirectives>{};
final ngMetaMap = await _extractNgMeta(); final ngMetaMap = await _extractNgMeta();
final ambientDirectives = await _readAmbientDirectives();
for (var reflectable in ngDeps.reflectables) { for (var reflectable in ngDeps.reflectables) {
if (ngMeta.types.containsKey(reflectable.name)) { if (ngMeta.types.containsKey(reflectable.name)) {
@ -71,6 +73,8 @@ class _CompileDataCreator {
if (compileDirectiveMetadata.template != null) { if (compileDirectiveMetadata.template != null) {
final compileDatum = new NormalizedComponentWithViewDirectives( final compileDatum = new NormalizedComponentWithViewDirectives(
compileDirectiveMetadata, <CompileDirectiveMetadata>[]); compileDirectiveMetadata, <CompileDirectiveMetadata>[]);
compileDatum.directives.addAll(ambientDirectives);
for (var dep in reflectable.directives) { for (var dep in reflectable.directives) {
if (!ngMetaMap.containsKey(dep.prefix)) { if (!ngMetaMap.containsKey(dep.prefix)) {
logger.warning( logger.warning(
@ -99,6 +103,50 @@ class _CompileDataCreator {
return new CompileDataResults._(ngMeta, compileData); return new CompileDataResults._(ngMeta, compileData);
} }
Future<List<CompileDirectiveMetadata>> _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<List<CompileDirectiveMetadata>> _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` /// Creates a map from import prefix to the asset: uris of all `.dart`
/// libraries visible from `entryPoint`, excluding `dart:` and `.ng_deps.dart` /// libraries visible from `entryPoint`, excluding `dart:` and `.ng_deps.dart`
/// files it imports. Unprefixed imports have the empty string as their key. /// files it imports. Unprefixed imports have the empty string as their key.

View File

@ -26,8 +26,8 @@ import 'compile_data_creator.dart';
/// ///
/// This method assumes a {@link DomAdapter} has been registered. /// This method assumes a {@link DomAdapter} has been registered.
Future<Outputs> processTemplates(AssetReader reader, AssetId assetId, Future<Outputs> processTemplates(AssetReader reader, AssetId assetId,
{bool reflectPropertiesAsAttributes: false}) async { {bool reflectPropertiesAsAttributes: false, List<String> ambientDirectives}) async {
var viewDefResults = await createCompileData(reader, assetId); var viewDefResults = await createCompileData(reader, assetId, ambientDirectives);
if (viewDefResults == null) return null; if (viewDefResults == null) return null;
final directiveMetadatas = viewDefResults.ngMeta.types.values; final directiveMetadatas = viewDefResults.ngMeta.types.values;
if (directiveMetadatas.isNotEmpty) { if (directiveMetadatas.isNotEmpty) {

View File

@ -37,7 +37,8 @@ class TemplateCompiler extends Transformer {
var primaryId = transform.primaryInput.id; var primaryId = transform.primaryInput.id;
var reader = new AssetReader.fromTransform(transform); var reader = new AssetReader.fromTransform(transform);
var outputs = await processTemplates(reader, primaryId, var outputs = await processTemplates(reader, primaryId,
reflectPropertiesAsAttributes: options.reflectPropertiesAsAttributes); reflectPropertiesAsAttributes: options.reflectPropertiesAsAttributes,
ambientDirectives: options.ambientDirectives);
var ngDepsCode = _emptyNgDepsContents; var ngDepsCode = _emptyNgDepsContents;
var templatesCode = ''; var templatesCode = '';
if (outputs != null) { if (outputs != null) {

View File

@ -74,9 +74,10 @@ void allTests() {
updateReader(); updateReader();
}); });
Future<String> process(AssetId assetId) { Future<String> process(AssetId assetId, {List<String> ambientDirectives}) {
logger = new RecordingLogger(); 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 // TODO(tbosch): This is just a temporary test that makes sure that the dart
@ -346,6 +347,65 @@ void allTests() {
expect(didThrow).toBeFalse(); expect(didThrow).toBeFalse();
}); });
it('should include ambient directives.', () async {
fooComponentMeta.template = new CompileTemplateMetadata(template: '<bar/>');
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: '<bar/>');
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) { void _formatThenExpectEquals(String actual, String expected) {