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'; | |
8 | 9 |
9 import 'package:analysis_server/src/protocol_server.dart' as protocol; | 10 import 'package:analysis_server/src/protocol_server.dart' as protocol; |
10 import 'package:analysis_server/src/protocol_server.dart' hide Element, | 11 import 'package:analysis_server/src/protocol_server.dart' hide Element, |
11 ElementKind; | 12 ElementKind; |
12 import 'package:analysis_server/src/services/completion/dart_completion_manager. dart'; | 13 import 'package:analysis_server/src/services/completion/dart_completion_manager. dart'; |
13 import 'package:analyzer/src/generated/ast.dart'; | 14 import 'package:analyzer/src/generated/ast.dart'; |
14 import 'package:analyzer/src/generated/element.dart'; | 15 import 'package:analyzer/src/generated/element.dart'; |
15 import 'package:analyzer/src/generated/utilities_dart.dart'; | 16 import 'package:analyzer/src/generated/utilities_dart.dart'; |
16 | 17 |
17 const String DYNAMIC = 'dynamic'; | 18 const String DYNAMIC = 'dynamic'; |
(...skipping 39 matching lines...) Expand 10 before | Expand all | Expand 10 after Loading... | |
57 bool isDeprecated = element.isDeprecated; | 58 bool isDeprecated = element.isDeprecated; |
58 CompletionSuggestion suggestion = new CompletionSuggestion( | 59 CompletionSuggestion suggestion = new CompletionSuggestion( |
59 kind, | 60 kind, |
60 isDeprecated ? COMPLETION_RELEVANCE_LOW : relevance, | 61 isDeprecated ? COMPLETION_RELEVANCE_LOW : relevance, |
61 completion, | 62 completion, |
62 completion.length, | 63 completion.length, |
63 0, | 64 0, |
64 isDeprecated, | 65 isDeprecated, |
65 false); | 66 false); |
66 suggestion.element = protocol.newElement_fromEngine(element); | 67 suggestion.element = protocol.newElement_fromEngine(element); |
67 if (element is ClassMemberElement) { | 68 Element enclosingElement = element.enclosingElement; |
68 ClassElement enclosingElement = element.enclosingElement; | 69 if (enclosingElement is ClassElement) { |
69 if (enclosingElement != null) { | 70 suggestion.declaringType = enclosingElement.displayName; |
70 suggestion.declaringType = enclosingElement.displayName; | |
71 } | |
72 } | 71 } |
73 suggestion.returnType = returnType; | 72 suggestion.returnType = returnType; |
74 if (element is ExecutableElement && element is! PropertyAccessorElement) { | 73 if (element is ExecutableElement && element is! PropertyAccessorElement) { |
75 suggestion.parameterNames = element.parameters.map( | 74 suggestion.parameterNames = element.parameters.map( |
76 (ParameterElement parameter) => parameter.name).toList(); | 75 (ParameterElement parameter) => parameter.name).toList(); |
77 suggestion.parameterTypes = element.parameters.map( | 76 suggestion.parameterTypes = element.parameters.map( |
78 (ParameterElement parameter) => parameter.type.displayName).toList(); | 77 (ParameterElement parameter) => parameter.type.displayName).toList(); |
79 suggestion.requiredParameterCount = element.parameters.where( | 78 suggestion.requiredParameterCount = element.parameters.where( |
80 (ParameterElement parameter) => | 79 (ParameterElement parameter) => |
81 parameter.parameterKind == ParameterKind.REQUIRED).length; | 80 parameter.parameterKind == ParameterKind.REQUIRED).length; |
(...skipping 76 matching lines...) Expand 10 before | Expand all | Expand 10 after Loading... | |
158 todo.add(classNode); | 157 todo.add(classNode); |
159 } else { | 158 } else { |
160 imported(name); | 159 imported(name); |
161 } | 160 } |
162 } | 161 } |
163 }); | 162 }); |
164 } | 163 } |
165 } | 164 } |
166 | 165 |
167 /** | 166 /** |
168 * This class visits elements in a class and provides suggestions based upon | |
169 * the visible members in that class. Clients should call | |
170 * [ClassElementSuggestionBuilder.suggestionsFor]. | |
171 */ | |
172 class ClassElementSuggestionBuilder extends GeneralizingElementVisitor with | |
173 ElementSuggestionBuilder { | |
174 final bool staticOnly; | |
175 final DartCompletionRequest request; | |
176 | |
177 ClassElementSuggestionBuilder(this.request, bool staticOnly) | |
178 : this.staticOnly = staticOnly; | |
179 | |
180 @override | |
181 CompletionSuggestionKind get kind => CompletionSuggestionKind.INVOCATION; | |
182 | |
183 @override | |
184 visitClassElement(ClassElement element) { | |
185 element.visitChildren(this); | |
186 element.allSupertypes.forEach((InterfaceType type) { | |
187 type.element.visitChildren(this); | |
188 }); | |
189 } | |
190 | |
191 @override | |
192 visitElement(Element element) { | |
193 // ignored | |
194 } | |
195 | |
196 @override | |
197 visitFieldElement(FieldElement element) { | |
198 if (staticOnly && !element.isStatic) { | |
199 return; | |
200 } | |
201 addSuggestion(element); | |
202 } | |
203 | |
204 @override | |
205 visitMethodElement(MethodElement element) { | |
206 if (staticOnly && !element.isStatic) { | |
207 return; | |
208 } | |
209 if (element.isOperator) { | |
210 return; | |
211 } | |
212 addSuggestion(element); | |
213 } | |
214 | |
215 @override | |
216 visitPropertyAccessorElement(PropertyAccessorElement element) { | |
217 if (staticOnly && !element.isStatic) { | |
218 return; | |
219 } | |
220 addSuggestion(element); | |
221 } | |
222 | |
223 /** | |
224 * Add suggestions for the visible members in the given class | |
225 */ | |
226 static void suggestionsFor(DartCompletionRequest request, Element element, | |
227 {bool staticOnly: false}) { | |
228 if (element == DynamicElementImpl.instance) { | |
229 element = request.cache.objectClassElement; | |
230 } | |
231 if (element is ClassElement) { | |
232 return element.accept( | |
233 new ClassElementSuggestionBuilder(request, staticOnly)); | |
234 } | |
235 } | |
236 } | |
237 | |
238 /** | |
239 * Common mixin for sharing behavior | 167 * Common mixin for sharing behavior |
240 */ | 168 */ |
241 abstract class ElementSuggestionBuilder { | 169 abstract class ElementSuggestionBuilder { |
242 | 170 |
243 /** | 171 /** |
244 * Internal collection of completions to prevent duplicate completions. | 172 * Internal collection of completions to prevent duplicate completions. |
245 */ | 173 */ |
246 final Set<String> _completions = new Set<String>(); | 174 final Set<String> _completions = new Set<String>(); |
247 | 175 |
248 /** | 176 /** |
(...skipping 29 matching lines...) Expand all Loading... | |
278 return; | 206 return; |
279 } | 207 } |
280 CompletionSuggestion suggestion = createSuggestion(element, kind: kind); | 208 CompletionSuggestion suggestion = createSuggestion(element, kind: kind); |
281 if (suggestion != null) { | 209 if (suggestion != null) { |
282 request.suggestions.add(suggestion); | 210 request.suggestions.add(suggestion); |
283 } | 211 } |
284 } | 212 } |
285 } | 213 } |
286 | 214 |
287 /** | 215 /** |
216 * This class provides suggestions based upon the visible instance members in | |
217 * an interface type. Clients should call | |
218 * [InterfaceTypeSuggestionBuilder.suggestionsFor]. | |
219 */ | |
220 class InterfaceTypeSuggestionBuilder { | |
221 /** | |
222 * Enumerated value indicating that we have not generated any completions for | |
223 * a given identifier yet. | |
224 */ | |
225 static const int _COMPLETION_TYPE_NONE = 0; | |
226 | |
227 /** | |
228 * Enumerated value indicating that we have generated a completion for a | |
229 * getter. | |
230 */ | |
231 static const int _COMPLETION_TYPE_GETTER = 1; | |
232 | |
233 /** | |
234 * Enumerated value indicating that we have generated a completion for a | |
235 * setter. | |
236 */ | |
237 static const int _COMPLETION_TYPE_SETTER = 2; | |
238 | |
239 /** | |
240 * Enumerated value indicating that we have generated a completion for a | |
241 * field, a method, or a getter/setter pair. | |
242 */ | |
243 static const int _COMPLETION_TYPE_FIELD_OR_METHOD_OR_GETSET = 3; | |
244 | |
245 final DartCompletionRequest request; | |
246 | |
247 /** | |
248 * Map indicating, for each possible completion identifier, whether we have | |
249 * already generated completions for a getter, setter, or both. The "both" | |
250 * case also handles the case where have generated a completion for a method | |
251 * or a field. | |
252 * | |
253 * Note: the enumerated values stored in this map are intended to be bitwise | |
254 * compared. | |
255 */ | |
256 Map<String, int> _completionTypesGenerated = new HashMap<String, int>(); | |
257 | |
258 InterfaceTypeSuggestionBuilder(this.request); | |
259 | |
260 @override | |
danrubel
2015/01/15 02:53:25
Remove override annotation?
Paul Berry
2015/01/21 15:10:33
Done.
| |
261 CompletionSuggestionKind get kind => CompletionSuggestionKind.INVOCATION; | |
262 | |
263 /** | |
264 * Add a suggestion based upon the given element, provided that it is not | |
265 * shadowed by a previously added suggestion. | |
266 */ | |
267 void addSuggestion(Element element) { | |
268 if (element.isPrivate) { | |
269 LibraryElement elementLibrary = element.library; | |
270 LibraryElement unitLibrary = request.unit.element.library; | |
271 if (elementLibrary != unitLibrary) { | |
272 return; | |
273 } | |
274 } | |
275 String identifier = element.displayName; | |
276 int alreadyGenerated = | |
277 _completionTypesGenerated.putIfAbsent(identifier, () => _COMPLETION_TYPE _NONE); | |
278 if (element is MethodElement) { | |
279 // Anything shadows a method. | |
280 if (alreadyGenerated != _COMPLETION_TYPE_NONE) { | |
281 return; | |
282 } | |
283 _completionTypesGenerated[identifier] = | |
284 _COMPLETION_TYPE_FIELD_OR_METHOD_OR_GETSET; | |
285 } else if (element is PropertyAccessorElement) { | |
286 if (element.isGetter) { | |
287 // Getters, fields, and methods shadow a getter. | |
288 if ((alreadyGenerated & _COMPLETION_TYPE_GETTER) != 0) { | |
289 return; | |
290 } | |
291 _completionTypesGenerated[identifier] |= _COMPLETION_TYPE_GETTER; | |
292 } else { | |
293 // Setters, fields, and methods shadow a setter. | |
294 if ((alreadyGenerated & _COMPLETION_TYPE_SETTER) != 0) { | |
295 return; | |
296 } | |
297 _completionTypesGenerated[identifier] |= _COMPLETION_TYPE_SETTER; | |
298 } | |
299 } else if (element is FieldElement) { | |
300 // Fields and methods shadow a field. A getter/setter pair shadows a | |
301 // field, but a getter or setter by itself doesn't. | |
302 if (alreadyGenerated == _COMPLETION_TYPE_FIELD_OR_METHOD_OR_GETSET) { | |
303 return; | |
304 } | |
305 _completionTypesGenerated[identifier] = | |
306 _COMPLETION_TYPE_FIELD_OR_METHOD_OR_GETSET; | |
307 } else { | |
308 // Unexpected element type; skip it. | |
309 assert(false); | |
310 return; | |
311 } | |
312 CompletionSuggestion suggestion = createSuggestion(element, kind: kind); | |
313 if (suggestion != null) { | |
314 request.suggestions.add(suggestion); | |
315 } | |
316 } | |
317 | |
318 void _buildSuggestions(InterfaceType type, LibraryElement library) { | |
319 // Visit all of the types in the class hierarchy, collecting possible | |
320 // completions. If multiple elements are found that complete to the same | |
321 // identifier, addSuggestion will discard all but the first (with a few | |
322 // exceptions to handle getter/setter pairs). | |
323 for (InterfaceType targetType in _getTypeOrdering(type)) { | |
324 for (MethodElement method in targetType.methods) { | |
325 addSuggestion(method); | |
326 } | |
327 for (PropertyAccessorElement propertyAccessor in targetType.accessors) { | |
328 if (propertyAccessor.isSynthetic) { | |
329 // Avoid visiting a field twice | |
330 if (propertyAccessor.isGetter) { | |
danrubel
2015/01/15 02:53:25
A synthetic getter indicates an non-synthetic fiel
Paul Berry
2015/01/21 15:10:33
That's true, but the only way InterfaceType provid
| |
331 addSuggestion(propertyAccessor.variable); | |
332 } | |
333 } else { | |
334 addSuggestion(propertyAccessor); | |
335 } | |
336 } | |
337 } | |
338 } | |
339 | |
340 /** | |
341 * Get a list of [InterfaceType]s that should be searched to find the | |
342 * possible completions for an object having type [type]. | |
343 */ | |
344 List<InterfaceType> _getTypeOrdering(InterfaceType type) { | |
345 // Candidate completions can come from [type] as well as any types above it | |
346 // in the class hierarchy (including mixins, superclasses, and interfaces). | |
347 // If a given completion identifier shows up in multiple types, we should | |
348 // use the element that is nearest in the superclass chain, so we will | |
349 // visit [type] first, then its mixins, then its superclass, then its | |
350 // superclass's mixins, etc., and only afterwards visit interfaces. | |
351 // | |
352 // We short-circuit loops in the class hierarchy by keeping track of the | |
353 // classes seen (not the interfaces) so that we won't be fooled by nonsense | |
354 // like "class C<T> extends C<List<T>> {}" | |
355 List<InterfaceType> result = <InterfaceType>[]; | |
356 Set<ClassElement> classesSeen = new HashSet<ClassElement>(); | |
357 List<InterfaceType> typesToVisit = <InterfaceType>[type]; | |
358 while (typesToVisit.isNotEmpty) { | |
359 InterfaceType nextType = typesToVisit.removeLast(); | |
360 if (!classesSeen.add(nextType.element)) { | |
361 // Class had already been seen, so ignore this type. | |
362 continue; | |
363 } | |
364 result.add(nextType); | |
365 // typesToVisit is a stack, so push on the interfaces first, then the | |
366 // superclass, then the mixins. This will ensure that they are visited | |
367 // in the reverse order. | |
368 typesToVisit.addAll(nextType.interfaces); | |
369 if (nextType.superclass != null) { | |
370 typesToVisit.add(nextType.superclass); | |
371 } | |
372 typesToVisit.addAll(nextType.mixins); | |
373 } | |
374 return result; | |
375 } | |
376 | |
377 /** | |
378 * Add suggestions for the visible members in the given interface | |
379 */ | |
380 static void suggestionsFor(DartCompletionRequest request, DartType type) { | |
381 CompilationUnit compilationUnit = | |
382 request.node.getAncestor((AstNode node) => node is CompilationUnit); | |
383 LibraryElement library = compilationUnit.element.library; | |
384 if (type is DynamicTypeImpl) { | |
385 type = request.cache.objectClassElement.type; | |
386 } | |
387 if (type is InterfaceType) { | |
388 return new InterfaceTypeSuggestionBuilder( | |
389 request)._buildSuggestions(type, library); | |
390 } | |
391 } | |
392 } | |
393 | |
394 /** | |
288 * This class visits elements in a library and provides suggestions based upon | 395 * This class visits elements in a library and provides suggestions based upon |
289 * the visible members in that library. Clients should call | 396 * the visible members in that library. Clients should call |
290 * [LibraryElementSuggestionBuilder.suggestionsFor]. | 397 * [LibraryElementSuggestionBuilder.suggestionsFor]. |
291 */ | 398 */ |
292 class LibraryElementSuggestionBuilder extends GeneralizingElementVisitor with | 399 class LibraryElementSuggestionBuilder extends GeneralizingElementVisitor with |
293 ElementSuggestionBuilder { | 400 ElementSuggestionBuilder { |
294 final DartCompletionRequest request; | 401 final DartCompletionRequest request; |
295 final CompletionSuggestionKind kind; | 402 final CompletionSuggestionKind kind; |
296 | 403 |
297 LibraryElementSuggestionBuilder(this.request, this.kind); | 404 LibraryElementSuggestionBuilder(this.request, this.kind); |
(...skipping 87 matching lines...) Expand 10 before | Expand all | Expand 10 after Loading... | |
385 addSuggestion(element); | 492 addSuggestion(element); |
386 } | 493 } |
387 | 494 |
388 @override | 495 @override |
389 visitElement(Element element) { | 496 visitElement(Element element) { |
390 // ignored | 497 // ignored |
391 } | 498 } |
392 } | 499 } |
393 | 500 |
394 /** | 501 /** |
502 * This class visits elements in a class and provides suggestions based upon | |
503 * the visible static members in that class. Clients should call | |
504 * [StaticClassElementSuggestionBuilder.suggestionsFor]. | |
505 */ | |
506 class StaticClassElementSuggestionBuilder extends GeneralizingElementVisitor | |
507 with ElementSuggestionBuilder { | |
508 final DartCompletionRequest request; | |
509 | |
510 StaticClassElementSuggestionBuilder(this.request); | |
511 | |
512 @override | |
513 CompletionSuggestionKind get kind => CompletionSuggestionKind.INVOCATION; | |
514 | |
515 @override | |
516 visitClassElement(ClassElement element) { | |
517 element.visitChildren(this); | |
518 element.allSupertypes.forEach((InterfaceType type) { | |
519 type.element.visitChildren(this); | |
520 }); | |
521 } | |
522 | |
523 @override | |
524 visitElement(Element element) { | |
525 // ignored | |
526 } | |
527 | |
528 @override | |
529 visitFieldElement(FieldElement element) { | |
530 if (!element.isStatic) { | |
531 return; | |
532 } | |
533 addSuggestion(element); | |
534 } | |
535 | |
536 @override | |
537 visitMethodElement(MethodElement element) { | |
538 if (!element.isStatic) { | |
539 return; | |
540 } | |
541 if (element.isOperator) { | |
542 return; | |
543 } | |
544 addSuggestion(element); | |
545 } | |
546 | |
547 @override | |
548 visitPropertyAccessorElement(PropertyAccessorElement element) { | |
549 if (!element.isStatic) { | |
550 return; | |
551 } | |
552 addSuggestion(element); | |
553 } | |
554 | |
555 /** | |
556 * Add suggestions for the visible members in the given class | |
557 */ | |
558 static void suggestionsFor(DartCompletionRequest request, Element element) { | |
559 if (element == DynamicElementImpl.instance) { | |
560 element = request.cache.objectClassElement; | |
561 } | |
562 if (element is ClassElement) { | |
563 return element.accept(new StaticClassElementSuggestionBuilder(request)); | |
564 } | |
565 } | |
566 } | |
567 | |
568 /** | |
395 * Common interface implemented by suggestion builders. | 569 * Common interface implemented by suggestion builders. |
396 */ | 570 */ |
397 abstract class SuggestionBuilder { | 571 abstract class SuggestionBuilder { |
398 /** | 572 /** |
399 * Compute suggestions and return `true` if building is complete, | 573 * Compute suggestions and return `true` if building is complete, |
400 * or `false` if [computeFull] should be called. | 574 * or `false` if [computeFull] should be called. |
401 */ | 575 */ |
402 bool computeFast(AstNode node); | 576 bool computeFast(AstNode node); |
403 | 577 |
404 /** | 578 /** |
405 * Return a future that computes the suggestions given a fully resolved AST. | 579 * Return a future that computes the suggestions given a fully resolved AST. |
406 * The future returns `true` if suggestions were added, else `false`. | 580 * The future returns `true` if suggestions were added, else `false`. |
407 */ | 581 */ |
408 Future<bool> computeFull(AstNode node); | 582 Future<bool> computeFull(AstNode node); |
409 } | 583 } |
OLD | NEW |