perf(dart/transform) Restructure transform to independent phases

Update summary:
- Removes the need for resolution, gaining transform speed at the cost
  of some precision and ability to detect errors
- Generates type registrations in the package alongside their declarations
- Ensures that line numbers do not change in transformed user code
This commit is contained in:
Tim Blasi 2015-02-27 14:42:21 -08:00
parent 08bd3a4443
commit d0aceef4e0
52 changed files with 1530 additions and 318 deletions

View File

@ -0,0 +1,92 @@
library angular2.src.transform.bind_generator.generator;
import 'package:analyzer/analyzer.dart';
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) {
if (_noSettersPresent(code)) return code;
var writer = new PrintStringWriter();
parseCompilationUnit(
code, name: path).accept(new CreateNgSettersVisitor(writer));
return writer.toString();
}
bool _noSettersPresent(String code) => code.indexOf('bind') < 0;
class CreateNgSettersVisitor extends ToSourceVisitor with VisitorMixin {
final PrintWriter writer;
final _ExtractSettersVisitor extractVisitor = new _ExtractSettersVisitor();
CreateNgSettersVisitor(PrintWriter writer)
: this.writer = writer,
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
/// `registerType` call and generating setters from any "bind" values found.
class _ExtractSettersVisitor extends Object
with RecursiveAstVisitor<Object>, VisitorMixin {
final List<String> bindPieces = [];
Identifier currentName = null;
void _extractFromMapLiteral(MapLiteral map) {
if (currentName == null) {
logger.error('Unexpected code path: `currentName` should never be null');
}
map.entries.forEach((entry) {
// 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 {
logger.error('`bind` currently only supports string literals');
}
});
}
@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);
}
}

View File

@ -0,0 +1,42 @@
library angular2.src.transform.bind_generator.transformer;
import 'dart:async';
import 'package:angular2/src/transform/common/formatter.dart';
import 'package:angular2/src/transform/common/logging.dart' as log;
import 'package:angular2/src/transform/common/names.dart';
import 'package:angular2/src/transform/common/options.dart';
import 'package:barback/barback.dart';
import 'generator.dart';
/// Transformer responsible for reading .ngDeps.dart files and generating
/// setters from the "annotations" information in the generated
/// `registerType` calls.
///
/// These setters are registered in the same `setupReflection` function with
/// the `registerType` calls.
class BindGenerator extends Transformer {
final TransformerOptions options;
BindGenerator(this.options);
@override
bool isPrimary(AssetId id) => id.path.endsWith(DEPS_EXTENSION);
@override
Future apply(Transform transform) async {
log.init(transform);
try {
var assetCode = await transform.primaryInput.readAsString();
var assetPath = transform.primaryInput.id.path;
var transformedCode = createNgSetters(assetCode, assetPath);
transform.addOutput(new Asset.fromString(transform.primaryInput.id,
formatter.format(transformedCode, uri: assetPath)));
} catch (ex, stackTrace) {
log.logger.error('Creating ng setters failed.\n'
'Exception: $ex\n'
'Stack Trace: $stackTrace');
}
}
}

View File

@ -0,0 +1,22 @@
library angular2.src.transform.common.formatter;
import 'package:dart_style/dart_style.dart';
import 'logging.dart';
DartFormatter _formatter;
void init(DartFormatter formatter) {
if (_formatter != null) {
logger.warning('Formatter is being overwritten.');
}
_formatter = formatter;
}
DartFormatter get formatter {
if (_formatter == null) {
logger.info('Formatter never initialized, using default formatter.');
_formatter = new DartFormatter();
}
return _formatter;
}

View File

@ -1,4 +1,4 @@
library angular2.src.transform.logging;
library angular2.src.transform.common.logging;
import 'package:barback/barback.dart';
import 'package:code_transformers/messages/build_logger.dart';
@ -7,12 +7,7 @@ BuildLogger _logger;
/// Prepares [logger] for use throughout the transformer.
void init(Transform t) {
if (_logger == null) {
_logger = new BuildLogger(t);
} else {
_logger.fine('Attempted to initialize logger multiple times.',
asset: t.primaryInput.id);
}
_logger = new BuildLogger(t);
}
/// The logger the transformer should use for messaging.

View File

@ -0,0 +1,7 @@
library angular2.src.transform.common.names;
const SETUP_METHOD_NAME = 'setupReflection';
const REFLECTOR_VAR_NAME = 'reflector';
const DEPS_EXTENSION = '.ngDeps.dart';
const REGISTER_TYPE_METHOD_NAME = 'registerType';
const REGISTER_SETTERS_METHOD_NAME = 'registerSetters';

View File

@ -0,0 +1,35 @@
library angular2.src.transform.common.ng_data;
import 'dart:convert';
const NG_DATA_VERSION = 1;
class NgData extends Object {
int importOffset = 0;
int registerOffset = 0;
List<String> imports = [];
NgData();
factory NgData.fromJson(String json) {
var data = JSON.decode(json);
return new NgData()
..importOffset = data['importOffset']
..registerOffset = data['registerOffset']
..imports = data['imports'];
}
String toJson() {
return JSON.encode({
'version': NG_DATA_VERSION,
'importOffset': importOffset,
'registerOffset': registerOffset,
'imports': imports
});
}
@override
String toString() {
return '[NgData: ${toJson()}]';
}
}

View File

@ -0,0 +1,29 @@
library angular2.src.transform.common.options;
const ENTRY_POINT_PARAM = 'entry_point';
const REFLECTION_ENTRY_POINT_PARAM = 'reflection_entry_point';
/// Provides information necessary to transform an Angular2 app.
class TransformerOptions {
/// The path to the file where the application's call to [bootstrap] is.
// TODO(kegluneq): Allow multiple entry points.
final String entryPoint;
/// The reflection entry point, that is, the path to the file where the
/// application's [ReflectionCapabilities] are set.
final String reflectionEntryPoint;
TransformerOptions._internal(this.entryPoint, this.reflectionEntryPoint);
factory TransformerOptions(String entryPoint, {String reflectionEntryPoint}) {
if (entryPoint == null) {
throw new ArgumentError.notNull(ENTRY_POINT_PARAM);
} else if (entryPoint.isEmpty) {
throw new ArgumentError.value(entryPoint, 'entryPoint');
}
if (reflectionEntryPoint == null || entryPoint.isEmpty) {
reflectionEntryPoint = entryPoint;
}
return new TransformerOptions._internal(entryPoint, reflectionEntryPoint);
}
}

View File

