Chromium Code Reviews| 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 |