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

Side by Side Diff: third_party/pkg/angular/lib/tools/transformer/metadata_extractor.dart

Issue 257423008: Update all Angular libs (run update_all.sh). (Closed) Base URL: https://dart.googlecode.com/svn/branches/bleeding_edge/dart
Patch Set: Created 6 years, 8 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 unified diff | Download patch | Annotate | Revision Log
OLDNEW
(Empty)
1 library angular.metadata_extractor;
2
3 import 'package:analyzer/src/generated/ast.dart';
4 import 'package:analyzer/src/generated/element.dart';
5 import 'package:analyzer/src/generated/parser.dart' show ResolutionCopier;
6 import 'package:analyzer/src/generated/scanner.dart';
7 import 'package:analyzer/src/generated/utilities_dart.dart' show ParameterKind;
8 import 'package:barback/barback.dart';
9 import 'package:code_transformers/resolver.dart';
10
11 class AnnotatedType {
12 final ClassElement type;
13 Iterable<Annotation> annotations;
14
15 AnnotatedType(this.type);
16
17 /**
18 * Finds all the libraries referenced by the annotations
19 */
20 Iterable<LibraryElement> get referencedLibraries {
21 var libs = new Set();
22 libs.add(type.library);
23
24 var libCollector = new _LibraryCollector();
25 for (var annotation in annotations) {
26 annotation.accept(libCollector);
27 }
28 libs.addAll(libCollector.libraries);
29
30 return libs;
31 }
32
33 void writeClassAnnotations(StringBuffer sink, TransformLogger logger,
34 Resolver resolver, Map<LibraryElement, String> prefixes) {
35 sink.write(' ${prefixes[type.library]}${type.name}: const [\n');
36 var writer = new _AnnotationWriter(sink, prefixes);
37 for (var annotation in annotations) {
38 sink.write(' ');
39 if (writer.writeAnnotation(annotation)) {
40 sink.write(',\n');
41 } else {
42 sink.write('null,\n');
43 logger.warning('Unable to serialize annotation $annotation.',
44 asset: resolver.getSourceAssetId(annotation.parent.element),
45 span: resolver.getSourceSpan(annotation.parent.element));
46 }
47 }
48 sink.write(' ],\n');
49 }
50 }
51
52 /**
53 * Helper which finds all libraries referenced within the provided AST.
54 */
55 class _LibraryCollector extends GeneralizingAstVisitor {
56 final Set<LibraryElement> libraries = new Set<LibraryElement>();
57 void visitSimpleIdentifier(SimpleIdentifier s) {
58 var element = s.bestElement;
59 if (element != null) {
60 libraries.add(element.library);
61 }
62 }
63 }
64
65 /**
66 * Helper class which writes annotations out to the buffer.
67 * This does not support every syntax possible, but will return false when
68 * the annotation cannot be serialized.
69 */
70 class _AnnotationWriter {
71 final StringBuffer sink;
72 final Map<LibraryElement, String> prefixes;
73
74 _AnnotationWriter(this.sink, this.prefixes);
75
76 /**
77 * Returns true if the annotation was successfully serialized.
78 * If the annotation could not be written then the buffer is returned to its
79 * original state.
80 */
81 bool writeAnnotation(Annotation annotation) {
82 // Record the current location in the buffer and if writing fails then
83 // back up the buffer to where we started.
84 var len = sink.length;
85 if (!_writeAnnotation(annotation)) {
86 var str = sink.toString();
87 sink.clear();
88 sink.write(str.substring(0, len));
89 return false;
90 }
91 return true;
92 }
93
94 bool _writeAnnotation(Annotation annotation) {
95 var element = annotation.element;
96 if (element is ConstructorElement) {
97 sink.write('const ${prefixes[element.library]}'
98 '${element.enclosingElement.name}');
99 // Named constructors
100 if (!element.name.isEmpty) {
101 sink.write('.${element.name}');
102 }
103 sink.write('(');
104 if (!_writeArguments(annotation)) return false;
105 sink.write(')');
106 return true;
107 } else if (element is PropertyAccessorElement) {
108 sink.write('${prefixes[element.library]}${element.name}');
109 return true;
110 }
111
112 return false;
113 }
114
115 /** Writes the arguments for a type constructor. */
116 bool _writeArguments(Annotation annotation) {
117 var args = annotation.arguments;
118 var index = 0;
119 for (var arg in args.arguments) {
120 if (arg is NamedExpression) {
121 sink.write('${arg.name.label.name}: ');
122 if (!_writeExpression(arg.expression)) return false;
123 } else {
124 if (!_writeExpression(arg)) return false;
125 }
126 if (++index < args.arguments.length) {
127 sink.write(', ');
128 }
129 }
130 return true;
131 }
132
133 /** Writes an expression. */
134 bool _writeExpression(Expression expression) {
135 if (expression is StringLiteral) {
136 sink.write(expression.toSource());
137 return true;
138 }
139 if (expression is ListLiteral) {
140 sink.write('const [');
141 for (var element in expression.elements) {
142 if (!_writeExpression(element)) return false;
143 sink.write(',');
144 }
145 sink.write(']');
146 return true;
147 }
148 if (expression is MapLiteral) {
149 sink.write('const {');
150 var index = 0;
151 for (var entry in expression.entries) {
152 if (!_writeExpression(entry.key)) return false;
153 sink.write(': ');
154 if (!_writeExpression(entry.value)) return false;
155 if (++index < expression.entries.length) {
156 sink.write(', ');
157 }
158 }
159 sink.write('}');
160 return true;
161 }
162 if (expression is Identifier) {
163 var element = expression.bestElement;
164 if (element == null || !element.isPublic) return false;
165
166 if (element is ClassElement) {
167 sink.write('${prefixes[element.library]}${element.name}');
168 return true;
169 }
170 if (element is PropertyAccessorElement) {
171 var variable = element.variable;
172 if (variable is FieldElement) {
173 var cls = variable.enclosingElement;
174 sink.write('${prefixes[cls.library]}${cls.name}.${variable.name}');
175 return true;
176 } else if (variable is TopLevelVariableElement) {
177 sink.write('${prefixes[variable.library]}${variable.name}');
178 return true;
179 }
180 }
181
182 if (element is MethodElement) {
183 var cls = element.enclosingElement;
184 sink.write('${prefixes[cls.library]}${cls.name}.${element.name}');
185 return true;
186 }
187 }
188 if (expression is BooleanLiteral || expression is DoubleLiteral ||
189 expression is IntegerLiteral || expression is NullLiteral) {
190 sink.write(expression.toSource());
191 return true;
192 }
193 return false;
194 }
195 }
196
197 class AnnotationExtractor {
198 final TransformLogger logger;
199 final Resolver resolver;
200 final AssetId outputId;
201
202 static const List<String> _angularAnnotationNames = const [
203 'angular.core.annotation_src.NgAttr',
204 'angular.core.annotation_src.NgOneWay',
205 'angular.core.annotation_src.NgOneWayOneTime',
206 'angular.core.annotation_src.NgTwoWay',
207 'angular.core.annotation_src.NgCallback'
208 ];
209
210 static const Map<String, String> _annotationToMapping = const {
211 'NgAttr': '@',
212 'NgOneWay': '=>',
213 'NgOneWayOneTime': '=>!',
214 'NgTwoWay': '<=>',
215 'NgCallback': '&',
216 };
217
218 ClassElement directiveType;
219 ClassElement formatterType;
220
221 /// Resolved annotations that this will pick up for members.
222 final List<Element> _annotationElements = <Element>[];
223
224 AnnotationExtractor(this.logger, this.resolver, this.outputId) {
225 for (var annotation in _angularAnnotationNames) {
226 var type = resolver.getType(annotation);
227 if (type == null) {
228 logger.warning('Unable to resolve $annotation, skipping metadata.');
229 continue;
230 }
231 _annotationElements.add(type.unnamedConstructor);
232 }
233 directiveType = resolver.getType('angular.core.annotation_src.Directive');
234 formatterType = resolver.getType('angular.core.annotation_src.Formatter');
235 if (directiveType == null) {
236 logger.warning('Unable to resolve Directive, skipping member annotations.' );
237 }
238 if (formatterType == null) {
239 logger.warning('Unable to resolve Formatter.');
240 }
241 }
242
243 /// Extracts all of the annotations for the specified class.
244 AnnotatedType extractAnnotations(ClassElement cls) {
245 var classElement = cls;
246 var visitor = new _AnnotationVisitor(_annotationElements);
247 while (classElement != null) {
248 if (resolver.getImportUri(classElement.library, from: outputId) == null) {
249 warn('Dropping annotations for ${classElement.name} because the '
250 'containing file cannot be imported (must be in a lib folder).', cla ssElement);
251 return null;
252 }
253 if (classElement.node != null) {
254 classElement.node.accept(visitor);
255 }
256
257 if (classElement.supertype != null) {
258 visitor.visitingSupertype = true;
259 classElement = classElement.supertype.element;
260 } else {
261 classElement = null;
262 }
263 }
264
265 if (!visitor.hasAnnotations) return null;
266
267 var type = new AnnotatedType(cls);
268 type.annotations = visitor.classAnnotations
269 .where((Annotation annotation) {
270 var element = annotation.element;
271 if (element != null && !element.isPublic) {
272 warn('Annotation $annotation is not public.',
273 annotation.parent.element);
274 return false;
275 }
276 if (element is! ConstructorElement) {
277 // Only keeping constructor elements.
278 return false;
279 }
280 ConstructorElement ctor = element;
281 var cls = ctor.enclosingElement;
282 if (!cls.isPublic) {
283 warn('Annotation $annotation is not public.',
284 annotation.parent.element);
285 return false;
286 }
287 return element.enclosingElement.type.isAssignableTo(directiveType.type ) ||
288 element.enclosingElement.type.isAssignableTo(formatterType.type );
289 }).toList();
290
291 if (type.annotations.isEmpty) return null;
292
293 var memberAnnotations = {};
294 visitor.memberAnnotations.forEach((memberName, annotations) {
295 if (annotations.length > 1) {
296 warn('$memberName can only have one annotation.',
297 annotations[0].parent.element);
298 return;
299 }
300
301 memberAnnotations[memberName] = annotations[0];
302 });
303
304 if (memberAnnotations.isNotEmpty) {
305 _foldMemberAnnotations(memberAnnotations, type);
306 }
307
308 return type;
309 }
310
311 /// Folds all AttrFieldAnnotations into the Directive annotation on the
312 /// class.
313 void _foldMemberAnnotations(Map<String, Annotation> memberAnnotations,
314 AnnotatedType type) {
315 // Filter down to Directive constructors.
316 var ngAnnotations = type.annotations.where((a) {
317 var element = a.element;
318 if (element is! ConstructorElement) return false;
319 return element.enclosingElement.type.isAssignableTo(
320 directiveType.type);
321 });
322
323 var mapType = resolver.getType('dart.core.Map').type;
324 // Find acceptable constructors- ones which take a param named 'map'
325 var acceptableAnnotations = ngAnnotations.where((a) {
326 var ctor = a.element;
327
328 for (var param in ctor.parameters) {
329 if (param.parameterKind != ParameterKind.NAMED) {
330 continue;
331 }
332 if (param.name == 'map' && param.type.isAssignableTo(mapType)) {
333 return true;
334 }
335 }
336 return false;
337 });
338
339 if (acceptableAnnotations.isEmpty) {
340 warn('Could not find a constructor for member annotations in '
341 '$ngAnnotations', type.type);
342 return;
343 }
344
345 // Merge attribute annotations in all of the class annotations
346 acceptableAnnotations.forEach((srcAnnotation) {
347 // Clone the annotation so we don't modify the one in the persistent AST.
348 var index = type.annotations.indexOf(srcAnnotation);
349 var annotation = new AstCloner().visitAnnotation(srcAnnotation);
350 ResolutionCopier.copyResolutionData(srcAnnotation, annotation);
351 type.annotations[index] = annotation;
352
353 var mapArg = annotation.arguments.arguments.firstWhere(
354 (arg) => (arg is NamedExpression) && (arg.name.label.name == 'map'),
355 orElse: () => null);
356
357 // If we don't have a 'map' parameter yet, add one.
358 if (mapArg == null) {
359 var map = new MapLiteral(null, null, null, [], null);
360 var label = new Label(new SimpleIdentifier(
361 new _GeneratedToken(TokenType.STRING, 'map')),
362 new _GeneratedToken(TokenType.COLON, ':'));
363 mapArg = new NamedExpression(label, map);
364 annotation.arguments.arguments.add(mapArg);
365 }
366
367 var map = mapArg.expression;
368 if (map is! MapLiteral) {
369 warn('Expected \'map\' argument of $annotation to be a map literal',
370 type.type);
371 return;
372 }
373 memberAnnotations.forEach((memberName, annotation) {
374 var key = annotation.arguments.arguments.first;
375 // If the key already exists then it means we have two annotations for
376 // same member.
377 if (map.entries.any((entry) => entry.key.toString() == key.toString())) {
378 warn('Directive $annotation already contains an entry for $key',
379 type.type);
380 return;
381 }
382
383 var typeName = annotation.element.enclosingElement.name;
384 var value = '${_annotationToMapping[typeName]}$memberName';
385 var entry = new MapLiteralEntry(
386 key,
387 new _GeneratedToken(TokenType.COLON, ':'),
388 new SimpleStringLiteral(stringToken(value), value));
389 map.entries.add(entry);
390 });
391 });
392 }
393
394 Token stringToken(String str) =>
395 new _GeneratedToken(TokenType.STRING, '\'$str\'');
396
397 void warn(String msg, Element element) {
398 logger.warning(msg, asset: resolver.getSourceAssetId(element),
399 span: resolver.getSourceSpan(element));
400 }
401 }
402
403 /// Subclass for tokens which we're generating here.
404 class _GeneratedToken extends Token {
405 final String lexeme;
406 _GeneratedToken(TokenType type, this.lexeme) : super(type, 0);
407 }
408
409
410 /**
411 * AST visitor which walks the current AST and finds all annotated
412 * classes and members.
413 */
414 class _AnnotationVisitor extends GeneralizingAstVisitor {
415 final List<Element> allowedMemberAnnotations;
416 final List<Annotation> classAnnotations = [];
417 final Map<String, List<Annotation>> memberAnnotations = {};
418 var visitingSupertype = false;
419
420 _AnnotationVisitor(this.allowedMemberAnnotations);
421
422 void visitAnnotation(Annotation annotation) {
423 var parent = annotation.parent;
424 if (parent is! Declaration) return;
425
426 if (parent.element is ClassElement && !visitingSupertype) {
427 classAnnotations.add(annotation);
428
429 } else if (allowedMemberAnnotations.contains(annotation.element)) {
430 if (parent is MethodDeclaration) {
431 memberAnnotations.putIfAbsent(parent.name.name, () => [])
432 .add(annotation);
433 } else if (parent is FieldDeclaration) {
434 var name = parent.fields.variables.first.name.name;
435 memberAnnotations.putIfAbsent(name, () => []).add(annotation);
436 }
437 }
438 }
439
440 bool get hasAnnotations =>
441 classAnnotations.isNotEmpty || memberAnnotations.isNotEmpty;
442 }
OLDNEW

Powered by Google App Engine
This is Rietveld 408576698