@ -0,0 +1,111 @@
library angular2.src.transform.common;
import 'package:analyzer/analyzer.dart';
import 'package:analyzer/src/generated/java_core.dart';
import 'package:analyzer/src/generated/scanner.dart';
/// Visitor providing common methods for concrete implementations.
class VisitorMixin {
PrintWriter writer;
/**
* Visit the given function body, printing the prefix before if given body is not empty.
*
* @param prefix the prefix to be printed if there is a node to visit
* @param body the function body to be visited
*/
void visitFunctionWithPrefix(String prefix, FunctionBody body) {
if (body is! EmptyFunctionBody) {
writer.print(prefix);
}
visitNode(body);
}
/// Safely visit [node].
void visitNode(AstNode node) {
if (node != null) {
node.accept(this);
}
}
/// Print a list of [nodes] without any separation.
void visitNodeList(NodeList<AstNode> nodes) {
visitNodeListWithSeparator(nodes, "");
}
/// Print a list of [nodes], separated by the given [separator].
void visitNodeListWithSeparator(NodeList<AstNode> nodes, String separator) {
if (nodes != null) {
int size = nodes.length;
for (int i = 0; i < size; i++) {
if (i > 0) {
writer.print(separator);
}
nodes[i].accept(this);
}
}
}
/// Print a list of [nodes], separated by the given [separator] and
/// preceded by the given [prefix].
void visitNodeListWithSeparatorAndPrefix(
String prefix, NodeList<AstNode> nodes, String separator) {
if (nodes != null) {
int size = nodes.length;
if (size > 0) {
writer.print(prefix);
for (int i = 0; i < size; i++) {
if (i > 0) {
writer.print(separator);
}
nodes[i].accept(this);
}
}
}
}
/// Print a list of [nodes], separated by the given [separator] and
/// succeeded by the given [suffix].
void visitNodeListWithSeparatorAndSuffix(
NodeList<AstNode> nodes, String separator, String suffix) {
if (nodes != null) {
int size = nodes.length;
if (size > 0) {
for (int i = 0; i < size; i++) {
if (i > 0) {
writer.print(separator);
}
nodes[i].accept(this);
}
writer.print(suffix);
}
}
}
/// If [node] is null does nothing. Otherwise, prints [prefix], then
/// visits [node].
void visitNodeWithPrefix(String prefix, AstNode node) {
if (node != null) {
writer.print(prefix);
node.accept(this);
}
}
/// If [node] is null does nothing. Otherwise, visits [node], then prints
/// [suffix].
void visitNodeWithSuffix(AstNode node, String suffix) {
if (node != null) {
node.accept(this);
writer.print(suffix);
}
}
/// Safely visit [node], printing [suffix] after the node if it is
/// non-`null`.
void visitTokenWithSuffix(Token token, String suffix) {
if (token != null) {
writer.print(token.lexeme);
writer.print(suffix);
}
}
}

View File

@ -0,0 +1,56 @@
library angular2.src.transform.directive_linker.linker;
import 'dart:async';
import 'package:angular2/src/transform/common/logging.dart';
import 'package:angular2/src/transform/common/names.dart';
import 'package:angular2/src/transform/common/ngdata.dart';
import 'package:barback/barback.dart';
import 'package:code_transformers/assets.dart';
import 'package:dart_style/dart_style.dart';
import 'package:path/path.dart' as path;
Future<String> linkNgDeps(Transform transform, String code, String path) async {
var commentIdx = code.lastIndexOf('//');
if (commentIdx < 0) return code;
var ngData = new NgData.fromJson(code.substring(commentIdx + 2));
StringBuffer importBuf =
new StringBuffer(code.substring(0, ngData.importOffset));
StringBuffer declarationBuf = new StringBuffer(
code.substring(ngData.importOffset, ngData.registerOffset));
String tail = code.substring(ngData.registerOffset, commentIdx);
var ngDeps = await _processNgImports(transform, ngData.imports);
for (var i = 0; i < ngDeps.length; ++i) {
importBuf.write('import \'${ngDeps[i]}\' as i${i};');
declarationBuf.write('i${i}.${SETUP_METHOD_NAME}(${REFLECTOR_VAR_NAME});');
}
return '${importBuf}${declarationBuf}${tail}';
}
String _toDepsUri(String importUri) =>
'${path.withoutExtension(importUri)}${DEPS_EXTENSION}';
bool _isNotDartImport(String importUri) {
return !importUri.startsWith('dart:');
}
Future<List<String>> _processNgImports(
Transform transform, List<String> imports) async {
var retVal = <String>[];
return Future
.wait(imports.where(_isNotDartImport).map(_toDepsUri).map((ngDepsUri) {
var importAsset = uriToAssetId(
transform.primaryInput.id, ngDepsUri, logger, null /* span */);
return transform.hasInput(importAsset).then((hasInput) {
if (hasInput) {
retVal.add(ngDepsUri);
}
});
})).then((_) => retVal);
}

View File

@ -0,0 +1,43 @@
library angular2.src.transform.directive_linker.transformer;
import 'dart:async';
import 'package:angular2/src/transform/common/formatter.dart';
import 'package:angular2/src/transform/common/logging.dart' as log;
import 'package:angular2/src/transform/common/names.dart';
import 'package:angular2/src/transform/common/options.dart';
import 'package:barback/barback.dart';
import 'linker.dart';
/// Transformer responsible for processing .ngDeps.dart files created by
/// [DirectiveProcessor] and ensuring that the generated calls to
/// `setupReflection` call the necessary `setupReflection` method in all
/// dependencies.
class DirectiveLinker extends Transformer {
final TransformerOptions options;
DirectiveLinker(this.options);
@override
bool isPrimary(AssetId id) => id.path.endsWith(DEPS_EXTENSION);
@override
Future apply(Transform transform) async {
log.init(transform);
try {
var assetCode = await transform.primaryInput.readAsString();
var assetPath = transform.primaryInput.id.path;
var transformedCode = await linkNgDeps(transform, assetCode, assetPath);
var formattedCode = formatter.format(transformedCode, uri: assetPath);
transform.addOutput(
new Asset.fromString(transform.primaryInput.id, formattedCode));
} catch (ex, stackTrace) {
log.logger.error('Linking ng directives failed.\n'
'Exception: $ex\n'
'Stack Trace: $stackTrace');
}
return null;
}
}

View File

