diff --git a/modules/angular2/src/transform/common/asset_reader.dart b/modules/angular2/src/transform/common/asset_reader.dart new file mode 100644 index 0000000000..1e8f1cfcc8 --- /dev/null +++ b/modules/angular2/src/transform/common/asset_reader.dart @@ -0,0 +1,26 @@ +library angular2.src.transform.common.asset_reader; + +import 'dart:async'; +import 'dart:convert'; + +import 'package:barback/barback.dart'; + +abstract class AssetReader { + Future readAsString(AssetId id, {Encoding encoding}); + Future hasInput(AssetId id); + + /// Creates an [AssetReader] using the `transform`, which should be a + /// [Transform] or [AggregateTransform]. + factory AssetReader.fromTransform(dynamic transform) => + new _TransformAssetReader(transform); +} + +class _TransformAssetReader implements AssetReader { + final dynamic t; + _TransformAssetReader(this.t); + + Future readAsString(AssetId id, {Encoding encoding}) => + t.readInputAsString(id, encoding: encoding); + + Future hasInput(AssetId id) => t.hasInput(id); +} diff --git a/modules/angular2/src/transform/common/parser.dart b/modules/angular2/src/transform/common/parser.dart new file mode 100644 index 0000000000..d9e605dc4f --- /dev/null +++ b/modules/angular2/src/transform/common/parser.dart @@ -0,0 +1,103 @@ +library angular2.src.transform.common.parser; + +import 'dart:async'; + +import 'package:analyzer/analyzer.dart'; +import 'package:angular2/src/transform/common/asset_reader.dart'; +import 'package:angular2/src/transform/common/logging.dart'; +import 'package:angular2/src/transform/common/names.dart'; +import 'package:barback/barback.dart'; +import 'package:code_transformers/assets.dart'; + +import 'registered_type.dart'; + +export 'registered_type.dart'; + +class Parser { + final AssetReader _reader; + final _ParseNgDepsVisitor _visitor = new _ParseNgDepsVisitor(); + + Parser(AssetReader this._reader); + + Future> parseRecursive(AssetId id) async { + return _recurse(id); + } + + Future parse(AssetId id) async { + if (!(await _reader.hasInput(id))) return null; + var ngDeps = new NgDeps(await _reader.readAsString(id)); + _visitor.ngDeps = ngDeps; + parseCompilationUnit(ngDeps.code, name: id.path).accept(_visitor); + return ngDeps; + } + + /// Parses the `.ngDeps.dart` file represented by [id] into an [NgDeps] + /// object. All `.ngDeps.dart` files imported by [id] are then parsed. The + /// results are added to [allDeps]. + Future> _recurse(AssetId id, + [List allDeps, Set seen]) async { + if (seen == null) seen = new Set(); + if (seen.contains(id)) return; + seen.add(id); + + if (allDeps == null) allDeps = []; + var ngDeps = await parse(id); + allDeps.add(ngDeps); + + var toWait = []; + ngDeps.imports.forEach((ImportDirective node) { + var uri = stringLiteralToString(node.uri); + if (uri.endsWith(DEPS_EXTENSION)) { + var importId = uriToAssetId(id, uri, logger, null); + toWait.add(_recurse(importId, allDeps, seen)); + } + }); + return Future.wait(toWait).then((_) => allDeps); + } +} + +class NgDeps { + final String code; + final List imports = []; + final List exports = []; + final List registeredTypes = []; + FunctionDeclaration setupMethod; + + NgDeps(this.code); +} + +class _ParseNgDepsVisitor extends Object with RecursiveAstVisitor { + NgDeps ngDeps = null; + _RegisteredTypeBuilder current = null; + + @override + Object visitImportDirective(ImportDirective node) { + ngDeps.imports.add(node); + return super.visitImportDirective(node); + } + + @override + Object visitExportDirective(ExportDirective node) { + ngDeps.exports.add(node); + return super.visitExportDirective(node); + } + + @override + Object visitFunctionDeclaration(FunctionDeclaration node) { + if ('${node.name}' == SETUP_METHOD_NAME) { + ngDeps.setupMethod = node; + } + return super.visitFunctionDeclaration(node); + } + + @override + Object visitMethodInvocation(MethodInvocation node) { + var isRegisterType = '${node.methodName}' == REGISTER_TYPE_METHOD_NAME; + + if (isRegisterType) { + ngDeps.registeredTypes.add(new RegisteredType.fromMethodInvocation(node)); + } + + return super.visitMethodInvocation(node); + } +} diff --git a/modules/angular2/src/transform/common/registered_type.dart b/modules/angular2/src/transform/common/registered_type.dart new file mode 100644 index 0000000000..20ace9a24f --- /dev/null +++ b/modules/angular2/src/transform/common/registered_type.dart @@ -0,0 +1,65 @@ +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`. +class RegisteredType { + final Identifier typeName; + final MethodInvocation registerMethod; + final Expression factory; + final Expression parameters; + final Expression annotations; + + RegisteredType._(this.typeName, this.registerMethod, this.factory, + this.parameters, this.annotations); + + /// Creates a [RegisteredType] given a [MethodInvocation] node representing + /// a call to `registerType`. + factory RegisteredType.fromMethodInvocation(MethodInvocation registerMethod) { + var visitor = new _ParseRegisterTypeVisitor(); + registerMethod.accept(visitor); + return new RegisteredType._(visitor.typeName, registerMethod, + visitor.factory, visitor.parameters, visitor.annotations); + } +} + +class _ParseRegisterTypeVisitor extends Object + with RecursiveAstVisitor { + Identifier typeName; + Expression factory; + Expression parameters; + Expression annotations; + + @override + Object visitMethodInvocation(MethodInvocation node) { + assert('${node.methodName}' == REGISTER_TYPE_METHOD_NAME); + + // The first argument to a `registerType` call is the type. + typeName = node.argumentList.arguments[0] is Identifier + ? node.argumentList.arguments[0] + : null; + return super.visitMethodInvocation(node); + } + + @override + Object visitMapLiteralEntry(MapLiteralEntry node) { + if (node.key is StringLiteral) { + var key = stringLiteralToString(node.key); + switch (key) { + case 'annotations': + annotations = node.value; + break; + case 'factory': + factory = node.value; + break; + case 'parameters': + parameters = node.value; + break; + } + } + // Do not need to descend any further. + return null; + } +} diff --git a/modules/angular2/test/transform/common/read_file.dart b/modules/angular2/test/transform/common/read_file.dart index 269866e151..dc81d937c6 100644 --- a/modules/angular2/test/transform/common/read_file.dart +++ b/modules/angular2/test/transform/common/read_file.dart @@ -1,7 +1,11 @@ library angular2.test.transform.common.read_file; +import 'dart:async'; import 'dart:io'; +import 'package:angular2/src/transform/common/asset_reader.dart'; +import 'package:barback/barback.dart'; + /// Smooths over differences in CWD between IDEs and running tests in Travis. String readFile(String path) { for (var myPath in [path, 'test/transform/${path}']) { @@ -12,3 +16,17 @@ String readFile(String path) { } return null; } + +class TestAssetReader implements AssetReader { + Future readAsString(AssetId id, {Encoding encoding}) => + new Future.value(readFile(id.path)); + + Future hasInput(AssetId id) { + var exists = false; + for (var myPath in [id.path, 'test/transform/${id.path}']) { + var file = new File(myPath); + exists = exists || file.existsSync(); + } + return new Future.value(exists); + } +}