blob: f2a9cfa55ef5ca48ee83db08a506eb8fb29e1e55 [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.contributor.dart.toplevel;
import 'dart:async';
import 'dart:collection';
import 'package:analysis_server/src/protocol_server.dart'
hide Element, ElementKind;
import 'package:analysis_server/src/services/completion/dart_completion_cache.dart';
import 'package:analysis_server/src/services/completion/dart_completion_manager.dart';
import 'package:analysis_server/src/services/completion/optype.dart';
import 'package:analysis_server/src/services/completion/suggestion_builder.dart';
import 'package:analyzer/src/generated/ast.dart';
import 'package:analyzer/src/generated/element.dart';
/**
* A contributor for calculating imported class and top level variable
* `completion.getSuggestions` request results.
*/
class ImportedReferenceContributor extends DartCompletionContributor {
bool shouldWaitForLowPrioritySuggestions;
bool suggestionsComputed;
_ImportedSuggestionBuilder builder;
ImportedReferenceContributor(
{this.shouldWaitForLowPrioritySuggestions: false});
@override
bool computeFast(DartCompletionRequest request) {
// Don't suggest in comments.
if (request.target.isCommentText) {
return true;
}
OpType optype = request.optype;
if (!optype.isPrefixed) {
if (optype.includeReturnValueSuggestions ||
optype.includeTypeNameSuggestions ||
optype.includeVoidReturnSuggestions ||
optype.includeConstructorSuggestions) {
builder = new _ImportedSuggestionBuilder(request, optype);
builder.shouldWaitForLowPrioritySuggestions =
shouldWaitForLowPrioritySuggestions;
// If target is an argument in an argument list
// then suggestions may need to be adjusted
suggestionsComputed =
builder.computeFast(request.target.containingNode);
return suggestionsComputed && request.target.argIndex == null;
}
}
return true;
}
@override
Future<bool> computeFull(DartCompletionRequest request) async {
if (builder != null) {
if (!suggestionsComputed) {
bool result = await builder.computeFull(request.target.containingNode);
_updateSuggestions(request);
return result;
}
_updateSuggestions(request);
return true;
}
return false;
}
/**
* If target is a function argument, suggest identifiers not invocations
*/
void _updateSuggestions(DartCompletionRequest request) {
if (request.target.isFunctionalArgument()) {
request.convertInvocationsToIdentifiers();
}
}
}
/**
* [_ImportedSuggestionBuilder] traverses the imports and builds suggestions
* based upon imported elements.
*/
class _ImportedSuggestionBuilder extends ElementSuggestionBuilder
implements SuggestionBuilder {
bool shouldWaitForLowPrioritySuggestions;
final DartCompletionRequest request;
final OpType optype;
DartCompletionCache cache;
_ImportedSuggestionBuilder(this.request, this.optype) {
cache = request.cache;
}
@override
CompletionSuggestionKind get kind => CompletionSuggestionKind.INVOCATION;
/**
* If the needed information is cached, then add suggestions and return `true`
* else return `false` indicating that additional work is necessary.
*/
bool computeFast(AstNode node) {
CompilationUnit unit = request.unit;
if (cache.isImportInfoCached(unit)) {
_addSuggestions(node);
return true;
}
return false;
}
/**
* Compute suggested based upon imported elements.
*/
Future<bool> computeFull(AstNode node) {
Future<bool> addSuggestions(_) {
_addSuggestions(node);
return new Future.value(true);
}
Future future = null;
if (!cache.isImportInfoCached(request.unit)) {
future = cache.computeImportInfo(request.unit, request.searchEngine,
shouldWaitForLowPrioritySuggestions);
}
if (future != null) {
return future.then(addSuggestions);
}
return addSuggestions(true);
}
/**
* Add constructor and library prefix suggestions from the cache.
* To reduce the number of suggestions sent to the client,
* filter the suggestions based upon the first character typed.
* If no characters are available to use for filtering,
* then exclude all low priority suggestions.
*/
void _addConstructorSuggestions() {
String filterText = request.filterText;
if (filterText.length > 1) {
filterText = filterText.substring(0, 1);
}
DartCompletionCache cache = request.cache;
_addFilteredSuggestions(filterText, cache.importedConstructorSuggestions);
}
/**
* Add imported element suggestions.
*/
void _addElementSuggestions(List<Element> elements,
{int relevance: DART_RELEVANCE_DEFAULT}) {
for (Element elem in elements) {
if (elem is! ClassElement) {
if (optype.includeOnlyTypeNameSuggestions) {
return;
}
if (elem is ExecutableElement) {
DartType returnType = elem.returnType;
if (returnType != null && returnType.isVoid) {
if (!optype.includeVoidReturnSuggestions) {
return;
}
}
}
}
addSuggestion(elem, relevance: relevance);
}
;
}
/**
* Add suggestions which start with the given text.
*/
_addFilteredSuggestions(
String filterText, List<CompletionSuggestion> unfiltered) {
//TODO (danrubel) Revisit this filtering once paged API has been added
unfiltered.forEach((CompletionSuggestion suggestion) {
if (filterText.length > 0) {
if (suggestion.completion.startsWith(filterText)) {
request.addSuggestion(suggestion);
}
} else {
if (suggestion.relevance != DART_RELEVANCE_LOW) {
request.addSuggestion(suggestion);
}
}
});
}
/**
* Add suggestions for any inherited imported members.
*/
void _addInheritedSuggestions(AstNode node) {
var classDecl = node.getAncestor((p) => p is ClassDeclaration);
if (classDecl is ClassDeclaration && !optype.inStaticMethodBody) {
// Build a list of inherited types that are imported
// and include any inherited imported members
List<String> inheritedTypes = new List<String>();
// local declarations are handled by the local reference contributor
visitInheritedTypes(classDecl, importedTypeName: (String typeName) {
inheritedTypes.add(typeName);
});
HashSet<String> visited = new HashSet<String>();
while (inheritedTypes.length > 0) {
String name = inheritedTypes.removeLast();
ClassElement elem = cache.importedClassMap[name];
if (visited.add(name) && elem != null) {
_addElementSuggestions(elem.fields,
relevance: DART_RELEVANCE_INHERITED_FIELD);
_addElementSuggestions(elem.accessors,
relevance: DART_RELEVANCE_INHERITED_ACCESSOR);
_addElementSuggestions(elem.methods,
relevance: DART_RELEVANCE_INHERITED_METHOD);
elem.allSupertypes.forEach((InterfaceType type) {
if (visited.add(type.name) && type.element != null) {
_addElementSuggestions(type.element.fields,
relevance: DART_RELEVANCE_INHERITED_FIELD);
_addElementSuggestions(type.element.accessors,
relevance: DART_RELEVANCE_INHERITED_ACCESSOR);
_addElementSuggestions(type.element.methods,
relevance: DART_RELEVANCE_INHERITED_METHOD);
}
});
}
}
}
}
/**
* Add suggested based upon imported elements.
*/
void _addSuggestions(AstNode node) {
if (optype.includeConstructorSuggestions) {
_addConstructorSuggestions();
}
if (optype.includeReturnValueSuggestions ||
optype.includeTypeNameSuggestions ||
optype.includeVoidReturnSuggestions) {
_addInheritedSuggestions(node);
_addTopLevelSuggestions();
}
}
/**
* Add top level suggestions from the cache.
* To reduce the number of suggestions sent to the client,
* filter the suggestions based upon the first character typed.
* If no characters are available to use for filtering,
* then exclude all low priority suggestions.
*/
void _addTopLevelSuggestions() {
String filterText = request.filterText;
if (filterText.length > 1) {
filterText = filterText.substring(0, 1);
}
DartCompletionCache cache = request.cache;
if (optype.includeTypeNameSuggestions) {
_addFilteredSuggestions(filterText, cache.importedTypeSuggestions);
}
if (optype.includeReturnValueSuggestions) {
_addFilteredSuggestions(filterText, cache.otherImportedSuggestions);
}
if (optype.includeVoidReturnSuggestions) {
_addFilteredSuggestions(filterText, cache.importedVoidReturnSuggestions);
}
}
}