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
|
||||
gulp.task('build.dart', function(done) {
|
||||
runSequence(
|
||||
['build/deps.js.dart2js', 'build/transpile.dart', 'build/html.dart'],
|
||||
'tests/transform.dart',
|
||||
'build/pubspec.dart',
|
||||
'build/multicopy.dart',
|
||||
'build/pubbuild.dart',
|
||||
|
|
|
@ -9,6 +9,11 @@ homepage: <%= packageJson.homepage %>
|
|||
environment:
|
||||
sdk: '>=1.4.0'
|
||||
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'
|
||||
dev_dependencies:
|
||||
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:
|
||||
guinness: ">=0.1.16 <0.2.0"
|
||||
transformers:
|
||||
- angular2:
|
||||
entry_point: web/src/hello_world/index.dart
|
||||
html_entry_point: web/src/hello_world/index.html
|
||||
- $dart2js:
|
||||
minify: true
|
||||
commandLineOptions: [--trust-type-annotations, --trust-primitives, --dump-info]
|
||||
|
|
Loading…
Reference in New Issue