blob: cad2006cc8adfce67357f116ef8b3ec2f9cc50ad [file] [log] [blame]
// Copyright (c) 2015, 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:collection';
import 'package:analysis_server/src/computer/computer_hover.dart';
import 'package:analysis_server/src/protocol_server.dart' as protocol;
import 'package:analysis_server/src/protocol_server.dart'
hide Element, ElementKind;
import 'package:analysis_server/src/protocol_server.dart'
show CompletionSuggestion;
import 'package:analysis_server/src/provisional/completion/dart/completion_dart.dart';
import 'package:analysis_server/src/services/completion/dart/feature_computer.dart';
import 'package:analysis_server/src/services/completion/dart/utilities.dart';
import 'package:analysis_server/src/utilities/flutter.dart';
import 'package:analyzer/dart/analysis/features.dart';
import 'package:analyzer/dart/ast/ast.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/src/util/comment.dart';
import 'package:meta/meta.dart';
/// Return a suggestion based on the given [element], or `null` if a suggestion
/// is not appropriate for the given element.
CompletionSuggestion createSuggestion(
DartCompletionRequest request, Element element,
{String completion,
protocol.ElementKind elementKind,
CompletionSuggestionKind kind,
int relevance = DART_RELEVANCE_DEFAULT}) {
if (element == null) {
return null;
}
if (element is ExecutableElement && element.isOperator) {
// Do not include operators in suggestions
return null;
}
completion ??= element.displayName;
if (completion == null || completion.isEmpty) {
return null;
}
kind ??= CompletionSuggestionKind.INVOCATION;
var suggestion = CompletionSuggestion(kind, relevance, completion,
completion.length, 0, element.hasOrInheritsDeprecated, false);
// Attach docs.
var doc = DartUnitHoverComputer.computeDocumentation(
request.dartdocDirectiveInfo, element);
if (doc != null) {
suggestion.docComplete = doc;
suggestion.docSummary = getDartDocSummary(doc);
}
suggestion.element = protocol.convertElement(element);
if (elementKind != null) {
suggestion.element.kind = elementKind;
}
var enclosingElement = element.enclosingElement;
if (enclosingElement is ClassElement) {
suggestion.declaringType = enclosingElement.displayName;
}
suggestion.returnType = getReturnTypeString(element);
if (element is ExecutableElement && element is! PropertyAccessorElement) {
suggestion.parameterNames = element.parameters
.map((ParameterElement parameter) => parameter.name)
.toList();
suggestion.parameterTypes =
element.parameters.map((ParameterElement parameter) {
var paramType = parameter.type;
// Gracefully degrade if type not resolved yet
return paramType != null
? paramType.getDisplayString(withNullability: false)
: 'var';
}).toList();
var requiredParameters = element.parameters
.where((ParameterElement param) => param.isRequiredPositional);
suggestion.requiredParameterCount = requiredParameters.length;
var namedParameters =
element.parameters.where((ParameterElement param) => param.isNamed);
suggestion.hasNamedParameters = namedParameters.isNotEmpty;
addDefaultArgDetails(
suggestion, element, requiredParameters, namedParameters);
}
return suggestion;
}
/// Common mixin for sharing behavior.
mixin ElementSuggestionBuilder {
/// A collection of completion suggestions.
final List<CompletionSuggestion> suggestions = <CompletionSuggestion>[];
/// A set of existing completions used to prevent duplicate suggestions.
final Set<String> _completions = <String>{};
/// A map of element names to suggestions for synthetic getters and setters.
final Map<String, CompletionSuggestion> _syntheticMap =
<String, CompletionSuggestion>{};
/// Return the library in which the completion is requested.
LibraryElement get containingLibrary;
/// Return the kind of suggestions that should be built.
CompletionSuggestionKind get kind;
/// Return the completion request for which suggestions are being built.
DartCompletionRequest get request;
/// Add a suggestion based upon the given element.
CompletionSuggestion addSuggestion(Element element,
{String prefix,
int relevance = DART_RELEVANCE_DEFAULT,
String elementCompletion}) {
if (element.isPrivate) {
if (element.library != containingLibrary) {
return null;
}
}
var completion = elementCompletion ?? element.displayName;
if (prefix != null && prefix.isNotEmpty) {
if (completion == null || completion.isEmpty) {
completion = prefix;
} else {
completion = '$prefix.$completion';
}
}
if (completion == null || completion.isEmpty) {
return null;
}
var suggestion = createSuggestion(request, element,
completion: completion, kind: kind, relevance: relevance);
if (suggestion != null) {
if (element.isSynthetic && element is PropertyAccessorElement) {
String cacheKey;
if (element.isGetter) {
cacheKey = element.name;
}
if (element.isSetter) {
cacheKey = element.name;
cacheKey = cacheKey.substring(0, cacheKey.length - 1);
}
if (cacheKey != null) {
var existingSuggestion = _syntheticMap[cacheKey];
// Pair getter/setter by updating the existing suggestion
if (existingSuggestion != null) {
var getter = element.isGetter ? suggestion : existingSuggestion;
var elemKind = element.enclosingElement is ClassElement
? protocol.ElementKind.FIELD
: protocol.ElementKind.TOP_LEVEL_VARIABLE;
existingSuggestion.element = protocol.Element(
elemKind,
existingSuggestion.element.name,
existingSuggestion.element.flags,
location: getter.element.location,
typeParameters: getter.element.typeParameters,
parameters: null,
returnType: getter.returnType);
return existingSuggestion;
}
// Cache lone getter/setter so that it can be paired
_syntheticMap[cacheKey] = suggestion;
}
}
if (_completions.add(suggestion.completion)) {
suggestions.add(suggestion);
}
}
return suggestion;
}
}
/// This class provides suggestions based upon the visible instance members in
/// an interface type.
class MemberSuggestionBuilder {
/// Enumerated value indicating that we have not generated any completions for
/// a given identifier yet.
static const int _COMPLETION_TYPE_NONE = 0;
/// Enumerated value indicating that we have generated a completion for a
/// getter.
static const int _COMPLETION_TYPE_GETTER = 1;
/// Enumerated value indicating that we have generated a completion for a
/// setter.
static const int _COMPLETION_TYPE_SETTER = 2;
/// Enumerated value indicating that we have generated a completion for a
/// field, a method, or a getter/setter pair.
static const int _COMPLETION_TYPE_FIELD_OR_METHOD_OR_GETSET = 3;
/// The request for which suggestions are being built.
final DartCompletionRequest request;
/// The builder used to build the suggestions.
final SuggestionBuilder builder;
/// Map indicating, for each possible completion identifier, whether we have
/// already generated completions for a getter, setter, or both. The "both"
/// case also handles the case where have generated a completion for a method
/// or a field.
///
/// Note: the enumerated values stored in this map are intended to be bitwise
/// compared.
final Map<String, int> _completionTypesGenerated = HashMap<String, int>();
MemberSuggestionBuilder(this.request, this.builder);
/// Add a suggestion for the given [accessor].
void addSuggestionForAccessor(
{@required PropertyAccessorElement accessor,
String containingMethodName,
@required double inheritanceDistance}) {
if (accessor.isAccessibleIn(request.libraryElement)) {
var member = accessor.isSynthetic ? accessor.variable : accessor;
if (_shouldAddSuggestion(member)) {
builder.suggestAccessor(accessor,
containingMemberName: containingMethodName,
inheritanceDistance: inheritanceDistance);
}
}
}
/// Add a suggestion for the given [method].
void addSuggestionForMethod(
{@required MethodElement method,
String containingMethodName,
CompletionSuggestionKind kind,
@required double inheritanceDistance}) {
if (method.isAccessibleIn(request.libraryElement) &&
_shouldAddSuggestion(method)) {
builder.suggestMethod(method,
containingMemberName: containingMethodName,
kind: kind,
inheritanceDistance: inheritanceDistance);
}
}
/// Return `true` if a suggestion for the given [element] should be created.
bool _shouldAddSuggestion(Element element) {
// TODO(brianwilkerson) Consider moving this into SuggestionBuilder.
var identifier = element.displayName;
var alreadyGenerated = _completionTypesGenerated.putIfAbsent(
identifier, () => _COMPLETION_TYPE_NONE);
if (element is MethodElement) {
// Anything shadows a method.
if (alreadyGenerated != _COMPLETION_TYPE_NONE) {
return false;
}
_completionTypesGenerated[identifier] =
_COMPLETION_TYPE_FIELD_OR_METHOD_OR_GETSET;
} else if (element is PropertyAccessorElement) {
if (element.isGetter) {
// Getters, fields, and methods shadow a getter.
if ((alreadyGenerated & _COMPLETION_TYPE_GETTER) != 0) {
return false;
}
_completionTypesGenerated[identifier] |= _COMPLETION_TYPE_GETTER;
} else {
// Setters, fields, and methods shadow a setter.
if ((alreadyGenerated & _COMPLETION_TYPE_SETTER) != 0) {
return false;
}
_completionTypesGenerated[identifier] |= _COMPLETION_TYPE_SETTER;
}
} else if (element is FieldElement) {
// Fields and methods shadow a field. A getter/setter pair shadows a
// field, but a getter or setter by itself doesn't.
if (alreadyGenerated == _COMPLETION_TYPE_FIELD_OR_METHOD_OR_GETSET) {
return false;
}
_completionTypesGenerated[identifier] =
_COMPLETION_TYPE_FIELD_OR_METHOD_OR_GETSET;
} else {
// Unexpected element type; skip it.
assert(false);
return false;
}
return true;
}
}
/// An object used to build a list of suggestions in response to a single
/// completion request.
class SuggestionBuilder {
/// The completion request for which suggestions are being built.
final DartCompletionRequest request;
/// A map from a completion identifier to a completion suggestion.
final Map<String, CompletionSuggestion> _suggestionMap =
<String, CompletionSuggestion>{};
/// A flag indicating whether a suggestion should replace any earlier
/// suggestions for the same completion (`true`) or whether earlier
/// suggestions should take priority over more recent suggestions.
// TODO(brianwilkerson) Attempt to convert the contributors so that a single
// approach is followed.
bool laterReplacesEarlier = true;
/// A flag indicating whether the [_cachedContainingMemberName] has been
/// computed.
bool _hasContainingMemberName = false;
/// The name of the member containing the completion location, or `null` if
/// either the completion location isn't within a member, the target of the
/// completion isn't `super`, or the name of the member hasn't yet been
/// computed. In the latter case, [_hasContainingMemberName] will be `false`.
String _cachedContainingMemberName;
/// A flag indicating whether the [_cachedContextType] has been computed.
bool _hasContextType = false;
/// The context type associated with the completion location, or `null` if
/// either the location doesn't have a context type, or the context type
/// hasn't yet been computed. In the latter case, [_hasContextType] will be
/// `false`.
DartType _cachedContextType;
/// The cached instance of the flutter utilities, or `null` if it hasn't been
/// created yet.
Flutter _flutter;
/// Initialize a newly created suggestion builder to build suggestions for the
/// given [request].
SuggestionBuilder(this.request);
/// Return an object that can answer questions about Flutter code based on the
/// flavor of Flutter being used.
Flutter get flutter => _flutter ??= Flutter.of(request.result);
/// Return an iterable that can be used to access the completion suggestions
/// that have been built.
Iterable<CompletionSuggestion> get suggestions => _suggestionMap.values;
/// Return the name of the member containing the completion location, or
/// `null` if the completion location isn't within a member or if the target
/// of the completion isn't `super`.
String get _containingMemberName {
if (!_hasContainingMemberName) {
_hasContainingMemberName = true;
if (request.dotTarget is SuperExpression) {
var containingMethod = request.target.containingNode
.thisOrAncestorOfType<MethodDeclaration>();
if (containingMethod != null) {
var id = containingMethod.name;
if (id != null) {
_cachedContainingMemberName = id.name;
}
}
}
}
return _cachedContainingMemberName;
}
/// Return the context type associated with the completion location, or `null`
/// if the location doesn't have a context type.
DartType get _contextType {
if (!_hasContextType) {
_hasContextType = true;
_cachedContextType = request.featureComputer
.computeContextType(request.target.containingNode);
}
return _cachedContextType;
}
/// Add a suggestion for an [accessor] declared within a class or extension.
/// If the accessor is being invoked with a target of `super`, then the
/// [containingMemberName] should be the name of the member containing the
/// invocation. The [inheritanceDistance] is the value of the inheritance
/// distance feature computed for the accessor (or `-1.0` if the accessor is a
/// static accessor).
void suggestAccessor(PropertyAccessorElement accessor,
{String containingMemberName, @required double inheritanceDistance}) {
// TODO(brianwilkerson) Remove [containingMemberName].
containingMemberName ??= _containingMemberName;
assert(accessor.enclosingElement is ClassElement ||
accessor.enclosingElement is ExtensionElement);
if (accessor.isSynthetic) {
// Avoid visiting a field twice. All fields induce a getter, but only
// non-final fields induce a setter, so we don't add a suggestion for a
// synthetic setter.
if (accessor.isGetter) {
var variable = accessor.variable;
if (variable is FieldElement) {
suggestField(variable,
containingMemberName: containingMemberName,
inheritanceDistance: inheritanceDistance);
}
}
} else {
var type =
accessor.isGetter ? accessor.returnType : accessor.parameters[0].type;
int relevance;
if (request.useNewRelevance) {
var featureComputer = request.featureComputer;
var contextType =
featureComputer.contextTypeFeature(request.contextType, type);
var elementKind = featureComputer.elementKindFeature(
accessor, request.opType.completionLocation);
var hasDeprecated = featureComputer.hasDeprecatedFeature(accessor);
var startsWithDollar =
featureComputer.startsWithDollarFeature(accessor.name);
var superMatches = featureComputer.superMatchesFeature(
containingMemberName, accessor.name);
relevance = _computeMemberRelevance(
contextType: contextType,
elementKind: elementKind,
hasDeprecated: hasDeprecated,
inheritanceDistance: inheritanceDistance,
startsWithDollar: startsWithDollar,
superMatches: superMatches);
} else {
relevance = _computeOldMemberRelevance(accessor);
if (request.opType.includeReturnValueSuggestions) {
relevance =
request.opType.returnValueSuggestionsFilter(type, relevance);
}
if (relevance == null) {
return;
}
}
_add(createSuggestion(request, accessor, relevance: relevance));
}
}
/// Add a suggestion for a catch [parameter].
void suggestCatchParameter(LocalVariableElement parameter) {
var variableType = parameter.type;
int relevance;
if (request.useNewRelevance) {
var contextTypeFeature = request.featureComputer
.contextTypeFeature(_contextType, variableType);
relevance = toRelevance(contextTypeFeature, 800);
} else {
relevance = _computeOldMemberRelevance(parameter);
relevance =
request.opType.returnValueSuggestionsFilter(variableType, relevance);
if (relevance == null) {
return;
}
}
_add(createSuggestion(request, parameter,
elementKind: protocol.ElementKind.PARAMETER, relevance: relevance));
}
/// Add a suggestion for a [classElement]. If a [kind] is provided it will
/// be used as the kind for the suggestion.
void suggestClass(ClassElement classElement,
{CompletionSuggestionKind kind = CompletionSuggestionKind.INVOCATION}) {
int relevance;
if (request.useNewRelevance) {
relevance = _computeTopLevelRelevance(classElement,
elementType: _instantiateClassElement(classElement));
} else if (classElement.hasOrInheritsDeprecated) {
relevance = DART_RELEVANCE_LOW;
} else {
relevance = request.opType.typeNameSuggestionsFilter(
_instantiateClassElement(classElement), DART_RELEVANCE_DEFAULT);
if (relevance == null) {
return;
}
}
_add(createSuggestion(request, classElement,
kind: kind, relevance: relevance));
}
/// Add a suggestion for a [constructor]. If a [kind] is provided it will be
/// used as the kind for the suggestion. The flag [hasClassName] should be
/// `true` if the completion is occurring after the name of the class and a
/// period, and hence should not include the name of the class.
void suggestConstructor(ConstructorElement constructor,
{CompletionSuggestionKind kind = CompletionSuggestionKind.INVOCATION,
bool hasClassName = false}) {
var classElement = constructor.enclosingElement;
if (classElement == null) {
return;
}
var prefix = classElement.name;
// TODO(brianwilkerson) It shouldn't be necessary to test for an empty
// prefix.
if (prefix == null || prefix.isEmpty) {
return;
}
var completion = constructor.displayName;
if (!hasClassName && prefix != null && prefix.isNotEmpty) {
if (completion == null || completion.isEmpty) {
completion = prefix;
} else {
completion = '$prefix.$completion';
}
}
if (completion == null || completion.isEmpty) {
return null;
}
int relevance;
if (request.useNewRelevance) {
relevance = _computeTopLevelRelevance(constructor);
} else {
relevance = constructor.hasOrInheritsDeprecated
? DART_RELEVANCE_LOW
: DART_RELEVANCE_DEFAULT;
}
_add(createSuggestion(request, constructor,
completion: completion, kind: kind, relevance: relevance));
}
/// Add a suggestion for a top-level [element]. If a [kind] is provided it
/// will be used as the kind for the suggestion.
void suggestElement(Element element,
{CompletionSuggestionKind kind = CompletionSuggestionKind.INVOCATION}) {
if (element is ClassElement) {
suggestClass(element, kind: kind);
} else if (element is ConstructorElement) {
suggestConstructor(element, kind: kind);
} else if (element is ExtensionElement) {
suggestExtension(element, kind: kind);
} else if (element is FunctionElement &&
element.enclosingElement is CompilationUnitElement) {
suggestTopLevelFunction(element, kind: kind);
} else if (element is FunctionTypeAliasElement) {
suggestFunctionTypeAlias(element, kind: kind);
} else if (element is PropertyAccessorElement &&
element.enclosingElement is CompilationUnitElement) {
suggestTopLevelPropertyAccessor(element, kind: kind);
} else {
throw ArgumentError('Cannot suggest a ${element.runtimeType}');
}
}
/// Add a suggestion for an enum [constant].
void suggestEnumConstant(FieldElement constant) {
var constantName = constant.name;
var enumElement = constant.enclosingElement;
var enumName = enumElement.name;
var completion = '$enumName.$constantName';
int relevance;
if (request.useNewRelevance) {
relevance =
_computeTopLevelRelevance(constant, elementType: constant.type);
} else if (constant.hasOrInheritsDeprecated) {
relevance = DART_RELEVANCE_LOW;
} else {
relevance = request.opType.returnValueSuggestionsFilter(
_instantiateClassElement(enumElement), DART_RELEVANCE_DEFAULT);
if (relevance == null) {
return;
}
}
_add(createSuggestion(request, constant,
completion: completion, relevance: relevance));
}
/// Add a suggestion for an [extension]. If a [kind] is provided it will be
/// used as the kind for the suggestion.
void suggestExtension(ExtensionElement extension,
{CompletionSuggestionKind kind = CompletionSuggestionKind.INVOCATION}) {
int relevance;
if (request.useNewRelevance) {
relevance = _computeTopLevelRelevance(extension,
elementType: extension.extendedType);
} else {
relevance = extension.hasOrInheritsDeprecated
? DART_RELEVANCE_LOW
: DART_RELEVANCE_DEFAULT;
}
_add(
createSuggestion(request, extension, kind: kind, relevance: relevance));
}
/// Add a suggestion for a [field]. If the field is being referenced with a
/// target of `super`, then the [containingMemberName] should be the name of
/// the member containing the reference. The [inheritanceDistance] is the
/// value of the inheritance distance feature computed for the field (or
/// `-1.0` if the field is a static field).
void suggestField(FieldElement field,
{String containingMemberName, @required double inheritanceDistance}) {
// TODO(brianwilkerson) Remove [containingMemberName].
containingMemberName ??= _containingMemberName;
int relevance;
if (request.useNewRelevance) {
var featureComputer = request.featureComputer;
var contextType =
featureComputer.contextTypeFeature(request.contextType, field.type);
var elementKind = featureComputer.elementKindFeature(
field, request.opType.completionLocation);
var hasDeprecated = featureComputer.hasDeprecatedFeature(field);
var startsWithDollar =
featureComputer.startsWithDollarFeature(field.name);
var superMatches =
featureComputer.superMatchesFeature(containingMemberName, field.name);
relevance = _computeMemberRelevance(
contextType: contextType,
elementKind: elementKind,
hasDeprecated: hasDeprecated,
inheritanceDistance: inheritanceDistance,
startsWithDollar: startsWithDollar,
superMatches: superMatches);
} else {
relevance = _computeOldMemberRelevance(field);
if (request.opType.includeReturnValueSuggestions) {
relevance =
request.opType.returnValueSuggestionsFilter(field.type, relevance);
}
if (relevance == null) {
return;
}
}
_add(createSuggestion(request, field, relevance: relevance));
}
/// Add a suggestion to reference a [field] in a field formal parameter.
void suggestFieldFormalParameter(FieldElement field) {
// TODO(brianwilkerson) Add a parameter (`bool includePrefix`) indicating
// whether to include the `this.` prefix in the completion.
var relevance = request.useNewRelevance
? Relevance.fieldFormalParameter
: DART_RELEVANCE_LOCAL_FIELD;
_add(createSuggestion(request, field, relevance: relevance));
}
/// Add a suggestion for the `call` method defined on functions.
void suggestFunctionCall() {
const callString = 'call()';
final element = protocol.Element(
protocol.ElementKind.METHOD, callString, protocol.Element.makeFlags(),
location: null,
typeParameters: null,
parameters: null,
returnType: 'void');
_add(CompletionSuggestion(
CompletionSuggestionKind.INVOCATION,
request.useNewRelevance ? Relevance.callFunction : DART_RELEVANCE_HIGH,
callString,
callString.length,
0,
false,
false,
displayText: callString,
element: element,
returnType: 'void',
));
}
/// Add a suggestion for a [functionTypeAlias]. If a [kind] is provided it
/// will be used as the kind for the suggestion.
void suggestFunctionTypeAlias(FunctionTypeAliasElement functionTypeAlias,
{CompletionSuggestionKind kind = CompletionSuggestionKind.INVOCATION}) {
int relevance;
if (request.useNewRelevance) {
relevance = _computeTopLevelRelevance(functionTypeAlias,
defaultRelevance: 750,
elementType: _instantiateFunctionTypeAlias(functionTypeAlias));
} else if (functionTypeAlias.hasOrInheritsDeprecated) {
relevance = DART_RELEVANCE_LOW;
} else {
relevance = functionTypeAlias.library == request.libraryElement
? DART_RELEVANCE_LOCAL_FUNCTION
: DART_RELEVANCE_DEFAULT;
}
_add(createSuggestion(request, functionTypeAlias,
kind: kind, relevance: relevance));
}
/// Add a suggestion for the `loadLibrary` [function] associated with a
/// prefix.
void suggestLoadLibraryFunction(FunctionElement function) {
int relevance;
if (request.useNewRelevance) {
// TODO(brianwilkerson) This might want to use the context type rather
// than a fixed value.
relevance = Relevance.loadLibrary;
} else {
relevance = function.hasOrInheritsDeprecated
? DART_RELEVANCE_LOW
: DART_RELEVANCE_DEFAULT;
}
_add(createSuggestion(request, function, relevance: relevance));
}
/// Add a suggestion for a local [variable].
void suggestLocalVariable(LocalVariableElement variable) {
var variableType = variable.type;
int relevance;
if (request.useNewRelevance) {
// TODO(brianwilkerson) Use the distance to the local variable as
// another feature.
var contextTypeFeature = request.featureComputer
.contextTypeFeature(_contextType, variableType);
relevance = toRelevance(contextTypeFeature, 800);
} else {
relevance = _computeOldMemberRelevance(variable);
relevance =
request.opType.returnValueSuggestionsFilter(variableType, relevance);
if (relevance == null) {
return;
}
}
_add(createSuggestion(request, variable, relevance: relevance));
}
/// Add a suggestion for a [method]. If the method is being invoked with a
/// target of `super`, then the [containingMemberName] should be the name of
/// the member containing the invocation. If a [kind] is provided it will be
/// used as the kind for the suggestion. The [inheritanceDistance] is the
/// value of the inheritance distance feature computed for the method.
void suggestMethod(MethodElement method,
{String containingMemberName,
CompletionSuggestionKind kind,
@required double inheritanceDistance}) {
// TODO(brianwilkerson) Remove [containingMemberName].
containingMemberName ??= _containingMemberName;
// TODO(brianwilkerson) Refactor callers so that we're passing in the type
// of the target (assuming we don't already have that type available via
// the [request]) and compute the [inheritanceDistance] in this method.
int relevance;
if (request.useNewRelevance) {
var featureComputer = request.featureComputer;
var contextType = featureComputer.contextTypeFeature(
request.contextType, method.returnType);
var elementKind = featureComputer.elementKindFeature(
method, request.opType.completionLocation);
var hasDeprecated = featureComputer.hasDeprecatedFeature(method);
var startsWithDollar =
featureComputer.startsWithDollarFeature(method.name);
var superMatches = featureComputer.superMatchesFeature(
containingMemberName, method.name);
relevance = _computeMemberRelevance(
contextType: contextType,
elementKind: elementKind,
hasDeprecated: hasDeprecated,
inheritanceDistance: inheritanceDistance,
startsWithDollar: startsWithDollar,
superMatches: superMatches);
} else {
relevance = _computeOldMemberRelevance(method,
containingMethodName: containingMemberName);
if (request.opType.includeReturnValueSuggestions) {
relevance = request.opType
.returnValueSuggestionsFilter(method.returnType, relevance);
}
if (relevance == null) {
return;
}
}
var suggestion =
createSuggestion(request, method, kind: kind, relevance: relevance);
if (suggestion != null) {
if (method.name == 'setState' &&
flutter.isExactState(method.enclosingElement)) {
// TODO(brianwilkerson) Make this more efficient by creating the correct
// suggestion in the first place.
// Find the line indentation.
var indent = getRequestLineIndent(request);
// Let the user know that we are going to insert a complete statement.
suggestion.displayText = 'setState(() {});';
// Build the completion and the selection offset.
var buffer = StringBuffer();
buffer.writeln('setState(() {');
buffer.write('$indent ');
suggestion.selectionOffset = buffer.length;
buffer.writeln();
buffer.write('$indent});');
suggestion.completion = buffer.toString();
// There are no arguments to fill.
suggestion.parameterNames = null;
suggestion.parameterTypes = null;
suggestion.requiredParameterCount = null;
suggestion.hasNamedParameters = null;
}
_add(suggestion);
}
}
/// Add a suggestion for a [parameter].
void suggestParameter(ParameterElement parameter) {
var variableType = parameter.type;
int relevance;
if (request.useNewRelevance) {
// TODO(brianwilkerson) Use the distance to the declaring function as
// another feature.
var contextTypeFeature = request.featureComputer
.contextTypeFeature(_contextType, variableType);
relevance = toRelevance(contextTypeFeature, 800);
} else {
relevance = _computeOldMemberRelevance(parameter);
relevance =
request.opType.returnValueSuggestionsFilter(variableType, relevance);
if (relevance == null) {
return;
}
}
_add(createSuggestion(request, parameter, relevance: relevance));
}
/// Add a suggestion for a top-level [function]. If a [kind] is provided it
/// will be used as the kind for the suggestion.
void suggestTopLevelFunction(FunctionElement function,
{CompletionSuggestionKind kind = CompletionSuggestionKind.INVOCATION}) {
int relevance;
if (request.useNewRelevance) {
relevance =
_computeTopLevelRelevance(function, elementType: function.returnType);
} else if (function.hasOrInheritsDeprecated) {
relevance = DART_RELEVANCE_LOW;
} else {
relevance = function.library == request.libraryElement
? DART_RELEVANCE_LOCAL_FUNCTION
: DART_RELEVANCE_DEFAULT;
relevance =
request.opType.returnValueSuggestionsFilter(function.type, relevance);
if (relevance == null) {
return;
}
}
_add(createSuggestion(request, function, kind: kind, relevance: relevance));
}
/// Add a suggestion for a top-level property [accessor]. If a [kind] is
/// provided it will be used as the kind for the suggestion.
void suggestTopLevelPropertyAccessor(PropertyAccessorElement accessor,
{CompletionSuggestionKind kind = CompletionSuggestionKind.INVOCATION}) {
assert(accessor.enclosingElement is CompilationUnitElement);
if (accessor.isSynthetic) {
// Avoid visiting a field twice. All fields induce a getter, but only
// non-final fields induce a setter, so we don't add a suggestion for a
// synthetic setter.
if (accessor.isGetter) {
var variable = accessor.variable;
if (variable is TopLevelVariableElement) {
suggestTopLevelVariable(variable, kind: kind);
}
}
} else {
var type =
accessor.isGetter ? accessor.returnType : accessor.parameters[0].type;
int relevance;
if (request.useNewRelevance) {
var featureComputer = request.featureComputer;
var contextType =
featureComputer.contextTypeFeature(request.contextType, type);
var elementKind = featureComputer.elementKindFeature(
accessor, request.opType.completionLocation);
var hasDeprecated = featureComputer.hasDeprecatedFeature(accessor);
var startsWithDollar =
featureComputer.startsWithDollarFeature(accessor.name);
relevance = _computeMemberRelevance(
contextType: contextType,
elementKind: elementKind,
hasDeprecated: hasDeprecated,
inheritanceDistance: -1.0,
startsWithDollar: startsWithDollar,
superMatches: -1.0);
} else {
relevance = _computeOldMemberRelevance(accessor);
if (request.opType.includeReturnValueSuggestions) {
relevance =
request.opType.returnValueSuggestionsFilter(type, relevance);
}
if (relevance == null) {
return;
}
}
_add(createSuggestion(request, accessor, relevance: relevance));
}
}
/// Add a suggestion for a top-level [variable]. If a [kind] is provided it
/// will be used as the kind for the suggestion.
void suggestTopLevelVariable(TopLevelVariableElement variable,
{CompletionSuggestionKind kind = CompletionSuggestionKind.INVOCATION}) {
assert(variable.enclosingElement is CompilationUnitElement);
int relevance;
if (request.useNewRelevance) {
relevance =
_computeTopLevelRelevance(variable, elementType: variable.type);
} else if (variable.hasOrInheritsDeprecated) {
relevance = DART_RELEVANCE_LOW;
} else {
var type = variable.type;
relevance = _computeOldMemberRelevance(variable);
relevance = request.opType.returnValueSuggestionsFilter(type, relevance);
if (relevance == null) {
return;
}
}
_add(createSuggestion(request, variable, kind: kind, relevance: relevance));
}
/// Add a suggestion for a type [parameter].
void suggestTypeParameter(TypeParameterElement parameter) {
int relevance;
if (request.useNewRelevance) {
relevance = Relevance.typeParameter;
} else {
relevance = _computeOldMemberRelevance(parameter);
}
_add(createSuggestion(request, parameter,
kind: CompletionSuggestionKind.IDENTIFIER, relevance: relevance));
}
/// Add the given [suggestion] if it isn't `null` and if it isn't shadowed by
/// a previously added suggestion.
void _add(protocol.CompletionSuggestion suggestion) {
if (suggestion != null) {
if (laterReplacesEarlier) {
_suggestionMap[suggestion.completion] = suggestion;
} else {
_suggestionMap.putIfAbsent(suggestion.completion, () => suggestion);
}
}
}
/// Compute a relevance value from the given feature scores:
/// - [contextType] is higher if the type of the element matches the context
/// type,
/// - [hasDeprecated] is higher if the element is not deprecated,
/// - [inheritanceDistance] is higher if the element is defined closer to the
/// target type,
/// - [startsWithDollar] is higher if the element's name doe _not_ start with
/// a dollar sign, and
/// - [superMatches] is higher if the element is being invoked through `super`
/// and the element's name matches the name of the enclosing method.
int _computeMemberRelevance(
{@required double contextType,
@required double elementKind,
@required double hasDeprecated,
@required double inheritanceDistance,
@required double startsWithDollar,
@required double superMatches}) {
var score = weightedAverage([
contextType,
elementKind,
hasDeprecated,
inheritanceDistance,
startsWithDollar,
superMatches
], [
1.0,
0.75,
0.5,
1.0,
0.5,
1.0
]);
return toRelevance(score, Relevance.member);
}
/// Compute the old relevance score for a member.
int _computeOldMemberRelevance(Element member,
{String containingMethodName}) {
// TODO(brianwilkerson) Remove [containingMethodName].
containingMethodName ??= _containingMemberName;
if (member.hasOrInheritsDeprecated) {
return DART_RELEVANCE_LOW;
} else if (member.name == containingMethodName) {
// Boost the relevance of a super expression calling a method of the
// same name as the containing method.
return DART_RELEVANCE_HIGH;
}
var identifier = member.displayName;
if (identifier != null && identifier.startsWith(r'$')) {
// Decrease relevance of suggestions starting with $
// https://github.com/dart-lang/sdk/issues/27303
return DART_RELEVANCE_LOW;
}
if (member is LocalVariableElement) {
return DART_RELEVANCE_LOCAL_VARIABLE;
}
if (!member.name.startsWith('_') &&
member.library == request.libraryElement) {
// Locally declared elements sometimes have a special relevance.
if (member is PropertyAccessorElement) {
return DART_RELEVANCE_LOCAL_ACCESSOR;
} else if (member is FieldElement) {
return DART_RELEVANCE_LOCAL_FIELD;
} else if (member is FunctionElement) {
return DART_RELEVANCE_LOCAL_FUNCTION;
} else if (member is MethodElement) {
return DART_RELEVANCE_LOCAL_METHOD;
} else if (member is ParameterElement) {
return DART_RELEVANCE_PARAMETER;
} else if (member is TopLevelVariableElement) {
return DART_RELEVANCE_LOCAL_TOP_LEVEL_VARIABLE;
} else if (member is TypeParameterElement) {
return DART_RELEVANCE_TYPE_PARAMETER;
}
}
return DART_RELEVANCE_DEFAULT;
}
/// Return the relevance score for a top-level [element].
int _computeTopLevelRelevance(Element element,
{int defaultRelevance = 800, DartType elementType}) {
// TODO(brianwilkerson) The old relevance computation used a signal based
// on whether the element being suggested was from the same library in
// which completion is being performed. Explore whether that's a useful
// signal.
var featureComputer = request.featureComputer;
var contextTypeFeature =
featureComputer.contextTypeFeature(_contextType, elementType);
var elementKind = featureComputer.elementKindFeature(
element, request.opType.completionLocation);
var hasDeprecated = featureComputer.hasDeprecatedFeature(element);
return toRelevance(
weightedAverage(
[contextTypeFeature, elementKind, hasDeprecated], [1.0, 0.75, 0.2]),
defaultRelevance);
}
InterfaceType _instantiateClassElement(ClassElement element) {
var typeParameters = element.typeParameters;
var typeArguments = const <DartType>[];
if (typeParameters.isNotEmpty) {
var neverType = request.libraryElement.typeProvider.neverType;
typeArguments = List.filled(typeParameters.length, neverType);
}
var nullabilitySuffix = request.featureSet.isEnabled(Feature.non_nullable)
? NullabilitySuffix.none
: NullabilitySuffix.star;
return element.instantiate(
typeArguments: typeArguments,
nullabilitySuffix: nullabilitySuffix,
);
}
FunctionType _instantiateFunctionTypeAlias(FunctionTypeAliasElement element) {
var typeParameters = element.typeParameters;
var typeArguments = const <DartType>[];
if (typeParameters.isNotEmpty) {
var neverType = request.libraryElement.typeProvider.neverType;
typeArguments = List.filled(typeParameters.length, neverType);
}
var nullabilitySuffix = request.featureSet.isEnabled(Feature.non_nullable)
? NullabilitySuffix.none
: NullabilitySuffix.star;
return element.instantiate(
typeArguments: typeArguments,
nullabilitySuffix: nullabilitySuffix,
);
}
}