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 <keertip@google.com>
Commit-Queue: Brian Wilkerson <brianwilkerson@google.com>
This commit is contained in:
Brian Wilkerson
2026-05-14 12:11:59 -07:00
committed by dart-scoped@luci-project-accounts.iam.gserviceaccount.com
parent f35be6757d
commit a667cde7c1
6 changed files with 211 additions and 1 deletions
@@ -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<void> 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;
}
}
@@ -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:
@@ -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,
@@ -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 = <DiagnosticCode, List<ProducerGenerator>>{
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],
@@ -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<void> 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<void> 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<void> 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<void> 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;
}
''');
}
}
@@ -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();