blob: 3397ab65f8fcd08956a21e235fc6501362f4d7bf [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 'package:analyzer/dart/analysis/features.dart';
import 'package:analyzer/dart/analysis/results.dart';
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/nullability_suffix.dart';
import 'package:analyzer/dart/element/type.dart';
import 'package:analyzer/dart/element/type_provider.dart';
import 'package:analyzer/src/dart/ast/utilities.dart';
import 'package:analyzer/src/dart/element/type.dart';
import 'package:analyzer/src/generated/source.dart';
import 'package:analyzer_plugin/protocol/protocol_common.dart'
hide Element, ElementKind;
import 'package:analyzer_plugin/src/utilities/change_builder/change_builder_core.dart';
import 'package:analyzer_plugin/src/utilities/charcodes.dart';
import 'package:analyzer_plugin/src/utilities/library.dart';
import 'package:analyzer_plugin/src/utilities/string_utilities.dart';
import 'package:analyzer_plugin/utilities/change_builder/change_builder_core.dart';
import 'package:analyzer_plugin/utilities/change_builder/change_builder_dart.dart';
import 'package:analyzer_plugin/utilities/range_factory.dart';
import 'package:dart_style/dart_style.dart';
/// An [EditBuilder] used to build edits in Dart files.
class DartEditBuilderImpl extends EditBuilderImpl implements DartEditBuilder {
final List<String> _KNOWN_METHOD_NAME_PREFIXES = ['get', 'is', 'to'];
/// Whether [_enclosingClass] and [_enclosingExecutable] have been
/// initialized.
bool _hasEnclosingElementsInitialized = false;
/// The enclosing class element, possibly `null`.
/// This field is lazily initialized in [_initializeEnclosingElements].
ClassElement? _enclosingClass;
/// The enclosing executable element, possibly `null`.
/// This field is lazily initialized in [_initializeEnclosingElements].
ExecutableElement? _enclosingExecutable;
/// If not `null`, [write] will copy everything into this buffer.
StringBuffer? _carbonCopyBuffer;
/// Initialize a newly created builder to build a source edit.
DartEditBuilderImpl(
DartFileEditBuilderImpl sourceFileEditBuilder, int offset, int length)
: super(sourceFileEditBuilder, offset, length);
DartFileEditBuilderImpl get dartFileEditBuilder =>
fileEditBuilder as DartFileEditBuilderImpl;
@override
void addLinkedEdit(String groupName,
void Function(DartLinkedEditBuilder builder) buildLinkedEdit) =>
super.addLinkedEdit(groupName,
(builder) => buildLinkedEdit(builder as DartLinkedEditBuilder));
@override
bool canWriteType(DartType? type, {ExecutableElement? methodBeingCopied}) =>
type != null && !type.isDynamic
? _canWriteType(type, methodBeingCopied: methodBeingCopied)
: false;
@override
LinkedEditBuilderImpl createLinkedEditBuilder() {
return DartLinkedEditBuilderImpl(this);
}
/// Returns the indentation with the given [level].
String getIndent(int level) => ' ' * level;
@override
void write(String string) {
super.write(string);
_carbonCopyBuffer?.write(string);
}
@override
void writeClassDeclaration(String name,
{Iterable<DartType>? interfaces,
bool isAbstract = false,
void Function()? membersWriter,
Iterable<DartType>? mixins,
String? nameGroupName,
DartType? superclass,
String? superclassGroupName}) {
// TODO(brianwilkerson) Add support for type parameters, probably as a
// parameterWriter parameter.
if (isAbstract) {
write(Keyword.ABSTRACT.lexeme);
write(' ');
}
write('class ');
if (nameGroupName == null) {
write(name);
} else {
addSimpleLinkedEdit(nameGroupName, name);
}
if (superclass != null) {
write(' extends ');
writeType(superclass, groupName: superclassGroupName);
} else if (mixins != null && mixins.isNotEmpty) {
// TODO(brianwilkerson) Remove this branch when 2.1 semantics are
// supported everywhere.
write(' extends Object ');
}
writeTypes(mixins, prefix: ' with ');
writeTypes(interfaces, prefix: ' implements ');
writeln(' {');
if (membersWriter != null) {
membersWriter();
}
write('}');
}
@override
void writeConstructorDeclaration(String className,
{ArgumentList? argumentList,
void Function()? bodyWriter,
String? classNameGroupName,
SimpleIdentifier? constructorName,
String? constructorNameGroupName,
List<String>? fieldNames,
void Function()? initializerWriter,
bool isConst = false,
void Function()? parameterWriter}) {
if (isConst) {
write(Keyword.CONST.lexeme);
write(' ');
}
if (classNameGroupName == null) {
write(className);
} else {
addSimpleLinkedEdit(classNameGroupName, className);
}
if (constructorName != null) {
write('.');
if (constructorNameGroupName == null) {
write(constructorName.name);
} else {
addSimpleLinkedEdit(constructorNameGroupName, constructorName.name);
}
}
write('(');
if (parameterWriter != null) {
parameterWriter();
} else if (argumentList != null) {
writeParametersMatchingArguments(argumentList);
} else if (fieldNames != null) {
for (var i = 0; i < fieldNames.length; i++) {
if (i > 0) {
write(', ');
}
write('this.');
write(fieldNames[i]);
}
}
write(')');
if (initializerWriter != null) {
write(' : ');
initializerWriter();
}
if (bodyWriter != null) {
bodyWriter();
} else {
write(';');
}
}
@override
void writeFieldDeclaration(String name,
{void Function()? initializerWriter,
bool isConst = false,
bool isFinal = false,
bool isStatic = false,
String? nameGroupName,
DartType? type,
String? typeGroupName}) {
if (isStatic) {
write(Keyword.STATIC.lexeme);
write(' ');
}
var typeRequired = true;
if (isConst) {
write(Keyword.CONST.lexeme);
write(' ');
typeRequired = false;
} else if (isFinal) {
write(Keyword.FINAL.lexeme);
write(' ');
typeRequired = false;
}
if (type != null) {
writeType(type, groupName: typeGroupName, required: true);
write(' ');
} else if (typeRequired) {
write(Keyword.VAR.lexeme);
write(' ');
}
if (nameGroupName != null) {
addSimpleLinkedEdit(nameGroupName, name);
} else {
write(name);
}
if (initializerWriter != null) {
write(' = ');
initializerWriter();
}
write(';');
}
@override
void writeFunctionDeclaration(String name,
{void Function()? bodyWriter,
bool isStatic = false,
String? nameGroupName,
void Function()? parameterWriter,
DartType? returnType,
String? returnTypeGroupName}) {
if (isStatic) {
write(Keyword.STATIC.lexeme);
write(' ');
}
if (returnType != null) {
if (writeType(returnType, groupName: returnTypeGroupName)) {
write(' ');
}
}
if (nameGroupName != null) {
addSimpleLinkedEdit(nameGroupName, name);
} else {
write(name);
}
write('(');
if (parameterWriter != null) {
parameterWriter();
}
write(')');
if (bodyWriter == null) {
if (returnType != null) {
write(' => null;');
} else {
write(' {}');
}
} else {
write(' ');
bodyWriter();
}
}
@override
void writeGetterDeclaration(String name,
{void Function()? bodyWriter,
bool isStatic = false,
String? nameGroupName,
DartType? returnType,
String? returnTypeGroupName}) {
if (isStatic) {
write(Keyword.STATIC.lexeme);
write(' ');
}
if (returnType != null && !returnType.isDynamic) {
if (writeType(returnType, groupName: returnTypeGroupName)) {
write(' ');
}
}
write(Keyword.GET.lexeme);
write(' ');
if (nameGroupName != null) {
addSimpleLinkedEdit(nameGroupName, name);
} else {
write(name);
}
if (bodyWriter == null) {
write(' => null;');
} else {
write(' ');
bodyWriter();
}
}
@override
void writeImportedName(List<Uri> uris, String name) {
assert(uris.isNotEmpty);
var imports = <ImportElement>[];
for (var uri in uris) {
imports.addAll(dartFileEditBuilder._getImportsForUri(uri));
}
var import = _getBestImportForName(imports, name);
if (import == null) {
var library = dartFileEditBuilder._importLibrary(uris[0]);
var prefix = library.prefix;
if (prefix != null) {
write(prefix);
write('.');
}
} else {
var prefix = import.prefix;
if (prefix != null) {
write(prefix.displayName);
write('.');
}
}
write(name);
}
@override
void writeLocalVariableDeclaration(String name,
{void Function()? initializerWriter,
bool isConst = false,
bool isFinal = false,
String? nameGroupName,
DartType? type,
String? typeGroupName}) {
var typeRequired = true;
if (isConst) {
write(Keyword.CONST.lexeme);
typeRequired = false;
} else if (isFinal) {
write(Keyword.FINAL.lexeme);
typeRequired = false;
}
if (type != null) {
if (!typeRequired) {
// The type is required unless we've written a keyword.
write(' ');
}
writeType(type, groupName: typeGroupName);
} else if (typeRequired) {
write(Keyword.VAR.lexeme);
}
write(' ');
if (nameGroupName != null) {
addSimpleLinkedEdit(nameGroupName, name);
} else {
write(name);
}
if (initializerWriter != null) {
write(' = ');
initializerWriter();
}
write(';');
}
@override
void writeMixinDeclaration(String name,
{Iterable<DartType>? interfaces,
void Function()? membersWriter,
String? nameGroupName,
Iterable<DartType>? superclassConstraints}) {
// TODO(brianwilkerson) Add support for type parameters, probably as a
// parameterWriter parameter.
write('mixin ');
if (nameGroupName == null) {
write(name);
} else {
addSimpleLinkedEdit(nameGroupName, name);
}
writeTypes(superclassConstraints, prefix: ' on ');
writeTypes(interfaces, prefix: ' implements ');
writeln(' {');
if (membersWriter != null) {
membersWriter();
}
write('}');
}
@override
void writeOverride(
ExecutableElement element, {
StringBuffer? displayTextBuffer,
String? returnTypeGroupName,
bool invokeSuper = false,
}) {
void withCarbonCopyBuffer(Function() f) {
_carbonCopyBuffer = displayTextBuffer;
try {
f();
} finally {
_carbonCopyBuffer = null;
}
}
var prefix = getIndent(1);
var prefix2 = getIndent(2);
var elementKind = element.kind;
var isGetter = elementKind == ElementKind.GETTER;
var isSetter = elementKind == ElementKind.SETTER;
var isMethod = elementKind == ElementKind.METHOD;
var isOperator = isMethod && (element as MethodElement).isOperator;
var memberName = element.displayName;
// @override
writeln('@override');
write(prefix);
if (isGetter) {
writeln('// TODO: implement ${element.displayName}');
write(prefix);
}
// return type
var returnType = element.returnType;
if (!isSetter) {
var typeWritten = writeType(returnType,
groupName: returnTypeGroupName, methodBeingCopied: element);
if (typeWritten) {
write(' ');
}
}
if (isGetter) {
write(Keyword.GET.lexeme);
write(' ');
} else if (isSetter) {
write(Keyword.SET.lexeme);
write(' ');
} else if (isOperator) {
write(Keyword.OPERATOR.lexeme);
write(' ');
}
// name
withCarbonCopyBuffer(() {
write(memberName);
});
// parameters + body
if (isGetter) {
if (invokeSuper) {
write(' => ');
selectAll(() {
write('super.');
write(memberName);
});
writeln(';');
} else {
write(' => ');
selectAll(() {
write('throw UnimplementedError()');
});
write(';');
}
displayTextBuffer?.write(' => …');
} else {
var parameters = element.parameters;
withCarbonCopyBuffer(() {
writeTypeParameters(element.type.typeFormals,
methodBeingCopied: element);
writeParameters(parameters, methodBeingCopied: element);
});
writeln(' {');
// TO-DO
write(prefix2);
write('// TODO: implement $memberName');
if (isSetter) {
if (invokeSuper) {
writeln();
write(prefix2);
selectAll(() {
write('super.');
write(memberName);
write(' = ');
write(parameters[0].name);
write(';');
});
writeln();
} else {
selectHere();
writeln();
}
} else if (returnType.isVoid) {
if (invokeSuper) {
writeln();
write(prefix2);
selectAll(() {
write('super');
_writeSuperMemberInvocation(element, memberName, parameters);
});
writeln();
} else {
selectHere();
writeln();
}
} else {
writeln();
write(prefix2);
if (invokeSuper) {
selectAll(() {
write('return super');
_writeSuperMemberInvocation(element, memberName, parameters);
});
} else {
selectAll(() {
write('throw UnimplementedError();');
});
}
writeln();
}
// close method
write(prefix);
write('}');
displayTextBuffer?.write(' { … }');
}
}
@override
void writeParameter(String name,
{bool isCovariant = false,
bool isRequiredNamed = false,
ExecutableElement? methodBeingCopied,
String? nameGroupName,
DartType? type,
String? typeGroupName,
bool isRequiredType = false}) {
bool writeType() {
if (typeGroupName != null) {
late bool hasType;
addLinkedEdit(typeGroupName, (DartLinkedEditBuilder builder) {
hasType = _writeType(type,
methodBeingCopied: methodBeingCopied, required: isRequiredType);
builder.addSuperTypesAsSuggestions(type);
});
return hasType;
}
return _writeType(type, methodBeingCopied: methodBeingCopied);
}
void writeName() {
if (nameGroupName != null) {
addLinkedEdit(nameGroupName, (DartLinkedEditBuilder builder) {
write(name);
});
} else {
write(name);
}
}
if (isCovariant) {
write('covariant ');
}
if (isRequiredNamed) {
var library = dartFileEditBuilder.resolvedUnit.libraryElement;
if (library.featureSet.isEnabled(Feature.non_nullable)) {
write('required ');
} else {
var result = dartFileEditBuilder
.importLibraryElement(Uri.parse('package:meta/meta.dart'));
var prefix = result.prefix;
if (prefix != null) {
write('@$prefix.required ');
} else {
write('@required ');
}
}
}
if (type != null) {
var hasType = writeType();
if (name.isNotEmpty) {
if (hasType) {
write(' ');
}
writeName();
}
} else {
writeName();
}
}
@override
void writeParameterMatchingArgument(
Expression argument, int index, Set<String> usedNames) {
// append type name
var type = argument.staticType;
var library = dartFileEditBuilder.resolvedUnit.libraryElement;
if (type == null || type.isBottom || type.isDartCoreNull) {
type = DynamicTypeImpl.instance;
}
if (argument is NamedExpression &&
library.isNonNullableByDefault &&
type.nullabilitySuffix == NullabilitySuffix.none) {
write('required ');
}
if (writeType(type, addSupertypeProposals: true, groupName: 'TYPE$index')) {
write(' ');
}
// append parameter name
if (argument is NamedExpression) {
write(argument.name.label.name);
} else {
var suggestions =
_getParameterNameSuggestions(usedNames, type, argument, index);
var favorite = suggestions[0];
usedNames.add(favorite);
addSimpleLinkedEdit('PARAM$index', favorite,
kind: LinkedEditSuggestionKind.PARAMETER, suggestions: suggestions);
}
}
@override
void writeParameters(Iterable<ParameterElement> parameters,
{ExecutableElement? methodBeingCopied, bool requiredTypes = false}) {
var parameterNames = <String>{};
for (var i = 0; i < parameters.length; i++) {
var name = parameters.elementAt(i).name;
if (name.isNotEmpty) {
parameterNames.add(name);
}
}
write('(');
var sawNamed = false;
var sawPositional = false;
for (var i = 0; i < parameters.length; i++) {
var parameter = parameters.elementAt(i);
if (i > 0) {
write(', ');
}
// Might be optional
if (parameter.isNamed) {
if (!sawNamed) {
write('{');
sawNamed = true;
}
} else if (parameter.isOptionalPositional) {
if (!sawPositional) {
write('[');
sawPositional = true;
}
}
// parameter
var name = parameter.name;
if (name.isEmpty) {
name = _generateUniqueName(parameterNames, 'p');
parameterNames.add(name);
}
var groupPrefix =
methodBeingCopied != null ? '${methodBeingCopied.name}:' : '';
writeParameter(name,
isCovariant: parameter.isCovariant,
isRequiredNamed: parameter.isRequiredNamed,
methodBeingCopied: methodBeingCopied,
nameGroupName: parameter.isNamed ? null : '${groupPrefix}PARAM$i',
type: parameter.type,
typeGroupName: '${groupPrefix}TYPE$i',
isRequiredType: requiredTypes);
// default value
var defaultCode = parameter.defaultValueCode;
if (defaultCode != null) {
write(' = ');
write(defaultCode);
}
}
// close parameters
if (sawNamed) {
write('}');
}
if (sawPositional) {
write(']');
}
write(')');
}
@override
void writeParametersMatchingArguments(ArgumentList argumentList) {
// TODO(brianwilkerson) Handle the case when there are required parameters
// after named parameters.
var usedNames = <String>{};
List<Expression> arguments = argumentList.arguments;
var hasNamedParameters = false;
for (var i = 0; i < arguments.length; i++) {
var argument = arguments[i];
if (i > 0) {
write(', ');
}
if (argument is NamedExpression && !hasNamedParameters) {
hasNamedParameters = true;
write('{');
}
writeParameterMatchingArgument(argument, i, usedNames);
}
if (hasNamedParameters) {
write('}');
}
}
@override
void writeReference(Element element) {
if (element.enclosingElement is CompilationUnitElement) {
_writeLibraryReference(element);
}
write(element.displayName);
}
@override
void writeSetterDeclaration(String name,
{void Function()? bodyWriter,
bool isStatic = false,
String? nameGroupName,
DartType? parameterType,
String? parameterTypeGroupName}) {
if (isStatic) {
write(Keyword.STATIC.lexeme);
write(' ');
}
write(Keyword.SET.lexeme);
write(' ');
if (nameGroupName != null) {
addSimpleLinkedEdit(nameGroupName, name);
} else {
write(name);
}
write('(');
if (parameterType != null && !parameterType.isDynamic) {
if (writeType(parameterType, groupName: parameterTypeGroupName)) {
write(' ');
}
}
// TODO(brianwilkerson) The name of the setter is unlikely to be a good name
// for the parameter. We need to find a better name to produce here.
write(name);
write(') ');
if (bodyWriter == null) {
write('{}');
} else {
bodyWriter();
}
}
@override
bool writeType(DartType? type,
{bool addSupertypeProposals = false,
String? groupName,
ExecutableElement? methodBeingCopied,
bool required = false}) {
var wroteType = false;
if (type != null && !type.isDynamic) {
if (groupName != null) {
addLinkedEdit(groupName, (LinkedEditBuilder builder) {
wroteType = _writeType(type, methodBeingCopied: methodBeingCopied);
if (wroteType && addSupertypeProposals) {
_addSuperTypeProposals(builder, type, <DartType>{});
}
});
} else {
wroteType = _writeType(type, methodBeingCopied: methodBeingCopied);
}
}
if (!wroteType && required) {
write(Keyword.VAR.lexeme);
return true;
}
return wroteType;
}
@override
void writeTypeParameter(TypeParameterElement typeParameter,
{ExecutableElement? methodBeingCopied}) {
write(typeParameter.name);
if (typeParameter.bound != null) {
write(' extends ');
_writeType(typeParameter.bound, methodBeingCopied: methodBeingCopied);
}
}
@override
void writeTypeParameters(List<TypeParameterElement> typeParameters,
{ExecutableElement? methodBeingCopied}) {
if (typeParameters.isNotEmpty) {
write('<');
var isFirst = true;
for (var typeParameter in typeParameters) {
if (!isFirst) {
write(', ');
}
isFirst = false;
writeTypeParameter(typeParameter, methodBeingCopied: methodBeingCopied);
}
write('>');
}
}
@override
void writeTypes(Iterable<DartType>? types, {String? prefix}) {
if (types == null || types.isEmpty) {
return;
}
var first = true;
for (var type in types) {
if (first) {
if (prefix != null) {
write(prefix);
}
first = false;
} else {
write(', ');
}
writeType(type);
}
}
/// Adds [toAdd] items which are not excluded.
void _addAll(
Set<String> excluded, Set<String> result, Iterable<String> toAdd) {
for (var item in toAdd) {
// add name based on "item", but not "excluded"
for (var suffix = 1;; suffix++) {
// prepare name, just "item" or "item2", "item3", etc
var name = item;
if (suffix > 1) {
name += suffix.toString();
}
// add once found not excluded
if (!excluded.contains(name)) {
result.add(name);
break;
}
}
}
}
/// Adds to [result] either [c] or the first ASCII character after it.
void _addSingleCharacterName(
Set<String> excluded, Set<String> result, int c) {
while (c < $z) {
var name = String.fromCharCode(c);
// may be done
if (!excluded.contains(name)) {
result.add(name);
break;
}
// next character
c = c + 1;
}
}
void _addSuperTypeProposals(
LinkedEditBuilder builder, DartType? type, Set<DartType> alreadyAdded) {
if (type is InterfaceType && alreadyAdded.add(type)) {
builder.addSuggestion(
LinkedEditSuggestionKind.TYPE,
type.getDisplayString(withNullability: false),
);
_addSuperTypeProposals(builder, type.superclass, alreadyAdded);
for (var interfaceType in type.interfaces) {
_addSuperTypeProposals(builder, interfaceType, alreadyAdded);
}
}
}
/// Check if the code to reference [type] in this compilation unit can be
/// written.
///
/// See also [_writeType]
bool _canWriteType(DartType? type,
{ExecutableElement? methodBeingCopied, bool required = false}) {
type = _getVisibleType(type, methodBeingCopied: methodBeingCopied);
// If not a useful type, don't write it.
if (type == null) {
return false;
}
if (type.isDynamic) {
if (required) {
return true;
}
return false;
}
if (type.isBottom) {
var library = dartFileEditBuilder.resolvedUnit.libraryElement;
if (library.isNonNullableByDefault) {
return true;
}
return false;
}
var alias = type.alias;
if (alias != null) {
return true;
}
if (type is FunctionType) {
return true;
}
if (type is InterfaceType) {
return true;
}
if (type is NeverType) {
return true;
}
if (type is TypeParameterType) {
return true;
}
if (type is VoidType) {
return true;
}
throw UnimplementedError('(${type.runtimeType}) $type');
}
/// Generate a name that does not occur in [existingNames] that begins with
/// the given [prefix].
String _generateUniqueName(Set<String> existingNames, String prefix) {
var index = 1;
var name = '$prefix$index';
while (existingNames.contains(name)) {
index++;
name = '$prefix$index';
}
return name;
}
String? _getBaseNameFromExpression(Expression expression) {
if (expression is AsExpression) {
return _getBaseNameFromExpression(expression.expression);
} else if (expression is ParenthesizedExpression) {
return _getBaseNameFromExpression(expression.expression);
}
return _getBaseNameFromUnwrappedExpression(expression);
}
String? _getBaseNameFromLocationInParent(Expression expression) {
// value in named expression
if (expression.parent is NamedExpression) {
var namedExpression = expression.parent as NamedExpression;
if (namedExpression.expression == expression) {
return namedExpression.name.label.name;
}
}
// positional argument
var parameter = expression.staticParameterElement;
if (parameter != null) {
return parameter.displayName;
}
// unknown
return null;
}
String? _getBaseNameFromUnwrappedExpression(Expression expression) {
String? name;
// analyze expressions
if (expression is SimpleIdentifier) {
return expression.name;
} else if (expression is PrefixedIdentifier) {
return expression.identifier.name;
} else if (expression is PropertyAccess) {
return expression.propertyName.name;
} else if (expression is MethodInvocation) {
name = expression.methodName.name;
} else if (expression is InstanceCreationExpression) {
var constructorName = expression.constructorName;
var typeName = constructorName.type;
var typeNameIdentifier = typeName.name;
// new ClassName()
if (typeNameIdentifier is SimpleIdentifier) {
return typeNameIdentifier.name;
}
// new prefix.name();
if (typeNameIdentifier is PrefixedIdentifier) {
var prefixed = typeNameIdentifier;
// new prefix.ClassName()
if (prefixed.prefix.staticElement is PrefixElement) {
return prefixed.identifier.name;
}
// new ClassName.constructorName()
return prefixed.prefix.name;
}
} else if (expression is IndexExpression) {
name = _getBaseNameFromExpression(expression.realTarget);
if (name != null) {
if (name.endsWith('es')) {
name = name.substring(0, name.length - 2);
} else if (name.endsWith('s')) {
name = name.substring(0, name.length - 1);
}
}
}
// strip known prefixes
if (name != null) {
for (var i = 0; i < _KNOWN_METHOD_NAME_PREFIXES.length; i++) {
var prefix = _KNOWN_METHOD_NAME_PREFIXES[i];
if (name.startsWith(prefix)) {
if (name == prefix) {
return null;
} else if (isUpperCase(name.codeUnitAt(prefix.length))) {
return name.substring(prefix.length);
}
}
}
}
// done
return name;
}
/// Given a list of [imports] that do, or can, make the [name] visible in
/// scope, return the one that will lead to the cleanest code.
ImportElement? _getBestImportForName(
List<ImportElement> imports, String name) {
if (imports.isEmpty) {
return null;
} else if (imports.length == 1) {
return imports[0];
}
imports.sort((first, second) {
// Prefer imports that make the name visible.
var firstDefinesName = first.namespace.definedNames.containsKey(name);
var secondDefinesName = second.namespace.definedNames.containsKey(name);
if (firstDefinesName != secondDefinesName) {
return firstDefinesName ? -1 : 1;
}
// Prefer imports without prefixes.
var firstHasPrefix = first.prefix != null;
var secondHasPrefix = second.prefix != null;
if (firstHasPrefix != secondHasPrefix) {
return firstHasPrefix ? 1 : -1;
}
return 0;
});
return imports[0];
}
/// Returns all variants of names by removing leading words one by one.
List<String> _getCamelWordCombinations(String? name) {
var result = <String>[];
var parts = getCamelWords(name);
for (var i = 0; i < parts.length; i++) {
var s1 = parts[i].toLowerCase();
var s2 = parts.skip(i + 1).join();
var suggestion = '$s1$s2';
result.add(suggestion);
}
return result;
}
/// Return a list containing the suggested names for a parameter with the
/// given [type] whose value in one location is computed by the given
/// [expression]. The list will not contain any names in the set of [excluded]
/// names. The [index] is the index of the argument, used to create a name if
/// no better name could be created. The first name in the list will be the
/// best name.
List<String> _getParameterNameSuggestions(
Set<String> usedNames, DartType type, Expression expression, int index) {
var suggestions =
_getVariableNameSuggestionsForExpression(type, expression, usedNames);
if (suggestions.isNotEmpty) {
return suggestions;
}
// TODO(brianwilkerson) Verify that the name below is not in the set of used names.
return <String>['param$index'];
}
/// Returns possible names for a variable with the given expected type and
/// expression assigned.
List<String> _getVariableNameSuggestionsForExpression(DartType? expectedType,
Expression? assignedExpression, Set<String> excluded) {
var res = <String>{};
// use expression
if (assignedExpression != null) {
var nameFromExpression = _getBaseNameFromExpression(assignedExpression);
if (nameFromExpression != null) {
nameFromExpression = removeStart(nameFromExpression, '_');
_addAll(excluded, res, _getCamelWordCombinations(nameFromExpression));
}
var nameFromParent = _getBaseNameFromLocationInParent(assignedExpression);
if (nameFromParent != null) {
_addAll(excluded, res, _getCamelWordCombinations(nameFromParent));
}
}
// use type
if (expectedType != null && !expectedType.isDynamic) {
if (expectedType.isDartCoreInt) {
_addSingleCharacterName(excluded, res, $i);
} else if (expectedType.isDartCoreDouble) {
_addSingleCharacterName(excluded, res, $d);
} else if (expectedType.isDartCoreString) {
_addSingleCharacterName(excluded, res, $s);
} else if (expectedType is InterfaceType) {
var className = expectedType.element.name;
_addAll(excluded, res, _getCamelWordCombinations(className));
}
}
// done
return List.from(res);
}
/// If the given [type] is visible in either the [_enclosingExecutable] or
/// [_enclosingClass], or if there is a local equivalent to the type (such as
/// in the case of a type parameter from a superclass), then return the type
/// that is locally visible. Otherwise, return `null`.
DartType? _getVisibleType(DartType? type,
{ExecutableElement? methodBeingCopied}) {
if (type is InterfaceType) {
var element = type.element;
if (element.isPrivate &&
!dartFileEditBuilder._isDefinedLocally(element)) {
return null;
}
return type;
}
if (type is TypeParameterType) {
_initializeEnclosingElements();
var element = type.element;
var enclosing = element.enclosingElement;
while (enclosing is GenericFunctionTypeElement ||
enclosing is ParameterElement) {
enclosing = enclosing!.enclosingElement;
}
if (enclosing == _enclosingExecutable ||
enclosing == _enclosingClass ||
enclosing == methodBeingCopied) {
return type;
}
return null;
}
return type;
}
/// Initialize the [_enclosingClass] and [_enclosingExecutable].
void _initializeEnclosingElements() {
if (!_hasEnclosingElementsInitialized) {
var finder = _EnclosingElementFinder();
finder.find(dartFileEditBuilder.resolvedUnit.unit, offset);
_enclosingClass = finder.enclosingClass;
_enclosingExecutable = finder.enclosingExecutable;
_hasEnclosingElementsInitialized = true;
}
}
/// Write the import prefix to reference the [element], if needed.
///
/// The prefix is not needed if the [element] is defined in the target
/// library, or there is already an import without prefix that exports the
/// [element]. If there there are no existing import that exports the
/// [element], a library that exports the [element] is scheduled for import,
/// possibly with a prefix.
void _writeLibraryReference(Element element) {
// If the element is defined in the library, then no prefix needed.
if (dartFileEditBuilder._isDefinedLocally(element)) {
return;
}
// TODO(scheglov) We should use "methodBeingCopied" to verify that
// we really are just copying this type parameter.
if (element is TypeParameterElement) {
return;
}
var import = dartFileEditBuilder._getImportElement(element);
if (import != null) {
var prefix = import.prefix;
if (prefix != null) {
write(prefix.displayName);
write('.');
}
} else {
var library = element.library?.source.uri;
if (library != null) {
var import = dartFileEditBuilder._importLibrary(library);
var prefix = import.prefix;
if (prefix != null) {
write(prefix);
write('.');
}
}
}
}
void _writeSuperMemberInvocation(ExecutableElement element, String memberName,
List<ParameterElement> parameters) {
final isOperator = element.isOperator;
write(isOperator ? ' ' : '.');
write(memberName);
write(isOperator ? ' ' : '(');
for (var i = 0; i < parameters.length; i++) {
if (i > 0) {
write(', ');
}
write(parameters[i].name);
}
write(isOperator ? ';' : ');');
}
/// Write the code to reference [type] in this compilation unit.
///
/// If a [methodBeingCopied] is provided, then the type parameters of that
/// method will be duplicated in the copy and will therefore be visible.
///
/// If [required] it `true`, then the type will be written even if it would
/// normally be omitted, such as with `dynamic`.
///
/// Causes any libraries whose elements are used by the generated code, to be
/// imported.
bool _writeType(DartType? type,
{ExecutableElement? methodBeingCopied, bool required = false}) {
type = _getVisibleType(type, methodBeingCopied: methodBeingCopied);
// If not a useful type, don't write it.
if (type == null) {
return false;
}
if (type.isDynamic) {
if (required) {
write('dynamic');
return true;
}
return false;
}
if (type.isBottom) {
var library = dartFileEditBuilder.resolvedUnit.libraryElement;
if (library.isNonNullableByDefault) {
write('Never');
return true;
}
return false;
}
var alias = type.alias;
if (alias != null) {
_writeTypeElementArguments(
element: alias.element,
typeArguments: alias.typeArguments,
methodBeingCopied: methodBeingCopied,
);
_writeTypeNullability(type);
return true;
}
if (type is FunctionType) {
if (_writeType(type.returnType, methodBeingCopied: methodBeingCopied)) {
write(' ');
}
write('Function');
writeTypeParameters(type.typeFormals,
methodBeingCopied: methodBeingCopied);
writeParameters(type.parameters,
methodBeingCopied: methodBeingCopied, requiredTypes: true);
if (type.nullabilitySuffix == NullabilitySuffix.question) {
write('?');
}
return true;
}
if (type is InterfaceType) {
_writeTypeElementArguments(
element: type.element,
typeArguments: type.typeArguments,
methodBeingCopied: methodBeingCopied,
);
_writeTypeNullability(type);
return true;
}
if (type is NeverType) {
write('Never');
_writeTypeNullability(type);
return true;
}
if (type is TypeParameterType) {
write(type.element.name);
_writeTypeNullability(type);
return true;
}
if (type is VoidType) {
write('void');
return true;
}
throw UnimplementedError('(${type.runtimeType}) $type');
}
void _writeTypeElementArguments({
required Element element,
required List<DartType> typeArguments,
required ExecutableElement? methodBeingCopied,
}) {
// Ensure that the element is imported.
_writeLibraryReference(element);
// Write the simple name.
var name = element.displayName;
write(name);
// Write type arguments.
if (typeArguments.isNotEmpty) {
// Check if has arguments.
var hasArguments = false;
var allArgumentsVisible = true;
for (var argument in typeArguments) {
hasArguments = hasArguments || !argument.isDynamic;
allArgumentsVisible = allArgumentsVisible &&
_getVisibleType(argument, methodBeingCopied: methodBeingCopied) !=
null;
}
// Write type arguments only if they are useful.
if (hasArguments && allArgumentsVisible) {
write('<');
for (var i = 0; i < typeArguments.length; i++) {
var argument = typeArguments[i];
if (i != 0) {
write(', ');
}
_writeType(argument,
required: true, methodBeingCopied: methodBeingCopied);
}
write('>');
}
}
}
void _writeTypeNullability(DartType type) {
if (type.nullabilitySuffix == NullabilitySuffix.question) {
write('?');
}
}
}
/// A [FileEditBuilder] used to build edits for Dart files.
class DartFileEditBuilderImpl extends FileEditBuilderImpl
implements DartFileEditBuilder {
/// The resolved unit for the file.
final ResolvedUnitResult resolvedUnit;
/// The change builder for the library
/// or `null` if the receiver is the builder for the library.
final DartFileEditBuilderImpl? libraryChangeBuilder;
/// The optional generator of prefixes for new imports.
ImportPrefixGenerator? importPrefixGenerator;
/// A mapping from libraries that need to be imported in order to make visible
/// the names used in generated code, to information about these imports.
Map<Uri, _LibraryToImport> librariesToImport = {};
/// Initialize a newly created builder to build a source file edit within the
/// change being built by the given [changeBuilder]. The file being edited has
/// the given [resolvedUnit] and [timeStamp].
DartFileEditBuilderImpl(ChangeBuilderImpl changeBuilder, this.resolvedUnit,
int timeStamp, this.libraryChangeBuilder)
: super(changeBuilder, resolvedUnit.path, timeStamp);
@override
bool get hasEdits => super.hasEdits || librariesToImport.isNotEmpty;
@override
void addInsertion(
int offset, void Function(DartEditBuilder builder) buildEdit,
{bool insertBeforeExisting = false}) =>
super.addInsertion(
offset, (builder) => buildEdit(builder as DartEditBuilder),
insertBeforeExisting: insertBeforeExisting);
@override
void addReplacement(SourceRange range,
void Function(DartEditBuilder builder) buildEdit) =>
super.addReplacement(
range, (builder) => buildEdit(builder as DartEditBuilder));
@override
bool canWriteType(DartType? type, {ExecutableElement? methodBeingCopied}) {
var builder = createEditBuilder(0, 0);
return builder.canWriteType(type, methodBeingCopied: methodBeingCopied);
}
@override
void convertFunctionFromSyncToAsync(
FunctionBody? body, TypeProvider typeProvider) {
if (body == null || body.keyword != null) {
throw ArgumentError(
'The function must have a synchronous, non-generator body.');
}
if (body is! EmptyFunctionBody) {
addInsertion(body.offset, (EditBuilder builder) {
if (_isFusedWithPreviousToken(body.beginToken)) {
builder.write(' ');
}
builder.write('async ');
});
}
_replaceReturnTypeWithFuture(body, typeProvider);
}
@override
DartFileEditBuilderImpl copyWith(ChangeBuilderImpl changeBuilder,
{Map<DartFileEditBuilderImpl, DartFileEditBuilderImpl> editBuilderMap =
const {}}) {
var copy = DartFileEditBuilderImpl(changeBuilder, resolvedUnit,
fileEdit.fileStamp, editBuilderMap[libraryChangeBuilder]);
copy.fileEdit.edits.addAll(fileEdit.edits);
copy.importPrefixGenerator = importPrefixGenerator;
for (var entry in librariesToImport.entries) {
copy.librariesToImport[entry.key] = entry.value;
}
return copy;
}
@override
DartEditBuilderImpl createEditBuilder(int offset, int length) {
return DartEditBuilderImpl(this, offset, length);
}
@override
void finalize() {
if (librariesToImport.isNotEmpty) {
_addLibraryImports(librariesToImport.values);
}
}
@override
void format(SourceRange range) {
var newContent = resolvedUnit.content;
var newRangeOffset = range.offset;
var newRangeLength = range.length;
for (var edit in fileEdit.edits) {
newContent = edit.apply(newContent);
var lengthDelta = edit.replacement.length - edit.length;
if (edit.offset < newRangeOffset) {
newRangeOffset += lengthDelta;
} else if (edit.offset < newRangeOffset + newRangeLength) {
newRangeLength += lengthDelta;
}
}
var formattedResult = DartFormatter().formatSource(
SourceCode(
newContent,
isCompilationUnit: true,
selectionStart: newRangeOffset,
selectionLength: newRangeLength,
),
);
replaceEdits(
range,
SourceEdit(
range.offset,
range.length,
formattedResult.selectedText,
),
);
}
@override
String importLibrary(Uri uri) {
return _importLibrary(uri).uriText;
}
@override
ImportLibraryElementResult importLibraryElement(Uri uri) {
if (resolvedUnit.libraryElement.source.uri == uri) {
return ImportLibraryElementResultImpl(null);
}
for (var import in resolvedUnit.libraryElement.imports) {
var importedLibrary = import.importedLibrary;
if (importedLibrary != null && importedLibrary.source.uri == uri) {
return ImportLibraryElementResultImpl(import.prefix?.name);
}
}
importLibrary(uri);
return ImportLibraryElementResultImpl(null);
}
String importLibraryWithAbsoluteUri(Uri uri, [String? prefix]) {
return _importLibrary(uri, prefix: prefix, forceAbsolute: true).uriText;
}
String importLibraryWithRelativeUri(Uri uri, [String? prefix]) {
return _importLibrary(uri, prefix: prefix, forceRelative: true).uriText;
}
@override
void replaceTypeWithFuture(
TypeAnnotation? typeAnnotation, TypeProvider typeProvider) {
//
// Check whether the type needs to be replaced.
//
var type = typeAnnotation?.type;
if (type == null || type.isDynamic || type.isDartAsyncFuture) {
return;
}
addReplacement(range.node(typeAnnotation!), (builder) {
var futureType = typeProvider.futureType(type);
if (!builder.writeType(futureType)) {
builder.write('void');
}
});
}
/// Adds edits ensure that all the [imports] are imported into the library.
void _addLibraryImports(Iterable<_LibraryToImport> imports) {
// Prepare information about existing imports.
LibraryDirective? libraryDirective;
var importDirectives = <ImportDirective>[];
PartDirective? partDirective;
var unit = resolvedUnit.unit;
for (var directive in unit.directives) {
if (directive is LibraryDirective) {
libraryDirective = directive;
} else if (directive is ImportDirective) {
importDirectives.add(directive);
} else if (directive is PartDirective) {
partDirective = directive;
}
}
// Sort imports by URIs.
var importList = imports.toList();
importList.sort((a, b) => a.uriText.compareTo(b.uriText));
void writeImport(EditBuilder builder, _LibraryToImport import) {
builder.write("import '");
builder.write(import.uriText);
builder.write("'");
var prefix = import.prefix;
if (prefix != null) {
builder.write(' as ');
builder.write(prefix);
}
builder.write(';');
}
// Insert imports: between existing imports.
if (importDirectives.isNotEmpty) {
for (var import in importList) {
var isDart = import.uriText.startsWith('dart:');
var isPackage = import.uriText.startsWith('package:');
var inserted = false;
void insert(
{ImportDirective? prev,
required ImportDirective next,
bool trailingNewLine = false}) {
var lineInfo = resolvedUnit.lineInfo;
if (prev != null) {
var offset = prev.end;
var line = lineInfo.getLocation(offset).lineNumber;
Token? comment = prev.endToken.next?.precedingComments;
while (comment != null) {
if (lineInfo.getLocation(comment.offset).lineNumber == line) {
offset = comment.end;
}
comment = comment.next;
}
addInsertion(offset, (EditBuilder builder) {
builder.writeln();
writeImport(builder, import);
});
} else {
// Annotations attached to the first directive should remain above
// the newly inserted import, as they are treated as being for the
// file.
var isFirst =
next == (next.parent as CompilationUnit).directives.first;
var offset = isFirst
? next.firstTokenAfterCommentAndMetadata.offset
: next.offset;
addInsertion(offset, (EditBuilder builder) {
writeImport(builder, import);
builder.writeln();
if (trailingNewLine) {
builder.writeln();
}
});
}
inserted = true;
}
ImportDirective? lastExisting;
ImportDirective? lastExistingDart;
ImportDirective? lastExistingPackage;
var isLastExistingDart = false;
var isLastExistingPackage = false;
for (var existingImport in importDirectives) {
var existingUri = existingImport.uriContent ?? '';
var isExistingDart = existingUri.startsWith('dart:');
var isExistingPackage = existingUri.startsWith('package:');
var isExistingRelative = !existingUri.contains(':');
var isNewBeforeExisting = import.uriText.compareTo(existingUri) < 0;
if (isDart) {
if (!isExistingDart || isNewBeforeExisting) {
insert(
prev: lastExistingDart,
next: existingImport,
trailingNewLine: !isExistingDart);
break;
}
} else if (isPackage) {
if (isExistingRelative || isNewBeforeExisting) {
insert(
prev: lastExistingPackage,
next: existingImport,
trailingNewLine: isExistingRelative);
break;
}
} else {
if (!isExistingDart && !isExistingPackage && isNewBeforeExisting) {
insert(next: existingImport);
break;
}
}
lastExisting = existingImport;
if (isExistingDart) {
lastExistingDart = existingImport;
} else if (isExistingPackage) {
lastExistingPackage = existingImport;
}
isLastExistingDart = isExistingDart;
isLastExistingPackage = isExistingPackage;
}
if (!inserted) {
addInsertion(lastExisting!.end, (EditBuilder builder) {
if (isPackage) {
if (isLastExistingDart) {
builder.writeln();
}
} else {
if (isLastExistingDart || isLastExistingPackage) {
builder.writeln();
}
}
builder.writeln();
writeImport(builder, import);
});
}
}
return;
}
// Insert imports: after the library directive.
if (libraryDirective != null) {
addInsertion(libraryDirective.end, (EditBuilder builder) {
builder.writeln();
builder.writeln();
for (var i = 0; i < importList.length; i++) {
var import = importList[i];
writeImport(builder, import);
if (i != importList.length - 1) {
builder.writeln();
}
}
});
return;
}
// Insert imports: before a part directive.
if (partDirective != null) {
addInsertion(partDirective.offset, (EditBuilder builder) {
for (var i = 0; i < importList.length; i++) {
var import = importList[i];
writeImport(builder, import);
builder.writeln();
}
builder.writeln();
});
return;
}
// If still at the beginning of the file, add before the first declaration.
int offset;
var insertEmptyLineAfter = false;
if (unit.declarations.isNotEmpty) {
offset = unit.declarations.first.offset;
insertEmptyLineAfter = true;
} else if (fileEdit.edits.isNotEmpty) {
// If this file has edits (besides the imports) the imports should go
// at the same offset as those edits and _not_ at `unit.end`. This is
// because if the document is non-zero length, `unit.end` could be after
// where the new edits will be inserted, but imports should go before
// generated non-import code.
// Edits are always sorted such that the first one has the lowest offset.
offset = fileEdit.edits.first.offset;
// Also ensure there's a blank line between the imports and the other
// code.
insertEmptyLineAfter = fileEdit.edits.isNotEmpty;
} else {
offset = unit.end;
}
addInsertion(
offset,
(EditBuilder builder) {
for (var i = 0; i < importList.length; i++) {
var import = importList[i];
writeImport(builder, import);
builder.writeln();
if (i == importList.length - 1 && insertEmptyLineAfter) {
builder.writeln();
}
}
},
insertBeforeExisting: true,
);
}
/// Return the import element used to import the given [element] into the
/// target library, or `null` if the element was not imported, such as when
/// the element is declared in the same library.
ImportElement? _getImportElement(Element element) {
for (var import in resolvedUnit.libraryElement.imports) {
var definedNames = import.namespace.definedNames;
if (definedNames.containsValue(element)) {
return import;
}
}
return null;
}
Iterable<ImportElement> _getImportsForUri(Uri uri) sync* {
for (var import in resolvedUnit.libraryElement.imports) {
var importUri = import.importedLibrary?.source.uri;
if (importUri == uri) {
yield import;
}
}
}
/// Computes the best URI to import [uri] into the target library.
///
/// [uri] may be converted from an absolute URI to a relative URI depending on
/// user preferences/lints unless [forceAbsolute] or [forceRelative] are `true`.
String _getLibraryUriText(
Uri uri, {
bool forceAbsolute = false,
bool forceRelative = false,
}) {
var pathContext = resolvedUnit.session.resourceProvider.pathContext;
/// Returns the relative path to import [whatPath] into [resolvedUnit].
String getRelativePath(String whatPath) {
var libraryPath = resolvedUnit.libraryElement.source.fullName;
var libraryFolder = pathContext.dirname(libraryPath);
var relativeFile = pathContext.relative(whatPath, from: libraryFolder);
return pathContext.split(relativeFile).join('/');
}
if (uri.isScheme('file')) {
var whatPath = pathContext.fromUri(uri);
return getRelativePath(whatPath);
}
var preferRelative = _isLintEnabled('prefer_relative_imports');
if (forceRelative || (preferRelative && !forceAbsolute)) {
if (canBeRelativeImport(uri, resolvedUnit.uri)) {
var whatPath = resolvedUnit.session.uriConverter.uriToPath(uri);
if (whatPath != null) {
return getRelativePath(whatPath);
}
}
}
return uri.toString();
}
/// Arrange to have an import added for the library with the given [uri].
///
/// [uri] may be converted from an absolute URI to a relative URI depending on
/// user preferences/lints unless [forceAbsolute] or [forceRelative] are `true`.
_LibraryToImport _importLibrary(
Uri uri, {
String? prefix,
bool forceAbsolute = false,
bool forceRelative = false,
}) {
var import = (libraryChangeBuilder ?? this).librariesToImport[uri];
if (import == null) {
var uriText = _getLibraryUriText(uri,
forceAbsolute: forceAbsolute, forceRelative: forceRelative);
prefix ??=
importPrefixGenerator != null ? importPrefixGenerator!(uri) : null;
import = _LibraryToImport(uriText, prefix);
(libraryChangeBuilder ?? this).librariesToImport[uri] = import;
}
return import;
}
/// Return `true` if the [element] is defined in the target library.
bool _isDefinedLocally(Element element) {
return element.library == resolvedUnit.libraryElement;
}
bool _isLintEnabled(String lintName) {
final analysisOptions =
resolvedUnit.session.analysisContext.analysisOptions;
return analysisOptions.isLintEnabled(lintName);
}
/// Create an edit to replace the return type of the innermost function
/// containing the given [node] with the type `Future`. The [typeProvider] is
/// used to check the current return type, because if it is already `Future`
/// no edit will be added.
void _replaceReturnTypeWithFuture(AstNode? node, TypeProvider typeProvider) {
while (node != null) {
node = node.parent;
if (node is FunctionDeclaration) {
replaceTypeWithFuture(node.returnType, typeProvider);
return;
} else if (node is FunctionExpression &&
node.parent is! FunctionDeclaration) {
// Closures don't have a return type.
return;
} else if (node is MethodDeclaration) {
replaceTypeWithFuture(node.returnType, typeProvider);
return;
}
}
}
static bool _isFusedWithPreviousToken(Token token) {
return token.previous?.end == token.offset;
}
}
/// A [LinkedEditBuilder] used to build linked edits for Dart files.
///
/// Clients may not extend, implement or mix-in this class.
class DartLinkedEditBuilderImpl extends LinkedEditBuilderImpl
implements DartLinkedEditBuilder {
/// Initialize a newly created linked edit builder.
DartLinkedEditBuilderImpl(EditBuilderImpl editBuilder) : super(editBuilder);
@override
void addSuperTypesAsSuggestions(DartType? type) {
_addSuperTypesAsSuggestions(type, <DartType>{});
}
/// Safely implement [addSuperTypesAsSuggestions] by using the set of
/// [alreadyAdded] types to prevent infinite loops.
void _addSuperTypesAsSuggestions(DartType? type, Set<DartType> alreadyAdded) {
if (type is InterfaceType && alreadyAdded.add(type)) {
addSuggestion(
LinkedEditSuggestionKind.TYPE,
type.getDisplayString(withNullability: false),
);
_addSuperTypesAsSuggestions(type.superclass, alreadyAdded);
for (var interfaceType in type.interfaces) {
_addSuperTypesAsSuggestions(interfaceType, alreadyAdded);
}
}
}
}
/// Information about a library to import.
class ImportLibraryElementResultImpl implements ImportLibraryElementResult {
@override
final String? prefix;
ImportLibraryElementResultImpl(this.prefix);
}
class _EnclosingElementFinder {
ClassElement? enclosingClass;
ExecutableElement? enclosingExecutable;
_EnclosingElementFinder();
void find(AstNode? target, int offset) {
var node = NodeLocator2(offset).searchWithin(target);
while (node != null) {
if (node is ClassDeclaration) {
enclosingClass = node.declaredElement;
} else if (node is ConstructorDeclaration) {
enclosingExecutable = node.declaredElement;
} else if (node is MethodDeclaration) {
enclosingExecutable = node.declaredElement;
} else if (node is FunctionDeclaration) {
enclosingExecutable = node.declaredElement;
}
node = node.parent;
}
}
}
/// Information about a new library to import.
class _LibraryToImport {
final String uriText;
final String? prefix;
_LibraryToImport(this.uriText, this.prefix);
@override
int get hashCode => uriText.hashCode;
@override
bool operator ==(other) {
return other is _LibraryToImport &&
other.uriText == uriText &&
other.prefix == prefix;
}
}