feat(dart/transform): Generate DirectiveMetadata for exports

For all files that export another library, include `DirectiveMetadata`
for the exported library in that file's associated `ng_meta.json` file.
This commit is contained in:
Tim Blasi 2015-05-06 10:00:25 -07:00
parent 6651aa1e1d
commit c8ebd11d63
14 changed files with 260 additions and 62 deletions

View File

@ -40,7 +40,7 @@ class _DirectiveMetadataVisitor extends Object
} }
meta = new DirectiveMetadata( meta = new DirectiveMetadata(
type: directiveType, type: directiveType,
compileChildren: false, compileChildren: true,
properties: {}, properties: {},
hostListeners: {}, hostListeners: {},
hostProperties: {}, hostProperties: {},

View File

@ -8,7 +8,7 @@ DartFormatter _formatter = null;
void init(DartFormatter formatter) { void init(DartFormatter formatter) {
if (_formatter != null) { if (_formatter != null) {
logger.warning('Formatter is being overwritten.'); logger.info('Formatter is being overwritten.');
} }
_formatter = formatter; _formatter = formatter;
} }

View File

@ -4,9 +4,32 @@ const SETUP_METHOD_NAME = 'initReflector';
const REFLECTOR_VAR_NAME = 'reflector'; const REFLECTOR_VAR_NAME = 'reflector';
const TRANSFORM_DYNAMIC_MODE = 'transform_dynamic'; const TRANSFORM_DYNAMIC_MODE = 'transform_dynamic';
const DEPS_EXTENSION = '.ng_deps.dart'; const DEPS_EXTENSION = '.ng_deps.dart';
const META_EXTENSION = '.ng_meta.dart'; const META_EXTENSION = '.ng_meta.json';
const REFLECTION_CAPABILITIES_NAME = 'ReflectionCapabilities'; const REFLECTION_CAPABILITIES_NAME = 'ReflectionCapabilities';
const REGISTER_TYPE_METHOD_NAME = 'registerType'; const REGISTER_TYPE_METHOD_NAME = 'registerType';
const REGISTER_GETTERS_METHOD_NAME = 'registerGetters'; const REGISTER_GETTERS_METHOD_NAME = 'registerGetters';
const REGISTER_SETTERS_METHOD_NAME = 'registerSetters'; const REGISTER_SETTERS_METHOD_NAME = 'registerSetters';
const REGISTER_METHODS_METHOD_NAME = 'registerMethods'; const REGISTER_METHODS_METHOD_NAME = 'registerMethods';
/// Returns `uri` with its extension updated to [META_EXTENSION].
String toMetaExtension(String uri) =>
_toExtension(uri, const [DEPS_EXTENSION, '.dart'], META_EXTENSION);
/// Returns `uri` with its extension updated to [DEPS_EXTENSION].
String toDepsExtension(String uri) =>
_toExtension(uri, const [META_EXTENSION, '.dart'], DEPS_EXTENSION);
/// Returns `uri` with its extension updated to `toExtension` if its
/// extension is currently in `fromExtension`.
String _toExtension(
String uri, Iterable<String> fromExtensions, String toExtension) {
if (uri == null) return null;
if (uri.endsWith(toExtension)) return uri;
for (var extension in fromExtensions) {
if (uri.endsWith(extension)) {
return '${uri.substring(0, uri.length-extension.length)}'
'$toExtension';
}
}
return uri;
}

View File

@ -2,20 +2,59 @@ library angular2.transform.directive_metadata_extractor.extractor;
import 'dart:async'; import 'dart:async';
import 'package:analyzer/analyzer.dart';
import 'package:angular2/src/render/api.dart'; import 'package:angular2/src/render/api.dart';
import 'package:angular2/src/transform/common/asset_reader.dart'; import 'package:angular2/src/transform/common/asset_reader.dart';
import 'package:angular2/src/transform/common/directive_metadata_reader.dart'; import 'package:angular2/src/transform/common/directive_metadata_reader.dart';
import 'package:angular2/src/transform/common/logging.dart';
import 'package:angular2/src/transform/common/names.dart';
import 'package:angular2/src/transform/common/parser.dart'; import 'package:angular2/src/transform/common/parser.dart';
import 'package:barback/barback.dart'; import 'package:barback/barback.dart';
import 'package:code_transformers/assets.dart';
/// Returns a map from a class name (that is, its `Identifier` stringified) /// Returns a map from a class name (that is, its `Identifier` stringified)
/// to its [DirectiveMetadata]. /// to [DirectiveMetadata] for all `Directive`-annotated classes visible
/// Will return `null` if there are no `Directive`-annotated classes in /// 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`. /// `entryPoint`.
Future<Map<String, DirectiveMetadata>> extractDirectiveMetadata( Future<Map<String, DirectiveMetadata>> extractDirectiveMetadata(
AssetReader reader, AssetId entryPoint) async { AssetReader reader, AssetId entryPoint) async {
var parser = new Parser(reader); return _extractDirectiveMetadataRecursive(
NgDeps ngDeps = await parser.parse(entryPoint); reader, new Parser(reader), entryPoint);
}
var _nullFuture = new Future.value(null);
Future<Map<String, DirectiveMetadata>> _extractDirectiveMetadataRecursive(
AssetReader reader, Parser parser, AssetId entryPoint) async {
if (!(await reader.hasInput(entryPoint))) return null;
var ngDeps = await parser.parse(entryPoint);
var baseMap = _metadataMapFromNgDeps(ngDeps);
return Future.wait(ngDeps.exports.map((export) {
var uri = stringLiteralToString(export.uri);
if (uri.startsWith('dart:')) return _nullFuture;
uri = toDepsExtension(uri);
var assetId = uriToAssetId(entryPoint, uri, logger, null /* span */);
if (assetId == entryPoint) return _nullFuture;
return _extractDirectiveMetadataRecursive(reader, parser, assetId)
.then((exportMap) {
if (exportMap != null) {
if (baseMap == null) {
baseMap = exportMap;
} else {
baseMap.addAll(exportMap);
}
}
});
})).then((_) => baseMap);
}
Map<String, DirectiveMetadata> _metadataMapFromNgDeps(NgDeps ngDeps) {
if (ngDeps == null || ngDeps.registeredTypes.isEmpty) return null; if (ngDeps == null || ngDeps.registeredTypes.isEmpty) return null;
var retVal = <String, DirectiveMetadata>{}; var retVal = <String, DirectiveMetadata>{};
ngDeps.registeredTypes.forEach((rType) { ngDeps.registeredTypes.forEach((rType) {

View File

@ -36,7 +36,7 @@ class DirectiveMetadataExtractor extends Transformer {
jsonMap[k] = directiveMetadataToMap(v); jsonMap[k] = directiveMetadataToMap(v);
}); });
transform.addOutput(new Asset.fromString( transform.addOutput(new Asset.fromString(
_outputAssetId(fromAssetId), '// ${JSON.encode(jsonMap)}')); _outputAssetId(fromAssetId), JSON.encode(jsonMap)));
} }
} catch (ex, stackTrace) { } catch (ex, stackTrace) {
log.logger.error('Extracting ng metadata failed.\n' log.logger.error('Extracting ng metadata failed.\n'
@ -49,8 +49,5 @@ class DirectiveMetadataExtractor extends Transformer {
AssetId _outputAssetId(AssetId inputAssetId) { AssetId _outputAssetId(AssetId inputAssetId) {
assert(inputAssetId.path.endsWith(DEPS_EXTENSION)); assert(inputAssetId.path.endsWith(DEPS_EXTENSION));
var pathIn = inputAssetId.path; return new AssetId(inputAssetId.package, toMetaExtension(inputAssetId.path));
return new AssetId(inputAssetId.package,
'${pathIn.substring(0, pathIn.length - DEPS_EXTENSION.length)}'
'${META_EXTENSION}');
} }

View File

@ -2,9 +2,12 @@ library angular2.test.transform.directive_metadata_extractor.all_tests;
import 'dart:async'; import 'dart:async';
import 'package:angular2/src/render/api.dart'; import 'package:angular2/src/render/api.dart';
import 'package:angular2/src/render/dom/convert.dart';
import 'package:angular2/src/transform/common/directive_metadata_reader.dart'; import 'package:angular2/src/transform/common/directive_metadata_reader.dart';
import 'package:angular2/src/transform/common/logging.dart'; import 'package:angular2/src/transform/common/logging.dart';
import 'package:angular2/src/transform/common/parser.dart'; import 'package:angular2/src/transform/common/parser.dart';
import 'package:angular2/src/transform/directive_metadata_extractor/'
'extractor.dart';
import 'package:barback/barback.dart'; import 'package:barback/barback.dart';
import 'package:dart_style/dart_style.dart'; import 'package:dart_style/dart_style.dart';
import 'package:guinness/guinness.dart'; import 'package:guinness/guinness.dart';
@ -24,50 +27,101 @@ void allTests() {
return readDirectiveMetadata(ngDeps.registeredTypes.first); return readDirectiveMetadata(ngDeps.registeredTypes.first);
} }
it('should parse selectors', () async { describe('readMetadata', () {
var metadata = await readMetadata( it('should parse selectors', () async {
'directive_metadata_extractor/directive_metadata_files/selector.ng_deps.dart'); var metadata = await readMetadata(
expect(metadata.selector).toEqual('hello-app'); 'directive_metadata_extractor/directive_metadata_files/'
'selector.ng_deps.dart');
expect(metadata.selector).toEqual('hello-app');
});
it('should parse compile children values', () async {
var ngDeps = await parser.parse(new AssetId('a',
'directive_metadata_extractor/'
'directive_metadata_files/compile_children.ng_deps.dart'));
var it = ngDeps.registeredTypes.iterator;
// Unset value defaults to `true`.
it.moveNext();
expect('${it.current.typeName}').toEqual('UnsetComp');
var unsetComp = readDirectiveMetadata(it.current);
expect(unsetComp.compileChildren).toBeTrue();
it.moveNext();
expect('${it.current.typeName}').toEqual('FalseComp');
var falseComp = readDirectiveMetadata(it.current);
expect(falseComp.compileChildren).toBeFalse();
it.moveNext();
expect('${it.current.typeName}').toEqual('TrueComp');
var trueComp = readDirectiveMetadata(it.current);
expect(trueComp.compileChildren).toBeTrue();
});
it('should parse properties.', () async {
var metadata = await readMetadata('directive_metadata_extractor/'
'directive_metadata_files/properties.ng_deps.dart');
expect(metadata.properties).toBeNotNull();
expect(metadata.properties.length).toBe(2);
expect(metadata.properties).toContain('key1');
expect(metadata.properties['key1']).toEqual('val1');
expect(metadata.properties).toContain('key2');
expect(metadata.properties['key2']).toEqual('val2');
});
it('should parse host listeners.', () async {
var metadata = await readMetadata('directive_metadata_extractor/'
'directive_metadata_files/host_listeners.ng_deps.dart');
expect(metadata.hostListeners).toBeNotNull();
expect(metadata.hostListeners.length).toBe(2);
expect(metadata.hostListeners).toContain('change');
expect(metadata.hostListeners['change']).toEqual('onChange(\$event)');
expect(metadata.hostListeners).toContain('keyDown');
expect(metadata.hostListeners['keyDown']).toEqual('onKeyDown(\$event)');
});
it('should fail when a class is annotated with multiple Directives.',
() async {
var ngDeps = await parser.parse(new AssetId('a',
'directive_metadata_extractor/'
'directive_metadata_files/too_many_directives.ng_deps.dart'));
expect(() => readDirectiveMetadata(ngDeps.registeredTypes.first))
.toThrowWith(anInstanceOf: PrintLoggerError);
});
}); });
it('should parse compile children values', () async { describe('extractMetadata', () {
var metadata = await readMetadata('directive_metadata_extractor/' it('should generate `DirectiveMetadata` from .ng_deps.dart files.',
'directive_metadata_files/compile_children.ng_deps.dart'); () async {
expect(metadata.compileChildren).toBeTrue(); var extracted = await extractDirectiveMetadata(reader, new AssetId(
'a', 'directive_metadata_extractor/simple_files/foo.ng_deps.dart'));
expect(extracted).toContain('FooComponent');
metadata = await readMetadata( var extractedMeta = extracted['FooComponent'];
'directive_metadata_extractor/directive_metadata_files/selector.ng_deps.dart'); expect(extractedMeta.selector).toEqual('[foo]');
expect(metadata.compileChildren).toBeFalse(); });
});
it('should parse properties.', () async { it('should include `DirectiveMetadata` from exported files.', () async {
var metadata = await readMetadata('directive_metadata_extractor/' var extracted = await extractDirectiveMetadata(reader, new AssetId(
'directive_metadata_files/properties.ng_deps.dart'); 'a', 'directive_metadata_extractor/export_files/foo.ng_deps.dart'));
expect(metadata.properties).toBeNotNull(); expect(extracted).toContain('FooComponent');
expect(metadata.properties.length).toBe(2); expect(extracted).toContain('BarComponent');
expect(metadata.properties).toContain('key1');
expect(metadata.properties['key1']).toEqual('val1');
expect(metadata.properties).toContain('key2');
expect(metadata.properties['key2']).toEqual('val2');
});
it('should parse host listeners.', () async { expect(extracted['FooComponent'].selector).toEqual('[foo]');
var metadata = await readMetadata('directive_metadata_extractor/' expect(extracted['BarComponent'].selector).toEqual('[bar]');
'directive_metadata_files/host_listeners.ng_deps.dart'); });
expect(metadata.hostListeners).toBeNotNull();
expect(metadata.hostListeners.length).toBe(2);
expect(metadata.hostListeners).toContain('change');
expect(metadata.hostListeners['change']).toEqual('onChange(\$event)');
expect(metadata.hostListeners).toContain('keyDown');
expect(metadata.hostListeners['keyDown']).toEqual('onKeyDown(\$event)');
});
it('should fail when a class is annotated with multiple Directives.', it('should include `DirectiveMetadata` recursively from exported files.',
() async { () async {
var ngDeps = await parser.parse(new AssetId('a', var extracted = await extractDirectiveMetadata(reader, new AssetId('a',
'directive_metadata_extractor/' 'directive_metadata_extractor/recursive_export_files/foo.ng_deps.dart'));
'directive_metadata_files/too_many_directives.ng_deps.dart')); expect(extracted).toContain('FooComponent');
expect(() => readDirectiveMetadata(ngDeps.registeredTypes.first)) expect(extracted).toContain('BarComponent');
.toThrowWith(anInstanceOf: PrintLoggerError); expect(extracted).toContain('BazComponent');
expect(extracted['FooComponent'].selector).toEqual('[foo]');
expect(extracted['BarComponent'].selector).toEqual('[bar]');
expect(extracted['BazComponent'].selector).toEqual('[baz]');
});
}); });
} }

View File

@ -8,8 +8,18 @@ void initReflector(reflector) {
if (_visited) return; if (_visited) return;
_visited = true; _visited = true;
reflector reflector
..registerType(HelloCmp, { ..registerType(UnsetComp, {
'factory': () => new HelloCmp(), 'factory': () => new UnsetComp(),
'parameters': const [const []],
'annotations': const [const Directive()]
})
..registerType(FalseComp, {
'factory': () => new FalseComp(),
'parameters': const [const []],
'annotations': const [const Directive(compileChildren: false)]
})
..registerType(TrueComp, {
'factory': () => new TrueComp(),
'parameters': const [const []], 'parameters': const [const []],
'annotations': const [const Directive(compileChildren: true)] 'annotations': const [const Directive(compileChildren: true)]
}); });

View File

@ -1,6 +1,6 @@
library foo.ng_deps.dart; library foo.ng_deps.dart;
import 'foo.dart'; import 'bar.dart';
import 'package:angular2/src/core/annotations/annotations.dart'; import 'package:angular2/src/core/annotations/annotations.dart';
var _visited = false; var _visited = false;
@ -8,9 +8,9 @@ void initReflector(reflector) {
if (_visited) return; if (_visited) return;
_visited = true; _visited = true;
reflector reflector
..registerType(DependencyComponent, { ..registerType(BarComponent, {
'factory': () => new DependencyComponent(), 'factory': () => new BarComponent(),
'parameters': const [], 'parameters': const [],
'annotations': const [const Component(selector: '[salad]')] 'annotations': const [const Component(selector: '[bar]')]
}); });
} }

View File

@ -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);
}

View File

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

View File

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

View File

@ -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);
}

View File

@ -1,4 +1,4 @@
library foo.ng_deps.dart; library bar.ng_deps.dart;
import 'foo.dart'; import 'foo.dart';
import 'package:angular2/src/core/annotations/annotations.dart'; import 'package:angular2/src/core/annotations/annotations.dart';
@ -8,9 +8,9 @@ void initReflector(reflector) {
if (_visited) return; if (_visited) return;
_visited = true; _visited = true;
reflector reflector
..registerType(DependencyComponent, { ..registerType(FooComponent, {
'factory': () => new DependencyComponent(), 'factory': () => new FooComponent(),
'parameters': const [], 'parameters': const [],
'annotations': const [const Component(selector: '[salad]')] 'annotations': const [const Component(selector: '[foo]')]
}); });
} }