Repository navigation
Expand file tree
/
Copy pathcodegen.dart
More file actions
390 lines (345 loc) · 12.3 KB
/
Copy pathcodegen.dart
File metadata and controls
390 lines (345 loc) · 12.3 KB
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
223
224
225
226
227
228
229
230
231
232
233
234
235
236
237
238
239
240
241
242
243
244
245
246
247
248
249
250
251
252
253
254
255
256
257
258
259
260
261
262
263
264
265
266
267
268
269
270
271
272
273
274
275
276
277
278
279
280
281
282
283
284
285
286
287
288
289
290
291
292
293
294
295
296
297
298
299
300
301
302
303
304
305
306
307
308
309
310
311
312
313
314
315
316
317
318
319
320
321
322
323
324
325
326
327
328
329
330
331
332
333
334
335
336
337
338
339
340
341
342
343
344
345
346
347
348
349
350
351
352
353
354
355
356
357
358
359
360
361
362
363
364
365
366
367
368
369
370
371
372
373
374
375
376
377
378
379
380
381
382
383
384
385
386
387
388
389
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;
}
}