@ -0,0 +1,207 @@
library angular2.src.transform.directive_processor;
import 'package:analyzer/analyzer.dart';
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/ngdata.dart';
import 'package:angular2/src/transform/common/visitor_mixin.dart';
import 'package:path/path.dart' as path;
import 'visitors.dart';
/// Generates a file registering all Angular 2 `Directive`s found in [code] in
/// ngDeps format [TODO(kegluneq): documentation reference needed]. [path] is
/// the path to the file (or asset) containing [code].
///
/// If no Angular 2 `Directive`s are found in [code], returns the empty
/// string unless [forceGenerate] is true, in which case an empty ngDeps
/// file is created.
String createNgDeps(String code, String path, {bool forceGenerate: false}) {
// TODO(kegluneq): Shortcut if we can determine that there are no
// [Directive]s present.
var writer = new PrintStringWriter();
var visitor = new CreateNgDepsVisitor(writer, path);
parseCompilationUnit(code, name: path).accept(visitor);
if (visitor.foundNgDirectives || forceGenerate) {
return writer.toString();
} else {
return '';
}
}
/// Visitor responsible for processing [CompilationUnit] and creating an
/// associated .ngDeps.dart file.
class CreateNgDepsVisitor extends Object
with SimpleAstVisitor<Object>, VisitorMixin {
final PrintWriter writer;
final _Tester _tester = const _Tester();
bool foundNgDirectives = false;
bool wroteImport = false;
final ToSourceVisitor _copyVisitor;
final FactoryTransformVisitor _factoryVisitor;
final ParameterTransformVisitor _paramsVisitor;
final AnnotationsTransformVisitor _metaVisitor;
final NgData _ngData = new NgData();
/// The path to the file which we are parsing.
final String importPath;
CreateNgDepsVisitor(PrintWriter writer, this.importPath)
: writer = writer,
_copyVisitor = new ToSourceVisitor(writer),
_factoryVisitor = new FactoryTransformVisitor(writer),
_paramsVisitor = new ParameterTransformVisitor(writer),
_metaVisitor = new AnnotationsTransformVisitor(writer);
@override
void visitCompilationUnit(CompilationUnit node) {
visitNodeListWithSeparator(node.directives, " ");
_openFunctionWrapper();
visitNodeListWithSeparator(node.declarations, " ");
_closeFunctionWrapper();
return null;
}
void _writeImport() {
writer.print('import \'${path.basename(importPath)}\';');
}
@override
Object visitImportDirective(ImportDirective node) {
if (!wroteImport) {
_writeImport();
wroteImport = true;
}
_ngData.imports.add(node.uri.stringValue);
return node.accept(_copyVisitor);
}
@override
Object visitExportDirective(ExportDirective node) {
_ngData.imports.add(node.uri.stringValue);
return node.accept(_copyVisitor);
}
void _openFunctionWrapper() {
// TODO(kegluneq): Use a [PrintWriter] with a length getter.
_ngData.importOffset = writer.toString().length;
writer.print('bool _visited = false;'
'void ${SETUP_METHOD_NAME}(${REFLECTOR_VAR_NAME}) {'
'if (_visited) return; _visited = true;');
}
void _closeFunctionWrapper() {
if (foundNgDirectives) {
writer.print(';');
}
// TODO(kegluneq): Use a [PrintWriter] with a length getter.
_ngData.registerOffset = writer.toString().length;
writer.print('}');
writer.print('// ${_ngData.toJson()}');
}
ConstructorDeclaration _getCtor(ClassDeclaration node) {
int numCtorsFound = 0;
var ctor = null;
for (ClassMember classMember in node.members) {
if (classMember is ConstructorDeclaration) {
numCtorsFound++;
ConstructorDeclaration constructor = classMember;
// Use the unnnamed constructor if it is present.
// Otherwise, use the first encountered.
if (ctor == null) {
ctor = constructor;
} else if (constructor.name == null) {
ctor = constructor;
}
}
}
if (numCtorsFound > 1) {
var ctorName = ctor.name;
ctorName = ctorName == null
? 'the unnamed constructor'
: 'constructor "${ctorName}"';
logger.warning('Found ${numCtorsFound} ctors for class ${node.name},'
'Using ${ctorName}.');
}
return ctor;
}
void _generateEmptyFactory(String typeName) {
writer.print('() => new ${typeName}()');
}
void _generateEmptyParams() => writer.print('const []');
@override
Object visitClassDeclaration(ClassDeclaration node) {
var shouldProcess = node.metadata.any(_tester._isDirective);
if (shouldProcess) {
var ctor = _getCtor(node);
if (!foundNgDirectives) {
// The receiver for cascaded calls.
writer.print(REFLECTOR_VAR_NAME);
foundNgDirectives = true;
}
writer.print('..registerType(');
visitNode(node.name);
writer.print(', {"factory": ');
if (ctor == null) {
_generateEmptyFactory(node.name.toString());
} else {
ctor.accept(_factoryVisitor);
}
writer.print(', "parameters": ');
if (ctor == null) {
_generateEmptyParams();
} else {
ctor.accept(_paramsVisitor);
}
writer.print(', "annotations": ');
node.accept(_metaVisitor);
writer.print('})');
return null;
}
}
Object _nodeToSource(AstNode node) {
if (node == null) return null;
return node.accept(_copyVisitor);
}
@override
Object visitLibraryDirective(LibraryDirective node) => _nodeToSource(node);
@override
Object visitPartOfDirective(PartOfDirective node) {
// TODO(kegluneq): Consider importing [node.libraryName].
logger.warning('[${importPath}]: '
'Found `part of` directive while generating ${DEPS_EXTENSION} file, '
'Transform may fail due to missing imports in generated file.');
return null;
}
@override
Object visitPrefixedIdentifier(PrefixedIdentifier node) =>
_nodeToSource(node);
@override
Object visitSimpleIdentifier(SimpleIdentifier node) => _nodeToSource(node);
}
class _Tester {
const _Tester();
bool _isDirective(Annotation meta) {
var metaName = meta.name.toString();
return metaName == 'Component' ||
metaName == 'Decorator' ||
metaName == 'Template';
}
}

View File

@ -0,0 +1,54 @@
library angular2.src.transform.directive_processor.transformer;
import 'dart:async';
import 'package:angular2/src/transform/common/formatter.dart';
import 'package:angular2/src/transform/common/logging.dart' as log;
import 'package:angular2/src/transform/common/names.dart';
import 'package:angular2/src/transform/common/options.dart';
import 'package:barback/barback.dart';
import 'rewriter.dart';
/// Transformer responsible for processing all .dart assets and creating
/// .ngDeps.dart files which register @Injectable annotated classes with the
/// reflector.
///
/// This will also create .ngDeps.dart files for classes annotated
/// with @Component, @Template, @Decorator, etc.
///
/// This transformer is the first phase in a two-phase transform. It should
/// be followed by [DirectiveLinker].
class DirectiveProcessor extends Transformer {
final TransformerOptions options;
DirectiveProcessor(this.options);
@override
bool isPrimary(AssetId id) => id.extension.endsWith('dart');
@override
Future apply(Transform transform) async {
log.init(transform);
try {
var assetCode = await transform.primaryInput.readAsString();
var ngDepsSrc = createNgDeps(assetCode, transform.primaryInput.id.path,
forceGenerate: transform.primaryInput.id.path == options.entryPoint);
if (ngDepsSrc != null && ngDepsSrc.isNotEmpty) {
var ngDepsAssetId =
transform.primaryInput.id.changeExtension(DEPS_EXTENSION);
var exists = await transform.hasInput(ngDepsAssetId);
if (exists) {
log.logger.error('Clobbering ${ngDepsAssetId}. '
'This probably will not end well');
}
transform.addOutput(new Asset.fromString(ngDepsAssetId, ngDepsSrc));
}
} catch (ex, stackTrace) {
log.logger.warning('Processing ng directives failed.\n'
'Exception: $ex\n'
'Stack Trace: $stackTrace');
}
}
}

View File

