Chromium Code Reviews
chromiumcodereview-hr@appspot.gserviceaccount.com (chromiumcodereview-hr) | Please choose your nickname with Settings | Help | Chromium Project | Gerrit Changes | Sign out
(8)

Side by Side Diff: pkg/analyzer_plugin/lib/utilities/completion/type_member_contributor.dart

Issue 2927663002: Port two completion contributors for use by plugins (Closed)
Patch Set: Created 3 years, 6 months ago
Use n/p to move between diff chunks; N/P to move between comments. Draft comments are only viewable by you.
Jump to:
View unified diff | Download patch
OLDNEW
(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 }
OLDNEW

Powered by Google App Engine
This is Rietveld 408576698