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;
|
library angular2.src.transform.bind_generator.generator;
|
||||||
|
|
||||||
import 'package:analyzer/analyzer.dart';
|
import 'dart:async';
|
||||||
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';
|
|
||||||
|
|
||||||
String createNgSetters(String code, String path) {
|
import 'package:angular2/src/transform/common/asset_reader.dart';
|
||||||
if (_noSettersPresent(code)) return code;
|
import 'package:angular2/src/transform/common/parser.dart';
|
||||||
|
import 'package:barback/barback.dart';
|
||||||
|
|
||||||
var writer = new PrintStringWriter();
|
import 'visitor.dart';
|
||||||
parseCompilationUnit(
|
|
||||||
code, name: path).accept(new CreateNgSettersVisitor(writer));
|
Future<String> createNgSetters(AssetReader reader, AssetId entryPoint) async {
|
||||||
return writer.toString();
|
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;
|
/// Consumes the map generated by [_createBindMap] to codegen setters.
|
||||||
|
List<String> _generateSetters(Map<String, String> bindMap) {
|
||||||
class CreateNgSettersVisitor extends ToSourceVisitor with VisitorMixin {
|
var setters = [];
|
||||||
final PrintWriter writer;
|
// TODO(kegluneq): Include types for receivers. See #886.
|
||||||
final _ExtractSettersVisitor extractVisitor = new _ExtractSettersVisitor();
|
bindMap.forEach((prop, type) {
|
||||||
|
setters.add('\'$prop\': (o, String v) => o.$prop = v');
|
||||||
CreateNgSettersVisitor(PrintWriter writer)
|
});
|
||||||
: this.writer = writer,
|
return setters;
|
||||||
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);
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|
||||||
/// Visitor responsible for crawling the "annotations" value in a
|
/// Collapses all `bindProperties` in [ngDeps] into a map where the keys are
|
||||||
/// `registerType` call and generating setters from any "bind" values found.
|
/// the bind properties and the values are either the one and only type
|
||||||
class _ExtractSettersVisitor extends Object
|
/// binding to that property or the empty string.
|
||||||
with RecursiveAstVisitor<Object>, VisitorMixin {
|
Map<String, String> _createBindMap(NgDeps ngDeps) {
|
||||||
final List<String> bindPieces = [];
|
var visitor = new ExtractSettersVisitor();
|
||||||
Identifier currentName = null;
|
var bindMap = {};
|
||||||
|
ngDeps.registeredTypes.forEach((RegisteredType t) {
|
||||||
void _extractFromMapLiteral(MapLiteral map) {
|
visitor.bindMappings.clear();
|
||||||
if (currentName == null) {
|
t.annotations.accept(visitor);
|
||||||
logger.error('Unexpected code path: `currentName` should never be null');
|
visitor.bindMappings.forEach((String prop, _) {
|
||||||
}
|
if (bindMap.containsKey(prop)) {
|
||||||
map.entries.forEach((entry) {
|
bindMap[prop] = '';
|
||||||
// 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');
|
|
||||||
} else {
|
} else {
|
||||||
logger.error('`bind` currently only supports string literals');
|
bindMap[prop] = '${t.typeName}';
|
||||||
}
|
}
|
||||||
});
|
});
|
||||||
}
|
});
|
||||||
|
return bindMap;
|
||||||
@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);
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|
|
@ -1,6 +1,7 @@
|
||||||
library angular2.src.transform.bind_generator.transformer;
|
library angular2.src.transform.bind_generator.transformer;
|
||||||
|
|
||||||
import 'dart:async';
|
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/formatter.dart';
|
||||||
import 'package:angular2/src/transform/common/logging.dart' as log;
|
import 'package:angular2/src/transform/common/logging.dart' as log;
|
||||||
import 'package:angular2/src/transform/common/names.dart';
|
import 'package:angular2/src/transform/common/names.dart';
|
||||||
|
@ -28,11 +29,11 @@ class BindGenerator extends Transformer {
|
||||||
log.init(transform);
|
log.init(transform);
|
||||||
|
|
||||||
try {
|
try {
|
||||||
var assetCode = await transform.primaryInput.readAsString();
|
var id = transform.primaryInput.id;
|
||||||
var assetPath = transform.primaryInput.id.path;
|
var reader = new AssetReader.fromTransform(transform);
|
||||||
var transformedCode = createNgSetters(assetCode, assetPath);
|
var transformedCode = await createNgSetters(reader, id);
|
||||||
transform.addOutput(new Asset.fromString(transform.primaryInput.id,
|
transform.addOutput(new Asset.fromString(
|
||||||
formatter.format(transformedCode, uri: assetPath)));
|
id, formatter.format(transformedCode, uri: id.path)));
|
||||||
} catch (ex, stackTrace) {
|
} catch (ex, stackTrace) {
|
||||||
log.logger.error('Creating ng setters failed.\n'
|
log.logger.error('Creating ng setters failed.\n'
|
||||||
'Exception: $ex\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';
|
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 {
|
abstract class AssetReader {
|
||||||
Future<String> readAsString(AssetId id, {Encoding encoding});
|
Future<String> readAsString(AssetId id, {Encoding encoding});
|
||||||
Future<bool> hasInput(AssetId id);
|
Future<bool> hasInput(AssetId id);
|
||||||
|
|
|
@ -13,16 +13,22 @@ import 'registered_type.dart';
|
||||||
|
|
||||||
export '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 {
|
class Parser {
|
||||||
final AssetReader _reader;
|
final AssetReader _reader;
|
||||||
final _ParseNgDepsVisitor _visitor = new _ParseNgDepsVisitor();
|
final _ParseNgDepsVisitor _visitor = new _ParseNgDepsVisitor();
|
||||||
|
|
||||||
Parser(AssetReader this._reader);
|
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 {
|
Future<List<NgDeps>> parseRecursive(AssetId id) async {
|
||||||
return _recurse(id);
|
return _recurse(id);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/// Parses only the `.ng_deps.dart` file represented by `id`.
|
||||||
|
/// See also [parseRecursive].
|
||||||
Future<NgDeps> parse(AssetId id) async {
|
Future<NgDeps> parse(AssetId id) async {
|
||||||
if (!(await _reader.hasInput(id))) return null;
|
if (!(await _reader.hasInput(id))) return null;
|
||||||
var ngDeps = new NgDeps(await _reader.readAsString(id));
|
var ngDeps = new NgDeps(await _reader.readAsString(id));
|
||||||
|
@ -56,6 +62,7 @@ class Parser {
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/// The contents of a `.ng_deps.dart` file.
|
||||||
class NgDeps {
|
class NgDeps {
|
||||||
final String code;
|
final String code;
|
||||||
final List<ImportDirective> imports = [];
|
final List<ImportDirective> imports = [];
|
||||||
|
|
|
@ -3,13 +3,17 @@ library angular2.src.transform.common.registered_type;
|
||||||
import 'package:analyzer/analyzer.dart';
|
import 'package:analyzer/analyzer.dart';
|
||||||
import 'package:angular2/src/transform/common/names.dart';
|
import 'package:angular2/src/transform/common/names.dart';
|
||||||
|
|
||||||
/// Represents a call to `Reflector#registerType` generated by
|
/// A call to `Reflector#registerType` generated by `DirectiveProcessor`.
|
||||||
/// `DirectiveProcessor`.
|
|
||||||
class RegisteredType {
|
class RegisteredType {
|
||||||
|
/// The type registered by this call.
|
||||||
final Identifier typeName;
|
final Identifier typeName;
|
||||||
|
/// The actual call to `Reflector#registerType`.
|
||||||
final MethodInvocation registerMethod;
|
final MethodInvocation registerMethod;
|
||||||
|
/// The factory method registered.
|
||||||
final Expression factory;
|
final Expression factory;
|
||||||
|
/// The parameters registered.
|
||||||
final Expression parameters;
|
final Expression parameters;
|
||||||
|
/// The annotations registered.
|
||||||
final Expression annotations;
|
final Expression annotations;
|
||||||
|
|
||||||
RegisteredType._(this.typeName, this.registerMethod, this.factory,
|
RegisteredType._(this.typeName, this.registerMethod, this.factory,
|
||||||
|
|
|
@ -23,5 +23,5 @@ void setupReflection(reflector) {
|
||||||
selector: '[tool-tip]', bind: const {'text': 'tool-tip'})
|
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