Chromium Code Reviews| Index: pkg/analysis_server/lib/src/services/completion/suggestion_builder.dart |
| diff --git a/pkg/analysis_server/lib/src/services/completion/suggestion_builder.dart b/pkg/analysis_server/lib/src/services/completion/suggestion_builder.dart |
| index 8be796345634bfcd7e4fc423bb6c0a2f745fd91c..197876ad67ca79ed7d7362e9e2038cd8448f204c 100644 |
| --- a/pkg/analysis_server/lib/src/services/completion/suggestion_builder.dart |
| +++ b/pkg/analysis_server/lib/src/services/completion/suggestion_builder.dart |
| @@ -5,6 +5,7 @@ |
| library services.completion.suggestion.builder; |
| import 'dart:async'; |
| +import 'dart:collection'; |
| import 'package:analysis_server/src/protocol_server.dart' as protocol; |
| import 'package:analysis_server/src/protocol_server.dart' hide Element, |
| @@ -64,11 +65,9 @@ CompletionSuggestion createSuggestion(Element element, |
| isDeprecated, |
| false); |
| suggestion.element = protocol.newElement_fromEngine(element); |
| - if (element is ClassMemberElement) { |
| - ClassElement enclosingElement = element.enclosingElement; |
| - if (enclosingElement != null) { |
| - suggestion.declaringType = enclosingElement.displayName; |
| - } |
| + Element enclosingElement = element.enclosingElement; |
| + if (enclosingElement is ClassElement) { |
| + suggestion.declaringType = enclosingElement.displayName; |
| } |
| suggestion.returnType = returnType; |
| if (element is ExecutableElement && element is! PropertyAccessorElement) { |
| @@ -165,77 +164,6 @@ void visitInheritedTypes(ClassDeclaration node, void |
| } |
| /** |
| - * This class visits elements in a class and provides suggestions based upon |
| - * the visible members in that class. Clients should call |
| - * [ClassElementSuggestionBuilder.suggestionsFor]. |
| - */ |
| -class ClassElementSuggestionBuilder extends GeneralizingElementVisitor with |
| - ElementSuggestionBuilder { |
| - final bool staticOnly; |
| - final DartCompletionRequest request; |
| - |
| - ClassElementSuggestionBuilder(this.request, bool staticOnly) |
| - : this.staticOnly = staticOnly; |
| - |
| - @override |
| - CompletionSuggestionKind get kind => CompletionSuggestionKind.INVOCATION; |
| - |
| - @override |
| - visitClassElement(ClassElement element) { |
| - element.visitChildren(this); |
| - element.allSupertypes.forEach((InterfaceType type) { |
| - type.element.visitChildren(this); |
| - }); |
| - } |
| - |
| - @override |
| - visitElement(Element element) { |
| - // ignored |
| - } |
| - |
| - @override |
| - visitFieldElement(FieldElement element) { |
| - if (staticOnly && !element.isStatic) { |
| - return; |
| - } |
| - addSuggestion(element); |
| - } |
| - |
| - @override |
| - visitMethodElement(MethodElement element) { |
| - if (staticOnly && !element.isStatic) { |
| - return; |
| - } |
| - if (element.isOperator) { |
| - return; |
| - } |
| - addSuggestion(element); |
| - } |
| - |
| - @override |
| - visitPropertyAccessorElement(PropertyAccessorElement element) { |
| - if (staticOnly && !element.isStatic) { |
| - return; |
| - } |
| - addSuggestion(element); |
| - } |
| - |
| - /** |
| - * Add suggestions for the visible members in the given class |
| - */ |
| - static void suggestionsFor(DartCompletionRequest request, Element element, |
| - {bool staticOnly: false}) { |
| - if (element == DynamicElementImpl.instance) { |
| - element = request.cache.objectClassElement; |
| - } |
| - if (element is ClassElement) { |
| - return element.accept( |
| - new ClassElementSuggestionBuilder(request, staticOnly)); |
| - } |
| - } |
| -} |
| - |
| -/** |
| * Common mixin for sharing behavior |
| */ |
| abstract class ElementSuggestionBuilder { |
| @@ -285,6 +213,185 @@ abstract class ElementSuggestionBuilder { |
| } |
| /** |
| + * This class provides suggestions based upon the visible instance members in |
| + * an interface type. Clients should call |
| + * [InterfaceTypeSuggestionBuilder.suggestionsFor]. |
| + */ |
| +class InterfaceTypeSuggestionBuilder { |
| + /** |
| + * 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; |
| + |
| + final DartCompletionRequest request; |
| + |
| + /** |
| + * 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>(); |
| + |
| + InterfaceTypeSuggestionBuilder(this.request); |
| + |
| + @override |
|
danrubel
2015/01/15 02:53:25
Remove override annotation?
Paul Berry
2015/01/21 15:10:33
Done.
|
| + CompletionSuggestionKind get kind => CompletionSuggestionKind.INVOCATION; |
| + |
| + /** |
| + * Add a suggestion based upon the given element, provided that it is not |
| + * shadowed by a previously added suggestion. |
| + */ |
| + void addSuggestion(Element element) { |
| + if (element.isPrivate) { |
| + LibraryElement elementLibrary = element.library; |
| + LibraryElement unitLibrary = request.unit.element.library; |
| + if (elementLibrary != unitLibrary) { |
| + return; |
| + } |
| + } |
| + String identifier = element.displayName; |
| + 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 = createSuggestion(element, kind: kind); |
| + if (suggestion != null) { |
| + request.suggestions.add(suggestion); |
| + } |
| + } |
| + |
| + void _buildSuggestions(InterfaceType type, LibraryElement library) { |
| + // 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). |
| + for (InterfaceType targetType in _getTypeOrdering(type)) { |
| + for (MethodElement method in targetType.methods) { |
| + addSuggestion(method); |
| + } |
| + for (PropertyAccessorElement propertyAccessor in targetType.accessors) { |
| + if (propertyAccessor.isSynthetic) { |
| + // Avoid visiting a field twice |
| + if (propertyAccessor.isGetter) { |
|
danrubel
2015/01/15 02:53:25
A synthetic getter indicates an non-synthetic fiel
Paul Berry
2015/01/21 15:10:33
That's true, but the only way InterfaceType provid
|
| + addSuggestion(propertyAccessor.variable); |
| + } |
| + } else { |
| + addSuggestion(propertyAccessor); |
| + } |
| + } |
| + } |
| + } |
| + |
| + /** |
| + * 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; |
| + } |
| + |
| + /** |
| + * Add suggestions for the visible members in the given interface |
| + */ |
| + static void suggestionsFor(DartCompletionRequest request, DartType type) { |
| + CompilationUnit compilationUnit = |
| + request.node.getAncestor((AstNode node) => node is CompilationUnit); |
| + LibraryElement library = compilationUnit.element.library; |
| + if (type is DynamicTypeImpl) { |
| + type = request.cache.objectClassElement.type; |
| + } |
| + if (type is InterfaceType) { |
| + return new InterfaceTypeSuggestionBuilder( |
| + request)._buildSuggestions(type, library); |
| + } |
| + } |
| +} |
| + |
| +/** |
| * This class visits elements in a library and provides suggestions based upon |
| * the visible members in that library. Clients should call |
| * [LibraryElementSuggestionBuilder.suggestionsFor]. |
| @@ -392,6 +499,73 @@ class NamedConstructorSuggestionBuilder extends GeneralizingElementVisitor with |
| } |
| /** |
| + * This class visits elements in a class and provides suggestions based upon |
| + * the visible static members in that class. Clients should call |
| + * [StaticClassElementSuggestionBuilder.suggestionsFor]. |
| + */ |
| +class StaticClassElementSuggestionBuilder extends GeneralizingElementVisitor |
| + with ElementSuggestionBuilder { |
| + final DartCompletionRequest request; |
| + |
| + StaticClassElementSuggestionBuilder(this.request); |
| + |
| + @override |
| + CompletionSuggestionKind get kind => CompletionSuggestionKind.INVOCATION; |
| + |
| + @override |
| + visitClassElement(ClassElement element) { |
| + element.visitChildren(this); |
| + element.allSupertypes.forEach((InterfaceType type) { |
| + type.element.visitChildren(this); |
| + }); |
| + } |
| + |
| + @override |
| + visitElement(Element element) { |
| + // ignored |
| + } |
| + |
| + @override |
| + visitFieldElement(FieldElement element) { |
| + if (!element.isStatic) { |
| + return; |
| + } |
| + addSuggestion(element); |
| + } |
| + |
| + @override |
| + visitMethodElement(MethodElement element) { |
| + if (!element.isStatic) { |
| + return; |
| + } |
| + if (element.isOperator) { |
| + return; |
| + } |
| + addSuggestion(element); |
| + } |
| + |
| + @override |
| + visitPropertyAccessorElement(PropertyAccessorElement element) { |
| + if (!element.isStatic) { |
| + return; |
| + } |
| + addSuggestion(element); |
| + } |
| + |
| + /** |
| + * Add suggestions for the visible members in the given class |
| + */ |
| + static void suggestionsFor(DartCompletionRequest request, Element element) { |
| + if (element == DynamicElementImpl.instance) { |
| + element = request.cache.objectClassElement; |
| + } |
| + if (element is ClassElement) { |
| + return element.accept(new StaticClassElementSuggestionBuilder(request)); |
| + } |
| + } |
| +} |
| + |
| +/** |
| * Common interface implemented by suggestion builders. |
| */ |
| abstract class SuggestionBuilder { |