| 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 |