diff --git a/pkg/analysis_server/lib/src/services/completion/dart/candidate_suggestion.dart b/pkg/analysis_server/lib/src/services/completion/dart/candidate_suggestion.dart index 1b0822553d1..4a7efe47b69 100644 --- a/pkg/analysis_server/lib/src/services/completion/dart/candidate_suggestion.dart +++ b/pkg/analysis_server/lib/src/services/completion/dart/candidate_suggestion.dart @@ -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; + } } diff --git a/pkg/analysis_server/lib/src/services/completion/dart/declaration_helper.dart b/pkg/analysis_server/lib/src/services/completion/dart/declaration_helper.dart index f7de3f46914..7bedc53218a 100644 --- a/pkg/analysis_server/lib/src/services/completion/dart/declaration_helper.dart +++ b/pkg/analysis_server/lib/src/services/completion/dart/declaration_helper.dart @@ -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 = {}; + // 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 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) { diff --git a/pkg/analysis_server/lib/src/services/completion/dart/in_scope_completion_pass.dart b/pkg/analysis_server/lib/src/services/completion/dart/in_scope_completion_pass.dart index 3824c884adb..889c9153ea6 100644 --- a/pkg/analysis_server/lib/src/services/completion/dart/in_scope_completion_pass.dart +++ b/pkg/analysis_server/lib/src/services/completion/dart/in_scope_completion_pass.dart @@ -100,8 +100,27 @@ class InScopeCompletionPass extends SimpleAstVisitor { } /// 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 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 { @override void visitCommentReference(CommentReference node) { - declarationHelper().addLexicalDeclarations(node); + declarationHelper(preferNonInvocation: true).addLexicalDeclarations(node); } @override @@ -341,10 +360,14 @@ class InScopeCompletionPass extends SimpleAstVisitor { @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 { @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 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 { @override void visitInterpolationExpression(InterpolationExpression node) { - declarationHelper().addLexicalDeclarations(node); + declarationHelper(mustBeStatic: node.inStaticContext) + .addLexicalDeclarations(node); } @override @@ -1092,7 +1123,7 @@ class InScopeCompletionPass extends SimpleAstVisitor { 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 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 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 { TypedLiteral literal, NodeList 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 { 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 { 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 _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 { /// 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 { } } 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 { } 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(); + 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; diff --git a/pkg/analysis_server/lib/src/services/completion/dart/keyword_helper.dart b/pkg/analysis_server/lib/src/services/completion/dart/keyword_helper.dart index f55f057a745..74abd46ad96 100644 --- a/pkg/analysis_server/lib/src/services/completion/dart/keyword_helper.dart +++ b/pkg/analysis_server/lib/src/services/completion/dart/keyword_helper.dart @@ -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); - // } } } diff --git a/pkg/analysis_server/lib/src/services/completion/dart/local_reference_contributor.dart b/pkg/analysis_server/lib/src/services/completion/dart/local_reference_contributor.dart index 537758326a9..3824ab43107 100644 --- a/pkg/analysis_server/lib/src/services/completion/dart/local_reference_contributor.dart +++ b/pkg/analysis_server/lib/src/services/completion/dart/local_reference_contributor.dart @@ -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(); - 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(); - 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; diff --git a/pkg/analysis_server/test/services/completion/dart/completion_test.dart b/pkg/analysis_server/test/services/completion/dart/completion_test.dart index 61146465f4f..9a7a3b12523 100644 --- a/pkg/analysis_server/test/services/completion/dart/completion_test.dart +++ b/pkg/analysis_server/test/services/completion/dart/completion_test.dart @@ -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 test_export_ignoreIfThisLibraryExports_1() async { diff --git a/pkg/analysis_server/test/services/completion/dart/declaration/enum_test.dart b/pkg/analysis_server/test/services/completion/dart/declaration/enum_test.dart index 9f29b1c7134..b4a0b691726 100644 --- a/pkg/analysis_server/test/services/completion/dart/declaration/enum_test.dart +++ b/pkg/analysis_server/test/services/completion/dart/declaration/enum_test.dart @@ -47,7 +47,6 @@ suggestions '''); } - @failingTest Future test_inside_implicitThis_getter() async { await computeSuggestions(''' enum E { @@ -69,7 +68,6 @@ suggestions '''); } - @failingTest Future test_inside_implicitThis_method() async { await computeSuggestions(''' enum E { diff --git a/pkg/analysis_server/test/services/completion/dart/declaration/local_reference_test.dart b/pkg/analysis_server/test/services/completion/dart/declaration/local_reference_test.dart index 5cb90eae914..02ee020e12f 100644 --- a/pkg/analysis_server/test/services/completion/dart/declaration/local_reference_test.dart +++ b/pkg/analysis_server/test/services/completion/dart/declaration/local_reference_test.dart @@ -7753,10 +7753,6 @@ suggestions kind: class B0 kind: constructorInvocation - a0 - kind: field - b0 - kind: methodInvocation f0 kind: field '''); diff --git a/pkg/analysis_server/test/services/completion/dart/location/constructor_declaration_test.dart b/pkg/analysis_server/test/services/completion/dart/location/constructor_declaration_test.dart index aa58744dd8d..387c7bbb8e0 100644 --- a/pkg/analysis_server/test/services/completion/dart/location/constructor_declaration_test.dart +++ b/pkg/analysis_server/test/services/completion/dart/location/constructor_declaration_test.dart @@ -30,23 +30,102 @@ class ConstructorDeclarationTest2 extends AbstractCompletionDriverTest mixin ConstructorDeclarationTestCases on AbstractCompletionDriverTest { Future 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 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 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 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 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 diff --git a/pkg/analysis_server/test/src/services/completion/dart/completion_test.dart b/pkg/analysis_server/test/src/services/completion/dart/completion_test.dart index 02729db5403..6233311612c 100644 --- a/pkg/analysis_server/test/src/services/completion/dart/completion_test.dart +++ b/pkg/analysis_server/test/src/services/completion/dart/completion_test.dart @@ -483,7 +483,6 @@ class C { @reflectiveTest class RedirectedConstructorCompletionTest extends CompletionTestCase { - @failingTest Future test_keywords() async { await getTestCodeSuggestions(''' class A {