Completion. Migrate LibraryMemberContributor.

We migrate all pre-existing contributors into the new schema, `InScopeCompletionPass`.

Change-Id: Ifa2834d79525f9efefff783900e83fd4d5843b35
Reviewed-on: https://dart-review.googlesource.com/c/sdk/+/358684
Commit-Queue: Konstantin Shcheglov <scheglov@google.com>
Reviewed-by: Brian Wilkerson <brianwilkerson@google.com>
Reviewed-by: Keerti Parthasarathy <keertip@google.com>
This commit is contained in:
Konstantin Shcheglov 2024-03-29 17:11:22 +00:00 committed by Commit Queue
parent f0c4243e6c
commit 15d310a851
11 changed files with 177 additions and 93 deletions

View file

@ -348,6 +348,19 @@ final class LabelSuggestion extends CandidateSuggestion {
String get completion => label.label.name;
}
/// The suggestion for `loadLibrary()`.
final class LoadLibraryFunctionSuggestion extends ExecutableSuggestion {
final FunctionElement element;
LoadLibraryFunctionSuggestion({
required super.kind,
required this.element,
});
@override
String get completion => element.name;
}
/// The information about a candidate suggestion based on a local function.
final class LocalFunctionSuggestion extends ExecutableSuggestion {
/// The element on which the suggestion is based.
@ -728,6 +741,10 @@ extension SuggestionBuilderExtension on SuggestionBuilder {
offset: suggestion.selectionOffset);
case LabelSuggestion():
suggestLabel(suggestion.label);
case LoadLibraryFunctionSuggestion():
suggestLoadLibraryFunction(
suggestion.element,
);
case LocalFunctionSuggestion():
suggestTopLevelFunction(suggestion.element);
case LocalVariableSuggestion():

View file

@ -9,7 +9,6 @@ import 'package:analysis_server/src/services/completion/dart/candidate_suggestio
import 'package:analysis_server/src/services/completion/dart/completion_state.dart';
import 'package:analysis_server/src/services/completion/dart/feature_computer.dart';
import 'package:analysis_server/src/services/completion/dart/in_scope_completion_pass.dart';
import 'package:analysis_server/src/services/completion/dart/library_member_contributor.dart';
import 'package:analysis_server/src/services/completion/dart/not_imported_contributor.dart';
import 'package:analysis_server/src/services/completion/dart/suggestion_builder.dart';
import 'package:analysis_server/src/services/completion/dart/suggestion_collector.dart';
@ -119,9 +118,7 @@ class DartCompletionManager {
// Compute the list of contributors that will be run.
var builder =
SuggestionBuilder(request, useFilter: useFilter, listener: listener);
var contributors = <DartCompletionContributor>[
LibraryMemberContributor(request, builder),
];
var contributors = <DartCompletionContributor>[];
if (includedElementKinds != null) {
_addIncludedElementKinds(request);

View file

@ -69,6 +69,10 @@ class DeclarationHelper {
/// Whether suggestions should be limited to only include types.
final bool mustBeType;
/// Whether suggestions should exclude type names, e.g. include only
/// constructor invocations.
final bool excludeTypeNames;
/// Whether suggestions should be tear-offs rather than invocations where
/// possible.
final bool preferNonInvocation;
@ -111,6 +115,7 @@ class DeclarationHelper {
required this.mustBeNonVoid,
required this.mustBeStatic,
required this.mustBeType,
required this.excludeTypeNames,
required this.preferNonInvocation,
required this.suggestUnnamedAsNew,
required this.skipImports,
@ -180,6 +185,17 @@ class DeclarationHelper {
namespace: importElement.namespace,
prefix: null,
);
if (importElement.prefix case var importPrefix?) {
if (importPrefix is DeferredImportElementPrefix) {
collector.addSuggestion(
LoadLibraryFunctionSuggestion(
kind: CompletionSuggestionKind.INVOCATION,
element: importedLibrary.loadLibraryFunction,
),
);
}
}
}
}
@ -1195,7 +1211,7 @@ class DeclarationHelper {
(mustBeMixable && !element.isMixableIn(request.libraryElement))) {
return;
}
if (!mustBeConstant) {
if (!mustBeConstant && !excludeTypeNames) {
var suggestion =
ClassSuggestion(importData: importData, element: element);
collector.addSuggestion(suggestion);

View file

@ -159,6 +159,7 @@ class InScopeCompletionPass extends SimpleAstVisitor<void> {
bool mustBeNonVoid = false,
bool mustBeStatic = false,
bool mustBeType = false,
bool excludeTypeNames = false,
bool preferNonInvocation = false,
bool suggestUnnamedAsNew = false,
Set<AstNode> excludedNodes = const {},
@ -200,6 +201,7 @@ class InScopeCompletionPass extends SimpleAstVisitor<void> {
mustBeNonVoid: mustBeNonVoid,
mustBeStatic: mustBeStatic,
mustBeType: mustBeType,
excludeTypeNames: excludeTypeNames,
preferNonInvocation: preferNonInvocation,
suggestUnnamedAsNew: suggestUnnamedAsNew,
skipImports: skipImports,
@ -1534,6 +1536,15 @@ class InScopeCompletionPass extends SimpleAstVisitor<void> {
} else {
type = element.type;
}
} else if (element is PrefixElement) {
var isInstanceCreation =
node.parent?.parent?.parent is InstanceCreationExpression;
declarationHelper(
excludeTypeNames: isInstanceCreation,
mustBeType: !isInstanceCreation,
mustBeNonVoid: isInstanceCreation,
).addDeclarationsThroughImportPrefix(element);
return;
} else if (element is VariableElement) {
type = element.type;
} else {
@ -1720,13 +1731,16 @@ class InScopeCompletionPass extends SimpleAstVisitor<void> {
if (type != null) {
_forMemberAccess(node, node.parent, type);
}
if ((type == null || type.isDartCoreType) &&
if ((type == null || type is InvalidType || type.isDartCoreType) &&
target is Identifier &&
(!node.isCascaded || offset == operator.offset + 1)) {
var element = target.staticElement;
if (element is InterfaceElement || element is ExtensionTypeElement) {
declarationHelper().addStaticMembersOfElement(element!);
}
if (element is PrefixElement) {
declarationHelper().addDeclarationsThroughImportPrefix(element);
}
}
}
}
@ -1835,7 +1849,10 @@ class InScopeCompletionPass extends SimpleAstVisitor<void> {
}
}
_forTypeAnnotation(node);
_forTypeAnnotation(
node,
excludeTypeNames: node.parent?.parent is InstanceCreationExpression,
);
}
@override
@ -1990,11 +2007,17 @@ class InScopeCompletionPass extends SimpleAstVisitor<void> {
var parent = node.parent;
var mustBeAssignable =
parent is AssignmentExpression && node == parent.leftHandSide;
declarationHelper(
mustBeAssignable: mustBeAssignable,
preferNonInvocation: element is InterfaceElement &&
state.request.shouldSuggestTearOff(element),
).addStaticMembersOfElement(element);
if (element is PrefixElement) {
declarationHelper(
mustBeAssignable: mustBeAssignable,
).addDeclarationsThroughImportPrefix(element);
} else {
declarationHelper(
mustBeAssignable: mustBeAssignable,
preferNonInvocation: element is InterfaceElement &&
state.request.shouldSuggestTearOff(element),
).addStaticMembersOfElement(element);
}
}
}
}
@ -2032,13 +2055,16 @@ class InScopeCompletionPass extends SimpleAstVisitor<void> {
_forMemberAccess(node, parent, type,
onlySuper: target is SuperExpression);
}
if ((type == null || type.isDartCoreType) &&
if ((type == null || type is InvalidType || type.isDartCoreType) &&
target is Identifier &&
(!node.isCascaded || offset == operator.offset + 1)) {
var element = target.staticElement;
if (element is InterfaceElement || element is ExtensionTypeElement) {
declarationHelper().addStaticMembersOfElement(element!);
}
if (element is PrefixElement) {
declarationHelper().addDeclarationsThroughImportPrefix(element);
}
}
if (type == null && target is ExtensionOverride) {
declarationHelper().addMembersFromExtensionElement(target.element);
@ -2255,6 +2281,18 @@ class InScopeCompletionPass extends SimpleAstVisitor<void> {
}
var type = node.type;
if (type != null) {
if (type is NamedType) {
if (type.importPrefix case var importPrefix?) {
var prefixElement = importPrefix.element;
if (prefixElement is PrefixElement) {
if (type.name2.coversOffset(offset)) {
declarationHelper(
mustBeType: true,
).addDeclarationsThroughImportPrefix(prefixElement);
}
}
}
}
if (type.beginToken.coversOffset(offset)) {
keywordHelper
.addFormalParameterKeywords(node.parentFormalParameterList);
@ -3261,6 +3299,7 @@ class InScopeCompletionPass extends SimpleAstVisitor<void> {
bool mustBeImplementable = false,
bool mustBeMixable = false,
bool mustBeNonVoid = false,
bool excludeTypeNames = false,
Set<AstNode> excludedNodes = const {},
}) {
if (!(mustBeExtensible || mustBeImplementable || mustBeMixable)) {
@ -3269,11 +3308,24 @@ class InScopeCompletionPass extends SimpleAstVisitor<void> {
keywordHelper.addKeyword(Keyword.VOID);
}
}
if (node is NamedType && node.importPrefix != null) {
// TODO(brianwilkerson): Figure out a better way to handle prefixed
// identifiers.
return;
if (node is NamedType) {
if (node.importPrefix case var importPrefix?) {
var prefixElement = importPrefix.element;
if (prefixElement is PrefixElement) {
declarationHelper(
mustBeExtensible: mustBeExtensible,
mustBeImplementable: mustBeImplementable,
mustBeMixable: mustBeMixable,
mustBeNonVoid: mustBeNonVoid,
excludedNodes: excludedNodes,
excludeTypeNames: excludeTypeNames,
).addDeclarationsThroughImportPrefix(prefixElement);
}
return;
}
}
declarationHelper(
mustBeExtensible: mustBeExtensible,
mustBeImplementable: mustBeImplementable,

View file

@ -1,76 +0,0 @@
// Copyright (c) 2014, the Dart project authors. Please see the AUTHORS file
// for details. All rights reserved. Use of this source code is governed by a
// BSD-style license that can be found in the LICENSE file.
import 'package:analysis_server/src/provisional/completion/dart/completion_dart.dart';
import 'package:analyzer/dart/ast/ast.dart';
import 'package:analyzer/dart/element/element.dart';
import 'package:analyzer/src/util/performance/operation_performance.dart';
/// A contributor that produces suggestions based on the members of a library
/// when the library was imported using a prefix. More concretely, this class
/// produces suggestions for expressions of the form `p.^`, where `p` is a
/// prefix.
class LibraryMemberContributor extends DartCompletionContributor {
LibraryMemberContributor(super.request, super.builder);
@override
Future<void> computeSuggestions({
required OperationPerformanceImpl performance,
}) async {
// Determine if the target looks like a library prefix.
var targetId = request.target.dotTarget;
if (targetId is SimpleIdentifier && !request.target.isCascade) {
var elem = targetId.staticElement;
if (elem is PrefixElement && !elem.isSynthetic) {
var imports = request.libraryElement.libraryImports;
_buildSuggestions(elem, imports);
}
}
}
void _buildSuggestions(
PrefixElement elem, List<LibraryImportElement> imports) {
var containingNode = request.target.containingNode;
var typesOnly =
containingNode is NamedType && containingNode.importPrefix != null;
var isConstructor = containingNode.parent is ConstructorName;
for (var importElem in imports) {
if (importElem.prefix?.element.name == elem.name) {
var library = importElem.importedLibrary;
if (library != null) {
builder.libraryUriStr = library.source.uri.toString();
for (var element in importElem.namespace.definedNames.values) {
if (typesOnly && isConstructor) {
// Suggest constructors from the imported libraries.
if (element is ClassElement) {
for (var constructor in element.constructors) {
if (!constructor.isPrivate) {
if (!element.isAbstract || constructor.isFactory) {
builder.suggestConstructor(constructor);
}
}
}
}
} else {
if (element is InterfaceElement ||
element is ExtensionElement ||
element is TypeAliasElement) {
builder.suggestElement(element);
} else if (!typesOnly &&
(element is FunctionElement ||
element is PropertyAccessorElement)) {
builder.suggestElement(element);
}
}
}
// If the import is `deferred` then suggest `loadLibrary`.
if (!typesOnly && importElem.prefix is DeferredImportElementPrefix) {
builder.suggestLoadLibraryFunction(library.loadLibraryFunction);
}
builder.libraryUriStr = null;
}
}
}
}
}

View file

@ -1811,6 +1811,10 @@ suggestions
kind: class
isNotImported: null
libraryUri: dart:math
Random
kind: constructorInvocation
isNotImported: null
libraryUri: dart:math
''');
}

View file

@ -10882,6 +10882,8 @@ suggestions
kind: topLevelVariable
Y
kind: class
Y
kind: constructorInvocation
m
kind: functionInvocation
''');
@ -10916,6 +10918,8 @@ suggestions
kind: topLevelVariable
Y
kind: class
Y
kind: constructorInvocation
m
kind: functionInvocation
''');
@ -10950,11 +10954,14 @@ suggestions
kind: topLevelVariable
Y
kind: class
Y
kind: constructorInvocation
m
kind: functionInvocation
''');
}
@FailingTest(reason: 'We suggest also the variable')
Future<void> test_library009_4() async {
allowedIdentifiers = {'Y', 'm', 'X'};
newFile('$testPackageLibPath/lib.dart', '''

View file

@ -5287,8 +5287,12 @@ suggestions
kind: topLevelVariable
X0
kind: class
X0
kind: constructorInvocation
Y0
kind: class
Y0
kind: constructorInvocation
''');
}

View file

@ -101,8 +101,12 @@ suggestions
kind: topLevelVariable
X0
kind: class
X0
kind: constructorInvocation
Y0
kind: class
Y0
kind: constructorInvocation
''');
}
@ -120,6 +124,14 @@ foo() {
suggestions
Future
kind: class
Future
kind: constructorInvocation
Future.delayed
kind: constructorInvocation
Future.microtask
kind: constructorInvocation
Future.value
kind: constructorInvocation
''');
}
@ -135,6 +147,14 @@ foo() {
suggestions
Future
kind: class
Future
kind: constructorInvocation
Future.delayed
kind: constructorInvocation
Future.microtask
kind: constructorInvocation
Future.value
kind: constructorInvocation
loadLibrary
kind: functionInvocation
''');
@ -157,6 +177,14 @@ f0() {
suggestions
Future
kind: class
Future
kind: constructorInvocation
Future.delayed
kind: constructorInvocation
Future.microtask
kind: constructorInvocation
Future.value
kind: constructorInvocation
loadLibrary
kind: functionInvocation
''');
@ -184,11 +212,18 @@ class C0 {}
suggestions
A0
kind: class
A0
kind: constructorInvocation
B0
kind: class
B0
kind: constructorInvocation
B1
kind: class
deprecated: true
B1
kind: constructorInvocation
deprecated: true
''');
}
@ -210,6 +245,8 @@ void f() {
suggestions
A0
kind: class
A0
kind: constructorInvocation
''');
}
@ -228,6 +265,8 @@ void f() {
suggestions
A0
kind: class
A0
kind: constructorInvocation
''');
}
@ -257,8 +296,12 @@ suggestions
kind: topLevelVariable
X0
kind: class
X0
kind: constructorInvocation
Y0
kind: class
Y0
kind: constructorInvocation
''');
}
@ -328,6 +371,14 @@ foo() {
suggestions
Future
kind: class
Future
kind: constructorInvocation
Future.delayed
kind: constructorInvocation
Future.microtask
kind: constructorInvocation
Future.value
kind: constructorInvocation
''');
}

View file

@ -6542,8 +6542,12 @@ suggestions
kind: topLevelVariable
X0
kind: class
X0
kind: constructorInvocation
Y0
kind: class
Y0
kind: constructorInvocation
''');
}

View file

@ -3180,8 +3180,12 @@ class C {}
suggestions
A0
kind: class
A0
kind: constructorInvocation
B0
kind: class
B0
kind: constructorInvocation
''');
}
@ -4445,8 +4449,12 @@ suggestions
kind: topLevelVariable
X0
kind: class
X0
kind: constructorInvocation
Y0
kind: class
Y0
kind: constructorInvocation
''');
}