feat(dart/transformer:: Initial commit of ctor stubs & annotation register

Closes #646

Closes #496

Closes #498
This commit is contained in:
Tim Blasi 2015-02-17 08:38:54 -08:00 committed by Misko Hevery
parent cbc76faf11
commit 6e90cacaf4
33 changed files with 1098 additions and 0 deletions

View File

@ -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',

View File

@ -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"

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

@ -0,0 +1,3 @@
<html><head></head><body>
<script type="application/dart" src="index.dart"></script>
</body></html>

View File

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

View File

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

View File

@ -0,0 +1,7 @@
library web_foo;
import 'bar.dart';
void main() {
new Component('Things');
}

View File

@ -0,0 +1,3 @@
<html><head></head><body>
<script type="application/dart" src="index.bootstrap.dart"></script>
</body></html>

View File

@ -0,0 +1,5 @@
library web_foo;
import 'package:angular2/src/core/annotations/annotations.dart';
void main() {}

View File

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

View File

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

View File

@ -0,0 +1,6 @@
library foo;
class MyContext {
final String s;
const MyContext(this.s);
}

View File

@ -0,0 +1,7 @@
library web_foo;
import 'bar.dart';
void main() {
new Component('Things');
}

View File

@ -0,0 +1,8 @@
library bar;
import 'package:angular2/src/core/annotations/annotations.dart';
@Directive(context: 'soup')
class Component {
Component();
}

View File

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

View File

@ -0,0 +1,7 @@
library web_foo;
import 'bar.dart';
void main() {
new Component('Things');
}

View File

@ -0,0 +1,6 @@
library bar;
import 'package:angular2/src/core/annotations/annotations.dart';
@Directive(context: 'soup')
class Component {}

View File

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

View File

@ -0,0 +1,7 @@
library web_foo;
import 'bar.dart';
void main() {
new Component();
}

View File

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

View File

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

View File

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

View File

@ -0,0 +1,8 @@
library foo;
const contextString = 'soup';
class MyContext {
final String s;
const MyContext(this.s);
}

View File

@ -0,0 +1,7 @@
library web_foo;
import 'bar.dart';
void main() {
new Component('Things');
}

View File

@ -0,0 +1,3 @@
library angular2.transformer_dart;
export 'src/transform/transformer.dart';

View File

@ -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]