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:
Tim Blasi 2015-02-23 17:38:34 -08:00 committed by Misko Hevery
parent 1376e49ea0
commit 50a74b1d91
6 changed files with 276 additions and 117 deletions

View File

@ -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;
}
}

View File

@ -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));

View File

@ -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;
}

View File

@ -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});
}

View File

@ -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);
}

View File

@ -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;
}