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

Side by Side Diff: pkg/analyzer/test/src/summary/element_text.dart

Issue 2728883004: Verify resynthesized elements by comparing text presentations. (Closed)
Patch Set: Final changes. Created 3 years, 9 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
« no previous file with comments | « no previous file | pkg/analyzer/test/src/summary/resynthesize_ast_test.dart » ('j') | no next file with comments »
Toggle Intra-line Diffs ('i') | Expand Comments ('e') | Collapse Comments ('c') | Show Comments Hide Comments ('s')
OLDNEW
(Empty)
1 // Copyright (c) 2017, the Dart project authors. Please see the AUTHORS file
2 // for details. All rights reserved. Use of this source code is governed by a
3 // BSD-style license that can be found in the LICENSE file.
4
5 import 'dart:io';
6
7 import 'package:analyzer/dart/ast/ast.dart';
8 import 'package:analyzer/dart/ast/token.dart';
9 import 'package:analyzer/dart/element/element.dart';
10 import 'package:analyzer/dart/element/type.dart';
11 import 'package:analyzer/src/dart/element/element.dart';
12 import 'package:analyzer/src/dart/element/type.dart';
13 import 'package:analyzer/src/generated/source.dart';
14 import 'package:analyzer/src/generated/utilities_dart.dart';
15 import 'package:test/test.dart';
16
17 /**
18 * Set this path to automatically replace expectations in invocations of
19 * [checkElementText] with the new actual texts.
20 */
21 const String _testPath = null;
22
23 /**
24 * The list of replacements that update expectations.
25 */
26 final List<_Replacement> _replacements = [];
27
28 /**
29 * The cached content of the file with the [_testPath].
30 */
31 String _testCode;
32
33 /**
34 * The cache line information for the [_testPath] file.
35 */
36 LineInfo _testCodeLines;
37
38 void applyCheckElementTextReplacements() {
39 if (_testPath != null && _replacements.isNotEmpty) {
40 _replacements.sort((a, b) => b.offset - a.offset);
41 String newCode = _testCode;
42 _replacements.forEach((r) {
43 newCode =
44 newCode.substring(0, r.offset) + r.text + newCode.substring(r.end);
45 });
46 new File(_testPath).writeAsStringSync(newCode);
47 }
48 }
49
50 /**
51 * Write the given [library] elements into the canonical text presentation
52 * taking into account the specified 'withX' options. Then compare the
53 * actual text with the given [expected] one.
54 */
55 void checkElementText(LibraryElement library, String expected,
56 {bool withOffsets: false}) {
57 var writer = new _ElementWriter(withOffsets: withOffsets);
58 writer.writeLibraryElement(library);
59 String actualText = writer.buffer.toString();
60 if (_testPath != null && actualText != expected) {
61 if (_testCode == null) {
62 _testCode = new File(_testPath).readAsStringSync();
63 _testCodeLines = new LineInfo.fromContent(_testCode);
64 }
65
66 try {
67 throw 42;
68 } catch (e, trace) {
69 String traceString = trace.toString();
70
71 // Assuming traceString contains "$_testPath:$invocationLine:$column",
72 // figure out the value of invocationLine.
73
74 int testFilePathOffset = traceString.indexOf(_testPath);
75 expect(testFilePathOffset, isNonNegative);
76
77 // Sanity check: there must be ':' after the path.
78 expect(traceString[testFilePathOffset + _testPath.length], ':');
79
80 int lineOffset = testFilePathOffset + _testPath.length + ':'.length;
81 int invocationLine = int.parse(traceString.substring(
82 lineOffset, traceString.indexOf(':', lineOffset)));
83 int invocationOffset = _testCodeLines.getOffsetOfLine(invocationLine - 1);
84
85 const String rawStringPrefix = "r'''";
86 int expectationOffset =
87 _testCode.indexOf(rawStringPrefix, invocationOffset);
88
89 // Sanity check: there must be no other strings or blocks.
90 expect(_testCode.substring(invocationOffset, expectationOffset),
91 isNot(anyOf(contains("'"), contains('"'), contains('}'))));
92
93 expectationOffset += rawStringPrefix.length;
94 int expectationEnd = _testCode.indexOf("'''", expectationOffset);
95
96 _replacements.add(new _Replacement(
97 expectationOffset, expectationEnd, '\n' + actualText));
98 }
99 }
100
101 // Print the actual text to simplify copy/paste into the expectation.
102 if (actualText != expected) {
103 print('-------- Actual --------');
104 print(actualText + '------------------------');
105 }
106
107 expect(actualText, expected);
108 }
109
110 /**
111 * Writes the canonical text presentation of elements.
112 */
113 class _ElementWriter {
114 final bool withOffsets;
115 final StringBuffer buffer = new StringBuffer();
116
117 _ElementWriter({this.withOffsets: false});
118
119 bool isDynamicType(DartType type) => type is DynamicTypeImpl;
120
121 bool isEnumElement(Element e) {
122 return e is ClassElement && e.isEnum;
123 }
124
125 void newLineIfNotEmpty() {
126 if (buffer.isNotEmpty) {
127 buffer.writeln();
128 }
129 }
130
131 void writeBodyModifiers(ExecutableElement e) {
132 if (e.isAsynchronous) {
133 expect(e.isSynchronous, isFalse);
134 buffer.write(' async');
135 }
136
137 if (e.isSynchronous && e.isGenerator) {
138 expect(e.isAsynchronous, isFalse);
139 buffer.write(' sync');
140 }
141
142 writeIf(e.isGenerator, '*');
143 }
144
145 void writeClassElement(ClassElement e) {
146 writeDocumentation(e);
147 writeMetadata(e, '', '\n');
148
149 writeIf(e.isAbstract, 'abstract ');
150
151 if (e.isEnum) {
152 buffer.write('enum ');
153 } else {
154 buffer.write('class ');
155 }
156
157 writeIf(e.isMixinApplication, 'alias ');
158
159 writeName(e);
160 writeTypeParameterElements(e.typeParameters);
161
162 if (e.supertype != null && e.supertype.displayName != 'Object' ||
163 e.mixins.isNotEmpty) {
164 buffer.write(' extends ');
165 writeType(e.supertype);
166 }
167
168 writeList(' with ', '', e.mixins, ', ', writeType);
169 writeList(' implements ', '', e.interfaces, ', ', writeType);
170
171 buffer.writeln(' {');
172
173 e.fields.forEach(writeFieldElement);
174 e.accessors.forEach(writePropertyAccessorElement);
175
176 if (e.isEnum) {
177 expect(e.constructors, isEmpty);
178 } else {
179 expect(e.constructors, isNotEmpty);
180 }
181
182 if (e.constructors.length == 1 &&
183 e.constructors[0].isSynthetic &&
184 e.mixins.isEmpty) {
185 expect(e.constructors[0].parameters, isEmpty);
186 } else {
187 e.constructors.forEach(writeConstructorElement);
188 }
189
190 e.methods.forEach(writeMethodElement);
191 buffer.writeln('}');
192 }
193
194 void writeConstructorElement(ConstructorElement e) {
195 writeDocumentation(e, ' ');
196 writeMetadata(e, ' ', '\n');
197
198 buffer.write(' ');
199
200 writeIf(e.isSynthetic, 'synthetic ');
201 writeIf(e.isExternal, 'external ');
202 writeIf(e.isConst, 'const ');
203 writeIf(e.isFactory, 'factory ');
204
205 buffer.write(e.enclosingElement.name);
206 if (e.name.isNotEmpty) {
207 buffer.write('.');
208 writeName(e);
209 }
210
211 writeParameterElements(e.parameters);
212
213 {
214 ConstructorElement redirected = e.redirectedConstructor;
215 if (redirected != null) {
216 buffer.write(' = ');
217 buffer.write(redirected.returnType);
218 if (redirected.name.isNotEmpty) {
219 buffer.write('.');
220 buffer.write(redirected.name);
221 }
222 }
223 }
224
225 if (e is ConstructorElementImpl) {
226 if (e.constantInitializers != null) {
227 writeList(' : ', '', e.constantInitializers, ', ', writeExpression);
228 }
229 }
230
231 expect(e.isAsynchronous, isFalse);
232 expect(e.isGenerator, isFalse);
233
234 buffer.writeln(';');
235 }
236
237 void writeDocumentation(Element e, [String prefix = '']) {
238 if (e.documentationComment != null) {
239 buffer.write(prefix);
240 buffer.writeln(e.documentationComment);
241 }
242 }
243
244 void writeExportElement(ExportElement e) {
245 writeMetadata(e, '', '\n');
246 buffer.write('export ');
247 writeUri(e, e.exportedLibrary.source);
248
249 e.combinators.forEach(writeNamespaceCombinator);
250
251 buffer.writeln(';');
252 }
253
254 void writeExpression(AstNode e) {
255 if (e is Annotation) {
256 buffer.write('@');
257 writeExpression(e.name);
258 if (e.constructorName != null) {
259 buffer.write('.');
260 writeExpression(e.constructorName);
261 }
262 if (e.arguments != null) {
263 writeList('(', ')', e.arguments.arguments, ', ', writeExpression,
264 includeEmpty: true);
265 }
266 } else if (e is AssertInitializer) {
267 buffer.write('assert(');
268 writeExpression(e.condition);
269 if (e.message != null) {
270 buffer.write(', ');
271 writeExpression(e.message);
272 }
273 buffer.write(')');
274 } else if (e is BinaryExpression) {
275 writeExpression(e.leftOperand);
276 buffer.write(' ');
277 buffer.write(e.operator.lexeme);
278 buffer.write(' ');
279 writeExpression(e.rightOperand);
280 } else if (e is BooleanLiteral) {
281 buffer.write(e.value);
282 } else if (e is ConditionalExpression) {
283 writeExpression(e.condition);
284 buffer.write(' ? ');
285 writeExpression(e.thenExpression);
286 buffer.write(' : ');
287 writeExpression(e.elseExpression);
288 } else if (e is ConstructorFieldInitializer) {
289 writeExpression(e.fieldName);
290 buffer.write(' = ');
291 writeExpression(e.expression);
292 } else if (e is ConstructorName) {
293 writeExpression(e.type);
294 if (e.name != null) {
295 buffer.write('.');
296 writeExpression(e.name);
297 }
298 } else if (e is DoubleLiteral) {
299 buffer.write(e.value);
300 } else if (e is InstanceCreationExpression) {
301 buffer.write(e.keyword.lexeme);
302 buffer.write(' ');
303 writeExpression(e.constructorName);
304 writeList('(', ')', e.argumentList.arguments, ', ', writeExpression,
305 includeEmpty: true);
306 } else if (e is IntegerLiteral) {
307 buffer.write(e.value);
308 } else if (e is InterpolationExpression) {
309 buffer.write(r'${');
310 writeExpression(e.expression);
311 buffer.write(r'}');
312 } else if (e is InterpolationString) {
313 buffer.write(e.value.replaceAll("'", r"\'"));
314 } else if (e is ListLiteral) {
315 if (e.constKeyword != null) {
316 buffer.write('const ');
317 }
318 if (e.typeArguments != null) {
319 writeList('<', '>', e.typeArguments.arguments, ', ', writeExpression);
320 }
321 writeList('[', ']', e.elements, ', ', writeExpression,
322 includeEmpty: true);
323 } else if (e is Label) {
324 writeExpression(e.label);
325 buffer.write(': ');
326 } else if (e is MapLiteral) {
327 if (e.constKeyword != null) {
328 buffer.write('const ');
329 }
330 if (e.typeArguments != null) {
331 writeList('<', '>', e.typeArguments.arguments, ', ', writeExpression);
332 }
333 writeList('{', '}', e.entries, ', ', writeExpression, includeEmpty: true);
334 } else if (e is MapLiteralEntry) {
335 writeExpression(e.key);
336 buffer.write(': ');
337 writeExpression(e.value);
338 } else if (e is NamedExpression) {
339 writeExpression(e.name);
340 buffer.write(e.expression);
341 } else if (e is NullLiteral) {
342 buffer.write('null');
343 } else if (e is PrefixExpression) {
344 buffer.write(e.operator.lexeme);
345 writeExpression(e.operand);
346 } else if (e is PrefixedIdentifier) {
347 writeExpression(e.prefix);
348 buffer.write('.');
349 writeExpression(e.identifier);
350 } else if (e is PropertyAccess) {
351 writeExpression(e.target);
352 buffer.write('.');
353 writeExpression(e.propertyName);
354 } else if (e is RedirectingConstructorInvocation) {
355 buffer.write('this');
356 if (e.constructorName != null) {
357 buffer.write('.');
358 writeExpression(e.constructorName);
359 }
360 writeList('(', ')', e.argumentList.arguments, ', ', writeExpression,
361 includeEmpty: true);
362 } else if (e is SimpleIdentifier) {
363 buffer.write(e.name);
364 } else if (e is SimpleStringLiteral) {
365 buffer.write("'");
366 buffer.write(e.value.replaceAll("'", r"\'"));
367 buffer.write("'");
368 } else if (e is StringInterpolation) {
369 buffer.write("'");
370 e.elements.forEach(writeExpression);
371 buffer.write("'");
372 } else if (e is SuperConstructorInvocation) {
373 buffer.write('super');
374 if (e.constructorName != null) {
375 buffer.write('.');
376 writeExpression(e.constructorName);
377 }
378 writeList('(', ')', e.argumentList.arguments, ', ', writeExpression,
379 includeEmpty: true);
380 } else if (e is SuperExpression) {
381 buffer.write('super');
382 } else if (e is SymbolLiteral) {
383 buffer.write('#');
384 writeList('', '', e.components, '.',
385 (Token token) => buffer.write(token.lexeme));
386 } else if (e is ThisExpression) {
387 buffer.write('this');
388 } else if (e is TypeName) {
389 writeExpression(e.name);
390 if (e.typeArguments != null) {
391 writeList('<', '>', e.typeArguments.arguments, ', ', writeExpression);
392 }
393 } else {
394 fail('Unsupported expression type: ${e.runtimeType}');
395 }
396 }
397
398 void writeFieldElement(FieldElement e) {
399 if (e.isSynthetic && !isEnumElement(e.enclosingElement)) {
400 return;
401 }
402
403 writeDocumentation(e, ' ');
404 writeMetadata(e, ' ', '\n');
405
406 buffer.write(' ');
407
408 writeIf(e.isStatic, 'static ');
409 writeIf(e is FieldElementImpl && e.isCovariant, 'covariant ');
410
411 writePropertyInducingElement(e);
412 }
413
414 void writeFunctionElement(FunctionElement e) {
415 writeIf(e.isExternal, 'external ');
416
417 writeType2(e.returnType);
418
419 writeName(e);
420
421 writeTypeParameterElements(e.typeParameters);
422 writeParameterElements(e.parameters);
423
424 writeBodyModifiers(e);
425
426 buffer.writeln(' {}');
427 }
428
429 void writeFunctionTypeAliasElement(FunctionTypeAliasElement e) {
430 writeDocumentation(e);
431 writeMetadata(e, '', '\n');
432
433 buffer.write('typedef ');
434 writeType2(e.returnType);
435
436 writeName(e);
437
438 writeTypeParameterElements(e.typeParameters);
439 writeParameterElements(e.parameters);
440
441 buffer.writeln(';');
442 }
443
444 void writeIf(bool flag, String str) {
445 if (flag) {
446 buffer.write(str);
447 }
448 }
449
450 void writeImportElement(ImportElement e) {
451 if (!e.isSynthetic) {
452 writeMetadata(e, '', '\n');
453 buffer.write('import ');
454 writeUri(e, e.importedLibrary.source);
455
456 writeIf(e.isDeferred, ' deferred');
457
458 if (e.prefix != null) {
459 buffer.write(' as ');
460 writeName(e.prefix);
461 if (withOffsets) {
462 buffer.write('(${e.prefixOffset})');
463 }
464 }
465
466 e.combinators.forEach(writeNamespaceCombinator);
467
468 buffer.writeln(';');
469 }
470 }
471
472 void writeLibraryElement(LibraryElement e) {
473 if (e.displayName != '') {
474 writeMetadata(e, '', '\n');
475 buffer.write('library ');
476 writeName(e);
477 buffer.writeln(';');
478 }
479
480 e.imports.forEach(writeImportElement);
481 e.exports.forEach(writeExportElement);
482 e.parts.forEach(writePartElement);
483
484 e.units.forEach(writeUnitElement);
485 }
486
487 void writeList<T>(String open, String close, List<T> items, String separator,
488 writeItem(T item),
489 {bool includeEmpty: false}) {
490 if (!includeEmpty && items.isEmpty) {
491 return;
492 }
493 buffer.write(open);
494 bool first = true;
495 for (T item in items) {
496 if (!first) {
497 buffer.write(separator);
498 }
499 writeItem(item);
500 first = false;
501 }
502 buffer.write(close);
503 }
504
505 void writeMetadata(Element e, String prefix, String separator) {
506 if (e.metadata.isNotEmpty) {
507 writeList(prefix, '', e.metadata, '$separator$prefix', (a) {
508 writeExpression((a as ElementAnnotationImpl).annotationAst);
509 });
510 buffer.write(separator);
511 }
512 }
513
514 void writeMethodElement(MethodElement e) {
515 writeDocumentation(e, ' ');
516 writeMetadata(e, ' ', '\n');
517
518 buffer.write(' ');
519
520 writeIf(e.isExternal, 'external ');
521 writeIf(e.isStatic, 'static ');
522
523 writeType2(e.returnType);
524
525 writeName(e);
526
527 writeTypeParameterElements(e.typeParameters);
528 writeParameterElements(e.parameters);
529
530 writeBodyModifiers(e);
531
532 if (e.isAbstract) {
533 buffer.writeln(';');
534 } else {
535 buffer.writeln(' {}');
536 }
537 }
538
539 void writeName(Element e) {
540 buffer.write(e.displayName);
541 if (withOffsets) {
542 buffer.write('@');
543 buffer.write(e.nameOffset);
544 }
545 }
546
547 void writeNamespaceCombinator(NamespaceCombinator e) {
548 if (e is ShowElementCombinator) {
549 buffer.write(' show ');
550 buffer.write(e.shownNames.join(', '));
551 } else if (e is HideElementCombinator) {
552 buffer.write(' hide ');
553 buffer.write(e.hiddenNames.join(', '));
554 }
555 }
556
557 void writeParameterElement(ParameterElement e) {
558 String defaultValueSeparator;
559 Expression defaultValue =
560 e is DefaultParameterElementImpl ? e.constantInitializer : null;
561 String closeString;
562 ParameterKind kind = e.parameterKind;
563 if (kind == ParameterKind.REQUIRED) {
564 closeString = '';
565 } else if (kind == ParameterKind.POSITIONAL) {
566 buffer.write('[');
567 defaultValueSeparator = ' = ';
568 closeString = ']';
569 } else if (kind == ParameterKind.NAMED) {
570 buffer.write('{');
571 defaultValueSeparator = ': ';
572 closeString = '}';
573 } else {
574 fail('Unknown parameter kind: $kind');
575 }
576
577 writeMetadata(e, '', ' ');
578
579 writeIf(e.isCovariant, 'covariant ');
580 writeIf(e.isFinal, 'final ');
581
582 writeType2(e.type);
583
584 if (e is FieldFormalParameterElement) {
585 buffer.write('this.');
586 }
587
588 writeName(e);
589
590 if (defaultValue != null) {
591 buffer.write(defaultValueSeparator);
592 writeExpression(defaultValue);
593 }
594
595 buffer.write(closeString);
596 }
597
598 void writeParameterElements(List<ParameterElement> elements) {
599 writeList('(', ')', elements, ', ', writeParameterElement,
600 includeEmpty: true);
601 }
602
603 void writePartElement(CompilationUnitElement e) {
604 writeMetadata(e, '', '\n');
605 buffer.write('part ');
606 writeUri(e, e.source);
607 buffer.writeln(';');
608 }
609
610 void writePropertyAccessorElement(PropertyAccessorElement e) {
611 if (e.isSynthetic) {
612 return;
613 }
614
615 if (e.enclosingElement is ClassElement) {
616 writeDocumentation(e, ' ');
617 writeMetadata(e, ' ', '\n');
618
619 buffer.write(' ');
620
621 writeIf(e.isStatic, 'static ');
622 } else {
623 writeDocumentation(e);
624 writeMetadata(e, '', '\n');
625 }
626
627 writeIf(e.isExternal, 'external ');
628
629 writeType2(e.returnType);
630
631 if (e.isGetter) {
632 buffer.write('get ');
633 } else {
634 buffer.write('set ');
635 }
636
637 writeName(e);
638
639 if (e.isSetter || e.parameters.isNotEmpty) {
640 writeParameterElements(e.parameters);
641 }
642
643 expect(e.typeParameters, isEmpty);
644
645 expect(e.isSynchronous, isTrue);
646 expect(e.isAsynchronous, isFalse);
647 expect(e.isGenerator, isFalse);
648
649 if (e.isAbstract) {
650 buffer.writeln(';');
651 } else {
652 buffer.writeln(' {}');
653 }
654 }
655
656 void writePropertyInducingElement(PropertyInducingElement e) {
657 DartType type = e.type;
658 expect(type, isNotNull);
659
660 writeIf(e.isFinal, 'final ');
661 writeIf(e.isConst, 'const ');
662 writeType2(type);
663
664 writeName(e);
665
666 if (e is ConstVariableElement) {
667 Expression initializer = (e as ConstVariableElement).constantInitializer;
668 if (initializer != null) {
669 buffer.write(' = ');
670 writeExpression(initializer);
671 }
672 }
673
674 // TODO(scheglov) Paul: One of the things that was hardest to get right
675 // when resynthesizing the element model was the synthetic function for the
676 // initializer. Can we write that out (along with its return type)?
677
678 buffer.writeln(';');
679 }
680
681 void writeTopLevelVariableElement(TopLevelVariableElement e) {
682 if (e.isSynthetic) {
683 return;
684 }
685 writeDocumentation(e);
686 writeMetadata(e, '', '\n');
687 writePropertyInducingElement(e);
688 }
689
690 void writeType(DartType type) {
691 if (type is InterfaceType) {
692 buffer.write(type.element.name);
693 if (type.element.typeParameters.isNotEmpty) {
694 writeList('<', '>', type.typeArguments, ', ', writeType);
695 }
696 } else {
697 buffer.write(type.displayName);
698 }
699 }
700
701 void writeType2(DartType type) {
702 writeType(type);
703 buffer.write(' ');
704 }
705
706 void writeTypeParameterElement(TypeParameterElement e) {
707 writeName(e);
708 if (e.bound != null) {
709 buffer.write(' extends ');
710 writeType(e.bound);
711 }
712 }
713
714 void writeTypeParameterElements(List<TypeParameterElement> elements) {
715 writeList('<', '>', elements, ', ', writeTypeParameterElement);
716 }
717
718 void writeUnitElement(CompilationUnitElement e) {
719 if (e.library.definingCompilationUnit != e) {
720 buffer.writeln('-' * 20);
721 buffer.writeln('unit: ${e.source.shortName}');
722 buffer.writeln();
723 }
724 e.functionTypeAliases.forEach(writeFunctionTypeAliasElement);
725 e.enums.forEach(writeClassElement);
726 e.types.forEach(writeClassElement);
727 e.topLevelVariables.forEach(writeTopLevelVariableElement);
728 e.accessors.forEach(writePropertyAccessorElement);
729 e.functions.forEach(writeFunctionElement);
730 }
731
732 void writeUri(UriReferencedElement e, Source source) {
733 String uri = e.uri ?? source.uri.toString();
734 buffer.write('\'$uri\'');
735 if (withOffsets) {
736 buffer.write('(');
737 buffer.write('${e.uriOffset}, ');
738 buffer.write('${e.uriEnd})');
739 buffer.write(')');
740 }
741 }
742 }
743
744 class _Replacement {
745 final int offset;
746 final int end;
747 final String text;
748 _Replacement(this.offset, this.end, this.text);
749 }
OLDNEW
« no previous file with comments | « no previous file | pkg/analyzer/test/src/summary/resynthesize_ast_test.dart » ('j') | no next file with comments »

Powered by Google App Engine
This is Rietveld 408576698