Skip to content

Commit db96984

Browse files
chloestefantsovaCommit Queue
authored andcommitted
[analyzer] Add correction for removing '?' before elements
Part of #56989 Change-Id: I36543863eb76724cb23b4521780eccd130750014 Reviewed-on: https://dart-review.googlesource.com/c/sdk/+/410042 Commit-Queue: Phil Quitslund <[email protected]> Reviewed-by: Brian Wilkerson <[email protected]> Reviewed-by: Phil Quitslund <[email protected]>
1 parent c04de77 commit db96984

File tree

7 files changed

+306
-4
lines changed

7 files changed

+306
-4
lines changed
Lines changed: 63 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,63 @@
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/fix.dart';
6+
import 'package:analysis_server_plugin/edit/dart/correction_producer.dart';
7+
import 'package:analyzer/dart/ast/ast.dart';
8+
import 'package:analyzer_plugin/utilities/change_builder/change_builder_core.dart';
9+
import 'package:analyzer_plugin/utilities/fixes/fixes.dart';
10+
import 'package:analyzer_plugin/utilities/range_factory.dart';
11+
12+
class ReplaceWithNotNullAwareElementOrEntry extends ResolvedCorrectionProducer {
13+
final _ReplaceWithNotNullAwareElementOrEntryKind _kind;
14+
15+
ReplaceWithNotNullAwareElementOrEntry.entry({required super.context})
16+
: _kind = _ReplaceWithNotNullAwareElementOrEntryKind.entry;
17+
18+
ReplaceWithNotNullAwareElementOrEntry.mapKey({required super.context})
19+
: _kind = _ReplaceWithNotNullAwareElementOrEntryKind.mapKey;
20+
21+
ReplaceWithNotNullAwareElementOrEntry.mapValue({required super.context})
22+
: _kind = _ReplaceWithNotNullAwareElementOrEntryKind.mapValue;
23+
24+
@override
25+
CorrectionApplicability get applicability =>
26+
CorrectionApplicability.automatically;
27+
28+
@override
29+
FixKind get fixKind =>
30+
DartFixKind.REPLACE_WITH_NOT_NULL_AWARE_ELEMENT_OR_ENTRY;
31+
32+
@override
33+
FixKind get multiFixKind =>
34+
DartFixKind.REPLACE_WITH_NOT_NULL_AWARE_ELEMENT_OR_ENTRY_MULTI;
35+
36+
@override
37+
Future<void> compute(ChangeBuilder builder) async {
38+
var node = coveringNode;
39+
switch (_kind) {
40+
case _ReplaceWithNotNullAwareElementOrEntryKind.entry:
41+
// This covers both list and set entries.
42+
if (node is NullAwareElement) {
43+
await builder.addDartFileEdit(file, (builder) {
44+
builder.addDeletion(range.token(node.question));
45+
});
46+
}
47+
case _ReplaceWithNotNullAwareElementOrEntryKind.mapKey:
48+
if (node is MapLiteralEntry && node.keyQuestion != null) {
49+
await builder.addDartFileEdit(file, (builder) {
50+
builder.addDeletion(range.token(node.keyQuestion!));
51+
});
52+
}
53+
case _ReplaceWithNotNullAwareElementOrEntryKind.mapValue:
54+
if (node is MapLiteralEntry && node.valueQuestion != null) {
55+
await builder.addDartFileEdit(file, (builder) {
56+
builder.addDeletion(range.token(node.valueQuestion!));
57+
});
58+
}
59+
}
60+
}
61+
}
62+
63+
enum _ReplaceWithNotNullAwareElementOrEntryKind { entry, mapKey, mapValue }

pkg/analysis_server/lib/src/services/correction/error_fix_status.yaml

