OLD | NEW |
(Empty) | |
| 1 // Copyright (c) 2017, the Dart project authors. Please see the AUTHORS file |
| 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. |
| 4 |
| 5 import 'dart:async'; |
| 6 import 'dart:collection'; |
| 7 |
| 8 import 'package:analyzer/dart/ast/ast.dart'; |
| 9 import 'package:analyzer/dart/element/element.dart'; |
| 10 import 'package:analyzer/dart/element/type.dart'; |
| 11 import 'package:analyzer/file_system/file_system.dart'; |
| 12 import 'package:analyzer_plugin/protocol/protocol_common.dart' hide Element; |
| 13 import 'package:analyzer_plugin/src/utilities/completion/completion_target.dart'
; |
| 14 import 'package:analyzer_plugin/src/utilities/completion/suggestion_builder.dart
'; |
| 15 import 'package:analyzer_plugin/src/utilities/visitors/local_declaration_visitor
.dart'; |
| 16 import 'package:analyzer_plugin/utilities/completion/completion_core.dart'; |
| 17 import 'package:analyzer_plugin/utilities/completion/relevance.dart'; |
| 18 import 'package:analyzer_plugin/utilities/completion/suggestion_builder.dart'; |
| 19 |
| 20 /** |
| 21 * A completion contributor that will generate suggestions for instance |
| 22 * invocations and accesses. |
| 23 */ |
| 24 class TypeMemberContributor implements CompletionContributor { |
| 25 @override |
| 26 Future<Null> computeSuggestions( |
| 27 CompletionRequest request, CompletionCollector collector) async { |
| 28 LibraryElement containingLibrary = request.result.libraryElement; |
| 29 // Gracefully degrade if the library element is not resolved |
| 30 // e.g. detached part file or source change |
| 31 if (containingLibrary == null) { |
| 32 return; |
| 33 } |
| 34 |
| 35 // Recompute the target since resolution may have changed it |
| 36 Expression expression = _computeDotTarget(request); |
| 37 if (expression == null || expression.isSynthetic) { |
| 38 return; |
| 39 } |
| 40 if (expression is Identifier) { |
| 41 Element element = expression.bestElement; |
| 42 if (element is ClassElement) { |
| 43 // Suggestions provided by StaticMemberContributor |
| 44 return; |
| 45 } |
| 46 if (element is PrefixElement) { |
| 47 // Suggestions provided by LibraryMemberContributor |
| 48 return; |
| 49 } |
| 50 } |
| 51 |
| 52 // Determine the target expression's type |
| 53 DartType type = expression.bestType; |
| 54 if (type.isDynamic) { |
| 55 // If the expression does not provide a good type |
| 56 // then attempt to get a better type from the element |
| 57 if (expression is Identifier) { |
| 58 Element elem = expression.bestElement; |
| 59 if (elem is FunctionTypedElement) { |
| 60 type = elem.returnType; |
| 61 } else if (elem is ParameterElement) { |
| 62 type = elem.type; |
| 63 } else if (elem is LocalVariableElement) { |
| 64 type = elem.type; |
| 65 } |
| 66 if ((type == null || type.isDynamic) && |
| 67 expression is SimpleIdentifier) { |
| 68 // If the element does not provide a good type |
| 69 // then attempt to get a better type from a local declaration |
| 70 _LocalBestTypeVisitor visitor = |
| 71 new _LocalBestTypeVisitor(expression.name, request.offset); |
| 72 if (visitor.visit(expression) && visitor.typeFound != null) { |
| 73 type = visitor.typeFound; |
| 74 } |
| 75 } |
| 76 } |
| 77 } |
| 78 String containingMethodName; |
| 79 if (expression is SuperExpression && type is InterfaceType) { |
| 80 // Suggest members from superclass if target is "super" |
| 81 type = (type as InterfaceType).superclass; |
| 82 // Determine the name of the containing method because |
| 83 // the most likely completion is a super expression with same name |
| 84 MethodDeclaration containingMethod = |
| 85 expression.getAncestor((p) => p is MethodDeclaration); |
| 86 if (containingMethod != null) { |
| 87 SimpleIdentifier id = containingMethod.name; |
| 88 if (id != null) { |
| 89 containingMethodName = id.name; |
| 90 } |
| 91 } |
| 92 } |
| 93 if (type.isDynamic) { |
| 94 // Suggest members from object if target is "dynamic" |
| 95 type = request.result.typeProvider.objectType; |
| 96 } |
| 97 |
| 98 // Build the suggestions |
| 99 if (type is InterfaceType) { |
| 100 _SuggestionBuilder builder = new _SuggestionBuilder( |
| 101 request.resourceProvider, collector, containingLibrary); |
| 102 builder.buildSuggestions(type, containingMethodName); |
| 103 } |
| 104 } |
| 105 |
| 106 /** |
| 107 * Update the completion [target] and [dotTarget] based on the given [unit]. |
| 108 */ |
| 109 Expression _computeDotTarget(CompletionRequest request) { |
| 110 CompletionTarget target = |
| 111 new CompletionTarget.forOffset(request.result.unit, request.offset); |
| 112 AstNode node = target.containingNode; |
| 113 if (node is MethodInvocation) { |
| 114 if (identical(node.methodName, target.entity)) { |
| 115 return node.realTarget; |
| 116 } else if (node.isCascaded && node.operator.offset + 1 == target.offset) { |
| 117 return node.realTarget; |
| 118 } |
| 119 } |
| 120 if (node is PropertyAccess) { |
| 121 if (identical(node.propertyName, target.entity)) { |
| 122 return node.realTarget; |
| 123 } else if (node.isCascaded && node.operator.offset + 1 == target.offset) { |
| 124 return node.realTarget; |
| 125 } |
| 126 } |
| 127 if (node is PrefixedIdentifier) { |
| 128 if (identical(node.identifier, target.entity)) { |
| 129 return node.prefix; |
| 130 } |
| 131 } |
| 132 return null; |
| 133 } |
| 134 } |
| 135 |
| 136 /** |
| 137 * An [AstVisitor] which looks for a declaration with the given name |
| 138 * and if found, tries to determine a type for that declaration. |
| 139 */ |
| 140 class _LocalBestTypeVisitor extends LocalDeclarationVisitor { |
| 141 /** |
| 142 * The name for the declaration to be found. |
| 143 */ |
| 144 final String targetName; |
| 145 |
| 146 /** |
| 147 * The best type for the found declaration, |
| 148 * or `null` if no declaration found or failed to determine a type. |
| 149 */ |
| 150 DartType typeFound; |
| 151 |
| 152 /** |
| 153 * Construct a new instance to search for a declaration |
| 154 */ |
| 155 _LocalBestTypeVisitor(this.targetName, int offset) : super(offset); |
| 156 |
| 157 @override |
| 158 void declaredClass(ClassDeclaration declaration) { |
| 159 if (declaration.name.name == targetName) { |
| 160 // no type |
| 161 finished(); |
| 162 } |
| 163 } |
| 164 |
| 165 @override |
| 166 void declaredClassTypeAlias(ClassTypeAlias declaration) { |
| 167 if (declaration.name.name == targetName) { |
| 168 // no type |
| 169 finished(); |
| 170 } |
| 171 } |
| 172 |
| 173 @override |
| 174 void declaredField(FieldDeclaration fieldDecl, VariableDeclaration varDecl) { |
| 175 if (varDecl.name.name == targetName) { |
| 176 // Type provided by the element in computeFull above |
| 177 finished(); |
| 178 } |
| 179 } |
| 180 |
| 181 @override |
| 182 void declaredFunction(FunctionDeclaration declaration) { |
| 183 if (declaration.name.name == targetName) { |
| 184 TypeAnnotation typeName = declaration.returnType; |
| 185 if (typeName != null) { |
| 186 typeFound = typeName.type; |
| 187 } |
| 188 finished(); |
| 189 } |
| 190 } |
| 191 |
| 192 @override |
| 193 void declaredFunctionTypeAlias(FunctionTypeAlias declaration) { |
| 194 if (declaration.name.name == targetName) { |
| 195 TypeAnnotation typeName = declaration.returnType; |
| 196 if (typeName != null) { |
| 197 typeFound = typeName.type; |
| 198 } |
| 199 finished(); |
| 200 } |
| 201 } |
| 202 |
| 203 @override |
| 204 void declaredLabel(Label label, bool isCaseLabel) { |
| 205 if (label.label.name == targetName) { |
| 206 // no type |
| 207 finished(); |
| 208 } |
| 209 } |
| 210 |
| 211 @override |
| 212 void declaredLocalVar(SimpleIdentifier name, TypeAnnotation type) { |
| 213 if (name.name == targetName) { |
| 214 typeFound = name.bestType; |
| 215 finished(); |
| 216 } |
| 217 } |
| 218 |
| 219 @override |
| 220 void declaredMethod(MethodDeclaration declaration) { |
| 221 if (declaration.name.name == targetName) { |
| 222 TypeAnnotation typeName = declaration.returnType; |
| 223 if (typeName != null) { |
| 224 typeFound = typeName.type; |
| 225 } |
| 226 finished(); |
| 227 } |
| 228 } |
| 229 |
| 230 @override |
| 231 void declaredParam(SimpleIdentifier name, TypeAnnotation type) { |
| 232 if (name.name == targetName) { |
| 233 // Type provided by the element in computeFull above |
| 234 finished(); |
| 235 } |
| 236 } |
| 237 |
| 238 @override |
| 239 void declaredTopLevelVar( |
| 240 VariableDeclarationList varList, VariableDeclaration varDecl) { |
| 241 if (varDecl.name.name == targetName) { |
| 242 // Type provided by the element in computeFull above |
| 243 finished(); |
| 244 } |
| 245 } |
| 246 } |
| 247 |
| 248 /** |
| 249 * This class provides suggestions based upon the visible instance members in |
| 250 * an interface type. |
| 251 */ |
| 252 class _SuggestionBuilder { |
| 253 /** |
| 254 * Enumerated value indicating that we have not generated any completions for |
| 255 * a given identifier yet. |
| 256 */ |
| 257 static const int _COMPLETION_TYPE_NONE = 0; |
| 258 |
| 259 /** |
| 260 * Enumerated value indicating that we have generated a completion for a |
| 261 * getter. |
| 262 */ |
| 263 static const int _COMPLETION_TYPE_GETTER = 1; |
| 264 |
| 265 /** |
| 266 * Enumerated value indicating that we have generated a completion for a |
| 267 * setter. |
| 268 */ |
| 269 static const int _COMPLETION_TYPE_SETTER = 2; |
| 270 |
| 271 /** |
| 272 * Enumerated value indicating that we have generated a completion for a |
| 273 * field, a method, or a getter/setter pair. |
| 274 */ |
| 275 static const int _COMPLETION_TYPE_FIELD_OR_METHOD_OR_GETSET = 3; |
| 276 |
| 277 /** |
| 278 * The resource provider used to access the file system. |
| 279 */ |
| 280 final ResourceProvider resourceProvider; |
| 281 |
| 282 /** |
| 283 * The collector being used to collect completion suggestions. |
| 284 */ |
| 285 final CompletionCollector collector; |
| 286 |
| 287 /** |
| 288 * The library containing the unit in which the completion is requested. |
| 289 */ |
| 290 final LibraryElement containingLibrary; |
| 291 |
| 292 /** |
| 293 * Map indicating, for each possible completion identifier, whether we have |
| 294 * already generated completions for a getter, setter, or both. The "both" |
| 295 * case also handles the case where have generated a completion for a method |
| 296 * or a field. |
| 297 * |
| 298 * Note: the enumerated values stored in this map are intended to be bitwise |
| 299 * compared. |
| 300 */ |
| 301 Map<String, int> _completionTypesGenerated = new HashMap<String, int>(); |
| 302 |
| 303 /** |
| 304 * Map from completion identifier to completion suggestion |
| 305 */ |
| 306 Map<String, CompletionSuggestion> _suggestionMap = |
| 307 <String, CompletionSuggestion>{}; |
| 308 |
| 309 /** |
| 310 * The builder used to build suggestions. |
| 311 */ |
| 312 final SuggestionBuilder builder; |
| 313 |
| 314 _SuggestionBuilder( |
| 315 this.resourceProvider, this.collector, this.containingLibrary) |
| 316 : builder = new SuggestionBuilderImpl(resourceProvider); |
| 317 |
| 318 Iterable<CompletionSuggestion> get suggestions => _suggestionMap.values; |
| 319 |
| 320 /** |
| 321 * Create completion suggestions for 'dot' completions on the given [type]. |
| 322 * If the 'dot' completion is a super expression, then [containingMethodName] |
| 323 * is the name of the method in which the completion is requested. |
| 324 */ |
| 325 void buildSuggestions(InterfaceType type, String containingMethodName) { |
| 326 // Visit all of the types in the class hierarchy, collecting possible |
| 327 // completions. If multiple elements are found that complete to the same |
| 328 // identifier, addSuggestion will discard all but the first (with a few |
| 329 // exceptions to handle getter/setter pairs). |
| 330 List<InterfaceType> types = _getTypeOrdering(type); |
| 331 for (InterfaceType targetType in types) { |
| 332 for (MethodElement method in targetType.methods) { |
| 333 // Exclude static methods when completion on an instance |
| 334 if (!method.isStatic) { |
| 335 // Boost the relevance of a super expression |
| 336 // calling a method of the same name as the containing method |
| 337 _addSuggestion(method, |
| 338 relevance: method.name == containingMethodName |
| 339 ? DART_RELEVANCE_HIGH |
| 340 : null); |
| 341 } |
| 342 } |
| 343 for (PropertyAccessorElement propertyAccessor in targetType.accessors) { |
| 344 if (!propertyAccessor.isStatic) { |
| 345 if (propertyAccessor.isSynthetic) { |
| 346 // Avoid visiting a field twice |
| 347 if (propertyAccessor.isGetter) { |
| 348 _addSuggestion(propertyAccessor.variable); |
| 349 } |
| 350 } else { |
| 351 _addSuggestion(propertyAccessor); |
| 352 } |
| 353 } |
| 354 } |
| 355 } |
| 356 for (CompletionSuggestion suggestion in suggestions) { |
| 357 collector.addSuggestion(suggestion); |
| 358 } |
| 359 } |
| 360 |
| 361 /** |
| 362 * Add a suggestion based upon the given element, provided that it is not |
| 363 * shadowed by a previously added suggestion. |
| 364 */ |
| 365 void _addSuggestion(Element element, {int relevance}) { |
| 366 if (element.isPrivate) { |
| 367 if (element.library != containingLibrary) { |
| 368 // Do not suggest private members for imported libraries |
| 369 return; |
| 370 } |
| 371 } |
| 372 String identifier = element.displayName; |
| 373 |
| 374 if (relevance == null) { |
| 375 // Decrease relevance of suggestions starting with $ |
| 376 // https://github.com/dart-lang/sdk/issues/27303 |
| 377 if (identifier != null && identifier.startsWith(r'$')) { |
| 378 relevance = DART_RELEVANCE_LOW; |
| 379 } else { |
| 380 relevance = DART_RELEVANCE_DEFAULT; |
| 381 } |
| 382 } |
| 383 |
| 384 int alreadyGenerated = _completionTypesGenerated.putIfAbsent( |
| 385 identifier, () => _COMPLETION_TYPE_NONE); |
| 386 if (element is MethodElement) { |
| 387 // Anything shadows a method. |
| 388 if (alreadyGenerated != _COMPLETION_TYPE_NONE) { |
| 389 return; |
| 390 } |
| 391 _completionTypesGenerated[identifier] = |
| 392 _COMPLETION_TYPE_FIELD_OR_METHOD_OR_GETSET; |
| 393 } else if (element is PropertyAccessorElement) { |
| 394 if (element.isGetter) { |
| 395 // Getters, fields, and methods shadow a getter. |
| 396 if ((alreadyGenerated & _COMPLETION_TYPE_GETTER) != 0) { |
| 397 return; |
| 398 } |
| 399 _completionTypesGenerated[identifier] |= _COMPLETION_TYPE_GETTER; |
| 400 } else { |
| 401 // Setters, fields, and methods shadow a setter. |
| 402 if ((alreadyGenerated & _COMPLETION_TYPE_SETTER) != 0) { |
| 403 return; |
| 404 } |
| 405 _completionTypesGenerated[identifier] |= _COMPLETION_TYPE_SETTER; |
| 406 } |
| 407 } else if (element is FieldElement) { |
| 408 // Fields and methods shadow a field. A getter/setter pair shadows a |
| 409 // field, but a getter or setter by itself doesn't. |
| 410 if (alreadyGenerated == _COMPLETION_TYPE_FIELD_OR_METHOD_OR_GETSET) { |
| 411 return; |
| 412 } |
| 413 _completionTypesGenerated[identifier] = |
| 414 _COMPLETION_TYPE_FIELD_OR_METHOD_OR_GETSET; |
| 415 } else { |
| 416 // Unexpected element type; skip it. |
| 417 assert(false); |
| 418 return; |
| 419 } |
| 420 CompletionSuggestion suggestion = |
| 421 builder.forElement(element, relevance: relevance); |
| 422 if (suggestion != null) { |
| 423 _suggestionMap[suggestion.completion] = suggestion; |
| 424 } |
| 425 } |
| 426 |
| 427 /** |
| 428 * Get a list of [InterfaceType]s that should be searched to find the |
| 429 * possible completions for an object having type [type]. |
| 430 */ |
| 431 List<InterfaceType> _getTypeOrdering(InterfaceType type) { |
| 432 // Candidate completions can come from [type] as well as any types above it |
| 433 // in the class hierarchy (including mixins, superclasses, and interfaces). |
| 434 // If a given completion identifier shows up in multiple types, we should |
| 435 // use the element that is nearest in the superclass chain, so we will |
| 436 // visit [type] first, then its mixins, then its superclass, then its |
| 437 // superclass's mixins, etc., and only afterwards visit interfaces. |
| 438 // |
| 439 // We short-circuit loops in the class hierarchy by keeping track of the |
| 440 // classes seen (not the interfaces) so that we won't be fooled by nonsense |
| 441 // like "class C<T> extends C<List<T>> {}" |
| 442 List<InterfaceType> result = <InterfaceType>[]; |
| 443 Set<ClassElement> classesSeen = new HashSet<ClassElement>(); |
| 444 List<InterfaceType> typesToVisit = <InterfaceType>[type]; |
| 445 while (typesToVisit.isNotEmpty) { |
| 446 InterfaceType nextType = typesToVisit.removeLast(); |
| 447 if (!classesSeen.add(nextType.element)) { |
| 448 // Class had already been seen, so ignore this type. |
| 449 continue; |
| 450 } |
| 451 result.add(nextType); |
| 452 // typesToVisit is a stack, so push on the interfaces first, then the |
| 453 // superclass, then the mixins. This will ensure that they are visited |
| 454 // in the reverse order. |
| 455 typesToVisit.addAll(nextType.interfaces); |
| 456 if (nextType.superclass != null) { |
| 457 typesToVisit.add(nextType.superclass); |
| 458 } |
| 459 typesToVisit.addAll(nextType.mixins); |
| 460 } |
| 461 return result; |
| 462 } |
| 463 } |
OLD | NEW |