feat(dart/transform): Generate setter stubs.
Generate calls to Reflector#registerSetters from the information in provided `Directive#bind` values. This is only an initial attempt - it covers only the most basic values of `bind`. Closes #780
This commit is contained in:
parent
1376e49ea0
commit
50a74b1d91
|
@ -8,30 +8,32 @@ import 'package:dart_style/dart_style.dart';
|
||||||
import 'package:path/path.dart' as path;
|
import 'package:path/path.dart' as path;
|
||||||
|
|
||||||
import 'annotation_processor.dart';
|
import 'annotation_processor.dart';
|
||||||
|
import 'logging.dart';
|
||||||
|
|
||||||
/// Base class that maintains codegen state.
|
/// Base class that maintains codegen state.
|
||||||
class Context {
|
class Context {
|
||||||
final TransformLogger _logger;
|
|
||||||
/// Maps libraries to the import prefixes we will use in the newly
|
/// Maps libraries to the import prefixes we will use in the newly
|
||||||
/// generated code.
|
/// generated code.
|
||||||
final Map<LibraryElement, String> _libraryPrefixes;
|
final Map<LibraryElement, String> _libraryPrefixes = {};
|
||||||
|
|
||||||
|
/// Whether to generate constructor stubs for classes annotated
|
||||||
|
/// with [Component], [Decorator], [Template], and [Inject] (and subtypes).
|
||||||
|
bool generateCtorStubs = true;
|
||||||
|
|
||||||
|
/// Whether to generate setter stubs for classes annotated with
|
||||||
|
/// [Directive] subtypes. These setters depend on the value passed to the
|
||||||
|
/// annotation's `bind` value.
|
||||||
|
bool generateSetterStubs = true;
|
||||||
|
|
||||||
DirectiveRegistry _directiveRegistry;
|
DirectiveRegistry _directiveRegistry;
|
||||||
/// Generates [registerType] calls for all [register]ed [AnnotationMatch]
|
/// Generates [registerType] calls for all [register]ed [AnnotationMatch]
|
||||||
/// objects.
|
/// objects.
|
||||||
DirectiveRegistry get directiveRegistry => _directiveRegistry;
|
DirectiveRegistry get directiveRegistry => _directiveRegistry;
|
||||||
|
|
||||||
Context({TransformLogger logger})
|
Context() {
|
||||||
: _logger = logger,
|
|
||||||
_libraryPrefixes = {} {
|
|
||||||
_directiveRegistry = new _DirectiveRegistryImpl(this);
|
_directiveRegistry = new _DirectiveRegistryImpl(this);
|
||||||
}
|
}
|
||||||
|
|
||||||
void error(String errorString) {
|
|
||||||
if (_logger == null) throw new CodegenError(errorString);
|
|
||||||
_logger.error(errorString);
|
|
||||||
}
|
|
||||||
|
|
||||||
/// If elements in [lib] should be prefixed in our generated code, returns
|
/// If elements in [lib] should be prefixed in our generated code, returns
|
||||||
/// the appropriate prefix followed by a `.`. Future items from the same
|
/// the appropriate prefix followed by a `.`. Future items from the same
|
||||||
/// library will use the same prefix.
|
/// library will use the same prefix.
|
||||||
|
@ -44,16 +46,6 @@ class Context {
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
class CodegenError extends Error {
|
|
||||||
final String message;
|
|
||||||
CodegenError(this.message);
|
|
||||||
|
|
||||||
@override
|
|
||||||
String toString() {
|
|
||||||
return 'Error generating Angular2 code: ${Error.safeToString(message)}';
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
/// Object which [register]s [AnnotationMatch] objects for code generation.
|
/// Object which [register]s [AnnotationMatch] objects for code generation.
|
||||||
abstract class DirectiveRegistry {
|
abstract class DirectiveRegistry {
|
||||||
// Adds [entry] to the `registerType` calls which will be generated.
|
// Adds [entry] to the `registerType` calls which will be generated.
|
||||||
|
@ -91,7 +83,7 @@ String codegenEntryPoint(Context context, {AssetId newEntryPoint}) {
|
||||||
return new DartFormatter().format(outBuffer.toString());
|
return new DartFormatter().format(outBuffer.toString());
|
||||||
}
|
}
|
||||||
|
|
||||||
String _codegenImports(
|
void _codegenImports(
|
||||||
Context context, AssetId newEntryPoint, StringBuffer buffer) {
|
Context context, AssetId newEntryPoint, StringBuffer buffer) {
|
||||||
context._libraryPrefixes.forEach((lib, prefix) {
|
context._libraryPrefixes.forEach((lib, prefix) {
|
||||||
buffer
|
buffer
|
||||||
|
@ -101,19 +93,19 @@ String _codegenImports(
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
|
|
||||||
_codegenImport(Context context, AssetId libraryId, AssetId entryPoint) {
|
String _codegenImport(Context context, AssetId libraryId, AssetId entryPoint) {
|
||||||
if (libraryId.path.startsWith('lib/')) {
|
if (libraryId.path.startsWith('lib/')) {
|
||||||
var packagePath = libraryId.path.replaceFirst('lib/', '');
|
var packagePath = libraryId.path.replaceFirst('lib/', '');
|
||||||
return "import 'package:${libraryId.package}/${packagePath}'";
|
return "import 'package:${libraryId.package}/${packagePath}'";
|
||||||
} else if (libraryId.package != entryPoint.package) {
|
} else if (libraryId.package != entryPoint.package) {
|
||||||
context._error("Can't import `${libraryId}` from `${entryPoint}`");
|
logger.error("Can't import `${libraryId}` from `${entryPoint}`");
|
||||||
} else if (path.url.split(libraryId.path)[0] ==
|
} else if (path.url.split(libraryId.path)[0] ==
|
||||||
path.url.split(entryPoint.path)[0]) {
|
path.url.split(entryPoint.path)[0]) {
|
||||||
var relativePath =
|
var relativePath =
|
||||||
path.relative(libraryId.path, from: path.dirname(entryPoint.path));
|
path.relative(libraryId.path, from: path.dirname(entryPoint.path));
|
||||||
return "import '${relativePath}'";
|
return "import '${relativePath}'";
|
||||||
} else {
|
} else {
|
||||||
context._error("Can't import `${libraryId}` from `${entryPoint}`");
|
logger.error("Can't import `${libraryId}` from `${entryPoint}`");
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -121,14 +113,29 @@ _codegenImport(Context context, AssetId libraryId, AssetId entryPoint) {
|
||||||
// Element#node.
|
// Element#node.
|
||||||
class _DirectiveRegistryImpl implements DirectiveRegistry {
|
class _DirectiveRegistryImpl implements DirectiveRegistry {
|
||||||
final Context _context;
|
final Context _context;
|
||||||
final StringBuffer _buffer = new StringBuffer();
|
final PrintWriter _writer;
|
||||||
final Set<ClassDeclaration> _seen = new Set();
|
final Set<ClassDeclaration> _seen = new Set();
|
||||||
|
final _AnnotationsTransformVisitor _annotationsVisitor;
|
||||||
|
final _BindTransformVisitor _bindVisitor;
|
||||||
|
final _FactoryTransformVisitor _factoryVisitor;
|
||||||
|
final _ParameterTransformVisitor _parametersVisitor;
|
||||||
|
|
||||||
_DirectiveRegistryImpl(this._context);
|
_DirectiveRegistryImpl._internal(Context context, PrintWriter writer)
|
||||||
|
: _writer = writer,
|
||||||
|
_context = context,
|
||||||
|
_annotationsVisitor = new _AnnotationsTransformVisitor(writer, context),
|
||||||
|
_bindVisitor = new _BindTransformVisitor(writer, context),
|
||||||
|
_factoryVisitor = new _FactoryTransformVisitor(writer, context),
|
||||||
|
_parametersVisitor = new _ParameterTransformVisitor(writer, context);
|
||||||
|
|
||||||
|
factory _DirectiveRegistryImpl(Context context) {
|
||||||
|
return new _DirectiveRegistryImpl._internal(
|
||||||
|
context, new PrintStringWriter());
|
||||||
|
}
|
||||||
|
|
||||||
@override
|
@override
|
||||||
String toString() {
|
String toString() {
|
||||||
return _buffer.isEmpty ? '' : 'reflector${_buffer};';
|
return _seen.isEmpty ? '' : 'reflector${_writer};';
|
||||||
}
|
}
|
||||||
|
|
||||||
// Adds [entry] to the `registerType` calls which will be generated.
|
// Adds [entry] to the `registerType` calls which will be generated.
|
||||||
|
@ -136,87 +143,104 @@ class _DirectiveRegistryImpl implements DirectiveRegistry {
|
||||||
if (_seen.contains(entry.node)) return;
|
if (_seen.contains(entry.node)) return;
|
||||||
_seen.add(entry.node);
|
_seen.add(entry.node);
|
||||||
|
|
||||||
|
if (_context.generateCtorStubs) {
|
||||||
|
_generateCtorStubs(entry);
|
||||||
|
}
|
||||||
|
if (_context.generateSetterStubs) {
|
||||||
|
_generateSetterStubs(entry);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
void _generateSetterStubs(AnnotationMatch entry) {
|
||||||
|
// TODO(kegluneq): Remove these requirements for setter stub generation.
|
||||||
|
if (entry.element is! ClassElement) {
|
||||||
|
logger.error('Directives can only be applied to classes.');
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
if (entry.node is! ClassDeclaration) {
|
||||||
|
logger.error('Unsupported annotation type for ctor stub generation. '
|
||||||
|
'Only class declarations are supported as Directives.');
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
entry.node.accept(_bindVisitor);
|
||||||
|
}
|
||||||
|
|
||||||
|
void _generateCtorStubs(AnnotationMatch entry) {
|
||||||
var element = entry.element;
|
var element = entry.element;
|
||||||
var annotation = entry.annotation;
|
var annotation = entry.annotation;
|
||||||
|
|
||||||
|
// TODO(kegluneq): Remove these requirements for ctor stub generation.
|
||||||
if (annotation.element is! ConstructorElement) {
|
if (annotation.element is! ConstructorElement) {
|
||||||
_context._error('Unsupported annotation type. '
|
logger.error('Unsupported annotation type for ctor stub generation. '
|
||||||
'Only constructors are supported as Directives.');
|
'Only constructors are supported as Directives.');
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
if (element is! ClassElement) {
|
if (element is! ClassElement) {
|
||||||
_context._error('Directives can only be applied to classes.');
|
logger.error('Directives can only be applied to classes.');
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
if (element.node is! ClassDeclaration) {
|
if (entry.node is! ClassDeclaration) {
|
||||||
_context._error('Unsupported annotation type. '
|
logger.error('Unsupported annotation type for ctor stub generation. '
|
||||||
'Only class declarations are supported as Directives.');
|
'Only class declarations are supported as Directives.');
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
final ConstructorElement ctor = element.unnamedConstructor;
|
var ctor = element.unnamedConstructor;
|
||||||
if (ctor == null) {
|
if (ctor == null) {
|
||||||
_context._error('No default constructor found for ${element.name}');
|
logger.error('No unnamed constructor found for ${element.name}');
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
var ctorNode = ctor.node;
|
||||||
|
|
||||||
_buffer.writeln('..registerType(${_codegenClassTypeString(element)}, {'
|
_writer.print('..registerType(');
|
||||||
'"factory": ${_codegenFactoryProp(ctor)},'
|
_codegenClassTypeString(element);
|
||||||
'"parameters": ${_codegenParametersProp(ctor)},'
|
_writer.print(', {"factory": ');
|
||||||
'"annotations": ${_codegenAnnotationsProp(element)}'
|
_codegenFactoryProp(ctorNode, element);
|
||||||
'})');
|
_writer.print(', "parameters": ');
|
||||||
|
_codegenParametersProp(ctorNode);
|
||||||
|
_writer.print(', "annotations": ');
|
||||||
|
_codegenAnnotationsProp(entry.node);
|
||||||
|
_writer.print('})');
|
||||||
}
|
}
|
||||||
|
|
||||||
String _codegenClassTypeString(ClassElement el) {
|
void _codegenClassTypeString(ClassElement el) {
|
||||||
return '${_context._getPrefixDot(el.library)}${el.name}';
|
_writer.print('${_context._getPrefixDot(el.library)}${el.name}');
|
||||||
}
|
}
|
||||||
|
|
||||||
/// Creates the 'annotations' property for the Angular2 [registerType] call
|
/// Creates the 'annotations' property for the Angular2 [registerType] call
|
||||||
/// for [el].
|
/// for [node].
|
||||||
String _codegenAnnotationsProp(ClassElement el) {
|
void _codegenAnnotationsProp(ClassDeclaration node) {
|
||||||
var writer = new PrintStringWriter();
|
node.accept(_annotationsVisitor);
|
||||||
var visitor = new _AnnotationsTransformVisitor(writer, _context);
|
|
||||||
el.node.accept(visitor);
|
|
||||||
return writer.toString();
|
|
||||||
}
|
}
|
||||||
|
|
||||||
/// Creates the 'factory' property for the Angular2 [registerType] call
|
/// Creates the 'factory' property for the Angular2 [registerType] call
|
||||||
/// for [ctor].
|
/// for [node]. [element] is necessary if [node] is null.
|
||||||
String _codegenFactoryProp(ConstructorElement ctor) {
|
void _codegenFactoryProp(ConstructorDeclaration node, ClassElement element) {
|
||||||
if (ctor.node == null) {
|
if (node == null) {
|
||||||
// This occurs when the class does not declare a constructor.
|
// This occurs when the class does not declare a constructor.
|
||||||
var prefix = _context._getPrefixDot(ctor.type.element.library);
|
var prefix = _context._getPrefixDot(element.library);
|
||||||
return '() => new ${prefix}${ctor.enclosingElement.displayName}()';
|
_writer.print('() => new ${prefix}${element.displayName}()');
|
||||||
} else {
|
} else {
|
||||||
var writer = new PrintStringWriter();
|
node.accept(_factoryVisitor);
|
||||||
var visitor = new _FactoryTransformVisitor(writer, _context);
|
|
||||||
ctor.node.accept(visitor);
|
|
||||||
return writer.toString();
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
/// Creates the 'parameters' property for the Angular2 [registerType] call
|
/// Creates the 'parameters' property for the Angular2 [registerType] call
|
||||||
/// for [ctor].
|
/// for [node].
|
||||||
String _codegenParametersProp(ConstructorElement ctor) {
|
void _codegenParametersProp(ConstructorDeclaration node) {
|
||||||
if (ctor.node == null) {
|
if (node == null) {
|
||||||
// This occurs when the class does not declare a constructor.
|
// This occurs when the class does not declare a constructor.
|
||||||
return 'const [const []]';
|
_writer.print('const [const []]');
|
||||||
} else {
|
} else {
|
||||||
var writer = new PrintStringWriter();
|
node.accept(_parametersVisitor);
|
||||||
var visitor = new _ParameterTransformVisitor(writer, _context);
|
|
||||||
ctor.node.accept(visitor);
|
|
||||||
return writer.toString();
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
/// Visitor providing common methods for concrete implementations.
|
/// Visitor providing common methods for concrete implementations.
|
||||||
abstract class _TransformVisitor extends ToSourceVisitor {
|
class _TransformVisitorMixin {
|
||||||
final Context _context;
|
final Context context;
|
||||||
final PrintWriter _writer;
|
final PrintWriter writer;
|
||||||
|
|
||||||
_TransformVisitor(PrintWriter writer, this._context)
|
|
||||||
: this._writer = writer,
|
|
||||||
super(writer);
|
|
||||||
|
|
||||||
/// Safely visit [node].
|
/// Safely visit [node].
|
||||||
void _visitNode(AstNode node) {
|
void _visitNode(AstNode node) {
|
||||||
|
@ -229,7 +253,7 @@ abstract class _TransformVisitor extends ToSourceVisitor {
|
||||||
/// visits [node].
|
/// visits [node].
|
||||||
void _visitNodeWithPrefix(String prefix, AstNode node) {
|
void _visitNodeWithPrefix(String prefix, AstNode node) {
|
||||||
if (node != null) {
|
if (node != null) {
|
||||||
_writer.print(prefix);
|
writer.print(prefix);
|
||||||
node.accept(this);
|
node.accept(this);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@ -239,29 +263,41 @@ abstract class _TransformVisitor extends ToSourceVisitor {
|
||||||
void _visitNodeWithSuffix(AstNode node, String suffix) {
|
void _visitNodeWithSuffix(AstNode node, String suffix) {
|
||||||
if (node != null) {
|
if (node != null) {
|
||||||
node.accept(this);
|
node.accept(this);
|
||||||
_writer.print(suffix);
|
writer.print(suffix);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
String prefixedSimpleIdentifier(SimpleIdentifier node) {
|
||||||
|
// Make sure the identifier is prefixed if necessary.
|
||||||
|
if (node.bestElement is ClassElementImpl ||
|
||||||
|
node.bestElement is PropertyAccessorElement) {
|
||||||
|
return context._getPrefixDot(node.bestElement.library) +
|
||||||
|
node.token.lexeme;
|
||||||
|
} else {
|
||||||
|
return node.token.lexeme;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
class _TransformVisitor extends ToSourceVisitor with _TransformVisitorMixin {
|
||||||
|
final Context context;
|
||||||
|
final PrintWriter writer;
|
||||||
|
|
||||||
|
_TransformVisitor(PrintWriter writer, this.context)
|
||||||
|
: this.writer = writer,
|
||||||
|
super(writer);
|
||||||
|
|
||||||
@override
|
@override
|
||||||
Object visitPrefixedIdentifier(PrefixedIdentifier node) {
|
Object visitPrefixedIdentifier(PrefixedIdentifier node) {
|
||||||
// We add our own prefixes in [visitSimpleIdentifier], discard any used in
|
// We add our own prefixes in [visitSimpleIdentifier], discard any used in
|
||||||
// the original source.
|
// the original source.
|
||||||
_visitNode(node.identifier);
|
writer.print(super.prefixedSimpleIdentifier(node.identifier));
|
||||||
return null;
|
return null;
|
||||||
}
|
}
|
||||||
|
|
||||||
@override
|
@override
|
||||||
Object visitSimpleIdentifier(SimpleIdentifier node) {
|
Object visitSimpleIdentifier(SimpleIdentifier node) {
|
||||||
// Make sure the identifier is prefixed if necessary.
|
writer.print(super.prefixedSimpleIdentifier(node));
|
||||||
if (node.bestElement is ClassElementImpl ||
|
|
||||||
node.bestElement is PropertyAccessorElement) {
|
|
||||||
_writer
|
|
||||||
..print(_context._getPrefixDot(node.bestElement.library))
|
|
||||||
..print(node.token.lexeme);
|
|
||||||
} else {
|
|
||||||
return super.visitSimpleIdentifier(node);
|
|
||||||
}
|
|
||||||
return null;
|
return null;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@ -280,8 +316,8 @@ class _CtorTransformVisitor extends _TransformVisitor {
|
||||||
Object _visitNormalFormalParameter(NormalFormalParameter node) {
|
Object _visitNormalFormalParameter(NormalFormalParameter node) {
|
||||||
if (_withParameterTypes) {
|
if (_withParameterTypes) {
|
||||||
var paramType = node.element.type;
|
var paramType = node.element.type;
|
||||||
var prefix = _context._getPrefixDot(paramType.element.library);
|
var prefix = context._getPrefixDot(paramType.element.library);
|
||||||
_writer.print('${prefix}${paramType.displayName}');
|
writer.print('${prefix}${paramType.displayName}');
|
||||||
if (_withParameterNames) {
|
if (_withParameterNames) {
|
||||||
_visitNodeWithPrefix(' ', node.identifier);
|
_visitNodeWithPrefix(' ', node.identifier);
|
||||||
}
|
}
|
||||||
|
@ -299,7 +335,7 @@ class _CtorTransformVisitor extends _TransformVisitor {
|
||||||
@override
|
@override
|
||||||
Object visitFieldFormalParameter(FieldFormalParameter node) {
|
Object visitFieldFormalParameter(FieldFormalParameter node) {
|
||||||
if (node.parameters != null) {
|
if (node.parameters != null) {
|
||||||
_context.error('Parameters in ctor not supported '
|
logger.error('Parameters in ctor not supported '
|
||||||
'(${super.visitFormalParameterList(node)}');
|
'(${super.visitFormalParameterList(node)}');
|
||||||
}
|
}
|
||||||
return _visitNormalFormalParameter(node);
|
return _visitNormalFormalParameter(node);
|
||||||
|
@ -315,16 +351,16 @@ class _CtorTransformVisitor extends _TransformVisitor {
|
||||||
@override
|
@override
|
||||||
/// Overridden to avoid outputting grouping operators for default parameters.
|
/// Overridden to avoid outputting grouping operators for default parameters.
|
||||||
Object visitFormalParameterList(FormalParameterList node) {
|
Object visitFormalParameterList(FormalParameterList node) {
|
||||||
_writer.print('(');
|
writer.print('(');
|
||||||
NodeList<FormalParameter> parameters = node.parameters;
|
NodeList<FormalParameter> parameters = node.parameters;
|
||||||
int size = parameters.length;
|
int size = parameters.length;
|
||||||
for (int i = 0; i < size; i++) {
|
for (int i = 0; i < size; i++) {
|
||||||
if (i > 0) {
|
if (i > 0) {
|
||||||
_writer.print(', ');
|
writer.print(', ');
|
||||||
}
|
}
|
||||||
parameters[i].accept(this);
|
parameters[i].accept(this);
|
||||||
}
|
}
|
||||||
_writer.print(')');
|
writer.print(')');
|
||||||
return null;
|
return null;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@ -339,9 +375,9 @@ class _ParameterTransformVisitor extends _CtorTransformVisitor {
|
||||||
Object visitConstructorDeclaration(ConstructorDeclaration node) {
|
Object visitConstructorDeclaration(ConstructorDeclaration node) {
|
||||||
_withParameterNames = false;
|
_withParameterNames = false;
|
||||||
_withParameterTypes = true;
|
_withParameterTypes = true;
|
||||||
_writer.print('const [const [');
|
writer.print('const [const [');
|
||||||
_visitNode(node.parameters);
|
_visitNode(node.parameters);
|
||||||
_writer.print(']]');
|
writer.print(']]');
|
||||||
return null;
|
return null;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -351,7 +387,7 @@ class _ParameterTransformVisitor extends _CtorTransformVisitor {
|
||||||
int size = parameters.length;
|
int size = parameters.length;
|
||||||
for (int i = 0; i < size; i++) {
|
for (int i = 0; i < size; i++) {
|
||||||
if (i > 0) {
|
if (i > 0) {
|
||||||
_writer.print(', ');
|
writer.print(', ');
|
||||||
}
|
}
|
||||||
parameters[i].accept(this);
|
parameters[i].accept(this);
|
||||||
}
|
}
|
||||||
|
@ -370,7 +406,7 @@ class _FactoryTransformVisitor extends _CtorTransformVisitor {
|
||||||
_withParameterNames = true;
|
_withParameterNames = true;
|
||||||
_withParameterTypes = true;
|
_withParameterTypes = true;
|
||||||
_visitNode(node.parameters);
|
_visitNode(node.parameters);
|
||||||
_writer.print(' => new ');
|
writer.print(' => new ');
|
||||||
_visitNode(node.returnType);
|
_visitNode(node.returnType);
|
||||||
_visitNodeWithPrefix(".", node.name);
|
_visitNodeWithPrefix(".", node.name);
|
||||||
_withParameterTypes = false;
|
_withParameterTypes = false;
|
||||||
|
@ -387,21 +423,21 @@ class _AnnotationsTransformVisitor extends _TransformVisitor {
|
||||||
|
|
||||||
@override
|
@override
|
||||||
Object visitClassDeclaration(ClassDeclaration node) {
|
Object visitClassDeclaration(ClassDeclaration node) {
|
||||||
_writer.print('const [');
|
writer.print('const [');
|
||||||
var size = node.metadata.length;
|
var size = node.metadata.length;
|
||||||
for (var i = 0; i < size; ++i) {
|
for (var i = 0; i < size; ++i) {
|
||||||
if (i > 0) {
|
if (i > 0) {
|
||||||
_writer.print(', ');
|
writer.print(', ');
|
||||||
}
|
}
|
||||||
node.metadata[i].accept(this);
|
node.metadata[i].accept(this);
|
||||||
}
|
}
|
||||||
_writer.print(']');
|
writer.print(']');
|
||||||
return null;
|
return null;
|
||||||
}
|
}
|
||||||
|
|
||||||
@override
|
@override
|
||||||
Object visitAnnotation(Annotation node) {
|
Object visitAnnotation(Annotation node) {
|
||||||
_writer.print('const ');
|
writer.print('const ');
|
||||||
_visitNode(node.name);
|
_visitNode(node.name);
|
||||||
// TODO(tjblasi): Do we need to handle named constructors for annotations?
|
// TODO(tjblasi): Do we need to handle named constructors for annotations?
|
||||||
// _visitNodeWithPrefix(".", node.constructorName);
|
// _visitNodeWithPrefix(".", node.constructorName);
|
||||||
|
@ -409,3 +445,71 @@ class _AnnotationsTransformVisitor extends _TransformVisitor {
|
||||||
return null;
|
return null;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/// Visitor designed to print a [ClassDeclaration] node as a
|
||||||
|
/// `registerSetters` call for Angular2.
|
||||||
|
class _BindTransformVisitor extends Object
|
||||||
|
with SimpleAstVisitor<Object>, _TransformVisitorMixin {
|
||||||
|
final Context context;
|
||||||
|
final PrintWriter writer;
|
||||||
|
final List<String> _bindPieces = [];
|
||||||
|
SimpleIdentifier _currentName = null;
|
||||||
|
|
||||||
|
_BindTransformVisitor(this.writer, this.context);
|
||||||
|
|
||||||
|
@override
|
||||||
|
Object visitClassDeclaration(ClassDeclaration node) {
|
||||||
|
_currentName = node.name;
|
||||||
|
node.metadata.forEach((meta) => _visitNode(meta));
|
||||||
|
if (_bindPieces.isNotEmpty) {
|
||||||
|
writer.print('..registerSetters({${_bindPieces.join(', ')}})');
|
||||||
|
}
|
||||||
|
return null;
|
||||||
|
}
|
||||||
|
|
||||||
|
@override
|
||||||
|
Object visitAnnotation(Annotation node) {
|
||||||
|
// TODO(kegluneq): Remove this restriction.
|
||||||
|
if (node.element is ConstructorElement) {
|
||||||
|
if (node.element.returnType.element is ClassElement) {
|
||||||
|
// TODO(kegluneq): Check if this is actually a `directive`.
|
||||||
|
node.arguments.arguments.forEach((arg) => _visitNode(arg));
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return null;
|
||||||
|
}
|
||||||
|
|
||||||
|
@override
|
||||||
|
Object visitNamedExpression(NamedExpression node) {
|
||||||
|
if (node.name.label.toString() == 'bind') {
|
||||||
|
// TODO(kegluneq): Remove this restriction.
|
||||||
|
if (node.expression is MapLiteral) {
|
||||||
|
node.expression.accept(this);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return null;
|
||||||
|
}
|
||||||
|
|
||||||
|
@override
|
||||||
|
Object visitMapLiteral(MapLiteral node) {
|
||||||
|
node.entries.forEach((entry) {
|
||||||
|
if (entry.key is SimpleStringLiteral) {
|
||||||
|
_visitNode(entry.key);
|
||||||
|
} else {
|
||||||
|
logger.error('`bind` currently only supports string literals');
|
||||||
|
}
|
||||||
|
});
|
||||||
|
return null;
|
||||||
|
}
|
||||||
|
|
||||||
|
@override
|
||||||
|
Object visitSimpleStringLiteral(SimpleStringLiteral node) {
|
||||||
|
if (_currentName == null) {
|
||||||
|
logger.error('Unexpected code path: `currentName` should never be null');
|
||||||
|
}
|
||||||
|
_bindPieces.add('"${node.value}": ('
|
||||||
|
'${super.prefixedSimpleIdentifier(_currentName)} o, String value) => '
|
||||||
|
'o.${node.value} = value');
|
||||||
|
return null;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
|
@ -118,16 +118,13 @@ class _BootstrapFileBuilder {
|
||||||
|
|
||||||
var types = new Angular2Types(_resolver);
|
var types = new Angular2Types(_resolver);
|
||||||
// TODO(kegluneq): Also match [Inject].
|
// TODO(kegluneq): Also match [Inject].
|
||||||
var matcher = new AnnotationMatcher(new Set.from([
|
var matcher = new AnnotationMatcher(
|
||||||
types.componentAnnotation,
|
new Set.from([types.directiveAnnotation, types.templateAnnotation]));
|
||||||
types.decoratorAnnotation,
|
|
||||||
types.templateAnnotation
|
|
||||||
]));
|
|
||||||
|
|
||||||
var traversal = new AngularVisibleTraversal(types, matcher);
|
var traversal = new AngularVisibleTraversal(types, matcher);
|
||||||
bootstrapCalls.forEach((call) => traversal.traverse(call.bootstrapType));
|
bootstrapCalls.forEach((call) => traversal.traverse(call.bootstrapType));
|
||||||
|
|
||||||
var context = new codegen.Context(logger: _transform.logger);
|
var context = new codegen.Context();
|
||||||
matcher.matchQueue
|
matcher.matchQueue
|
||||||
.forEach((entry) => context.directiveRegistry.register(entry));
|
.forEach((entry) => context.directiveRegistry.register(entry));
|
||||||
|
|
||||||
|
|
|
@ -0,0 +1,11 @@
|
||||||
|
library bar;
|
||||||
|
|
||||||
|
import 'package:angular2/src/core/annotations/annotations.dart';
|
||||||
|
|
||||||
|
@Component(selector: 'soup', componentServices: const [ToolTip])
|
||||||
|
class MyComponent {}
|
||||||
|
|
||||||
|
@Decorator(selector: '[tool-tip]', bind: const {'text': 'tool-tip'})
|
||||||
|
class ToolTip {
|
||||||
|
String text;
|
||||||
|
}
|
|
@ -0,0 +1,26 @@
|
||||||
|
library angular2.src.transform.generated;
|
||||||
|
|
||||||
|
import 'package:angular2/src/reflection/reflection.dart' show reflector;
|
||||||
|
import 'bar.dart' as i0;
|
||||||
|
import 'package:angular2/src/core/annotations/annotations.dart' as i1;
|
||||||
|
|
||||||
|
setupReflection() {
|
||||||
|
reflector
|
||||||
|
..registerType(i0.MyComponent, {
|
||||||
|
"factory": () => new i0.MyComponent(),
|
||||||
|
"parameters": const [const []],
|
||||||
|
"annotations": const [
|
||||||
|
const i1.Component(
|
||||||
|
selector: 'soup', componentServices: const [i0.ToolTip])
|
||||||
|
]
|
||||||
|
})
|
||||||
|
..registerType(i0.ToolTip, {
|
||||||
|
"factory": () => new i0.ToolTip(),
|
||||||
|
"parameters": const [const []],
|
||||||
|
"annotations": const [
|
||||||
|
const i1.Decorator(
|
||||||
|
selector: '[tool-tip]', bind: const {'text': 'tool-tip'})
|
||||||
|
]
|
||||||
|
})
|
||||||
|
..registerSetters({"text": (i0.ToolTip o, String value) => o.text = value});
|
||||||
|
}
|
|
@ -0,0 +1,10 @@
|
||||||
|
library web_foo;
|
||||||
|
|
||||||
|
import 'package:angular2/src/core/application.dart';
|
||||||
|
import 'package:angular2/src/reflection/reflection_capabilities.dart';
|
||||||
|
import 'bar.dart';
|
||||||
|
|
||||||
|
void main() {
|
||||||
|
reflector.reflectionCapabilities = new ReflectionCapabilities();
|
||||||
|
bootstrap(MyComponent);
|
||||||
|
}
|
|
@ -73,10 +73,7 @@ void _runTests() {
|
||||||
inputs: {
|
inputs: {
|
||||||
'a|web/index.dart': 'list_of_types_files/index.dart',
|
'a|web/index.dart': 'list_of_types_files/index.dart',
|
||||||
'a|web/foo.dart': 'list_of_types_files/foo.dart',
|
'a|web/foo.dart': 'list_of_types_files/foo.dart',
|
||||||
'a|web/bar.dart': 'list_of_types_files/bar.dart',
|
'a|web/bar.dart': 'list_of_types_files/bar.dart'
|
||||||
'angular2|lib/src/core/annotations/annotations.dart':
|
|
||||||
'../../lib/src/core/annotations/annotations.dart',
|
|
||||||
'angular2|lib/src/core/application.dart': 'common.dart'
|
|
||||||
},
|
},
|
||||||
outputs: {
|
outputs: {
|
||||||
'a|web/index.bootstrap.dart':
|
'a|web/index.bootstrap.dart':
|
||||||
|
@ -85,10 +82,7 @@ void _runTests() {
|
||||||
new TestConfig('Component with synthetic Constructor',
|
new TestConfig('Component with synthetic Constructor',
|
||||||
inputs: {
|
inputs: {
|
||||||
'a|web/index.dart': 'synthetic_ctor_files/index.dart',
|
'a|web/index.dart': 'synthetic_ctor_files/index.dart',
|
||||||
'a|web/bar.dart': 'synthetic_ctor_files/bar.dart',
|
'a|web/bar.dart': 'synthetic_ctor_files/bar.dart'
|
||||||
'angular2|lib/src/core/annotations/annotations.dart':
|
|
||||||
'../../lib/src/core/annotations/annotations.dart',
|
|
||||||
'angular2|lib/src/core/application.dart': 'common.dart'
|
|
||||||
},
|
},
|
||||||
outputs: {
|
outputs: {
|
||||||
'a|web/index.bootstrap.dart':
|
'a|web/index.bootstrap.dart':
|
||||||
|
@ -98,15 +92,21 @@ void _runTests() {
|
||||||
inputs: {
|
inputs: {
|
||||||
'a|web/index.dart': 'two_annotations_files/index.dart',
|
'a|web/index.dart': 'two_annotations_files/index.dart',
|
||||||
'a|web/bar.dart': 'two_annotations_files/bar.dart',
|
'a|web/bar.dart': 'two_annotations_files/bar.dart',
|
||||||
'angular2|lib/src/core/annotations/annotations.dart':
|
|
||||||
'../../lib/src/core/annotations/annotations.dart',
|
|
||||||
'angular2|lib/src/core/annotations/template.dart':
|
'angular2|lib/src/core/annotations/template.dart':
|
||||||
'../../lib/src/core/annotations/template.dart',
|
'../../lib/src/core/annotations/template.dart'
|
||||||
'angular2|lib/src/core/application.dart': 'common.dart'
|
|
||||||
},
|
},
|
||||||
outputs: {
|
outputs: {
|
||||||
'a|web/index.bootstrap.dart':
|
'a|web/index.bootstrap.dart':
|
||||||
'two_annotations_files/expected/index.bootstrap.dart'
|
'two_annotations_files/expected/index.bootstrap.dart'
|
||||||
|
}),
|
||||||
|
new TestConfig('Basic `bind`',
|
||||||
|
inputs: {
|
||||||
|
'a|web/index.dart': 'basic_bind_files/index.dart',
|
||||||
|
'a|web/bar.dart': 'basic_bind_files/bar.dart'
|
||||||
|
},
|
||||||
|
outputs: {
|
||||||
|
'a|web/index.bootstrap.dart':
|
||||||
|
'basic_bind_files/expected/index.bootstrap.dart'
|
||||||
})
|
})
|
||||||
];
|
];
|
||||||
|
|
||||||
|
@ -118,12 +118,12 @@ void _runTests() {
|
||||||
config.assetPathToInputPath
|
config.assetPathToInputPath
|
||||||
..addAll(commonInputs)
|
..addAll(commonInputs)
|
||||||
..forEach((key, value) {
|
..forEach((key, value) {
|
||||||
config.assetPathToInputPath[key] = cache.putIfAbsent(value,
|
config.assetPathToInputPath[
|
||||||
() => new File('test/transform/${value}').readAsStringSync());
|
key] = cache.putIfAbsent(value, () => _readFile(value));
|
||||||
});
|
});
|
||||||
config.assetPathToExpectedOutputPath.forEach((key, value) {
|
config.assetPathToExpectedOutputPath.forEach((key, value) {
|
||||||
config.assetPathToExpectedOutputPath[key] = cache.putIfAbsent(value, () {
|
config.assetPathToExpectedOutputPath[key] = cache.putIfAbsent(value, () {
|
||||||
var code = new File('test/transform/${value}').readAsStringSync();
|
var code = _readFile(value);
|
||||||
return value.endsWith('dart') ? formatter.format(code) : code;
|
return value.endsWith('dart') ? formatter.format(code) : code;
|
||||||
});
|
});
|
||||||
});
|
});
|
||||||
|
@ -132,3 +132,14 @@ void _runTests() {
|
||||||
], config.assetPathToInputPath, config.assetPathToExpectedOutputPath, []);
|
], config.assetPathToInputPath, config.assetPathToExpectedOutputPath, []);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/// Smoothes over differences in CWD between IDEs and running tests in Travis.
|
||||||
|
String _readFile(String path) {
|
||||||
|
for (var myPath in [path, 'test/transform/${path}']) {
|
||||||
|
var file = new File(myPath);
|
||||||
|
if (file.existsSync()) {
|
||||||
|
return file.readAsStringSync();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return null;
|
||||||
|
}
|
||||||
|
|
Loading…
Reference in New Issue