blob: bd68a35353f2900d41006dddbc33118b1bef1958 [file] [log] [blame]
// Copyright (c) 2017, the Dart project authors. Please see the AUTHORS file
// for details. All rights reserved. Use of this source code is governed by a
// BSD-style license that can be found in the LICENSE file.
import 'dart:io';
import 'package:analyzer/dart/ast/ast.dart';
import 'package:analyzer/dart/ast/token.dart';
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/summary2/reference.dart';
import 'package:analyzer/src/task/inference_error.dart';
import 'package:test/test.dart';
import 'resolved_ast_printer.dart';
/// Set this path to automatically replace expectations in invocations of
/// [checkElementText] with the new actual texts.
const String? _testPath = null;
/// The list of replacements that update expectations.
final List<_Replacement> _replacements = [];
/// The cached content of the file with the [_testPath].
String? _testCode;
/// The cache line information for the [_testPath] file.
LineInfo? _testCodeLines;
void applyCheckElementTextReplacements() {
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);
});
File(_testPath!).writeAsStringSync(newCode);
}
}
/// Write the given [library] elements into the canonical text presentation
/// taking into account the specified 'withX' options. Then compare the
/// actual text with the given [expected] one.
void checkElementText(
LibraryElement library,
String expected, {
bool withAliasElementArguments = false,
bool withCodeRanges = false,
bool withConstElements = true,
bool withExportScope = false,
bool withFullyResolvedAst = false,
bool withOffsets = false,
bool withSyntheticAccessors = false,
bool withSyntheticFields = false,
bool withTypes = false,
bool withTypeParameterVariance = false,
}) {
var writer = _ElementWriter(
selfUriStr: '${library.source.uri}',
withAliasElementArguments: withAliasElementArguments,
withCodeRanges: withCodeRanges,
withConstElements: withConstElements,
withExportScope: withExportScope,
withFullyResolvedAst: withFullyResolvedAst,
withOffsets: withOffsets,
withSyntheticAccessors: withSyntheticAccessors,
withSyntheticFields: withSyntheticFields,
withTypes: withTypes,
withTypeParameterVariance: withTypeParameterVariance,
);
writer.writeLibraryElement(library);
String actualText = writer.buffer.toString();
actualText =
actualText.split('\n').map((line) => line.trimRight()).join('\n');
if (_testPath != null && actualText != expected) {
if (_testCode == null) {
_testCode = File(_testPath!).readAsStringSync();
_testCodeLines = LineInfo.fromContent(_testCode!);
}
try {
throw 42;
} catch (e, trace) {
String traceString = trace.toString();
// Assuming traceString contains "$_testPath:$invocationLine:$column",
// figure out the value of invocationLine.
int testFilePathOffset = traceString.indexOf(_testPath!);
expect(testFilePathOffset, isNonNegative);
// Sanity check: there must be ':' after the path.
expect(traceString[testFilePathOffset + _testPath!.length], ':');
int lineOffset = testFilePathOffset + _testPath!.length + ':'.length;
int invocationLine = int.parse(traceString.substring(
lineOffset, traceString.indexOf(':', lineOffset)));
int invocationOffset =
_testCodeLines!.getOffsetOfLine(invocationLine - 1);
const String rawStringPrefix = "r'''";
int expectationOffset =
_testCode!.indexOf(rawStringPrefix, invocationOffset);
// Sanity check: there must be no other strings or blocks.
expect(_testCode!.substring(invocationOffset, expectationOffset),
isNot(anyOf(contains("'"), contains('"'), contains('}'))));
expectationOffset += rawStringPrefix.length;
int expectationEnd = _testCode!.indexOf("'''", expectationOffset);
_replacements.add(
_Replacement(expectationOffset, expectationEnd, '\n' + actualText));
}
}
// Print the actual text to simplify copy/paste into the expectation.
// if (actualText != expected) {
// print('-------- Actual --------');
// print(actualText + '------------------------');
// }
expect(actualText, expected);
}
/// Writes the canonical text presentation of elements.
class _ElementWriter {
final String? selfUriStr;
final bool withAliasElementArguments;
final bool withCodeRanges;
final bool withExportScope;
final bool withFullyResolvedAst;
final bool withOffsets;
final bool withConstElements;
final bool withSyntheticAccessors;
final bool withSyntheticFields;
final bool withTypes;
final bool withTypeParameterVariance;
final StringBuffer buffer = StringBuffer();
String indent = '';
_ElementWriter({
this.selfUriStr,
this.withAliasElementArguments = false,
this.withCodeRanges = false,
this.withConstElements = true,
this.withExportScope = false,
this.withFullyResolvedAst = false,
this.withOffsets = false,
this.withSyntheticAccessors = false,
this.withSyntheticFields = false,
this.withTypes = false,
this.withTypeParameterVariance = false,
});
bool isDynamicType(DartType type) => type is DynamicTypeImpl;
bool isEnumField(Element e) {
var enclosing = e.enclosingElement;
return enclosing is ClassElement && enclosing.isEnum;
}
void newLineIfNotEmpty() {
if (buffer.isNotEmpty) {
buffer.writeln();
}
}
void writeBodyModifiers(ExecutableElement e) {
if (e.isAsynchronous) {
expect(e.isSynchronous, isFalse);
buffer.write(' async');
}
if (e.isSynchronous && e.isGenerator) {
expect(e.isAsynchronous, isFalse);
buffer.write(' sync');
}
writeIf(e.isGenerator, '*');
}
void writeClassElement(ClassElement e) {
writeDocumentation(e);
writeMetadata(e, '', '\n');
writeIf(e.isAbstract && !e.isMixin, 'abstract ');
writeIf(!e.isSimplyBounded, 'notSimplyBounded ');
if (e.isEnum) {
buffer.write('enum ');
} else if (e.isMixin) {
buffer.write('mixin ');
} else {
buffer.write('class ');
}
writeIf(e.isMixinApplication, 'alias ');
writeName(e);
writeCodeRange(e);
writeTypeParameterElements(e.typeParameters, withDefault: true);
if (e.supertype != null && e.supertype!.element.name != 'Object' ||
e.mixins.isNotEmpty) {
buffer.write(' extends ');
writeType(e.supertype!);
}
if (e.isMixin) {
if (e.superclassConstraints.isEmpty) {
throw StateError('At least Object is expected.');
}
writeList(' on ', '', e.superclassConstraints, ', ', writeType);
}
writeList(' with ', '', e.mixins, ', ', writeType);
writeList(' implements ', '', e.interfaces, ', ', writeType);
buffer.writeln(' {');
_withIndent(() {
e.fields.forEach(writePropertyInducingElement);
e.accessors.forEach(writePropertyAccessorElement);
if (e.isEnum) {
expect(e.constructors, isEmpty);
} else {
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('}');
if (withFullyResolvedAst) {
_withIndent(() {
_writeResolvedMetadata(e.metadata);
_writeResolvedTypeParameters(e.typeParameters);
});
}
}
void writeCodeRange(Element e) {
if (withCodeRanges) {
var elementImpl = e as ElementImpl;
buffer.write('/*codeOffset=');
buffer.write(elementImpl.codeOffset);
buffer.write(', codeLength=');
buffer.write(elementImpl.codeLength);
buffer.write('*/');
}
}
void writeConstructorElement(ConstructorElement e) {
writeDocumentation(e, ' ');
writeMetadata(e, ' ', '\n');
buffer.write(' ');
writeIf(e.isSynthetic, 'synthetic ');
writeIf(e.isExternal, 'external ');
writeIf(e.isConst, 'const ');
writeIf(e.isFactory, 'factory ');
expect(e.isAbstract, isFalse);
buffer.write(e.enclosingElement.name);
if (e.name.isNotEmpty) {
buffer.write('.');
buffer.write(e.displayName);
}
if (!e.isSynthetic) {
writeCodeRange(e);
if (withOffsets) {
var periodOffset = e.periodOffset;
var nameEnd = e.nameEnd;
if (periodOffset != null && nameEnd != null) {
buffer.write('[periodOffset: $periodOffset]');
buffer.write('[nameOffset: ${e.nameOffset}]');
buffer.write('[nameEnd: $nameEnd]');
} else {
buffer.write('[nameOffset: ${e.nameOffset}]');
}
}
}
writeParameterElements(e.parameters);
{
var redirected = e.redirectedConstructor;
if (redirected != null) {
buffer.write(' = ');
writeType(redirected.returnType);
if (redirected.name.isNotEmpty) {
buffer.write('.');
buffer.write(redirected.name);
}
}
}
var initializers = (e as ConstructorElementImpl).constantInitializers;
if (withFullyResolvedAst) {
buffer.writeln(';');
_withIndent(() {
if (initializers.isNotEmpty) {
_writelnWithIndent('constantInitializers');
_withIndent(() {
for (var initializer in initializers) {
_writeResolvedNode(initializer);
}
});
}
_writeParameterElementDefaultValues(e.parameters);
});
} else {
writeList(' : ', '', initializers, ', ', writeNode);
buffer.writeln(';');
}
expect(e.isAsynchronous, isFalse);
expect(e.isGenerator, isFalse);
}
void writeDocumentation(Element e, [String prefix = '']) {
var comment = e.documentationComment;
if (comment != null) {
if (comment.startsWith('///')) {
comment = comment.split('\n').join('\n$prefix');
}
buffer.write(prefix);
buffer.writeln(comment);
}
}
void writeExportElement(ExportElement e) {
writeMetadata(e, '', '\n');
buffer.write('export ');
writeUri(e.exportedLibrary?.source);
e.combinators.forEach(writeNamespaceCombinator);
buffer.writeln(';');
}
void writeExportScope(LibraryElement e) {
if (!withExportScope) return;
buffer.writeln();
buffer.writeln('-' * 20);
buffer.writeln('Exports:');
var map = e.exportNamespace.definedNames;
var names = map.keys.toList()..sort();
for (var name in names) {
var element = map[name];
var elementLocationStr = _getElementLocationString(element);
buffer.writeln(' $name: $elementLocationStr');
}
}
void writeExtensionElement(ExtensionElement e) {
writeDocumentation(e);
writeMetadata(e, '', '\n');
buffer.write('extension ');
writeName(e);
writeCodeRange(e);
writeTypeParameterElements(e.typeParameters, withDefault: false);
buffer.write(' on ');
writeType(e.extendedType);
buffer.writeln(' {');
_withIndent(() {
e.fields.forEach(writePropertyInducingElement);
e.accessors.forEach(writePropertyAccessorElement);
e.methods.forEach(writeMethodElement);
});
buffer.writeln('}');
if (withFullyResolvedAst) {
_withIndent(() {
_writeResolvedMetadata(e.metadata);
_writeResolvedTypeParameters(e.typeParameters);
});
}
}
void writeFunctionElement(FunctionElement e) {
writeDocumentation(e);
writeMetadata(e, '', '\n');
writeIf(e.isExternal, 'external ');
writeType2(e.returnType);
writeName(e);
writeCodeRange(e);
writeTypeParameterElements(e.typeParameters, withDefault: false);
writeParameterElements(e.parameters);
writeBodyModifiers(e);
buffer.writeln(' {}');
if (withFullyResolvedAst) {
_withIndent(() {
_writeParameterElementDefaultValues(e.parameters);
});
}
}
void writeIf(bool flag, String str) {
if (flag) {
buffer.write(str);
}
}
void writeImportElement(ImportElement e) {
if (!e.isSynthetic) {
writeMetadata(e, '', '\n');
buffer.write('import ');
writeUri(e.importedLibrary?.source);
writeIf(e.isDeferred, ' deferred');
var prefix = e.prefix;
if (prefix != null) {
buffer.write(' as ');
writeName(prefix);
if (withOffsets) {
buffer.write('(${prefix.nameOffset})');
}
}
e.combinators.forEach(writeNamespaceCombinator);
buffer.writeln(';');
if (withFullyResolvedAst) {
_withIndent(() {
_writeResolvedMetadata(e.metadata);
});
}
}
}
void writeInterfaceTypeArgsComment(Expression e) {
var typeArguments = (e.staticType as InterfaceType).typeArguments;
writeList('/*typeArgs=', '*/', typeArguments, ',', writeType);
}
void writeLibraryElement(LibraryElement e) {
if (e.documentationComment != null) {
buffer.writeln(e.documentationComment);
}
if (e.displayName != '') {
writeMetadata(e, '', '\n');
buffer.write('library ');
writeName(e);
buffer.writeln(';');
}
e.imports.forEach(writeImportElement);
e.exports.forEach(writeExportElement);
e.parts.forEach(writePartElement);
e.units.forEach(writeUnitElement);
writeExportScope(e);
}
void writeList<T>(String open, String close, List<T> items, String separator,
Function(T item) writeItem,
{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 writeMetadata(Element e, String prefix, String separator) {
if (withFullyResolvedAst) {
return;
}
if (e.metadata.isNotEmpty) {
writeList(prefix, '', e.metadata, '$separator$prefix', (a) {
writeNode((a as ElementAnnotationImpl).annotationAst);
});
buffer.write(separator);
}
}
void writeMethodElement(MethodElement e) {
writeDocumentation(e, ' ');
writeMetadata(e, ' ', '\n');
buffer.write(' ');
writeIf(e.isExternal, 'external ');
writeIf(e.isStatic, 'static ');
writeType2(e.returnType);
writeName(e);
writeCodeRange(e);
writeTypeInferenceError(e);
writeTypeParameterElements(e.typeParameters, withDefault: false);
writeParameterElements(e.parameters);
writeBodyModifiers(e);
if (e.isAbstract) {
buffer.writeln(';');
} else {
buffer.writeln(' {}');
}
if (withFullyResolvedAst) {
_withIndent(() {
_writeResolvedTypeParameters(e.typeParameters);
_writeResolvedMetadata(e.metadata);
_writeParameterElementDefaultValues(e.parameters);
});
}
}
void writeName(Element e) {
buffer.write(e.displayName);
if (withOffsets) {
buffer.write('@');
buffer.write(e.nameOffset);
}
}
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 writeNode(AstNode? e, {Expression? enclosing}) {
bool needsParenthesis = e is Expression &&
enclosing != null &&
e.precedence < enclosing.precedence;
if (needsParenthesis) {
buffer.write('(');
}
if (e == null) {
buffer.write('<null>');
} else if (e is SimpleIdentifier && e.name == '#invalidConst') {
buffer.write('#invalidConst');
} else if (e is AdjacentStrings) {
writeList("'", "'", e.strings, '',
(StringLiteral s) => buffer.write(s.stringValue),
includeEmpty: true);
} else if (e is Annotation) {
buffer.write('@');
writeNode(e.name);
if (e.constructorName != null) {
buffer.write('.');
writeNode(e.constructorName);
}
if (e.arguments != null) {
writeList('(', ')', e.arguments!.arguments, ', ', writeNode,
includeEmpty: true);
}
} else if (e is AsExpression) {
writeNode(e.expression);
buffer.write(' as ');
writeNode(e.type);
} else if (e is AssertInitializer) {
buffer.write('assert(');
writeNode(e.condition);
if (e.message != null) {
buffer.write(', ');
writeNode(e.message);
}
buffer.write(')');
} else if (e is BinaryExpression) {
writeNode(e.leftOperand, enclosing: e);
buffer.write(' ');
buffer.write(e.operator.lexeme);
buffer.write(' ');
writeNode(e.rightOperand, enclosing: e);
} else if (e is BooleanLiteral) {
buffer.write(e.value);
} else if (e is ConditionalExpression) {
writeNode(e.condition);
buffer.write(' ? ');
writeNode(e.thenExpression);
buffer.write(' : ');
writeNode(e.elseExpression);
} else if (e is ConstructorFieldInitializer) {
writeNode(e.fieldName);
buffer.write(' = ');
writeNode(e.expression);
} else if (e is ConstructorName) {
writeNode(e.type);
if (e.name != null) {
buffer.write('.');
writeNode(e.name);
}
} else if (e is DoubleLiteral) {
buffer.write(e.value);
} else if (e is GenericFunctionType) {
if (e.returnType != null) {
writeNode(e.returnType);
buffer.write(' ');
}
buffer.write('Function');
if (e.typeParameters != null) {
writeList('<', '>', e.typeParameters!.typeParameters, ', ', writeNode);
}
writeList('(', ')', e.parameters.parameters, ', ', writeNode,
includeEmpty: true);
} else if (e is InstanceCreationExpression) {
if (e.keyword != null) {
buffer.write(e.keyword!.lexeme);
buffer.write(' ');
}
if (withTypes && e.constructorName.type.typeArguments == null) {
writeInterfaceTypeArgsComment(e);
}
writeNode(e.constructorName);
writeList('(', ')', e.argumentList.arguments, ', ', writeNode,
includeEmpty: true);
} else if (e is IntegerLiteral) {
buffer.write(e.value);
} else if (e is InterpolationExpression) {
buffer.write(r'${');
writeNode(e.expression);
buffer.write(r'}');
} else if (e is InterpolationString) {
buffer.write(e.value.replaceAll("'", r"\'"));
} else if (e is IsExpression) {
writeNode(e.expression);
buffer.write(e.notOperator == null ? ' is ' : ' is! ');
writeNode(e.type);
} else if (e is ListLiteral) {
if (e.constKeyword != null) {
buffer.write('const ');
}
if (e.typeArguments != null) {
writeList('<', '>', e.typeArguments!.arguments, ', ', writeNode);
} else if (withTypes) {
writeInterfaceTypeArgsComment(e);
}
writeList('[', ']', e.elements, ', ', writeNode, includeEmpty: true);
} else if (e is Label) {
writeNode(e.label);
buffer.write(': ');
} else if (e is SetOrMapLiteral) {
if (e.constKeyword != null) {
buffer.write('const ');
}
if (e.typeArguments != null) {
writeList('<', '>', e.typeArguments!.arguments, ', ', writeNode);
} else if (withTypes) {
writeInterfaceTypeArgsComment(e);
}
writeList('{', '}', e.elements, ', ', writeNode, includeEmpty: true);
if (e.isMap) {
buffer.write('/*isMap*/');
}
if (e.isSet) {
buffer.write('/*isSet*/');
}
} else if (e is MapLiteralEntry) {
writeNode(e.key);
buffer.write(': ');
writeNode(e.value);
} else if (e is MethodInvocation) {
if (e.target != null) {
writeNode(e.target);
buffer.write(e.operator);
}
writeNode(e.methodName);
if (e.typeArguments != null) {
writeList('<', '>', e.typeArguments!.arguments, ', ', writeNode);
}
writeList('(', ')', e.argumentList.arguments, ', ', writeNode,
includeEmpty: true);
} else if (e is NamedExpression) {
writeNode(e.name);
buffer.write(e.expression);
} else if (e is NullLiteral) {
buffer.write('null');
} else if (e is ParenthesizedExpression) {
writeNode(e.expression, enclosing: e);
} else if (e is PrefixExpression) {
buffer.write(e.operator.lexeme);
writeNode(e.operand, enclosing: e);
} else if (e is PrefixedIdentifier) {
writeNode(e.prefix);
buffer.write('.');
writeNode(e.identifier);
} else if (e is PropertyAccess) {
writeNode(e.target, enclosing: e);
buffer.write('.');
writeNode(e.propertyName);
} else if (e is RedirectingConstructorInvocation) {
buffer.write('this');
if (e.constructorName != null) {
buffer.write('.');
writeNode(e.constructorName);
}
writeList('(', ')', e.argumentList.arguments, ', ', writeNode,
includeEmpty: true);
} else if (e is SimpleFormalParameter) {
writeNode(e.type);
if (e.identifier != null) {
buffer.write(' ');
buffer.write(e.identifier!.name);
}
} else if (e is SimpleIdentifier) {
if (withConstElements) {
buffer.writeln();
buffer.write(' ' * 4);
buffer.write(e.name);
buffer.write('/*');
buffer.write('location: ');
buffer.write(_getElementLocationString(e.staticElement));
buffer.write('*/');
} else {
buffer.write(e.name);
}
} else if (e is SimpleStringLiteral) {
buffer.write("'");
buffer.write(e.value.replaceAll("'", r"\'"));
buffer.write("'");
} else if (e is StringInterpolation) {
buffer.write("'");
e.elements.forEach(writeNode);
buffer.write("'");
} else if (e is SuperConstructorInvocation) {
buffer.write('super');
if (e.constructorName != null) {
buffer.write('.');
writeNode(e.constructorName);
}
writeList('(', ')', e.argumentList.arguments, ', ', writeNode,
includeEmpty: true);
} else if (e is SuperExpression) {
buffer.write('super');
} else if (e is SymbolLiteral) {
buffer.write('#');
writeList('', '', e.components, '.',
(Token token) => buffer.write(token.lexeme));
} else if (e is ThisExpression) {
buffer.write('this');
} else if (e is ThrowExpression) {
buffer.write('throw ');
writeNode(e.expression);
} else if (e is TypeName) {
writeNode(e.name);
if (e.typeArguments != null) {
writeList('<', '>', e.typeArguments!.arguments, ', ', writeNode);
}
} else if (e is SpreadElement) {
buffer.write(e.spreadOperator.lexeme);
writeNode(e.expression);
} else if (e is IfElement) {
buffer.write('if (');
writeNode(e.condition);
buffer.write(') ');
writeNode(e.thenElement);
var elseElement = e.elseElement;
if (elseElement != null) {
buffer.write(' else ');
writeNode(elseElement);
}
} else {
fail('Unsupported expression type: ${e.runtimeType}');
}
if (needsParenthesis) {
buffer.write(')');
}
}
void writeParameterElement(ParameterElement e) {
String? defaultValueSeparator;
Expression? defaultValue;
String closeString;
if (e.isRequiredPositional) {
closeString = '';
} else if (e.isOptionalPositional) {
buffer.write('[');
defaultValueSeparator = ' = ';
defaultValue = (e as ConstVariableElement).constantInitializer;
closeString = ']';
} else if (e.isNamed) {
buffer.write('{');
defaultValueSeparator = ': ';
defaultValue = (e as ConstVariableElement).constantInitializer;
closeString = '}';
} else {
fail('Unknown parameter kind');
}
writeMetadata(e, '', ' ');
writeIf(e.isCovariant, 'covariant ');
writeIf(e.isFinal, 'final ');
writeType2(e.type);
if (e is FieldFormalParameterElement) {
buffer.write('this.');
}
writeName(e);
writeCodeRange(e);
if (!withFullyResolvedAst) {
if (e.typeParameters.isNotEmpty) {
buffer.write('/*');
writeTypeParameterElements(e.typeParameters, withDefault: false);
buffer.write('*/');
}
if (e.parameters.isNotEmpty) {
buffer.write('/*');
writeList('(', ')', e.parameters, ', ', writeParameterElement);
buffer.write('*/');
}
if (defaultValue != null) {
buffer.write(defaultValueSeparator);
writeNode(defaultValue);
}
}
buffer.write(closeString);
}
void writeParameterElements(List<ParameterElement> elements) {
writeList('(', ')', elements, ', ', writeParameterElement,
includeEmpty: true);
}
void writePartElement(CompilationUnitElement e) {
writeMetadata(e, '', '\n');
buffer.write('part ');
writeUri(e.source);
buffer.writeln(';');
}
void writePropertyAccessorElement(PropertyAccessorElement e) {
if (e.isSynthetic && !withSyntheticAccessors) {
return;
}
if (!e.isSynthetic) {
PropertyInducingElement variable = e.variable;
expect(variable, isNotNull);
if (e.isGetter) {
// Usually we have a synthetic property for a non-synthetic accessor.
expect(variable.isSynthetic, isTrue);
} else {
// But it is possible to have a non-synthetic property.
// class A {
// final int foo;
// set foo(int newValue) {}
// }
}
var variableEnclosing = variable.enclosingElement;
if (variableEnclosing is CompilationUnitElement) {
expect(variableEnclosing.topLevelVariables, contains(variable));
} else if (variableEnclosing is ClassElement) {
expect(variableEnclosing.fields, contains(variable));
}
if (e.isGetter) {
expect(variable.getter, same(e));
if (variable.setter != null) {
expect(variable.setter!.variable, same(variable));
}
} else {
expect(variable.setter, same(e));
if (variable.getter != null) {
expect(variable.getter!.variable, same(variable));
}
}
}
if (e.enclosingElement is ClassElement ||
e.enclosingElement is ExtensionElement) {
writeDocumentation(e, ' ');
writeMetadata(e, ' ', '\n');
buffer.write(' ');
writeIf(e.isSynthetic, 'synthetic ');
writeIf(e.isStatic, 'static ');
} else {
writeDocumentation(e);
writeMetadata(e, '', '\n');
writeIf(e.isSynthetic, 'synthetic ');
}
writeIf(e.isExternal, 'external ');
writeType2(e.returnType);
if (e.isGetter) {
buffer.write('get ');
} else {
buffer.write('set ');
}
writeName(e);
expect(e.typeParameters, isEmpty);
if (e.isSetter || e.parameters.isNotEmpty) {
writeParameterElements(e.parameters);
}
writeBodyModifiers(e);
if (e.isAbstract || e.isExternal) {
buffer.writeln(';');
} else {
buffer.writeln(' {}');
}
}
void writePropertyInducingElement(PropertyInducingElement e) {
if (e.isSynthetic && !withSyntheticFields && !isEnumField(e)) {
return;
}
DartType type = e.type;
expect(type, isNotNull);
if (!e.isSynthetic) {
expect(e.getter, isNotNull);
_assertSyntheticAccessorEnclosing(e, e.getter!);
if (e.setter != null) {
_assertSyntheticAccessorEnclosing(e, e.setter!);
}
}
if (e.enclosingElement is ClassElement ||
e.enclosingElement is ExtensionElement) {
writeDocumentation(e, ' ');
writeMetadata(e, ' ', '\n');
buffer.write(' ');
writeIf(e.isSynthetic, 'synthetic ');
writeIf(e.isStatic, 'static ');
writeIf(e is FieldElementImpl && e.isAbstract, 'abstract ');
writeIf(e is FieldElementImpl && e.isCovariant, 'covariant ');
writeIf(e is FieldElementImpl && e.isExternal, 'external ');
} else {
writeDocumentation(e);
writeMetadata(e, '', '\n');
writeIf(e is TopLevelVariableElementImpl && e.isExternal, 'external ');
}
writeIf(e.isLate, 'late ');
writeIf(e.isFinal, 'final ');
writeIf(e.isConst, 'const ');
writeType2(type);
writeName(e);
writeCodeRange(e);
writeTypeInferenceError(e);
if (withFullyResolvedAst) {
buffer.writeln(';');
_withIndent(() {
_writeResolvedMetadata(e.metadata);
if (e is ConstVariableElement) {
var initializer = (e as ConstVariableElement).constantInitializer;
if (initializer != null) {
_writelnWithIndent('constantInitializer');
_withIndent(() {
_writeResolvedNode(initializer);
});
}
}
});
} else {
if (e is ConstVariableElement) {
var initializer = (e as ConstVariableElement).constantInitializer;
if (initializer != null) {
buffer.write(' = ');
writeNode(initializer);
}
}
buffer.writeln(';');
}
// TODO(scheglov) Paul: One of the things that was hardest to get right
// when resynthesizing the element model was the synthetic function for the
// initializer. Can we write that out (along with its return type)?
}
void writeType(DartType type) {
var typeStr = _typeStr(type);
buffer.write(typeStr);
if (withAliasElementArguments) {
var aliasElement = type.aliasElement;
if (aliasElement is TypeAliasElementImpl) {
buffer.write('<aliasElement: ');
buffer.write(_referenceToString(aliasElement.reference!));
var typeArguments = type.aliasArguments!;
writeList(', aliasArguments: [', ']', typeArguments, ', ', writeType);
buffer.write('>');
}
}
}
void writeType2(DartType type) {
writeType(type);
buffer.write(' ');
}
void writeTypeAliasElement(TypeAliasElement e) {
writeDocumentation(e);
writeMetadata(e, '', '\n');
writeIf(!e.isSimplyBounded, 'notSimplyBounded ');
buffer.write('typedef ');
writeName(e);
writeCodeRange(e);
writeTypeParameterElements(e.typeParameters, withDefault: true);
buffer.write(' = ');
var aliasedElement = e.aliasedElement;
if (aliasedElement is GenericFunctionTypeElement) {
writeType(aliasedElement.returnType);
buffer.write(' Function');
writeTypeParameterElements(aliasedElement.typeParameters,
withDefault: false);
writeParameterElements(aliasedElement.parameters);
} else {
writeType(e.aliasedType);
}
buffer.writeln(';');
if (withFullyResolvedAst) {
_withIndent(() {
_writeResolvedMetadata(e.metadata);
_writeResolvedTypeParameters(e.typeParameters);
var aliasedElement = e.aliasedElement;
if (aliasedElement is GenericFunctionTypeElement) {
_writelnWithIndent('aliasedElement');
_withIndent(() {
_writeResolvedTypeParameters(aliasedElement.typeParameters);
_writeResolvedParameterElements(aliasedElement.parameters);
});
}
});
}
}
void writeTypeInferenceError(Element e) {
TopLevelInferenceError? inferenceError;
if (e is MethodElementImpl) {
inferenceError = e.typeInferenceError;
} else if (e is PropertyInducingElementImpl) {
inferenceError = e.typeInferenceError;
}
if (inferenceError != null) {
String kindName = inferenceError.kind.toString();
if (kindName.startsWith('TopLevelInferenceErrorKind.')) {
kindName = kindName.substring('TopLevelInferenceErrorKind.'.length);
}
buffer.write('/*error: $kindName*/');
}
}
void writeTypeParameterElement(
TypeParameterElement e, {
required bool withDefault,
}) {
var impl = e as TypeParameterElementImpl;
writeMetadata(e, '', '\n');
// TODO (kallentu) : Clean up TypeParameterElementImpl casting once
// variance is added to the interface.
if (withTypeParameterVariance) {
var variance = impl.variance;
buffer.write(variance.toString() + ' ');
}
writeName(e);
writeCodeRange(e);
if (e.bound != null && !e.bound!.isDartCoreObject) {
buffer.write(' extends ');
writeType(e.bound!);
}
if (withDefault) {
var defaultType = impl.defaultType!;
if (defaultType is! DynamicTypeImpl) {
buffer.write(' = ');
writeType(defaultType);
}
}
}
void writeTypeParameterElements(
List<TypeParameterElement> elements, {
required bool withDefault,
}) {
if (!withFullyResolvedAst) {
writeList<TypeParameterElement>('<', '>', elements, ', ', (e) {
writeTypeParameterElement(e, withDefault: withDefault);
});
}
}
void writeUnitElement(CompilationUnitElement e) {
if (e.library.definingCompilationUnit != e) {
buffer.writeln('-' * 20);
buffer.writeln('unit: ${e.source.shortName}');
buffer.writeln();
}
e.typeAliases.forEach(writeTypeAliasElement);
e.enums.forEach(writeClassElement);
e.types.forEach(writeClassElement);
e.mixins.forEach(writeClassElement);
e.extensions.forEach(writeExtensionElement);
e.topLevelVariables.forEach(writePropertyInducingElement);
e.accessors.forEach(writePropertyAccessorElement);
e.functions.forEach(writeFunctionElement);
}
void writeUri(Source? source) {
if (source != null) {
Uri uri = source.uri;
String uriStr = uri.toString();
if (uri.isScheme('file')) {
uriStr = uri.pathSegments.last;
}
buffer.write('\'$uriStr\'');
} else {
buffer.write('\'<unresolved>\'');
}
}
/// Assert that the [accessor] of the [property] is correctly linked to
/// the same enclosing element as the [property].
void _assertSyntheticAccessorEnclosing(
PropertyInducingElement property, PropertyAccessorElement accessor) {
if (accessor.isSynthetic) {
// Usually we have a non-synthetic property, and a synthetic accessor.
} else {
// But it is possible to have a non-synthetic setter.
// class A {
// final int foo;
// set foo(int newValue) {}
// }
assert(accessor.isSetter, isTrue);
}
expect(accessor.variable, same(property));
var propertyEnclosing = property.enclosingElement;
expect(accessor.enclosingElement, same(propertyEnclosing));
if (propertyEnclosing is CompilationUnitElement) {
expect(propertyEnclosing.accessors, contains(accessor));
} else if (propertyEnclosing is ClassElement) {
expect(propertyEnclosing.accessors, contains(accessor));
}
}
String _getElementLocationString(Element? element) {
if (element == null || element is MultiplyDefinedElement) {
return 'null';
}
String onlyName(String uri) {
if (uri.startsWith('file:///')) {
return uri.substring(uri.lastIndexOf('/') + 1);
}
return uri;
}
var location = element.location!;
List<String> components = location.components.toList();
if (components.isNotEmpty) {
components[0] = onlyName(components[0]);
}
if (components.length >= 2) {
components[1] = onlyName(components[1]);
if (components[0] == components[1]) {
components.removeAt(0);
}
}
return components.join(';');
}
String _referenceToString(Reference reference) {
var parent = reference.parent!;
if (parent.parent == null) {
var libraryUriStr = reference.name;
if (libraryUriStr == selfUriStr) {
return 'self';
}
return libraryUriStr;
}
// Ignore the unit, skip to the library.
if (parent.name == '@unit') {
return _referenceToString(parent.parent!);
}
var name = reference.name;
return _referenceToString(parent) + '::$name';
}
String? _typeStr(DartType? type) {
return type?.getDisplayString(
withNullability: true,
);
}
void _withIndent(void Function() f) {
var savedIndent = indent;
indent = '$savedIndent ';
f();
indent = savedIndent;
}
void _writelnTypeWithIndent(String name, DartType? type) {
buffer.write(indent);
buffer.write('$name: ');
buffer.writeln(_typeStr(type));
}
void _writelnWithIndent(String line) {
buffer.write(indent);
buffer.writeln(line);
}
void _writeParameterElementDefaultValues(
List<ParameterElement> parameters, {
String enclosingNames = '',
}) {
for (var parameter in parameters) {
if (parameter is ConstVariableElement) {
var defaultParameter = parameter as ConstVariableElement;
var defaultValue = defaultParameter.constantInitializer;
if (defaultValue != null) {
_writelnWithIndent(enclosingNames + parameter.name);
_withIndent(() {
_writeResolvedNode(defaultValue);
});
}
}
var subParameters = parameter.parameters;
_withIndent(() {
_writeParameterElementDefaultValues(
subParameters,
enclosingNames: enclosingNames + parameter.name + '::',
);
});
}
}
void _writeResolvedMetadata(List<ElementAnnotation> metadata) {
if (metadata.isNotEmpty) {
_writelnWithIndent('metadata');
_withIndent(() {
for (var annotation in metadata) {
annotation as ElementAnnotationImpl;
_writeResolvedNode(annotation.annotationAst);
}
});
}
}
void _writeResolvedNode(AstNode node) {
buffer.write(indent);
node.accept(
ResolvedAstPrinter(
selfUriStr: selfUriStr,
sink: buffer,
indent: indent,
withNullability: true,
),
);
}
void _writeResolvedParameterElements(
List<ParameterElement> parameters, {
String enclosingNames = '',
}) {
if (parameters.isNotEmpty) {
_writelnWithIndent('parameters');
_withIndent(() {
var index = 0;
for (var formalParameter in parameters) {
var metadata = formalParameter.metadata;
var name = formalParameter.name;
if (name.isEmpty) {
name = '$index';
}
_writelnWithIndent(name);
_withIndent(() {
_writeResolvedMetadata(metadata);
var subParameters = formalParameter.parameters;
_withIndent(() {
_writeResolvedParameterElements(
subParameters,
enclosingNames: enclosingNames + name + '::',
);
});
});
index++;
}
});
}
}
void _writeResolvedTypeParameters(List<TypeParameterElement> elements) {
if (elements.isNotEmpty) {
_writelnWithIndent('typeParameters');
_withIndent(() {
for (var e in elements) {
e as TypeParameterElementImpl;
_writelnWithIndent(e.name);
_withIndent(() {
_writelnTypeWithIndent('bound', e.bound);
_writelnTypeWithIndent('defaultType', e.defaultType);
_writeResolvedMetadata(e.metadata);
});
}
});
}
}
}
class _Replacement {
final int offset;
final int end;
final String text;
_Replacement(this.offset, this.end, this.text);
}