| OLD | NEW |
| 1 // Copyright (c) 2014, the Dart project authors. Please see the AUTHORS file | 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 | 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. | 3 // BSD-style license that can be found in the LICENSE file. |
| 4 | 4 |
| 5 library services.completion.dart.cache; | 5 library services.completion.dart.cache; |
| 6 | 6 |
| 7 import 'dart:async'; | 7 import 'dart:async'; |
| 8 import 'dart:collection'; | 8 import 'dart:collection'; |
| 9 | 9 |
| 10 import 'package:analysis_server/src/protocol_server.dart' hide Element, | 10 import 'package:analysis_server/src/protocol_server.dart' hide Element, |
| (...skipping 63 matching lines...) Expand 10 before | Expand all | Expand 10 after Loading... |
| 74 DartCompletionCache(AnalysisContext context, Source source) | 74 DartCompletionCache(AnalysisContext context, Source source) |
| 75 : super(context, source); | 75 : super(context, source); |
| 76 | 76 |
| 77 /** | 77 /** |
| 78 * Return a hash of the import directives for the cached import info | 78 * Return a hash of the import directives for the cached import info |
| 79 * or `null` if nothing has been cached. | 79 * or `null` if nothing has been cached. |
| 80 */ | 80 */ |
| 81 String get importKey => _importKey; | 81 String get importKey => _importKey; |
| 82 | 82 |
| 83 /** | 83 /** |
| 84 * Return the [ClassElement] for Object. |
| 85 */ |
| 86 ClassElement get objectClassElement { |
| 87 if (_objectClassElement == null) { |
| 88 Source coreUri = context.sourceFactory.forUri('dart:core'); |
| 89 LibraryElement coreLib = context.getLibraryElement(coreUri); |
| 90 _objectClassElement = coreLib.getType('Object'); |
| 91 } |
| 92 return _objectClassElement; |
| 93 } |
| 94 |
| 95 /** |
| 84 * Given a resolved compilation unit, compute suggestions based upon the | 96 * Given a resolved compilation unit, compute suggestions based upon the |
| 85 * imports and other dart files (e.g. "part" files) in the library containing | 97 * imports and other dart files (e.g. "part" files) in the library containing |
| 86 * the given compilation unit. The returned future completes when the cache | 98 * the given compilation unit. The returned future completes when the cache |
| 87 * is populated. | 99 * is populated. |
| 88 * | 100 * |
| 89 * If [shouldWaitForLowPrioritySuggestions] is `true` then the returned | 101 * If [shouldWaitForLowPrioritySuggestions] is `true` then the returned |
| 90 * future will complete when the cache is fully populated. If `false`, | 102 * future will complete when the cache is fully populated. If `false`, |
| 91 * the returned future will complete sooner, but the cache will not include | 103 * the returned future will complete sooner, but the cache will not include |
| 92 * the lower priority suggestions added as a result of a global search. | 104 * the lower priority suggestions added as a result of a global search. |
| 93 * In this case, those lower priority suggestions will be added later | 105 * In this case, those lower priority suggestions will be added later |
| (...skipping 13 matching lines...) Expand all Loading... |
| 107 assert(unit.element.source == source); | 119 assert(unit.element.source == source); |
| 108 | 120 |
| 109 // Exclude elements from local library | 121 // Exclude elements from local library |
| 110 // because they are provided by LocalComputer | 122 // because they are provided by LocalComputer |
| 111 Set<LibraryElement> excludedLibs = new Set<LibraryElement>(); | 123 Set<LibraryElement> excludedLibs = new Set<LibraryElement>(); |
| 112 excludedLibs.add(unit.element.enclosingElement); | 124 excludedLibs.add(unit.element.enclosingElement); |
| 113 | 125 |
| 114 // Determine the compilation unit defining the library containing | 126 // Determine the compilation unit defining the library containing |
| 115 // this compilation unit | 127 // this compilation unit |
| 116 List<Source> libraries = context.getLibrariesContaining(source); | 128 List<Source> libraries = context.getLibrariesContaining(source); |
| 117 Source libSource = null; | 129 assert(libraries != null); |
| 118 Future<CompilationUnit> futureLibUnit; | 130 Source libSource = libraries.length > 0 ? libraries[0] : null; |
| 119 if (libraries != null && libraries.length > 0) { | 131 Future<CompilationUnit> futureLibUnit = _computeLibUnit(libSource, unit); |
| 120 libSource = libraries[0]; | |
| 121 if (libSource == source) { | |
| 122 // If the sources are the same then we already have the library unit | |
| 123 futureLibUnit = new Future.value(unit); | |
| 124 } else { | |
| 125 // If the sources are different, then get the library unit | |
| 126 // to traverse the library directives and cache the imported elements | |
| 127 futureLibUnit = | |
| 128 context.computeResolvedCompilationUnitAsync(libSource, libSource); | |
| 129 } | |
| 130 } else { | |
| 131 futureLibUnit = new Future.value(null); | |
| 132 } | |
| 133 | 132 |
| 134 // Include explicitly imported elements | 133 // Include implicitly imported dart:core elements |
| 134 _addDartCoreSuggestions(); |
| 135 |
| 136 // Include explicitly imported and part elements |
| 135 Future futureImportsCached = futureLibUnit.then((CompilationUnit libUnit) { | 137 Future futureImportsCached = futureLibUnit.then((CompilationUnit libUnit) { |
| 136 if (libUnit != null) { | 138 _addImportedElemSuggestions(libSource, libUnit, excludedLibs); |
| 137 libUnit.directives.forEach((Directive directive) { | |
| 138 if (directive is ImportDirective) { | |
| 139 ImportElement importElem = directive.element; | |
| 140 if (importElem != null && importElem.importedLibrary != null) { | |
| 141 if (directive.prefix == null) { | |
| 142 Namespace importNamespace = | |
| 143 new NamespaceBuilder().createImportNamespaceForDirective(imp
ortElem); | |
| 144 // Include top level elements | |
| 145 importNamespace.definedNames.forEach( | |
| 146 (String name, Element elem) { | |
| 147 if (elem is ClassElement) { | |
| 148 importedClassMap[name] = elem; | |
| 149 } | |
| 150 addSuggestion(elem, CompletionRelevance.DEFAULT); | |
| 151 }); | |
| 152 } else { | |
| 153 // Exclude elements from prefixed imports | |
| 154 // because they are provided by InvocationComputer | |
| 155 excludedLibs.add(importElem.importedLibrary); | |
| 156 _addLibraryPrefixSuggestion(importElem); | |
| 157 } | |
| 158 } | |
| 159 } else if (directive is PartDirective) { | |
| 160 CompilationUnitElement partElem = directive.element; | |
| 161 if (partElem != null && partElem.source != source) { | |
| 162 partElem.accept(new _NonLocalElementCacheVisitor(this)); | |
| 163 } | |
| 164 } | |
| 165 }); | |
| 166 if (libSource != source) { | |
| 167 libUnit.element.accept(new _NonLocalElementCacheVisitor(this)); | |
| 168 } | |
| 169 } | |
| 170 // Don't wait for search of lower relevance results to complete. | 139 // Don't wait for search of lower relevance results to complete. |
| 171 // Set key indicating results are ready, and lower relevance results | 140 // Set key indicating results are ready, and lower relevance results |
| 172 // will be added to the cache when the search completes. | 141 // will be added to the cache when the search completes. |
| 173 _importKey = _computeImportKey(unit); | 142 _importKey = _computeImportKey(unit); |
| 174 return true; | 143 return true; |
| 175 }); | 144 }); |
| 176 | 145 |
| 177 // Include implicitly imported dart:core elements | |
| 178 Source coreUri = context.sourceFactory.forUri('dart:core'); | |
| 179 LibraryElement coreLib = context.getLibraryElement(coreUri); | |
| 180 Namespace coreNamespace = | |
| 181 new NamespaceBuilder().createPublicNamespaceForLibrary(coreLib); | |
| 182 coreNamespace.definedNames.forEach((String name, Element elem) { | |
| 183 if (elem is ClassElement) { | |
| 184 importedClassMap[name] = elem; | |
| 185 } | |
| 186 addSuggestion(elem, CompletionRelevance.DEFAULT); | |
| 187 }); | |
| 188 _objectClassElement = importedClassMap['Object']; | |
| 189 | |
| 190 // Add non-imported elements as low relevance | 146 // Add non-imported elements as low relevance |
| 191 // after the imported element suggestions have been added | 147 // after the imported element suggestions have been added |
| 192 Future<bool> futureAllCached = futureImportsCached.then((_) { | 148 Future<bool> futureAllCached = futureImportsCached.then((_) { |
| 193 return searchEngine.searchTopLevelDeclarations( | 149 return searchEngine.searchTopLevelDeclarations( |
| 194 '').then((List<SearchMatch> matches) { | 150 '').then((List<SearchMatch> matches) { |
| 195 matches.forEach((SearchMatch match) { | 151 _addNonImportedElementSuggestions(matches, excludedLibs); |
| 196 if (match.kind == MatchKind.DECLARATION) { | |
| 197 Element element = match.element; | |
| 198 if (element.isPublic && | |
| 199 !excludedLibs.contains(element.library) && | |
| 200 !_importedCompletions.contains(element.displayName)) { | |
| 201 addSuggestion(element, CompletionRelevance.LOW); | |
| 202 } | |
| 203 } | |
| 204 }); | |
| 205 return true; | 152 return true; |
| 206 }); | 153 }); |
| 207 }); | 154 }); |
| 208 | 155 |
| 209 return shouldWaitForLowPrioritySuggestions ? | 156 return shouldWaitForLowPrioritySuggestions ? |
| 210 futureAllCached : | 157 futureAllCached : |
| 211 futureImportsCached; | 158 futureImportsCached; |
| 212 } | 159 } |
| 213 | 160 |
| 214 /** | 161 /** |
| 215 * Return the [ClassElement] for Object. | |
| 216 */ | |
| 217 ClassElement get objectClassElement { | |
| 218 if (_objectClassElement == null) { | |
| 219 Source coreUri = context.sourceFactory.forUri('dart:core'); | |
| 220 LibraryElement coreLib = context.getLibraryElement(coreUri); | |
| 221 Namespace coreNamespace = | |
| 222 new NamespaceBuilder().createPublicNamespaceForLibrary(coreLib); | |
| 223 _objectClassElement = coreNamespace.definedNames['Object']; | |
| 224 } | |
| 225 return _objectClassElement; | |
| 226 } | |
| 227 | |
| 228 /** | |
| 229 * Return `true` if the import information is cached for the given | 162 * Return `true` if the import information is cached for the given |
| 230 * compilation unit. | 163 * compilation unit. |
| 231 */ | 164 */ |
| 232 bool isImportInfoCached(CompilationUnit unit) => | 165 bool isImportInfoCached(CompilationUnit unit) => |
| 233 _importKey != null && _importKey == _computeImportKey(unit); | 166 _importKey != null && _importKey == _computeImportKey(unit); |
| 234 | 167 |
| 168 /** |
| 169 * Add suggestions for implicitly imported elements in dart:core. |
| 170 */ |
| 171 void _addDartCoreSuggestions() { |
| 172 Source coreUri = context.sourceFactory.forUri('dart:core'); |
| 173 LibraryElement coreLib = context.getLibraryElement(coreUri); |
| 174 Namespace coreNamespace = |
| 175 new NamespaceBuilder().createPublicNamespaceForLibrary(coreLib); |
| 176 coreNamespace.definedNames.forEach((String name, Element elem) { |
| 177 if (elem is ClassElement) { |
| 178 importedClassMap[name] = elem; |
| 179 } |
| 180 _addSuggestion(elem, CompletionRelevance.DEFAULT); |
| 181 }); |
| 182 } |
| 183 |
| 184 /** |
| 185 * Add suggestions for explicitly imported and part elements in the given |
| 186 * library. Add libraries that should not have their elements suggested |
| 187 * even as low priority to [excludedLibs]. |
| 188 */ |
| 189 void _addImportedElemSuggestions(Source libSource, CompilationUnit libUnit, |
| 190 Set<LibraryElement> excludedLibs) { |
| 191 if (libUnit != null) { |
| 192 libUnit.directives.forEach((Directive directive) { |
| 193 if (directive is ImportDirective) { |
| 194 ImportElement importElem = directive.element; |
| 195 if (importElem != null && importElem.importedLibrary != null) { |
| 196 if (directive.prefix == null) { |
| 197 Namespace importNamespace = |
| 198 new NamespaceBuilder().createImportNamespaceForDirective(impor
tElem); |
| 199 // Include top level elements |
| 200 importNamespace.definedNames.forEach((String name, Element elem) { |
| 201 if (elem is ClassElement) { |
| 202 importedClassMap[name] = elem; |
| 203 } |
| 204 _addSuggestion(elem, CompletionRelevance.DEFAULT); |
| 205 }); |
| 206 } else { |
| 207 // Exclude elements from prefixed imports |
| 208 // because they are provided by InvocationComputer |
| 209 _addLibraryPrefixSuggestion(importElem); |
| 210 excludedLibs.add(importElem.importedLibrary); |
| 211 } |
| 212 } |
| 213 } else if (directive is PartDirective) { |
| 214 CompilationUnitElement partElem = directive.element; |
| 215 if (partElem != null && partElem.source != source) { |
| 216 partElem.accept(new _NonLocalElementCacheVisitor(this)); |
| 217 } |
| 218 } |
| 219 }); |
| 220 if (libSource != source) { |
| 221 libUnit.element.accept(new _NonLocalElementCacheVisitor(this)); |
| 222 } |
| 223 } |
| 224 } |
| 225 |
| 235 void _addLibraryPrefixSuggestion(ImportElement importElem) { | 226 void _addLibraryPrefixSuggestion(ImportElement importElem) { |
| 236 CompletionSuggestion suggestion = null; | 227 CompletionSuggestion suggestion = null; |
| 237 String completion = importElem.prefix.displayName; | 228 String completion = importElem.prefix.displayName; |
| 238 if (completion != null && completion.length > 0) { | 229 if (completion != null && completion.length > 0) { |
| 239 suggestion = new CompletionSuggestion( | 230 suggestion = new CompletionSuggestion( |
| 240 CompletionSuggestionKind.INVOCATION, | 231 CompletionSuggestionKind.INVOCATION, |
| 241 CompletionRelevance.DEFAULT, | 232 CompletionRelevance.DEFAULT, |
| 242 completion, | 233 completion, |
| 243 completion.length, | 234 completion.length, |
| 244 0, | 235 0, |
| 245 importElem.isDeprecated, | 236 importElem.isDeprecated, |
| 246 false); | 237 false); |
| 247 LibraryElement lib = importElem.importedLibrary; | 238 LibraryElement lib = importElem.importedLibrary; |
| 248 if (lib != null) { | 239 if (lib != null) { |
| 249 suggestion.element = newElement_fromEngine(lib); | 240 suggestion.element = newElement_fromEngine(lib); |
| 250 } | 241 } |
| 251 libraryPrefixSuggestions.add(suggestion); | 242 libraryPrefixSuggestions.add(suggestion); |
| 252 _importedCompletions.add(suggestion.completion); | 243 _importedCompletions.add(suggestion.completion); |
| 253 } | 244 } |
| 254 } | 245 } |
| 255 | 246 |
| 256 void addSuggestion(Element element, CompletionRelevance relevance) { | 247 /** |
| 248 * Add suggestions for all top level elements in the context |
| 249 * excluding those elemnents for which suggestions have already been added. |
| 250 */ |
| 251 void _addNonImportedElementSuggestions(List<SearchMatch> matches, |
| 252 Set<LibraryElement> excludedLibs) { |
| 253 matches.forEach((SearchMatch match) { |
| 254 if (match.kind == MatchKind.DECLARATION) { |
| 255 Element element = match.element; |
| 256 if (element.isPublic && |
| 257 !excludedLibs.contains(element.library) && |
| 258 !_importedCompletions.contains(element.displayName)) { |
| 259 _addSuggestion(element, CompletionRelevance.LOW); |
| 260 } |
| 261 } |
| 262 }); |
| 263 } |
| 264 |
| 265 /** |
| 266 * Add a suggestion for the given element. |
| 267 */ |
| 268 void _addSuggestion(Element element, CompletionRelevance relevance) { |
| 257 | 269 |
| 258 if (element is ExecutableElement) { | 270 if (element is ExecutableElement) { |
| 259 if (element.isOperator) { | 271 if (element.isOperator) { |
| 260 return; | 272 return; |
| 261 } | 273 } |
| 262 } | 274 } |
| 263 | 275 |
| 264 CompletionSuggestion suggestion = | 276 CompletionSuggestion suggestion = |
| 265 createElementSuggestion(element, relevance: relevance); | 277 createElementSuggestion(element, relevance: relevance); |
| 266 | 278 |
| (...skipping 15 matching lines...) Expand all Loading... |
| 282 /** | 294 /** |
| 283 * Compute the hash of the imports for the given compilation unit. | 295 * Compute the hash of the imports for the given compilation unit. |
| 284 */ | 296 */ |
| 285 String _computeImportKey(CompilationUnit unit) { | 297 String _computeImportKey(CompilationUnit unit) { |
| 286 StringBuffer sb = new StringBuffer(); | 298 StringBuffer sb = new StringBuffer(); |
| 287 unit.directives.forEach((Directive directive) { | 299 unit.directives.forEach((Directive directive) { |
| 288 sb.write(directive.toSource()); | 300 sb.write(directive.toSource()); |
| 289 }); | 301 }); |
| 290 return sb.toString(); | 302 return sb.toString(); |
| 291 } | 303 } |
| 304 |
| 305 /** |
| 306 * Compute the library unit for the given library source, |
| 307 * where the [unit] is the resolved compilation unit associated with [source]. |
| 308 */ |
| 309 Future<CompilationUnit> _computeLibUnit(Source libSource, |
| 310 CompilationUnit unit) { |
| 311 // If the sources are the same then we already have the library unit |
| 312 if (libSource == source) { |
| 313 return new Future.value(unit); |
| 314 } |
| 315 // If [source] is a part, then compute the library unit |
| 316 if (libSource != null) { |
| 317 return context.computeResolvedCompilationUnitAsync(libSource, libSource); |
| 318 } |
| 319 return new Future.value(null); |
| 320 } |
| 292 } | 321 } |
| 293 | 322 |
| 294 /** | 323 /** |
| 295 * A visitor for building suggestions based upon the elements defined by | 324 * A visitor for building suggestions based upon the elements defined by |
| 296 * a source file contained in the same library but not the same as | 325 * a source file contained in the same library but not the same as |
| 297 * the source in which the completions are being requested. | 326 * the source in which the completions are being requested. |
| 298 */ | 327 */ |
| 299 class _NonLocalElementCacheVisitor extends GeneralizingElementVisitor { | 328 class _NonLocalElementCacheVisitor extends GeneralizingElementVisitor { |
| 300 final DartCompletionCache cache; | 329 final DartCompletionCache cache; |
| 301 | 330 |
| 302 _NonLocalElementCacheVisitor(this.cache); | 331 _NonLocalElementCacheVisitor(this.cache); |
| 303 | 332 |
| 304 @override | 333 @override |
| 305 void visitClassElement(ClassElement element) { | 334 void visitClassElement(ClassElement element) { |
| 306 cache.addSuggestion(element, CompletionRelevance.DEFAULT); | 335 cache._addSuggestion(element, CompletionRelevance.DEFAULT); |
| 307 } | 336 } |
| 308 | 337 |
| 309 @override | 338 @override |
| 310 void visitCompilationUnitElement(CompilationUnitElement element) { | 339 void visitCompilationUnitElement(CompilationUnitElement element) { |
| 311 element.visitChildren(this); | 340 element.visitChildren(this); |
| 312 } | 341 } |
| 313 | 342 |
| 314 @override | 343 @override |
| 315 void visitElement(Element element) { | 344 void visitElement(Element element) { |
| 316 // ignored | 345 // ignored |
| 317 } | 346 } |
| 318 | 347 |
| 319 @override | 348 @override |
| 320 void visitFunctionElement(FunctionElement element) { | 349 void visitFunctionElement(FunctionElement element) { |
| 321 cache.addSuggestion(element, CompletionRelevance.DEFAULT); | 350 cache._addSuggestion(element, CompletionRelevance.DEFAULT); |
| 322 } | 351 } |
| 323 | 352 |
| 324 @override | 353 @override |
| 325 void visitFunctionTypeAliasElement(FunctionTypeAliasElement element) { | 354 void visitFunctionTypeAliasElement(FunctionTypeAliasElement element) { |
| 326 cache.addSuggestion(element, CompletionRelevance.DEFAULT); | 355 cache._addSuggestion(element, CompletionRelevance.DEFAULT); |
| 327 } | 356 } |
| 328 | 357 |
| 329 @override | 358 @override |
| 330 void visitTopLevelVariableElement(TopLevelVariableElement element) { | 359 void visitTopLevelVariableElement(TopLevelVariableElement element) { |
| 331 cache.addSuggestion(element, CompletionRelevance.DEFAULT); | 360 cache._addSuggestion(element, CompletionRelevance.DEFAULT); |
| 332 } | 361 } |
| 333 } | 362 } |
| OLD | NEW |