diff --git a/pkg/analysis_server/lib/src/services/correction/dart/convert_to_initializing_formal.dart b/pkg/analysis_server/lib/src/services/correction/dart/convert_to_initializing_formal.dart index c940a694557..fde362f5881 100644 --- a/pkg/analysis_server/lib/src/services/correction/dart/convert_to_initializing_formal.dart +++ b/pkg/analysis_server/lib/src/services/correction/dart/convert_to_initializing_formal.dart @@ -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? initializers; Comment? documentationComment; FunctionBody? body; + var fieldInitializers = []; + + void findFieldInitializers(AstNode? containerBody) { + switch (containerBody) { + case ClassBody(:var members): + case EnumBody(:var members): + fieldInitializers = members + .whereType() + .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 fieldInitializers, + required NodeList? initializers, + required List nodesBeingRemoved, + required FormalParameter parameter, + required List 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 _computeChange( - ChangeBuilder builder, - FormalParameterList parameterList, - NodeList? initializers, - Comment? documentationComment, - FormalParameter parameter, - FormalParameterElement parameterElement, - VariableElement field, { - ConstructorFieldInitializer? initializer, + Future _computeChange({ + required ChangeBuilder builder, + required FunctionBody? constructorBody, + required Comment? documentationComment, + required VariableElement field, + required List fieldInitializers, + required NodeList? 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 = []; 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 _computeChangeFromParameter( - ChangeBuilder builder, - FormalParameterList parameterList, - NodeList? initializers, - Comment? documentationComment, - FunctionBody? body, - FormalParameter parameter, - FormalParameterElement parameterElement, - ) async { + Future _computeChangeFromParameter({ + required ChangeBuilder builder, + required FunctionBody? body, + required Comment? documentationComment, + required List fieldInitializers, + required NodeList? 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 { + final List references; + + final FormalParameterElement parameterElement; + + final List 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); + } + } +} diff --git a/pkg/analysis_server/test/src/services/correction/assist/convert_to_initializing_formal_test.dart b/pkg/analysis_server/test/src/services/correction/assist/convert_to_initializing_formal_test.dart index 36760134f6a..7b1dac97ebd 100644 --- a/pkg/analysis_server/test/src/services/correction/assist/convert_to_initializing_formal_test.dart +++ b/pkg/analysis_server/test/src/services/correction/assist/convert_to_initializing_formal_test.dart @@ -627,6 +627,102 @@ class C { '''); } + Future 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 + 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 + 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 + 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 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 test_named_unsupported() async { await resolveTestCode(''' // @dart=3.10