blob: 8a72ce768501c468790e01d09d9a541e8190b27b [file] [log] [blame]
// Copyright (c) 2015, 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/protocol_server.dart';
import 'package:analysis_server/src/provisional/completion/completion_core.dart'
show AbortCompletion, CompletionContributor, CompletionRequest;
import 'package:analysis_server/src/provisional/completion/dart/completion_dart.dart';
import 'package:analysis_server/src/services/completion/completion_core.dart';
import 'package:analysis_server/src/services/completion/completion_performance.dart';
import 'package:analysis_server/src/services/completion/dart/arglist_contributor.dart';
import 'package:analysis_server/src/services/completion/dart/combinator_contributor.dart';
import 'package:analysis_server/src/services/completion/dart/common_usage_sorter.dart';
import 'package:analysis_server/src/services/completion/dart/completion_ranking.dart';
import 'package:analysis_server/src/services/completion/dart/contribution_sorter.dart';
import 'package:analysis_server/src/services/completion/dart/extension_member_contributor.dart';
import 'package:analysis_server/src/services/completion/dart/feature_computer.dart';
import 'package:analysis_server/src/services/completion/dart/field_formal_contributor.dart';
import 'package:analysis_server/src/services/completion/dart/imported_reference_contributor.dart';
import 'package:analysis_server/src/services/completion/dart/inherited_reference_contributor.dart';
import 'package:analysis_server/src/services/completion/dart/keyword_contributor.dart';
import 'package:analysis_server/src/services/completion/dart/label_contributor.dart';
import 'package:analysis_server/src/services/completion/dart/library_member_contributor.dart';
import 'package:analysis_server/src/services/completion/dart/library_prefix_contributor.dart';
import 'package:analysis_server/src/services/completion/dart/local_constructor_contributor.dart';
import 'package:analysis_server/src/services/completion/dart/local_library_contributor.dart';
import 'package:analysis_server/src/services/completion/dart/local_reference_contributor.dart';
import 'package:analysis_server/src/services/completion/dart/named_constructor_contributor.dart';
import 'package:analysis_server/src/services/completion/dart/override_contributor.dart';
import 'package:analysis_server/src/services/completion/dart/static_member_contributor.dart';
import 'package:analysis_server/src/services/completion/dart/suggestion_builder.dart';
import 'package:analysis_server/src/services/completion/dart/type_member_contributor.dart';
import 'package:analysis_server/src/services/completion/dart/uri_contributor.dart';
import 'package:analysis_server/src/services/completion/dart/variable_name_contributor.dart';
import 'package:analyzer/dart/analysis/features.dart';
import 'package:analyzer/dart/analysis/results.dart';
import 'package:analyzer/dart/analysis/session.dart';
import 'package:analyzer/dart/ast/ast.dart';
import 'package:analyzer/dart/ast/token.dart';
import 'package:analyzer/dart/element/element.dart';
import 'package:analyzer/dart/element/type.dart';
import 'package:analyzer/exception/exception.dart';
import 'package:analyzer/file_system/file_system.dart';
import 'package:analyzer/src/dart/analysis/driver_based_analysis_context.dart';
import 'package:analyzer/src/dartdoc/dartdoc_directive_info.dart';
import 'package:analyzer/src/generated/engine.dart';
import 'package:analyzer/src/generated/source.dart';
import 'package:analyzer_plugin/protocol/protocol_common.dart';
import 'package:analyzer_plugin/protocol/protocol_common.dart' as protocol;
import 'package:analyzer_plugin/src/utilities/completion/completion_target.dart';
import 'package:analyzer_plugin/src/utilities/completion/optype.dart';
/// [DartCompletionManager] determines if a completion request is Dart specific
/// and forwards those requests to all [DartCompletionContributor]s.
class DartCompletionManager implements CompletionContributor {
/// The [contributionSorter] is a long-lived object that isn't allowed
/// to maintain state between calls to [DartContributionSorter#sort(...)].
static DartContributionSorter contributionSorter = CommonUsageSorter();
/// The object used to resolve macros in Dartdoc comments.
final DartdocDirectiveInfo dartdocDirectiveInfo;
/// If not `null`, then instead of using [ImportedReferenceContributor],
/// fill this set with kinds of elements that are applicable at the
/// completion location, so should be suggested from available suggestion
/// sets.
final Set<protocol.ElementKind> includedElementKinds;
/// If [includedElementKinds] is not null, must be also not `null`, and
/// will be filled with names of all top-level declarations from all
/// included suggestion sets.
final Set<String> includedElementNames;
/// If [includedElementKinds] is not null, must be also not `null`, and
/// will be filled with tags for suggestions that should be given higher
/// relevance than other included suggestions.
final List<IncludedSuggestionRelevanceTag> includedSuggestionRelevanceTags;
/// Initialize a newly created completion manager. The parameters
/// [includedElementKinds], [includedElementNames], and
/// [includedSuggestionRelevanceTags] must either all be `null` or must all be
/// non-`null`.
DartCompletionManager({
this.dartdocDirectiveInfo,
this.includedElementKinds,
this.includedElementNames,
this.includedSuggestionRelevanceTags,
}) : assert((includedElementKinds != null &&
includedElementNames != null &&
includedSuggestionRelevanceTags != null) ||
(includedElementKinds == null &&
includedElementNames == null &&
includedSuggestionRelevanceTags == null));
@override
Future<List<CompletionSuggestion>> computeSuggestions(
CompletionRequest request) async {
request.checkAborted();
if (!AnalysisEngine.isDartFileName(request.result.path)) {
return const <CompletionSuggestion>[];
}
var performance = (request as CompletionRequestImpl).performance;
DartCompletionRequestImpl dartRequest =
await DartCompletionRequestImpl.from(request, dartdocDirectiveInfo);
// Don't suggest in comments.
if (dartRequest.target.isCommentText) {
return const <CompletionSuggestion>[];
}
final ranking = CompletionRanking.instance;
var probabilityFuture =
ranking != null ? ranking.predict(dartRequest) : Future.value(null);
var range = dartRequest.target.computeReplacementRange(dartRequest.offset);
(request as CompletionRequestImpl)
..replacementOffset = range.offset
..replacementLength = range.length;
// Request Dart specific completions from each contributor
var suggestionMap = <String, CompletionSuggestion>{};
var constructorMap = <String, List<String>>{};
var builder = SuggestionBuilder(dartRequest);
var contributors = <DartCompletionContributor>[
ArgListContributor(),
CombinatorContributor(),
ExtensionMemberContributor(),
FieldFormalContributor(),
InheritedReferenceContributor(),
KeywordContributor(),
LabelContributor(),
LibraryMemberContributor(),
LibraryPrefixContributor(),
LocalConstructorContributor(),
LocalLibraryContributor(),
LocalReferenceContributor(),
NamedConstructorContributor(),
OverrideContributor(),
StaticMemberContributor(),
TypeMemberContributor(),
UriContributor(),
VariableNameContributor()
];
if (includedElementKinds != null) {
_addIncludedElementKinds(dartRequest);
_addIncludedSuggestionRelevanceTags(dartRequest);
} else {
contributors.add(ImportedReferenceContributor());
}
void addSuggestionToMap(CompletionSuggestion newSuggestion) {
// TODO(brianwilkerson) After all contributors are using SuggestionBuilder
// move this logic into SuggestionBuilder.
var key = newSuggestion.completion;
// Append parenthesis for constructors to disambiguate from classes.
if (_isConstructor(newSuggestion)) {
key += '()';
var className = _getConstructorClassName(newSuggestion);
_ensureList(constructorMap, className).add(key);
}
// Local declarations hide both the class and its constructors.
if (!_isClass(newSuggestion)) {
var constructorKeys = constructorMap[key];
constructorKeys?.forEach(suggestionMap.remove);
}
var oldSuggestion = suggestionMap[key];
if (oldSuggestion == null ||
oldSuggestion.relevance < newSuggestion.relevance) {
suggestionMap[key] = newSuggestion;
}
}
try {
for (var contributor in contributors) {
var contributorTag =
'DartCompletionManager - ${contributor.runtimeType}';
performance.logStartTime(contributorTag);
var contributorSuggestions =
await contributor.computeSuggestions(dartRequest, builder);
performance.logElapseTime(contributorTag);
request.checkAborted();
for (var newSuggestion in contributorSuggestions) {
addSuggestionToMap(newSuggestion);
}
}
for (var newSuggestion in builder.suggestions) {
addSuggestionToMap(newSuggestion);
}
} on InconsistentAnalysisException {
// The state of the code being analyzed has changed, so results are likely
// to be inconsistent. Just abort the operation.
throw AbortCompletion();
}
// Adjust suggestion relevance before returning
var suggestions = suggestionMap.values.toList();
const SORT_TAG = 'DartCompletionManager - sort';
performance.logStartTime(SORT_TAG);
if (ranking != null) {
request.checkAborted();
try {
suggestions = await ranking.rerank(
probabilityFuture,
suggestions,
includedElementNames,
includedSuggestionRelevanceTags,
dartRequest,
request.result.unit.featureSet);
} catch (exception, stackTrace) {
// TODO(brianwilkerson) Shutdown the isolates that have already been
// started.
// Disable smart ranking if prediction fails.
CompletionRanking.instance = null;
AnalysisEngine.instance.instrumentationService.logException(
CaughtException.withMessage(
'Failed to rerank completion suggestions',
exception,
stackTrace));
await contributionSorter.sort(dartRequest, suggestions);
}
} else if (!request.useNewRelevance) {
await contributionSorter.sort(dartRequest, suggestions);
}
performance.logElapseTime(SORT_TAG);
request.checkAborted();
return suggestions;
}
void _addIncludedElementKinds(DartCompletionRequestImpl request) {
var opType = request.opType;
if (!opType.includeIdentifiers) return;
var kinds = includedElementKinds;
if (kinds != null) {
if (opType.includeConstructorSuggestions) {
kinds.add(protocol.ElementKind.CONSTRUCTOR);
}
if (opType.includeTypeNameSuggestions) {
kinds.add(protocol.ElementKind.CLASS);
kinds.add(protocol.ElementKind.CLASS_TYPE_ALIAS);
kinds.add(protocol.ElementKind.ENUM);
kinds.add(protocol.ElementKind.FUNCTION_TYPE_ALIAS);
kinds.add(protocol.ElementKind.MIXIN);
}
if (opType.includeReturnValueSuggestions) {
kinds.add(protocol.ElementKind.CONSTRUCTOR);
kinds.add(protocol.ElementKind.ENUM_CONSTANT);
kinds.add(protocol.ElementKind.EXTENSION);
// Static fields.
kinds.add(protocol.ElementKind.FIELD);
kinds.add(protocol.ElementKind.FUNCTION);
// Static and top-level properties.
kinds.add(protocol.ElementKind.GETTER);
kinds.add(protocol.ElementKind.SETTER);
kinds.add(protocol.ElementKind.TOP_LEVEL_VARIABLE);
}
}
}
void _addIncludedSuggestionRelevanceTags(DartCompletionRequestImpl request) {
var target = request.target;
void addTypeTag(DartType type) {
if (type is InterfaceType) {
var element = type.element;
var tag = '${element.librarySource.uri}::${element.name}';
if (element.isEnum) {
includedSuggestionRelevanceTags.add(
IncludedSuggestionRelevanceTag(
tag,
DART_RELEVANCE_BOOST_AVAILABLE_ENUM,
),
);
} else {
includedSuggestionRelevanceTags.add(
IncludedSuggestionRelevanceTag(
tag,
DART_RELEVANCE_BOOST_AVAILABLE_DECLARATION,
),
);
}
}
}
var parameter = target.parameterElement;
if (parameter != null) {
addTypeTag(parameter.type);
}
var containingNode = target.containingNode;
if (containingNode is AssignmentExpression &&
containingNode.operator.type == TokenType.EQ &&
target.offset >= containingNode.operator.end) {
addTypeTag(containingNode.leftHandSide.staticType);
}
if (containingNode is ListLiteral &&
target.offset >= containingNode.leftBracket.end &&
target.offset <= containingNode.rightBracket.offset) {
var type = containingNode.staticType;
if (type is InterfaceType) {
var typeArguments = type.typeArguments;
if (typeArguments.isNotEmpty) {
addTypeTag(typeArguments[0]);
}
}
}
if (containingNode is VariableDeclaration &&
containingNode.equals != null &&
target.offset >= containingNode.equals.end) {
var parent = containingNode.parent;
if (parent is VariableDeclarationList) {
var type = parent.type?.type;
if (type is InterfaceType) {
addTypeTag(type);
}
}
}
}
static List<String> _ensureList(Map<String, List<String>> map, String key) {
var list = map[key];
if (list == null) {
list = <String>[];
map[key] = list;
}
return list;
}
static String _getConstructorClassName(CompletionSuggestion suggestion) {
var completion = suggestion.completion;
var dotIndex = completion.indexOf('.');
if (dotIndex != -1) {
return completion.substring(0, dotIndex);
} else {
return completion;
}
}
static bool _isClass(CompletionSuggestion suggestion) {
return suggestion.element?.kind == protocol.ElementKind.CLASS;
}
static bool _isConstructor(CompletionSuggestion suggestion) {
return suggestion.element?.kind == protocol.ElementKind.CONSTRUCTOR;
}
}
/// The information about a requested list of completions within a Dart file.
class DartCompletionRequestImpl implements DartCompletionRequest {
@override
final ResolvedUnitResult result;
@override
final ResourceProvider resourceProvider;
@override
final InterfaceType objectType;
@override
final Source source;
@override
final int offset;
@override
Expression dotTarget;
@override
final Source librarySource;
@override
CompletionTarget target;
OpType _opType;
@override
final FeatureComputer featureComputer;
@override
final DartdocDirectiveInfo dartdocDirectiveInfo;
/// A flag indicating whether the [_contextType] has been computed.
bool _hasComputedContextType = false;
/// The context type associated with the target's `containingNode`.
DartType _contextType;
final CompletionRequest _originalRequest;
final CompletionPerformance performance;
DartCompletionRequestImpl._(
this.result,
this.resourceProvider,
this.objectType,
this.librarySource,
this.source,
this.offset,
CompilationUnit unit,
this.dartdocDirectiveInfo,
this._originalRequest,
this.performance)
: featureComputer =
FeatureComputer(result.typeSystem, result.typeProvider) {
_updateTargets(unit);
}
@override
DartType get contextType {
if (!_hasComputedContextType) {
_contextType = featureComputer.computeContextType(target.containingNode);
_hasComputedContextType = true;
}
return _contextType;
}
@override
FeatureSet get featureSet =>
result.session.analysisContext.analysisOptions.contextFeatures;
@override
bool get includeIdentifiers {
return opType.includeIdentifiers;
}
@override
LibraryElement get libraryElement {
//TODO(danrubel) build the library element rather than all the declarations
var unit = target.unit;
if (unit != null) {
var elem = unit.declaredElement;
if (elem != null) {
return elem.library;
}
}
return null;
}
@override
OpType get opType {
_opType ??= OpType.forCompletion(target, offset);
return _opType;
}
@override
String get sourceContents => result.content;
@override
SourceFactory get sourceFactory {
DriverBasedAnalysisContext context = result.session.analysisContext;
return context.driver.sourceFactory;
}
@override
bool get useNewRelevance => _originalRequest.useNewRelevance;
/// Throw [AbortCompletion] if the completion request has been aborted.
@override
void checkAborted() {
_originalRequest.checkAborted();
}
/// Update the completion [target] and [dotTarget] based on the given [unit].
void _updateTargets(CompilationUnit unit) {
_opType = null;
dotTarget = null;
target = CompletionTarget.forOffset(unit, offset);
var node = target.containingNode;
if (node is MethodInvocation) {
if (identical(node.methodName, target.entity)) {
dotTarget = node.realTarget;
} else if (node.isCascaded && node.operator.offset + 1 == target.offset) {
dotTarget = node.realTarget;
}
}
if (node is PropertyAccess) {
if (identical(node.propertyName, target.entity)) {
dotTarget = node.realTarget;
} else if (node.isCascaded && node.operator.offset + 1 == target.offset) {
dotTarget = node.realTarget;
}
}
if (node is PrefixedIdentifier) {
if (identical(node.identifier, target.entity)) {
dotTarget = node.prefix;
}
}
}
/// Return a [Future] that completes with a newly created completion request
/// based on the given [request]. This method will throw [AbortCompletion]
/// if the completion request has been aborted.
static Future<DartCompletionRequest> from(CompletionRequest request,
DartdocDirectiveInfo dartdocDirectiveInfo) async {
request.checkAborted();
var performance = (request as CompletionRequestImpl).performance;
const BUILD_REQUEST_TAG = 'build DartCompletionRequest';
performance.logStartTime(BUILD_REQUEST_TAG);
var unit = request.result.unit;
var libSource = unit.declaredElement.library.source;
var objectType = request.result.typeProvider.objectType;
var dartRequest = DartCompletionRequestImpl._(
request.result,
request.resourceProvider,
objectType,
libSource,
request.source,
request.offset,
unit,
dartdocDirectiveInfo,
request,
performance);
performance.logElapseTime(BUILD_REQUEST_TAG);
return dartRequest;
}
}