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.suggestion.builder; | 5 library services.completion.suggestion.builder; |
6 | 6 |
7 import 'dart:async'; | 7 import 'dart:async'; |
8 import 'dart:collection'; | |
9 | 8 |
10 import 'package:analysis_server/src/protocol_server.dart' | 9 import 'package:analysis_server/src/protocol_server.dart' |
11 hide Element, ElementKind; | 10 hide Element, ElementKind; |
12 import 'package:analysis_server/src/services/completion/dart/suggestion_builder.
dart' | 11 import 'package:analysis_server/src/services/completion/dart/suggestion_builder.
dart' |
13 show createSuggestion; | 12 show createSuggestion; |
14 import 'package:analysis_server/src/services/completion/dart_completion_manager.
dart'; | 13 import 'package:analysis_server/src/services/completion/dart_completion_manager.
dart'; |
15 import 'package:analyzer/src/generated/ast.dart'; | 14 import 'package:analyzer/src/generated/ast.dart'; |
16 import 'package:analyzer/src/generated/element.dart'; | 15 import 'package:analyzer/src/generated/element.dart'; |
17 import 'package:analyzer/src/generated/engine.dart' as engine; | |
18 | 16 |
19 export 'package:analysis_server/src/services/completion/dart/suggestion_builder.
dart' | 17 export 'package:analysis_server/src/services/completion/dart/suggestion_builder.
dart' |
20 show createSuggestion; | 18 show createSuggestion; |
21 | 19 |
22 const String DYNAMIC = 'dynamic'; | 20 const String DYNAMIC = 'dynamic'; |
23 | 21 |
24 /** | 22 /** |
25 * Call the given function with each non-null non-empty inherited type name | 23 * Call the given function with each non-null non-empty inherited type name |
26 * that is defined in the given class. | 24 * that is defined in the given class. |
27 */ | 25 */ |
(...skipping 139 matching lines...) Expand 10 before | Expand all | Expand 10 after Loading... |
167 if (parent is ClassElement && parent.isEnum) { | 165 if (parent is ClassElement && parent.isEnum) { |
168 if (element.name == 'values') { | 166 if (element.name == 'values') { |
169 return true; | 167 return true; |
170 } | 168 } |
171 } | 169 } |
172 return false; | 170 return false; |
173 } | 171 } |
174 } | 172 } |
175 | 173 |
176 /** | 174 /** |
177 * This class provides suggestions based upon the visible instance members in | |
178 * an interface type. Clients should call | |
179 * [InterfaceTypeSuggestionBuilder.suggestionsFor]. | |
180 */ | |
181 class InterfaceTypeSuggestionBuilder { | |
182 /** | |
183 * Enumerated value indicating that we have not generated any completions for | |
184 * a given identifier yet. | |
185 */ | |
186 static const int _COMPLETION_TYPE_NONE = 0; | |
187 | |
188 /** | |
189 * Enumerated value indicating that we have generated a completion for a | |
190 * getter. | |
191 */ | |
192 static const int _COMPLETION_TYPE_GETTER = 1; | |
193 | |
194 /** | |
195 * Enumerated value indicating that we have generated a completion for a | |
196 * setter. | |
197 */ | |
198 static const int _COMPLETION_TYPE_SETTER = 2; | |
199 | |
200 /** | |
201 * Enumerated value indicating that we have generated a completion for a | |
202 * field, a method, or a getter/setter pair. | |
203 */ | |
204 static const int _COMPLETION_TYPE_FIELD_OR_METHOD_OR_GETSET = 3; | |
205 | |
206 final DartCompletionRequest request; | |
207 | |
208 /** | |
209 * Map indicating, for each possible completion identifier, whether we have | |
210 * already generated completions for a getter, setter, or both. The "both" | |
211 * case also handles the case where have generated a completion for a method | |
212 * or a field. | |
213 * | |
214 * Note: the enumerated values stored in this map are intended to be bitwise | |
215 * compared. | |
216 */ | |
217 Map<String, int> _completionTypesGenerated = new HashMap<String, int>(); | |
218 | |
219 InterfaceTypeSuggestionBuilder(this.request); | |
220 | |
221 CompletionSuggestionKind get kind => CompletionSuggestionKind.INVOCATION; | |
222 | |
223 /** | |
224 * Add a suggestion based upon the given element, provided that it is not | |
225 * shadowed by a previously added suggestion. | |
226 */ | |
227 void addSuggestion(Element element, {int relevance: DART_RELEVANCE_DEFAULT}) { | |
228 if (element.isPrivate) { | |
229 LibraryElement elementLibrary = element.library; | |
230 LibraryElement unitLibrary = request.unit.element.library; | |
231 if (elementLibrary != unitLibrary) { | |
232 return; | |
233 } | |
234 } | |
235 String identifier = element.displayName; | |
236 int alreadyGenerated = _completionTypesGenerated.putIfAbsent( | |
237 identifier, () => _COMPLETION_TYPE_NONE); | |
238 if (element is MethodElement) { | |
239 // Anything shadows a method. | |
240 if (alreadyGenerated != _COMPLETION_TYPE_NONE) { | |
241 return; | |
242 } | |
243 _completionTypesGenerated[identifier] = | |
244 _COMPLETION_TYPE_FIELD_OR_METHOD_OR_GETSET; | |
245 } else if (element is PropertyAccessorElement) { | |
246 if (element.isGetter) { | |
247 // Getters, fields, and methods shadow a getter. | |
248 if ((alreadyGenerated & _COMPLETION_TYPE_GETTER) != 0) { | |
249 return; | |
250 } | |
251 _completionTypesGenerated[identifier] |= _COMPLETION_TYPE_GETTER; | |
252 } else { | |
253 // Setters, fields, and methods shadow a setter. | |
254 if ((alreadyGenerated & _COMPLETION_TYPE_SETTER) != 0) { | |
255 return; | |
256 } | |
257 _completionTypesGenerated[identifier] |= _COMPLETION_TYPE_SETTER; | |
258 } | |
259 } else if (element is FieldElement) { | |
260 // Fields and methods shadow a field. A getter/setter pair shadows a | |
261 // field, but a getter or setter by itself doesn't. | |
262 if (alreadyGenerated == _COMPLETION_TYPE_FIELD_OR_METHOD_OR_GETSET) { | |
263 return; | |
264 } | |
265 _completionTypesGenerated[identifier] = | |
266 _COMPLETION_TYPE_FIELD_OR_METHOD_OR_GETSET; | |
267 } else { | |
268 // Unexpected element type; skip it. | |
269 assert(false); | |
270 return; | |
271 } | |
272 CompletionSuggestion suggestion = | |
273 createSuggestion(element, kind: kind, relevance: relevance); | |
274 if (suggestion != null) { | |
275 request.addSuggestion(suggestion); | |
276 } | |
277 } | |
278 | |
279 void _buildSuggestions(InterfaceType type, LibraryElement library, | |
280 bool isSuper, String containingMethodName) { | |
281 if (isSuper) { | |
282 // Suggest members from superclass if the target is "super" | |
283 type = type.superclass; | |
284 if (type == null) { | |
285 return; | |
286 } | |
287 } | |
288 // Visit all of the types in the class hierarchy, collecting possible | |
289 // completions. If multiple elements are found that complete to the same | |
290 // identifier, addSuggestion will discard all but the first (with a few | |
291 // exceptions to handle getter/setter pairs). | |
292 List<InterfaceType> types = _getTypeOrdering(type); | |
293 for (InterfaceType targetType in types) { | |
294 for (MethodElement method in targetType.methods) { | |
295 // Exclude static methods when completion on an instance | |
296 if (!method.isStatic) { | |
297 addSuggestion(method, | |
298 relevance: method.name == containingMethodName | |
299 ? DART_RELEVANCE_HIGH | |
300 : DART_RELEVANCE_DEFAULT); | |
301 } | |
302 } | |
303 for (PropertyAccessorElement propertyAccessor in targetType.accessors) { | |
304 if (!propertyAccessor.isStatic) { | |
305 if (propertyAccessor.isSynthetic) { | |
306 // Avoid visiting a field twice | |
307 if (propertyAccessor.isGetter) { | |
308 addSuggestion(propertyAccessor.variable); | |
309 } | |
310 } else { | |
311 addSuggestion(propertyAccessor); | |
312 } | |
313 } | |
314 } | |
315 } | |
316 } | |
317 | |
318 /** | |
319 * Get a list of [InterfaceType]s that should be searched to find the | |
320 * possible completions for an object having type [type]. | |
321 */ | |
322 List<InterfaceType> _getTypeOrdering(InterfaceType type) { | |
323 // Candidate completions can come from [type] as well as any types above it | |
324 // in the class hierarchy (including mixins, superclasses, and interfaces). | |
325 // If a given completion identifier shows up in multiple types, we should | |
326 // use the element that is nearest in the superclass chain, so we will | |
327 // visit [type] first, then its mixins, then its superclass, then its | |
328 // superclass's mixins, etc., and only afterwards visit interfaces. | |
329 // | |
330 // We short-circuit loops in the class hierarchy by keeping track of the | |
331 // classes seen (not the interfaces) so that we won't be fooled by nonsense | |
332 // like "class C<T> extends C<List<T>> {}" | |
333 List<InterfaceType> result = <InterfaceType>[]; | |
334 Set<ClassElement> classesSeen = new HashSet<ClassElement>(); | |
335 List<InterfaceType> typesToVisit = <InterfaceType>[type]; | |
336 while (typesToVisit.isNotEmpty) { | |
337 InterfaceType nextType = typesToVisit.removeLast(); | |
338 if (!classesSeen.add(nextType.element)) { | |
339 // Class had already been seen, so ignore this type. | |
340 continue; | |
341 } | |
342 result.add(nextType); | |
343 // typesToVisit is a stack, so push on the interfaces first, then the | |
344 // superclass, then the mixins. This will ensure that they are visited | |
345 // in the reverse order. | |
346 typesToVisit.addAll(nextType.interfaces); | |
347 if (nextType.superclass != null) { | |
348 typesToVisit.add(nextType.superclass); | |
349 } | |
350 typesToVisit.addAll(nextType.mixins); | |
351 } | |
352 return result; | |
353 } | |
354 | |
355 /** | |
356 * Add suggestions for the visible members in the given interface | |
357 */ | |
358 static void suggestionsFor(DartCompletionRequest request, DartType type, | |
359 {bool isSuper: false, String containingMethodName: null}) { | |
360 CompilationUnit compilationUnit = | |
361 request.target.containingNode.getAncestor((n) => n is CompilationUnit); | |
362 CompilationUnitElement unitElem = compilationUnit.element; | |
363 if (unitElem == null) { | |
364 engine.AnalysisEngine.instance.logger | |
365 .logInformation('Completion expected resolved AST'); | |
366 return; | |
367 } | |
368 LibraryElement library = unitElem.library; | |
369 if (type is DynamicTypeImpl) { | |
370 type = request.cache.objectClassElement.type; | |
371 } | |
372 if (type is InterfaceType) { | |
373 new InterfaceTypeSuggestionBuilder(request) | |
374 ._buildSuggestions(type, library, isSuper, containingMethodName); | |
375 } | |
376 } | |
377 } | |
378 | |
379 /** | |
380 * This class visits elements in a library and provides suggestions based upon | |
381 * the visible members in that library. Clients should call | |
382 * [LibraryElementSuggestionBuilder.suggestionsFor]. | |
383 */ | |
384 class LibraryElementSuggestionBuilder extends GeneralizingElementVisitor | |
385 with ElementSuggestionBuilder { | |
386 final DartCompletionRequest request; | |
387 final CompletionSuggestionKind kind; | |
388 final bool typesOnly; | |
389 final bool instCreation; | |
390 | |
391 LibraryElementSuggestionBuilder( | |
392 this.request, this.kind, this.typesOnly, this.instCreation); | |
393 | |
394 @override | |
395 visitClassElement(ClassElement element) { | |
396 if (instCreation) { | |
397 element.visitChildren(this); | |
398 } else { | |
399 addSuggestion(element); | |
400 } | |
401 } | |
402 | |
403 @override | |
404 visitCompilationUnitElement(CompilationUnitElement element) { | |
405 element.visitChildren(this); | |
406 LibraryElement containingLibrary = element.library; | |
407 if (containingLibrary != null) { | |
408 for (var lib in containingLibrary.exportedLibraries) { | |
409 lib.visitChildren(this); | |
410 } | |
411 } | |
412 } | |
413 | |
414 @override | |
415 visitConstructorElement(ConstructorElement element) { | |
416 if (instCreation) { | |
417 ClassElement classElem = element.enclosingElement; | |
418 if (classElem != null) { | |
419 String prefix = classElem.name; | |
420 if (prefix != null && prefix.length > 0) { | |
421 addSuggestion(element, prefix: prefix); | |
422 } | |
423 } | |
424 } | |
425 } | |
426 | |
427 @override | |
428 visitElement(Element element) { | |
429 // ignored | |
430 } | |
431 | |
432 @override | |
433 visitFunctionElement(FunctionElement element) { | |
434 if (!typesOnly) { | |
435 addSuggestion(element); | |
436 } | |
437 } | |
438 | |
439 @override | |
440 visitFunctionTypeAliasElement(FunctionTypeAliasElement element) { | |
441 if (!instCreation) { | |
442 addSuggestion(element); | |
443 } | |
444 } | |
445 | |
446 @override | |
447 visitTopLevelVariableElement(TopLevelVariableElement element) { | |
448 if (!typesOnly) { | |
449 addSuggestion(element); | |
450 } | |
451 } | |
452 | |
453 /** | |
454 * Add suggestions for the visible members in the given library | |
455 */ | |
456 static void suggestionsFor( | |
457 DartCompletionRequest request, | |
458 CompletionSuggestionKind kind, | |
459 LibraryElement library, | |
460 bool typesOnly, | |
461 bool instCreation) { | |
462 if (library != null) { | |
463 library.visitChildren(new LibraryElementSuggestionBuilder( | |
464 request, kind, typesOnly, instCreation)); | |
465 } | |
466 } | |
467 } | |
468 | |
469 /** | |
470 * This class visits elements in a class and provides suggestions based upon | |
471 * the visible static members in that class. Clients should call | |
472 * [StaticClassElementSuggestionBuilder.suggestionsFor]. | |
473 */ | |
474 class StaticClassElementSuggestionBuilder extends GeneralizingElementVisitor | |
475 with ElementSuggestionBuilder { | |
476 final DartCompletionRequest request; | |
477 | |
478 StaticClassElementSuggestionBuilder(this.request); | |
479 | |
480 @override | |
481 CompletionSuggestionKind get kind => CompletionSuggestionKind.INVOCATION; | |
482 | |
483 @override | |
484 visitClassElement(ClassElement element) { | |
485 element.visitChildren(this); | |
486 element.allSupertypes.forEach((InterfaceType type) { | |
487 type.element.visitChildren(this); | |
488 }); | |
489 } | |
490 | |
491 @override | |
492 visitElement(Element element) { | |
493 // ignored | |
494 } | |
495 | |
496 @override | |
497 visitFieldElement(FieldElement element) { | |
498 if (!element.isStatic) { | |
499 return; | |
500 } | |
501 addSuggestion(element); | |
502 } | |
503 | |
504 @override | |
505 visitMethodElement(MethodElement element) { | |
506 if (!element.isStatic) { | |
507 return; | |
508 } | |
509 if (element.isOperator) { | |
510 return; | |
511 } | |
512 addSuggestion(element); | |
513 } | |
514 | |
515 @override | |
516 visitPropertyAccessorElement(PropertyAccessorElement element) { | |
517 if (!element.isStatic) { | |
518 return; | |
519 } | |
520 addSuggestion(element); | |
521 } | |
522 | |
523 /** | |
524 * Add suggestions for the visible members in the given class | |
525 */ | |
526 static void suggestionsFor(DartCompletionRequest request, Element element) { | |
527 if (element == DynamicElementImpl.instance) { | |
528 element = request.cache.objectClassElement; | |
529 } | |
530 if (element is ClassElement) { | |
531 return element.accept(new StaticClassElementSuggestionBuilder(request)); | |
532 } | |
533 } | |
534 } | |
535 | |
536 /** | |
537 * Common interface implemented by suggestion builders. | 175 * Common interface implemented by suggestion builders. |
538 */ | 176 */ |
539 abstract class SuggestionBuilder { | 177 abstract class SuggestionBuilder { |
540 /** | 178 /** |
541 * Compute suggestions and return `true` if building is complete, | 179 * Compute suggestions and return `true` if building is complete, |
542 * or `false` if [computeFull] should be called. | 180 * or `false` if [computeFull] should be called. |
543 */ | 181 */ |
544 bool computeFast(AstNode node); | 182 bool computeFast(AstNode node); |
545 | 183 |
546 /** | 184 /** |
547 * Return a future that computes the suggestions given a fully resolved AST. | 185 * Return a future that computes the suggestions given a fully resolved AST. |
548 * The future returns `true` if suggestions were added, else `false`. | 186 * The future returns `true` if suggestions were added, else `false`. |
549 */ | 187 */ |
550 Future<bool> computeFull(AstNode node); | 188 Future<bool> computeFull(AstNode node); |
551 } | 189 } |
OLD | NEW |