[code completion] Move completion generation for initializing and super parameters

Change-Id: Ie9d09b323c7072c4fe375b6de12347a16065ac5e
Reviewed-on: https://dart-review.googlesource.com/c/sdk/+/341823
Reviewed-by: Samuel Rawlins <srawlins@google.com>
Commit-Queue: Brian Wilkerson <brianwilkerson@google.com>
This commit is contained in:
Brian Wilkerson 2023-12-15 17:41:32 +00:00 committed by Commit Queue
parent 07bba0a7d2
commit ae15831811
7 changed files with 108 additions and 155 deletions

View file

@ -356,6 +356,19 @@ final class StaticFieldSuggestion extends ImportableSuggestion {
}
}
/// The information about a candidate suggestion based on a parameter from a
/// super constructor.
final class SuperParameterSuggestion extends CandidateSuggestion {
/// The element on which the suggestion is based.
final ParameterElement element;
/// Initialize a newly created candidate suggestion to suggest the [element].
SuperParameterSuggestion(this.element);
@override
String get completion => element.name;
}
/// The information about a candidate suggestion based on a top-level getter or
/// setter.
final class TopLevelFunctionSuggestion extends ImportableSuggestion {
@ -516,6 +529,8 @@ extension SuggestionBuilderExtension on SuggestionBuilder {
libraryUriStr = suggestion.libraryUriStr;
suggestStaticField(suggestion.element, prefix: suggestion.prefix);
libraryUriStr = null;
case SuperParameterSuggestion():
suggestSuperFormalParameter(suggestion.element);
case TopLevelFunctionSuggestion():
libraryUriStr = suggestion.libraryUriStr;
suggestTopLevelFunction(suggestion.element,

View file

@ -12,7 +12,6 @@ import 'package:analysis_server/src/services/completion/dart/completion_state.da
import 'package:analysis_server/src/services/completion/dart/enum_constant_constructor_contributor.dart';
import 'package:analysis_server/src/services/completion/dart/extension_member_contributor.dart';
import 'package:analysis_server/src/services/completion/dart/feature_computer.dart';
import 'package:analysis_server/src/services/completion/dart/field_formal_contributor.dart';
import 'package:analysis_server/src/services/completion/dart/in_scope_completion_pass.dart';
import 'package:analysis_server/src/services/completion/dart/library_member_contributor.dart';
import 'package:analysis_server/src/services/completion/dart/library_prefix_contributor.dart';
@ -24,7 +23,6 @@ import 'package:analysis_server/src/services/completion/dart/redirecting_contrib
import 'package:analysis_server/src/services/completion/dart/static_member_contributor.dart';
import 'package:analysis_server/src/services/completion/dart/suggestion_builder.dart';
import 'package:analysis_server/src/services/completion/dart/suggestion_collector.dart';
import 'package:analysis_server/src/services/completion/dart/super_formal_contributor.dart';
import 'package:analysis_server/src/services/completion/dart/type_member_contributor.dart';
import 'package:analysis_server/src/services/completion/dart/uri_contributor.dart';
import 'package:analysis_server/src/services/completion/dart/variable_name_contributor.dart';
@ -137,7 +135,6 @@ class DartCompletionManager {
ClosureContributor(request, builder),
EnumConstantConstructorContributor(request, builder),
ExtensionMemberContributor(request, builder),
FieldFormalContributor(request, builder),
LibraryMemberContributor(request, builder),
LibraryPrefixContributor(request, builder),
NamedConstructorContributor(request, builder),
@ -145,7 +142,6 @@ class DartCompletionManager {
RecordLiteralContributor(request, builder),
RedirectingContributor(request, builder),
StaticMemberContributor(request, builder),
SuperFormalContributor(request, builder),
TypeMemberContributor(request, builder),
if (enableUriContributor) UriContributor(request, builder),
VariableNameContributor(request, builder),

View file

@ -11,6 +11,7 @@ import 'package:analysis_server/src/services/completion/dart/visibility_tracker.
import 'package:analyzer/dart/ast/ast.dart';
import 'package:analyzer/dart/element/element.dart';
import 'package:analyzer/dart/element/type.dart';
import 'package:analyzer/src/dart/element/element.dart';
import 'package:analyzer/src/dart/resolver/scope.dart';
/// A helper class that produces candidate suggestions for all of the
@ -114,8 +115,10 @@ class DeclarationHelper {
}
/// Add any fields that can be initialized in the initializer list of the
/// given [constructor].
void addFieldsForInitializers(ConstructorDeclaration constructor) {
/// given [constructor]. If a [fieldToInclude] is provided, then it should not
/// be skipped because the cursor is inside that field's name.
void addFieldsForInitializers(
ConstructorDeclaration constructor, FieldElement? fieldToInclude) {
var containingElement = constructor.declaredElement?.enclosingElement;
if (containingElement == null) {
return;
@ -133,6 +136,9 @@ class DeclarationHelper {
}
// Skip fields that are already initialized in the parameter list.
for (var parameter in constructor.parameters.parameters) {
if (parameter is DefaultFormalParameter) {
parameter = parameter.parameter;
}
if (parameter is FieldFormalParameter) {
var parameterElement = parameter.declaredElement;
if (parameterElement is FieldFormalParameterElement) {
@ -143,10 +149,14 @@ class DeclarationHelper {
}
}
}
fieldsToSkip.remove(fieldToInclude);
for (var field in containingElement.fields) {
// Skip fields that are already initialized at their declaration.
if (!fieldsToSkip.contains(field) && !field.hasInitializer) {
if (!field.isStatic &&
!field.isSynthetic &&
!fieldsToSkip.contains(field) &&
(!(field.isFinal || field.isConst) || !field.hasInitializer)) {
_suggestField(field, containingElement);
}
}
@ -204,6 +214,57 @@ class DeclarationHelper {
// TODO(brianwilkerson): Implement this.
}
/// Add any parameters from the super constructor of the constructor
/// containing the [node] that can be referenced as a super parameter.
void addParametersFromSuperConstructor(SuperFormalParameter node) {
var element = node.declaredElement;
if (element is! SuperFormalParameterElementImpl) {
return;
}
var constructor = node.thisOrAncestorOfType<ConstructorDeclaration>();
if (constructor == null) {
return;
}
var constructorElement = constructor.declaredElement;
if (constructorElement is! ConstructorElementImpl) {
return;
}
var superConstructor = constructorElement.superConstructor;
if (superConstructor == null) {
return;
}
if (node.isNamed) {
var superConstructorInvocation = constructor.initializers
.whereType<SuperConstructorInvocation>()
.singleOrNull;
var specified = <String>{
...constructorElement.parameters.map((e) => e.name),
...?superConstructorInvocation?.argumentList.arguments
.whereType<NamedExpression>()
.map((e) => e.name.label.name),
};
for (var superParameter in superConstructor.parameters) {
if (superParameter.isNamed &&
!specified.contains(superParameter.name)) {
collector.addSuggestion(SuperParameterSuggestion(superParameter));
}
}
} else if (node.isPositional) {
var indexOfThis = element.indexIn(constructorElement);
var superPositionalList = superConstructor.parameters
.where((parameter) => parameter.isPositional)
.toList();
if (indexOfThis >= 0 && indexOfThis < superPositionalList.length) {
var superPositional = superPositionalList[indexOfThis];
collector.addSuggestion(SuperParameterSuggestion(superPositional));
}
}
}
/// Add suggestions for any constructors that are declared within the
/// [library].
void _addConstructors(LibraryElement library, String? prefix) {

View file

@ -1,78 +0,0 @@
// Copyright (c) 2014, the Dart project authors. Please see the AUTHORS file
// for details. All rights reserved. Use of this source code is governed by a
// BSD-style license that can be found in the LICENSE file.
import 'package:analysis_server/src/provisional/completion/dart/completion_dart.dart';
import 'package:analyzer/dart/ast/ast.dart';
import 'package:analyzer/dart/element/element.dart';
import 'package:analyzer/src/util/performance/operation_performance.dart';
/// A contributor that produces suggestions for field formal parameters that are
/// based on the fields declared directly by the enclosing class that are not
/// already initialized. More concretely, this class produces suggestions for
/// expressions of the form `this.^` in a constructor's parameter list.
class FieldFormalContributor extends DartCompletionContributor {
FieldFormalContributor(super.request, super.builder);
@override
Future<void> computeSuggestions({
required OperationPerformanceImpl performance,
}) async {
var node = request.target.containingNode;
// TODO(brianwilkerson): We should suggest field formal parameters even if
// the user hasn't already typed the `this.` prefix, by including the
// prefix in the completion.
if (node is! FieldFormalParameter) {
return;
}
var constructor = node.thisOrAncestorOfType<ConstructorDeclaration>();
if (constructor == null) {
return;
}
// Compute the list of fields already referenced in the constructor.
// TODO(brianwilkerson): This doesn't include fields in initializers, which
// shouldn't be suggested.
var referencedFields = <String>[];
for (var param in constructor.parameters.parameters) {
if (param is DefaultFormalParameter) {
param = param.parameter;
}
if (param is FieldFormalParameter) {
var fieldId = param.name;
if (fieldId != request.target.entity) {
var fieldName = fieldId.lexeme;
if (fieldName.isNotEmpty) {
referencedFields.add(fieldName);
}
}
}
}
InterfaceElement? enclosingClass;
var constructorParent = constructor.parent;
if (constructorParent is ClassDeclaration) {
enclosingClass = constructorParent.declaredElement;
} else if (constructorParent is EnumDeclaration) {
enclosingClass = constructorParent.declaredElement;
} else {
return;
}
if (enclosingClass == null) {
return;
}
// Add suggestions for fields that are not already referenced.
for (var field in enclosingClass.fields) {
if (!field.isSynthetic && !field.isEnumConstant && !field.isStatic) {
var fieldName = field.name;
if (fieldName.isNotEmpty) {
if (!referencedFields.contains(fieldName)) {
builder.suggestFieldFormalParameter(field);
}
}
}
}
}
}

View file

@ -759,6 +759,22 @@ class InScopeCompletionPass extends SimpleAstVisitor<void> {
}
}
@override
void visitFieldFormalParameter(FieldFormalParameter node) {
var constructor = node.parent?.parent;
if (constructor is FormalParameterList) {
constructor = constructor.parent;
}
if (constructor is ConstructorDeclaration) {
var declaredElement = node.declaredElement;
FieldElement? field;
if (declaredElement is FieldFormalParameterElement) {
field = declaredElement.field;
}
declarationHelper().addFieldsForInitializers(constructor, field);
}
}
@override
void visitForEachPartsWithDeclaration(ForEachPartsWithDeclaration node) {
_visitForEachParts(node);
@ -1475,6 +1491,11 @@ class InScopeCompletionPass extends SimpleAstVisitor<void> {
node.parent as ConstructorDeclaration, node);
}
@override
void visitSuperFormalParameter(SuperFormalParameter node) {
declarationHelper().addParametersFromSuperConstructor(node);
}
@override
void visitSwitchCase(SwitchCase node) {
_forStatement(node);
@ -1941,8 +1962,13 @@ class InScopeCompletionPass extends SimpleAstVisitor<void> {
/// beginning of a constructor's initializer.
void _forConstructorInitializer(ConstructorDeclaration constructor,
ConstructorFieldInitializer? initializer) {
var element = initializer?.fieldName.staticElement;
FieldElement? field;
if (element is FieldElement) {
field = element;
}
keywordHelper.addConstructorInitializerKeywords(constructor, initializer);
declarationHelper().addFieldsForInitializers(constructor);
declarationHelper().addFieldsForInitializers(constructor, field);
}
/// Add the suggestions that are appropriate when the selection is at the

View file

@ -1,69 +0,0 @@
// Copyright (c) 2022, the Dart project authors. Please see the AUTHORS file
// for details. All rights reserved. Use of this source code is governed by a
// BSD-style license that can be found in the LICENSE file.
import 'package:analysis_server/src/provisional/completion/dart/completion_dart.dart';
import 'package:analyzer/dart/ast/ast.dart';
import 'package:analyzer/src/dart/element/element.dart';
import 'package:analyzer/src/util/performance/operation_performance.dart';
/// A contributor that produces suggestions for super formal parameters that
/// are based on the parameters declared by the invoked super-constructor.
/// The enclosing declaration is expected to be a constructor.
class SuperFormalContributor extends DartCompletionContributor {
SuperFormalContributor(super.request, super.builder);
@override
Future<void> computeSuggestions({
required OperationPerformanceImpl performance,
}) async {
var node = request.target.containingNode;
if (node is! SuperFormalParameter) {
return;
}
var element = node.declaredElement as SuperFormalParameterElementImpl;
var constructor = node.thisOrAncestorOfType<ConstructorDeclaration>();
if (constructor == null) {
return;
}
var constructorElement = constructor.declaredElement;
constructorElement as ConstructorElementImpl;
var superConstructor = constructorElement.superConstructor;
if (superConstructor == null) {
return;
}
if (node.isNamed) {
var superConstructorInvocation = constructor.initializers
.whereType<SuperConstructorInvocation>()
.singleOrNull;
var specified = <String>{
...constructorElement.parameters.map((e) => e.name),
...?superConstructorInvocation?.argumentList.arguments
.whereType<NamedExpression>()
.map((e) => e.name.label.name),
};
for (var superParameter in superConstructor.parameters) {
if (superParameter.isNamed &&
!specified.contains(superParameter.name)) {
builder.suggestSuperFormalParameter(superParameter);
}
}
}
if (node.isPositional) {
var indexOfThis = element.indexIn(constructorElement);
var superPositionalList = superConstructor.parameters
.where((parameter) => parameter.isPositional)
.toList();
if (indexOfThis >= 0 && indexOfThis < superPositionalList.length) {
var superPositional = superPositionalList[indexOfThis];
builder.suggestSuperFormalParameter(superPositional);
}
}
}
}

View file

@ -92,6 +92,8 @@ class A {
suggestions
assert
kind: keyword
f0
kind: field
f1
kind: field
super