blob: 476125aeffa4b28ecd78c2a65971f97f003f6d49 [file] [log] [blame]
// Copyright (c) 2019, 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:analysis_server/src/protocol_server.dart'
show CompletionSuggestionKind;
import 'package:analysis_server/src/provisional/completion/dart/completion_dart.dart';
import 'package:analysis_server/src/services/completion/dart/suggestion_builder.dart';
import 'package:analysis_server/src/utilities/extensions/element.dart';
import 'package:analyzer/dart/ast/ast.dart';
import 'package:analyzer/dart/element/element.dart';
import 'package:analyzer/dart/element/type.dart';
/// A contributor that produces suggestions based on the members of an
/// extension.
class ExtensionMemberContributor extends DartCompletionContributor {
late MemberSuggestionBuilder memberBuilder;
@override
Future<void> computeSuggestions(
DartCompletionRequest request, SuggestionBuilder builder) async {
var containingLibrary = request.libraryElement;
memberBuilder = MemberSuggestionBuilder(request, builder);
var defaultKind = request.target.isFunctionalArgument()
? CompletionSuggestionKind.IDENTIFIER
: request.opType.suggestKind;
// Recompute the target because resolution might have changed it.
var expression = request.dotTarget;
if (expression == null) {
if (!request.includeIdentifiers) {
return;
}
var classOrMixin = request.target.containingNode
.thisOrAncestorOfType<ClassOrMixinDeclaration>();
if (classOrMixin != null) {
var type = classOrMixin.declaredElement?.thisType;
if (type != null) {
_addExtensionMembers(containingLibrary, defaultKind, type);
}
} else {
var extension = request.target.containingNode
.thisOrAncestorOfType<ExtensionDeclaration>();
if (extension != null) {
var extendedType = extension.extendedType.type;
if (extendedType is InterfaceType) {
var types = [extendedType, ...extendedType.allSupertypes];
for (var type in types) {
var inheritanceDistance = memberBuilder.request.featureComputer
.inheritanceDistanceFeature(
extendedType.element, type.element);
_addTypeMembers(type, defaultKind, inheritanceDistance);
}
_addExtensionMembers(containingLibrary, defaultKind, extendedType);
}
}
}
return;
}
if (expression.isSynthetic) {
return;
}
if (expression is Identifier) {
var elem = expression.staticElement;
if (elem is ClassElement) {
// Suggestions provided by StaticMemberContributor.
return;
} else if (elem is ExtensionElement) {
// Suggestions provided by StaticMemberContributor.
return;
} else if (elem is PrefixElement) {
// Suggestions provided by LibraryMemberContributor.
return;
}
}
if (expression is ExtensionOverride) {
var staticElement = expression.staticElement;
if (staticElement != null) {
_addInstanceMembers(staticElement, defaultKind, 0.0);
}
} else {
var type = expression.staticType;
if (type == null) {
// Without a type we can't find the extensions that apply. We shouldn't
// get to this point, but there's an NPE if we invoke
// `resolvedExtendedType` when `type` is `null`, so we guard against it
// to ensure that we can return the suggestions from other providers.
return;
}
var containingNode = request.target.containingNode;
if (containingNode is PropertyAccess &&
containingNode.operator.lexeme == '?.') {
// After a null-safe operator we know that the member will only be
// invoked on a non-null value.
type = containingLibrary.typeSystem.promoteToNonNull(type);
}
_addExtensionMembers(containingLibrary, defaultKind, type);
expression.staticType;
}
}
void _addExtensionMembers(LibraryElement containingLibrary,
CompletionSuggestionKind? kind, DartType type) {
var typeSystem = containingLibrary.typeSystem;
for (var extension in containingLibrary.accessibleExtensions) {
var extendedType =
extension.resolvedExtendedType(containingLibrary, type);
if (extendedType != null && typeSystem.isSubtypeOf(type, extendedType)) {
var inheritanceDistance = 0.0;
if (type is InterfaceType && extendedType is InterfaceType) {
inheritanceDistance = memberBuilder.request.featureComputer
.inheritanceDistanceFeature(type.element, extendedType.element);
}
// TODO(brianwilkerson) We might want to apply the substitution to the
// members of the extension for display purposes.
_addInstanceMembers(extension, kind, inheritanceDistance);
}
}
}
void _addInstanceMembers(ExtensionElement extension,
CompletionSuggestionKind? kind, double inheritanceDistance) {
for (var method in extension.methods) {
if (!method.isStatic) {
memberBuilder.addSuggestionForMethod(
method: method,
kind: kind,
inheritanceDistance: inheritanceDistance);
}
}
for (var accessor in extension.accessors) {
if (!accessor.isStatic) {
memberBuilder.addSuggestionForAccessor(
accessor: accessor, inheritanceDistance: inheritanceDistance);
}
}
}
void _addTypeMembers(InterfaceType type, CompletionSuggestionKind? kind,
double inheritanceDistance) {
for (var method in type.methods) {
memberBuilder.addSuggestionForMethod(
method: method, kind: kind, inheritanceDistance: inheritanceDistance);
}
for (var accessor in type.accessors) {
memberBuilder.addSuggestionForAccessor(
accessor: accessor, inheritanceDistance: inheritanceDistance);
}
}
}