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

Unified Diff: pkg/analyzer/test/src/summary/element_text.dart

Issue 2728883004: Verify resynthesized elements by comparing text presentations. (Closed)
Patch Set: Created 3 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: pkg/analyzer/test/src/summary/element_text.dart
diff --git a/pkg/analyzer/test/src/summary/element_text.dart b/pkg/analyzer/test/src/summary/element_text.dart
new file mode 100644
index 0000000000000000000000000000000000000000..c33abcc71b97ea37b3e3c55cf06027a63a7e5e98
--- /dev/null
+++ b/pkg/analyzer/test/src/summary/element_text.dart
@@ -0,0 +1,492 @@
+import 'dart:io';
Brian Wilkerson 2017/03/03 16:09:33 Missing copyright notice.
scheglov 2017/03/03 16:24:29 Done.
+
+import 'package:analyzer/dart/element/element.dart';
+import 'package:analyzer/dart/element/type.dart';
+import 'package:analyzer/src/dart/element/element.dart';
+import 'package:analyzer/src/dart/element/type.dart';
+import 'package:analyzer/src/generated/source.dart';
+import 'package:analyzer/src/generated/utilities_dart.dart';
+import 'package:test/test.dart';
+
+const String _testPath =
+ '/Users/scheglov/Source/Dart/sdk.git/sdk/pkg/analyzer/test/src/summary/resynthesize_common.dart';
Brian Wilkerson 2017/03/03 16:09:33 That won't work on other machines. :-)
scheglov 2017/03/03 16:24:29 No, it won't. I will null it before commit. Then t
Paul Berry 2017/03/03 17:22:52 You should be able to dynamically figure out the c
+
+final List<_Replacement> _replacements = [];
+
+String _testCode;
+LineInfo _testCodeLines;
+void applyReplacements() {
+ if (_testPath != null && _replacements.isNotEmpty) {
+ _replacements.sort((a, b) => b.offset - a.offset);
+ String newCode = _testCode;
+ _replacements.forEach((r) {
+ newCode =
+ newCode.substring(0, r.offset) + r.text + newCode.substring(r.end);
+ });
+ new File(_testPath).writeAsStringSync(newCode);
+ }
+}
+
+void checkElementText(LibraryElement library, String expected,
+ {bool updateExpectations: false, bool withOffsets: false}) {
+ var writer = new _ElementWriter(withOffsets: withOffsets);
+ writer.writeLibraryElement(library);
+ String actualText = writer.buffer.toString();
+ if (/*updateExpectations &&*/ actualText != expected) {
Brian Wilkerson 2017/03/03 16:09:33 I assume you'll remove the comments before committ
scheglov 2017/03/03 16:24:29 Acknowledged.
+ if (_testCode == null) {
+ _testCode = new File(_testPath).readAsStringSync();
+ _testCodeLines = new LineInfo.fromContent(_testCode);
+ }
+
+ try {
+ throw 42;
+ } catch (e, trace) {
+ String traceString = trace.toString();
+
+ int testFilePathOffset = traceString.indexOf(_testPath);
Paul Berry 2017/03/03 17:22:51 Reading this code, it's really non-obvious what th
scheglov 2017/03/03 18:46:31 Done.
+ expect(testFilePathOffset, isNonNegative);
+
+ int lineOffset = testFilePathOffset + _testPath.length + ':'.length;
Paul Berry 2017/03/03 17:22:52 Nit: for sanity, verify that traceString[testFileP
scheglov 2017/03/03 18:46:31 Done.
+ int invocationLine = int.parse(traceString.substring(
+ lineOffset, traceString.indexOf(':', lineOffset)));
+ int invocationOffset = _testCodeLines.getOffsetOfLine(invocationLine - 1);
+
+ int expectationOffset = _testCode.indexOf("r'''", invocationOffset);
Paul Berry 2017/03/03 17:22:51 It would be nice to have a sanity check here to ma
scheglov 2017/03/03 18:46:30 Done.
+ expectationOffset += 5;
Paul Berry 2017/03/03 17:22:51 Why 5? I would have expected `"r'''".length`, whi
scheglov 2017/03/03 18:46:31 Done.
+ int expectationEnd = _testCode.indexOf("'''", expectationOffset);
+
+ _replacements
+ .add(new _Replacement(expectationOffset, expectationEnd, actualText));
+ }
+ }
+
+ expect(actualText, expected);
+}
+
+class _ElementWriter {
+ final bool withOffsets;
+ final StringBuffer buffer = new StringBuffer();
+
+ _ElementWriter({this.withOffsets: false});
+
+ bool isDynamicType(DartType type) => type is DynamicTypeImpl;
+
+ void newLineIfNotEmpty() {
+ if (buffer.isNotEmpty) {
+ buffer.writeln('');
Brian Wilkerson 2017/03/03 16:09:33 You don't need an argument to writeln.
scheglov 2017/03/03 16:24:29 Done.
+ }
+ }
+
+ void writeClassElement(ClassElement e) {
+ if (e.isAbstract) {
+ buffer.write('abstract ');
+ }
+ buffer.write('class ');
+
+ writeName(e);
+ writeTypeParameterElements(e.typeParameters);
+
+ if (e.supertype != null && e.supertype.displayName != 'Object' ||
+ e.mixins.isNotEmpty) {
+ buffer.write(' extends ');
+ writeType(e.supertype);
+ }
+
+ writeList(' with ', '', e.mixins, ', ', writeType);
+ writeList(' implements ', '', e.interfaces, ', ', writeType);
+
+ buffer.writeln(' {');
+
+ e.fields.forEach(writeFieldElement);
+ e.accessors.forEach(writePropertyAccessorElement);
+
+ expect(e.constructors, isNotEmpty);
+ if (e.constructors.length == 1 &&
+ e.constructors[0].isSynthetic &&
+ e.mixins.isEmpty) {
+ expect(e.constructors[0].parameters, isEmpty);
+ } else {
+ e.constructors.forEach(writeConstructorElement);
+ }
+
+ e.methods.forEach(writeMethodElement);
+ buffer.writeln('}');
+ }
+
+ void writeConstructorElement(ConstructorElement e) {
+ buffer.write(' ');
+
+ if (e.isSynthetic) {
+ buffer.write('synthetic ');
+ }
+
+ buffer.write(e.enclosingElement.name);
+ if (e.name != '') {
Brian Wilkerson 2017/03/03 16:09:33 isNotEmpty?
scheglov 2017/03/03 16:24:29 Done.
+ buffer.write('.');
+ writeName(e);
+ }
+
+ writeParameterElements(e.parameters);
+ buffer.writeln(';');
+ }
+
+ void writeEnumElement(ClassElement e) {
Paul Berry 2017/03/03 17:22:52 I'm concerned that this method makes a lot of assu
scheglov 2017/03/03 18:46:31 Done. Alternatively we could add more checks for
+// var e = _e as EnumElementImpl;
Paul Berry 2017/03/03 17:22:52 Delete this line.
+ expect(e.isEnum, isTrue);
Brian Wilkerson 2017/03/03 16:09:33 Should we expect the opposite is writeClassElement
scheglov 2017/03/03 16:24:29 Done.
+
+ buffer.write('enum ');
+ writeName(e);
+ buffer.write(' { ');
+
+ bool firstEnumValue = true;
+ for (FieldElement field in e.fields) {
+ if (field.name == 'index') {
+ expect(field.isSynthetic, isTrue);
+ expect(field.isStatic, isFalse);
+ continue;
+ }
+ if (field.name == 'values') {
+ expect(field.isSynthetic, isTrue);
+ expect(field.isStatic, isTrue);
+ continue;
+ }
+ if (firstEnumValue) {
+ firstEnumValue = false;
+ } else {
+ buffer.write(', ');
+ }
+ expect(field.isStatic, isTrue);
+ writeName(field);
+ }
+
+ buffer.writeln(' }');
+ }
+
+ void writeExportElement(ExportElement e) {
+ buffer.write('export ');
+ writeUri(e, e.exportedLibrary.source);
+
+ e.combinators.forEach(writeNamespaceCombinator);
+
Paul Berry 2017/03/03 17:22:52 Also write out conditional URIs.
scheglov 2017/03/03 18:46:30 I'm adding TODO for now.
+ buffer.writeln(';');
+ }
+
+ void writeFieldElement(FieldElement e) {
+ if (!e.isSynthetic) {
+ buffer.write(' ');
+
+ if (e.isStatic) {
+ buffer.write('static ');
+ }
+
+ if (e is FieldElementImpl && e.isCovariant) {
+ buffer.write('convariant ');
Paul Berry 2017/03/03 17:22:52 s/convariant/covariant/
scheglov 2017/03/03 18:46:30 Done.
+ }
+
+ writePropertyInducingElement(e);
+ }
+ }
+
+ void writeFunctionElement(FunctionElement e) {
+ if (e.isExternal) {
+ buffer.write('external ');
+ }
+
+ writeType2(e.returnType);
+
+ writeName(e);
+
+ writeTypeParameterElements(e.typeParameters);
+ writeParameterElements(e.parameters);
+
+ // TODO(scheglov) async*
+
+ buffer.writeln(' {}');
+ }
+
+ void writeFunctionTypeAliasElement(FunctionTypeAliasElement e) {
+ buffer.write('typedef ');
+ writeType2(e.returnType);
+
+ writeName(e);
+
+ writeTypeParameterElements(e.typeParameters);
+ writeParameterElements(e.parameters);
+
+ buffer.writeln(';');
+ }
+
+ void writeImportElement(ImportElement e) {
+ if (!e.isSynthetic) {
+ buffer.write('import ');
+ writeUri(e, e.importedLibrary.source);
+
+ if (e.isDeferred) {
+ buffer.write(' deferred');
+ }
+
+ if (e.prefix != null) {
+ buffer.write(' as ');
+ writeName(e.prefix);
+ if (withOffsets) {
+ buffer.write('(${e.prefixOffset})');
+ }
+ }
+
+ e.combinators.forEach(writeNamespaceCombinator);
+
Paul Berry 2017/03/03 17:22:52 Also write out conditional URIs.
scheglov 2017/03/03 18:46:31 I'm adding TODO for now.
scheglov 2017/03/03 19:03:06 Actually, ImportElement does not expose all condit
+ buffer.writeln(';');
+ }
+ }
+
+ void writeLibraryElement(LibraryElement e) {
+ if (e.displayName != '') {
+ buffer.write('library ');
+ writeName(e);
+ buffer.writeln(';');
+ }
+
+ e.imports.forEach(writeImportElement);
+ e.exports.forEach(writeExportElement);
+ e.parts.forEach(writePartElement);
+
+ e.units.forEach(writeUnitElement);
+ }
+
+ void writeList<T>(String open, String close, List<T> items, String separator,
+ writeItem(T item),
+ {bool includeEmpty: false}) {
+ if (!includeEmpty && items.isEmpty) {
+ return;
+ }
+ buffer.write(open);
+ bool first = true;
+ for (T item in items) {
+ if (!first) {
+ buffer.write(separator);
+ }
+ writeItem(item);
+ first = false;
+ }
+ buffer.write(close);
+ }
+
+ void writeMethodElement(MethodElement e) {
+ buffer.write(' ');
+
+ if (e.isStatic) {
+ buffer.write('static ');
+ }
+
+ if (e.isExternal) {
+ buffer.write('external ');
+ }
+
+ writeType2(e.returnType);
+
+ writeName(e);
+
+ writeTypeParameterElements(e.typeParameters);
+ writeParameterElements(e.parameters);
+
+ // TODO(scheglov) async*
+
+ if (e.isAbstract) {
+ buffer.writeln(';');
+ } else {
+ buffer.writeln(' {}');
+ }
+ }
+
+ void writeName(Element e) {
+ buffer.write(e.displayName);
+ if (withOffsets) {
+ buffer.write('(');
Paul Berry 2017/03/03 17:22:52 This makes a name decorated with an offset look li
scheglov 2017/03/03 18:46:31 Done.
Paul Berry 2017/03/03 23:24:35 It looks like you haven't done this yet. Did you
scheglov 2017/03/04 02:21:58 I'm sorry for the oversight. I made the change, bu
+ buffer.write(e.nameOffset);
+ buffer.write(')');
+ }
+ }
+
+ void writeNamespaceCombinator(NamespaceCombinator e) {
+ if (e is ShowElementCombinator) {
+ buffer.write(' show ');
+ buffer.write(e.shownNames.join(', '));
+ } else if (e is HideElementCombinator) {
+ buffer.write(' hide ');
+ buffer.write(e.hiddenNames.join(', '));
+ }
+ }
+
+ void writeParameterElement(ParameterElement e) {
+ String closeString;
+ ParameterKind kind = e.parameterKind;
+ if (kind == ParameterKind.REQUIRED) {
+ closeString = '';
+ } else if (kind == ParameterKind.POSITIONAL) {
+ buffer.write('[');
+ closeString = ']';
+ } else if (kind == ParameterKind.NAMED) {
+ buffer.write('{');
+ closeString = '}';
+ } else {
+ fail('Unknown parameter kind: $kind');
+ }
+
+ if (e.isCovariant) {
+ buffer.write('convariant ');
Paul Berry 2017/03/03 17:22:52 s/convariant/covariant/
scheglov 2017/03/03 18:46:30 Done.
+ }
+
+ if (e.isFinal) {
+ buffer.write('final ');
+ }
+
+ writeType2(e.type);
+
+ if (e is FieldFormalParameterElement) {
+ buffer.write('this.');
+ }
+
+ writeName(e);
+
+ buffer.write(closeString);
+ }
+
+ void writeParameterElements(List<ParameterElement> elements) {
+ writeList('(', ')', elements, ', ', writeParameterElement,
+ includeEmpty: true);
+ }
+
+ void writePartElement(CompilationUnitElement e) {
+ buffer.write('part ');
+ writeUri(e, e.source);
+ buffer.writeln(';');
+ }
+
+ void writePropertyAccessorElement(PropertyAccessorElement e) {
+ if (e.isSynthetic) {
+ return;
+ }
+
+ if (e.enclosingElement is ClassElement) {
+ buffer.write(' ');
+
+ if (e.isStatic) {
+ buffer.write('static ');
+ }
+ }
+
+ if (e.isExternal) {
+ buffer.write('external ');
+ }
+
+ writeType2(e.returnType);
+
+ if (e.isGetter) {
+ buffer.write('get ');
+ } else {
+ buffer.write('set ');
+ }
+
+ writeName(e);
+
+ if (e.isSetter || e.parameters.isNotEmpty) {
+ writeParameterElements(e.parameters);
+ }
+
+ expect(e.typeParameters, isEmpty);
+
+ expect(e.isSynchronous, isTrue);
+ expect(e.isAsynchronous, isFalse);
+ expect(e.isGenerator, isFalse);
+
+ if (e.isAbstract) {
+ buffer.writeln(';');
+ } else {
+ buffer.writeln(' {}');
+ }
+ }
+
+ void writePropertyInducingElement(PropertyInducingElement e) {
+ if (!e.isSynthetic) {
+ DartType type = e.type;
+ expect(type, isNotNull);
+ bool hasDynamicType = isDynamicType(type);
+
+ if (e.isFinal) {
+ buffer.write('final ');
+ } else if (e.isConst) {
+ buffer.write('const ');
+ } else if (hasDynamicType) {
+ buffer.write('var ');
+ }
+
+ if (!hasDynamicType) {
+ writeType(type);
+ buffer.write(' ');
+ }
Brian Wilkerson 2017/03/03 16:09:33 Replace with writeType2?
scheglov 2017/03/03 16:24:29 Done.
+
+ writeName(e);
+
Paul Berry 2017/03/03 17:22:52 One of the things that was hardest to get right wh
scheglov 2017/03/03 18:46:31 I'm adding TODO for now.
+ buffer.writeln(';');
+ }
+ }
+
+ void writeTopLevelVariableElement(TopLevelVariableElement e) {
+ writePropertyInducingElement(e);
+ }
+
+ void writeType(DartType type) {
+ buffer.write(type.displayName);
Paul Berry 2017/03/03 17:22:52 The "→" character that we use when writing out fun
scheglov 2017/03/03 18:46:31 It looks better with this character :-( If possibl
+ }
+
+ void writeType2(DartType type) {
Paul Berry 2017/03/03 17:22:52 IMHO this is more sophisticated than we ought to b
scheglov 2017/03/03 18:46:31 Done.
+ if (type != DynamicTypeImpl.instance) {
+ writeType(type);
+ buffer.write(' ');
+ }
+ }
+
+ void writeTypeParameterElement(TypeParameterElement e) {
+ writeName(e);
+ if (e.bound != null) {
+ buffer.write(' extends ');
+ writeType(e.bound);
+ }
+ }
+
+ void writeTypeParameterElements(List<TypeParameterElement> elements) {
+ writeList('<', '>', elements, ', ', writeTypeParameterElement);
Brian Wilkerson 2017/03/03 16:09:33 Probably want to include if empty.
scheglov 2017/03/03 16:24:29 I don't think so. This would transform "class C {}
+ }
+
+ void writeUnitElement(CompilationUnitElement e) {
+ if (e.library.definingCompilationUnit != e) {
+ buffer.writeln('-' * 20);
+ buffer.writeln('unit: ${e.source.shortName}');
+ buffer.writeln('');
+ }
+ e.functionTypeAliases.forEach(writeFunctionTypeAliasElement);
+ e.enums.forEach(writeEnumElement);
+ e.types.forEach(writeClassElement);
+ e.topLevelVariables.forEach(writeTopLevelVariableElement);
+ e.accessors.forEach(writePropertyAccessorElement);
+ e.functions.forEach(writeFunctionElement);
+ }
+
+ void writeUri(UriReferencedElement e, Source source) {
+ String uri = e.uri ?? source.uri.toString();
+ buffer.write('\'$uri\'');
+ if (withOffsets) {
+ buffer.write('(');
+ buffer.write('${e.uriOffset}, ');
+ buffer.write('${e.uriEnd})');
+ buffer.write(')');
+ }
+ }
+}
+
+class _Replacement {
+ final int offset;
+ final int end;
+ final String text;
+ _Replacement(this.offset, this.end, this.text);
+}

Powered by Google App Engine
This is Rietveld 408576698