Lines changed: 3 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -3410,11 +3410,11 @@ StaticWarningCode.INVALID_NULL_AWARE_OPERATOR:
34103410
StaticWarningCode.INVALID_NULL_AWARE_OPERATOR_AFTER_SHORT_CIRCUIT:
34113411
status: hasFix
34123412
StaticWarningCode.INVALID_NULL_AWARE_ELEMENT:
3413-
status: needsEvaluation
3413+
status: hasFix
34143414
StaticWarningCode.INVALID_NULL_AWARE_MAP_ENTRY_KEY:
3415-
status: needsEvaluation
3415+
status: hasFix
34163416
StaticWarningCode.INVALID_NULL_AWARE_MAP_ENTRY_VALUE:
3417-
status: needsEvaluation
3417+
status: hasFix
34183418
StaticWarningCode.MISSING_ENUM_CONSTANT_IN_SWITCH:
34193419
status: hasFix
34203420
StaticWarningCode.UNNECESSARY_NON_NULL_ASSERTION:

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

Lines changed: 10 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1980,6 +1980,16 @@ abstract final class DartFixKind {
19801980
DartFixKindPriority.standard,
19811981
"Replace with '{0}'",
19821982
);
1983+
static const REPLACE_WITH_NOT_NULL_AWARE_ELEMENT_OR_ENTRY = FixKind(
1984+
'dart.fix.replace.withNotNullAwareElementOrEntry',
1985+
DartFixKindPriority.standard,
1986+
"Remove the '?'",
1987+
);
1988+
static const REPLACE_WITH_NOT_NULL_AWARE_ELEMENT_OR_ENTRY_MULTI = FixKind(
1989+
'dart.fix.replace.withNotNullAwareElementOrEntry.multi',
1990+
DartFixKindPriority.inFile,
1991+
"Remove the '?' everywhere in file",
1992+
);
19831993
static const REPLACE_WITH_NOT_NULL_AWARE_MULTI = FixKind(
19841994
'dart.fix.replace.withNotNullAware.multi',
19851995
DartFixKindPriority.inFile,

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

Lines changed: 10 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -226,6 +226,7 @@ import 'package:analysis_server/src/services/correction/dart/replace_with_is_emp
226226
import 'package:analysis_server/src/services/correction/dart/replace_with_is_nan.dart';
227227
import 'package:analysis_server/src/services/correction/dart/replace_with_named_constant.dart';
228228
import 'package:analysis_server/src/services/correction/dart/replace_with_not_null_aware.dart';
229+
import 'package:analysis_server/src/services/correction/dart/replace_with_not_null_aware_element_or_entry.dart';
229230
import 'package:analysis_server/src/services/correction/dart/replace_with_null_aware.dart';
230231
import 'package:analysis_server/src/services/correction/dart/replace_with_part_of_uri.dart';
231232
import 'package:analysis_server/src/services/correction/dart/replace_with_tear_off.dart';
@@ -1243,6 +1244,15 @@ final _builtInNonLintProducers = <ErrorCode, List<ProducerGenerator>>{
12431244
RemoveUnexpectedUnderscores.new,
12441245
],
12451246
StaticWarningCode.DEAD_NULL_AWARE_EXPRESSION: [RemoveDeadIfNull.new],
1247+
StaticWarningCode.INVALID_NULL_AWARE_ELEMENT: [
1248+
ReplaceWithNotNullAwareElementOrEntry.entry,
1249+
],
1250+
StaticWarningCode.INVALID_NULL_AWARE_MAP_ENTRY_KEY: [
1251+
ReplaceWithNotNullAwareElementOrEntry.mapKey,
1252+
],
1253+
StaticWarningCode.INVALID_NULL_AWARE_MAP_ENTRY_VALUE: [
1254+
ReplaceWithNotNullAwareElementOrEntry.mapValue,
1255+
],
12461256
StaticWarningCode.INVALID_NULL_AWARE_OPERATOR: [ReplaceWithNotNullAware.new],
12471257
StaticWarningCode.INVALID_NULL_AWARE_OPERATOR_AFTER_SHORT_CIRCUIT: [
12481258
ReplaceWithNotNullAware.new,
Lines changed: 212 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,212 @@
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/fix.dart';
6+
import 'package:analyzer_plugin/utilities/fixes/fixes.dart';
7+
import 'package:test_reflective_loader/test_reflective_loader.dart';
8+
9+
import 'fix_processor.dart';
10+
11+
void main() {
12+
defineReflectiveSuite(() {
13+
defineReflectiveTests(ReplaceWithNotNullAwareElementOrEntryBulkTest);
14+
defineReflectiveTests(ReplaceWithNotNullAwareElementOrEntryTest);
15+
});
16+
}
17+
18+
@reflectiveTest
19+
class ReplaceWithNotNullAwareElementOrEntryBulkTest
20+
extends BulkFixProcessorTest {
21+
Future<void> test_list() async {
22+
await resolveTestCode('''
23+
void f(int x, int y) {
24+
[?x, ?y];
25+
}
26+
''');
27+
await assertHasFix('''
28+
void f(int x, int y) {
29+
[x, y];
30+
}
31+
''');
32+
}
33+
34+
Future<void> test_map_key() async {
35+
await resolveTestCode('''
36+
Map<int, String> f(int x, int y) {
37+
var map = {?x: "", ?y: ""};
38+
return map;
39+
}
40+
''');
41+
await assertHasFix('''
42+
Map<int, String> f(int x, int y) {
43+
var map = {x: "", y: ""};
44+
return map;
45+
}
46+
''');
47+
}
48+
49+
Future<void> test_map_nonNullKey_nonNullValue() async {
50+
await resolveTestCode('''
51+
Map<Symbol, bool> f(Symbol key, bool value) {
52+
var map = {?key: ?value};
53+
return map;
54+
}
55+
''');
56+
await assertHasFix('''
57+
Map<Symbol, bool> f(Symbol key, bool value) {
58+
var map = {key: value};
59+
return map;
60+
}
61+
''');
62+
}
63+
64+
Future<void> test_map_nonNullKey_nonNullValue_multiple() async {
65+
await resolveTestCode('''
66+
Map<int, String> f(int key1, String value1, int key2, String value2) {
67+
var map = {?key1: ?value1, ?key2: ?value2};
68+
return map;
69+
}
70+
''');
71+
await assertHasFix('''
72+
Map<int, String> f(int key1, String value1, int key2, String value2) {
73+
var map = {key1: value1, key2: value2};
74+
return map;
75+
}
76+
''');
77+
}
78+
79+
Future<void> test_map_nonNullKey_nullValue() async {
80+
await resolveTestCode('''
81+
Map<Symbol, bool> f(Symbol key, bool? value) {
82+
var map = {?key: ?value};
83+
return map;
84+
}
85+
''');
86+
await assertHasFix('''
87+
Map<Symbol, bool> f(Symbol key, bool? value) {
88+
var map = {key: ?value};
89+
return map;
90+
}
91+
''');
92+
}
93+
94+
Future<void> test_map_nullKey_nonNullValue() async {
95+
await resolveTestCode('''
96+
Map<Symbol, bool> f(Symbol? key, bool value) {
97+
var map = {?key: ?value};
98+
return map;
99+
}
100+
''');
101+
await assertHasFix('''
102+
Map<Symbol, bool> f(Symbol? key, bool value) {
103+
var map = {?key: value};
104+
return map;
105+
}
106+
''');
107+
}
108+
109+
Future<void> test_map_nullKey_nullValue() async {
110+
await resolveTestCode('''
111+
Map<Symbol, bool> f(Symbol? key, bool? value) {
112+
var map = {?key: ?value};
113+
return map;
114+
}
115+
''');
116+
await assertNoFix();
117+
}
118+
119+
Future<void> test_map_value() async {
120+
await resolveTestCode('''
121+
Map<Symbol, String> f(String x, String y) {
122+
var map = {#key1: ?x, #key2: ?y};
123+
return map;
124+
}
125+
''');
126+
await assertHasFix('''
127+
Map<Symbol, String> f(String x, String y) {
128+
var map = {#key1: x, #key2: y};
129+
return map;
130+
}
131+
''');
132+
}
133+
134+
Future<void> test_set() async {
135+
await resolveTestCode('''
136+
Set<int> f(int x, int y) {
137+
var set = {?x, ?y};
138+
return set;
139+
}
140+
''');
141+
await assertHasFix('''
142+
Set<int> f(int x, int y) {
143+
var set = {x, y};
144+
return set;
145+
}
146+
''');
147+
}
148+
}
149+
150+
@reflectiveTest
151+
class ReplaceWithNotNullAwareElementOrEntryTest extends FixProcessorTest {
152+
@override
153+
FixKind get kind => DartFixKind.REPLACE_WITH_NOT_NULL_AWARE_ELEMENT_OR_ENTRY;
154+
155+
Future<void> test_list() async {
156+
await resolveTestCode('''
157+
void f(int x) {
158+
[?x];
159+
}
160+
''');
161+
await assertHasFix('''
162+
void f(int x) {
163+
[x];
164+
}
165+
''');
166+
}
167+
168+
Future<void> test_map_key() async {
169+
await resolveTestCode('''
170+
Map<int, String> f(int x) {
171+
var map = {?x: ""};
172+
return map;
173+
}
174+
''');
175+
await assertHasFix('''
176+
Map<int, String> f(int x) {
177+
var map = {x: ""};
178+
return map;
179+
}
180+
''');
181+
}
182+
183+
Future<void> test_map_value() async {
184+
await resolveTestCode('''
185+
Map<String, num> f(double x) {
186+
var map = {"key": ?x};
187+
return map;
188+
}
189+
''');
190+
await assertHasFix('''
191+
Map<String, num> f(double x) {
192+
var map = {"key": x};
193+
return map;
194+
}
195+
''');
196+
}
197+
198+
Future<void> test_set() async {
199+
await resolveTestCode('''
200+
Set<int> f(int x) {
201+
var set = {?x};
202+
return set;
203+
}
204+
''');
205+
await assertHasFix('''
206+
Set<int> f(int x) {
207+
var set = {x};
208+
return set;
209+
}
210+
''');
211+
}
212+
}

pkg/analysis_server/test/src/services/correction/fix/test_all.dart

Lines changed: 3 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -286,6 +286,8 @@ import 'replace_with_is_empty_test.dart' as replace_with_is_empty;
286286
import 'replace_with_is_nan_test.dart' as replace_with_is_nan;
287287
import 'replace_with_is_not_empty_test.dart' as replace_with_is_not_empty;
288288
import 'replace_with_named_constant_test.dart' as replace_with_named_constant;
289+
import 'replace_with_not_null_aware_element_or_entry_test.dart'
290+
as replace_with_not_null_aware_element_or_entry;
289291
import 'replace_with_not_null_aware_test.dart' as replace_with_not_null_aware;
290292
import 'replace_with_null_aware_test.dart' as replace_with_null_aware;
291293
import 'replace_with_part_of_uri_test.dart' as replace_with_part_of_uri;
@@ -558,6 +560,7 @@ void main() {
558560
replace_with_is_not_empty.main();
559561
replace_with_named_constant.main();
560562
replace_with_not_null_aware.main();
563+
replace_with_not_null_aware_element_or_entry.main();
561564
replace_with_null_aware.main();
562565
replace_with_part_of_uri.main();
563566
replace_with_tear_off.main();

pkg/analyzer/lib/src/generated/error_verifier.dart

Lines changed: 5 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -6668,7 +6668,11 @@ class LibraryVerificationContext {
66686668

66696669
/// Signals the kind of the null-aware element or entry observed in list, set,
66706670
/// or map literals.
6671-
enum _NullAwareElementOrMapEntryKind { element, mapEntryKey, mapEntryValue }
6671+
enum _NullAwareElementOrMapEntryKind {
6672+
element,
6673+
mapEntryKey,
6674+
mapEntryValue,
6675+
}
66726676

66736677
/// Recursively visits a type annotation, looking uninstantiated bounds.
66746678
class _UninstantiatedBoundChecker extends RecursiveAstVisitor<void> {

0 commit comments

Comments
 (0)