Chromium Code Reviews
chromiumcodereview-hr@appspot.gserviceaccount.com (chromiumcodereview-hr) | Please choose your nickname with Settings | Help | Chromium Project | Gerrit Changes | Sign out
(22)

Unified Diff: pkg/analyzer_plugin/lib/utilities/completion/type_member_contributor.dart

Issue 2927663002: Port two completion contributors for use by plugins (Closed)
Patch Set: Created 3 years, 6 months ago
Use n/p to move between diff chunks; N/P to move between comments. Draft comments are only viewable by you.
Jump to:
View side-by-side diff with in-line comments
Download patch
Index: pkg/analyzer_plugin/lib/utilities/completion/type_member_contributor.dart
diff --git a/pkg/analyzer_plugin/lib/utilities/completion/type_member_contributor.dart b/pkg/analyzer_plugin/lib/utilities/completion/type_member_contributor.dart
new file mode 100644
index 0000000000000000000000000000000000000000..ec5b94ba7cae02006cf7a3e7bf1e5371bca5884b
--- /dev/null
+++ b/pkg/analyzer_plugin/lib/utilities/completion/type_member_contributor.dart
@@ -0,0 +1,463 @@
+// Copyright (c) 2017, the Dart project authors. Please see the AUTHORS file
+// for details. All rights reserved. Use of this source code is governed by a
+// BSD-style license that can be found in the LICENSE file.
+
+import 'dart:async';
+import 'dart:collection';
+
+import 'package:analyzer/dart/ast/ast.dart';
+import 'package:analyzer/dart/element/element.dart';
+import 'package:analyzer/dart/element/type.dart';
+import 'package:analyzer/file_system/file_system.dart';
+import 'package:analyzer_plugin/protocol/protocol_common.dart' hide Element;
+import 'package:analyzer_plugin/src/utilities/completion/completion_target.dart';
+import 'package:analyzer_plugin/src/utilities/completion/suggestion_builder.dart';
+import 'package:analyzer_plugin/src/utilities/visitors/local_declaration_visitor.dart';
+import 'package:analyzer_plugin/utilities/completion/completion_core.dart';
+import 'package:analyzer_plugin/utilities/completion/relevance.dart';
+import 'package:analyzer_plugin/utilities/completion/suggestion_builder.dart';
+
+/**
+ * A completion contributor that will generate suggestions for instance
+ * invocations and accesses.
+ */
+class TypeMemberContributor implements CompletionContributor {
+ @override
+ Future<Null> computeSuggestions(
+ CompletionRequest request, CompletionCollector collector) async {
+ LibraryElement containingLibrary = request.result.libraryElement;
+ // Gracefully degrade if the library element is not resolved
+ // e.g. detached part file or source change
+ if (containingLibrary == null) {
+ return;
+ }
+
+ // Recompute the target since resolution may have changed it
+ Expression expression = _computeDotTarget(request);
+ if (expression == null || expression.isSynthetic) {
+ return;
+ }
+ if (expression is Identifier) {
+ Element element = expression.bestElement;
+ if (element is ClassElement) {
+ // Suggestions provided by StaticMemberContributor
+ return;
+ }
+ if (element is PrefixElement) {
+ // Suggestions provided by LibraryMemberContributor
+ return;
+ }
+ }
+
+ // Determine the target expression's type
+ DartType type = expression.bestType;
+ if (type.isDynamic) {
+ // If the expression does not provide a good type
+ // then attempt to get a better type from the element
+ if (expression is Identifier) {
+ Element elem = expression.bestElement;
+ if (elem is FunctionTypedElement) {
+ type = elem.returnType;
+ } else if (elem is ParameterElement) {
+ type = elem.type;
+ } else if (elem is LocalVariableElement) {
+ type = elem.type;
+ }
+ if ((type == null || type.isDynamic) &&
+ expression is SimpleIdentifier) {
+ // If the element does not provide a good type
+ // then attempt to get a better type from a local declaration
+ _LocalBestTypeVisitor visitor =
+ new _LocalBestTypeVisitor(expression.name, request.offset);
+ if (visitor.visit(expression) && visitor.typeFound != null) {
+ type = visitor.typeFound;
+ }
+ }
+ }
+ }
+ String containingMethodName;
+ if (expression is SuperExpression && type is InterfaceType) {
+ // Suggest members from superclass if target is "super"
+ type = (type as InterfaceType).superclass;
+ // Determine the name of the containing method because
+ // the most likely completion is a super expression with same name
+ MethodDeclaration containingMethod =
+ expression.getAncestor((p) => p is MethodDeclaration);
+ if (containingMethod != null) {
+ SimpleIdentifier id = containingMethod.name;
+ if (id != null) {
+ containingMethodName = id.name;
+ }
+ }
+ }
+ if (type.isDynamic) {
+ // Suggest members from object if target is "dynamic"
+ type = request.result.typeProvider.objectType;
+ }
+
+ // Build the suggestions
+ if (type is InterfaceType) {
+ _SuggestionBuilder builder = new _SuggestionBuilder(
+ request.resourceProvider, collector, containingLibrary);
+ builder.buildSuggestions(type, containingMethodName);
+ }
+ }
+
+ /**
+ * Update the completion [target] and [dotTarget] based on the given [unit].
+ */
+ Expression _computeDotTarget(CompletionRequest request) {
+ CompletionTarget target =
+ new CompletionTarget.forOffset(request.result.unit, request.offset);
+ AstNode node = target.containingNode;
+ if (node is MethodInvocation) {
+ if (identical(node.methodName, target.entity)) {
+ return node.realTarget;
+ } else if (node.isCascaded && node.operator.offset + 1 == target.offset) {
+ return node.realTarget;
+ }
+ }
+ if (node is PropertyAccess) {
+ if (identical(node.propertyName, target.entity)) {
+ return node.realTarget;
+ } else if (node.isCascaded && node.operator.offset + 1 == target.offset) {
+ return node.realTarget;
+ }
+ }
+ if (node is PrefixedIdentifier) {
+ if (identical(node.identifier, target.entity)) {
+ return node.prefix;
+ }
+ }
+ return null;
+ }
+}
+
+/**
+ * An [AstVisitor] which looks for a declaration with the given name
+ * and if found, tries to determine a type for that declaration.
+ */
+class _LocalBestTypeVisitor extends LocalDeclarationVisitor {
+ /**
+ * The name for the declaration to be found.
+ */
+ final String targetName;
+
+ /**
+ * The best type for the found declaration,
+ * or `null` if no declaration found or failed to determine a type.
+ */
+ DartType typeFound;
+
+ /**
+ * Construct a new instance to search for a declaration
+ */
+ _LocalBestTypeVisitor(this.targetName, int offset) : super(offset);
+
+ @override
+ void declaredClass(ClassDeclaration declaration) {
+ if (declaration.name.name == targetName) {
+ // no type
+ finished();
+ }
+ }
+
+ @override
+ void declaredClassTypeAlias(ClassTypeAlias declaration) {
+ if (declaration.name.name == targetName) {
+ // no type
+ finished();
+ }
+ }
+
+ @override
+ void declaredField(FieldDeclaration fieldDecl, VariableDeclaration varDecl) {
+ if (varDecl.name.name == targetName) {
+ // Type provided by the element in computeFull above
+ finished();
+ }
+ }
+
+ @override
+ void declaredFunction(FunctionDeclaration declaration) {
+ if (declaration.name.name == targetName) {
+ TypeAnnotation typeName = declaration.returnType;
+ if (typeName != null) {
+ typeFound = typeName.type;
+ }
+ finished();
+ }
+ }
+
+ @override
+ void declaredFunctionTypeAlias(FunctionTypeAlias declaration) {
+ if (declaration.name.name == targetName) {
+ TypeAnnotation typeName = declaration.returnType;
+ if (typeName != null) {
+ typeFound = typeName.type;
+ }
+ finished();
+ }
+ }
+
+ @override
+ void declaredLabel(Label label, bool isCaseLabel) {
+ if (label.label.name == targetName) {
+ // no type
+ finished();
+ }
+ }
+
+ @override
+ void declaredLocalVar(SimpleIdentifier name, TypeAnnotation type) {
+ if (name.name == targetName) {
+ typeFound = name.bestType;
+ finished();
+ }
+ }
+
+ @override
+ void declaredMethod(MethodDeclaration declaration) {
+ if (declaration.name.name == targetName) {
+ TypeAnnotation typeName = declaration.returnType;
+ if (typeName != null) {
+ typeFound = typeName.type;
+ }
+ finished();
+ }
+ }
+
+ @override
+ void declaredParam(SimpleIdentifier name, TypeAnnotation type) {
+ if (name.name == targetName) {
+ // Type provided by the element in computeFull above
+ finished();
+ }
+ }
+
+ @override
+ void declaredTopLevelVar(
+ VariableDeclarationList varList, VariableDeclaration varDecl) {
+ if (varDecl.name.name == targetName) {
+ // Type provided by the element in computeFull above
+ finished();
+ }
+ }
+}
+
+/**
+ * This class provides suggestions based upon the visible instance members in
+ * an interface type.
+ */
+class _SuggestionBuilder {
+ /**
+ * 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 resource provider used to access the file system.
+ */
+ final ResourceProvider resourceProvider;
+
+ /**
+ * The collector being used to collect completion suggestions.
+ */
+ final CompletionCollector collector;
+
+ /**
+ * The library containing the unit in which the completion is requested.
+ */
+ final LibraryElement containingLibrary;
+
+ /**
+ * 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.
+ */
+ Map<String, int> _completionTypesGenerated = new HashMap<String, int>();
+
+ /**
+ * Map from completion identifier to completion suggestion
+ */
+ Map<String, CompletionSuggestion> _suggestionMap =
+ <String, CompletionSuggestion>{};
+
+ /**
+ * The builder used to build suggestions.
+ */
+ final SuggestionBuilder builder;
+
+ _SuggestionBuilder(
+ this.resourceProvider, this.collector, this.containingLibrary)
+ : builder = new SuggestionBuilderImpl(resourceProvider);
+
+ Iterable<CompletionSuggestion> get suggestions => _suggestionMap.values;
+
+ /**
+ * Create completion suggestions for 'dot' completions on the given [type].
+ * If the 'dot' completion is a super expression, then [containingMethodName]
+ * is the name of the method in which the completion is requested.
+ */
+ void buildSuggestions(InterfaceType type, String containingMethodName) {
+ // Visit all of the types in the class hierarchy, collecting possible
+ // completions. If multiple elements are found that complete to the same
+ // identifier, addSuggestion will discard all but the first (with a few
+ // exceptions to handle getter/setter pairs).
+ List<InterfaceType> types = _getTypeOrdering(type);
+ for (InterfaceType targetType in types) {
+ for (MethodElement method in targetType.methods) {
+ // Exclude static methods when completion on an instance
+ if (!method.isStatic) {
+ // Boost the relevance of a super expression
+ // calling a method of the same name as the containing method
+ _addSuggestion(method,
+ relevance: method.name == containingMethodName
+ ? DART_RELEVANCE_HIGH
+ : null);
+ }
+ }
+ for (PropertyAccessorElement propertyAccessor in targetType.accessors) {
+ if (!propertyAccessor.isStatic) {
+ if (propertyAccessor.isSynthetic) {
+ // Avoid visiting a field twice
+ if (propertyAccessor.isGetter) {
+ _addSuggestion(propertyAccessor.variable);
+ }
+ } else {
+ _addSuggestion(propertyAccessor);
+ }
+ }
+ }
+ }
+ for (CompletionSuggestion suggestion in suggestions) {
+ collector.addSuggestion(suggestion);
+ }
+ }
+
+ /**
+ * Add a suggestion based upon the given element, provided that it is not
+ * shadowed by a previously added suggestion.
+ */
+ void _addSuggestion(Element element, {int relevance}) {
+ if (element.isPrivate) {
+ if (element.library != containingLibrary) {
+ // Do not suggest private members for imported libraries
+ return;
+ }
+ }
+ String identifier = element.displayName;
+
+ if (relevance == null) {
+ // Decrease relevance of suggestions starting with $
+ // https://github.com/dart-lang/sdk/issues/27303
+ if (identifier != null && identifier.startsWith(r'$')) {
+ relevance = DART_RELEVANCE_LOW;
+ } else {
+ relevance = DART_RELEVANCE_DEFAULT;
+ }
+ }
+
+ int alreadyGenerated = _completionTypesGenerated.putIfAbsent(
+ identifier, () => _COMPLETION_TYPE_NONE);
+ if (element is MethodElement) {
+ // Anything shadows a method.
+ if (alreadyGenerated != _COMPLETION_TYPE_NONE) {
+ return;
+ }
+ _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;
+ }
+ _completionTypesGenerated[identifier] |= _COMPLETION_TYPE_GETTER;
+ } else {
+ // Setters, fields, and methods shadow a setter.
+ if ((alreadyGenerated & _COMPLETION_TYPE_SETTER) != 0) {
+ return;
+ }
+ _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;
+ }
+ _completionTypesGenerated[identifier] =
+ _COMPLETION_TYPE_FIELD_OR_METHOD_OR_GETSET;
+ } else {
+ // Unexpected element type; skip it.
+ assert(false);
+ return;
+ }
+ CompletionSuggestion suggestion =
+ builder.forElement(element, relevance: relevance);
+ if (suggestion != null) {
+ _suggestionMap[suggestion.completion] = suggestion;
+ }
+ }
+
+ /**
+ * Get a list of [InterfaceType]s that should be searched to find the
+ * possible completions for an object having type [type].
+ */
+ List<InterfaceType> _getTypeOrdering(InterfaceType type) {
+ // Candidate completions can come from [type] as well as any types above it
+ // in the class hierarchy (including mixins, superclasses, and interfaces).
+ // If a given completion identifier shows up in multiple types, we should
+ // use the element that is nearest in the superclass chain, so we will
+ // visit [type] first, then its mixins, then its superclass, then its
+ // superclass's mixins, etc., and only afterwards visit interfaces.
+ //
+ // We short-circuit loops in the class hierarchy by keeping track of the
+ // classes seen (not the interfaces) so that we won't be fooled by nonsense
+ // like "class C<T> extends C<List<T>> {}"
+ List<InterfaceType> result = <InterfaceType>[];
+ Set<ClassElement> classesSeen = new HashSet<ClassElement>();
+ List<InterfaceType> typesToVisit = <InterfaceType>[type];
+ while (typesToVisit.isNotEmpty) {
+ InterfaceType nextType = typesToVisit.removeLast();
+ if (!classesSeen.add(nextType.element)) {
+ // Class had already been seen, so ignore this type.
+ continue;
+ }
+ result.add(nextType);
+ // typesToVisit is a stack, so push on the interfaces first, then the
+ // superclass, then the mixins. This will ensure that they are visited
+ // in the reverse order.
+ typesToVisit.addAll(nextType.interfaces);
+ if (nextType.superclass != null) {
+ typesToVisit.add(nextType.superclass);
+ }
+ typesToVisit.addAll(nextType.mixins);
+ }
+ return result;
+ }
+}

Powered by Google App Engine
This is Rietveld 408576698