blob: 8f8e0240673dd228c9043d1eedec1f4605a24e53 [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.
import 'dart:async';
import 'package:analysis_server/src/provisional/completion/dart/completion_dart.dart';
import 'package:analysis_server/src/services/completion/dart/feature_computer.dart';
import 'package:analysis_server/src/services/completion/dart/suggestion_builder.dart'
show createSuggestion, ElementSuggestionBuilder, SuggestionBuilder;
import 'package:analyzer/dart/analysis/features.dart';
import 'package:analyzer/dart/element/element.dart';
import 'package:analyzer/dart/element/nullability_suffix.dart';
import 'package:analyzer/dart/element/type.dart';
import 'package:analyzer/dart/element/visitor.dart';
import 'package:analyzer_plugin/src/utilities/completion/optype.dart';
import '../../../protocol_server.dart'
show CompletionSuggestion, CompletionSuggestionKind;
/// A visitor for building suggestions based upon the elements defined by
/// a source file contained in the same library but not the same as
/// the source in which the completions are being requested.
class LibraryElementSuggestionBuilder extends GeneralizingElementVisitor
with ElementSuggestionBuilder {
@override
final DartCompletionRequest request;
final OpType optype;
DartType contextType;
@override
CompletionSuggestionKind kind;
final String prefix;
/// The set of libraries that have been, or are currently being, visited.
final Set<LibraryElement> visitedLibraries = <LibraryElement>{};
LibraryElementSuggestionBuilder(this.request, [this.prefix])
: optype = request.opType {
contextType = request.featureComputer
.computeContextType(request.target.containingNode);
kind = request.target.isFunctionalArgument()
? CompletionSuggestionKind.IDENTIFIER
: optype.suggestKind;
}
@override
LibraryElement get containingLibrary => request.libraryElement;
@override
void visitClassElement(ClassElement element) {
if (optype.includeTypeNameSuggestions) {
// if includeTypeNameSuggestions, then use the filter
int relevance;
if (request.useNewRelevance) {
relevance = _relevanceForType(element.thisType);
} else if (element.hasDeprecated) {
relevance = DART_RELEVANCE_LOW;
} else {
relevance = optype.typeNameSuggestionsFilter(
_instantiateClassElement(element), DART_RELEVANCE_DEFAULT);
}
if (relevance != null) {
addSuggestion(element, prefix: prefix, relevance: relevance);
}
}
if (optype.includeConstructorSuggestions) {
int relevance;
if (request.useNewRelevance) {
relevance = _relevanceForType(element.thisType);
} else if (element.hasDeprecated) {
relevance = DART_RELEVANCE_LOW;
} else {
relevance = optype.returnValueSuggestionsFilter(
_instantiateClassElement(element), DART_RELEVANCE_DEFAULT);
}
_addConstructorSuggestions(element, relevance);
}
if (optype.includeReturnValueSuggestions) {
if (element.isEnum) {
var enumName = element.displayName;
int relevance;
if (request.useNewRelevance) {
relevance = _relevanceForType(element.thisType);
} else if (element.hasDeprecated) {
relevance = DART_RELEVANCE_LOW;
} else {
relevance = optype.returnValueSuggestionsFilter(
_instantiateClassElement(element), DART_RELEVANCE_DEFAULT);
}
for (var field in element.fields) {
if (field.isEnumConstant) {
addSuggestion(field,
prefix: prefix,
relevance: relevance,
elementCompletion: '$enumName.${field.name}');
}
}
}
}
}
@override
void visitCompilationUnitElement(CompilationUnitElement element) {
element.visitChildren(this);
}
@override
void visitElement(Element element) {
// ignored
}
@override
void visitExtensionElement(ExtensionElement element) {
if (optype.includeReturnValueSuggestions) {
int relevance;
if (request.useNewRelevance) {
relevance = _relevanceForType(element.extendedType);
} else {
relevance =
element.hasDeprecated ? DART_RELEVANCE_LOW : DART_RELEVANCE_DEFAULT;
}
addSuggestion(element, prefix: prefix, relevance: relevance);
}
element.visitChildren(this);
}
@override
void visitFunctionElement(FunctionElement element) {
// Do not suggest operators or local functions
if (element.isOperator) {
return;
}
if (element.enclosingElement is! CompilationUnitElement) {
return;
}
var returnType = element.returnType;
int relevance;
if (request.useNewRelevance) {
relevance = _relevanceForType(returnType);
} else {
relevance = element.hasDeprecated
? DART_RELEVANCE_LOW
: (element.library == containingLibrary
? DART_RELEVANCE_LOCAL_FUNCTION
: DART_RELEVANCE_DEFAULT);
}
if (returnType != null && returnType.isVoid) {
if (optype.includeVoidReturnSuggestions) {
addSuggestion(element, prefix: prefix, relevance: relevance);
}
} else {
if (optype.includeReturnValueSuggestions) {
addSuggestion(element, prefix: prefix, relevance: relevance);
}
}
}
@override
void visitFunctionTypeAliasElement(FunctionTypeAliasElement element) {
if (optype.includeTypeNameSuggestions) {
int relevance;
if (request.useNewRelevance) {
// TODO(brianwilkerson) Figure out whether there are any features that
// ought to be used here and what the right default value is.
relevance = 400;
} else {
relevance = element.hasDeprecated
? DART_RELEVANCE_LOW
: (element.library == containingLibrary
? DART_RELEVANCE_LOCAL_FUNCTION
: DART_RELEVANCE_DEFAULT);
}
addSuggestion(element, prefix: prefix, relevance: relevance);
}
}
@override
void visitLibraryElement(LibraryElement element) {
if (visitedLibraries.add(element)) {
element.visitChildren(this);
}
}
@override
void visitPropertyAccessorElement(PropertyAccessorElement element) {
if (optype.includeReturnValueSuggestions) {
int relevance;
if (request.useNewRelevance) {
relevance = _relevanceForType(element.returnType);
} else if (element.hasDeprecated) {
relevance = DART_RELEVANCE_LOW;
} else {
if (element.library == containingLibrary) {
if (element.enclosingElement is ClassElement) {
relevance = DART_RELEVANCE_LOCAL_FIELD;
} else {
relevance = DART_RELEVANCE_LOCAL_TOP_LEVEL_VARIABLE;
}
} else {
relevance = DART_RELEVANCE_DEFAULT;
}
}
addSuggestion(element, prefix: prefix, relevance: relevance);
}
}
@override
void visitTopLevelVariableElement(TopLevelVariableElement element) {
if (optype.includeReturnValueSuggestions) {
int relevance;
if (request.useNewRelevance) {
relevance = _relevanceForType(element.type);
} else {
relevance = element.hasDeprecated
? DART_RELEVANCE_LOW
: (element.library == containingLibrary
? DART_RELEVANCE_LOCAL_TOP_LEVEL_VARIABLE
: DART_RELEVANCE_DEFAULT);
}
addSuggestion(element, prefix: prefix, relevance: relevance);
}
}
/// Add constructor suggestions for the given class.
void _addConstructorSuggestions(ClassElement classElem, int relevance) {
var className = classElem.name;
for (var constructor in classElem.constructors) {
if (constructor.isPrivate) {
continue;
}
if (classElem.isAbstract && !constructor.isFactory) {
continue;
}
var completion = constructor.displayName;
completion = completion.isNotEmpty ? '$className.$completion' : className;
if (prefix != null && prefix.isNotEmpty) {
completion = '$prefix.$completion';
}
var suggestion = createSuggestion(request, constructor,
completion: completion, relevance: relevance);
if (suggestion != null) {
suggestion.selectionOffset = suggestion.completion.length;
suggestions.add(suggestion);
}
}
}
InterfaceType _instantiateClassElement(ClassElement element) {
var typeParameters = element.typeParameters;
var typeArguments = const <DartType>[];
if (typeParameters.isNotEmpty) {
var typeProvider = request.libraryElement.typeProvider;
typeArguments = typeParameters.map((t) {
return typeProvider.dynamicType;
}).toList();
}
var nullabilitySuffix = request.featureSet.isEnabled(Feature.non_nullable)
? NullabilitySuffix.none
: NullabilitySuffix.star;
return element.instantiate(
typeArguments: typeArguments,
nullabilitySuffix: nullabilitySuffix,
);
}
int _relevanceForType(DartType elementType) {
var contextTypeFeature =
request.featureComputer.contextTypeFeature(contextType, elementType);
// TODO(brianwilkerson) Figure out whether there are other features that
// ought to be used here and what the right default value is.
return toRelevance(contextTypeFeature, 800);
}
}
/// A contributor that produces suggestions based on the top level members in
/// the library in which the completion is requested but outside the file in
/// which the completion is requested.
class LocalLibraryContributor extends DartCompletionContributor {
@override
Future<List<CompletionSuggestion>> computeSuggestions(
DartCompletionRequest request, SuggestionBuilder builder) async {
if (!request.includeIdentifiers) {
return const <CompletionSuggestion>[];
}
var libraryUnits = request.result.unit.declaredElement.library.units;
if (libraryUnits == null) {
return const <CompletionSuggestion>[];
}
var visitor = LibraryElementSuggestionBuilder(request);
for (var unit in libraryUnits) {
if (unit != null && unit.source != request.source) {
unit.accept(visitor);
}
}
return visitor.suggestions;
}
}