blob: f1e7198937ac0e13e794be23acb3b9497e1f46b8 [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.dart.cache;
import 'dart:async';
import 'dart:collection';
import 'package:analysis_server/src/protocol_server.dart' hide Element,
ElementKind;
import 'package:analysis_server/src/services/completion/completion_manager.dart';
import 'package:analysis_server/src/services/completion/suggestion_builder.dart';
import 'package:analysis_server/src/services/search/search_engine.dart';
import 'package:analyzer/src/generated/ast.dart';
import 'package:analyzer/src/generated/element.dart';
import 'package:analyzer/src/generated/engine.dart';
import 'package:analyzer/src/generated/resolver.dart';
import 'package:analyzer/src/generated/source.dart';
/**
* The `DartCompletionCache` contains cached information from a prior code
* completion operation.
*/
class DartCompletionCache extends CompletionCache {
/**
* A hash of the import directives
* or `null` if nothing has been cached.
*/
String _importKey;
/**
* Library prefix suggestions based upon imports,
* or `null` if nothing has been cached.
*/
List<CompletionSuggestion> libraryPrefixSuggestions;
/**
* Type suggestions based upon imports,
* or `null` if nothing has been cached.
*/
List<CompletionSuggestion> importedTypeSuggestions;
/**
* Suggestions for methods and functions that have void return type,
* or `null` if nothing has been cached.
*/
List<CompletionSuggestion> importedVoidReturnSuggestions;
/**
* Other suggestions based upon imports,
* or `null` if nothing has been cached.
*/
List<CompletionSuggestion> otherImportedSuggestions;
/**
* A collection of all imported completions
* or `null` if nothing has been cached.
*/
HashSet<String> _importedCompletions;
/**
* A map of simple identifier to imported class element
* or `null` if nothing has been cached.
*/
Map<String, ClassElement> importedClassMap;
/**
* The [ClassElement] for Object.
*/
ClassElement _objectClassElement;
DartCompletionCache(AnalysisContext context, Source source)
: super(context, source);
/**
* Return a hash of the import directives for the cached import info
* or `null` if nothing has been cached.
*/
String get importKey => _importKey;
/**
* Compute suggestions based upon the imports in the given compilation unit.
* On return, the cache will be populated except for lower priority
* suggestions added as a result of a global search. Callers may wait
* on the returned future if they want to ensure those lower priority
* suggestions are part of the cached suggestions.
*/
Future<bool> computeImportInfo(CompilationUnit unit,
SearchEngine searchEngine) {
importedTypeSuggestions = <CompletionSuggestion>[];
libraryPrefixSuggestions = <CompletionSuggestion>[];
otherImportedSuggestions = <CompletionSuggestion>[];
importedVoidReturnSuggestions = <CompletionSuggestion>[];
importedClassMap = new Map<String, ClassElement>();
_importedCompletions = new HashSet<String>();
// Exclude elements from local library
// because they are provided by LocalComputer
Set<LibraryElement> excludedLibs = new Set<LibraryElement>();
excludedLibs.add(unit.element.enclosingElement);
// Include explicitly imported elements
unit.directives.forEach((Directive directive) {
if (directive is ImportDirective) {
ImportElement importElem = directive.element;
if (importElem != null && importElem.importedLibrary != null) {
if (directive.prefix == null) {
Namespace importNamespace =
new NamespaceBuilder().createImportNamespaceForDirective(importElem);
// Include top level elements
importNamespace.definedNames.forEach((String name, Element elem) {
if (elem is ClassElement) {
importedClassMap[name] = elem;
}
addSuggestion(elem, CompletionRelevance.DEFAULT);
});
} else {
// Exclude elements from prefixed imports
// because they are provided by InvocationComputer
excludedLibs.add(importElem.importedLibrary);
_addLibraryPrefixSuggestion(importElem);
}
}
}
});
// Include implicitly imported dart:core elements
Source coreUri = context.sourceFactory.forUri('dart:core');
LibraryElement coreLib = context.getLibraryElement(coreUri);
Namespace coreNamespace =
new NamespaceBuilder().createPublicNamespaceForLibrary(coreLib);
coreNamespace.definedNames.forEach((String name, Element elem) {
if (elem is ClassElement) {
importedClassMap[name] = elem;
}
addSuggestion(elem, CompletionRelevance.DEFAULT);
});
_objectClassElement = importedClassMap['Object'];
/*
* Don't wait for search of lower relevance results to complete.
* Set key indicating results are ready, and lower relevance results
* will be added to the cache when the search completes.
*/
_importKey = _computeImportKey(unit);
// Add non-imported elements as low relevance
Future<List<SearchMatch>> future =
searchEngine.searchTopLevelDeclarations('');
return future.then((List<SearchMatch> matches) {
matches.forEach((SearchMatch match) {
if (match.kind == MatchKind.DECLARATION) {
Element element = match.element;
if (element.isPublic &&
!excludedLibs.contains(element.library) &&
!_importedCompletions.contains(element.displayName)) {
addSuggestion(element, CompletionRelevance.LOW);
}
}
});
return true;
});
}
/**
* Return the [ClassElement] for Object.
*/
ClassElement get objectClassElement {
if (_objectClassElement == null) {
Source coreUri = context.sourceFactory.forUri('dart:core');
LibraryElement coreLib = context.getLibraryElement(coreUri);
Namespace coreNamespace =
new NamespaceBuilder().createPublicNamespaceForLibrary(coreLib);
_objectClassElement = coreNamespace.definedNames['Object'];
}
return _objectClassElement;
}
/**
* Return `true` if the import information is cached for the given
* compilation unit.
*/
bool isImportInfoCached(CompilationUnit unit) =>
_importKey != null && _importKey == _computeImportKey(unit);
void _addLibraryPrefixSuggestion(ImportElement importElem) {
CompletionSuggestion suggestion = null;
String completion = importElem.prefix.displayName;
if (completion != null && completion.length > 0) {
suggestion = new CompletionSuggestion(
CompletionSuggestionKind.INVOCATION,
CompletionRelevance.DEFAULT,
completion,
completion.length,
0,
importElem.isDeprecated,
false);
LibraryElement lib = importElem.importedLibrary;
if (lib != null) {
suggestion.element = newElement_fromEngine(lib);
}
libraryPrefixSuggestions.add(suggestion);
_importedCompletions.add(suggestion.completion);
}
}
void addSuggestion(Element element, CompletionRelevance relevance) {
if (element is ExecutableElement) {
if (element.isOperator) {
return;
}
}
CompletionSuggestion suggestion =
createElementSuggestion(element, relevance: relevance);
if (element is ExecutableElement) {
DartType returnType = element.returnType;
if (returnType != null && returnType.isVoid) {
importedVoidReturnSuggestions.add(suggestion);
} else {
otherImportedSuggestions.add(suggestion);
}
} else if (element is ClassElement) {
importedTypeSuggestions.add(suggestion);
} else {
otherImportedSuggestions.add(suggestion);
}
_importedCompletions.add(suggestion.completion);
}
/**
* Compute the hash of the imports for the given compilation unit.
*/
String _computeImportKey(CompilationUnit unit) {
StringBuffer sb = new StringBuffer();
unit.directives.forEach((Directive directive) {
if (directive is ImportDirective) {
sb.write(directive.toSource());
}
});
return sb.toString();
}
}