@ -0,0 +1,181 @@
library angular2.src.transform.directive_processor;
import 'package:analyzer/analyzer.dart';
import 'package:analyzer/src/generated/java_core.dart';
import 'package:angular2/src/transform/common/logging.dart';
import 'package:angular2/src/transform/common/visitor_mixin.dart';
/// SourceVisitor designed to accept [ConstructorDeclaration] nodes.
class _CtorTransformVisitor extends ToSourceVisitor with VisitorMixin {
bool _withParameterTypes = true;
bool _withParameterNames = true;
final PrintWriter writer;
/// Maps field names to their declared types. This is populated whenever
/// the listener visits a [ConstructorDeclaration] node.
final Map<String, TypeName> _fieldNameToType = {};
_CtorTransformVisitor(PrintWriter writer)
: this.writer = writer,
super(writer);
/// If [_withParameterTypes] is true, this method outputs [node]'s type. If
/// [_withParameterNames] is true, this method outputs [node]'s identifier.
Object _visitNormalFormalParameter(TypeName type, SimpleIdentifier name) {
if (_withParameterTypes) {
visitNodeWithSuffix(type, ' ');
}
if (_withParameterNames) {
visitNode(name);
}
return null;
}
void _buildFieldMap(ConstructorDeclaration node) {
ClassDeclaration clazz =
node.getAncestor((node) => node is ClassDeclaration);
_fieldNameToType.clear();
clazz.members.where((member) => member is FieldDeclaration).forEach(
(FieldDeclaration field) {
var type = field.fields.type;
if (type != null) {
field.fields.variables.forEach((VariableDeclaration decl) {
_fieldNameToType[decl.name.toString()] = type;
});
}
});
}
@override
Object visitSimpleFormalParameter(SimpleFormalParameter node) {
return _visitNormalFormalParameter(node.type, node.identifier);
}
@override
Object visitFieldFormalParameter(FieldFormalParameter node) {
if (node.parameters != null) {
logger.error('Parameters in ctor not supported '
'(${node.toSource()})');
}
var type = node.type;
if (type == null) {
type = _fieldNameToType[node.identifier.toString()];
}
return _visitNormalFormalParameter(type, node.identifier);
}
@override
Object visitFunctionTypedFormalParameter(FunctionTypedFormalParameter node) {
logger.error('Function typed formal parameters not supported '
'(${node.toSource()})');
return _visitNormalFormalParameter(null, node.identifier);
}
@override
Object visitDefaultFormalParameter(DefaultFormalParameter node) {
visitNode(node.parameter);
// Ignore the declared default value.
return null;
}
@override
/// Overridden to avoid outputting grouping operators for default parameters.
Object visitFormalParameterList(FormalParameterList node) {
writer.print('(');
NodeList<FormalParameter> parameters = node.parameters;
int size = parameters.length;
for (int i = 0; i < size; i++) {
if (i > 0) {
writer.print(', ');
}
parameters[i].accept(this);
}
writer.print(')');
return null;
}
}
/// ToSourceVisitor designed to print 'parameters' values for Angular2's
/// [registerType] calls.
class ParameterTransformVisitor extends _CtorTransformVisitor {
ParameterTransformVisitor(PrintWriter writer) : super(writer);
@override
Object visitConstructorDeclaration(ConstructorDeclaration node) {
_buildFieldMap(node);
_withParameterNames = false;
_withParameterTypes = true;
writer.print('const [');
visitNode(node.parameters);
writer.print(']');
return null;
}
@override
Object visitFormalParameterList(FormalParameterList node) {
NodeList<FormalParameter> parameters = node.parameters;
int size = parameters.length;
for (int i = 0; i < size; i++) {
if (i > 0) {
writer.print(', ');
}
// TODO(kegluneq): Include annotations on parameters.
writer.print('const [');
parameters[i].accept(this);
writer.print(']');
}
return null;
}
}
/// ToSourceVisitor designed to print 'factory' values for Angular2's
/// [registerType] calls.
class FactoryTransformVisitor extends _CtorTransformVisitor {
FactoryTransformVisitor(PrintWriter writer) : super(writer);
@override
Object visitConstructorDeclaration(ConstructorDeclaration node) {
_buildFieldMap(node);
_withParameterNames = true;
_withParameterTypes = true;
visitNode(node.parameters);
writer.print(' => new ');
visitNode(node.returnType);
visitNodeWithPrefix(".", node.name);
_withParameterTypes = false;
visitNode(node.parameters);
return null;
}
}
/// ToSourceVisitor designed to print a [ClassDeclaration] node as a
/// 'annotations' value for Angular2's [registerType] calls.
class AnnotationsTransformVisitor extends ToSourceVisitor with VisitorMixin {
final PrintWriter writer;
AnnotationsTransformVisitor(PrintWriter writer)
: this.writer = writer,
super(writer);
@override
Object visitClassDeclaration(ClassDeclaration node) {
writer.print('const [');
var size = node.metadata.length;
for (var i = 0; i < size; ++i) {
if (i > 0) {
writer.print(', ');
}
node.metadata[i].accept(this);
}
writer.print(']');
return null;
}
@override
Object visitAnnotation(Annotation node) {
writer.print('const ');
visitNode(node.name);
visitNodeWithPrefix(".", node.constructorName);
visitNode(node.arguments);
return null;
}
}

View File

