Chromium Code Reviews
chromiumcodereview-hr@appspot.gserviceaccount.com (chromiumcodereview-hr) | Please choose your nickname with Settings | Help | Chromium Project | Gerrit Changes | Sign out
(1484)

Unified Diff: modules/angular2/src/transform/codegen.dart

Issue 927373004: Initial commit of Dart transformer to generate constructor stubs, see https://github.com/angular/an… (Closed) Base URL: https://github.com/kegluneq/angular.git@master
Patch Set: Created 5 years, 10 months ago
Use n/p to move between diff chunks; N/P to move between comments. Draft comments are only viewable by you.
Jump to:
View side-by-side diff with in-line comments
Download patch
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;
+ }
+}

Powered by Google App Engine
This is Rietveld 408576698