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:
parent
08bd3a4443
commit
d0aceef4e0
|
@ -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);
|
||||
}
|
||||
}
|
|
@ -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');
|
||||
}
|
||||
}
|
||||
}
|
|
@ -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;
|
||||
}
|
|
@ -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.
|
|
@ -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';
|
|
@ -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()}]';
|
||||
}
|
||||
}
|
|
@ -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);
|
||||
}
|
||||
}
|
|
@ -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);
|
||||
}
|
||||
}
|
||||
}
|
|
@ -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);
|
||||
}
|
|
@ -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;
|
||||
}
|
||||
}
|
|
@ -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';
|
||||
}
|
||||
}
|
|
@ -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');
|
||||
}
|
||||
}
|
||||
}
|
|
@ -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;
|
||||
}
|
||||
}
|
|
@ -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('})');
|
||||
}
|
||||
|
|
@ -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
|
|
@ -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
|
|
@ -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);
|
||||
}
|
||||
}
|
|
@ -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;
|
||||
}
|
||||
}
|
|
@ -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;
|
||||
}
|
||||
}
|
|
@ -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));
|
||||
}
|
|
@ -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);
|
||||
}
|
||||
}
|
|
@ -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');
|
||||
}
|
||||
}
|
||||
}
|
|
@ -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]);
|
||||
}
|
||||
|
|
|
@ -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});
|
||||
}
|
|
@ -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});
|
||||
}
|
|
@ -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();
|
||||
}
|
|
@ -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);
|
||||
}
|
|
@ -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]')]
|
||||
});
|
||||
}
|
|
@ -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);
|
||||
}
|
|
@ -0,0 +1,8 @@
|
|||
library foo;
|
||||
|
||||
import 'package:angular2/src/core/annotations/annotations.dart';
|
||||
|
||||
@Component(selector: '[salad]')
|
||||
class DependencyComponent {
|
||||
DependencyComponent();
|
||||
}
|
|
@ -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);
|
||||
}
|
|
@ -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])
|
||||
]
|
||||
});
|
||||
}
|
|
@ -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])
|
||||
]
|
||||
});
|
||||
}
|
|
@ -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.
|
|
@ -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);
|
||||
}
|
||||
""";
|
|
@ -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);
|
||||
}
|
|
@ -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]')]
|
||||
});
|
||||
}
|
|
@ -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]')]
|
||||
});
|
||||
}
|
|
@ -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);
|
||||
}
|
||||
|
|
|
@ -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);
|
||||
}
|
|
@ -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]')]
|
||||
});
|
||||
}
|
|
@ -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]')]
|
||||
});
|
||||
}
|
|
@ -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;
|
||||
}
|
||||
|
|
|
@ -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')
|
||||
]
|
||||
});
|
||||
}
|
|
@ -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')
|
||||
]
|
||||
});
|
||||
}
|
|
@ -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)
|
||||
]
|
||||
});
|
||||
}
|
|
@ -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)]
|
||||
});
|
||||
}
|
Loading…
Reference in New Issue