Chromium Code Reviews| Index: modules/angular2/src/transform/codegen.dart |
| diff --git a/modules/angular2/src/transform/codegen.dart b/modules/angular2/src/transform/codegen.dart |
| new file mode 100644 |
| index 0000000000000000000000000000000000000000..399cc277017da693ad771bd0e2966ca75028941a |
| --- /dev/null |
| +++ b/modules/angular2/src/transform/codegen.dart |
| @@ -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; |
|
jakemac
2015/02/18 16:18:50
might be nice to have a comment here
tjblasi
2015/02/18 21:18:25
Done.
|
| + DirectiveRegistry get directiveRegistry => _directiveRegistry; |
| + |
| + Context({TransformLogger logger}) |
| + : _logger = logger, |
| + _libraryPrefixes = {} { |
| + _directiveRegistry = new _DirectiveRegistryImpl(this); |
| + } |
| + |
| + void error(String errorString) { |
| + if (_logger != null) { |
|
jakemac
2015/02/17 23:46:45
nit, you could rewrite this:
if (_logger == null)
tjblasi
2015/02/18 21:18:25
Done.
|
| + _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 |
|
jakemac
2015/02/18 16:18:50
I might reorganize this a little bit to return ear
tjblasi
2015/02/18 21:18:25
Done.
|
| + ? _libraryPrefixes.putIfAbsent(lib, () => 'i${_libraryPrefixes.length}') |
| + : null; |
| + return prefix == null ? '' : '${prefix}.'; |
| + } |
| +} |
| + |
| +abstract class DirectiveRegistry { |
|
jakemac
2015/02/18 16:18:50
A comment here would be good
tjblasi
2015/02/18 21:18:25
Done.
|
| + // Adds [entry] to the `registerType` calls which will be generated. |
| + void register(AnnotationMatch entry); |
| +} |
| + |
| +const _reflectorImport = |
|
jakemac
2015/02/18 16:18:50
You might just want to use ''' here
tjblasi
2015/02/18 21:18:25
Done.
|
| + '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) { |
|
jakemac
2015/02/17 23:46:46
Just fyi, any call to `.node` is potentially prett
tjblasi
2015/02/18 21:18:25
Added an issue [https://github.com/kegluneq/angula
|
| + _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 |
|
jakemac
2015/02/17 23:46:45
I haven't really seen us use these JavaDoc style c
tjblasi
2015/02/18 21:18:25
Ah, I stole this code from analyzer, which was gen
|
| + void _visitNode(AstNode node) { |
| + if (node != null) { |
| + node.accept(this); |
| + } |
| + } |
| + |
| + /** |
|
jakemac
2015/02/17 23:46:46
Usually we just do the /// style comments, not sur
tjblasi
2015/02/18 21:18:25
Done.
|
| + * 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; |
| + } |
| +} |