feat(dart/transformer:: Initial commit of ctor stubs & annotation register
Closes #646 Closes #496 Closes #498
This commit is contained in:
parent
cbc76faf11
commit
6e90cacaf4
|
@ -516,11 +516,17 @@ gulp.task('ci', function(done) {
|
||||||
);
|
);
|
||||||
});
|
});
|
||||||
|
|
||||||
|
gulp.task('tests/transform.dart', function() {
|
||||||
|
return gulp.src('modules/angular2/test/transform/**')
|
||||||
|
.pipe(gulp.dest('dist/dart/angular2/test/transform'));
|
||||||
|
});
|
||||||
|
|
||||||
// -----------------
|
// -----------------
|
||||||
// orchestrated targets
|
// orchestrated targets
|
||||||
gulp.task('build.dart', function(done) {
|
gulp.task('build.dart', function(done) {
|
||||||
runSequence(
|
runSequence(
|
||||||
['build/deps.js.dart2js', 'build/transpile.dart', 'build/html.dart'],
|
['build/deps.js.dart2js', 'build/transpile.dart', 'build/html.dart'],
|
||||||
|
'tests/transform.dart',
|
||||||
'build/pubspec.dart',
|
'build/pubspec.dart',
|
||||||
'build/multicopy.dart',
|
'build/multicopy.dart',
|
||||||
'build/pubbuild.dart',
|
'build/pubbuild.dart',
|
||||||
|
|
|
@ -9,6 +9,11 @@ homepage: <%= packageJson.homepage %>
|
||||||
environment:
|
environment:
|
||||||
sdk: '>=1.4.0'
|
sdk: '>=1.4.0'
|
||||||
dependencies:
|
dependencies:
|
||||||
|
analyzer: '0.22.4'
|
||||||
|
barback: '0.15.2+2'
|
||||||
|
code_transformers: '0.2.5'
|
||||||
|
dart_style: '0.1.3'
|
||||||
|
html5lib: '0.12.0'
|
||||||
stack_trace: '>=1.1.1 <1.2.0'
|
stack_trace: '>=1.1.1 <1.2.0'
|
||||||
dev_dependencies:
|
dev_dependencies:
|
||||||
guinness: ">=0.1.16 <0.2.0"
|
guinness: ">=0.1.16 <0.2.0"
|
||||||
|
|
|
@ -0,0 +1,58 @@
|
||||||
|
import 'dart:collection' show Queue;
|
||||||
|
import 'package:analyzer/src/generated/element.dart';
|
||||||
|
|
||||||
|
/// Provides a mechanism for checking an element for the provided
|
||||||
|
/// [_annotationClass] and reporting the resulting (element, annotation) pairs.
|
||||||
|
class AnnotationMatcher {
|
||||||
|
/// Queue for annotations.
|
||||||
|
final initQueue = new Queue<AnnotationMatch>();
|
||||||
|
/// All the annotations we have seen for each element
|
||||||
|
final _seenAnnotations = new Map<Element, Set<ElementAnnotation>>();
|
||||||
|
|
||||||
|
/// The class we are searching for to populate [initQueue].
|
||||||
|
final ClassElement _annotationClass;
|
||||||
|
|
||||||
|
AnnotationMatcher(this._annotationClass);
|
||||||
|
|
||||||
|
/// Records all [_annotationClass] annotations and the [element]s they apply to.
|
||||||
|
/// Returns [true] if 1) [element] is annotated with [_annotationClass] and
|
||||||
|
/// 2) ([element], [_annotationClass]) has been seen previously.
|
||||||
|
bool processAnnotations(ClassElement element) {
|
||||||
|
var found = false;
|
||||||
|
element.metadata.where((ElementAnnotation meta) {
|
||||||
|
// Only process [_annotationClass]s.
|
||||||
|
// TODO(tjblasi): Make this recognize non-ConstructorElement annotations.
|
||||||
|
return meta.element is ConstructorElement &&
|
||||||
|
_isAnnotationMatch(meta.element.returnType);
|
||||||
|
}).where((ElementAnnotation meta) {
|
||||||
|
// Only process ([element], [meta]) combinations we haven't seen previously.
|
||||||
|
return !_seenAnnotations
|
||||||
|
.putIfAbsent(element, () => new Set<ElementAnnotation>())
|
||||||
|
.contains(meta);
|
||||||
|
}).forEach((ElementAnnotation meta) {
|
||||||
|
_seenAnnotations[element].add(meta);
|
||||||
|
initQueue.addLast(new AnnotationMatch(element, meta));
|
||||||
|
found = true;
|
||||||
|
});
|
||||||
|
return found;
|
||||||
|
}
|
||||||
|
|
||||||
|
/// Whether [type], its superclass, or one of its interfaces matches [_annotationClass].
|
||||||
|
bool _isAnnotationMatch(InterfaceType type) {
|
||||||
|
if (type == null || type.element == null) return false;
|
||||||
|
if (type.element.type == _annotationClass.type) return true;
|
||||||
|
if (_isAnnotationMatch(type.superclass)) return true;
|
||||||
|
for (var interface in type.interfaces) {
|
||||||
|
if (_isAnnotationMatch(interface)) return true;
|
||||||
|
}
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// Element/ElementAnnotation pair.
|
||||||
|
class AnnotationMatch {
|
||||||
|
final Element element;
|
||||||
|
final ElementAnnotation annotation;
|
||||||
|
|
||||||
|
AnnotationMatch(this.element, this.annotation);
|
||||||
|
}
|
|
@ -0,0 +1,390 @@
|
||||||
|
library angular2.transformer;
|
||||||
|
|
||||||
|
import 'package:analyzer/src/generated/ast.dart';
|
||||||
|
import 'package:analyzer/src/generated/element.dart';
|
||||||
|
import 'package:analyzer/src/generated/java_core.dart';
|
||||||
|
import 'package:barback/barback.dart' show AssetId, TransformLogger;
|
||||||
|
import 'package:dart_style/dart_style.dart';
|
||||||
|
import 'package:path/path.dart' as path;
|
||||||
|
|
||||||
|
import 'annotation_processor.dart';
|
||||||
|
|
||||||
|
/// Base class that maintains codegen state.
|
||||||
|
class Context {
|
||||||
|
final TransformLogger _logger;
|
||||||
|
/// Maps libraries to the import prefixes we will use in the newly
|
||||||
|
/// generated code.
|
||||||
|
final Map<LibraryElement, String> _libraryPrefixes;
|
||||||
|
|
||||||
|
DirectiveRegistry _directiveRegistry;
|
||||||
|
DirectiveRegistry get directiveRegistry => _directiveRegistry;
|
||||||
|
|
||||||
|
Context({TransformLogger logger})
|
||||||
|
: _logger = logger,
|
||||||
|
_libraryPrefixes = {} {
|
||||||
|
_directiveRegistry = new _DirectiveRegistryImpl(this);
|
||||||
|
}
|
||||||
|
|
||||||
|
void error(String errorString) {
|
||||||
|
if (_logger != null) {
|
||||||
|
_logger.error(errorString);
|
||||||
|
} else {
|
||||||
|
throw new Error(errorString);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/// If elements in [lib] should be prefixed in our generated code, returns
|
||||||
|
/// the appropriate prefix followed by a `.`. Future items from the same
|
||||||
|
/// library will use the same prefix.
|
||||||
|
/// If [lib] does not need a prefix, returns the empty string.
|
||||||
|
String _getPrefixDot(LibraryElement lib) {
|
||||||
|
var prefix = lib != null && !lib.isInSdk
|
||||||
|
? _libraryPrefixes.putIfAbsent(lib, () => 'i${_libraryPrefixes.length}')
|
||||||
|
: null;
|
||||||
|
return prefix == null ? '' : '${prefix}.';
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
abstract class DirectiveRegistry {
|
||||||
|
// Adds [entry] to the `registerType` calls which will be generated.
|
||||||
|
void register(AnnotationMatch entry);
|
||||||
|
}
|
||||||
|
|
||||||
|
const _reflectorImport =
|
||||||
|
'import \'package:angular2/src/reflection/reflection.dart\' '
|
||||||
|
'show reflector;';
|
||||||
|
|
||||||
|
/// Default implementation to map from [LibraryElement] to [AssetId]. This
|
||||||
|
/// assumes that [el.source] has a getter called [assetId].
|
||||||
|
AssetId _assetIdFromLibraryElement(LibraryElement el) {
|
||||||
|
return (el.source as dynamic).assetId;
|
||||||
|
}
|
||||||
|
|
||||||
|
String codegenEntryPoint(Context context,
|
||||||
|
{LibraryElement entryPoint, AssetId newEntryPoint}) {
|
||||||
|
// This must be called prior to [codegenImports] or the entry point
|
||||||
|
// library will not be imported.
|
||||||
|
var entryPointPrefix = context._getPrefixDot(entryPoint);
|
||||||
|
// TODO(jakemac): copyright and library declaration
|
||||||
|
var outBuffer = new StringBuffer(_reflectorImport);
|
||||||
|
_codegenImports(context, newEntryPoint, outBuffer);
|
||||||
|
outBuffer
|
||||||
|
..write('main() {')
|
||||||
|
..write(context.directiveRegistry.toString())
|
||||||
|
..write('${entryPointPrefix}main();}');
|
||||||
|
|
||||||
|
return new DartFormatter().format(outBuffer.toString());
|
||||||
|
}
|
||||||
|
|
||||||
|
String _codegenImports(
|
||||||
|
Context context, AssetId newEntryPoint, StringBuffer buffer) {
|
||||||
|
context._libraryPrefixes.forEach((lib, prefix) {
|
||||||
|
buffer
|
||||||
|
..write(_codegenImport(
|
||||||
|
context, _assetIdFromLibraryElement(lib), newEntryPoint))
|
||||||
|
..writeln('as ${prefix};');
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
_codegenImport(Context context, AssetId libraryId, AssetId entryPoint) {
|
||||||
|
if (libraryId.path.startsWith('lib/')) {
|
||||||
|
var packagePath = libraryId.path.replaceFirst('lib/', '');
|
||||||
|
return "import 'package:${libraryId.package}/${packagePath}'";
|
||||||
|
} else if (libraryId.package != entryPoint.package) {
|
||||||
|
context._error("Can't import `${libraryId}` from `${entryPoint}`");
|
||||||
|
} else if (path.url.split(libraryId.path)[0] ==
|
||||||
|
path.url.split(entryPoint.path)[0]) {
|
||||||
|
var relativePath =
|
||||||
|
path.relative(libraryId.path, from: path.dirname(entryPoint.path));
|
||||||
|
return "import '${relativePath}'";
|
||||||
|
} else {
|
||||||
|
context._error("Can't import `${libraryId}` from `${entryPoint}`");
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
class _DirectiveRegistryImpl implements DirectiveRegistry {
|
||||||
|
final Context _context;
|
||||||
|
final StringBuffer _buffer = new StringBuffer();
|
||||||
|
|
||||||
|
_DirectiveRegistryImpl(this._context);
|
||||||
|
|
||||||
|
@override
|
||||||
|
String toString() {
|
||||||
|
return _buffer.isEmpty ? '' : 'reflector${_buffer};';
|
||||||
|
}
|
||||||
|
|
||||||
|
// Adds [entry] to the `registerType` calls which will be generated.
|
||||||
|
void register(AnnotationMatch entry) {
|
||||||
|
var element = entry.element;
|
||||||
|
var annotation = entry.annotation;
|
||||||
|
|
||||||
|
if (annotation.element is! ConstructorElement) {
|
||||||
|
_context._error('Unsupported annotation type. '
|
||||||
|
'Only constructors are supported as Directives.');
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
if (element is! ClassElement) {
|
||||||
|
_context._error('Directives can only be applied to classes.');
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
if (element.node is! ClassDeclaration) {
|
||||||
|
_context._error('Unsupported annotation type. '
|
||||||
|
'Only class declarations are supported as Directives.');
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
final ConstructorElement ctor = element.unnamedConstructor;
|
||||||
|
if (ctor == null) {
|
||||||
|
_context._error('No default constructor found for ${element.name}');
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
_buffer.writeln('..registerType(${_codegenClassTypeString(element)}, {'
|
||||||
|
'"factory": ${_codegenFactoryProp(ctor)},'
|
||||||
|
'"parameters": ${_codegenParametersProp(ctor)},'
|
||||||
|
'"annotations": ${_codegenAnnotationsProp(element)}'
|
||||||
|
'})');
|
||||||
|
}
|
||||||
|
|
||||||
|
String _codegenClassTypeString(ClassElement el) {
|
||||||
|
return '${_context._getPrefixDot(el.library)}${el.name}';
|
||||||
|
}
|
||||||
|
|
||||||
|
/// Creates the 'annotations' property for the Angular2 [registerType] call
|
||||||
|
/// for [el].
|
||||||
|
String _codegenAnnotationsProp(ClassElement el) {
|
||||||
|
var writer = new PrintStringWriter();
|
||||||
|
var visitor = new _AnnotationsTransformVisitor(writer, _context);
|
||||||
|
el.node.accept(visitor);
|
||||||
|
return writer.toString();
|
||||||
|
}
|
||||||
|
|
||||||
|
/// Creates the 'factory' property for the Angular2 [registerType] call
|
||||||
|
/// for [ctor].
|
||||||
|
String _codegenFactoryProp(ConstructorElement ctor) {
|
||||||
|
if (ctor.node == null) {
|
||||||
|
// This occurs when the class does not declare a constructor.
|
||||||
|
var prefix = _context._getPrefixDot(ctor.type.element.library);
|
||||||
|
return '() => new ${prefix}${ctor.enclosingElement.displayName}()';
|
||||||
|
} else {
|
||||||
|
var writer = new PrintStringWriter();
|
||||||
|
var visitor = new _FactoryTransformVisitor(writer, _context);
|
||||||
|
ctor.node.accept(visitor);
|
||||||
|
return writer.toString();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/// Creates the 'parameters' property for the Angular2 [registerType] call
|
||||||
|
/// for [ctor].
|
||||||
|
String _codegenParametersProp(ConstructorElement ctor) {
|
||||||
|
if (ctor.node == null) {
|
||||||
|
// This occurs when the class does not declare a constructor.
|
||||||
|
return 'const [const []]';
|
||||||
|
} else {
|
||||||
|
var writer = new PrintStringWriter();
|
||||||
|
var visitor = new _ParameterTransformVisitor(writer, _context);
|
||||||
|
ctor.node.accept(visitor);
|
||||||
|
return writer.toString();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/// Visitor providing common methods for concrete implementations.
|
||||||
|
abstract class _TransformVisitor extends ToSourceVisitor {
|
||||||
|
final Context _context;
|
||||||
|
final PrintWriter _writer;
|
||||||
|
|
||||||
|
_TransformVisitor(PrintWriter writer, this._context)
|
||||||
|
: this._writer = writer,
|
||||||
|
super(writer);
|
||||||
|
|
||||||
|
/// Safely visit the given node.
|
||||||
|
/// @param node the node to be visited
|
||||||
|
void _visitNode(AstNode node) {
|
||||||
|
if (node != null) {
|
||||||
|
node.accept(this);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Safely visit the given node, printing the prefix before the node if it is non-`null`.
|
||||||
|
*
|
||||||
|
* @param prefix the prefix to be printed if there is a node to visit
|
||||||
|
* @param node the node to be visited
|
||||||
|
*/
|
||||||
|
void _visitNodeWithPrefix(String prefix, AstNode node) {
|
||||||
|
if (node != null) {
|
||||||
|
_writer.print(prefix);
|
||||||
|
node.accept(this);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Safely visit the given node, printing the suffix after the node if it is non-`null`.
|
||||||
|
*
|
||||||
|
* @param suffix the suffix to be printed if there is a node to visit
|
||||||
|
* @param node the node to be visited
|
||||||
|
*/
|
||||||
|
void _visitNodeWithSuffix(AstNode node, String suffix) {
|
||||||
|
if (node != null) {
|
||||||
|
node.accept(this);
|
||||||
|
_writer.print(suffix);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
@override
|
||||||
|
Object visitSimpleIdentifier(SimpleIdentifier node) {
|
||||||
|
// Make sure the identifier is prefixed if necessary.
|
||||||
|
if (node.bestElement is ClassElementImpl ||
|
||||||
|
node.bestElement is PropertyAccessorElement) {
|
||||||
|
_writer
|
||||||
|
..print(_context._getPrefixDot(node.bestElement.library))
|
||||||
|
..print(node.token.lexeme);
|
||||||
|
} else {
|
||||||
|
return super.visitSimpleIdentifier(node);
|
||||||
|
}
|
||||||
|
return null;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/// SourceVisitor designed to accept [ConstructorDeclaration] nodes.
|
||||||
|
class _CtorTransformVisitor extends _TransformVisitor {
|
||||||
|
bool _withParameterTypes = true;
|
||||||
|
bool _withParameterNames = true;
|
||||||
|
|
||||||
|
_CtorTransformVisitor(PrintWriter writer, Context _context)
|
||||||
|
: super(writer, _context);
|
||||||
|
|
||||||
|
/// If [_withParameterTypes] is true, this method outputs [node]'s type
|
||||||
|
/// (appropriately prefixed based on [_libraryPrefixes]. If
|
||||||
|
/// [_withParameterNames] is true, this method outputs [node]'s identifier.
|
||||||
|
Object _visitNormalFormalParameter(NormalFormalParameter node) {
|
||||||
|
if (_withParameterTypes) {
|
||||||
|
var paramType = node.element.type;
|
||||||
|
var prefix = _context._getPrefixDot(paramType.element.library);
|
||||||
|
_writer.print('${prefix}${paramType.displayName}');
|
||||||
|
if (_withParameterNames) {
|
||||||
|
_visitNodeWithPrefix(' ', node.identifier);
|
||||||
|
}
|
||||||
|
} else if (_withParameterNames) {
|
||||||
|
_visitNode(node.identifier);
|
||||||
|
}
|
||||||
|
return null;
|
||||||
|
}
|
||||||
|
|
||||||
|
@override
|
||||||
|
Object visitSimpleFormalParameter(SimpleFormalParameter node) {
|
||||||
|
return _visitNormalFormalParameter(node);
|
||||||
|
}
|
||||||
|
|
||||||
|
@override
|
||||||
|
Object visitFieldFormalParameter(FieldFormalParameter node) {
|
||||||
|
if (node.parameters != null) {
|
||||||
|
_context.error('Parameters in ctor not supported '
|
||||||
|
'(${super.visitFormalParameterList(node)}');
|
||||||
|
}
|
||||||
|
return _visitNormalFormalParameter(node);
|
||||||
|
}
|
||||||
|
|
||||||
|
@override
|
||||||
|
Object visitDefaultFormalParameter(DefaultFormalParameter node) {
|
||||||
|
_visitNode(node.parameter);
|
||||||
|
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, Context _context)
|
||||||
|
: super(writer, _context);
|
||||||
|
|
||||||
|
@override
|
||||||
|
Object visitConstructorDeclaration(ConstructorDeclaration node) {
|
||||||
|
_withParameterNames = false;
|
||||||
|
_withParameterTypes = true;
|
||||||
|
_writer.print('const [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(', ');
|
||||||
|
}
|
||||||
|
parameters[i].accept(this);
|
||||||
|
}
|
||||||
|
return null;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/// ToSourceVisitor designed to print 'factory' values for Angular2's
|
||||||
|
/// [registerType] calls.
|
||||||
|
class _FactoryTransformVisitor extends _CtorTransformVisitor {
|
||||||
|
_FactoryTransformVisitor(PrintWriter writer, Context _context)
|
||||||
|
: super(writer, _context);
|
||||||
|
|
||||||
|
@override
|
||||||
|
Object visitConstructorDeclaration(ConstructorDeclaration 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 _TransformVisitor {
|
||||||
|
_AnnotationsTransformVisitor(PrintWriter writer, Context _context)
|
||||||
|
: super(writer, _context);
|
||||||
|
|
||||||
|
@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);
|
||||||
|
// TODO(tjblasi): Do we need to handle named constructors for annotations?
|
||||||
|
// _visitNodeWithPrefix(".", node.constructorName);
|
||||||
|
_visitNode(node.arguments);
|
||||||
|
return null;
|
||||||
|
}
|
||||||
|
}
|
|
@ -0,0 +1,52 @@
|
||||||
|
library angular2.transformer;
|
||||||
|
|
||||||
|
import 'dart:async';
|
||||||
|
import 'package:barback/barback.dart';
|
||||||
|
import 'package:html5lib/dom.dart' as dom;
|
||||||
|
import 'package:html5lib/parser.dart' show parse;
|
||||||
|
import 'package:path/path.dart' as path;
|
||||||
|
|
||||||
|
import 'options.dart';
|
||||||
|
|
||||||
|
Future transformHtmlEntryPoint(
|
||||||
|
TransformerOptions options, Transform transform) {
|
||||||
|
// For now at least, [options.htmlEntryPoint], [options.entryPoint], and
|
||||||
|
// [options.newEntryPoint] need to be in the same folder.
|
||||||
|
// TODO(jakemac): support package urls with [options.entryPoint] or
|
||||||
|
// [options.newEntryPoint] in `lib`, and [options.htmlEntryPoint] in another
|
||||||
|
// directory.
|
||||||
|
var _expectedDir = path.split(options.htmlEntryPoint)[0];
|
||||||
|
if (!options.inSameTopLevelDir()) {
|
||||||
|
transform.logger.error(
|
||||||
|
'${options.htmlEntryPointParam}, ${options.entryPointParam}, and '
|
||||||
|
'${options.newEntryPointParam} (if supplied) all must be in the '
|
||||||
|
'same top level directory.');
|
||||||
|
}
|
||||||
|
|
||||||
|
// The relative path from [options.htmlEntryPoint] to [dartEntry]. You must
|
||||||
|
// ensure that neither of these is null before calling this function.
|
||||||
|
String _relativeDartEntryPath(String dartEntry) =>
|
||||||
|
path.relative(dartEntry, from: path.dirname(options.htmlEntryPoint));
|
||||||
|
|
||||||
|
// Checks if the src of this script tag is pointing at `options.entryPoint`.
|
||||||
|
bool _isEntryPointScript(dom.Element script) =>
|
||||||
|
path.normalize(script.attributes['src']) ==
|
||||||
|
_relativeDartEntryPath(options.entryPoint);
|
||||||
|
|
||||||
|
return transform.primaryInput.readAsString().then((String html) {
|
||||||
|
var found = false;
|
||||||
|
var doc = parse(html);
|
||||||
|
var scripts = doc.querySelectorAll('script[type="application/dart"]');
|
||||||
|
for (dom.Element script in scripts) {
|
||||||
|
if (!_isEntryPointScript(script)) continue;
|
||||||
|
script.attributes['src'] = _relativeDartEntryPath(options.newEntryPoint);
|
||||||
|
found = true;
|
||||||
|
}
|
||||||
|
if (!found) {
|
||||||
|
transform.logger.error('Unable to find script for ${options.entryPoint} '
|
||||||
|
'in ${options.htmlEntryPoint}.');
|
||||||
|
}
|
||||||
|
return transform.addOutput(
|
||||||
|
new Asset.fromString(transform.primaryInput.id, doc.outerHtml));
|
||||||
|
});
|
||||||
|
}
|
|
@ -0,0 +1,17 @@
|
||||||
|
library angular2.transformer;
|
||||||
|
|
||||||
|
import 'package:path/path.dart' as path;
|
||||||
|
|
||||||
|
class TransformerOptions {
|
||||||
|
final String entryPoint;
|
||||||
|
final String newEntryPoint;
|
||||||
|
final String htmlEntryPoint;
|
||||||
|
|
||||||
|
TransformerOptions(this.entryPoint, this.newEntryPoint, this.htmlEntryPoint);
|
||||||
|
|
||||||
|
bool inSameTopLevelDir() {
|
||||||
|
var expectedDir = path.split(htmlEntryPoint)[0];
|
||||||
|
return (expectedDir == path.split(entryPoint)[0] &&
|
||||||
|
expectedDir == path.split(newEntryPoint)[0]);
|
||||||
|
}
|
||||||
|
}
|
|
@ -0,0 +1,43 @@
|
||||||
|
import 'package:code_transformers/resolver.dart';
|
||||||
|
|
||||||
|
Resolvers createResolvers() {
|
||||||
|
return new Resolvers.fromMock({
|
||||||
|
// The list of types below is derived from:
|
||||||
|
// * types that are used internally by the resolver (see
|
||||||
|
// _initializeFrom in resolver.dart).
|
||||||
|
// TODO(jakemac): Move this into code_transformers so it can be shared.
|
||||||
|
'dart:core': '''
|
||||||
|
library dart.core;
|
||||||
|
class Object {}
|
||||||
|
class Function {}
|
||||||
|
class StackTrace {}
|
||||||
|
class Symbol {}
|
||||||
|
class Type {}
|
||||||
|
|
||||||
|
class String extends Object {}
|
||||||
|
class bool extends Object {}
|
||||||
|
class num extends Object {}
|
||||||
|
class int extends num {}
|
||||||
|
class double extends num {}
|
||||||
|
class DateTime extends Object {}
|
||||||
|
class Null extends Object {}
|
||||||
|
|
||||||
|
class Deprecated extends Object {
|
||||||
|
final String expires;
|
||||||
|
const Deprecated(this.expires);
|
||||||
|
}
|
||||||
|
const Object deprecated = const Deprecated("next release");
|
||||||
|
class _Override { const _Override(); }
|
||||||
|
const Object override = const _Override();
|
||||||
|
class _Proxy { const _Proxy(); }
|
||||||
|
const Object proxy = const _Proxy();
|
||||||
|
|
||||||
|
class List<V> extends Object {}
|
||||||
|
class Map<K, V> extends Object {}
|
||||||
|
''',
|
||||||
|
'dart:html': '''
|
||||||
|
library dart.html;
|
||||||
|
class HtmlElement {}
|
||||||
|
''',
|
||||||
|
});
|
||||||
|
}
|
|
@ -0,0 +1,103 @@
|
||||||
|
// Copyright (c) 2015, the Dart project authors. Please see the AUTHORS file
|
||||||
|
// for details. All rights reserved. Use of this source code is governed by a
|
||||||
|
// BSD-style license that can be found in the LICENSE file.
|
||||||
|
library angular2.transformer;
|
||||||
|
|
||||||
|
import 'dart:async';
|
||||||
|
import 'package:barback/barback.dart';
|
||||||
|
import 'package:code_transformers/resolver.dart';
|
||||||
|
|
||||||
|
import 'annotation_processor.dart';
|
||||||
|
import 'codegen.dart' as codegen;
|
||||||
|
import 'html_transform.dart';
|
||||||
|
import 'options.dart';
|
||||||
|
import 'resolvers.dart';
|
||||||
|
import 'traversal.dart';
|
||||||
|
|
||||||
|
export '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();
|
||||||
|
|
||||||
|
static const _entryPointParam = 'entry_point';
|
||||||
|
static const _newEntryPointParam = 'new_entry_point';
|
||||||
|
static const _htmlEntryPointParam = 'html_entry_point';
|
||||||
|
|
||||||
|
factory AngularTransformer.asPlugin(BarbackSettings settings) {
|
||||||
|
var entryPoint = settings.configuration[_entryPointParam];
|
||||||
|
var newEntryPoint = settings.configuration[_newEntryPointParam];
|
||||||
|
if (newEntryPoint == null) {
|
||||||
|
newEntryPoint = entryPoint.replaceFirst('.dart', '.bootstrap.dart');
|
||||||
|
}
|
||||||
|
var htmlEntryPoint = settings.configuration[_htmlEntryPointParam];
|
||||||
|
return new AngularTransformer(
|
||||||
|
new TransformerOptions(entryPoint, newEntryPoint, htmlEntryPoint));
|
||||||
|
}
|
||||||
|
|
||||||
|
bool isPrimary(AssetId id) =>
|
||||||
|
options.entryPoint == id.path || options.htmlEntryPoint == id.path;
|
||||||
|
|
||||||
|
Future apply(Transform transform) {
|
||||||
|
if (transform.primaryInput.id.path == options.entryPoint) {
|
||||||
|
return _buildBootstrapFile(transform);
|
||||||
|
} else if (transform.primaryInput.id.path == options.htmlEntryPoint) {
|
||||||
|
return transformHtmlEntryPoint(options, transform);
|
||||||
|
}
|
||||||
|
return null;
|
||||||
|
}
|
||||||
|
|
||||||
|
Future _buildBootstrapFile(Transform transform) {
|
||||||
|
var newEntryPointId =
|
||||||
|
new AssetId(transform.primaryInput.id.package, options.newEntryPoint);
|
||||||
|
return transform.hasInput(newEntryPointId).then((exists) {
|
||||||
|
if (exists) {
|
||||||
|
transform.logger
|
||||||
|
.error('New entry point file $newEntryPointId already exists.');
|
||||||
|
} else {
|
||||||
|
return _resolvers.get(transform).then((resolver) {
|
||||||
|
new _BootstrapFileBuilder(resolver, transform,
|
||||||
|
transform.primaryInput.id, newEntryPointId).run();
|
||||||
|
resolver.release();
|
||||||
|
});
|
||||||
|
}
|
||||||
|
});
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
class _BootstrapFileBuilder {
|
||||||
|
final Resolver _resolver;
|
||||||
|
final Transform _transform;
|
||||||
|
final AssetId _entryPoint;
|
||||||
|
final AssetId _newEntryPoint;
|
||||||
|
|
||||||
|
AnnotationMatcher _directiveInfo;
|
||||||
|
|
||||||
|
_BootstrapFileBuilder(Resolver resolver, Transform transform,
|
||||||
|
this._entryPoint, this._newEntryPoint)
|
||||||
|
: _resolver = resolver,
|
||||||
|
_transform = transform,
|
||||||
|
_directiveInfo = new AnnotationMatcher(resolver
|
||||||
|
.getLibrary(new AssetId(
|
||||||
|
'angular2', 'lib/src/core/annotations/annotations.dart'))
|
||||||
|
.getType('Directive'));
|
||||||
|
|
||||||
|
/// Adds the new entry point file to the transform. Should only be ran once.
|
||||||
|
void run() {
|
||||||
|
var entryLib = _resolver.getLibrary(_entryPoint);
|
||||||
|
|
||||||
|
new ImportTraversal(_directiveInfo).traverse(entryLib);
|
||||||
|
|
||||||
|
var context = new codegen.Context(logger: _transform.logger);
|
||||||
|
_directiveInfo.initQueue
|
||||||
|
.forEach((entry) => context.directiveRegistry.register(entry));
|
||||||
|
|
||||||
|
_transform.addOutput(new Asset.fromString(_newEntryPoint, codegen
|
||||||
|
.codegenEntryPoint(context,
|
||||||
|
entryPoint: entryLib, newEntryPoint: _newEntryPoint)));
|
||||||
|
}
|
||||||
|
}
|
|
@ -0,0 +1,100 @@
|
||||||
|
import 'package:analyzer/src/generated/element.dart';
|
||||||
|
import 'package:path/path.dart' as path;
|
||||||
|
|
||||||
|
import 'annotation_processor.dart';
|
||||||
|
|
||||||
|
class ImportTraversal {
|
||||||
|
final AnnotationMatcher _annotationMatcher;
|
||||||
|
|
||||||
|
ImportTraversal(this._annotationMatcher);
|
||||||
|
|
||||||
|
/// Reads Initializer annotations on this library and all its dependencies in
|
||||||
|
/// post-order.
|
||||||
|
void traverse(LibraryElement library, [Set<LibraryElement> seen]) {
|
||||||
|
if (seen == null) seen = new Set<LibraryElement>();
|
||||||
|
seen.add(library);
|
||||||
|
|
||||||
|
// Visit all our dependencies.
|
||||||
|
for (var importedLibrary in _sortedLibraryImports(library)) {
|
||||||
|
// Don't include anything from the sdk.
|
||||||
|
if (importedLibrary.isInSdk) continue;
|
||||||
|
if (seen.contains(importedLibrary)) continue;
|
||||||
|
traverse(importedLibrary, seen);
|
||||||
|
}
|
||||||
|
|
||||||
|
for (var clazz in _classesOfLibrary(library, seen)) {
|
||||||
|
var superClass = clazz.supertype;
|
||||||
|
while (superClass != null) {
|
||||||
|
if (_annotationMatcher.processAnnotations(superClass.element) &&
|
||||||
|
superClass.element.library != clazz.library) {
|
||||||
|
_logger.warning(
|
||||||
|
'We have detected a cycle in your import graph when running '
|
||||||
|
'initializers on ${clazz.name}. This means the super class '
|
||||||
|
'${superClass.name} has a dependency on this library '
|
||||||
|
'(possibly transitive).');
|
||||||
|
}
|
||||||
|
superClass = superClass.superclass;
|
||||||
|
}
|
||||||
|
_annotationMatcher.processAnnotations(clazz);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/// Retrieves all classes that are visible if you were to import [lib]. This
|
||||||
|
/// includes exported classes from other libraries.
|
||||||
|
List<ClassElement> _classesOfLibrary(
|
||||||
|
LibraryElement library, Set<LibraryElement> seen) {
|
||||||
|
var result = [];
|
||||||
|
result.addAll(library.units.expand((u) => u.types));
|
||||||
|
for (var export in library.exports) {
|
||||||
|
if (seen.contains(export.exportedLibrary)) continue;
|
||||||
|
var exported = _classesOfLibrary(export.exportedLibrary, seen);
|
||||||
|
_filter(exported, export.combinators);
|
||||||
|
result.addAll(exported);
|
||||||
|
}
|
||||||
|
result.sort((a, b) => a.name.compareTo(b.name));
|
||||||
|
return result;
|
||||||
|
}
|
||||||
|
|
||||||
|
/// Filters [elements] that come from an export, according to its show/hide
|
||||||
|
/// combinators. This modifies [elements] in place.
|
||||||
|
void _filter(List<Element> elements, List<NamespaceCombinator> combinators) {
|
||||||
|
for (var c in combinators) {
|
||||||
|
if (c is ShowElementCombinator) {
|
||||||
|
var show = c.shownNames.toSet();
|
||||||
|
elements.retainWhere((e) => show.contains(e.displayName));
|
||||||
|
} else if (c is HideElementCombinator) {
|
||||||
|
var hide = c.hiddenNames.toSet();
|
||||||
|
elements.removeWhere((e) => hide.contains(e.displayName));
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
Iterable<LibraryElement> _sortedLibraryImports(LibraryElement library) =>
|
||||||
|
(new List.from(library.imports)
|
||||||
|
..sort((ImportElement a, ImportElement b) {
|
||||||
|
// dart: imports don't have a uri
|
||||||
|
if (a.uri == null && b.uri != null) return -1;
|
||||||
|
if (b.uri == null && a.uri != null) return 1;
|
||||||
|
if (a.uri == null && b.uri == null) {
|
||||||
|
return a.importedLibrary.name.compareTo(b.importedLibrary.name);
|
||||||
|
}
|
||||||
|
|
||||||
|
// package: imports next
|
||||||
|
var aIsPackage = a.uri.startsWith('package:');
|
||||||
|
var bIsPackage = b.uri.startsWith('package:');
|
||||||
|
if (aIsPackage && !bIsPackage) {
|
||||||
|
return -1;
|
||||||
|
} else if (bIsPackage && !aIsPackage) {
|
||||||
|
return 1;
|
||||||
|
} else if (bIsPackage && aIsPackage) {
|
||||||
|
return a.uri.compareTo(b.uri);
|
||||||
|
}
|
||||||
|
|
||||||
|
// And finally compare based on the relative uri if both are file paths.
|
||||||
|
var aUri = path.relative(a.source.uri.path,
|
||||||
|
from: path.dirname(library.source.uri.path));
|
||||||
|
var bUri = path.relative(b.source.uri.path,
|
||||||
|
from: path.dirname(library.source.uri.path));
|
||||||
|
return aUri.compareTo(bUri);
|
||||||
|
})).map((import) => import.importedLibrary);
|
||||||
|
}
|
|
@ -0,0 +1,11 @@
|
||||||
|
// Copyright (c) 2013, the Dart project authors. Please see the AUTHORS file
|
||||||
|
// for details. All rights reserved. Use of this source code is governed by a
|
||||||
|
// BSD-style license that can be found in the LICENSE file.
|
||||||
|
library initialize.test.build.common;
|
||||||
|
|
||||||
|
// TODO(kegluneq): Remove this and use the actual Directive def'n.
|
||||||
|
// Simple mock of Directive.
|
||||||
|
class Directive {
|
||||||
|
final context;
|
||||||
|
const Directive({this.context});
|
||||||
|
}
|
|
@ -0,0 +1,3 @@
|
||||||
|
<html><head></head><body>
|
||||||
|
<script type="application/dart" src="index.dart"></script>
|
||||||
|
</body></html>
|
|
@ -0,0 +1,9 @@
|
||||||
|
library bar;
|
||||||
|
|
||||||
|
import 'package:angular2/src/core/annotations/annotations.dart';
|
||||||
|
|
||||||
|
@Directive(context: 'soup')
|
||||||
|
class Component {
|
||||||
|
final dynamic c;
|
||||||
|
Component([this.c = 'sandwich']);
|
||||||
|
}
|
|
@ -0,0 +1,14 @@
|
||||||
|
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 'index.dart' as i2;
|
||||||
|
|
||||||
|
main() {
|
||||||
|
reflector
|
||||||
|
..registerType(i0.Component, {
|
||||||
|
"factory": (dynamic c) => new i0.Component(c),
|
||||||
|
"parameters": const [const [dynamic]],
|
||||||
|
"annotations": const [const i1.Directive(context: 'soup')]
|
||||||
|
});
|
||||||
|
i2.main();
|
||||||
|
}
|
|
@ -0,0 +1,7 @@
|
||||||
|
library web_foo;
|
||||||
|
|
||||||
|
import 'bar.dart';
|
||||||
|
|
||||||
|
void main() {
|
||||||
|
new Component('Things');
|
||||||
|
}
|
|
@ -0,0 +1,3 @@
|
||||||
|
<html><head></head><body>
|
||||||
|
<script type="application/dart" src="index.bootstrap.dart"></script>
|
||||||
|
</body></html>
|
|
@ -0,0 +1,5 @@
|
||||||
|
library web_foo;
|
||||||
|
|
||||||
|
import 'package:angular2/src/core/annotations/annotations.dart';
|
||||||
|
|
||||||
|
void main() {}
|
|
@ -0,0 +1,10 @@
|
||||||
|
library bar;
|
||||||
|
|
||||||
|
import 'package:angular2/src/core/annotations/annotations.dart';
|
||||||
|
import 'foo.dart';
|
||||||
|
|
||||||
|
@Directive(context: const [MyContext])
|
||||||
|
class Component {
|
||||||
|
final MyContext c;
|
||||||
|
Component(this.c);
|
||||||
|
}
|
|
@ -0,0 +1,15 @@
|
||||||
|
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;
|
||||||
|
import 'index.dart' as i3;
|
||||||
|
|
||||||
|
main() {
|
||||||
|
reflector
|
||||||
|
..registerType(i0.Component, {
|
||||||
|
"factory": (i1.MyContext c) => new i0.Component(c),
|
||||||
|
"parameters": const [const [i1.MyContext]],
|
||||||
|
"annotations": const [const i2.Directive(context: const [i1.MyContext])]
|
||||||
|
});
|
||||||
|
i3.main();
|
||||||
|
}
|
|
@ -0,0 +1,6 @@
|
||||||
|
library foo;
|
||||||
|
|
||||||
|
class MyContext {
|
||||||
|
final String s;
|
||||||
|
const MyContext(this.s);
|
||||||
|
}
|
|
@ -0,0 +1,7 @@
|
||||||
|
library web_foo;
|
||||||
|
|
||||||
|
import 'bar.dart';
|
||||||
|
|
||||||
|
void main() {
|
||||||
|
new Component('Things');
|
||||||
|
}
|
|
@ -0,0 +1,8 @@
|
||||||
|
library bar;
|
||||||
|
|
||||||
|
import 'package:angular2/src/core/annotations/annotations.dart';
|
||||||
|
|
||||||
|
@Directive(context: 'soup')
|
||||||
|
class Component {
|
||||||
|
Component();
|
||||||
|
}
|
|
@ -0,0 +1,14 @@
|
||||||
|
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 'index.dart' as i2;
|
||||||
|
|
||||||
|
main() {
|
||||||
|
reflector
|
||||||
|
..registerType(i0.Component, {
|
||||||
|
"factory": () => new i0.Component(),
|
||||||
|
"parameters": const [const []],
|
||||||
|
"annotations": const [const i1.Directive(context: 'soup')]
|
||||||
|
});
|
||||||
|
i2.main();
|
||||||
|
}
|
|
@ -0,0 +1,7 @@
|
||||||
|
library web_foo;
|
||||||
|
|
||||||
|
import 'bar.dart';
|
||||||
|
|
||||||
|
void main() {
|
||||||
|
new Component('Things');
|
||||||
|
}
|
|
@ -0,0 +1,6 @@
|
||||||
|
library bar;
|
||||||
|
|
||||||
|
import 'package:angular2/src/core/annotations/annotations.dart';
|
||||||
|
|
||||||
|
@Directive(context: 'soup')
|
||||||
|
class Component {}
|
|
@ -0,0 +1,14 @@
|
||||||
|
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 'index.dart' as i2;
|
||||||
|
|
||||||
|
main() {
|
||||||
|
reflector
|
||||||
|
..registerType(i0.Component, {
|
||||||
|
"factory": () => new i0.Component(),
|
||||||
|
"parameters": const [const []],
|
||||||
|
"annotations": const [const i1.Directive(context: 'soup')]
|
||||||
|
});
|
||||||
|
i2.main();
|
||||||
|
}
|
|
@ -0,0 +1,7 @@
|
||||||
|
library web_foo;
|
||||||
|
|
||||||
|
import 'bar.dart';
|
||||||
|
|
||||||
|
void main() {
|
||||||
|
new Component();
|
||||||
|
}
|
|
@ -0,0 +1,126 @@
|
||||||
|
library angular2.test;
|
||||||
|
|
||||||
|
import 'dart:io';
|
||||||
|
import 'package:barback/barback.dart';
|
||||||
|
import 'package:angular2/transformer.dart';
|
||||||
|
import 'package:code_transformers/tests.dart';
|
||||||
|
import 'package:dart_style/dart_style.dart';
|
||||||
|
import 'package:unittest/unittest.dart';
|
||||||
|
import 'package:unittest/vm_config.dart';
|
||||||
|
|
||||||
|
import 'common.dart';
|
||||||
|
|
||||||
|
main() {
|
||||||
|
useVMConfiguration();
|
||||||
|
|
||||||
|
// TODO(kegluneq): Add a test for generating multiple annotations.
|
||||||
|
|
||||||
|
group('Annotation tests:', _runTests);
|
||||||
|
}
|
||||||
|
|
||||||
|
var formatter = new DartFormatter();
|
||||||
|
var transform = new AngularTransformer(new TransformerOptions(
|
||||||
|
'web/index.dart', 'web/index.bootstrap.dart', 'web/index.html'));
|
||||||
|
|
||||||
|
class TestConfig {
|
||||||
|
final String name;
|
||||||
|
final Map<String, String> assetPathToInputPath;
|
||||||
|
final Map<String, String> assetPathToExpectedOutputPath;
|
||||||
|
|
||||||
|
TestConfig(this.name,
|
||||||
|
{Map<String, String> inputs, Map<String, String> outputs})
|
||||||
|
: this.assetPathToInputPath = inputs,
|
||||||
|
this.assetPathToExpectedOutputPath = outputs;
|
||||||
|
}
|
||||||
|
|
||||||
|
void _runTests() {
|
||||||
|
// Each test has its own directory for inputs & an `expected` directory for
|
||||||
|
// expected outputs.
|
||||||
|
var tests = [
|
||||||
|
new TestConfig('Html entry point',
|
||||||
|
inputs: {
|
||||||
|
'a|web/index.html': 'common.html',
|
||||||
|
'a|web/index.dart': 'html_entry_point_files/index.dart',
|
||||||
|
'angular2|lib/src/core/annotations/annotations.dart': 'common.dart'
|
||||||
|
},
|
||||||
|
outputs: {
|
||||||
|
'a|web/index.html': 'html_entry_point_files/expected/index.html'
|
||||||
|
}),
|
||||||
|
new TestConfig('Simple',
|
||||||
|
inputs: {
|
||||||
|
'a|web/index.html': 'common.html',
|
||||||
|
'a|web/index.dart': 'simple_annotation_files/index.dart',
|
||||||
|
'a|web/bar.dart': 'simple_annotation_files/bar.dart',
|
||||||
|
'angular2|lib/src/core/annotations/annotations.dart': 'common.dart'
|
||||||
|
},
|
||||||
|
outputs: {
|
||||||
|
'a|web/index.bootstrap.dart':
|
||||||
|
'simple_annotation_files/expected/index.bootstrap.dart'
|
||||||
|
}),
|
||||||
|
new TestConfig('Two injected dependencies',
|
||||||
|
inputs: {
|
||||||
|
'a|web/index.html': 'common.html',
|
||||||
|
'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',
|
||||||
|
'angular2|lib/src/core/annotations/annotations.dart': 'common.dart'
|
||||||
|
},
|
||||||
|
outputs: {
|
||||||
|
'a|web/index.bootstrap.dart':
|
||||||
|
'two_deps_files/expected/index.bootstrap.dart'
|
||||||
|
}),
|
||||||
|
new TestConfig('List of types',
|
||||||
|
inputs: {
|
||||||
|
'a|web/index.html': 'common.html',
|
||||||
|
'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',
|
||||||
|
'angular2|lib/src/core/annotations/annotations.dart': 'common.dart'
|
||||||
|
},
|
||||||
|
outputs: {
|
||||||
|
'a|web/index.bootstrap.dart':
|
||||||
|
'list_of_types_files/expected/index.bootstrap.dart'
|
||||||
|
}),
|
||||||
|
new TestConfig('Component ctor with default value',
|
||||||
|
inputs: {
|
||||||
|
'a|web/index.html': 'common.html',
|
||||||
|
'a|web/index.dart': 'ctor_with_default_value_files/index.dart',
|
||||||
|
'a|web/bar.dart': 'ctor_with_default_value_files/bar.dart',
|
||||||
|
'angular2|lib/src/core/annotations/annotations.dart': 'common.dart'
|
||||||
|
},
|
||||||
|
outputs: {
|
||||||
|
'a|web/index.bootstrap.dart':
|
||||||
|
'ctor_with_default_value_files/expected/index.bootstrap.dart'
|
||||||
|
}),
|
||||||
|
new TestConfig('Component with synthetic Constructor',
|
||||||
|
inputs: {
|
||||||
|
'a|web/index.html': 'common.html',
|
||||||
|
'a|web/index.dart': 'synthetic_ctor_files/index.dart',
|
||||||
|
'a|web/bar.dart': 'synthetic_ctor_files/bar.dart',
|
||||||
|
'angular2|lib/src/core/annotations/annotations.dart': 'common.dart'
|
||||||
|
},
|
||||||
|
outputs: {
|
||||||
|
'a|web/index.bootstrap.dart':
|
||||||
|
'synthetic_ctor_files/expected/index.bootstrap.dart'
|
||||||
|
})
|
||||||
|
];
|
||||||
|
|
||||||
|
var cache = {};
|
||||||
|
|
||||||
|
for (var config in tests) {
|
||||||
|
// Read in input & output files.
|
||||||
|
config.assetPathToInputPath.forEach((key, value) {
|
||||||
|
config.assetPathToInputPath[key] =
|
||||||
|
cache.putIfAbsent(value, () => new File(value).readAsStringSync());
|
||||||
|
});
|
||||||
|
config.assetPathToExpectedOutputPath.forEach((key, value) {
|
||||||
|
config.assetPathToExpectedOutputPath[key] = cache.putIfAbsent(value, () {
|
||||||
|
var code = new File(value).readAsStringSync();
|
||||||
|
return value.endsWith('dart') ? formatter.format(code) : code;
|
||||||
|
});
|
||||||
|
});
|
||||||
|
testPhases(config.name, [
|
||||||
|
[transform]
|
||||||
|
], config.assetPathToInputPath, config.assetPathToExpectedOutputPath, []);
|
||||||
|
}
|
||||||
|
}
|
|
@ -0,0 +1,13 @@
|
||||||
|
library bar;
|
||||||
|
|
||||||
|
import 'package:angular2/src/core/annotations/annotations.dart';
|
||||||
|
import 'foo.dart';
|
||||||
|
|
||||||
|
@Directive(context: const MyContext(contextString))
|
||||||
|
class Component2 {
|
||||||
|
final MyContext c;
|
||||||
|
final String generatedValue;
|
||||||
|
Component2(this.c, String inValue) {
|
||||||
|
generatedValue = 'generated ' + inValue;
|
||||||
|
}
|
||||||
|
}
|
|
@ -0,0 +1,18 @@
|
||||||
|
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;
|
||||||
|
import 'index.dart' as i3;
|
||||||
|
|
||||||
|
main() {
|
||||||
|
reflector
|
||||||
|
..registerType(i0.Component2, {
|
||||||
|
"factory":
|
||||||
|
(i1.MyContext c, String inValue) => new i0.Component2(c, inValue),
|
||||||
|
"parameters": const [const [i1.MyContext, String]],
|
||||||
|
"annotations": const [
|
||||||
|
const i2.Directive(context: const i1.MyContext(i1.contextString))
|
||||||
|
]
|
||||||
|
});
|
||||||
|
i3.main();
|
||||||
|
}
|
|
@ -0,0 +1,8 @@
|
||||||
|
library foo;
|
||||||
|
|
||||||
|
const contextString = 'soup';
|
||||||
|
|
||||||
|
class MyContext {
|
||||||
|
final String s;
|
||||||
|
const MyContext(this.s);
|
||||||
|
}
|
|
@ -0,0 +1,7 @@
|
||||||
|
library web_foo;
|
||||||
|
|
||||||
|
import 'bar.dart';
|
||||||
|
|
||||||
|
void main() {
|
||||||
|
new Component('Things');
|
||||||
|
}
|
|
@ -0,0 +1,3 @@
|
||||||
|
library angular2.transformer_dart;
|
||||||
|
|
||||||
|
export 'src/transform/transformer.dart';
|
|
@ -8,6 +8,9 @@ dependencies:
|
||||||
dev_dependencies:
|
dev_dependencies:
|
||||||
guinness: ">=0.1.16 <0.2.0"
|
guinness: ">=0.1.16 <0.2.0"
|
||||||
transformers:
|
transformers:
|
||||||
|
- angular2:
|
||||||
|
entry_point: web/src/hello_world/index.dart
|
||||||
|
html_entry_point: web/src/hello_world/index.html
|
||||||
- $dart2js:
|
- $dart2js:
|
||||||
minify: true
|
minify: true
|
||||||
commandLineOptions: [--trust-type-annotations, --trust-primitives, --dump-info]
|
commandLineOptions: [--trust-type-annotations, --trust-primitives, --dump-info]
|
||||||
|
|
Loading…
Reference in New Issue