Skip to content

Commit edce10b

Browse files
bwilkersonCommit Queue
authored andcommitted
Add an assist to convert from a field formal parameter to a normal parameter
Fixes #60852 Change-Id: Iffc674e61b4df16e80ca71cfd18e12c548770b54 Reviewed-on: https://dart-review.googlesource.com/c/sdk/+/433220 Reviewed-by: Samuel Rawlins <[email protected]> Reviewed-by: Brian Wilkerson <[email protected]> Commit-Queue: Brian Wilkerson <[email protected]>
1 parent 9d6c5d4 commit edce10b

File tree

5 files changed

+374
-0
lines changed

5 files changed

+374
-0
lines changed

pkg/analysis_server/lib/src/services/correction/assist.dart

Lines changed: 5 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -51,6 +51,11 @@ abstract final class DartAssistKind {
5151
DartAssistKindPriority.DEFAULT,
5252
'Convert to line documentation comment',
5353
);
54+
static const CONVERT_FIELD_FORMAL_TO_NORMAL = AssistKind(
55+
'dart.assist.convert.fieldFormalToNormal',
56+
DartAssistKindPriority.DEFAULT,
57+
'Convert to a normal parameter',
58+
);
5459
static const CONVERT_INTO_ASYNC_BODY = AssistKind(
5560
'dart.assist.convert.bodyToAsync',
5661
DartAssistKindPriority.PRIORITY,

pkg/analysis_server/lib/src/services/correction/assist_internal.dart

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -13,6 +13,7 @@ import 'package:analysis_server/src/services/correction/dart/convert_class_to_mi
1313
import 'package:analysis_server/src/services/correction/dart/convert_conditional_expression_to_if_element.dart';
1414
import 'package:analysis_server/src/services/correction/dart/convert_documentation_into_block.dart';
1515
import 'package:analysis_server/src/services/correction/dart/convert_documentation_into_line.dart';
16+
import 'package:analysis_server/src/services/correction/dart/convert_field_formal_to_normal.dart';
1617
import 'package:analysis_server/src/services/correction/dart/convert_into_async_body.dart';
1718
import 'package:analysis_server/src/services/correction/dart/convert_into_block_body.dart';
1819
import 'package:analysis_server/src/services/correction/dart/convert_into_final_field.dart';
@@ -90,6 +91,7 @@ const Set<ProducerGenerator> _builtInGenerators = {
9091
ConvertConditionalExpressionToIfElement.new,
9192
ConvertDocumentationIntoBlock.new,
9293
ConvertDocumentationIntoLine.new,
94+
ConvertFieldFormalToNormal.new,
9395
ConvertIfStatementToSwitchStatement.new,
9496
ConvertIntoAsyncBody.new,
9597
ConvertIntoBlockBody.missingBody,
Lines changed: 64 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,64 @@
1+
// Copyright (c) 2025, the Dart project authors. Please see the AUTHORS file
2+
// for details. All rights reserved. Use of this source code is governed by a
3+
// BSD-style license that can be found in the LICENSE file.
4+
5+
import 'package:analysis_server/src/services/correction/assist.dart';
6+
import 'package:analysis_server_plugin/edit/dart/correction_producer.dart';
7+
import 'package:analyzer/src/dart/ast/ast.dart';
8+
import 'package:analyzer_plugin/utilities/assist/assist.dart';
9+
import 'package:analyzer_plugin/utilities/change_builder/change_builder_core.dart';
10+
import 'package:analyzer_plugin/utilities/range_factory.dart';
11+
12+
class ConvertFieldFormalToNormal extends ResolvedCorrectionProducer {
13+
ConvertFieldFormalToNormal({required super.context});
14+
15+
@override
16+
CorrectionApplicability get applicability =>
17+
// This isn't offered as a fix.
18+
CorrectionApplicability.singleLocation;
19+
20+
@override
21+
AssistKind get assistKind => DartAssistKind.CONVERT_FIELD_FORMAL_TO_NORMAL;
22+
23+
@override
24+
Future<void> compute(ChangeBuilder builder) async {
25+
var parameter = node;
26+
if (parameter is! FieldFormalParameter) {
27+
return;
28+
}
29+
var field = parameter.declaredFragment?.element.field2;
30+
if (field == null) {
31+
return;
32+
}
33+
var constructor =
34+
parameter.thisOrAncestorOfType<FormalParameterList>()?.parent;
35+
if (constructor is! ConstructorDeclaration) {
36+
return;
37+
}
38+
var initializers = constructor.initializers;
39+
await builder.addDartFileEdit(file, (builder) {
40+
var thisRange = range.startEnd(parameter.thisKeyword, parameter.period);
41+
var type = parameter.type;
42+
if (type == null) {
43+
// The type of the field needs to be added to the declaration.
44+
builder.addReplacement(thisRange, (builder) {
45+
builder.writeType(field.type);
46+
builder.write(' ');
47+
});
48+
} else {
49+
builder.addDeletion(thisRange);
50+
}
51+
int offset;
52+
String prefix;
53+
if (initializers.isEmpty) {
54+
offset = constructor.parameters.end;
55+
prefix = ' :';
56+
} else {
57+
offset = initializers.last.end;
58+
prefix = ',';
59+
}
60+
var name = parameter.name.lexeme;
61+
builder.addSimpleInsertion(offset, '$prefix $name = $name');
62+
});
63+
}
64+
}
Lines changed: 300 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,300 @@
1+
// Copyright (c) 2025, the Dart project authors. Please see the AUTHORS file
2+
// for details. All rights reserved. Use of this source code is governed by a
3+
// BSD-style license that can be found in the LICENSE file.
4+
5+
import 'package:analysis_server/src/services/correction/assist.dart';
6+
import 'package:analyzer_plugin/utilities/assist/assist.dart';
7+
import 'package:test_reflective_loader/test_reflective_loader.dart';
8+
9+
import 'assist_processor.dart';
10+
11+
void main() {
12+
defineReflectiveSuite(() {
13+
defineReflectiveTests(ConvertFieldFormalToNormalTest);
14+
});
15+
}
16+
17+
@reflectiveTest
18+
class ConvertFieldFormalToNormalTest extends AssistProcessorTest {
19+
@override
20+
AssistKind get kind => DartAssistKind.CONVERT_FIELD_FORMAL_TO_NORMAL;
21+
22+
Future<void> test_optionalNamed_explicitType() async {
23+
await resolveTestCode('''
24+
class C {
25+
num f;
26+
27+
C({int this.^f = 0});
28+
}
29+
''');
30+
await assertHasAssist('''
31+
class C {
32+
num f;
33+
34+
C({int f = 0}) : f = f;
35+
}
36+
''');
37+
}
38+
39+
Future<void> test_optionalNamed_implicitType() async {
40+
await resolveTestCode('''
41+
class C {
42+
int f;
43+
44+
C({this.^f = 0});
45+
}
46+
''');
47+
await assertHasAssist('''
48+
class C {
49+
int f;
50+
51+
C({int f = 0}) : f = f;
52+
}
53+
''');
54+
}
55+
56+
Future<void> test_optionalPositional_explicitType() async {
57+
await resolveTestCode('''
58+
class C {
59+
num f;
60+
61+
C([int this.^f = 0]);
62+
}
63+
''');
64+
await assertHasAssist('''
65+
class C {
66+
num f;
67+
68+
C([int f = 0]) : f = f;
69+
}
70+
''');
71+
}
72+
73+
Future<void> test_optionalPositional_implicitType() async {
74+
await resolveTestCode('''
75+
class C {
76+
int f;
77+
78+
C([this.^f = 0]);
79+
}
80+
''');
81+
await assertHasAssist('''
82+
class C {
83+
int f;
84+
85+
C([int f = 0]) : f = f;
86+
}
87+
''');
88+
}
89+
90+
Future<void> test_position_first() async {
91+
await resolveTestCode('''
92+
class C {
93+
int e;
94+
int f;
95+
int g;
96+
97+
C(this.^e, this.f, this.g);
98+
}
99+
''');
100+
await assertHasAssist('''
101+
class C {
102+
int e;
103+
int f;
104+
int g;
105+
106+
C(int e, this.f, this.g) : e = e;
107+
}
108+
''');
109+
}
110+
111+
Future<void> test_position_last() async {
112+
await resolveTestCode('''
113+
class C {
114+
int e;
115+
int f;
116+
int g;
117+
118+
C(this.e, this.f, this.^g);
119+
}
120+
''');
121+
await assertHasAssist('''
122+
class C {
123+
int e;
124+
int f;
125+
int g;
126+
127+
C(this.e, this.f, int g) : g = g;
128+
}
129+
''');
130+
}
131+
132+
Future<void> test_position_last_beforeNamed() async {
133+
await resolveTestCode('''
134+
class C {
135+
int e;
136+
int f;
137+
int g;
138+
139+
C(this.e, this.^f, {this.g = 0});
140+
}
141+
''');
142+
await assertHasAssist('''
143+
class C {
144+
int e;
145+
int f;
146+
int g;
147+
148+
C(this.e, int f, {this.g = 0}) : f = f;
149+
}
150+
''');
151+
}
152+
153+
Future<void> test_position_last_beforeOptional() async {
154+
await resolveTestCode('''
155+
class C {
156+
int e;
157+
int f;
158+
int g;
159+
160+
C(this.e, this.^f, [this.g = 0]);
161+
}
162+
''');
163+
await assertHasAssist('''
164+
class C {
165+
int e;
166+
int f;
167+
int g;
168+
169+
C(this.e, int f, [this.g = 0]) : f = f;
170+
}
171+
''');
172+
}
173+
174+
Future<void> test_position_middle() async {
175+
await resolveTestCode('''
176+
class C {
177+
int e;
178+
int f;
179+
int g;
180+
181+
C(this.e, this.^f, this.g);
182+
}
183+
''');
184+
await assertHasAssist('''
185+
class C {
186+
int e;
187+
int f;
188+
int g;
189+
190+
C(this.e, int f, this.g) : f = f;
191+
}
192+
''');
193+
}
194+
195+
Future<void> test_requiredNamed_explicitType() async {
196+
await resolveTestCode('''
197+
class C {
198+
num f;
199+
200+
C({required int this.^f});
201+
}
202+
''');
203+
await assertHasAssist('''
204+
class C {
205+
num f;
206+
207+
C({required int f}) : f = f;
208+
}
209+
''');
210+
}
211+
212+
Future<void> test_requiredNamed_implicitType() async {
213+
await resolveTestCode('''
214+
class C {
215+
int f;
216+
217+
C({required this.^f});
218+
}
219+
''');
220+
await assertHasAssist('''
221+
class C {
222+
int f;
223+
224+
C({required int f}) : f = f;
225+
}
226+
''');
227+
}
228+
229+
Future<void> test_requiredPositional_explicitType() async {
230+
await resolveTestCode('''
231+
class C {
232+
num f;
233+
234+
C(int this.^f);
235+
}
236+
''');
237+
await assertHasAssist('''
238+
class C {
239+
num f;
240+
241+
C(int f) : f = f;
242+
}
243+
''');
244+
}
245+
246+
Future<void> test_requiredPositional_implicitType() async {
247+
await resolveTestCode('''
248+
class C {
249+
int f;
250+
251+
C(this.^f);
252+
}
253+
''');
254+
await assertHasAssist('''
255+
class C {
256+
int f;
257+
258+
C(int f) : f = f;
259+
}
260+
''');
261+
}
262+
263+
Future<void> test_withExistingInitializer() async {
264+
await resolveTestCode('''
265+
class C {
266+
int e;
267+
int f;
268+
int g;
269+
270+
C(this.e, this.^f, int g) : g = g;
271+
}
272+
''');
273+
await assertHasAssist('''
274+
class C {
275+
int e;
276+
int f;
277+
int g;
278+
279+
C(this.e, int f, int g) : g = g, f = f;
280+
}
281+
''');
282+
}
283+
284+
Future<void> test_withFunctionTypedField() async {
285+
await resolveTestCode('''
286+
class C {
287+
void Function() f;
288+
289+
C({required this.f^()});
290+
}
291+
''');
292+
await assertHasAssist('''
293+
class C {
294+
void Function() f;
295+
296+
C({required void Function() f()}) : f = f;
297+
}
298+
''');
299+
}
300+
}

0 commit comments

Comments
 (0)