mirror of
https://github.com/dart-lang/sdk
synced 2024-10-02 23:59:16 +00:00
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:
parent
2e7149b260
commit
57ece5b452
|
@ -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;
|
||||
}
|
||||
}
|
||||
|
|
|
@ -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) {
|
||||
|
|
|
@ -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;
|
||||
|
|
|
@ -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);
|
||||
// }
|
||||
}
|
||||
}
|
||||
|
||||
|
|
|
@ -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;
|
||||
|
|
|
@ -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 {
|
||||
|
|
|
@ -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 {
|
||||
|
|
|
@ -7753,10 +7753,6 @@ suggestions
|
|||
kind: class
|
||||
B0
|
||||
kind: constructorInvocation
|
||||
a0
|
||||
kind: field
|
||||
b0
|
||||
kind: methodInvocation
|
||||
f0
|
||||
kind: field
|
||||
''');
|
||||
|
|
|
@ -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
|
||||
|
|
|
@ -483,7 +483,6 @@ class C {
|
|||
|
||||
@reflectiveTest
|
||||
class RedirectedConstructorCompletionTest extends CompletionTestCase {
|
||||
@failingTest
|
||||
Future<void> test_keywords() async {
|
||||
await getTestCodeSuggestions('''
|
||||
class A {
|
||||
|
|
Loading…
Reference in a new issue