Chromium Code Reviews| OLD | NEW |
|---|---|
| (Empty) | |
| 1 library angular2.transformer; | |
| 2 | |
| 3 import 'package:analyzer/src/generated/ast.dart'; | |
| 4 import 'package:analyzer/src/generated/element.dart'; | |
| 5 import 'package:analyzer/src/generated/java_core.dart'; | |
| 6 import 'package:barback/barback.dart' show AssetId, TransformLogger; | |
| 7 import 'package:dart_style/dart_style.dart'; | |
| 8 import 'package:path/path.dart' as path; | |
| 9 | |
| 10 import 'annotation_processor.dart'; | |
| 11 | |
| 12 /// Base class that maintains codegen state. | |
| 13 class Context { | |
| 14 final TransformLogger _logger; | |
| 15 /// Maps libraries to the import prefixes we will use in the newly | |
| 16 /// generated code. | |
| 17 final Map<LibraryElement, String> _libraryPrefixes; | |
| 18 | |
| 19 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.
| |
| 20 DirectiveRegistry get directiveRegistry => _directiveRegistry; | |
| 21 | |
| 22 Context({TransformLogger logger}) | |
| 23 : _logger = logger, | |
| 24 _libraryPrefixes = {} { | |
| 25 _directiveRegistry = new _DirectiveRegistryImpl(this); | |
| 26 } | |
| 27 | |
| 28 void error(String errorString) { | |
| 29 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.
| |
| 30 _logger.error(errorString); | |
| 31 } else { | |
| 32 throw new Error(errorString); | |
| 33 } | |
| 34 } | |
| 35 | |
| 36 /// If elements in [lib] should be prefixed in our generated code, returns | |
| 37 /// the appropriate prefix followed by a `.`. Future items from the same | |
| 38 /// library will use the same prefix. | |
| 39 /// If [lib] does not need a prefix, returns the empty string. | |
| 40 String _getPrefixDot(LibraryElement lib) { | |
| 41 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.
| |
| 42 ? _libraryPrefixes.putIfAbsent(lib, () => 'i${_libraryPrefixes.length}') | |
| 43 : null; | |
| 44 return prefix == null ? '' : '${prefix}.'; | |
| 45 } | |
| 46 } | |
| 47 | |
| 48 abstract class DirectiveRegistry { | |
|
jakemac
2015/02/18 16:18:50
A comment here would be good
tjblasi
2015/02/18 21:18:25
Done.
| |
| 49 // Adds [entry] to the `registerType` calls which will be generated. | |
| 50 void register(AnnotationMatch entry); | |
| 51 } | |
| 52 | |
| 53 const _reflectorImport = | |
|
jakemac
2015/02/18 16:18:50
You might just want to use ''' here
tjblasi
2015/02/18 21:18:25
Done.
| |
| 54 'import \'package:angular2/src/reflection/reflection.dart\' ' | |
| 55 'show reflector;'; | |
| 56 | |
| 57 /// Default implementation to map from [LibraryElement] to [AssetId]. This | |
| 58 /// assumes that [el.source] has a getter called [assetId]. | |
| 59 AssetId _assetIdFromLibraryElement(LibraryElement el) { | |
| 60 return (el.source as dynamic).assetId; | |
| 61 } | |
| 62 | |
| 63 String codegenEntryPoint(Context context, | |
| 64 {LibraryElement entryPoint, AssetId newEntryPoint}) { | |
| 65 // This must be called prior to [codegenImports] or the entry point | |
| 66 // library will not be imported. | |
| 67 var entryPointPrefix = context._getPrefixDot(entryPoint); | |
| 68 // TODO(jakemac): copyright and library declaration | |
| 69 var outBuffer = new StringBuffer(_reflectorImport); | |
| 70 _codegenImports(context, newEntryPoint, outBuffer); | |
| 71 outBuffer | |
| 72 ..write('main() {') | |
| 73 ..write(context.directiveRegistry.toString()) | |
| 74 ..write('${entryPointPrefix}main();}'); | |
| 75 | |
| 76 return new DartFormatter().format(outBuffer.toString()); | |
| 77 } | |
| 78 | |
| 79 String _codegenImports( | |
| 80 Context context, AssetId newEntryPoint, StringBuffer buffer) { | |
| 81 context._libraryPrefixes.forEach((lib, prefix) { | |
| 82 buffer | |
| 83 ..write(_codegenImport( | |
| 84 context, _assetIdFromLibraryElement(lib), newEntryPoint)) | |
| 85 ..writeln('as ${prefix};'); | |
| 86 }); | |
| 87 } | |
| 88 | |
| 89 _codegenImport(Context context, AssetId libraryId, AssetId entryPoint) { | |
| 90 if (libraryId.path.startsWith('lib/')) { | |
| 91 var packagePath = libraryId.path.replaceFirst('lib/', ''); | |
| 92 return "import 'package:${libraryId.package}/${packagePath}'"; | |
| 93 } else if (libraryId.package != entryPoint.package) { | |
| 94 context._error("Can't import `${libraryId}` from `${entryPoint}`"); | |
| 95 } else if (path.url.split(libraryId.path)[0] == | |
| 96 path.url.split(entryPoint.path)[0]) { | |
| 97 var relativePath = | |
| 98 path.relative(libraryId.path, from: path.dirname(entryPoint.path)); | |
| 99 return "import '${relativePath}'"; | |
| 100 } else { | |
| 101 context._error("Can't import `${libraryId}` from `${entryPoint}`"); | |
| 102 } | |
| 103 } | |
| 104 | |
| 105 class _DirectiveRegistryImpl implements DirectiveRegistry { | |
| 106 final Context _context; | |
| 107 final StringBuffer _buffer = new StringBuffer(); | |
| 108 | |
| 109 _DirectiveRegistryImpl(this._context); | |
| 110 | |
| 111 @override | |
| 112 String toString() { | |
| 113 return _buffer.isEmpty ? '' : 'reflector${_buffer};'; | |
| 114 } | |
| 115 | |
| 116 // Adds [entry] to the `registerType` calls which will be generated. | |
| 117 void register(AnnotationMatch entry) { | |
| 118 var element = entry.element; | |
| 119 var annotation = entry.annotation; | |
| 120 | |
| 121 if (annotation.element is! ConstructorElement) { | |
| 122 _context._error('Unsupported annotation type. ' | |
| 123 'Only constructors are supported as Directives.'); | |
| 124 return; | |
| 125 } | |
| 126 if (element is! ClassElement) { | |
| 127 _context._error('Directives can only be applied to classes.'); | |
| 128 return; | |
| 129 } | |
| 130 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
| |
| 131 _context._error('Unsupported annotation type. ' | |
| 132 'Only class declarations are supported as Directives.'); | |
| 133 return; | |
| 134 } | |
| 135 final ConstructorElement ctor = element.unnamedConstructor; | |
| 136 if (ctor == null) { | |
| 137 _context._error('No default constructor found for ${element.name}'); | |
| 138 return; | |
| 139 } | |
| 140 | |
| 141 _buffer.writeln('..registerType(${_codegenClassTypeString(element)}, {' | |
| 142 '"factory": ${_codegenFactoryProp(ctor)},' | |
| 143 '"parameters": ${_codegenParametersProp(ctor)},' | |
| 144 '"annotations": ${_codegenAnnotationsProp(element)}' | |
| 145 '})'); | |
| 146 } | |
| 147 | |
| 148 String _codegenClassTypeString(ClassElement el) { | |
| 149 return '${_context._getPrefixDot(el.library)}${el.name}'; | |
| 150 } | |
| 151 | |
| 152 /// Creates the 'annotations' property for the Angular2 [registerType] call | |
| 153 /// for [el]. | |
| 154 String _codegenAnnotationsProp(ClassElement el) { | |
| 155 var writer = new PrintStringWriter(); | |
| 156 var visitor = new _AnnotationsTransformVisitor(writer, _context); | |
| 157 el.node.accept(visitor); | |
| 158 return writer.toString(); | |
| 159 } | |
| 160 | |
| 161 /// Creates the 'factory' property for the Angular2 [registerType] call | |
| 162 /// for [ctor]. | |
| 163 String _codegenFactoryProp(ConstructorElement ctor) { | |
| 164 if (ctor.node == null) { | |
| 165 // This occurs when the class does not declare a constructor. | |
| 166 var prefix = _context._getPrefixDot(ctor.type.element.library); | |
| 167 return '() => new ${prefix}${ctor.enclosingElement.displayName}()'; | |
| 168 } else { | |
| 169 var writer = new PrintStringWriter(); | |
| 170 var visitor = new _FactoryTransformVisitor(writer, _context); | |
| 171 ctor.node.accept(visitor); | |
| 172 return writer.toString(); | |
| 173 } | |
| 174 } | |
| 175 | |
| 176 /// Creates the 'parameters' property for the Angular2 [registerType] call | |
| 177 /// for [ctor]. | |
| 178 String _codegenParametersProp(ConstructorElement ctor) { | |
| 179 if (ctor.node == null) { | |
| 180 // This occurs when the class does not declare a constructor. | |
| 181 return 'const [const []]'; | |
| 182 } else { | |
| 183 var writer = new PrintStringWriter(); | |
| 184 var visitor = new _ParameterTransformVisitor(writer, _context); | |
| 185 ctor.node.accept(visitor); | |
| 186 return writer.toString(); | |
| 187 } | |
| 188 } | |
| 189 } | |
| 190 | |
| 191 /// Visitor providing common methods for concrete implementations. | |
| 192 abstract class _TransformVisitor extends ToSourceVisitor { | |
| 193 final Context _context; | |
| 194 final PrintWriter _writer; | |
| 195 | |
| 196 _TransformVisitor(PrintWriter writer, this._context) | |
| 197 : this._writer = writer, | |
| 198 super(writer); | |
| 199 | |
| 200 /// Safely visit the given node. | |
| 201 /// @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
| |
| 202 void _visitNode(AstNode node) { | |
| 203 if (node != null) { | |
| 204 node.accept(this); | |
| 205 } | |
| 206 } | |
| 207 | |
| 208 /** | |
|
jakemac
2015/02/17 23:46:46
Usually we just do the /// style comments, not sur
tjblasi
2015/02/18 21:18:25
Done.
| |
| 209 * Safely visit the given node, printing the prefix before the node if it is n on-`null`. | |
| 210 * | |
| 211 * @param prefix the prefix to be printed if there is a node to visit | |
| 212 * @param node the node to be visited | |
| 213 */ | |
| 214 void _visitNodeWithPrefix(String prefix, AstNode node) { | |
| 215 if (node != null) { | |
| 216 _writer.print(prefix); | |
| 217 node.accept(this); | |
| 218 } | |
| 219 } | |
| 220 | |
| 221 /** | |
| 222 * Safely visit the given node, printing the suffix after the node if it is no n-`null`. | |
| 223 * | |
| 224 * @param suffix the suffix to be printed if there is a node to visit | |
| 225 * @param node the node to be visited | |
| 226 */ | |
| 227 void _visitNodeWithSuffix(AstNode node, String suffix) { | |
| 228 if (node != null) { | |
| 229 node.accept(this); | |
| 230 _writer.print(suffix); | |
| 231 } | |
| 232 } | |
| 233 | |
| 234 @override | |
| 235 Object visitSimpleIdentifier(SimpleIdentifier node) { | |
| 236 // Make sure the identifier is prefixed if necessary. | |
| 237 if (node.bestElement is ClassElementImpl || | |
| 238 node.bestElement is PropertyAccessorElement) { | |
| 239 _writer | |
| 240 ..print(_context._getPrefixDot(node.bestElement.library)) | |
| 241 ..print(node.token.lexeme); | |
| 242 } else { | |
| 243 return super.visitSimpleIdentifier(node); | |
| 244 } | |
| 245 return null; | |
| 246 } | |
| 247 } | |
| 248 | |
| 249 /// SourceVisitor designed to accept [ConstructorDeclaration] nodes. | |
| 250 class _CtorTransformVisitor extends _TransformVisitor { | |
| 251 bool _withParameterTypes = true; | |
| 252 bool _withParameterNames = true; | |
| 253 | |
| 254 _CtorTransformVisitor(PrintWriter writer, Context _context) | |
| 255 : super(writer, _context); | |
| 256 | |
| 257 /// If [_withParameterTypes] is true, this method outputs [node]'s type | |
| 258 /// (appropriately prefixed based on [_libraryPrefixes]. If | |
| 259 /// [_withParameterNames] is true, this method outputs [node]'s identifier. | |
| 260 Object _visitNormalFormalParameter(NormalFormalParameter node) { | |
| 261 if (_withParameterTypes) { | |
| 262 var paramType = node.element.type; | |
| 263 var prefix = _context._getPrefixDot(paramType.element.library); | |
| 264 _writer.print('${prefix}${paramType.displayName}'); | |
| 265 if (_withParameterNames) { | |
| 266 _visitNodeWithPrefix(' ', node.identifier); | |
| 267 } | |
| 268 } else if (_withParameterNames) { | |
| 269 _visitNode(node.identifier); | |
| 270 } | |
| 271 return null; | |
| 272 } | |
| 273 | |
| 274 @override | |
| 275 Object visitSimpleFormalParameter(SimpleFormalParameter node) { | |
| 276 return _visitNormalFormalParameter(node); | |
| 277 } | |
| 278 | |
| 279 @override | |
| 280 Object visitFieldFormalParameter(FieldFormalParameter node) { | |
| 281 if (node.parameters != null) { | |
| 282 _context.error('Parameters in ctor not supported ' | |
| 283 '(${super.visitFormalParameterList(node)}'); | |
| 284 } | |
| 285 return _visitNormalFormalParameter(node); | |
| 286 } | |
| 287 | |
| 288 @override | |
| 289 Object visitDefaultFormalParameter(DefaultFormalParameter node) { | |
| 290 _visitNode(node.parameter); | |
| 291 return null; | |
| 292 } | |
| 293 | |
| 294 @override | |
| 295 /// Overridden to avoid outputting grouping operators for default parameters. | |
| 296 Object visitFormalParameterList(FormalParameterList node) { | |
| 297 _writer.print('('); | |
| 298 NodeList<FormalParameter> parameters = node.parameters; | |
| 299 int size = parameters.length; | |
| 300 for (int i = 0; i < size; i++) { | |
| 301 if (i > 0) { | |
| 302 _writer.print(', '); | |
| 303 } | |
| 304 parameters[i].accept(this); | |
| 305 } | |
| 306 _writer.print(')'); | |
| 307 return null; | |
| 308 } | |
| 309 } | |
| 310 | |
| 311 /// ToSourceVisitor designed to print 'parameters' values for Angular2's | |
| 312 /// [registerType] calls. | |
| 313 class _ParameterTransformVisitor extends _CtorTransformVisitor { | |
| 314 _ParameterTransformVisitor(PrintWriter writer, Context _context) | |
| 315 : super(writer, _context); | |
| 316 | |
| 317 @override | |
| 318 Object visitConstructorDeclaration(ConstructorDeclaration node) { | |
| 319 _withParameterNames = false; | |
| 320 _withParameterTypes = true; | |
| 321 _writer.print('const [const ['); | |
| 322 _visitNode(node.parameters); | |
| 323 _writer.print(']]'); | |
| 324 return null; | |
| 325 } | |
| 326 | |
| 327 @override | |
| 328 Object visitFormalParameterList(FormalParameterList node) { | |
| 329 NodeList<FormalParameter> parameters = node.parameters; | |
| 330 int size = parameters.length; | |
| 331 for (int i = 0; i < size; i++) { | |
| 332 if (i > 0) { | |
| 333 _writer.print(', '); | |
| 334 } | |
| 335 parameters[i].accept(this); | |
| 336 } | |
| 337 return null; | |
| 338 } | |
| 339 } | |
| 340 | |
| 341 /// ToSourceVisitor designed to print 'factory' values for Angular2's | |
| 342 /// [registerType] calls. | |
| 343 class _FactoryTransformVisitor extends _CtorTransformVisitor { | |
| 344 _FactoryTransformVisitor(PrintWriter writer, Context _context) | |
| 345 : super(writer, _context); | |
| 346 | |
| 347 @override | |
| 348 Object visitConstructorDeclaration(ConstructorDeclaration node) { | |
| 349 _withParameterNames = true; | |
| 350 _withParameterTypes = true; | |
| 351 _visitNode(node.parameters); | |
| 352 _writer.print(' => new '); | |
| 353 _visitNode(node.returnType); | |
| 354 _visitNodeWithPrefix(".", node.name); | |
| 355 _withParameterTypes = false; | |
| 356 _visitNode(node.parameters); | |
| 357 return null; | |
| 358 } | |
| 359 } | |
| 360 | |
| 361 /// ToSourceVisitor designed to print a [ClassDeclaration] node as a | |
| 362 /// 'annotations' value for Angular2's [registerType] calls. | |
| 363 class _AnnotationsTransformVisitor extends _TransformVisitor { | |
| 364 _AnnotationsTransformVisitor(PrintWriter writer, Context _context) | |
| 365 : super(writer, _context); | |
| 366 | |
| 367 @override | |
| 368 Object visitClassDeclaration(ClassDeclaration node) { | |
| 369 _writer.print('const ['); | |
| 370 var size = node.metadata.length; | |
| 371 for (var i = 0; i < size; ++i) { | |
| 372 if (i > 0) { | |
| 373 _writer.print(', '); | |
| 374 } | |
| 375 node.metadata[i].accept(this); | |
| 376 } | |
| 377 _writer.print(']'); | |
| 378 return null; | |
| 379 } | |
| 380 | |
| 381 @override | |
| 382 Object visitAnnotation(Annotation node) { | |
| 383 _writer.print('const '); | |
| 384 _visitNode(node.name); | |
| 385 // TODO(tjblasi): Do we need to handle named constructors for annotations? | |
| 386 // _visitNodeWithPrefix(".", node.constructorName); | |
| 387 _visitNode(node.arguments); | |
| 388 return null; | |
| 389 } | |
| 390 } | |
| OLD | NEW |