@ -8,7 +8,7 @@ import 'package:dart_style/dart_style.dart';
import 'package:path/path.dart' as path;
import 'annotation_processor.dart';
import 'logging.dart';
import 'common/logging.dart';
/// Base class that maintains codegen state.
class Context {
@ -72,7 +72,7 @@ String codegenEntryPoint(Context context, {AssetId newEntryPoint}) {
if (newEntryPoint == null) {
throw new ArgumentError.notNull('newEntryPoint');
}
// TODO(jakemac): copyright and library declaration
// TODO(kegluneq): copyright declaration
var outBuffer = new StringBuffer()
..write(_libraryDeclaration)
..write(_reflectorImport);
@ -195,11 +195,14 @@ class _DirectiveRegistryImpl implements DirectiveRegistry {
_writer.print('..registerType(');
_codegenClassTypeString(element);
_writer.print(', {"factory": ');
_codegenFactoryProp(ctorNode, element);
_codegenClassTypeString(element);
_writer.print('.ngFactory');
_writer.print(', "parameters": ');
_codegenParametersProp(ctorNode);
_codegenClassTypeString(element);
_writer.print('.ngParameters');
_writer.print(', "annotations": ');
_codegenAnnotationsProp(entry.node);
_codegenClassTypeString(element);
_writer.print('.ngAnnotations');
_writer.print('})');
}

View File

@ -9,7 +9,7 @@ import 'package:dart_style/dart_style.dart';
import 'package:path/path.dart' as path;
import 'codegen.dart';
import 'logging.dart';
import 'common/logging.dart';
import 'resolvers.dart';
/// Finds all calls to the Angular2 [ReflectionCapabilities] constructor

View File

@ -4,7 +4,7 @@ import 'package:analyzer/src/generated/ast.dart';
import 'package:analyzer/src/generated/element.dart';
import 'annotation_processor.dart';
import 'logging.dart';
import 'common/logging.dart';
import 'resolvers.dart';
/// Walks through an Angular2 application, finding all classes matching the

View File

@ -1,41 +0,0 @@
library angular2.src.transform;
const entryPointParam = 'entry_point';
const reflectionEntryPointParam = 'reflection_entry_point';
const newEntryPointParam = 'new_entry_point';
/// Provides information necessary to transform an Angular2 app.
class TransformerOptions {
/// The file where the application's call to [bootstrap] is.
// TODO(kegluneq): Allow multiple entry points.
final String entryPoint;
/// The reflection entry point, that is, where the
/// application's [ReflectionCapabilities] are set.
final String reflectionEntryPoint;
/// The path where we should generate code.
final String newEntryPoint;
TransformerOptions._internal(
this.entryPoint, this.reflectionEntryPoint, this.newEntryPoint);
factory TransformerOptions(String entryPoint,
{String reflectionEntryPoint, String newEntryPoint}) {
if (entryPoint == null || entryPoint.isEmpty) {
throw new ArgumentError.notNull(entryPointParam);
}
if (reflectionEntryPoint == null || entryPoint.isEmpty) {
reflectionEntryPoint = entryPoint;
}
if (newEntryPoint == null || newEntryPoint.isEmpty) {
newEntryPoint =
reflectionEntryPoint.replaceFirst('.dart', '.bootstrap.dart');
if (newEntryPoint == reflectionEntryPoint) {
newEntryPoint = 'bootstrap.${newEntryPoint}';
}
}
return new TransformerOptions._internal(
entryPoint, reflectionEntryPoint, newEntryPoint);
}
}

View File

@ -0,0 +1,36 @@
library angular2.src.transform.reflection_remover.ast_tester;
import 'package:analyzer/src/generated/ast.dart';
import 'package:analyzer/src/generated/element.dart';
/// An object that checks for [ReflectionCapabilities] syntactically, that is,
/// without resolution information.
class AstTester {
static const REFLECTION_CAPABILITIES_NAME = 'ReflectionCapabilities';
const AstTester();
bool isNewReflectionCapabilities(InstanceCreationExpression node) =>
'${node.constructorName.type.name}' == REFLECTION_CAPABILITIES_NAME;
bool isReflectionCapabilitiesImport(ImportDirective node) {
return node.uri.stringValue.endsWith("reflection_capabilities.dart");
}
}
/// An object that checks for [ReflectionCapabilities] using a fully resolved
/// Ast.
class ResolvedTester implements AstTester {
final ClassElement _forbiddenClass;
ResolvedTester(this._forbiddenClass);
bool isNewReflectionCapabilities(InstanceCreationExpression node) {
var typeElement = node.constructorName.type.name.bestElement;
return typeElement != null && typeElement == _forbiddenClass;
}
bool isReflectionCapabilitiesImport(ImportDirective node) {
return node.uriElement == _forbiddenClass.library;
}
}

View File

@ -0,0 +1,89 @@
library angular2.src.transform.reflection_remover.codegen;
import 'package:analyzer/src/generated/ast.dart';
import 'package:barback/barback.dart';
import 'package:code_transformers/resolver.dart';
import 'package:path/path.dart' as path;
import 'package:angular2/src/transform/common/names.dart';
class Codegen {
static const _PREFIX_BASE = 'ngStaticInit';
/// The prefix used to import our generated file.
final String prefix;
/// The import uri
final String importUri;
Codegen(String reflectionEntryPointPath, String newEntryPointPath,
{String prefix})
: this.prefix = prefix == null ? _PREFIX_BASE : prefix,
importUri = path.relative(newEntryPointPath,
from: path.dirname(reflectionEntryPointPath)) {
if (this.prefix.isEmpty) throw new ArgumentError.value('(empty)', 'prefix');
}
factory Codegen.fromResolver(
Resolver resolver, AssetId reflectionEntryPoint, AssetId newEntryPoint) {
var lib = resolver.getLibrary(reflectionEntryPoint);
var prefix = _PREFIX_BASE;
var idx = 0;
while (lib.imports.any((import) {
return import.prefix != null && import.prefix == prefix;
})) {
prefix = '${_PREFIX_BASE}${idx++}';
}
return new Codegen(reflectionEntryPoint, newEntryPoint, prefix: prefix);
}
/// Generates code to import the library containing the method which sets up
/// Angular2 reflection statically.
///
/// The code generated here should follow the example of code generated for
/// an [ImportDirective] node.
String codegenImport() {
return 'import \'${importUri}\' as ${prefix};';
}
/// Generates code to call the method which sets up Angular2 reflection
/// statically.
///
/// If [reflectorAssignment] is provided, it is expected to be the node
/// representing the [ReflectionCapabilities] assignment, and we will
/// attempt to parse the access of [reflector] from it so that [reflector] is
/// properly prefixed if necessary.
String codegenSetupReflectionCall(
{AssignmentExpression reflectorAssignment}) {
var reflectorExpression = null;
if (reflectorAssignment != null) {
reflectorExpression = reflectorAssignment.accept(new _ReflectorVisitor());
}
if (reflectorExpression == null) {
reflectorExpression = 'reflector';
}
return '${prefix}.${SETUP_METHOD_NAME}(${reflectorExpression});';
}
}
/// A visitor whose job it is to find the access of [reflector].
class _ReflectorVisitor extends Object with SimpleAstVisitor<Expression> {
@override
Expression visitAssignmentExpression(AssignmentExpression node) {
if (node == null || node.leftHandSide == null) return null;
return node.leftHandSide.accept(this);
}
@override
Expression visitPropertyAccess(PropertyAccess node) {
if (node == null || node.target == null) return;
return node.target;
}
@override
Expression visitPrefixedIdentifier(PrefixedIdentifier node) {
if (node == null || node.prefix == null) return null;
return node.prefix;
}
}

View File

@ -0,0 +1,21 @@
library angular2.src.transform.reflection_remover.remove_reflection_capabilities;
import 'package:analyzer/analyzer.dart';
import 'codegen.dart';
import 'rewriter.dart';
/// Finds the call to the Angular2 [ReflectionCapabilities] constructor
/// in [code] and replaces it with a call to `setupReflection` in
/// [newEntryPoint].
///
/// [reflectionEntryPointPath] is the path where [code] is defined and is
/// used to display parsing errors.
///
/// This only searches [code] not `part`s, `import`s, `export`s, etc.
String removeReflectionCapabilities(
String code, String reflectionEntryPointPath, String newEntryPointPath) {
var codegen = new Codegen(reflectionEntryPointPath, newEntryPointPath);
return new Rewriter(code, codegen)
.rewrite(parseCompilationUnit(code, name: reflectionEntryPointPath));
}

View File

@ -0,0 +1,127 @@
library angular2.src.transform.reflection_remover.rewriter;
import 'package:analyzer/src/generated/ast.dart';
import 'package:angular2/src/transform/common/logging.dart';
import 'ast_tester.dart';
import 'codegen.dart';
class Rewriter {
final String _code;
final Codegen _codegen;
final AstTester _tester;
Rewriter(this._code, this._codegen, {AstTester tester})
: _tester = tester == null ? const AstTester() : tester;
/// Rewrites the provided code removing imports of the
/// [ReflectionCapabilities] library and instantiations of
/// [ReflectionCapabilities], as detected by the (potentially) provided
/// [AstTester].
///
/// To the extent possible, this method does not change line numbers or
/// offsets in the provided code to facilitate debugging via source maps.
String rewrite(CompilationUnit node) {
if (node == null) throw new ArgumentError.notNull('node');
var visitor = new _FindReflectionCapabilitiesVisitor(_tester);
node.accept(visitor);
if (visitor.reflectionCapabilityImports.isEmpty) {
logger.error(
'Failed to find ${AstTester.REFLECTION_CAPABILITIES_NAME} import.');
return _code;
}
if (visitor.reflectionCapabilityAssignments.isEmpty) {
logger.error('Failed to find ${AstTester.REFLECTION_CAPABILITIES_NAME} '
'instantiation.');
return _code;
}
var compare = (AstNode a, AstNode b) => b.offset - a.offset;
visitor.reflectionCapabilityImports.sort(compare);
visitor.reflectionCapabilityAssignments.sort(compare);
var importAdded = false;
var buf = new StringBuffer();
var idx = visitor.reflectionCapabilityImports.fold(0,
(int lastIdx, ImportDirective node) {
buf.write(_code.substring(lastIdx, node.offset));
if ('${node.prefix}' == _codegen.prefix) {
logger.warning(
'Found import prefix "${_codegen.prefix}" in source file.'
' Transform may not succeed.');
}
buf.write(_commentedNode(node));
if (!importAdded) {
buf.write(_codegen.codegenImport());
importAdded = true;
}
return node.end;
});
var setupAdded = false;
idx = visitor.reflectionCapabilityAssignments.fold(idx,
(int lastIdx, AssignmentExpression assignNode) {
var node = assignNode;
while (node.parent is ExpressionStatement) {
node = node.parent;
}
buf.write(_code.substring(lastIdx, node.offset));
buf.write(_commentedNode(node));
if (!setupAdded) {
buf.write(_codegen.codegenSetupReflectionCall(
reflectorAssignment: assignNode));
setupAdded = true;
}
return node.end;
});
if (idx < _code.length) buf.write(_code.substring(idx));
return buf.toString();
}
String _commentedNode(AstNode node) {
// TODO(kegluneq): Return commented code once we generate all needed code.
return _code.substring(node.offset, node.end);
}
}
/// Visitor responsible for rewriting the Angular 2 code which instantiates
/// [ReflectionCapabilities] and removing its associated import.
///
/// This breaks our dependency on dart:mirrors, which enables smaller code
/// size and better performance.
class _FindReflectionCapabilitiesVisitor extends Object
with RecursiveAstVisitor<Object> {
final reflectionCapabilityImports = new List<ImportDirective>();
final reflectionCapabilityAssignments = new List<AssignmentExpression>();
final AstTester _tester;
_FindReflectionCapabilitiesVisitor(this._tester);
@override
Object visitImportDirective(ImportDirective node) {
if (_tester.isReflectionCapabilitiesImport(node)) {
reflectionCapabilityImports.add(node);
}
return null;
}
@override
Object visitAssignmentExpression(AssignmentExpression node) {
if (node.rightHandSide is InstanceCreationExpression &&
_tester.isNewReflectionCapabilities(node.rightHandSide)) {
reflectionCapabilityAssignments.add(node);
}
return super.visitAssignmentExpression(node);
}
@override
Object visitInstanceCreationExpression(InstanceCreationExpression node) {
if (_tester.isNewReflectionCapabilities(node) &&
!reflectionCapabilityAssignments.contains(node.parent)) {
logger.error('Unexpected format in creation of '
'${reflectionCapabilitiesTypeName}');
}
return super.visitInstanceCreationExpression(node);
}
}

View File

@ -0,0 +1,47 @@
library angular2.src.transform.reflection_remover.transformer;
import 'dart:async';
import 'package:angular2/src/transform/common/logging.dart' as log;
import 'package:angular2/src/transform/common/names.dart';
import 'package:angular2/src/transform/common/options.dart';
import 'package:barback/barback.dart';
import 'remove_reflection_capabilities.dart';
/// Transformer responsible for removing the import and instantiation of
/// [ReflectionCapabilities].
///
/// The goal of this is to break the app's dependency on dart:mirrors.
///
/// This transformer assumes that [DirectiveProcessor] and [DirectiveLinker]
/// have already been run and that a .ngDeps.dart file has been generated for
/// [options.entryPoint]. The instantiation of [ReflectionCapabilities] is
/// replaced by calling `setupReflection` in that .ngDeps.dart file.
class ReflectionRemover extends Transformer {
final TransformerOptions options;
ReflectionRemover(this.options);
@override
bool isPrimary(AssetId id) => options.reflectionEntryPoint == id.path;
@override
Future apply(Transform transform) async {
log.init(transform);
try {
var newEntryPoint = new AssetId(
transform.primaryInput.id.package, options.entryPoint)
.changeExtension(DEPS_EXTENSION);
var assetCode = await transform.primaryInput.readAsString();
transform.addOutput(new Asset.fromString(transform.primaryInput.id,
removeReflectionCapabilities(
assetCode, transform.primaryInput.id.path, newEntryPoint.path)));
} catch (ex, stackTrace) {
log.logger.error('Removing reflection failed.\n'
'Exception: $ex\n'
'Stack Trace: $stackTrace');
}
}
}

View File

@ -1,134 +1,35 @@
library angular2.src.transform;
import 'dart:async';
import 'package:analyzer/src/generated/ast.dart';
import 'package:analyzer/src/generated/element.dart';
import 'package:barback/barback.dart';
import 'package:code_transformers/resolver.dart';
import 'package:dart_style/dart_style.dart';
import 'annotation_processor.dart';
import 'codegen.dart' as codegen;
import 'find_bootstrap.dart';
import 'find_reflection_capabilities.dart';
import 'logging.dart' as log;
import 'options.dart';
import 'resolvers.dart';
import 'traversal.dart';
import 'directive_linker/transformer.dart';
import 'directive_processor/transformer.dart';
import 'bind_generator/transformer.dart';
import 'reflection_remover/transformer.dart';
import 'common/formatter.dart' as formatter;
import 'common/options.dart';
export 'options.dart';
export 'common/options.dart';
/// Removes the mirror-based initialization logic and replaces it with static
/// logic.
class AngularTransformer extends Transformer {
final Resolvers _resolvers;
final TransformerOptions options;
AngularTransformer(this.options) : _resolvers = createResolvers();
factory AngularTransformer.asPlugin(BarbackSettings settings) {
var config = settings.configuration;
return new AngularTransformer(new TransformerOptions(
config[entryPointParam],
reflectionEntryPoint: config[reflectionEntryPointParam],
newEntryPoint: config[newEntryPointParam]));
class AngularTransformerGroup extends TransformerGroup {
AngularTransformerGroup(TransformerOptions options) : super([
[new DirectiveProcessor(options)],
[new DirectiveLinker(options)],
[new BindGenerator(options), new ReflectionRemover(options)]
]) {
formatter.init(new DartFormatter());
}
bool isPrimary(AssetId id) => options.reflectionEntryPoint == id.path;
Future apply(Transform transform) {
log.init(transform);
var entryPointId =
new AssetId(transform.primaryInput.id.package, options.entryPoint);
var reflectionEntryPointId = new AssetId(
transform.primaryInput.id.package, options.reflectionEntryPoint);
var newEntryPointId =
new AssetId(transform.primaryInput.id.package, options.newEntryPoint);
var reflectionExists = transform.hasInput(reflectionEntryPointId);
var newEntryPointExists = transform.hasInput(newEntryPointId);
Resolver myResolver;
return Future
.wait([reflectionExists, newEntryPointExists])
.then((existsList) {
if (!existsList[0]) {
log.logger.error('Reflection entry point file '
'${reflectionEntryPointId} does not exist.');
} else if (existsList[1]) {
log.logger
.error('New entry point file $newEntryPointId already exists.');
} else {
return _resolvers
.get(transform, [entryPointId, reflectionEntryPointId])
.then((resolver) {
myResolver = resolver;
try {
String reflectionCapabilitiesCreation = findReflectionCapabilities(
resolver, reflectionEntryPointId, newEntryPointId);
transform.addOutput(new Asset.fromString(
reflectionEntryPointId, reflectionCapabilitiesCreation));
// Find the call to `new ReflectionCapabilities()`
// Generate new source.
} catch (err, stackTrace) {
log.logger.error('${err}: ${stackTrace}',
asset: reflectionEntryPointId);
rethrow;
}
try {
new _BootstrapFileBuilder(
resolver, transform, entryPointId, newEntryPointId).run();
} catch (err, stackTrace) {
log.logger.error('${err}: ${stackTrace}',
asset: transform.primaryInput.id);
rethrow;
}
});
}
}).whenComplete(() {
if (myResolver != null) {
myResolver.release();
}
});
factory AngularTransformerGroup.asPlugin(BarbackSettings settings) {
return new AngularTransformerGroup(_parseOptions(settings));
}
}
class _BootstrapFileBuilder {
final Resolver _resolver;
final Transform _transform;
final AssetId _entryPoint;
final AssetId _newEntryPoint;
_BootstrapFileBuilder(Resolver resolver, Transform transform,
this._entryPoint, this._newEntryPoint)
: _resolver = resolver,
_transform = transform;
/// Adds the new entry point file to the transform. Should only be ran once.
void run() {
Set<BootstrapCallInfo> bootstrapCalls =
findBootstrapCalls(_resolver, _resolver.getLibrary(_entryPoint));
log.logger.info('found ${bootstrapCalls.length} call(s) to `bootstrap`');
bootstrapCalls.forEach((BootstrapCallInfo info) {
log.logger.info('Arg1: ${info.bootstrapType}');
});
var types = new Angular2Types(_resolver);
// TODO(kegluneq): Also match [Inject].
var matcher = new AnnotationMatcher(
new Set.from([types.directiveAnnotation, types.templateAnnotation]));
var traversal = new AngularVisibleTraversal(types, matcher);
bootstrapCalls.forEach((call) => traversal.traverse(call.bootstrapType));
var context = new codegen.Context();
matcher.matchQueue
.forEach((entry) => context.directiveRegistry.register(entry));
_transform.addOutput(new Asset.fromString(_newEntryPoint,
codegen.codegenEntryPoint(context, newEntryPoint: _newEntryPoint)));
}
TransformerOptions _parseOptions(BarbackSettings settings) {
var config = settings.configuration;
return new TransformerOptions(config[ENTRY_POINT_PARAM],
reflectionEntryPoint: config[REFLECTION_ENTRY_POINT_PARAM]);
}

View File

@ -0,0 +1,27 @@
library bar;
import 'bar.dart';
import 'package:angular2/src/core/annotations/annotations.dart';
bool _visited = false;
void setupReflection(reflector) {
if (_visited) return;
_visited = true;
reflector
..registerType(MyComponent, {
"factory": () => new MyComponent(),
"parameters": const [],
"annotations": const [
const Component(selector: 'soup', componentServices: const [ToolTip])
]
})
..registerType(ToolTip, {
"factory": () => new ToolTip(),
"parameters": const [],
"annotations": const [
const Decorator(
selector: '[tool-tip]', bind: const {'text': 'tool-tip'})
]
})
..registerSetters({"text": (ToolTip o, String value) => o.text = value});
}

View File

@ -1,26 +0,0 @@
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 bar;
import 'package:angular2/src/core/annotations/annotations.dart';
import 'foo.dart' as dep;
@Component(
selector: '[soup]', componentServices: const [dep.DependencyComponent])
class MyComponent {
MyComponent();
}

View File

@ -0,0 +1,23 @@
library bar;
import 'bar.dart';
import 'package:angular2/src/core/annotations/annotations.dart';
import 'foo.dart' as dep;
import 'foo.ngDeps.dart' as i0;
bool _visited = false;
void setupReflection(reflector) {
if (_visited) return;
_visited = true;
reflector
..registerType(MyComponent, {
"factory": () => new MyComponent(),
"parameters": const [],
"annotations": const [
const Component(
selector: '[soup]',
componentServices: const [dep.DependencyComponent])
]
});
i0.setupReflection(reflector);
}

View File

@ -0,0 +1,16 @@
library foo;
import 'foo.dart';
import 'package:angular2/src/core/annotations/annotations.dart';
bool _visited = false;
void setupReflection(reflector) {
if (_visited) return;
_visited = true;
reflector
..registerType(DependencyComponent, {
"factory": () => new DependencyComponent(),
"parameters": const [],
"annotations": const [const Component(selector: '[salad]')]
});
}

View File

@ -0,0 +1,13 @@
library web_foo;
import 'package:angular2/src/core/application.dart';
import 'package:angular2/src/reflection/reflection_capabilities.dart';
import 'bar.dart';
import 'a:web/bar.ngDeps.dart' as i0;
bool _visited = false;
void setupReflection(reflector) {
if (_visited) return;
_visited = true;
i0.setupReflection(reflector);
}

View File

@ -0,0 +1,8 @@
library foo;
import 'package:angular2/src/core/annotations/annotations.dart';
@Component(selector: '[salad]')
class DependencyComponent {
DependencyComponent();
}

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

@ -0,0 +1,19 @@
library bar;
import 'bar.dart';
import 'package:angular2/src/core/annotations/annotations.dart';
import 'foo.dart';
bool _visited = false;
void setupReflection(reflector) {
if (_visited) return;
_visited = true;
reflector
..registerType(MyComponent, {
"factory": (MyContext c) => new MyComponent(c),
"parameters": const [const [MyContext]],
"annotations": const [
const Component(componentServices: const [MyContext])
]
});
}

View File

@ -1,17 +0,0 @@
library angular2.src.transform.generated;
import 'package:angular2/src/reflection/reflection.dart' show reflector;
import 'bar.dart' as i0;
import 'foo.dart' as i1;
import 'package:angular2/src/core/annotations/annotations.dart' as i2;
setupReflection() {
reflector
..registerType(i0.MyComponent, {
"factory": (i1.MyContext c) => new i0.MyComponent(c),
"parameters": const [const [i1.MyContext]],
"annotations": const [
const i2.Component(componentServices: const [i1.MyContext])
]
});
}

View File

@ -0,0 +1,7 @@
Tests that the reflection removal step:
1. Comments out the import of reflection_capabilities.dart
2. Comments out the instantiation of `ReflectionCapabilities`
3. Adds the appropriate import.
4. Adds the call to `setupReflection`
5. Does not change line numbers in the source.
6. Makes minimal changes to source offsets.

View File

@ -0,0 +1,22 @@
library angular2.test.transform.reflection_remover_files;
// This file is intentionally formatted as a string to avoid having the
// automatic transformer prettify it.
//
// This file represents transformed user code. Because this code will be
// linked to output by a source map, we cannot change line numbers from the
// original code and we therefore add our generated code on the same line as
// those we are removing.
var code = """
library web_foo;
import 'package:angular2/src/core/application.dart';
import 'package:angular2/src/reflection/reflection.dart';
import 'package:angular2/src/reflection/reflection_capabilities.dart';import 'index.ngDeps.dart' as ngStaticInit;
void main() {
reflector.reflectionCapabilities = new ReflectionCapabilities();ngStaticInit.setupReflection(reflector);
bootstrap(MyComponent);
}
""";

View File

@ -0,0 +1,10 @@
library web_foo;
import 'package:angular2/src/core/application.dart';
import 'package:angular2/src/reflection/reflection.dart';
import 'package:angular2/src/reflection/reflection_capabilities.dart';
void main() {
reflector.reflectionCapabilities = new ReflectionCapabilities();
bootstrap(MyComponent);
}

View File

@ -0,0 +1,16 @@
library bar;
import 'bar.dart';
import 'package:angular2/src/core/annotations/annotations.dart';
bool _visited = false;
void setupReflection(reflector) {
if (_visited) return;
_visited = true;
reflector
..registerType(MyComponent, {
"factory": () => new MyComponent(),
"parameters": const [],
"annotations": const [const Component(selector: '[soup]')]
});
}

View File

@ -1,14 +0,0 @@
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]')]
});
}

View File

@ -2,12 +2,12 @@ library web_foo;
import 'package:angular2/src/core/application.dart';
import 'package:angular2/src/reflection/reflection.dart';
import 'index.bootstrap.dart' as ngStaticInit;
import 'index.ngDeps.dart' as ngStaticInit;
import 'package:angular2/src/reflection/reflection_capabilities.dart';
import 'bar.dart';
void main() {
ngStaticInit.setupReflection();
ngStaticInit.setupReflection(reflector);
reflector.reflectionCapabilities = new ReflectionCapabilities();
bootstrap(MyComponent);
}

View File

@ -0,0 +1,15 @@
library web_foo;
import 'index.dart';
import 'package:angular2/src/core/application.dart';
import 'package:angular2/src/reflection/reflection.dart';
import 'package:angular2/src/reflection/reflection_capabilities.dart';
import 'bar.dart';
import 'bar.ngDeps.dart' as i0;
bool _visited = false;
void setupReflection(reflector) {
if (_visited) return;
_visited = true;
i0.setupReflection(reflector);
}

View File

@ -0,0 +1,16 @@
library bar;
import 'bar.dart';
import 'package:angular2/src/core/annotations/annotations.dart';
bool _visited = false;
void setupReflection(reflector) {
if (_visited) return;
_visited = true;
reflector
..registerType(MyComponent, {
"factory": () => new MyComponent(),
"parameters": const [],
"annotations": const [const Component(selector: '[soup]')]
});
}

View File

@ -1,14 +0,0 @@
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]')]
});
}

