feat(dart/transform): Add the DirectiveMetadataReader
Add a class that parses and reads Directive metadata to prepare for running the Render compiler in the Dart transformer.
This commit is contained in:
parent
c65fd31e86
commit
cf7bef58b0
|
@ -0,0 +1,140 @@
|
||||||
|
library angular2.transform.template_compiler.directive_metadata_reader;
|
||||||
|
|
||||||
|
import 'package:analyzer/analyzer.dart';
|
||||||
|
import 'package:angular2/src/render/api.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:angular2/src/transform/common/parser.dart';
|
||||||
|
import 'package:angular2/src/transform/common/property_utils.dart' as prop;
|
||||||
|
|
||||||
|
/// Reads [DirectiveMetadata] from the `attributes` of `t`.
|
||||||
|
List<DirectiveMetadata> readDirectiveMetadata(RegisteredType t) {
|
||||||
|
var visitor = new _DirectiveMetadataVisitor();
|
||||||
|
t.annotations.accept(visitor);
|
||||||
|
return visitor.directiveMetadata;
|
||||||
|
}
|
||||||
|
|
||||||
|
num _getDirectiveType(String annotationName) {
|
||||||
|
// TODO(kegluneq): Detect subtypes & implementations of `Directive`s.
|
||||||
|
switch (annotationName) {
|
||||||
|
case 'Decorator':
|
||||||
|
return DirectiveMetadata.DECORATOR_TYPE;
|
||||||
|
case 'Component':
|
||||||
|
return DirectiveMetadata.COMPONENT_TYPE;
|
||||||
|
case 'Viewport':
|
||||||
|
return DirectiveMetadata.VIEWPORT_TYPE;
|
||||||
|
default:
|
||||||
|
return -1;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/// Visitor responsible for processing the `annotations` property of a
|
||||||
|
/// [RegisterType] object and pulling out [DirectiveMetadata].
|
||||||
|
class _DirectiveMetadataVisitor extends Object
|
||||||
|
with RecursiveAstVisitor<Object> {
|
||||||
|
DirectiveMetadata current;
|
||||||
|
final List<DirectiveMetadata> directiveMetadata = [];
|
||||||
|
|
||||||
|
@override
|
||||||
|
Object visitInstanceCreationExpression(InstanceCreationExpression node) {
|
||||||
|
var directiveType = _getDirectiveType('${node.constructorName.type.name}');
|
||||||
|
if (directiveType >= 0) {
|
||||||
|
current = new DirectiveMetadata(
|
||||||
|
type: directiveType,
|
||||||
|
compileChildren: false,
|
||||||
|
properties: {},
|
||||||
|
hostListeners: {},
|
||||||
|
setters: [],
|
||||||
|
readAttributes: []);
|
||||||
|
directiveMetadata.add(current);
|
||||||
|
super.visitInstanceCreationExpression(node);
|
||||||
|
}
|
||||||
|
// Annotation we do not recognize - no need to visit.
|
||||||
|
return null;
|
||||||
|
}
|
||||||
|
|
||||||
|
@override
|
||||||
|
Object visitNamedExpression(NamedExpression node) {
|
||||||
|
// TODO(kegluneq): Remove this limitation.
|
||||||
|
if (node.name is! Label || node.name.label is! SimpleIdentifier) {
|
||||||
|
logger.error(
|
||||||
|
'Angular 2 currently only supports simple identifiers in directives.'
|
||||||
|
' Source: ${node}');
|
||||||
|
return null;
|
||||||
|
}
|
||||||
|
var keyString = '${node.name.label}';
|
||||||
|
// TODO(kegluneq): Populate the other values in [DirectiveMetadata] once
|
||||||
|
// they are specified as `hostAttributes` and `hostSetters`.
|
||||||
|
// See [https://github.com/angular/angular/issues/1244]
|
||||||
|
switch (keyString) {
|
||||||
|
case 'selector':
|
||||||
|
_populateSelector(node.expression);
|
||||||
|
break;
|
||||||
|
case 'compileChildren':
|
||||||
|
_populateCompileChildren(node.expression);
|
||||||
|
break;
|
||||||
|
case 'properties':
|
||||||
|
_populateProperties(node.expression);
|
||||||
|
break;
|
||||||
|
case 'hostListeners':
|
||||||
|
_populateHostListeners(node.expression);
|
||||||
|
}
|
||||||
|
return null;
|
||||||
|
}
|
||||||
|
|
||||||
|
String _expressionToString(Expression node, String nodeDescription) {
|
||||||
|
// TODO(kegluneq): Accept more options.
|
||||||
|
if (node is! SimpleStringLiteral) {
|
||||||
|
logger.error('Angular 2 currently only supports string literals '
|
||||||
|
'in $nodeDescription. Source: ${node}');
|
||||||
|
return null;
|
||||||
|
}
|
||||||
|
return stringLiteralToString(node);
|
||||||
|
}
|
||||||
|
|
||||||
|
void _populateSelector(Expression selectorValue) {
|
||||||
|
current.selector = _expressionToString(selectorValue, 'Directive#selector');
|
||||||
|
}
|
||||||
|
|
||||||
|
void _populateCompileChildren(Expression compileChildrenValue) {
|
||||||
|
if (compileChildrenValue is! BooleanLiteral) {
|
||||||
|
logger.error(
|
||||||
|
'Angular 2 currently only supports boolean literal values for '
|
||||||
|
'Decorator#compileChildren.'
|
||||||
|
' Source: ${node}');
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
current.compileChildren = compileChildrenValue.value;
|
||||||
|
}
|
||||||
|
|
||||||
|
void _populateProperties(Expression propertiesValue) {
|
||||||
|
if (propertiesValue is! MapLiteral) {
|
||||||
|
logger.error('Angular 2 currently only supports map literal values for '
|
||||||
|
'Directive#properties.'
|
||||||
|
' Source: ${node}');
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
for (MapLiteralEntry entry in propertiesValue.entries) {
|
||||||
|
var sKey = _expressionToString(entry.key, 'Directive#properties keys');
|
||||||
|
var sVal = _expressionToString(entry.value, 'Direcive#properties values');
|
||||||
|
current.properties[sKey] = sVal;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
void _populateHostListeners(Expression hostListenersValue) {
|
||||||
|
if (hostListenersValue is! MapLiteral) {
|
||||||
|
logger.error('Angular 2 currently only supports map literal values for '
|
||||||
|
'Directive#hostListeners.'
|
||||||
|
' Source: ${node}');
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
for (MapLiteralEntry entry in hostListenersValue.entries) {
|
||||||
|
var sKey = _expressionToString(entry.key, 'Directive#hostListeners keys');
|
||||||
|
var sVal =
|
||||||
|
_expressionToString(entry.value, 'Directive#hostListeners values');
|
||||||
|
current.hostListeners[sKey] = sVal;
|
||||||
|
;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
|
@ -3,6 +3,8 @@ library angular2.test.transform.template_compiler.all_tests;
|
||||||
import 'package:barback/barback.dart';
|
import 'package:barback/barback.dart';
|
||||||
import 'package:angular2/src/dom/html_adapter.dart';
|
import 'package:angular2/src/dom/html_adapter.dart';
|
||||||
import 'package:angular2/src/transform/common/asset_reader.dart';
|
import 'package:angular2/src/transform/common/asset_reader.dart';
|
||||||
|
import 'package:angular2/src/transform/common/parser.dart';
|
||||||
|
import 'package:angular2/src/transform/template_compiler/directive_metadata_reader.dart';
|
||||||
import 'package:angular2/src/transform/template_compiler/generator.dart';
|
import 'package:angular2/src/transform/template_compiler/generator.dart';
|
||||||
import 'package:dart_style/dart_style.dart';
|
import 'package:dart_style/dart_style.dart';
|
||||||
import 'package:guinness/guinness.dart';
|
import 'package:guinness/guinness.dart';
|
||||||
|
@ -14,6 +16,7 @@ var formatter = new DartFormatter();
|
||||||
void allTests() {
|
void allTests() {
|
||||||
Html5LibDomAdapter.makeCurrent();
|
Html5LibDomAdapter.makeCurrent();
|
||||||
AssetReader reader = new TestAssetReader();
|
AssetReader reader = new TestAssetReader();
|
||||||
|
var parser = new Parser(reader);
|
||||||
|
|
||||||
it('should parse simple expressions in inline templates.', () async {
|
it('should parse simple expressions in inline templates.', () async {
|
||||||
var inputPath =
|
var inputPath =
|
||||||
|
@ -47,6 +50,48 @@ void allTests() {
|
||||||
var output = await processTemplates(reader, new AssetId('a', inputPath));
|
var output = await processTemplates(reader, new AssetId('a', inputPath));
|
||||||
_formatThenExpectEquals(output, expected);
|
_formatThenExpectEquals(output, expected);
|
||||||
});
|
});
|
||||||
|
|
||||||
|
describe('DirectiveMetadataReader', () {
|
||||||
|
it('should parse simple expressions in inline templates', () async {
|
||||||
|
var inputPath =
|
||||||
|
'template_compiler/inline_expression_files/hello.ng_deps.dart';
|
||||||
|
var ngDeps = await parser.parse(new AssetId('a', inputPath));
|
||||||
|
|
||||||
|
var metadata = readDirectiveMetadata(ngDeps.registeredTypes.first);
|
||||||
|
expect(metadata.length).toEqual(2);
|
||||||
|
expect(metadata[0].selector).toEqual('hello-app');
|
||||||
|
});
|
||||||
|
|
||||||
|
it('should parse simple methods in inline templates', () async {
|
||||||
|
var inputPath =
|
||||||
|
'template_compiler/inline_method_files/hello.ng_deps.dart';
|
||||||
|
var ngDeps = await parser.parse(new AssetId('a', inputPath));
|
||||||
|
|
||||||
|
var metadata = readDirectiveMetadata(ngDeps.registeredTypes.first);
|
||||||
|
expect(metadata.length).toEqual(2);
|
||||||
|
expect(metadata[0].selector).toEqual('hello-app');
|
||||||
|
});
|
||||||
|
|
||||||
|
it('should parse simple expressions in linked templates.', () async {
|
||||||
|
var inputPath =
|
||||||
|
'template_compiler/url_expression_files/hello.ng_deps.dart';
|
||||||
|
var ngDeps = await parser.parse(new AssetId('a', inputPath));
|
||||||
|
|
||||||
|
var metadata = readDirectiveMetadata(ngDeps.registeredTypes.first);
|
||||||
|
expect(metadata.length).toEqual(2);
|
||||||
|
expect(metadata[0].selector).toEqual('hello-app');
|
||||||
|
});
|
||||||
|
|
||||||
|
it('should parse simple methods in linked templates.', () async {
|
||||||
|
var inputPath =
|
||||||
|
'template_compiler/url_method_files/hello.ng_deps.dart';
|
||||||
|
var ngDeps = await parser.parse(new AssetId('a', inputPath));
|
||||||
|
|
||||||
|
var metadata = readDirectiveMetadata(ngDeps.registeredTypes.first);
|
||||||
|
expect(metadata.length).toEqual(2);
|
||||||
|
expect(metadata[0].selector).toEqual('hello-app');
|
||||||
|
});
|
||||||
|
});
|
||||||
}
|
}
|
||||||
|
|
||||||
void _formatThenExpectEquals(String actual, String expected) {
|
void _formatThenExpectEquals(String actual, String expected) {
|
||||||
|
|
Loading…
Reference in New Issue