From c8ebd11d633d3f85675ee8ca4f102e69a6090013 Mon Sep 17 00:00:00 2001 From: Tim Blasi Date: Wed, 6 May 2015 10:00:25 -0700 Subject: [PATCH] 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. --- .../common/directive_metadata_reader.dart | 2 +- .../src/transform/common/formatter.dart | 2 +- .../angular2/src/transform/common/names.dart | 25 +++- .../extractor.dart | 47 ++++++- .../transformer.dart | 7 +- .../all_tests.dart | 132 ++++++++++++------ .../compile_children.ng_deps.dart | 14 +- .../bar.ng_deps.dart} | 8 +- .../export_files/foo.ng_deps.dart | 20 +++ .../recursive_export_files/bar.ng_deps.dart | 20 +++ .../recursive_export_files/baz.ng_deps.dart | 16 +++ .../recursive_export_files/foo.ng_deps.dart | 20 +++ .../simple_files/expected/foo.ng_meta.dart | 1 - .../simple_files/foo.ng_deps.dart | 8 +- 14 files changed, 260 insertions(+), 62 deletions(-) rename modules/angular2/test/transform/directive_metadata_extractor/{simple_files/expected/foo.ng_deps.dart => export_files/bar.ng_deps.dart} (57%) create mode 100644 modules/angular2/test/transform/directive_metadata_extractor/export_files/foo.ng_deps.dart create mode 100644 modules/angular2/test/transform/directive_metadata_extractor/recursive_export_files/bar.ng_deps.dart create mode 100644 modules/angular2/test/transform/directive_metadata_extractor/recursive_export_files/baz.ng_deps.dart create mode 100644 modules/angular2/test/transform/directive_metadata_extractor/recursive_export_files/foo.ng_deps.dart delete mode 100644 modules/angular2/test/transform/directive_metadata_extractor/simple_files/expected/foo.ng_meta.dart diff --git a/modules/angular2/src/transform/common/directive_metadata_reader.dart b/modules/angular2/src/transform/common/directive_metadata_reader.dart index c775672459..5dfe3bf301 100644 --- a/modules/angular2/src/transform/common/directive_metadata_reader.dart +++ b/modules/angular2/src/transform/common/directive_metadata_reader.dart @@ -40,7 +40,7 @@ class _DirectiveMetadataVisitor extends Object } meta = new DirectiveMetadata( type: directiveType, - compileChildren: false, + compileChildren: true, properties: {}, hostListeners: {}, hostProperties: {}, diff --git a/modules/angular2/src/transform/common/formatter.dart b/modules/angular2/src/transform/common/formatter.dart index 916101b4cc..ad96b1418a 100644 --- a/modules/angular2/src/transform/common/formatter.dart +++ b/modules/angular2/src/transform/common/formatter.dart @@ -8,7 +8,7 @@ DartFormatter _formatter = null; void init(DartFormatter formatter) { if (_formatter != null) { - logger.warning('Formatter is being overwritten.'); + logger.info('Formatter is being overwritten.'); } _formatter = formatter; } diff --git a/modules/angular2/src/transform/common/names.dart b/modules/angular2/src/transform/common/names.dart index dc7b2fa7bb..ebd83ccfb2 100644 --- a/modules/angular2/src/transform/common/names.dart +++ b/modules/angular2/src/transform/common/names.dart @@ -4,9 +4,32 @@ const SETUP_METHOD_NAME = 'initReflector'; const REFLECTOR_VAR_NAME = 'reflector'; const TRANSFORM_DYNAMIC_MODE = 'transform_dynamic'; const DEPS_EXTENSION = '.ng_deps.dart'; -const META_EXTENSION = '.ng_meta.dart'; +const META_EXTENSION = '.ng_meta.json'; const REFLECTION_CAPABILITIES_NAME = 'ReflectionCapabilities'; const REGISTER_TYPE_METHOD_NAME = 'registerType'; const REGISTER_GETTERS_METHOD_NAME = 'registerGetters'; const REGISTER_SETTERS_METHOD_NAME = 'registerSetters'; 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 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; +} diff --git a/modules/angular2/src/transform/directive_metadata_extractor/extractor.dart b/modules/angular2/src/transform/directive_metadata_extractor/extractor.dart index dbfbea3cba..94cb56297d 100644 --- a/modules/angular2/src/transform/directive_metadata_extractor/extractor.dart +++ b/modules/angular2/src/transform/directive_metadata_extractor/extractor.dart @@ -2,20 +2,59 @@ library angular2.transform.directive_metadata_extractor.extractor; import 'dart:async'; +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/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:barback/barback.dart'; +import 'package:code_transformers/assets.dart'; /// Returns a map from a class name (that is, its `Identifier` stringified) -/// to its [DirectiveMetadata]. -/// Will return `null` if there are no `Directive`-annotated classes in +/// 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 { - var parser = new Parser(reader); - NgDeps ngDeps = await parser.parse(entryPoint); + return _extractDirectiveMetadataRecursive( + reader, new Parser(reader), entryPoint); +} + +var _nullFuture = new Future.value(null); + +Future> _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 _metadataMapFromNgDeps(NgDeps ngDeps) { if (ngDeps == null || ngDeps.registeredTypes.isEmpty) return null; var retVal = {}; ngDeps.registeredTypes.forEach((rType) { diff --git a/modules/angular2/src/transform/directive_metadata_extractor/transformer.dart b/modules/angular2/src/transform/directive_metadata_extractor/transformer.dart index 8d4a41b060..6a4bb3abba 100644 --- a/modules/angular2/src/transform/directive_metadata_extractor/transformer.dart +++ b/modules/angular2/src/transform/directive_metadata_extractor/transformer.dart @@ -36,7 +36,7 @@ class DirectiveMetadataExtractor extends Transformer { jsonMap[k] = directiveMetadataToMap(v); }); transform.addOutput(new Asset.fromString( - _outputAssetId(fromAssetId), '// ${JSON.encode(jsonMap)}')); + _outputAssetId(fromAssetId), JSON.encode(jsonMap))); } } catch (ex, stackTrace) { log.logger.error('Extracting ng metadata failed.\n' @@ -49,8 +49,5 @@ class DirectiveMetadataExtractor extends Transformer { AssetId _outputAssetId(AssetId inputAssetId) { assert(inputAssetId.path.endsWith(DEPS_EXTENSION)); - var pathIn = inputAssetId.path; - return new AssetId(inputAssetId.package, - '${pathIn.substring(0, pathIn.length - DEPS_EXTENSION.length)}' - '${META_EXTENSION}'); + return new AssetId(inputAssetId.package, toMetaExtension(inputAssetId.path)); } 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 93938980f4..8922dd4afe 100644 --- a/modules/angular2/test/transform/directive_metadata_extractor/all_tests.dart +++ b/modules/angular2/test/transform/directive_metadata_extractor/all_tests.dart @@ -2,9 +2,12 @@ library angular2.test.transform.directive_metadata_extractor.all_tests; import 'dart:async'; 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/logging.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:dart_style/dart_style.dart'; import 'package:guinness/guinness.dart'; @@ -24,50 +27,101 @@ void allTests() { return readDirectiveMetadata(ngDeps.registeredTypes.first); } - it('should parse selectors', () async { - var metadata = await readMetadata( - 'directive_metadata_extractor/directive_metadata_files/selector.ng_deps.dart'); - expect(metadata.selector).toEqual('hello-app'); + describe('readMetadata', () { + it('should parse selectors', () async { + var metadata = await readMetadata( + '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 { - var metadata = await readMetadata('directive_metadata_extractor/' - 'directive_metadata_files/compile_children.ng_deps.dart'); - expect(metadata.compileChildren).toBeTrue(); + 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'); - metadata = await readMetadata( - 'directive_metadata_extractor/directive_metadata_files/selector.ng_deps.dart'); - expect(metadata.compileChildren).toBeFalse(); - }); + var extractedMeta = extracted['FooComponent']; + expect(extractedMeta.selector).toEqual('[foo]'); + }); - 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 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'); - 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)'); - }); + expect(extracted['FooComponent'].selector).toEqual('[foo]'); + expect(extracted['BarComponent'].selector).toEqual('[bar]'); + }); - 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 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'); + + expect(extracted['FooComponent'].selector).toEqual('[foo]'); + expect(extracted['BarComponent'].selector).toEqual('[bar]'); + expect(extracted['BazComponent'].selector).toEqual('[baz]'); + }); }); } diff --git a/modules/angular2/test/transform/directive_metadata_extractor/directive_metadata_files/compile_children.ng_deps.dart b/modules/angular2/test/transform/directive_metadata_extractor/directive_metadata_files/compile_children.ng_deps.dart index c15cda82a0..91502ba4ad 100644 --- a/modules/angular2/test/transform/directive_metadata_extractor/directive_metadata_files/compile_children.ng_deps.dart +++ b/modules/angular2/test/transform/directive_metadata_extractor/directive_metadata_files/compile_children.ng_deps.dart @@ -8,8 +8,18 @@ void initReflector(reflector) { if (_visited) return; _visited = true; reflector - ..registerType(HelloCmp, { - 'factory': () => new HelloCmp(), + ..registerType(UnsetComp, { + '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 []], 'annotations': const [const Directive(compileChildren: true)] }); diff --git a/modules/angular2/test/transform/directive_metadata_extractor/simple_files/expected/foo.ng_deps.dart b/modules/angular2/test/transform/directive_metadata_extractor/export_files/bar.ng_deps.dart similarity index 57% rename from modules/angular2/test/transform/directive_metadata_extractor/simple_files/expected/foo.ng_deps.dart rename to modules/angular2/test/transform/directive_metadata_extractor/export_files/bar.ng_deps.dart index afa3650415..381592661d 100644 --- a/modules/angular2/test/transform/directive_metadata_extractor/simple_files/expected/foo.ng_deps.dart +++ b/modules/angular2/test/transform/directive_metadata_extractor/export_files/bar.ng_deps.dart @@ -1,6 +1,6 @@ library foo.ng_deps.dart; -import 'foo.dart'; +import 'bar.dart'; import 'package:angular2/src/core/annotations/annotations.dart'; var _visited = false; @@ -8,9 +8,9 @@ void initReflector(reflector) { if (_visited) return; _visited = true; reflector - ..registerType(DependencyComponent, { - 'factory': () => new DependencyComponent(), + ..registerType(BarComponent, { + 'factory': () => new BarComponent(), 'parameters': const [], - 'annotations': const [const Component(selector: '[salad]')] + 'annotations': const [const Component(selector: '[bar]')] }); } diff --git a/modules/angular2/test/transform/directive_metadata_extractor/export_files/foo.ng_deps.dart b/modules/angular2/test/transform/directive_metadata_extractor/export_files/foo.ng_deps.dart new file mode 100644 index 0000000000..57e2c54e79 --- /dev/null +++ b/modules/angular2/test/transform/directive_metadata_extractor/export_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_metadata_extractor/recursive_export_files/bar.ng_deps.dart b/modules/angular2/test/transform/directive_metadata_extractor/recursive_export_files/bar.ng_deps.dart new file mode 100644 index 0000000000..6a1aade971 --- /dev/null +++ b/modules/angular2/test/transform/directive_metadata_extractor/recursive_export_files/bar.ng_deps.dart @@ -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); +} diff --git a/modules/angular2/test/transform/directive_metadata_extractor/recursive_export_files/baz.ng_deps.dart b/modules/angular2/test/transform/directive_metadata_extractor/recursive_export_files/baz.ng_deps.dart new file mode 100644 index 0000000000..476e668ee5 --- /dev/null +++ b/modules/angular2/test/transform/directive_metadata_extractor/recursive_export_files/baz.ng_deps.dart @@ -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]')] + }); +} diff --git a/modules/angular2/test/transform/directive_metadata_extractor/recursive_export_files/foo.ng_deps.dart b/modules/angular2/test/transform/directive_metadata_extractor/recursive_export_files/foo.ng_deps.dart new file mode 100644 index 0000000000..57e2c54e79 --- /dev/null +++ b/modules/angular2/test/transform/directive_metadata_extractor/recursive_export_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_metadata_extractor/simple_files/expected/foo.ng_meta.dart b/modules/angular2/test/transform/directive_metadata_extractor/simple_files/expected/foo.ng_meta.dart deleted file mode 100644 index b4483579d1..0000000000 --- a/modules/angular2/test/transform/directive_metadata_extractor/simple_files/expected/foo.ng_meta.dart +++ /dev/null @@ -1 +0,0 @@ -// {selector: '[salad]', type: 1} diff --git a/modules/angular2/test/transform/directive_metadata_extractor/simple_files/foo.ng_deps.dart b/modules/angular2/test/transform/directive_metadata_extractor/simple_files/foo.ng_deps.dart index afa3650415..a0fcd9b1b2 100644 --- a/modules/angular2/test/transform/directive_metadata_extractor/simple_files/foo.ng_deps.dart +++ b/modules/angular2/test/transform/directive_metadata_extractor/simple_files/foo.ng_deps.dart @@ -1,4 +1,4 @@ -library foo.ng_deps.dart; +library bar.ng_deps.dart; import 'foo.dart'; import 'package:angular2/src/core/annotations/annotations.dart'; @@ -8,9 +8,9 @@ void initReflector(reflector) { if (_visited) return; _visited = true; reflector - ..registerType(DependencyComponent, { - 'factory': () => new DependencyComponent(), + ..registerType(FooComponent, { + 'factory': () => new FooComponent(), 'parameters': const [], - 'annotations': const [const Component(selector: '[salad]')] + 'annotations': const [const Component(selector: '[foo]')] }); }