feat(dart/transform): Add a parser for `.ng_deps.dart` files and use.
Use the parser in `BindGenerator` This checkin also removes types from `registerSetters` calls since they can cause runtime failures (see #886). We will resolve this by generating change detector classes in the future.
This commit is contained in:
parent
92b22d24d0
commit
4b12c19560
|
@ -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<String> 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<String> _generateSetters(Map<String, String> 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<Object>, VisitorMixin {
|
||||
final List<String> 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<String, String> _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;
|
||||
}
|
||||
|
|
|
@ -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'
|
||||
|
|
|
@ -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<Object> {
|
||||
final Map<String, String> 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);
|
||||
}
|
||||
}
|
|
@ -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<String> readAsString(AssetId id, {Encoding encoding});
|
||||
Future<bool> hasInput(AssetId id);
|
||||
|
|
|
@ -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<List<NgDeps>> parseRecursive(AssetId id) async {
|
||||
return _recurse(id);
|
||||
}
|
||||
|
||||
/// Parses only the `.ng_deps.dart` file represented by `id`.
|
||||
/// See also [parseRecursive].
|
||||
Future<NgDeps> 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<ImportDirective> imports = [];
|
||||
|
|
|
@ -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,
|
||||
|
|
|
@ -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});
|
||||
}
|
||||
|
|
Loading…
Reference in New Issue