View File

@ -1,4 +1,4 @@
library angular2.test;
library angular2.test.transform;
import 'dart:io';
import 'package:barback/barback.dart';
@ -8,31 +8,30 @@ import 'package:dart_style/dart_style.dart';
import 'package:unittest/unittest.dart';
import 'package:unittest/vm_config.dart';
import 'reflection_remover_files/expected/index.dart'
as reflection_remover_output;
main() {
useVMConfiguration();
// TODO(kegluneq): Add a test for generating multiple annotations.
group('Annotation tests:', _runTests);
group('Integration tests:', _integrationTests);
}
var formatter = new DartFormatter();
var transform = new AngularTransformer(new TransformerOptions('web/index.dart',
reflectionEntryPoint: 'web/index.dart',
newEntryPoint: 'web/index.bootstrap.dart'));
var transform = new AngularTransformerGroup(new TransformerOptions(
'web/index.dart', reflectionEntryPoint: 'web/index.dart'));
class TestConfig {
class IntegrationTestConfig {
final String name;
final Map<String, String> assetPathToInputPath;
final Map<String, String> assetPathToExpectedOutputPath;
TestConfig(this.name,
IntegrationTestConfig(this.name,
{Map<String, String> inputs, Map<String, String> outputs})
: this.assetPathToInputPath = inputs,
this.assetPathToExpectedOutputPath = outputs;
}
void _runTests() {
void _integrationTests() {
/*
* Each test has its own directory for inputs & an `expected` directory for
* expected outputs.
@ -43,52 +42,53 @@ void _runTests() {
var commonInputs = {
'angular2|lib/src/core/annotations/annotations.dart':
'../../lib/src/core/annotations/annotations.dart',
'angular2|lib/src/core/application.dart': 'common.dart',
'angular2|lib/src/core/application.dart': 'common/application.dart',
'angular2|lib/src/reflection/reflection_capabilities.dart':
'reflection_capabilities.dart'
'common/reflection_capabilities.dart'
};
var tests = [
new TestConfig('Simple',
new IntegrationTestConfig('Simple',
inputs: {
'a|web/index.dart': 'simple_annotation_files/index.dart',
'a|web/bar.dart': 'simple_annotation_files/bar.dart'
},
outputs: {
'a|web/index.bootstrap.dart':
'simple_annotation_files/expected/index.bootstrap.dart',
'a|web/index.dart': 'simple_annotation_files/expected/index.dart',
'a|web/bar.ngDeps.dart':
'simple_annotation_files/expected/bar.ngDeps.dart',
'a|web/index.ngDeps.dart':
'simple_annotation_files/expected/index.ngDeps.dart'
}),
new TestConfig('Two injected dependencies',
new IntegrationTestConfig('Reflection Remover',
inputs: {'a|web/index.dart': 'reflection_remover_files/index.dart'},
outputs: {'a|web/index.dart': reflection_remover_output.code}),
new IntegrationTestConfig('Two injected dependencies',
inputs: {
'a|web/index.dart': 'two_deps_files/index.dart',
'a|web/foo.dart': 'two_deps_files/foo.dart',
'a|web/bar.dart': 'two_deps_files/bar.dart'
},
outputs: {
'a|web/index.bootstrap.dart':
'two_deps_files/expected/index.bootstrap.dart'
'a|web/bar.ngDeps.dart': 'two_deps_files/expected/bar.ngDeps.dart'
}),
new TestConfig('List of types',
new IntegrationTestConfig('List of types',
inputs: {
'a|web/index.dart': 'list_of_types_files/index.dart',
'a|web/foo.dart': 'list_of_types_files/foo.dart',
'a|web/bar.dart': 'list_of_types_files/bar.dart'
},
outputs: {
'a|web/index.bootstrap.dart':
'list_of_types_files/expected/index.bootstrap.dart'
'a|web/bar.ngDeps.dart': 'list_of_types_files/expected/bar.ngDeps.dart'
}),
new TestConfig('Component with synthetic Constructor',
new IntegrationTestConfig('Component with synthetic Constructor',
inputs: {
'a|web/index.dart': 'synthetic_ctor_files/index.dart',
'a|web/bar.dart': 'synthetic_ctor_files/bar.dart'
},
outputs: {
'a|web/index.bootstrap.dart':
'synthetic_ctor_files/expected/index.bootstrap.dart'
'a|web/bar.ngDeps.dart': 'synthetic_ctor_files/expected/bar.ngDeps.dart'
}),
new TestConfig('Component with two annotations',
new IntegrationTestConfig('Component with two annotations',
inputs: {
'a|web/index.dart': 'two_annotations_files/index.dart',
'a|web/bar.dart': 'two_annotations_files/bar.dart',
@ -96,17 +96,25 @@ void _runTests() {
'../../lib/src/core/annotations/template.dart'
},
outputs: {
'a|web/index.bootstrap.dart':
'two_annotations_files/expected/index.bootstrap.dart'
'a|web/bar.ngDeps.dart': 'two_annotations_files/expected/bar.ngDeps.dart'
}),
new TestConfig('Basic `bind`',
new IntegrationTestConfig('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'
'a|web/bar.ngDeps.dart': 'basic_bind_files/expected/bar.ngDeps.dart'
}),
new IntegrationTestConfig('Chained dependencies',
inputs: {
'a|web/index.dart': 'chained_deps_files/index.dart',
'a|web/foo.dart': 'chained_deps_files/foo.dart',
'a|web/bar.dart': 'chained_deps_files/bar.dart'
},
outputs: {
'a|web/bar.ngDeps.dart': 'chained_deps_files/expected/bar.ngDeps.dart',
'a|web/foo.ngDeps.dart': 'chained_deps_files/expected/foo.ngDeps.dart'
})
];
@ -118,8 +126,8 @@ void _runTests() {
config.assetPathToInputPath
..addAll(commonInputs)
..forEach((key, value) {
config.assetPathToInputPath[
key] = cache.putIfAbsent(value, () => _readFile(value));
config.assetPathToInputPath[key] =
cache.putIfAbsent(value, () => _readFile(value));
});
config.assetPathToExpectedOutputPath.forEach((key, value) {
config.assetPathToExpectedOutputPath[key] = cache.putIfAbsent(value, () {
@ -133,7 +141,7 @@ void _runTests() {
}
}
/// Smoothes over differences in CWD between IDEs and running tests in Travis.
/// Smooths 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);
@ -141,5 +149,5 @@ String _readFile(String path) {
return file.readAsStringSync();
}
}
return null;
return path;
}

View File

@ -0,0 +1,20 @@
library bar;
import 'bar.dart';
import 'package:angular2/src/core/annotations/annotations.dart';
import 'package:angular2/src/core/annotations/template.dart';
bool _visited = false;
void setupReflection(reflector) {
if (_visited) return;
_visited = true;
reflector
..registerType(MyComponent, {
"factory": () => new MyComponent(),
"parameters": const [],
"annotations": const [
const Component(selector: '[soup]'),
const Template(inline: 'Salad')
]
});
}

View File

@ -1,18 +0,0 @@
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;
import 'package:angular2/src/core/annotations/template.dart' as i2;
setupReflection() {
reflector
..registerType(i0.MyComponent, {
"factory": () => new i0.MyComponent(),
"parameters": const [const []],
"annotations": const [
const i1.Component(selector: '[soup]'),
const i2.Template(inline: 'Salad')
]
});
}

View File

@ -0,0 +1,20 @@
library bar;
import 'bar.dart';
import 'package:angular2/src/core/annotations/annotations.dart';
import 'foo.dart' as prefix;
bool _visited = false;
void setupReflection(reflector) {
if (_visited) return;
_visited = true;
reflector
..registerType(MyComponent, {
"factory":
(prefix.MyContext c, String inValue) => new MyComponent(c, inValue),
"parameters": const [const [prefix.MyContext], const [String]],
"annotations": const [
const Component(selector: prefix.preDefinedSelector)
]
});
}

View File

@ -1,16 +0,0 @@
library angular2.src.transform.generated;
import 'package:angular2/src/reflection/reflection.dart' show reflector;
import 'bar.dart' as i0;
import 'foo.dart' as i1;
import 'package:angular2/src/core/annotations/annotations.dart' as i2;
setupReflection() {
reflector
..registerType(i0.MyComponent, {
"factory":
(i1.MyContext c, String inValue) => new i0.MyComponent(c, inValue),
"parameters": const [const [i1.MyContext, String]],
"annotations": const [const i2.Component(selector: i1.preDefinedSelector)]
});
}