| OLD | NEW |
| (Empty) |
| 1 // Copyright (c) 2014, the Dart project authors. Please see the AUTHORS file | |
| 2 // for details. All rights reserved. Use of this source code is governed by a | |
| 3 // BSD-style license that can be found in the LICENSE file. | |
| 4 | |
| 5 library services.completion.dart.cache; | |
| 6 | |
| 7 import 'dart:async'; | |
| 8 import 'dart:collection'; | |
| 9 | |
| 10 import 'package:analysis_server/src/protocol_server.dart' | |
| 11 hide Element, ElementKind; | |
| 12 import 'package:analysis_server/src/services/completion/completion_manager.dart'
; | |
| 13 import 'package:analysis_server/src/services/completion/dart_completion_manager.
dart'; | |
| 14 import 'package:analysis_server/src/services/completion/suggestion_builder.dart'
; | |
| 15 import 'package:analysis_server/src/services/search/search_engine.dart'; | |
| 16 import 'package:analyzer/dart/element/element.dart'; | |
| 17 import 'package:analyzer/dart/element/type.dart'; | |
| 18 import 'package:analyzer/src/generated/ast.dart'; | |
| 19 import 'package:analyzer/src/generated/engine.dart'; | |
| 20 import 'package:analyzer/src/generated/resolver.dart'; | |
| 21 import 'package:analyzer/src/generated/source.dart'; | |
| 22 import 'package:analyzer/src/task/dart.dart'; | |
| 23 | |
| 24 /** | |
| 25 * The `DartCompletionCache` contains cached information from a prior code | |
| 26 * completion operation. | |
| 27 */ | |
| 28 class DartCompletionCache extends CompletionCache { | |
| 29 /** | |
| 30 * A hash of the import directives | |
| 31 * or `null` if nothing has been cached. | |
| 32 */ | |
| 33 String _importKey; | |
| 34 | |
| 35 /** | |
| 36 * Type suggestions based upon imports, | |
| 37 * or `null` if nothing has been cached. | |
| 38 */ | |
| 39 List<CompletionSuggestion> importedTypeSuggestions; | |
| 40 | |
| 41 /** | |
| 42 * Suggestions for methods and functions that have void return type, | |
| 43 * or `null` if nothing has been cached. | |
| 44 */ | |
| 45 List<CompletionSuggestion> importedVoidReturnSuggestions; | |
| 46 | |
| 47 /** | |
| 48 * Other suggestions based upon imports, | |
| 49 * or `null` if nothing has been cached. | |
| 50 */ | |
| 51 List<CompletionSuggestion> otherImportedSuggestions; | |
| 52 | |
| 53 /** | |
| 54 * Suggestions for constructors | |
| 55 * or `null` if nothing has been cached. | |
| 56 */ | |
| 57 List<CompletionSuggestion> importedConstructorSuggestions; | |
| 58 | |
| 59 /** | |
| 60 * A collection of all imported completions | |
| 61 * or `null` if nothing has been cached. | |
| 62 */ | |
| 63 HashSet<String> _importedCompletions; | |
| 64 | |
| 65 /** | |
| 66 * A map of simple identifier to imported class element | |
| 67 * or `null` if nothing has been cached. | |
| 68 */ | |
| 69 Map<String, ClassElement> importedClassMap; | |
| 70 | |
| 71 /** | |
| 72 * The [ClassElement] for Object. | |
| 73 */ | |
| 74 ClassElement _objectClassElement; | |
| 75 | |
| 76 DartCompletionCache(AnalysisContext context, Source source) | |
| 77 : super(context, source); | |
| 78 | |
| 79 /** | |
| 80 * Return a hash of the import directives for the cached import info | |
| 81 * or `null` if nothing has been cached. | |
| 82 */ | |
| 83 String get importKey => _importKey; | |
| 84 | |
| 85 /** | |
| 86 * Return the [ClassElement] for Object. | |
| 87 */ | |
| 88 ClassElement get objectClassElement { | |
| 89 if (_objectClassElement == null) { | |
| 90 Source coreUri = context.sourceFactory.forUri('dart:core'); | |
| 91 LibraryElement coreLib = context.getResult(coreUri, LIBRARY_ELEMENT8); | |
| 92 _objectClassElement = coreLib.getType('Object'); | |
| 93 } | |
| 94 return _objectClassElement; | |
| 95 } | |
| 96 | |
| 97 /** | |
| 98 * Given a resolved compilation unit, compute suggestions based upon the | |
| 99 * imports and other dart files (e.g. "part" files) in the library containing | |
| 100 * the given compilation unit. The returned future completes when the cache | |
| 101 * is populated. | |
| 102 * | |
| 103 * If [shouldWaitForLowPrioritySuggestions] is `true` then the returned | |
| 104 * future will complete when the cache is fully populated. If `false`, | |
| 105 * the returned future will complete sooner, but the cache will not include | |
| 106 * the lower priority suggestions added as a result of a global search. | |
| 107 * In this case, those lower priority suggestions will be added later | |
| 108 * when the index has been updated and the global search completes. | |
| 109 */ | |
| 110 Future<bool> computeImportInfo(CompilationUnit unit, | |
| 111 SearchEngine searchEngine, bool shouldWaitForLowPrioritySuggestions) { | |
| 112 importedTypeSuggestions = <CompletionSuggestion>[]; | |
| 113 otherImportedSuggestions = <CompletionSuggestion>[]; | |
| 114 importedConstructorSuggestions = <CompletionSuggestion>[]; | |
| 115 importedVoidReturnSuggestions = <CompletionSuggestion>[]; | |
| 116 importedClassMap = new Map<String, ClassElement>(); | |
| 117 _importedCompletions = new HashSet<String>(); | |
| 118 | |
| 119 // Assert that the compilation unit is resolved | |
| 120 // and represents the expected source | |
| 121 assert(unit.element.source == source); | |
| 122 | |
| 123 // Exclude elements from local library | |
| 124 // because they are provided by LocalReferenceContributor | |
| 125 Set<LibraryElement> excludedLibs = new Set<LibraryElement>(); | |
| 126 excludedLibs.add(unit.element.enclosingElement); | |
| 127 | |
| 128 // Determine the compilation unit defining the library containing | |
| 129 // this compilation unit | |
| 130 List<Source> libraries = context.getLibrariesContaining(source); | |
| 131 assert(libraries != null); | |
| 132 Source libSource = libraries.length > 0 ? libraries[0] : null; | |
| 133 Future<CompilationUnit> futureLibUnit = _computeLibUnit(libSource, unit); | |
| 134 | |
| 135 // Include implicitly imported dart:core elements | |
| 136 _addDartCoreSuggestions(); | |
| 137 | |
| 138 // Include explicitly imported and part elements | |
| 139 Future futureImportsCached = futureLibUnit.then((CompilationUnit libUnit) { | |
| 140 _addImportedElemSuggestions(libSource, libUnit, excludedLibs); | |
| 141 // Don't wait for search of lower relevance results to complete. | |
| 142 // Set key indicating results are ready, and lower relevance results | |
| 143 // will be added to the cache when the search completes. | |
| 144 _importKey = _computeImportKey(unit); | |
| 145 return true; | |
| 146 }); | |
| 147 | |
| 148 // Add non-imported elements as low relevance | |
| 149 // after the imported element suggestions have been added | |
| 150 Future<bool> futureAllCached = futureImportsCached.then((_) { | |
| 151 return searchEngine | |
| 152 .searchTopLevelDeclarations('') | |
| 153 .then((List<SearchMatch> matches) { | |
| 154 _addNonImportedElementSuggestions(matches, excludedLibs); | |
| 155 return true; | |
| 156 }); | |
| 157 }); | |
| 158 | |
| 159 return shouldWaitForLowPrioritySuggestions | |
| 160 ? futureAllCached | |
| 161 : futureImportsCached; | |
| 162 } | |
| 163 | |
| 164 /** | |
| 165 * Return `true` if the import information is cached for the given | |
| 166 * compilation unit. | |
| 167 */ | |
| 168 bool isImportInfoCached(CompilationUnit unit) => | |
| 169 _importKey != null && _importKey == _computeImportKey(unit); | |
| 170 | |
| 171 /** | |
| 172 * Add constructor suggestions for the given class. | |
| 173 */ | |
| 174 void _addConstructorSuggestions( | |
| 175 ClassElement classElem, int relevance, Source importForSource) { | |
| 176 String className = classElem.name; | |
| 177 for (ConstructorElement constructor in classElem.constructors) { | |
| 178 if (!constructor.isPrivate) { | |
| 179 CompletionSuggestion suggestion = createSuggestion(constructor, | |
| 180 relevance: relevance, importForSource: importForSource); | |
| 181 if (suggestion != null) { | |
| 182 String name = suggestion.completion; | |
| 183 name = name.length > 0 ? '$className.$name' : className; | |
| 184 suggestion.completion = name; | |
| 185 suggestion.selectionOffset = suggestion.completion.length; | |
| 186 importedConstructorSuggestions.add(suggestion); | |
| 187 } | |
| 188 } | |
| 189 } | |
| 190 } | |
| 191 | |
| 192 /** | |
| 193 * Add suggestions for implicitly imported elements in dart:core. | |
| 194 */ | |
| 195 void _addDartCoreSuggestions() { | |
| 196 Source coreUri = context.sourceFactory.forUri('dart:core'); | |
| 197 LibraryElement coreLib = context.getResult(coreUri, LIBRARY_ELEMENT8); | |
| 198 if (coreLib == null) { | |
| 199 // If the core library has not been analyzed yet, then we cannot add any | |
| 200 // suggestions from it. | |
| 201 return; | |
| 202 } | |
| 203 Namespace coreNamespace = | |
| 204 new NamespaceBuilder().createPublicNamespaceForLibrary(coreLib); | |
| 205 coreNamespace.definedNames.forEach((String name, Element elem) { | |
| 206 if (elem is ClassElement) { | |
| 207 importedClassMap[name] = elem; | |
| 208 } | |
| 209 _addSuggestion(elem, DART_RELEVANCE_DEFAULT); | |
| 210 }); | |
| 211 } | |
| 212 | |
| 213 /** | |
| 214 * Add suggestions for explicitly imported and part elements in the given | |
| 215 * library. Add libraries that should not have their elements suggested | |
| 216 * even as low priority to [excludedLibs]. | |
| 217 */ | |
| 218 void _addImportedElemSuggestions(Source libSource, CompilationUnit libUnit, | |
| 219 Set<LibraryElement> excludedLibs) { | |
| 220 if (libUnit != null) { | |
| 221 libUnit.directives.forEach((Directive directive) { | |
| 222 if (directive is ImportDirective) { | |
| 223 ImportElement importElem = directive.element; | |
| 224 if (importElem != null && importElem.importedLibrary != null) { | |
| 225 if (directive.prefix == null) { | |
| 226 Namespace importNamespace = new NamespaceBuilder() | |
| 227 .createImportNamespaceForDirective(importElem); | |
| 228 // Include top level elements | |
| 229 importNamespace.definedNames.forEach((String name, Element elem) { | |
| 230 if (elem is ClassElement) { | |
| 231 importedClassMap[name] = elem; | |
| 232 } | |
| 233 _addSuggestion(elem, DART_RELEVANCE_DEFAULT); | |
| 234 }); | |
| 235 } else { | |
| 236 // Exclude elements from prefixed imports | |
| 237 // because they are provided by PrefixedElementContributor | |
| 238 // Suggested by LibraryPrefixContributor | |
| 239 // _addLibraryPrefixSuggestion(importElem); | |
| 240 // excludedLibs.add(importElem.importedLibrary); | |
| 241 } | |
| 242 } | |
| 243 } else if (directive is PartDirective) { | |
| 244 // Suggested by LocalLibraryContributor | |
| 245 // CompilationUnitElement partElem = directive.element; | |
| 246 // if (partElem != null && partElem.source != source) { | |
| 247 // partElem.accept(new _NonLocalElementCacheVisitor(this)); | |
| 248 // } | |
| 249 } | |
| 250 }); | |
| 251 if (libSource != source) { | |
| 252 // Suggested by LocalLibraryContributor | |
| 253 // libUnit.element.accept(new _NonLocalElementCacheVisitor(this)); | |
| 254 } | |
| 255 } | |
| 256 } | |
| 257 | |
| 258 /** | |
| 259 * Add suggestions for all top level elements in the context | |
| 260 * excluding those elemnents for which suggestions have already been added. | |
| 261 */ | |
| 262 void _addNonImportedElementSuggestions( | |
| 263 List<SearchMatch> matches, Set<LibraryElement> excludedLibs) { | |
| 264 // Exclude internal Dart SDK libraries | |
| 265 for (var lib in context.sourceFactory.dartSdk.sdkLibraries) { | |
| 266 if (lib.isInternal) { | |
| 267 Source libUri = context.sourceFactory.forUri(lib.shortName); | |
| 268 if (libUri != null) { | |
| 269 LibraryElement libElem = context.getResult(libUri, LIBRARY_ELEMENT8); | |
| 270 if (libElem != null) { | |
| 271 excludedLibs.add(libElem); | |
| 272 } | |
| 273 } | |
| 274 } | |
| 275 } | |
| 276 | |
| 277 AnalysisContext sdkContext = context.sourceFactory.dartSdk.context; | |
| 278 matches.forEach((SearchMatch match) { | |
| 279 if (match.kind == MatchKind.DECLARATION) { | |
| 280 Element element = match.element; | |
| 281 if ((element.context == context || element.context == sdkContext) && | |
| 282 element.isPublic && | |
| 283 !excludedLibs.contains(element.library) && | |
| 284 !_importedCompletions.contains(element.displayName)) { | |
| 285 _addSuggestion(element, DART_RELEVANCE_LOW, importForSource: source); | |
| 286 } | |
| 287 } | |
| 288 }); | |
| 289 } | |
| 290 | |
| 291 /** | |
| 292 * Add a suggestion for the given element. | |
| 293 */ | |
| 294 void _addSuggestion(Element element, int relevance, | |
| 295 {Source importForSource}) { | |
| 296 if (element is ExecutableElement) { | |
| 297 // Do not suggest operators or local functions | |
| 298 if (element.isOperator) { | |
| 299 return; | |
| 300 } | |
| 301 if (element is FunctionElement) { | |
| 302 if (element.enclosingElement is! CompilationUnitElement) { | |
| 303 return; | |
| 304 } | |
| 305 } | |
| 306 } | |
| 307 | |
| 308 CompletionSuggestion suggestion = createSuggestion(element, | |
| 309 relevance: relevance, importForSource: importForSource); | |
| 310 | |
| 311 if (suggestion != null) { | |
| 312 if (element is ExecutableElement) { | |
| 313 DartType returnType = element.returnType; | |
| 314 if (returnType != null && returnType.isVoid) { | |
| 315 importedVoidReturnSuggestions.add(suggestion); | |
| 316 } else { | |
| 317 otherImportedSuggestions.add(suggestion); | |
| 318 } | |
| 319 } else if (element is FunctionTypeAliasElement) { | |
| 320 importedTypeSuggestions.add(suggestion); | |
| 321 } else if (element is ClassElement) { | |
| 322 importedTypeSuggestions.add(suggestion); | |
| 323 _addConstructorSuggestions(element, relevance, importForSource); | |
| 324 } else { | |
| 325 otherImportedSuggestions.add(suggestion); | |
| 326 } | |
| 327 _importedCompletions.add(suggestion.completion); | |
| 328 } | |
| 329 } | |
| 330 | |
| 331 /** | |
| 332 * Compute the hash of the imports for the given compilation unit. | |
| 333 */ | |
| 334 String _computeImportKey(CompilationUnit unit) { | |
| 335 StringBuffer sb = new StringBuffer(); | |
| 336 unit.directives.forEach((Directive directive) { | |
| 337 sb.write(directive.toSource()); | |
| 338 }); | |
| 339 return sb.toString(); | |
| 340 } | |
| 341 | |
| 342 /** | |
| 343 * Compute the library unit for the given library source, | |
| 344 * where the [unit] is the resolved compilation unit associated with [source]. | |
| 345 */ | |
| 346 Future<CompilationUnit> _computeLibUnit( | |
| 347 Source libSource, CompilationUnit unit) { | |
| 348 // If the sources are the same then we already have the library unit | |
| 349 if (libSource == source) { | |
| 350 return new Future.value(unit); | |
| 351 } | |
| 352 // If [source] is a part, then compute the library unit | |
| 353 if (libSource != null) { | |
| 354 return context.computeResolvedCompilationUnitAsync(libSource, libSource); | |
| 355 } | |
| 356 return new Future.value(null); | |
| 357 } | |
| 358 } | |
| OLD | NEW |