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
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

View File

@ -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<String> 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<ClassDescriptor> customAnnotationDescriptors: const [],
bool inlineViews: false,
bool reflectPropertiesAsAttributes: true,
List<String> 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);
}

View File

@ -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<String> _readFileList(Map config, String paramName) {
List<String> _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<String> _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

View File

@ -20,9 +20,9 @@ import 'package:barback/barback.dart';
/// The returned value wraps the [NgDepsModel] at `assetId` as well as these
/// created objects.
Future<CompileDataResults> createCompileData(
AssetReader reader, AssetId assetId) async {
AssetReader reader, AssetId assetId, List<String> 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<String> 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<String> 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 =
<ReflectionInfoModel, NormalizedComponentWithViewDirectives>{};
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, <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<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`
/// libraries visible from `entryPoint`, excluding `dart:` and `.ng_deps.dart`
/// 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.
Future<Outputs> processTemplates(AssetReader reader, AssetId assetId,
{bool reflectPropertiesAsAttributes: false}) async {
var viewDefResults = await createCompileData(reader, assetId);
{bool reflectPropertiesAsAttributes: false, List<String> ambientDirectives}) async {
var viewDefResults = await createCompileData(reader, assetId, ambientDirectives);
if (viewDefResults == null) return null;
final directiveMetadatas = viewDefResults.ngMeta.types.values;
if (directiveMetadatas.isNotEmpty) {

View File

@ -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) {

View File

@ -74,9 +74,10 @@ void allTests() {
updateReader();
});
Future<String> process(AssetId assetId) {
Future<String> process(AssetId assetId, {List<String> 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: '<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) {