blob: 710ca2d9f15c9286c20c457eb9e0d568932b32e0 [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/contribution_sorter.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/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/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/file_system/file_system.dart';
import 'package:analyzer/src/dart/analysis/driver_based_analysis_context.dart';
import 'package:analyzer/src/generated/engine.dart' hide AnalysisResult;
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 = new CommonUsageSorter();
/// 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 tags for suggestions that should be given higher
/// relevance than other included suggestions.
final List<IncludedSuggestionRelevanceTag> includedSuggestionRelevanceTags;
DartCompletionManager({
this.includedElementKinds,
this.includedSuggestionRelevanceTags,
});
@override
Future<List<CompletionSuggestion>> computeSuggestions(
CompletionRequest request) async {
// TODO(brianwilkerson) Determine whether this await is necessary.
await null;
request.checkAborted();
if (!AnalysisEngine.isDartFileName(request.result.path)) {
return const <CompletionSuggestion>[];
}
CompletionPerformance performance =
(request as CompletionRequestImpl).performance;
DartCompletionRequestImpl dartRequest =
await DartCompletionRequestImpl.from(request);
// Don't suggest in comments.
if (dartRequest.target.isCommentText) {
return const <CompletionSuggestion>[];
}
SourceRange 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>>{};
List<DartCompletionContributor> contributors = <DartCompletionContributor>[
new ArgListContributor(),
new CombinatorContributor(),
new FieldFormalContributor(),
new InheritedReferenceContributor(),
new KeywordContributor(),
new LabelContributor(),
new LibraryMemberContributor(),
new LibraryPrefixContributor(),
new LocalConstructorContributor(),
new LocalLibraryContributor(),
new LocalReferenceContributor(),
new NamedConstructorContributor(),
new OverrideContributor(),
new StaticMemberContributor(),
new TypeMemberContributor(),
new UriContributor(),
new VariableNameContributor()
];
if (includedElementKinds != null) {
_addIncludedElementKinds(dartRequest);
_addIncludedSuggestionRelevanceTags(dartRequest);
} else {
contributors.add(new ImportedReferenceContributor());
}
try {
for (DartCompletionContributor contributor in contributors) {
String contributorTag =
'DartCompletionManager - ${contributor.runtimeType}';
performance.logStartTime(contributorTag);
List<CompletionSuggestion> contributorSuggestions =
await contributor.computeSuggestions(dartRequest);
performance.logElapseTime(contributorTag);
request.checkAborted();
for (CompletionSuggestion newSuggestion in contributorSuggestions) {
String key = newSuggestion.completion;
// Append parenthesis for constructors to disambiguate from classes.
if (_isConstructor(newSuggestion)) {
key += '()';
String className = _getConstructorClassName(newSuggestion);
_ensureList(constructorMap, className).add(key);
}
// Local declarations hide both the class and its constructors.
if (!_isClass(newSuggestion)) {
List<String> constructorKeys = constructorMap[key];
constructorKeys?.forEach(suggestionMap.remove);
}
CompletionSuggestion oldSuggestion = suggestionMap[key];
if (oldSuggestion == null ||
oldSuggestion.relevance < newSuggestion.relevance) {
suggestionMap[key] = newSuggestion;
}
}
}
} on InconsistentAnalysisException {
// The state of the code being analyzed has changed, so results are likely
// to be inconsistent. Just abort the operation.
throw new AbortCompletion();
}
// Adjust suggestion relevance before returning
List<CompletionSuggestion> suggestions = suggestionMap.values.toList();
const SORT_TAG = 'DartCompletionManager - sort';
performance.logStartTime(SORT_TAG);
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.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.ENUM_CONSTANT);
kinds.add(protocol.ElementKind.FUNCTION);
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]);
}
}
}
}
static List<String> _ensureList(Map<String, List<String>> map, String key) {
List<String> list = map[key];
if (list == null) {
list = <String>[];
map[key] = list;
}
return list;
}
static String _getConstructorClassName(CompletionSuggestion suggestion) {
String completion = suggestion.completion;
int 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;
final CompletionRequest _originalRequest;
final CompletionPerformance performance;
DartCompletionRequestImpl._(
this.result,
this.resourceProvider,
this.objectType,
this.librarySource,
this.source,
this.offset,
CompilationUnit unit,
this._originalRequest,
this.performance) {
_updateTargets(unit);
}
@override
List<String> get enabledExperiments =>
result.session.analysisContext.analysisOptions.enabledExperiments;
@override
bool get includeIdentifiers {
return opType.includeIdentifiers;
}
@override
LibraryElement get libraryElement {
//TODO(danrubel) build the library element rather than all the declarations
CompilationUnit unit = target.unit;
if (unit != null) {
CompilationUnitElement elem = unit.declaredElement;
if (elem != null) {
return elem.library;
}
}
return null;
}
OpType get opType {
if (_opType == null) {
_opType = new 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;
}
/**
* Throw [AbortCompletion] if the completion request has been aborted.
*/
void checkAborted() {
_originalRequest.checkAborted();
}
/**
* Update the completion [target] and [dotTarget] based on the given [unit].
*/
void _updateTargets(CompilationUnit unit) {
_opType = null;
dotTarget = null;
target = new CompletionTarget.forOffset(unit, offset);
AstNode 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) async {
// TODO(brianwilkerson) Determine whether this await is necessary.
await null;
request.checkAborted();
CompletionPerformance performance =
(request as CompletionRequestImpl).performance;
const BUILD_REQUEST_TAG = 'build DartCompletionRequest';
performance.logStartTime(BUILD_REQUEST_TAG);
CompilationUnit unit = request.result.unit;
Source libSource = unit.declaredElement.library.source;
InterfaceType objectType = request.result.typeProvider.objectType;
DartCompletionRequestImpl dartRequest = new DartCompletionRequestImpl._(
request.result,
request.resourceProvider,
objectType,
libSource,
request.source,
request.offset,
unit,
request,
performance);
performance.logElapseTime(BUILD_REQUEST_TAG);
return dartRequest;
}
}