From 5a21dc534046004d8ed383bb93d7df39b70f982a Mon Sep 17 00:00:00 2001 From: Jacob MacDonald Date: Thu, 25 Jun 2015 07:29:20 -0700 Subject: [PATCH] fix(transformer): Add getters for `events`. closes https://github.com/angular/angular/issues/2725 --- .../transform/bind_generator/generator.dart | 62 ++++++++++++++++--- .../transform/bind_generator/transformer.dart | 4 +- .../src/transform/bind_generator/visitor.dart | 10 ++- .../transform/bind_generator/all_tests.dart | 21 +++++-- .../events_files/bar.ng_deps.dart | 19 ++++++ .../events_files/expected/bar.ng_deps.dart | 20 ++++++ 6 files changed, 118 insertions(+), 18 deletions(-) create mode 100644 modules/angular2/test/transform/bind_generator/events_files/bar.ng_deps.dart create mode 100644 modules/angular2/test/transform/bind_generator/events_files/expected/bar.ng_deps.dart diff --git a/modules/angular2/src/transform/bind_generator/generator.dart b/modules/angular2/src/transform/bind_generator/generator.dart index 75280a2fd6..ebc134a2fa 100644 --- a/modules/angular2/src/transform/bind_generator/generator.dart +++ b/modules/angular2/src/transform/bind_generator/generator.dart @@ -9,22 +9,32 @@ import 'package:barback/barback.dart'; import 'visitor.dart'; -Future createNgSetters(AssetReader reader, AssetId entryPoint) async { +Future createNgSettersAndGetters( + AssetReader reader, AssetId entryPoint) async { NgDeps ngDeps = await NgDeps.parse(reader, entryPoint); String code = ngDeps.code; - var setters = _generateSetters(_createBindMap(ngDeps)); + var setters = _generateSetters(_createPropertiesMap(ngDeps)); + var getters = _generateGetters(_createEventsMap(ngDeps)); - if (setters.length == 0) return code; + if (setters.isEmpty && getters.isEmpty) return code; + var out = new StringBuffer(); var codeInjectIdx = ngDeps.registeredTypes.last.registerMethod.end; - return '${code.substring(0, codeInjectIdx)}' - '..registerSetters({${setters.join(', ')}})' - '${code.substring(codeInjectIdx)}'; + out.write(code.substring(0, codeInjectIdx)); + if (setters.isNotEmpty) { + out.write('..registerSetters({${setters.join(', ')}})'); + } + if (getters.isNotEmpty) { + out.write('..registerGetters({${getters.join(', ')}})'); + } + out.write(code.substring(codeInjectIdx)); + return '$out'; } // TODO(kegluneq): De-dupe from template_compiler/generator.dart. -/// Consumes the map generated by {@link _createBindMap} to codegen setters. +/// Consumes the map generated by {@link _createPropertiesMap} to codegen +/// setters. List _generateSetters(Map bindMap) { var setters = []; // TODO(kegluneq): Include types for receivers. See #886. @@ -43,8 +53,8 @@ List _generateSetters(Map bindMap) { /// Collapses all `properties` in {@link ngDeps} into a map where the keys are /// the bind properties and the values are either the one and only type /// binding to that property or the empty string. -Map _createBindMap(NgDeps ngDeps) { - var visitor = new ExtractSettersVisitor(); +Map _createPropertiesMap(NgDeps ngDeps) { + var visitor = new ExtractNamedExpressionVisitor('properties'); var bindMap = {}; ngDeps.registeredTypes.forEach((RegisteredType t) { visitor.bindConfig.clear(); @@ -68,3 +78,37 @@ Map _createBindMap(NgDeps ngDeps) { }); return bindMap; } + +/// Consumes the map generated by {@link _createEventsMap} to codegen getters. +List _generateGetters(Map bindMap) { + var getters = []; + // TODO(kegluneq): Include types for receivers. See #886. + bindMap.forEach((getterName, eventName) { + if (!prop.isValid(eventName)) { + // TODO(kegluenq): Eagerly throw here once #1295 is addressed. + getters.add(prop.lazyInvalidGetter(eventName)); + } else { + getters.add(''' '${prop.sanitize(eventName)}': (o) => o.$getterName'''); + } + }); + return getters; +} + +/// Collapses all `events` in {@link ngDeps} into a map where the keys are +/// the property names for the event emitters and the values are the event name. +Map _createEventsMap(NgDeps ngDeps) { + var visitor = new ExtractNamedExpressionVisitor('events'); + var bindMap = {}; + ngDeps.registeredTypes.forEach((RegisteredType t) { + visitor.bindConfig.clear(); + t.annotations.accept(visitor); + visitor.bindConfig.forEach((String config) { + // See comments for `Directive` in annotations_impl/annotations.ts for + // details on how `events` is specified. + var parts = config.split(':').map((p) => p.trim()).toList(); + bindMap[parts[0]] = parts.length > 1 ? parts[1] : parts[0]; + }); + }); + return bindMap; +} + diff --git a/modules/angular2/src/transform/bind_generator/transformer.dart b/modules/angular2/src/transform/bind_generator/transformer.dart index 000cc49dd6..37cb687a9d 100644 --- a/modules/angular2/src/transform/bind_generator/transformer.dart +++ b/modules/angular2/src/transform/bind_generator/transformer.dart @@ -31,11 +31,11 @@ class BindGenerator extends Transformer { try { var id = transform.primaryInput.id; var reader = new AssetReader.fromTransform(transform); - var transformedCode = await createNgSetters(reader, id); + var transformedCode = await createNgSettersAndGetters(reader, id); transform.addOutput(new Asset.fromString( id, formatter.format(transformedCode, uri: id.path))); } catch (ex, stackTrace) { - log.logger.error('Creating ng setters failed.\n' + log.logger.error('Creating ng setters/getters failed.\n' 'Exception: $ex\n' 'Stack Trace: $stackTrace'); } diff --git a/modules/angular2/src/transform/bind_generator/visitor.dart b/modules/angular2/src/transform/bind_generator/visitor.dart index 47d0a516e5..ce05d619a5 100644 --- a/modules/angular2/src/transform/bind_generator/visitor.dart +++ b/modules/angular2/src/transform/bind_generator/visitor.dart @@ -6,18 +6,22 @@ import 'package:angular2/src/transform/common/logging.dart'; /// Visitor responsible for crawling the "annotations" value in a /// `registerType` call and pulling out the properties of any "bind" /// values found. -class ExtractSettersVisitor extends Object with RecursiveAstVisitor { +class ExtractNamedExpressionVisitor extends Object with + RecursiveAstVisitor { final ConstantEvaluator _evaluator = new ConstantEvaluator(); final List bindConfig = []; + final String nameToExtract; + + ExtractNamedExpressionVisitor(this.nameToExtract); @override Object visitNamedExpression(NamedExpression node) { - if ('${node.name.label}' == 'properties') { + if ('${node.name.label}' == nameToExtract) { var evaluated = node.expression.accept(_evaluator); if (evaluated is List) { bindConfig.addAll(evaluated); } else { - logger.error('`properties` currently only supports List values'); + logger.error('`$nameToExtract` currently only supports List values'); } return null; } diff --git a/modules/angular2/test/transform/bind_generator/all_tests.dart b/modules/angular2/test/transform/bind_generator/all_tests.dart index 817e774584..119ab858cd 100644 --- a/modules/angular2/test/transform/bind_generator/all_tests.dart +++ b/modules/angular2/test/transform/bind_generator/all_tests.dart @@ -9,6 +9,8 @@ import '../common/read_file.dart'; var formatter = new DartFormatter(); +main() => allTests(); + void allTests() { var reader = new TestAssetReader(); @@ -18,8 +20,8 @@ void allTests() { var expected = formatter.format( readFile('bind_generator/basic_bind_files/expected/bar.ng_deps.dart')); - var output = formatter - .format(await createNgSetters(reader, new AssetId('a', inputPath))); + var output = formatter.format( + await createNgSettersAndGetters(reader, new AssetId('a', inputPath))); expect(output).toEqual(expected); }); @@ -30,8 +32,19 @@ void allTests() { var expected = formatter.format(readFile( 'bind_generator/duplicate_bind_name_files/expected/soup.ng_deps.dart')); - var output = formatter - .format(await createNgSetters(reader, new AssetId('a', inputPath))); + var output = formatter.format( + await createNgSettersAndGetters(reader, new AssetId('a', inputPath))); + expect(output).toEqual(expected); + }); + + it('should generate a getter for a `events` property in an annotation.', + () async { + var inputPath = 'bind_generator/events_files/bar.ng_deps.dart'; + var expected = formatter.format( + readFile('bind_generator/events_files/expected/bar.ng_deps.dart')); + + var output = formatter.format( + await createNgSettersAndGetters(reader, new AssetId('a', inputPath))); expect(output).toEqual(expected); }); } diff --git a/modules/angular2/test/transform/bind_generator/events_files/bar.ng_deps.dart b/modules/angular2/test/transform/bind_generator/events_files/bar.ng_deps.dart new file mode 100644 index 0000000000..53018d38a2 --- /dev/null +++ b/modules/angular2/test/transform/bind_generator/events_files/bar.ng_deps.dart @@ -0,0 +1,19 @@ +library bar.ng_deps.dart; + +import 'bar.dart'; +import 'package:angular2/src/core/annotations_impl/annotations.dart'; + +var _visited = false; +void initReflector(reflector) { + if (_visited) return; + _visited = true; + reflector + ..registerType(ToolTip, { + 'factory': () => new ToolTip(), + 'parameters': const [], + 'annotations': const [ + const Directive( + selector: '[tool-tip]', events: ['onOpen', 'close: onClose']) + ] + }); +} diff --git a/modules/angular2/test/transform/bind_generator/events_files/expected/bar.ng_deps.dart b/modules/angular2/test/transform/bind_generator/events_files/expected/bar.ng_deps.dart new file mode 100644 index 0000000000..1eb7260695 --- /dev/null +++ b/modules/angular2/test/transform/bind_generator/events_files/expected/bar.ng_deps.dart @@ -0,0 +1,20 @@ +library bar.ng_deps.dart; + +import 'bar.dart'; +import 'package:angular2/src/core/annotations_impl/annotations.dart'; + +var _visited = false; +void initReflector(reflector) { + if (_visited) return; + _visited = true; + reflector + ..registerType(ToolTip, { + 'factory': () => new ToolTip(), + 'parameters': const [], + 'annotations': const [ + const Directive( + selector: '[tool-tip]', events: ['onOpen', 'close: onClose']) + ] + }) + ..registerGetters({'onOpen': (o) => o.onOpen, 'onClose': (o) => o.close}); +}