| // Copyright (c) 2014, 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. |
| |
| library services.completion.suggestion.builder; |
| |
| 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/services/completion/dart_completion_manager.dart'; |
| import 'package:analyzer/src/generated/ast.dart'; |
| import 'package:analyzer/src/generated/element.dart'; |
| |
| /** |
| * Call the given function with each non-null non-empty inherited type name |
| * that is defined in the given class. |
| */ |
| visitInheritedTypeNames(ClassDeclaration node, void inherited(String name)) { |
| |
| void visit(TypeName type) { |
| if (type != null) { |
| Identifier id = type.name; |
| if (id != null) { |
| String name = id.name; |
| if (name != null && name.length > 0) { |
| inherited(name); |
| } |
| } |
| } |
| } |
| |
| ExtendsClause extendsClause = node.extendsClause; |
| if (extendsClause != null) { |
| visit(extendsClause.superclass); |
| } |
| ImplementsClause implementsClause = node.implementsClause; |
| if (implementsClause != null) { |
| NodeList<TypeName> interfaces = implementsClause.interfaces; |
| if (interfaces != null) { |
| interfaces.forEach((TypeName type) { |
| visit(type); |
| }); |
| } |
| } |
| WithClause withClause = node.withClause; |
| if (withClause != null) { |
| NodeList<TypeName> mixinTypes = withClause.mixinTypes; |
| if (mixinTypes != null) { |
| mixinTypes.forEach((TypeName type) { |
| visit(type); |
| }); |
| } |
| } |
| } |
| |
| /** |
| * Call the given functions with each non-null non-empty inherited class |
| * declaration, if the class is defined locally, or type name if it is not |
| * defined locally. |
| */ |
| void visitInheritedTypes(ClassDeclaration node, void |
| local(ClassDeclaration classNode), void imported(String typeName)) { |
| CompilationUnit unit = node.getAncestor((p) => p is CompilationUnit); |
| List<ClassDeclaration> todo = new List<ClassDeclaration>(); |
| todo.add(node); |
| Set<String> visited = new Set<String>(); |
| while (todo.length > 0) { |
| node = todo.removeLast(); |
| visitInheritedTypeNames(node, (String name) { |
| if (visited.add(name)) { |
| var classNode = unit.declarations.firstWhere((member) { |
| if (member is ClassDeclaration) { |
| SimpleIdentifier id = member.name; |
| if (id != null && id.name == name) { |
| return true; |
| } |
| } |
| return false; |
| }, orElse: () => null); |
| if (classNode is ClassDeclaration) { |
| local(classNode); |
| todo.add(classNode); |
| } else { |
| imported(name); |
| } |
| } |
| }); |
| } |
| } |
| |
| /** |
| * 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 _AbstractSuggestionBuilder { |
| final bool staticOnly; |
| |
| ClassElementSuggestionBuilder(DartCompletionRequest request, {bool staticOnly: |
| false}) |
| : super(request), |
| this.staticOnly = staticOnly; |
| |
| @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; |
| } |
| _addElementSuggestion( |
| element, |
| CompletionSuggestionKind.GETTER, |
| element.type, |
| element.enclosingElement); |
| } |
| |
| @override |
| visitMethodElement(MethodElement element) { |
| if (staticOnly && !element.isStatic) { |
| return; |
| } |
| if (element.isOperator) { |
| return; |
| } |
| _addElementSuggestion( |
| element, |
| CompletionSuggestionKind.METHOD, |
| element.returnType, |
| element.enclosingElement); |
| } |
| |
| @override |
| visitPropertyAccessorElement(PropertyAccessorElement element) { |
| if (staticOnly && !element.isStatic) { |
| return; |
| } |
| if (element.isGetter) { |
| _addElementSuggestion( |
| element, |
| CompletionSuggestionKind.GETTER, |
| element.returnType, |
| element.enclosingElement); |
| } else if (element.isSetter) { |
| _addElementSuggestion( |
| element, |
| CompletionSuggestionKind.SETTER, |
| element.returnType, |
| element.enclosingElement); |
| } |
| } |
| |
| /** |
| * Add suggestions for the visible members in the given class |
| */ |
| static void suggestionsFor(DartCompletionRequest request, Element element) { |
| if (element is ClassElement) { |
| return element.accept(new ClassElementSuggestionBuilder(request)); |
| } |
| } |
| |
| /** |
| * Add suggestions for the visible static members in the given class |
| */ |
| static void staticSuggestionsFor(DartCompletionRequest request, |
| ClassElement element) { |
| return element.accept( |
| new ClassElementSuggestionBuilder(request, staticOnly: true)); |
| } |
| } |
| |
| /** |
| * This class visits elements in a library and provides suggestions based upon |
| * the visible members in that library. Clients should call |
| * [LibraryElementSuggestionBuilder.suggestionsFor]. |
| */ |
| class LibraryElementSuggestionBuilder extends _AbstractSuggestionBuilder { |
| |
| LibraryElementSuggestionBuilder(DartCompletionRequest request) |
| : super(request); |
| |
| @override |
| visitClassElement(ClassElement element) { |
| _addElementSuggestion(element, CompletionSuggestionKind.CLASS, null, null); |
| } |
| |
| @override |
| visitCompilationUnitElement(CompilationUnitElement element) { |
| element.visitChildren(this); |
| } |
| |
| @override |
| visitElement(Element element) { |
| // ignored |
| } |
| |
| @override |
| visitFunctionElement(FunctionElement element) { |
| _addElementSuggestion( |
| element, |
| CompletionSuggestionKind.FUNCTION, |
| element.returnType, |
| null); |
| } |
| |
| @override |
| visitFunctionTypeAliasElement(FunctionTypeAliasElement element) { |
| _addElementSuggestion( |
| element, |
| CompletionSuggestionKind.FUNCTION_TYPE_ALIAS, |
| element.returnType, |
| null); |
| } |
| |
| @override |
| visitTopLevelVariableElement(TopLevelVariableElement element) { |
| _addElementSuggestion( |
| element, |
| CompletionSuggestionKind.TOP_LEVEL_VARIABLE, |
| element.type, |
| null); |
| } |
| |
| /** |
| * Add suggestions for the visible members in the given library |
| */ |
| static void suggestionsFor(DartCompletionRequest request, |
| LibraryElement library) { |
| if (library != null) { |
| library.visitChildren(new LibraryElementSuggestionBuilder(request)); |
| } |
| } |
| } |
| |
| /** |
| * This class visits elements in a class and provides suggestions based upon |
| * the visible named constructors in that class. Clients should call |
| * [NamedConstructorSuggestionBuilder.suggestionsFor]. |
| */ |
| class NamedConstructorSuggestionBuilder extends _AbstractSuggestionBuilder { |
| |
| NamedConstructorSuggestionBuilder(DartCompletionRequest request) |
| : super(request); |
| |
| @override |
| visitClassElement(ClassElement element) { |
| element.visitChildren(this); |
| } |
| |
| @override |
| visitConstructorElement(ConstructorElement element) { |
| _addElementSuggestion( |
| element, |
| CompletionSuggestionKind.CONSTRUCTOR, |
| element.returnType, |
| element.enclosingElement); |
| } |
| |
| @override |
| visitElement(Element element) { |
| // ignored |
| } |
| |
| /** |
| * Add suggestions for the visible members in the given class |
| */ |
| static void suggestionsFor(DartCompletionRequest request, Element element) { |
| if (element is ClassElement) { |
| element.accept(new NamedConstructorSuggestionBuilder(request)); |
| } |
| } |
| } |
| |
| /** |
| * Common superclass for sharing behavior |
| */ |
| class _AbstractSuggestionBuilder extends GeneralizingElementVisitor { |
| final DartCompletionRequest request; |
| final Set<String> _completions = new Set<String>(); |
| |
| _AbstractSuggestionBuilder(this.request); |
| |
| void _addElementSuggestion(Element element, CompletionSuggestionKind kind, |
| DartType type, ClassElement enclosingElement) { |
| if (element.isSynthetic) { |
| return; |
| } |
| if (element.isPrivate) { |
| LibraryElement elementLibrary = element.library; |
| LibraryElement unitLibrary = request.unit.element.library; |
| if (elementLibrary != unitLibrary) { |
| return; |
| } |
| } |
| String completion = element.displayName; |
| if (completion == null || |
| completion.length <= 0 || |
| !_completions.add(completion)) { |
| return; |
| } |
| CompletionSuggestion suggestion = new CompletionSuggestion( |
| kind, |
| CompletionRelevance.DEFAULT, |
| completion, |
| completion.length, |
| 0, |
| element.isDeprecated, |
| false); |
| suggestion.element = protocol.newElement_fromEngine(element); |
| if (suggestion.element != null) { |
| if (element is FieldElement) { |
| suggestion.element.kind = protocol.ElementKind.GETTER; |
| suggestion.element.returnType = |
| element.type != null ? element.type.displayName : 'dynamic'; |
| } |
| } |
| if (enclosingElement != null) { |
| suggestion.declaringType = enclosingElement.displayName; |
| } |
| if (type != null) { |
| String typeName = type.displayName; |
| if (typeName != null && typeName.length > 0 && typeName != 'dynamic') { |
| suggestion.returnType = typeName; |
| } |
| } |
| request.suggestions.add(suggestion); |
| } |
| } |