feat(dart/transform): Add a `.ng_deps.dart` file parser.

Create a common, reusable `.ng_deps.dart` file parser. All future transformer
phases build on the information in `.ng_deps.dart` files.
This commit is contained in:
Tim Blasi 2015-03-12 13:31:32 -07:00
parent 95c9eca64c
commit 92b22d24d0
4 changed files with 212 additions and 0 deletions

View File

@ -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<String> readAsString(AssetId id, {Encoding encoding});
Future<bool> 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<String> readAsString(AssetId id, {Encoding encoding}) =>
t.readInputAsString(id, encoding: encoding);
Future<bool> hasInput(AssetId id) => t.hasInput(id);
}

View File

@ -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<List<NgDeps>> parseRecursive(AssetId id) async {
return _recurse(id);
}
Future<NgDeps> 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<List<NgDeps>> _recurse(AssetId id,
[List<NgDeps> allDeps, Set<AssetId> seen]) async {
if (seen == null) seen = new Set<AssetId>();
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<ImportDirective> imports = [];
final List<ExportDirective> exports = [];
final List<RegisteredType> registeredTypes = [];
FunctionDeclaration setupMethod;
NgDeps(this.code);
}
class _ParseNgDepsVisitor extends Object with RecursiveAstVisitor<Object> {
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);
}
}

View File

@ -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<Object> {
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;
}
}

View File

@ -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<String> readAsString(AssetId id, {Encoding encoding}) =>
new Future.value(readFile(id.path));
Future<bool> 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);
}
}