From a667cde7c167fa7686798c6d95d6f97e6591ebd6 Mon Sep 17 00:00:00 2001 From: Brian Wilkerson Date: Thu, 14 May 2026 12:11:59 -0700 Subject: [PATCH] Add a fix for initialize_in_field_declaration The correction producer implements a fix. Pending discussion we might choose to extend it to produce an assist, either in addition to the fix (if we keep the lint) or instead of a fix (if we drop the lint). Whatever we choose, I'd like to do that in a separate CL because I want this to be available for testing purposes as soon as possible. Change-Id: Icdd17ba0130149fc785c196be69e9ef990529800 Reviewed-on: https://dart-review.googlesource.com/c/sdk/+/503520 Reviewed-by: Keerti Parthasarathy Commit-Queue: Brian Wilkerson --- ...e_initialization_to_field_declaration.dart | 99 +++++++++++++++++++ .../services/correction/error_fix_status.yaml | 2 +- .../lib/src/services/correction/fix.dart | 10 ++ .../src/services/correction/fix_internal.dart | 2 + ...tialization_to_field_declaration_test.dart | 96 ++++++++++++++++++ .../src/services/correction/fix/test_all.dart | 3 + 6 files changed, 211 insertions(+), 1 deletion(-) create mode 100644 pkg/analysis_server/lib/src/services/correction/dart/move_initialization_to_field_declaration.dart create mode 100644 pkg/analysis_server/test/src/services/correction/fix/move_initialization_to_field_declaration_test.dart diff --git a/pkg/analysis_server/lib/src/services/correction/dart/move_initialization_to_field_declaration.dart b/pkg/analysis_server/lib/src/services/correction/dart/move_initialization_to_field_declaration.dart new file mode 100644 index 00000000000..1e7826623ae --- /dev/null +++ b/pkg/analysis_server/lib/src/services/correction/dart/move_initialization_to_field_declaration.dart @@ -0,0 +1,99 @@ +// Copyright (c) 2026, 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/services/correction/fix.dart'; +import 'package:analysis_server_plugin/edit/dart/correction_producer.dart'; +import 'package:analyzer/dart/ast/ast.dart'; +import 'package:analyzer/dart/element/element.dart'; +import 'package:analyzer/src/utilities/extensions/ast.dart'; +import 'package:analyzer_plugin/utilities/change_builder/change_builder_core.dart'; +import 'package:analyzer_plugin/utilities/fixes/fixes.dart'; +import 'package:analyzer_plugin/utilities/range_factory.dart'; + +class MoveInitializationToFieldDeclaration extends ResolvedCorrectionProducer { + MoveInitializationToFieldDeclaration({required super.context}); + + @override + CorrectionApplicability get applicability => + CorrectionApplicability.automatically; + + @override + FixKind get fixKind => DartFixKind.moveInitializationToFieldDeclaration; + + @override + FixKind get multiFixKind => + DartFixKind.moveInitializationToFieldDeclarationMulti; + + @override + Future compute(ChangeBuilder builder) async { + var node = this.node; + if (node is! SimpleIdentifier) { + return; + } + var initializer = node.parent; + if (initializer is! ConstructorFieldInitializer) { + return; + } + var field = node.element; + if (field is! FieldElement) { + return; + } + var constructorDeclaration = initializer.parent; + if (constructorDeclaration is! ClassMember) { + return; + } + var initializers = switch (constructorDeclaration) { + ConstructorDeclaration() => constructorDeclaration.initializers, + PrimaryConstructorBody() => constructorDeclaration.initializers, + _ => null, + }; + if (initializers == null) { + return; + } + var fieldDeclaration = findFieldDeclaration(field, constructorDeclaration); + if (fieldDeclaration == null || + fieldDeclaration.equals != null || + fieldDeclaration.initializer != null) { + return; + } + var expression = utils.getRangeText(initializer.expression.sourceRange); + + await builder.addDartFileEdit(file, (builder) { + builder.addDeletion(range.nodeInList(initializers, initializer)); + builder.addSimpleInsertion(fieldDeclaration.end, ' = $expression'); + }); + } + + /// Returns the declaration of the [field]. + /// + /// Uses the [initializer] to find the container containing the field's + /// declaration. + /// + /// Returns `null` if the declaration can't be found. + VariableDeclaration? findFieldDeclaration( + FieldElement field, + ClassMember constructorDeclaration, + ) { + // TODO(brianwilkerson): When support for augmentations is added, this will + // need to look at all of the fragments of the container in order to find + // the declaration. + var container = constructorDeclaration.parent; + var members = switch (container) { + ClassBody() => container.members, + EnumBody() => container.members, + _ => null, + }; + if (members == null) return null; + for (var member in members) { + if (member is FieldDeclaration) { + for (var fieldDeclarartion in member.fields.variables) { + if (fieldDeclarartion.declaredFieldElement == field) { + return fieldDeclarartion; + } + } + } + } + return null; + } +} diff --git a/pkg/analysis_server/lib/src/services/correction/error_fix_status.yaml b/pkg/analysis_server/lib/src/services/correction/error_fix_status.yaml index 06dd414776d..f357abee758 100644 --- a/pkg/analysis_server/lib/src/services/correction/error_fix_status.yaml +++ b/pkg/analysis_server/lib/src/services/correction/error_fix_status.yaml @@ -2165,7 +2165,7 @@ implicit_call_tearoffs: implicit_reopen: status: hasFix initialize_in_field_declaration: - status: needsFix + status: hasFix invalid_case_patterns: status: hasFix invalid_runtime_check_with_js_interop_types_catch_clause_js_interop_type: diff --git a/pkg/analysis_server/lib/src/services/correction/fix.dart b/pkg/analysis_server/lib/src/services/correction/fix.dart index c318119501b..1002c8c111e 100644 --- a/pkg/analysis_server/lib/src/services/correction/fix.dart +++ b/pkg/analysis_server/lib/src/services/correction/fix.dart @@ -1243,6 +1243,16 @@ abstract final class DartFixKind { DartFixKindPriority.standard, 'Move this doc comment to a library directive', ); + static const moveInitializationToFieldDeclaration = FixKind( + 'dart.fix.moveInitializationToFieldDeclaration', + DartFixKindPriority.standard, + "Move the initialization to the field's declaration", + ); + static const moveInitializationToFieldDeclarationMulti = FixKind( + 'dart.fix.moveInitializationToFieldDeclaration.multi', + DartFixKindPriority.standard, + "Move initialization to the field's declaration everywhere", + ); static const moveTypeArgumentsToClass = FixKind( 'dart.fix.moveTypeArgumentsToClass', DartFixKindPriority.standard, diff --git a/pkg/analysis_server/lib/src/services/correction/fix_internal.dart b/pkg/analysis_server/lib/src/services/correction/fix_internal.dart index 63b6cc5328d..16c8a9686b6 100644 --- a/pkg/analysis_server/lib/src/services/correction/fix_internal.dart +++ b/pkg/analysis_server/lib/src/services/correction/fix_internal.dart @@ -131,6 +131,7 @@ import 'package:analysis_server/src/services/correction/dart/make_variable_nulla import 'package:analysis_server/src/services/correction/dart/merge_combinators.dart'; import 'package:analysis_server/src/services/correction/dart/move_annotation_to_library_directive.dart'; import 'package:analysis_server/src/services/correction/dart/move_doc_comment_to_library_directive.dart'; +import 'package:analysis_server/src/services/correction/dart/move_initialization_to_field_declaration.dart'; import 'package:analysis_server/src/services/correction/dart/move_type_arguments_to_class.dart'; import 'package:analysis_server/src/services/correction/dart/organize_imports.dart'; import 'package:analysis_server/src/services/correction/dart/qualify_reference.dart'; @@ -341,6 +342,7 @@ final _builtInLintGenerators = >{ diag.hashAndEquals: [CreateMethod.equalityOrHashCode], diag.implicitCallTearoffs: [AddExplicitCall.new], diag.implicitReopen: [AddReopen.new], + diag.initializeInFieldDeclaration: [MoveInitializationToFieldDeclaration.new], diag.invalidCasePatterns: [AddConst.new], diag.leadingNewlinesInMultilineStrings: [AddLeadingNewlineToString.new], diag.libraryAnnotations: [MoveAnnotationToLibraryDirective.new], diff --git a/pkg/analysis_server/test/src/services/correction/fix/move_initialization_to_field_declaration_test.dart b/pkg/analysis_server/test/src/services/correction/fix/move_initialization_to_field_declaration_test.dart new file mode 100644 index 00000000000..d7daa9c16ba --- /dev/null +++ b/pkg/analysis_server/test/src/services/correction/fix/move_initialization_to_field_declaration_test.dart @@ -0,0 +1,96 @@ +// Copyright (c) 2026, 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/services/correction/fix.dart'; +import 'package:analyzer_plugin/utilities/fixes/fixes.dart'; +import 'package:test_reflective_loader/test_reflective_loader.dart'; + +import 'fix_processor.dart'; + +void main() { + defineReflectiveSuite(() { + defineReflectiveTests(MoveInitializationToFieldDeclarationTest); + }); +} + +@reflectiveTest +class MoveInitializationToFieldDeclarationTest extends FixProcessorLintTest { + @override + FixKind get kind => DartFixKind.moveInitializationToFieldDeclaration; + + @override + String get lintCode => LintNames.initialize_in_field_declaration; + + Future test_class_multipleFields() async { + await resolveTestCode(''' +class C(int x) { + this : y = x, z = 0; + + int y, z; +} +'''); + await assertHasFix(''' +class C(int x) { + this : z = 0; + + int y = x, z; +} +'''); + } + + Future test_class_multipleInitializers_first() async { + await resolveTestCode(''' +class C(int x) { + this : y = x, z = 0; + + int y; + int z; +} +'''); + await assertHasFix(''' +class C(int x) { + this : z = 0; + + int y = x; + int z; +} +'''); + } + + Future test_class_multipleInitializers_last() async { + await resolveTestCode(''' +class C(int x) { + this : y = 0, z = x; + + int y; + int z; +} +'''); + await assertHasFix(''' +class C(int x) { + this : y = 0; + + int y; + int z = x; +} +'''); + } + + Future test_class_singleInitializer() async { + await resolveTestCode(''' +class C(int x) { + this : y = x; + + int y; +} +'''); + await assertHasFix(''' +class C(int x) { + this; + + int y = x; +} +'''); + } +} diff --git a/pkg/analysis_server/test/src/services/correction/fix/test_all.dart b/pkg/analysis_server/test/src/services/correction/fix/test_all.dart index eeecc0c19c1..8d670a01a2f 100644 --- a/pkg/analysis_server/test/src/services/correction/fix/test_all.dart +++ b/pkg/analysis_server/test/src/services/correction/fix/test_all.dart @@ -180,6 +180,8 @@ import 'move_annotation_to_library_directive_test.dart' as move_annotation_to_library_directive; import 'move_doc_comment_to_library_directive_test.dart' as move_doc_comment_to_library_directive; +import 'move_initialization_to_field_declaration_test.dart' + as move_initialization_to_field_declaration; import 'move_type_arguments_to_class_test.dart' as move_type_arguments_to_class; import 'organize_imports_test.dart' as organize_imports; import 'pubspec/test_all.dart' as pubspec; @@ -485,6 +487,7 @@ void main() { merge_combinators.main(); move_annotation_to_library_directive.main(); move_doc_comment_to_library_directive.main(); + move_initialization_to_field_declaration.main(); move_type_arguments_to_class.main(); organize_imports.main(); pubspec.main();