Move generation of suggestions for methods and fields

Change-Id: I84b95c465fc11338326f4591aadae9baac6a2779
Reviewed-on: https://dart-review.googlesource.com/c/sdk/+/334140
Reviewed-by: Konstantin Shcheglov <scheglov@google.com>
Commit-Queue: Brian Wilkerson <brianwilkerson@google.com>
This commit is contained in:
Brian Wilkerson 2023-11-06 21:20:54 +00:00 committed by Commit Queue
parent 2e7149b260
commit 57ece5b452
10 changed files with 572 additions and 117 deletions

View file

@ -2,6 +2,8 @@
// 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/protocol_server.dart'
show CompletionSuggestionKind;
import 'package:analysis_server/src/services/completion/dart/suggestion_builder.dart';
import 'package:analyzer/dart/ast/ast.dart';
import 'package:analyzer/dart/ast/token.dart';
@ -21,6 +23,49 @@ sealed class CandidateSuggestion {
String get completion;
}
/// The information about a candidate suggestion based on a constructor.
final class ConstructorSuggestion extends CandidateSuggestion {
/// The element on which the suggestion is based.
final ConstructorElement element;
/// Initialize a newly created candidate suggestion to suggest the [element].
ConstructorSuggestion(this.element);
@override
String get completion => element.displayName;
}
/// The information about a candidate suggestion based on an executable element,
/// either a method or function.
sealed class ExecutableSuggestion extends CandidateSuggestion {
/// The kind of suggestion to be made, either
/// [CompletionSuggestionKind.IDENTIFIER] or
/// [CompletionSuggestionKind.INVOCATION].
final CompletionSuggestionKind kind;
/// Initialize a newly created suggestion to use the given [kind] of
/// suggestion.
ExecutableSuggestion(this.kind)
: assert(kind == CompletionSuggestionKind.IDENTIFIER ||
kind == CompletionSuggestionKind.INVOCATION);
}
/// The information about a candidate suggestion based on a field.
final class FieldSuggestion extends CandidateSuggestion {
/// The element on which the suggestion is based.
final FieldElement element;
/// The class from which the field is being referenced, or `null` if the class
/// is not being referenced from within a class.
final ClassElement? referencingClass;
/// Initialize a newly created candidate suggestion to suggest the [element].
FieldSuggestion(this.element, this.referencingClass);
@override
String get completion => element.name;
}
/// The information about a candidate suggestion based on a formal parameter.
final class FormalParameterSuggestion extends CandidateSuggestion {
/// The element on which the suggestion is based.
@ -33,7 +78,8 @@ final class FormalParameterSuggestion extends CandidateSuggestion {
String get completion => element.name;
}
/// The information about a candidate suggestion based on an identifier.
/// The information about a candidate suggestion based on an identifier being
/// guessed for a declaration site.
final class IdentifierSuggestion extends CandidateSuggestion {
/// The identifier to be inserted.
final String identifier;
@ -118,12 +164,12 @@ final class LabelSuggestion extends CandidateSuggestion {
}
/// The information about a candidate suggestion based on a local function.
final class LocalFunctionSuggestion extends CandidateSuggestion {
final class LocalFunctionSuggestion extends ExecutableSuggestion {
/// The element on which the suggestion is based.
final FunctionElement element;
/// Initialize a newly created candidate suggestion to suggest the [element].
LocalFunctionSuggestion(this.element);
LocalFunctionSuggestion(super.kind, this.element);
@override
String get completion => element.name;
@ -145,6 +191,34 @@ final class LocalVariableSuggestion extends CandidateSuggestion {
String get completion => element.name;
}
/// The information about a candidate suggestion based on a method.
final class MethodSuggestion extends ExecutableSuggestion {
/// The element on which the suggestion is based.
final MethodElement element;
final ClassElement? referencingClass;
/// Initialize a newly created candidate suggestion to suggest the [element].
MethodSuggestion(super.kind, this.element, this.referencingClass);
@override
String get completion => element.name;
}
/// The information about a candidate suggestion based on a method.
final class PropertyAccessSuggestion extends CandidateSuggestion {
/// The element on which the suggestion is based.
final PropertyAccessorElement element;
final ClassElement? referencingClass;
/// Initialize a newly created candidate suggestion to suggest the [element].
PropertyAccessSuggestion(this.element, this.referencingClass);
@override
String get completion => element.name;
}
extension SuggestionBuilderExtension on SuggestionBuilder {
// TODO(brianwilkerson) Move these to `SuggestionBuilder`, possibly as part
// of splitting it into a legacy builder and an LSP builder.
@ -152,6 +226,15 @@ extension SuggestionBuilderExtension on SuggestionBuilder {
/// Add a suggestion based on the candidate [suggestion].
void suggestFromCandidate(CandidateSuggestion suggestion) {
switch (suggestion) {
case ConstructorSuggestion():
suggestConstructor(suggestion.element);
case FieldSuggestion():
suggestField(suggestion.element,
inheritanceDistance: _inheritanceDistance(
suggestion.referencingClass,
suggestion.element.enclosingElement));
case FormalParameterSuggestion():
suggestParameter(suggestion.element);
case IdentifierSuggestion():
suggestName(suggestion.identifier);
case KeywordSuggestion():
@ -165,8 +248,31 @@ extension SuggestionBuilderExtension on SuggestionBuilder {
// TODO(brianwilkerson) Enhance `suggestLocalVariable` to allow the
// distance to be passed in.
suggestLocalVariable(suggestion.element);
case FormalParameterSuggestion():
suggestParameter(suggestion.element);
case MethodSuggestion():
// TODO(brianwilkerson) Correctly set the kind of suggestion in cases
// where `isFunctionalArgument` would return `true` so we can stop
// using the `request.target`.
var kind = request.target.isFunctionalArgument()
? CompletionSuggestionKind.IDENTIFIER
: suggestion.kind;
suggestMethod(
suggestion.element,
kind: kind,
inheritanceDistance: _inheritanceDistance(
suggestion.referencingClass, suggestion.element.enclosingElement),
);
case PropertyAccessSuggestion():
var inheritanceDistance = 0.0;
var referencingClass = suggestion.referencingClass;
var declaringClass = suggestion.element.enclosingElement;
if (referencingClass != null && declaringClass is InterfaceElement) {
inheritanceDistance = request.featureComputer
.inheritanceDistanceFeature(referencingClass, declaringClass);
}
suggestAccessor(
suggestion.element,
inheritanceDistance: inheritanceDistance,
);
}
}
@ -176,4 +282,16 @@ extension SuggestionBuilderExtension on SuggestionBuilder {
suggestFromCandidate(suggestion);
}
}
/// Returns the inheritance distance from the [referencingClass] to the
/// [declaringClass].
double _inheritanceDistance(
ClassElement? referencingClass, Element? declaringClass) {
var distance = 0.0;
if (referencingClass != null && declaringClass is InterfaceElement) {
distance = request.featureComputer
.inheritanceDistanceFeature(referencingClass, declaringClass);
}
return distance;
}
}

View file

@ -2,6 +2,8 @@
// 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/protocol_server.dart'
show CompletionSuggestionKind;
import 'package:analysis_server/src/services/completion/dart/candidate_suggestion.dart';
import 'package:analysis_server/src/services/completion/dart/suggestion_collector.dart';
import 'package:analysis_server/src/services/completion/dart/visibility_tracker.dart';
@ -28,33 +30,109 @@ class DeclarationHelper {
/// A flag indicating whether suggestions should be limited to only include
/// valid constants.
bool mustBeConstant = false;
final bool mustBeConstant;
/// A flag indicating whether suggestions should be limited to only include
/// static members.
final bool mustBeStatic;
/// A flag indicating whether suggestions should be limited to only include
/// methods with a non-`void` return type.
final bool mustBeNonVoid;
/// A flag indicating whether suggestions should be tear-offs rather than
/// invocations where possible.
final bool preferNonInvocation;
/// The number of local variables that have already been suggested.
int _variableDistance = 0;
/// Initialize a newly created helper to add suggestions to the [collector].
DeclarationHelper({required this.collector, required this.offset});
/// Initialize a newly created helper to add suggestions to the [collector]
/// that are appropriate for the location at the [offset].
///
/// The flags [mustBeConstant], [mustBeStatic] and [mustBeNonVoid] are used to
/// control which declarations are suggested. The flag [preferNonInvocation]
/// is used to control what kind of suggestion is made for executable
/// elements.
DeclarationHelper(
{required this.collector,
required this.offset,
required this.mustBeConstant,
required this.mustBeStatic,
required this.mustBeNonVoid,
required this.preferNonInvocation});
/// Return the suggestion kind that should be used for executable elements.
CompletionSuggestionKind get _executableSuggestionKind => preferNonInvocation
? CompletionSuggestionKind.IDENTIFIER
: CompletionSuggestionKind.INVOCATION;
/// Add any fields that can be initialized in the initializer list of the
/// given [constructor].
void addFieldsForInitializers(ConstructorDeclaration constructor) {
var containingElement = constructor.declaredElement?.enclosingElement;
if (containingElement == null) {
return;
}
var fieldsToSkip = <FieldElement>{};
// Skip fields that are already initialized in the initializer list.
for (var initializer in constructor.initializers) {
if (initializer is ConstructorFieldInitializer) {
var fieldElement = initializer.fieldName.staticElement;
if (fieldElement is FieldElement) {
fieldsToSkip.add(fieldElement);
}
}
}
// Skip fields that are already initialized in the parameter list.
for (var parameter in constructor.parameters.parameters) {
if (parameter is FieldFormalParameter) {
var parameterElement = parameter.declaredElement;
if (parameterElement is FieldFormalParameterElement) {
var field = parameterElement.field;
if (field != null) {
fieldsToSkip.add(field);
}
}
}
}
for (var field in containingElement.fields) {
// Skip fields that are already initialized at their declaration.
if (!fieldsToSkip.contains(field) && !field.hasInitializer) {
_suggestField(field, containingElement);
}
}
}
/// Add any declarations that are visible at the completion location,
/// given that the completion location is within the [node]. This includes
/// local variables, local functions, and parameters. If [mustBeConstant] is `true`, then
/// only constants will be suggested.
void addLexicalDeclarations(AstNode node, {bool mustBeConstant = false}) {
this.mustBeConstant = mustBeConstant;
_addLocalDeclarations(node);
/// local variables, local functions, and parameters.
void addLexicalDeclarations(AstNode node) {
var containingMember = _addLocalDeclarations(node);
if (containingMember == null) {
return;
}
var parent = containingMember.parent;
_addMembersOf(parent, containingMember);
}
void addMembersOfType(DartType type) {
// TODO(brianwilkerson) Implement this.
}
/// Add any local declarations that are visible at the completion location,
/// given that the completion location is within the [node]. This includes
/// local variables, local functions, and parameters. Return the member
/// containing the local declarations that were added, or `null` if there is
/// an error such as the AST being malformed or we encountered an AST
/// Add suggestions for any local declarations that are visible at the
/// completion location, given that the completion location is within the
/// [node].
///
/// This includes local variables, local functions, and parameters. Return the
/// member containing the local declarations that were added, or `null` if
/// there is an error such as the AST being malformed or we encountered an AST
/// structure that isn't handled correctly.
///
/// The returned member can be either a [ClassMember] or a
/// [CompilationUnitMember].
AstNode? _addLocalDeclarations(AstNode node) {
AstNode? previousNode;
AstNode? currentNode = node;
@ -115,21 +193,140 @@ class DeclarationHelper {
return currentNode;
}
/// Add suggestions for the [members] of the [containingElement].
void _addMembers(Element containingElement, NodeList<ClassMember> members) {
for (var member in members) {
switch (member) {
case ConstructorDeclaration():
// Constructors are suggested when the enclosing class is suggested.
break;
case FieldDeclaration():
if (mustBeStatic && !member.isStatic) {
continue;
}
for (var field in member.fields.variables) {
var declaredElement = field.declaredElement;
if (declaredElement is FieldElement) {
_suggestField(declaredElement, containingElement);
}
}
case MethodDeclaration():
if (mustBeStatic && !member.isStatic) {
continue;
}
var declaredElement = member.declaredElement;
if (declaredElement is MethodElement) {
_suggestMethod(declaredElement, containingElement);
} else if (declaredElement is PropertyAccessorElement) {
_suggestProperty(declaredElement, containingElement);
}
}
}
}
/// Add suggestions for any members of the [parent].
///
/// The [parent] is expected to be either a [CompilationUnitMember] or a
/// [CompilationUnit]. The [containingMember] is the member within the
/// [parent] in which completion was requested.
void _addMembersOf(AstNode? parent, AstNode containingMember) {
while (parent != null) {
switch (parent) {
case ClassDeclaration():
var classElement = parent.declaredElement;
if (classElement != null) {
_addMembers(classElement, parent.members);
}
case CompilationUnit():
var library = parent.declaredElement?.library;
if (library != null) {
_addTopLevelDeclarations(library);
}
case EnumDeclaration():
var enumElement = parent.declaredElement;
if (enumElement != null) {
_addMembers(enumElement, parent.members);
}
case ExtensionDeclaration():
var extensionElement = parent.declaredElement;
if (extensionElement != null) {
_addMembers(extensionElement, parent.members);
}
case ExtensionTypeDeclaration():
var extensionTypeElement = parent.declaredElement;
if (extensionTypeElement != null) {
_addMembers(extensionTypeElement, parent.members);
}
case MixinDeclaration():
var mixinElement = parent.declaredElement;
if (mixinElement != null) {
_addMembers(mixinElement, parent.members);
}
}
parent = parent.parent;
}
}
/// Add suggestions for any top-level declarations that are visible within the
/// [library].
void _addTopLevelDeclarations(LibraryElement library) {
// TODO(brianwilkerson) Implement this.
// for (var unit in library.units) {
// for (var element in unit.accessors) {}
// for (var element in unit.classes) {}
// for (var element in unit.enums) {}
// for (var element in unit.extensions) {}
// for (var element in unit.extensionTypes) {}
// for (var element in unit.functions) {}
// for (var element in unit.mixins) {}
// for (var element in unit.topLevelVariables) {}
// for (var element in unit.typeAliases) {}
// }
// for (var importedLibrary in library.importedLibraries) {}
}
/// Return `true` if the [identifier] is composed of one or more underscore
/// characters and nothing else.
bool _isUnused(String identifier) => UnusedIdentifier.hasMatch(identifier);
/// Add a suggestion for the field represented by the [element] contained
/// in the [containingElement].
void _suggestField(FieldElement element, Element containingElement) {
if (mustBeConstant && !element.isConst) {
return;
}
if (visibilityTracker.isVisible(element)) {
var suggestion = FieldSuggestion(element,
(containingElement is ClassElement) ? containingElement : null);
collector.addSuggestion(suggestion);
}
}
/// Add a suggestion for the local function represented by the [element].
void _suggestFunction(ExecutableElement element) {
if (element is FunctionElement && visibilityTracker.isVisible(element)) {
var suggestion = LocalFunctionSuggestion(element);
var suggestion =
LocalFunctionSuggestion(_executableSuggestionKind, element);
collector.addSuggestion(suggestion);
}
}
/// Add a suggestion for the method represented by the [element] contained
/// in the [containingElement].
void _suggestMethod(MethodElement element, Element containingElement) {
if (mustBeConstant) {
return;
}
if (visibilityTracker.isVisible(element)) {
var suggestion = MethodSuggestion(_executableSuggestionKind, element,
(containingElement is ClassElement) ? containingElement : null);
collector.addSuggestion(suggestion);
}
}
/// Add a suggestion for the parameter represented by the [element].
void _suggestParameter(ParameterElement element) {
if (mustBeConstant && !element.isConst) {
if (mustBeConstant) {
return;
}
if (visibilityTracker.isVisible(element) && !_isUnused(element.name)) {
@ -138,6 +335,20 @@ class DeclarationHelper {
}
}
/// Add a suggestion for the getter or setter represented by the [element]
/// contained in the [containingElement].
void _suggestProperty(
PropertyAccessorElement element, Element containingElement) {
if (mustBeConstant) {
return;
}
if (visibilityTracker.isVisible(element)) {
var suggestion = PropertyAccessSuggestion(element,
(containingElement is ClassElement) ? containingElement : null);
collector.addSuggestion(suggestion);
}
}
/// Add a suggestion for the local variable represented by the [element].
void _suggestVariable(LocalVariableElement element) {
if (mustBeConstant && !element.isConst) {
@ -162,7 +373,8 @@ class DeclarationHelper {
}
AstNode? _visitCommentReference(CommentReference node) {
var member = node.parent?.parent;
var comment = node.parent;
var member = comment?.parent;
switch (member) {
case ConstructorDeclaration():
_visitParameterList(member.parameters);
@ -173,7 +385,7 @@ class DeclarationHelper {
case MethodDeclaration():
_visitParameterList(member.parameters);
}
return member;
return comment;
}
void _visitDeclaredVariablePattern(DeclaredVariablePattern pattern) {

View file

@ -100,8 +100,27 @@ class InScopeCompletionPass extends SimpleAstVisitor<void> {
}
/// Return the helper used to suggest declarations that are in scope.
DeclarationHelper declarationHelper() => _declarationHelper =
DeclarationHelper(collector: collector, offset: offset);
DeclarationHelper declarationHelper(
{bool mustBeConstant = false,
bool mustBeStatic = false,
bool preferNonInvocation = false}) {
// Ensure that we aren't attempting to create multiple declaration helpers
// with inconsistent states.
assert(() {
var helper = _declarationHelper;
return helper == null ||
(helper.mustBeConstant == mustBeConstant &&
helper.mustBeStatic == mustBeStatic &&
helper.preferNonInvocation == preferNonInvocation);
}());
return _declarationHelper ??= DeclarationHelper(
collector: collector,
offset: offset,
mustBeConstant: mustBeConstant,
mustBeStatic: mustBeStatic,
mustBeNonVoid: false,
preferNonInvocation: preferNonInvocation);
}
@override
void visitAdjacentStrings(AdjacentStrings node) {
@ -162,7 +181,7 @@ class InScopeCompletionPass extends SimpleAstVisitor<void> {
void visitAssertInitializer(AssertInitializer node) {
collector.completionLocation = 'ConstructorDeclaration_initializer';
keywordHelper.addConstructorInitializerKeywords(
node.parent as ConstructorDeclaration);
node.parent as ConstructorDeclaration, node);
}
@override
@ -303,7 +322,7 @@ class InScopeCompletionPass extends SimpleAstVisitor<void> {
@override
void visitCommentReference(CommentReference node) {
declarationHelper().addLexicalDeclarations(node);
declarationHelper(preferNonInvocation: true).addLexicalDeclarations(node);
}
@override
@ -341,10 +360,14 @@ class InScopeCompletionPass extends SimpleAstVisitor<void> {
@override
void visitConstructorDeclaration(ConstructorDeclaration node) {
var separator = node.separator;
if (separator != null) {
if (separator == null) {
return;
}
var type = separator.type;
if (type == TokenType.COLON) {
if (offset >= separator.end && offset <= node.body.offset) {
collector.completionLocation = 'ConstructorDeclaration_initializer';
keywordHelper.addConstructorInitializerKeywords(node);
_forConstructorInitializer(node, null);
}
}
}
@ -352,8 +375,8 @@ class InScopeCompletionPass extends SimpleAstVisitor<void> {
@override
void visitConstructorFieldInitializer(ConstructorFieldInitializer node) {
collector.completionLocation = 'ConstructorDeclaration_initializer';
keywordHelper.addConstructorInitializerKeywords(
node.parent as ConstructorDeclaration);
var constructor = node.parent as ConstructorDeclaration;
_forConstructorInitializer(constructor, node);
}
@override
@ -407,6 +430,13 @@ class InScopeCompletionPass extends SimpleAstVisitor<void> {
void visitDoStatement(DoStatement node) {
if (offset <= node.doKeyword.end) {
_forStatement(node);
} else if (node.leftParenthesis.end <= offset &&
offset <= node.rightParenthesis.offset) {
if (node.condition.isSynthetic ||
offset <= node.condition.offset ||
offset == node.condition.end) {
_forExpression(node);
}
}
}
@ -837,7 +867,8 @@ class InScopeCompletionPass extends SimpleAstVisitor<void> {
@override
void visitInterpolationExpression(InterpolationExpression node) {
declarationHelper().addLexicalDeclarations(node);
declarationHelper(mustBeStatic: node.inStaticContext)
.addLexicalDeclarations(node);
}
@override
@ -1092,7 +1123,7 @@ class InScopeCompletionPass extends SimpleAstVisitor<void> {
RedirectingConstructorInvocation node) {
collector.completionLocation = 'ConstructorDeclaration_initializer';
keywordHelper.addConstructorInitializerKeywords(
node.parent as ConstructorDeclaration);
node.parent as ConstructorDeclaration, node);
}
@override
@ -1184,7 +1215,7 @@ class InScopeCompletionPass extends SimpleAstVisitor<void> {
void visitSuperConstructorInvocation(SuperConstructorInvocation node) {
collector.completionLocation = 'ConstructorDeclaration_initializer';
keywordHelper.addConstructorInitializerKeywords(
node.parent as ConstructorDeclaration);
node.parent as ConstructorDeclaration, node);
}
@override
@ -1494,6 +1525,13 @@ class InScopeCompletionPass extends SimpleAstVisitor<void> {
void visitWhileStatement(WhileStatement node) {
if (offset <= node.whileKeyword.end) {
_forStatement(node);
} else if (node.leftParenthesis.end <= offset &&
offset <= node.rightParenthesis.offset) {
if (node.condition.isSynthetic ||
offset <= node.condition.offset ||
offset == node.condition.end) {
_forExpression(node);
}
}
}
@ -1520,7 +1558,8 @@ class InScopeCompletionPass extends SimpleAstVisitor<void> {
TypedLiteral literal, NodeList<CollectionElement> elements) {
var preceedingElement = elements.elementBefore(offset);
keywordHelper.addCollectionElementKeywords(literal, elements);
declarationHelper().addLexicalDeclarations(preceedingElement ?? literal);
declarationHelper(mustBeStatic: literal.inStaticContext)
.addLexicalDeclarations(preceedingElement ?? literal);
}
/// Add the suggestions that are appropriate when the selection is at the
@ -1529,6 +1568,8 @@ class InScopeCompletionPass extends SimpleAstVisitor<void> {
keywordHelper.addCompilationUnitDeclarationKeywords();
}
/// Add the suggestions that are appropriate when the selection is at the
/// beginning of a member at the top-level of a compilation unit.
void _forCompilationUnitMember(CompilationUnit unit,
({AstNode? before, AstNode? after}) surroundingMembers) {
var before = surroundingMembers.before;
@ -1547,7 +1588,16 @@ class InScopeCompletionPass extends SimpleAstVisitor<void> {
var inConstantContext = node is Expression && node.inConstantContext;
keywordHelper.addConstantExpressionKeywords(
inConstantContext: inConstantContext);
declarationHelper().addLexicalDeclarations(node, mustBeConstant: true);
declarationHelper(mustBeConstant: true, mustBeStatic: node.inStaticContext)
.addLexicalDeclarations(node);
}
/// Add the suggestions that are appropriate when the selection is at the
/// beginning of a constructor's initializer.
void _forConstructorInitializer(ConstructorDeclaration constructor,
ConstructorFieldInitializer? initializer) {
keywordHelper.addConstructorInitializerKeywords(constructor, initializer);
declarationHelper().addFieldsForInitializers(constructor);
}
/// Add the suggestions that are appropriate when the selection is at the
@ -1569,7 +1619,8 @@ class InScopeCompletionPass extends SimpleAstVisitor<void> {
void _forExpression(AstNode? node) {
keywordHelper.addExpressionKeywords(node);
if (node != null) {
declarationHelper().addLexicalDeclarations(node);
declarationHelper(mustBeStatic: node.inStaticContext)
.addLexicalDeclarations(node);
}
}
@ -1660,8 +1711,9 @@ class InScopeCompletionPass extends SimpleAstVisitor<void> {
/// beginning of a pattern.
void _forPattern(AstNode node, {bool mustBeConst = true}) {
keywordHelper.addPatternKeywords();
declarationHelper()
.addLexicalDeclarations(node, mustBeConstant: mustBeConst);
declarationHelper(
mustBeConstant: mustBeConst, mustBeStatic: node.inStaticContext)
.addLexicalDeclarations(node);
}
/// Add the suggestions that are appropriate when the selection is at the
@ -1813,7 +1865,8 @@ class InScopeCompletionPass extends SimpleAstVisitor<void> {
}
} else if (!node.inKeyword.isSynthetic) {
keywordHelper.addKeyword(Keyword.AWAIT);
declarationHelper().addLexicalDeclarations(node);
declarationHelper(mustBeStatic: node.inStaticContext)
.addLexicalDeclarations(node);
}
}
@ -1827,6 +1880,18 @@ class InScopeCompletionPass extends SimpleAstVisitor<void> {
}
extension on AstNode {
/// Whether the completion location is in a context where only static members
/// of the enclosing type can be suggested.
bool get inStaticContext {
var encosingMember = thisOrAncestorOfType<ClassMember>();
if (encosingMember is MethodDeclaration) {
return encosingMember.isStatic;
} else if (encosingMember is FieldDeclaration) {
return encosingMember.isStatic;
}
return false;
}
/// Whether all of the tokens in this node are synthetic.
bool get isFullySynthetic {
var current = beginToken;

View file

@ -174,28 +174,26 @@ class KeywordHelper {
}
/// Add the keywords that are appropriate when the selection is in the
/// initializer list of the given [node].
void addConstructorInitializerKeywords(ConstructorDeclaration node) {
/// [initializer] list of the given [constructor].
void addConstructorInitializerKeywords(
ConstructorDeclaration constructor, ConstructorInitializer? initializer) {
addKeyword(Keyword.ASSERT);
var suggestSuper = node.parent is! ExtensionTypeDeclaration;
var initializers = node.initializers;
if (initializers.isNotEmpty) {
var initializers = constructor.initializers;
if (initializer == null || initializers.last == initializer) {
var last = initializers.lastNonSynthetic;
if (offset >= last.end &&
last is! SuperConstructorInvocation &&
last is! RedirectingConstructorInvocation) {
if (suggestSuper) {
if (constructor.parent is! ExtensionTypeDeclaration) {
addKeyword(Keyword.SUPER);
}
addKeyword(Keyword.THIS);
}
} else {
// if (separator.end <= offset && offset <= separator.next!.offset) {
if (suggestSuper) {
addKeyword(Keyword.SUPER);
} else if (initializer is ConstructorFieldInitializer) {
var equals = initializer.equals;
if (equals.end <= offset && offset <= equals.next!.offset) {
addKeyword(Keyword.THIS);
}
addKeyword(Keyword.THIS);
// }
}
}

View file

@ -253,29 +253,6 @@ class _LocalVisitor extends LocalDeclarationVisitor {
}
}
@override
void declaredField(FieldDeclaration fieldDecl, VariableDeclaration varDecl) {
var field = varDecl.declaredElement;
if (field is FieldElement &&
((visibilityTracker.isVisible(field) &&
opType.includeReturnValueSuggestions &&
(!opType.inStaticMethodBody || fieldDecl.isStatic)) ||
suggestLocalFields)) {
var inheritanceDistance = 0.0;
var enclosingClass = request.target.containingNode
.thisOrAncestorOfType<ClassDeclaration>();
var enclosingElement = enclosingClass?.declaredElement;
if (enclosingElement != null) {
var enclosingElement = field.enclosingElement;
if (enclosingElement is InterfaceElement) {
inheritanceDistance = request.featureComputer
.inheritanceDistanceFeature(enclosingElement, enclosingElement);
}
}
builder.suggestField(field, inheritanceDistance: inheritanceDistance);
}
}
@override
void declaredFunction(FunctionDeclaration declaration) {
if (visibilityTracker.isVisible(declaration.declaredElement) &&
@ -317,39 +294,6 @@ class _LocalVisitor extends LocalDeclarationVisitor {
}
}
@override
void declaredLabel(Label label, bool isCaseLabel) {
// ignored: handled by the label_contributor.dart
}
@override
void declaredMethod(MethodDeclaration declaration) {
var element = declaration.declaredElement;
if (visibilityTracker.isVisible(element) &&
(opType.includeReturnValueSuggestions ||
opType.includeVoidReturnSuggestions) &&
(!opType.inStaticMethodBody || declaration.isStatic)) {
var inheritanceDistance = 0.0;
var enclosingClass = request.target.containingNode
.thisOrAncestorOfType<ClassDeclaration>();
if (enclosingClass != null) {
var enclosingElement = element?.enclosingElement;
if (enclosingElement is InterfaceElement) {
inheritanceDistance = request.featureComputer
.inheritanceDistanceFeature(
enclosingClass.declaredElement!, enclosingElement);
}
}
if (element is MethodElement) {
builder.suggestMethod(element,
inheritanceDistance: inheritanceDistance, kind: _defaultKind);
} else if (element is PropertyAccessorElement) {
builder.suggestAccessor(element,
inheritanceDistance: inheritanceDistance);
}
}
}
@override
void declaredMixin(MixinDeclaration declaration) {
var declaredElement = declaration.declaredElement;

View file

@ -1210,6 +1210,18 @@ class Q {
replacement
right: 1
suggestions
false
kind: keyword
null
kind: keyword
super
kind: keyword
switch
kind: keyword
this
kind: keyword
true
kind: keyword
x
kind: field
''');
@ -1238,6 +1250,18 @@ class Q {
replacement
right: 1
suggestions
false
kind: keyword
null
kind: keyword
super
kind: keyword
switch
kind: keyword
this
kind: keyword
true
kind: keyword
x
kind: field
''');
@ -13590,13 +13614,35 @@ suggestions
await computeSuggestions('''
class Foo { int boo = 7; mth() { while (b^) {} }}
''');
assertResponse(r'''
if (isProtocolVersion2) {
assertResponse(r'''
replacement
left: 1
suggestions
boo
kind: field
''');
} else {
assertResponse(r'''
replacement
left: 1
suggestions
boo
kind: field
false
kind: keyword
null
kind: keyword
super
kind: keyword
switch
kind: keyword
this
kind: keyword
true
kind: keyword
''');
}
}
Future<void> test_export_ignoreIfThisLibraryExports_1() async {

View file

@ -47,7 +47,6 @@ suggestions
''');
}
@failingTest
Future<void> test_inside_implicitThis_getter() async {
await computeSuggestions('''
enum E {
@ -69,7 +68,6 @@ suggestions
''');
}
@failingTest
Future<void> test_inside_implicitThis_method() async {
await computeSuggestions('''
enum E {

View file

@ -7753,10 +7753,6 @@ suggestions
kind: class
B0
kind: constructorInvocation
a0
kind: field
b0
kind: methodInvocation
f0
kind: field
''');

View file

@ -30,23 +30,102 @@ class ConstructorDeclarationTest2 extends AbstractCompletionDriverTest
mixin ConstructorDeclarationTestCases on AbstractCompletionDriverTest {
Future<void> test_initializers_beforeInitializer() async {
await computeSuggestions('''
class A { int f; A() : ^, f = 1; }
class A {
int f0;
int f1;
A() : ^, f1 = 1;
}
''');
assertResponse(r'''
suggestions
assert
kind: keyword
f0
kind: field
this
kind: keyword
''');
}
Future<void> test_initializers_first() async {
await computeSuggestions('''
class A { A() : ^; }
class A {
int f0;
int f1;
A() : ^;
}
''');
assertResponse(r'''
suggestions
assert
kind: keyword
f0
kind: field
f1
kind: field
super
kind: keyword
this
kind: keyword
''');
}
Future<void> test_initializers_last() async {
await computeSuggestions('''
class A {
int f0;
int f1;
A() : f0 = 1, ^;
}
''');
assertResponse(r'''
suggestions
assert
kind: keyword
f1
kind: field
super
kind: keyword
this
kind: keyword
''');
}
Future<void> test_initializers_withDeclarationInitializer() async {
await computeSuggestions('''
class A {
int f0 = 0;
int f1;
A() : ^;
}
''');
assertResponse(r'''
suggestions
assert
kind: keyword
f1
kind: field
super
kind: keyword
this
kind: keyword
''');
}
Future<void> test_initializers_withFieldFormalInitializer() async {
await computeSuggestions('''
class A {
int f0;
int f1;
A(this.f0) : ^;
}
''');
assertResponse(r'''
suggestions
assert
kind: keyword
f1
kind: field
super
kind: keyword
this

View file

@ -483,7 +483,6 @@ class C {
@reflectiveTest
class RedirectedConstructorCompletionTest extends CompletionTestCase {
@failingTest
Future<void> test_keywords() async {
await getTestCodeSuggestions('''
class A {