| // 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/completion_manager.dart'; |
| import 'package:analysis_server/src/services/completion/dart/suggestion_builder.dart'; |
| import 'package:analyzer/dart/ast/ast.dart'; |
| import 'package:analyzer/dart/element/element.dart'; |
| import 'package:analyzer/dart/element/type.dart'; |
| import 'package:analyzer/src/dart/resolver/applicable_extensions.dart'; |
| |
| /// A contributor that produces suggestions based on the members of an |
| /// extension. |
| class ExtensionMemberContributor extends DartCompletionContributor { |
| late final memberBuilder = MemberSuggestionBuilder(request, builder); |
| |
| ExtensionMemberContributor( |
| DartCompletionRequest request, |
| SuggestionBuilder builder, |
| ) : super(request, builder); |
| |
| void addExtensions(List<ExtensionElement> extensions) { |
| var containingLibrary = request.libraryElement; |
| |
| var defaultKind = request.target.isFunctionalArgument() |
| ? CompletionSuggestionKind.IDENTIFIER |
| : request.opType.suggestKind; |
| |
| // Recompute the target because resolution might have changed it. |
| var expression = request.target.dotTarget; |
| |
| if (expression == null) { |
| if (!request.includeIdentifiers) { |
| return; |
| } |
| |
| var thisClassType = request.target.enclosingClassElement?.thisType; |
| if (thisClassType != null) { |
| _addExtensionMembers(extensions, defaultKind, thisClassType); |
| } else { |
| var thisExtendedType = |
| request.target.enclosingExtensionElement?.extendedType; |
| if (thisExtendedType is InterfaceType) { |
| var types = [thisExtendedType, ...thisExtendedType.allSupertypes]; |
| for (var type in types) { |
| var inheritanceDistance = memberBuilder.request.featureComputer |
| .inheritanceDistanceFeature( |
| thisExtendedType.element, type.element); |
| _addTypeMembers(type, defaultKind, inheritanceDistance); |
| } |
| _addExtensionMembers(extensions, defaultKind, thisExtendedType); |
| } |
| // TODO(scheglov) It seems that we don't support non-interface types. |
| } |
| 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(extensions, defaultKind, type); |
| expression.staticType; |
| } |
| } |
| |
| @override |
| Future<void> computeSuggestions() async { |
| addExtensions( |
| request.libraryElement.accessibleExtensions, |
| ); |
| } |
| |
| void _addExtensionMembers(List<ExtensionElement> extensions, |
| CompletionSuggestionKind kind, DartType type) { |
| var applicableExtensions = extensions.applicableTo( |
| targetLibrary: request.libraryElement, |
| targetType: type, |
| ); |
| for (var instantiatedExtension in applicableExtensions) { |
| var extendedType = instantiatedExtension.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( |
| instantiatedExtension.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); |
| } |
| } |
| } |