From 207e8f6f3c79e83934edcd2baf478ed5724fa0f6 Mon Sep 17 00:00:00 2001 From: Robert Nystrom Date: Tue, 24 Feb 2026 17:13:50 -0800 Subject: [PATCH] Change the ConvertToInitializingFormal availability to allow it in dart fix. Change-Id: I9dc2a579f5c7a832bc3ba2adc4ea0ad965845397 Reviewed-on: https://dart-review.googlesource.com/c/sdk/+/483361 Reviewed-by: Brian Wilkerson Reviewed-by: Paul Berry Auto-Submit: Bob Nystrom Commit-Queue: Brian Wilkerson --- .../dart/convert_to_initializing_formal.dart | 11 +- .../lib/src/services/correction/fix.dart | 5 + .../convert_to_initializing_formal_test.dart | 110 ++++++++++++++++++ pkg/dartdev/test/commands/fix_test.dart | 52 +++++++++ 4 files changed, 175 insertions(+), 3 deletions(-) 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 32ea50026a8..ea0c34fc894 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 @@ -19,9 +19,11 @@ class ConvertToInitializingFormal extends ResolvedCorrectionProducer { @override CorrectionApplicability get applicability => - // The fix isn't able to remove the initializer list / block function body - // in the case where multiple initializers / statements are being removed. - CorrectionApplicability.singleLocation; + // The code to remove an initializer or assignment statement assumes that + // no other initializers or statements are being removed concurrently, so + // only works one at a time. But it is safe to run this fix multiple times + // sequentially. + CorrectionApplicability.automatically; @override AssistKind get assistKind => DartAssistKind.convertToInitializingFormal; @@ -29,6 +31,9 @@ class ConvertToInitializingFormal extends ResolvedCorrectionProducer { @override FixKind get fixKind => DartFixKind.convertToInitializingFormal; + @override + FixKind get multiFixKind => DartFixKind.convertToInitializingFormalMulti; + @override Future compute(ChangeBuilder builder) async { var constructor = node.thisOrAncestorOfType(); diff --git a/pkg/analysis_server/lib/src/services/correction/fix.dart b/pkg/analysis_server/lib/src/services/correction/fix.dart index ab4b079ca9d..5cf2d5a25a3 100644 --- a/pkg/analysis_server/lib/src/services/correction/fix.dart +++ b/pkg/analysis_server/lib/src/services/correction/fix.dart @@ -551,6 +551,11 @@ abstract final class DartFixKind { DartFixKindPriority.standard, 'Convert to an initializing formal parameter', ); + static const convertToInitializingFormalMulti = FixKind( + 'dart.fix.convert.toInitializingFormal.multi', + DartFixKindPriority.standard, + 'Convert to initializing formal parameters everywhere in file', + ); static const convertToIntLiteral = FixKind( 'dart.fix.convert.toIntLiteral', DartFixKindPriority.standard, diff --git a/pkg/analysis_server/test/src/services/correction/fix/convert_to_initializing_formal_test.dart b/pkg/analysis_server/test/src/services/correction/fix/convert_to_initializing_formal_test.dart index 18d383054eb..2cd269f554f 100644 --- a/pkg/analysis_server/test/src/services/correction/fix/convert_to_initializing_formal_test.dart +++ b/pkg/analysis_server/test/src/services/correction/fix/convert_to_initializing_formal_test.dart @@ -12,10 +12,120 @@ import 'fix_processor.dart'; void main() { defineReflectiveSuite(() { + defineReflectiveTests(ConvertToInitializingFormalBulkTest); defineReflectiveTests(ConvertToInitializingFormalTest); }); } +@reflectiveTest +class ConvertToInitializingFormalBulkTest extends BulkFixProcessorTest { + @override + String get lintCode => LintNames.prefer_initializing_formals; + + Future test_inBody_contiguous() async { + await resolveTestCode(r''' +class C { + int? a; + int? b; + int? c; + int? d; + C(int? a, int? b, int? c, int? d) { + this.a = a; + this.b = b; + this.c = c; + this.d = d; + } +} +'''); + // Doesn't fix b because both a and b try to remove the whitespace between + // the first two statements. + await assertHasFix(r''' +class C { + int? a; + int? b; + int? c; + int? d; + C(this.a, int? b, this.c, this.d) { + this.b = b; + } +} +'''); + } + + Future test_inBody_noncontiguous() async { + await resolveTestCode(r''' +class C { + int? a; + int? b; + int? c; + C(int? a, int? b, int? c) { + this.a = a; + print(1); + this.b = b; + print(2); + this.c = c; + } +} +'''); + await assertHasFix(r''' +class C { + int? a; + int? b; + int? c; + C(this.a, this.b, this.c) { + print(1); + print(2); + } +} +'''); + } + + Future test_inInitializer_contiguous() async { + await resolveTestCode(r''' +class C { + int? a; + int? b; + int? c; + int? d; + C(int? a, int? b, int? c, int? d) : a = a, b = b, c = c, d = d; +} +'''); + // Doesn't fix b because both a and b try to remove the same comma. + await assertHasFix(r''' +class C { + int? a; + int? b; + int? c; + int? d; + C(this.a, int? b, this.c, this.d) : b = b; +} +'''); + } + + Future test_inInitializer_noncontiguous() async { + await resolveTestCode(r''' +class C { + int? a; + int x; + int? b; + int y; + int? c; + C(int? a, int? b, int? c) : a = a, x = 1, b = b, y = 2, c = c; +} +'''); + await assertHasFix(r''' +class C { + int? a; + int x; + int? b; + int y; + int? c; + C(this.a, this.b, this.c) : x = 1, y = 2; +} +'''); + } +} + @reflectiveTest class ConvertToInitializingFormalTest extends FixProcessorLintTest { @override diff --git a/pkg/dartdev/test/commands/fix_test.dart b/pkg/dartdev/test/commands/fix_test.dart index 62f44a0703d..d4e8a2ee849 100644 --- a/pkg/dartdev/test/commands/fix_test.dart +++ b/pkg/dartdev/test/commands/fix_test.dart @@ -658,6 +658,58 @@ linter: ); }); + test('--apply (contiguous initializing formals require a second ' + 'pass)', () async { + // We can't convert contiguous leading initializers or assignments to + // initializing formals because the edits collide on the comma or + // whitespace between them. But two passes is enough to catch them all. + p = project( + mainSrc: ''' +class C { + int a; + int b; + int c; + int d; + int e; + int f; + C(int a, int b, int c, int d, int e, int f) : a = a, b = b, c = c { + this.d = d; + this.e = e; + this.f = f; + } +} +''', + analysisOptions: ''' +linter: + rules: + - prefer_initializing_formals +''', + ); + var result = await p!.runFix(['--apply', '.'], workingDir: p!.dirPath); + expect(result.exitCode, 0); + expect(result.stderr, isEmpty); + expect( + result.stdout, + stringContainsInOrderWithVariableBullets([ + 'Applying fixes...', + 'lib${Platform.pathSeparator}main.dart', + ' prefer_initializing_formals $bullet 6', + '6 fixes made in 1 file.', + ]), + ); + expect(p!.findFile('lib/main.dart')!.readAsStringSync(), ''' +class C { + int a; + int b; + int c; + int d; + int e; + int f; + C(this.a, this.b, this.c, this.d, this.e, this.f); +} +'''); + }); + group('AOT mode', () { test('--use-aot-snapshot', () async { p = project(