feat(dart/transform): Add getters, setters, methods to NgDepsModel

Add `List<String>` `getters`, `setters`, and `methods`, which will be
used to generate code that registers those closures with the reflection
system.
This commit is contained in:
Tim Blasi 2015-10-08 10:04:47 -07:00
parent b318945680
commit d68955ac6d
4 changed files with 206 additions and 5 deletions

View File

@ -12,20 +12,23 @@ import 'annotation_code.dart';
import 'import_export_code.dart';
import 'reflection_info_code.dart';
import 'parameter_code.dart';
import 'queries_code.dart';
/// Visitor responsible for parsing source Dart files (that is, not
/// `.ng_deps.dart` files) into [NgDepsModel] objects.
class NgDepsVisitor extends RecursiveAstVisitor<Object> {
final AssetId processedFile;
final ImportVisitor _importVisitor = new ImportVisitor();
final ExportVisitor _exportVisitor = new ExportVisitor();
final _importVisitor = new ImportVisitor();
final _exportVisitor = new ExportVisitor();
final ReflectionInfoVisitor _reflectableVisitor;
final QueriesVisitor _queriesVisitor;
bool _isPart = false;
NgDepsModel _model = null;
NgDepsVisitor(AssetId processedFile, AnnotationMatcher annotationMatcher)
: this.processedFile = processedFile,
_queriesVisitor = new QueriesVisitor(processedFile, annotationMatcher),
_reflectableVisitor =
new ReflectionInfoVisitor(processedFile, annotationMatcher);
@ -48,6 +51,14 @@ class NgDepsVisitor extends RecursiveAstVisitor<Object> {
var reflectableModel = _reflectableVisitor.visitClassDeclaration(node);
if (reflectableModel != null) {
model.reflectables.add(reflectableModel);
var queryFields = _queriesVisitor.visitClassDeclaration(node);
if (queryFields != null) {
for (var queryField in queryFields) {
if (!model.setters.contains(queryField)) {
model.setters.add(queryField);
}
}
}
}
return null;
}
@ -155,9 +166,39 @@ abstract class NgDepsWriterMixin
..writeln('void ${SETUP_METHOD_NAME}() {')
..writeln('if (_visited) return; _visited = true;');
if (model.reflectables != null && model.reflectables.isNotEmpty) {
final needsReceiver = (model.reflectables != null &&
model.reflectables.isNotEmpty) ||
(model.getters != null && model.getters.isNotEmpty) ||
(model.setters != null && model.setters.isNotEmpty) ||
(model.methods != null && model.methods.isNotEmpty);
if (needsReceiver) {
buffer.writeln('$REFLECTOR_PREFIX.$REFLECTOR_VAR_NAME');
}
if (model.reflectables != null && model.reflectables.isNotEmpty) {
model.reflectables.forEach(writeRegistration);
}
if (model.getters != null && model.getters.isNotEmpty) {
buffer.writeln('..registerGetters({'
'${model.getters.map((g) => "'$g': (o) => o.$g").join(', ')}'
'})');
}
if (model.setters != null && model.setters.isNotEmpty) {
buffer.writeln('..registerSetters({'
'${model.setters.map((g) => "'$g': (o, v) => o.$g = v").join(', ')}'
'})');
}
if (model.methods != null && model.methods.isNotEmpty) {
buffer.writeln('..registerMethods({'
'${model.methods.map((g) => "'$g': (o, args) => o.$g.apply(args)").join(', ')}'
'})');
}
if (needsReceiver) {
buffer.writeln(';');
}

View File

@ -0,0 +1,136 @@
library angular2.transform.common.code.queries_code;
import 'package:analyzer/analyzer.dart';
import 'package:analyzer/src/generated/ast.dart';
import 'package:angular2/src/transform/common/annotation_matcher.dart';
import 'package:angular2/src/transform/common/dumb_eval.dart';
import 'package:barback/barback.dart';
/// Visitor responsbile for processing a [ClassDeclaration] and extracting any
/// query fields it defines.
class QueriesVisitor extends RecursiveAstVisitor<Iterable<String>> {
final _QueriesAnnotationVisitor _annotationVisitor;
final _propertyVisitor = new _QueriesPropertyVisitor();
QueriesVisitor(AssetId assetId, AnnotationMatcher annotationMatcher)
: _annotationVisitor =
new _QueriesAnnotationVisitor(assetId, annotationMatcher);
@override
Iterable<String> visitClassDeclaration(ClassDeclaration node) {
final queryFields = new Set<String>();
if (node.metadata != null) {
for (var annotation in node.metadata) {
var annotationQueryFields =
_annotationVisitor.visitAnnotation(annotation);
if (annotationQueryFields != null) {
queryFields.addAll(annotationQueryFields);
}
}
}
// Record annotations attached to properties.
if (node.members != null) {
for (var member in node.members) {
var queryProp = member.accept(_propertyVisitor);
if (queryProp != null) {
queryFields.add(queryProp);
}
}
}
return queryFields.isNotEmpty ? queryFields : null;
}
}
/// Visitor responsbile for processing properties and getters on a
/// [ClassDeclaration] and extracting any query fields it contains.
class _QueriesPropertyVisitor extends SimpleAstVisitor<String> {
@override
String visitFieldDeclaration(FieldDeclaration node) {
for (var variable in node.fields.variables) {
for (var meta in node.metadata) {
if (_isQueryAnnotation(meta)) {
return '${variable.name}';
}
}
}
return null;
}
@override
String visitMethodDeclaration(MethodDeclaration node) {
if (node.isGetter || node.isSetter) {
for (var meta in node.metadata) {
if (_isQueryAnnotation(meta)) {
return '${node.name}';
}
}
}
return null;
}
bool _isQueryAnnotation(Annotation node) {
// TODO(kegluenq): Use ClassMatcherBase to ensure this is a match.
var id = node.name;
final name = id is PrefixedIdentifier ? '${id.identifier}' : '$id';
switch (name) {
case "ContentChild":
case "ViewChild":
case "ContentChildren":
case "ViewChildren":
return true;
default:
return false;
}
}
}
/// Visitor responsible for processing the [Annotation]s on a [ClassDeclaration]
/// and extracting any query fields it contains.
class _QueriesAnnotationVisitor extends SimpleAstVisitor<Iterable<String>> {
/// The file we are processing.
final AssetId assetId;
/// Responsible for testing whether [Annotation]s are those recognized by
/// Angular 2, for example `@Component`.
final AnnotationMatcher _annotationMatcher;
/// All currently found query fields.
Set<String> _queryFields = null;
_QueriesAnnotationVisitor(this.assetId, this._annotationMatcher);
@override
Iterable<String> visitAnnotation(Annotation node) {
var queryFields = null;
if (_annotationMatcher.isView(node, assetId) ||
_annotationMatcher.isComponent(node, assetId)) {
queryFields = _queryFields = new Set<String>();
if (node.arguments != null && node.arguments.arguments != null) {
node.arguments.arguments.accept(this);
}
_queryFields = null;
}
return queryFields;
}
@override
Iterable<String> visitNamedExpression(NamedExpression node) {
if ('${node.name.label}' == "queries") {
if (node.expression is! MapLiteral) {
throw new FormatException(
'Expected a map value for "queries", but got ${node.expression}',
node.toSource());
}
final queries = node.expression as MapLiteral;
for (var entry in queries.entries) {
var queryField = dumbEval(entry.key);
if (queryField != NOT_A_CONSTANT) {
_queryFields.add(queryField.toString());
}
}
}
return null;
}
}

View File

@ -17,7 +17,10 @@ class NgDepsModel extends GeneratedMessage {
ExportModel.create)
..pp(5, 'reflectables', PbFieldType.PM, ReflectionInfoModel.$checkItem,
ReflectionInfoModel.create)
..a(6, 'sourceFile', PbFieldType.OS);
..a(6, 'sourceFile', PbFieldType.OS)
..p(7, 'getters', PbFieldType.PS)
..p(8, 'setters', PbFieldType.PS)
..p(9, 'methods', PbFieldType.PS);
NgDepsModel() : super();
NgDepsModel.fromBuffer(List<int> i,
@ -63,6 +66,12 @@ class NgDepsModel extends GeneratedMessage {
bool hasSourceFile() => $_has(5, 6);
void clearSourceFile() => clearField(6);
List<String> get getters => $_get(6, 7, null);
List<String> get setters => $_get(7, 8, null);
List<String> get methods => $_get(8, 9, null);
}
class _ReadonlyNgDepsModel extends NgDepsModel with ReadonlyMessageMixin {}
@ -94,12 +103,15 @@ const NgDepsModel$json = const {
'6': '.angular2.src.transform.common.model.proto.ReflectionInfoModel'
},
const {'1': 'source_file', '3': 6, '4': 1, '5': 9},
const {'1': 'getters', '3': 7, '4': 3, '5': 9},
const {'1': 'setters', '3': 8, '4': 3, '5': 9},
const {'1': 'methods', '3': 9, '4': 3, '5': 9},
],
};
/**
* Generated with:
* ng_deps_model.proto (83fe43a087fdd0a7ebee360cd6b669570df4d216)
* ng_deps_model.proto (ab93a7f3c411be8a6dafe914f8aae56027e1bac6)
* libprotoc 2.6.1
* dart-protoc-plugin (af5fc2bf1de367a434c3b1847ab260510878ffc0)
*/

View File

@ -20,4 +20,16 @@ message NgDepsModel {
// The basename of the file from which the ng_deps were generated.
// Example: component.dart
optional string source_file = 6;
// The names of getters that should be registered in the Angular 2 reflection
// framework.
repeated string getters = 7;
// The names of setters that should be registered in the Angular 2 reflection
// framework.
repeated string setters = 8;
// The names of methods that should be registered in the Angular 2 reflection
// framework.
repeated string methods = 9;
}