diff --git a/modules/angular2/src/transform/bind_generator/generator.dart b/modules/angular2/src/transform/bind_generator/generator.dart index 48df6aa1e4..cd9b27ecd7 100644 --- a/modules/angular2/src/transform/bind_generator/generator.dart +++ b/modules/angular2/src/transform/bind_generator/generator.dart @@ -1,93 +1,53 @@ library angular2.src.transform.bind_generator.generator; -import 'package:analyzer/analyzer.dart'; -import 'package:analyzer/src/generated/java_core.dart'; -import 'package:angular2/src/transform/common/logging.dart'; -import 'package:angular2/src/transform/common/names.dart'; -import 'package:angular2/src/transform/common/visitor_mixin.dart'; +import 'dart:async'; -String createNgSetters(String code, String path) { - if (_noSettersPresent(code)) return code; +import 'package:angular2/src/transform/common/asset_reader.dart'; +import 'package:angular2/src/transform/common/parser.dart'; +import 'package:barback/barback.dart'; - var writer = new PrintStringWriter(); - parseCompilationUnit( - code, name: path).accept(new CreateNgSettersVisitor(writer)); - return writer.toString(); +import 'visitor.dart'; + +Future createNgSetters(AssetReader reader, AssetId entryPoint) async { + var parser = new Parser(reader); + NgDeps ngDeps = await parser.parse(entryPoint); + + String code = ngDeps.code; + var setters = _generateSetters(_createBindMap(ngDeps)); + + if (setters.length == 0) return code; + var codeInjectIdx = ngDeps.registeredTypes.last.registerMethod.end; + return '${code.substring(0, codeInjectIdx)}' + '..registerSetters({${setters.join(', ')}})' + '${code.substring(codeInjectIdx)}'; } -bool _noSettersPresent(String code) => code.indexOf('bind') < 0; - -class CreateNgSettersVisitor extends ToSourceVisitor with VisitorMixin { - final PrintWriter writer; - final _ExtractSettersVisitor extractVisitor = new _ExtractSettersVisitor(); - - CreateNgSettersVisitor(PrintWriter writer) - : this.writer = writer, - super(writer); - - @override - Object visitMethodInvocation(MethodInvocation node) { - var isRegisterType = - node.methodName.toString() == REGISTER_TYPE_METHOD_NAME; - // The first argument to a `registerType` call is the type. - extractVisitor.currentName = node.argumentList.arguments[0] is Identifier - ? node.argumentList.arguments[0] - : null; - extractVisitor.bindPieces.clear(); - - var retVal = super.visitMethodInvocation(node); - if (isRegisterType) { - if (extractVisitor.bindPieces.isNotEmpty) { - writer.print('..${REGISTER_SETTERS_METHOD_NAME}({'); - writer.print(extractVisitor.bindPieces.join(',')); - writer.print('})'); - } - } - return retVal; - } - - @override - Object visitMapLiteralEntry(MapLiteralEntry node) { - if (node.key is StringLiteral && - stringLiteralToString(node.key) == 'annotations') { - node.value.accept(extractVisitor); - } - return super.visitMapLiteralEntry(node); - } +/// Consumes the map generated by [_createBindMap] to codegen setters. +List _generateSetters(Map bindMap) { + var setters = []; + // TODO(kegluneq): Include types for receivers. See #886. + bindMap.forEach((prop, type) { + setters.add('\'$prop\': (o, String v) => o.$prop = v'); + }); + return setters; } -/// Visitor responsible for crawling the "annotations" value in a -/// `registerType` call and generating setters from any "bind" values found. -class _ExtractSettersVisitor extends Object - with RecursiveAstVisitor, VisitorMixin { - final List bindPieces = []; - Identifier currentName = null; - - void _extractFromMapLiteral(MapLiteral map) { - if (currentName == null) { - logger.error('Unexpected code path: `currentName` should never be null'); - } - map.entries.forEach((entry) { - // TODO(kegluneq): Remove this restriction - if (entry.key is SimpleStringLiteral) { - var propName = entry.key.value; - bindPieces.add('\'${propName}\': (' - '${currentName} o, String value) => o.${propName} = value'); +/// Collapses all `bindProperties` in [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(); + var bindMap = {}; + ngDeps.registeredTypes.forEach((RegisteredType t) { + visitor.bindMappings.clear(); + t.annotations.accept(visitor); + visitor.bindMappings.forEach((String prop, _) { + if (bindMap.containsKey(prop)) { + bindMap[prop] = ''; } else { - logger.error('`bind` currently only supports string literals'); + bindMap[prop] = '${t.typeName}'; } }); - } - - @override - Object visitNamedExpression(NamedExpression node) { - if (node.name.label.toString() == 'bind') { - // TODO(kegluneq): Remove this restriction. - if (node.expression is MapLiteral) { - _extractFromMapLiteral(node.expression); - } - return null; - } - return super.visitNamedExpression(node); - } + }); + return bindMap; } diff --git a/modules/angular2/src/transform/bind_generator/transformer.dart b/modules/angular2/src/transform/bind_generator/transformer.dart index b932610b98..f95b77e1c7 100644 --- a/modules/angular2/src/transform/bind_generator/transformer.dart +++ b/modules/angular2/src/transform/bind_generator/transformer.dart @@ -1,6 +1,7 @@ library angular2.src.transform.bind_generator.transformer; import 'dart:async'; +import 'package:angular2/src/transform/common/asset_reader.dart'; import 'package:angular2/src/transform/common/formatter.dart'; import 'package:angular2/src/transform/common/logging.dart' as log; import 'package:angular2/src/transform/common/names.dart'; @@ -28,11 +29,11 @@ class BindGenerator extends Transformer { log.init(transform); try { - var assetCode = await transform.primaryInput.readAsString(); - var assetPath = transform.primaryInput.id.path; - var transformedCode = createNgSetters(assetCode, assetPath); - transform.addOutput(new Asset.fromString(transform.primaryInput.id, - formatter.format(transformedCode, uri: assetPath))); + var id = transform.primaryInput.id; + var reader = new AssetReader.fromTransform(transform); + var transformedCode = await createNgSetters(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' 'Exception: $ex\n' diff --git a/modules/angular2/src/transform/bind_generator/visitor.dart b/modules/angular2/src/transform/bind_generator/visitor.dart new file mode 100644 index 0000000000..100cb2f786 --- /dev/null +++ b/modules/angular2/src/transform/bind_generator/visitor.dart @@ -0,0 +1,39 @@ +library angular2.src.transform.bind_generator.visitor; + +import 'package:analyzer/analyzer.dart'; +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 { + final Map bindMappings = {}; + + void _extractFromMapLiteral(MapLiteral map) { + map.entries.forEach((entry) { + // TODO(kegluneq): Remove this restriction + if (entry.key is SimpleStringLiteral && + entry.value is SimpleStringLiteral) { + bindMappings[stringLiteralToString(entry.key)] = + stringLiteralToString(entry.value); + } else { + logger.error('`bind` currently only supports string literals ' + '(${entry})'); + } + }); + } + + @override + Object visitNamedExpression(NamedExpression node) { + if ('${node.name.label}' == 'bind') { + // TODO(kegluneq): Remove this restriction. + if (node.expression is MapLiteral) { + _extractFromMapLiteral(node.expression); + } else { + logger.error('`bind` currently only supports map literals'); + } + return null; + } + return super.visitNamedExpression(node); + } +} diff --git a/modules/angular2/src/transform/common/asset_reader.dart b/modules/angular2/src/transform/common/asset_reader.dart index 1e8f1cfcc8..e7514fc565 100644 --- a/modules/angular2/src/transform/common/asset_reader.dart +++ b/modules/angular2/src/transform/common/asset_reader.dart @@ -5,6 +5,8 @@ import 'dart:convert'; import 'package:barback/barback.dart'; +/// A class that allows fetching code using [AssetId]s without all the +/// additional baggage of a [Transform]. abstract class AssetReader { Future readAsString(AssetId id, {Encoding encoding}); Future hasInput(AssetId id); diff --git a/modules/angular2/src/transform/common/parser.dart b/modules/angular2/src/transform/common/parser.dart index d9e605dc4f..0f03e58392 100644 --- a/modules/angular2/src/transform/common/parser.dart +++ b/modules/angular2/src/transform/common/parser.dart @@ -13,16 +13,22 @@ import 'registered_type.dart'; export 'registered_type.dart'; +/// A parser that reads `.ng_deps.dart` files (represented by [AssetId]s into +/// easier to manage [NgDeps] files. class Parser { final AssetReader _reader; final _ParseNgDepsVisitor _visitor = new _ParseNgDepsVisitor(); Parser(AssetReader this._reader); + /// Parses the `.ng_deps.dart` file represented by `id` and all of the ` + /// .ng_deps.dart` files that it imports. Future> parseRecursive(AssetId id) async { return _recurse(id); } + /// Parses only the `.ng_deps.dart` file represented by `id`. + /// See also [parseRecursive]. Future parse(AssetId id) async { if (!(await _reader.hasInput(id))) return null; var ngDeps = new NgDeps(await _reader.readAsString(id)); @@ -56,6 +62,7 @@ class Parser { } } +/// The contents of a `.ng_deps.dart` file. class NgDeps { final String code; final List imports = []; diff --git a/modules/angular2/src/transform/common/registered_type.dart b/modules/angular2/src/transform/common/registered_type.dart index 20ace9a24f..5b899301ee 100644 --- a/modules/angular2/src/transform/common/registered_type.dart +++ b/modules/angular2/src/transform/common/registered_type.dart @@ -3,13 +3,17 @@ library angular2.src.transform.common.registered_type; import 'package:analyzer/analyzer.dart'; import 'package:angular2/src/transform/common/names.dart'; -/// Represents a call to `Reflector#registerType` generated by -/// `DirectiveProcessor`. +/// A call to `Reflector#registerType` generated by `DirectiveProcessor`. class RegisteredType { + /// The type registered by this call. final Identifier typeName; + /// The actual call to `Reflector#registerType`. final MethodInvocation registerMethod; + /// The factory method registered. final Expression factory; + /// The parameters registered. final Expression parameters; + /// The annotations registered. final Expression annotations; RegisteredType._(this.typeName, this.registerMethod, this.factory, diff --git a/modules/angular2/test/transform/integration/basic_bind_files/expected/bar.ngDeps.dart b/modules/angular2/test/transform/integration/basic_bind_files/expected/bar.ngDeps.dart index b9fa0b4ca1..eb12c20473 100644 --- a/modules/angular2/test/transform/integration/basic_bind_files/expected/bar.ngDeps.dart +++ b/modules/angular2/test/transform/integration/basic_bind_files/expected/bar.ngDeps.dart @@ -23,5 +23,5 @@ void setupReflection(reflector) { selector: '[tool-tip]', bind: const {'text': 'tool-tip'}) ] }) - ..registerSetters({'text': (ToolTip o, String value) => o.text = value}); + ..registerSetters({'text': (o, String v) => o.text = v}); }