Fix convertToInitializingFormal to rename references to the parameter
The fix was previously missing logic for renaming references to the parameter when the field name is private. This CL should fix that. Closes https://github.com/dart-lang/sdk/issues/63505 Change-Id: Ic4435414adbbf7a625b2c7c47e0a41136c4c58b8 Reviewed-on: https://dart-review.googlesource.com/c/sdk/+/509160 Reviewed-by: Samuel Rawlins <srawlins@google.com>
This commit is contained in:
+170
-60
@@ -8,6 +8,7 @@ import 'package:analysis_server_plugin/edit/dart/correction_producer.dart';
|
||||
import 'package:analyzer/dart/analysis/features.dart';
|
||||
import 'package:analyzer/dart/ast/ast.dart';
|
||||
import 'package:analyzer/dart/ast/token.dart';
|
||||
import 'package:analyzer/dart/ast/visitor.dart';
|
||||
import 'package:analyzer/dart/element/element.dart';
|
||||
import 'package:analyzer/src/dart/ast/ast.dart';
|
||||
import 'package:analyzer_plugin/utilities/assist/assist.dart';
|
||||
@@ -41,6 +42,20 @@ class ConvertToInitializingFormal extends ResolvedCorrectionProducer {
|
||||
NodeList<ConstructorInitializer>? initializers;
|
||||
Comment? documentationComment;
|
||||
FunctionBody? body;
|
||||
var fieldInitializers = <Expression>[];
|
||||
|
||||
void findFieldInitializers(AstNode? containerBody) {
|
||||
switch (containerBody) {
|
||||
case ClassBody(:var members):
|
||||
case EnumBody(:var members):
|
||||
fieldInitializers = members
|
||||
.whereType<FieldDeclaration>()
|
||||
.expand((declaration) => declaration.fields.variables)
|
||||
.map((declaration) => declaration.initializer)
|
||||
.nonNulls
|
||||
.toList();
|
||||
}
|
||||
}
|
||||
|
||||
var anchorNode = _findAnchorNode();
|
||||
if (anchorNode case ConstructorDeclaration constructor) {
|
||||
@@ -48,6 +63,7 @@ class ConvertToInitializingFormal extends ResolvedCorrectionProducer {
|
||||
initializers = constructor.initializers;
|
||||
documentationComment = constructor.documentationComment;
|
||||
body = constructor.body;
|
||||
findFieldInitializers(constructor.parent);
|
||||
} else if (anchorNode
|
||||
case PrimaryConstructorDeclaration primaryConstructor) {
|
||||
parameterList = primaryConstructor.formalParameters;
|
||||
@@ -57,6 +73,15 @@ class ConvertToInitializingFormal extends ResolvedCorrectionProducer {
|
||||
documentationComment = primaryBody.documentationComment;
|
||||
body = primaryBody.body;
|
||||
}
|
||||
var containerBody = switch (primaryConstructor.parent) {
|
||||
ClassDeclaration(:var body) => body,
|
||||
EnumDeclaration(:var body) => body,
|
||||
ExtensionDeclaration(:var body) => body,
|
||||
ExtensionTypeDeclaration(:var body) => body,
|
||||
MixinDeclaration(:var body) => body,
|
||||
_ => null,
|
||||
};
|
||||
findFieldInitializers(containerBody);
|
||||
} else if (anchorNode case PrimaryConstructorBody primaryBody) {
|
||||
var declaration = primaryBody.declaration;
|
||||
if (declaration == null) return;
|
||||
@@ -64,6 +89,7 @@ class ConvertToInitializingFormal extends ResolvedCorrectionProducer {
|
||||
initializers = primaryBody.initializers;
|
||||
documentationComment = primaryBody.documentationComment;
|
||||
body = primaryBody.body;
|
||||
findFieldInitializers(primaryBody.parent);
|
||||
} else {
|
||||
return;
|
||||
}
|
||||
@@ -85,14 +111,16 @@ class ConvertToInitializingFormal extends ResolvedCorrectionProducer {
|
||||
case VariableElement field:
|
||||
case SetterElement(variable: VariableElement field):
|
||||
await _computeChange(
|
||||
builder,
|
||||
parameterList,
|
||||
initializers,
|
||||
documentationComment,
|
||||
parameter,
|
||||
parameterElement,
|
||||
field,
|
||||
assignment: statement,
|
||||
builder: builder,
|
||||
constructorBody: body,
|
||||
documentationComment: documentationComment,
|
||||
field: field,
|
||||
fieldInitializers: fieldInitializers,
|
||||
initializers: initializers,
|
||||
parameter: parameter,
|
||||
parameterElement: parameterElement,
|
||||
parameterList: parameterList,
|
||||
);
|
||||
}
|
||||
}
|
||||
@@ -107,27 +135,30 @@ class ConvertToInitializingFormal extends ResolvedCorrectionProducer {
|
||||
if (field is! VariableElement) return;
|
||||
|
||||
await _computeChange(
|
||||
builder,
|
||||
parameterList,
|
||||
initializers,
|
||||
documentationComment,
|
||||
parameter,
|
||||
parameterElement,
|
||||
field,
|
||||
builder: builder,
|
||||
constructorBody: body,
|
||||
documentationComment: documentationComment,
|
||||
field: field,
|
||||
fieldInitializers: fieldInitializers,
|
||||
initializer: initializer,
|
||||
initializers: initializers,
|
||||
parameter: parameter,
|
||||
parameterElement: parameterElement,
|
||||
parameterList: parameterList,
|
||||
);
|
||||
}
|
||||
|
||||
case RegularFormalParameter parameter:
|
||||
// A constructor parameter declaration.
|
||||
await _computeChangeFromParameter(
|
||||
builder,
|
||||
parameterList,
|
||||
initializers,
|
||||
documentationComment,
|
||||
body,
|
||||
parameter,
|
||||
parameter.declaredFragment!.element,
|
||||
body: body,
|
||||
builder: builder,
|
||||
documentationComment: documentationComment,
|
||||
fieldInitializers: fieldInitializers,
|
||||
initializers: initializers,
|
||||
parameter: parameter,
|
||||
parameterElement: parameter.declaredFragment!.element,
|
||||
parameterList: parameterList,
|
||||
);
|
||||
|
||||
case SimpleIdentifier parameterUse:
|
||||
@@ -138,19 +169,52 @@ class ConvertToInitializingFormal extends ResolvedCorrectionProducer {
|
||||
_,
|
||||
)) {
|
||||
await _computeChangeFromParameter(
|
||||
builder,
|
||||
parameterList,
|
||||
initializers,
|
||||
documentationComment,
|
||||
body,
|
||||
parameter,
|
||||
parameterElement,
|
||||
body: body,
|
||||
builder: builder,
|
||||
documentationComment: documentationComment,
|
||||
fieldInitializers: fieldInitializers,
|
||||
initializers: initializers,
|
||||
parameter: parameter,
|
||||
parameterElement: parameterElement,
|
||||
parameterList: parameterList,
|
||||
);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/// Add to the list of [references] any references to the [parameter] that
|
||||
/// occur inside either the [initializers] or the [constructorBody].
|
||||
void _addReferencesToParam({
|
||||
required FunctionBody? constructorBody,
|
||||
required List<Expression> fieldInitializers,
|
||||
required NodeList<ConstructorInitializer>? initializers,
|
||||
required List<AstNode> nodesBeingRemoved,
|
||||
required FormalParameter parameter,
|
||||
required List<SimpleIdentifier> references,
|
||||
}) {
|
||||
var parameterElement = parameter.declaredFragment?.element;
|
||||
if (parameterElement == null) {
|
||||
return;
|
||||
}
|
||||
var visitor = _ReferenceVisitor(
|
||||
references,
|
||||
parameterElement,
|
||||
nodesBeingRemoved,
|
||||
);
|
||||
if (initializers != null) {
|
||||
for (var initializer in initializers) {
|
||||
if (!nodesBeingRemoved.contains(initializer)) {
|
||||
initializer.accept(visitor);
|
||||
}
|
||||
}
|
||||
}
|
||||
for (var initializer in fieldInitializers) {
|
||||
initializer.accept(visitor);
|
||||
}
|
||||
constructorBody?.accept(visitor);
|
||||
}
|
||||
|
||||
/// Attempts to compute a change [parameter] to an initializing formal for
|
||||
/// [field].
|
||||
///
|
||||
@@ -159,21 +223,24 @@ class ConvertToInitializingFormal extends ResolvedCorrectionProducer {
|
||||
///
|
||||
/// The parameter should currently be initialized by either [initializer] or
|
||||
/// [assignment] but not both.
|
||||
Future<void> _computeChange(
|
||||
ChangeBuilder builder,
|
||||
FormalParameterList parameterList,
|
||||
NodeList<ConstructorInitializer>? initializers,
|
||||
Comment? documentationComment,
|
||||
FormalParameter parameter,
|
||||
FormalParameterElement parameterElement,
|
||||
VariableElement field, {
|
||||
ConstructorFieldInitializer? initializer,
|
||||
Future<void> _computeChange({
|
||||
required ChangeBuilder builder,
|
||||
required FunctionBody? constructorBody,
|
||||
required Comment? documentationComment,
|
||||
required VariableElement field,
|
||||
required List<Expression> fieldInitializers,
|
||||
required NodeList<ConstructorInitializer>? initializers,
|
||||
required FormalParameter parameter,
|
||||
required FormalParameterElement parameterElement,
|
||||
required FormalParameterList parameterList,
|
||||
Statement? assignment,
|
||||
ConstructorFieldInitializer? initializer,
|
||||
}) async {
|
||||
assert(initializer == null || assignment == null);
|
||||
if (parameter is SuperFormalParameter) return;
|
||||
var parameterName = parameter.name!.lexeme;
|
||||
var fieldName = field.displayName;
|
||||
var references = <SimpleIdentifier>[];
|
||||
var updateCommentReferences = false;
|
||||
|
||||
if (parameter.isNamed) {
|
||||
@@ -184,6 +251,14 @@ class ConvertToInitializingFormal extends ResolvedCorrectionProducer {
|
||||
Identifier.isPrivateName(fieldName)) {
|
||||
return;
|
||||
}
|
||||
_addReferencesToParam(
|
||||
constructorBody: constructorBody,
|
||||
fieldInitializers: fieldInitializers,
|
||||
initializers: initializers,
|
||||
nodesBeingRemoved: [?initializer, ?assignment],
|
||||
parameter: parameter,
|
||||
references: references,
|
||||
);
|
||||
updateCommentReferences = true;
|
||||
} else if (fieldName != parameterName) {
|
||||
// We can't rename the parameter to match the field name if the parameter
|
||||
@@ -277,6 +352,11 @@ class ConvertToInitializingFormal extends ResolvedCorrectionProducer {
|
||||
}
|
||||
}
|
||||
|
||||
// Update references to the parameter to use the name of the field.
|
||||
for (var reference in references) {
|
||||
builder.addSimpleReplacement(reference.sourceRange, fieldName);
|
||||
}
|
||||
|
||||
if (updateCommentReferences) {
|
||||
var references = documentationComment?.references;
|
||||
if (references != null) {
|
||||
@@ -297,15 +377,16 @@ class ConvertToInitializingFormal extends ResolvedCorrectionProducer {
|
||||
///
|
||||
/// Looks for a corresponding constructor initializer or assignment statement
|
||||
/// and applies the conversion if one is found.
|
||||
Future<void> _computeChangeFromParameter(
|
||||
ChangeBuilder builder,
|
||||
FormalParameterList parameterList,
|
||||
NodeList<ConstructorInitializer>? initializers,
|
||||
Comment? documentationComment,
|
||||
FunctionBody? body,
|
||||
FormalParameter parameter,
|
||||
FormalParameterElement parameterElement,
|
||||
) async {
|
||||
Future<void> _computeChangeFromParameter({
|
||||
required ChangeBuilder builder,
|
||||
required FunctionBody? body,
|
||||
required Comment? documentationComment,
|
||||
required List<Expression> fieldInitializers,
|
||||
required NodeList<ConstructorInitializer>? initializers,
|
||||
required FormalParameter parameter,
|
||||
required FormalParameterElement parameterElement,
|
||||
required FormalParameterList parameterList,
|
||||
}) async {
|
||||
// If there happens to be both an initializer and an assignment, the
|
||||
// initializer will be first, so convert that and ignore the later mutating
|
||||
// assignment.
|
||||
@@ -314,28 +395,32 @@ class ConvertToInitializingFormal extends ResolvedCorrectionProducer {
|
||||
: null;
|
||||
if (initializer?.fieldName.element case VariableElement field) {
|
||||
await _computeChange(
|
||||
builder,
|
||||
parameterList,
|
||||
initializers,
|
||||
documentationComment,
|
||||
parameter,
|
||||
parameterElement,
|
||||
field,
|
||||
builder: builder,
|
||||
constructorBody: body,
|
||||
documentationComment: documentationComment,
|
||||
field: field,
|
||||
fieldInitializers: fieldInitializers,
|
||||
initializer: initializer,
|
||||
initializers: initializers,
|
||||
parameter: parameter,
|
||||
parameterElement: parameterElement,
|
||||
parameterList: parameterList,
|
||||
);
|
||||
} else if (_findAssignment(body, parameterElement) case (
|
||||
var statement,
|
||||
var field,
|
||||
)) {
|
||||
await _computeChange(
|
||||
builder,
|
||||
parameterList,
|
||||
initializers,
|
||||
documentationComment,
|
||||
parameter,
|
||||
parameterElement,
|
||||
field,
|
||||
assignment: statement,
|
||||
builder: builder,
|
||||
constructorBody: body,
|
||||
documentationComment: documentationComment,
|
||||
field: field,
|
||||
fieldInitializers: fieldInitializers,
|
||||
initializers: initializers,
|
||||
parameter: parameter,
|
||||
parameterElement: parameterElement,
|
||||
parameterList: parameterList,
|
||||
);
|
||||
} else if (parameter is RegularFormalParameter &&
|
||||
parameterElement is FieldFormalParameterElement) {
|
||||
@@ -465,3 +550,28 @@ class ConvertToInitializingFormal extends ResolvedCorrectionProducer {
|
||||
return null;
|
||||
}
|
||||
}
|
||||
|
||||
class _ReferenceVisitor extends GeneralizingAstVisitor<void> {
|
||||
final List<SimpleIdentifier> references;
|
||||
|
||||
final FormalParameterElement parameterElement;
|
||||
|
||||
final List<AstNode> nodesBeingRemoved;
|
||||
|
||||
new(this.references, this.parameterElement, this.nodesBeingRemoved);
|
||||
|
||||
@override
|
||||
void visitNode(AstNode node) {
|
||||
if (!nodesBeingRemoved.contains(node)) {
|
||||
super.visitNode(node);
|
||||
}
|
||||
}
|
||||
|
||||
@override
|
||||
void visitSimpleIdentifier(SimpleIdentifier node) {
|
||||
var element = node.element;
|
||||
if (element == parameterElement) {
|
||||
references.add(node);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
+96
@@ -627,6 +627,102 @@ class C {
|
||||
''');
|
||||
}
|
||||
|
||||
Future<void> test_named_inInitializer_referencedInBody_secondary() async {
|
||||
await resolveTestCode('''
|
||||
class C {
|
||||
final int _i;
|
||||
|
||||
C({required int ^i}) : _i = i {
|
||||
print(i);
|
||||
}
|
||||
}
|
||||
''');
|
||||
await assertHasAssist('''
|
||||
class C {
|
||||
final int _i;
|
||||
|
||||
C({required this._i}) {
|
||||
print(_i);
|
||||
}
|
||||
}
|
||||
''');
|
||||
}
|
||||
|
||||
Future<void>
|
||||
test_named_inInitializer_referencedInFieldInitializer_primary() async {
|
||||
await resolveTestCode('''
|
||||
class C({required int ^i}) {
|
||||
final int _i;
|
||||
final int _j = i;
|
||||
|
||||
this : _i = i;
|
||||
}
|
||||
''');
|
||||
await assertHasAssist('''
|
||||
class C({required this._i}) {
|
||||
final int _i;
|
||||
final int _j = _i;
|
||||
|
||||
this;
|
||||
}
|
||||
''');
|
||||
}
|
||||
|
||||
Future<void>
|
||||
test_named_inInitializer_referencedInInitializer_primary() async {
|
||||
await resolveTestCode('''
|
||||
class C({required int ^i}) {
|
||||
final int _i;
|
||||
final int _j;
|
||||
|
||||
this : _i = i, _j = i;
|
||||
}
|
||||
''');
|
||||
await assertHasAssist('''
|
||||
class C({required this._i}) {
|
||||
final int _i;
|
||||
final int _j;
|
||||
|
||||
this : _j = _i;
|
||||
}
|
||||
''');
|
||||
}
|
||||
|
||||
Future<void>
|
||||
test_named_inInitializer_referencedInInitializer_secondary() async {
|
||||
await resolveTestCode('''
|
||||
class C {
|
||||
final int _i;
|
||||
final int _j;
|
||||
|
||||
C({required int ^i}) : _i = i, _j = i;
|
||||
}
|
||||
''');
|
||||
await assertHasAssist('''
|
||||
class C {
|
||||
final int _i;
|
||||
final int _j;
|
||||
|
||||
C({required this._i}) : _j = _i;
|
||||
}
|
||||
''');
|
||||
}
|
||||
|
||||
Future<void> test_named_inInitializer_rightSide() async {
|
||||
await resolveTestCode('''
|
||||
class C({required int i}) {
|
||||
int _i;
|
||||
this : _i = ^i;
|
||||
}
|
||||
''');
|
||||
await assertHasAssist('''
|
||||
class C({required this._i}) {
|
||||
int _i;
|
||||
this;
|
||||
}
|
||||
''');
|
||||
}
|
||||
|
||||
Future<void> test_named_unsupported() async {
|
||||
await resolveTestCode('''
|
||||
// @dart=3.10
|
||||
|
||||
Reference in New Issue
Block a user