blob: 391ac2a41a23d26b0eeb1189264c3d6499d0a0ed [file] [log] [blame]
// 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);
}
}