Skip to content

Commit 11edab5

Browse files
chloestefantsovaCommit Queue
authored andcommitted
[analyzer] Add correction for adding '?' before null-aware elements
Part of #56989 Change-Id: I487790e796a37679fd5fe1c9bc1e72ccb6248249 Reviewed-on: https://dart-review.googlesource.com/c/sdk/+/405520 Reviewed-by: Keerti Parthasarathy <[email protected]> Commit-Queue: Chloe Stefantsova <[email protected]>
1 parent b6778c9 commit 11edab5

21 files changed

+662
-43
lines changed
Lines changed: 32 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,32 @@
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+
11+
class ConvertToNullAwareListElement extends ResolvedCorrectionProducer {
12+
ConvertToNullAwareListElement({required super.context});
13+
14+
@override
15+
CorrectionApplicability get applicability =>
16+
// TODO(applicability): comment on why.
17+
CorrectionApplicability
18+
.singleLocation;
19+
20+
@override
21+
FixKind get fixKind => DartFixKind.CONVERT_TO_NULL_AWARE_LIST_ELEMENT;
22+
23+
@override
24+
Future<void> compute(ChangeBuilder builder) async {
25+
var parent = coveringNode?.parent;
26+
if (parent is ListLiteral && coveringNode is! NullAwareElement) {
27+
await builder.addDartFileEdit(file, (builder) {
28+
builder.addSimpleInsertion(coveringNode!.offset, '?');
29+
});
30+
}
31+
}
32+
}
Lines changed: 59 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,59 @@
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+
11+
class ConvertToNullAwareMapEntryKey extends ResolvedCorrectionProducer {
12+
ConvertToNullAwareMapEntryKey({required super.context});
13+
14+
@override
15+
CorrectionApplicability get applicability =>
16+
// TODO(applicability): comment on why.
17+
CorrectionApplicability
18+
.singleLocation;
19+
20+
@override
21+
FixKind get fixKind => DartFixKind.CONVERT_TO_NULL_AWARE_MAP_ENTRY_KEY;
22+
23+
@override
24+
Future<void> compute(ChangeBuilder builder) async {
25+
var parent = coveringNode?.parent;
26+
if (parent is MapLiteralEntry &&
27+
coveringNode == parent.key &&
28+
parent.keyQuestion == null) {
29+
await builder.addDartFileEdit(file, (builder) {
30+
builder.addSimpleInsertion(coveringNode!.offset, '?');
31+
});
32+
}
33+
}
34+
}
35+
36+
class ConvertToNullAwareMapEntryValue extends ResolvedCorrectionProducer {
37+
ConvertToNullAwareMapEntryValue({required super.context});
38+
39+
@override
40+
CorrectionApplicability get applicability =>
41+
// TODO(applicability): comment on why.
42+
CorrectionApplicability
43+
.singleLocation;
44+
45+
@override
46+
FixKind get fixKind => DartFixKind.CONVERT_TO_NULL_AWARE_MAP_ENTRY_VALUE;
47+
48+
@override
49+
Future<void> compute(ChangeBuilder builder) async {
50+
var parent = coveringNode?.parent;
51+
if (parent is MapLiteralEntry &&
52+
coveringNode == parent.value &&
53+
parent.valueQuestion == null) {
54+
await builder.addDartFileEdit(file, (builder) {
55+
builder.addSimpleInsertion(coveringNode!.offset, '?');
56+
});
57+
}
58+
}
59+
}
Lines changed: 34 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,34 @@
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+
11+
class ConvertToNullAwareSetElement extends ResolvedCorrectionProducer {
12+
ConvertToNullAwareSetElement({required super.context});
13+
14+
@override
15+
CorrectionApplicability get applicability =>
16+
// TODO(applicability): comment on why.
17+
CorrectionApplicability
18+
.singleLocation;
19+
20+
@override
21+
FixKind get fixKind => DartFixKind.CONVERT_TO_NULL_AWARE_SET_ELEMENT;
22+
23+
@override
24+
Future<void> compute(ChangeBuilder builder) async {
25+
var parent = coveringNode?.parent;
26+
if (parent is SetOrMapLiteral &&
27+
parent.isSet &&
28+
coveringNode is! NullAwareElement) {
29+
await builder.addDartFileEdit(file, (builder) {
30+
builder.addSimpleInsertion(coveringNode!.offset, '?');
31+
});
32+
}
33+
}
34+
}

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

Lines changed: 9 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1019,8 +1019,10 @@ CompileTimeErrorCode.LATE_FINAL_FIELD_WITH_CONST_CONSTRUCTOR:
10191019
Remove `const`.
10201020
CompileTimeErrorCode.LATE_FINAL_LOCAL_ALREADY_ASSIGNED:
10211021
status: hasFix
1022+
CompileTimeErrorCode.LIST_ELEMENT_TYPE_NOT_ASSIGNABLE_NULLABILITY:
1023+
status: hasFix
10221024
CompileTimeErrorCode.LIST_ELEMENT_TYPE_NOT_ASSIGNABLE:
1023-
status: noFix
1025+
status: needsEvaluation
10241026
CompileTimeErrorCode.MACRO_DECLARATIONS_PHASE_INTROSPECTION_CYCLE:
10251027
status: needsEvaluation
10261028
CompileTimeErrorCode.MACRO_DEFINITION_APPLICATION_SAME_LIBRARY_CYCLE:
@@ -1049,8 +1051,12 @@ CompileTimeErrorCode.MAIN_IS_NOT_FUNCTION:
10491051
status: noFix
10501052
CompileTimeErrorCode.MAP_ENTRY_NOT_IN_MAP:
10511053
status: noFix
1054+
CompileTimeErrorCode.MAP_KEY_TYPE_NOT_ASSIGNABLE_NULLABILITY:
1055+
status: hasFix
10521056
CompileTimeErrorCode.MAP_KEY_TYPE_NOT_ASSIGNABLE:
10531057
status: noFix
1058+
CompileTimeErrorCode.MAP_VALUE_TYPE_NOT_ASSIGNABLE_NULLABILITY:
1059+
status: hasFix
10541060
CompileTimeErrorCode.MAP_VALUE_TYPE_NOT_ASSIGNABLE:
10551061
status: noFix
10561062
CompileTimeErrorCode.MISSING_CONST_IN_LIST_LITERAL:
@@ -1444,6 +1450,8 @@ CompileTimeErrorCode.SET_ELEMENT_FROM_DEFERRED_LIBRARY:
14441450
status: needsFix
14451451
notes: |-
14461452
Remove the `deferred` keyword from the import.
1453+
CompileTimeErrorCode.SET_ELEMENT_TYPE_NOT_ASSIGNABLE_NULLABILITY:
1454+
status: hasFix
14471455
CompileTimeErrorCode.SET_ELEMENT_TYPE_NOT_ASSIGNABLE:
14481456
status: noFix
14491457
CompileTimeErrorCode.SHARED_DEFERRED_PREFIX:

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

Lines changed: 20 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -577,6 +577,26 @@ abstract final class DartFixKind {
577577
DartFixKindPriority.standard,
578578
"Convert to use '?.'",
579579
);
580+
static const CONVERT_TO_NULL_AWARE_LIST_ELEMENT = FixKind(
581+
'dart.fix.convert.toNullAwareListElement',
582+
DartFixKindPriority.standard,
583+
"Convert to use '?'",
584+
);
585+
static const CONVERT_TO_NULL_AWARE_MAP_ENTRY_KEY = FixKind(
586+
'dart.fix.convert.toNullAwareMapEntryKey',
587+
DartFixKindPriority.standard,
588+
"Convert to use '?'",
589+
);
590+
static const CONVERT_TO_NULL_AWARE_MAP_ENTRY_VALUE = FixKind(
591+
'dart.fix.convert.toNullAwareMayEntryValue',
592+
DartFixKindPriority.standard,
593+
"Convert to use '?'",
594+
);
595+
static const CONVERT_TO_NULL_AWARE_SET_ELEMENT = FixKind(
596+
'dart.fix.convert.toNullAwareSetElement',
597+
DartFixKindPriority.standard,
598+
"Convert to use '?'",
599+
);
580600
static const CONVERT_TO_NULL_AWARE_MULTI = FixKind(
581601
'dart.fix.convert.toNullAware.multi',
582602
DartFixKindPriority.inFile,

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

Lines changed: 15 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -69,6 +69,9 @@ import 'package:analysis_server/src/services/correction/dart/convert_to_int_lite
6969
import 'package:analysis_server/src/services/correction/dart/convert_to_map_literal.dart';
7070
import 'package:analysis_server/src/services/correction/dart/convert_to_named_arguments.dart';
7171
import 'package:analysis_server/src/services/correction/dart/convert_to_null_aware.dart';
72+
import 'package:analysis_server/src/services/correction/dart/convert_to_null_aware_list_element.dart';
73+
import 'package:analysis_server/src/services/correction/dart/convert_to_null_aware_map_entry.dart';
74+
import 'package:analysis_server/src/services/correction/dart/convert_to_null_aware_set_element.dart';
7275
import 'package:analysis_server/src/services/correction/dart/convert_to_null_aware_spread.dart';
7376
import 'package:analysis_server/src/services/correction/dart/convert_to_on_type.dart';
7477
import 'package:analysis_server/src/services/correction/dart/convert_to_package_import.dart';
@@ -848,6 +851,15 @@ final _builtInNonLintProducers = <ErrorCode, List<ProducerGenerator>>{
848851
CompileTimeErrorCode.LATE_FINAL_LOCAL_ALREADY_ASSIGNED: [
849852
MakeVariableNotFinal.new,
850853
],
854+
CompileTimeErrorCode.LIST_ELEMENT_TYPE_NOT_ASSIGNABLE_NULLABILITY: [
855+
ConvertToNullAwareListElement.new,
856+
],
857+
CompileTimeErrorCode.MAP_KEY_TYPE_NOT_ASSIGNABLE_NULLABILITY: [
858+
ConvertToNullAwareMapEntryKey.new,
859+
],
860+
CompileTimeErrorCode.MAP_VALUE_TYPE_NOT_ASSIGNABLE_NULLABILITY: [
861+
ConvertToNullAwareMapEntryValue.new,
862+
],
851863
CompileTimeErrorCode.MISSING_DEFAULT_VALUE_FOR_PARAMETER: [
852864
AddRequiredKeyword.new,
853865
MakeVariableNullable.new,
@@ -971,6 +983,9 @@ final _builtInNonLintProducers = <ErrorCode, List<ProducerGenerator>>{
971983
MakeReturnTypeNullable.new,
972984
ReplaceReturnType.new,
973985
],
986+
CompileTimeErrorCode.SET_ELEMENT_TYPE_NOT_ASSIGNABLE_NULLABILITY: [
987+
ConvertToNullAwareSetElement.new,
988+
],
974989
CompileTimeErrorCode.SUBTYPE_OF_BASE_IS_NOT_BASE_FINAL_OR_SEALED: [
975990
AddClassModifier.baseModifier,
976991
AddClassModifier.finalModifier,
Lines changed: 71 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,71 @@
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(ConvertToNullAwareListElementTest);
14+
});
15+
}
16+
17+
@reflectiveTest
18+
class ConvertToNullAwareListElementTest extends FixProcessorTest {
19+
@override
20+
FixKind get kind => DartFixKind.CONVERT_TO_NULL_AWARE_LIST_ELEMENT;
21+
22+
Future<void> test_const_list_withGeneralUnassignable() async {
23+
// Check that the fix isn't suggested when the assignability issue can't be
24+
// fixed by removing nullability.
25+
await resolveTestCode('''
26+
void f () {
27+
const <String>[0];
28+
}
29+
''');
30+
await assertNoFix();
31+
}
32+
33+
Future<void> test_const_list_withNullabilityUnassignable() async {
34+
await resolveTestCode('''
35+
void f () {
36+
const String? s = null;
37+
const <String>[s];
38+
}
39+
''');
40+
await assertHasFix('''
41+
void f () {
42+
const String? s = null;
43+
const <String>[?s];
44+
}
45+
''', errorFilter: (error) => error.message.contains('String?'));
46+
}
47+
48+
Future<void> test_nonConst_list_withGeneralUnassignable() async {
49+
// Check that the fix isn't suggested when the assignability issue can't be
50+
// fixed by removing nullability.
51+
await resolveTestCode('''
52+
void f (int arg) {
53+
<String>[arg];
54+
}
55+
''');
56+
await assertNoFix();
57+
}
58+
59+
Future<void> test_nonConst_list_withNullabilityUnassignable() async {
60+
await resolveTestCode('''
61+
void f (String? arg) {
62+
<String>[arg];
63+
}
64+
''');
65+
await assertHasFix('''
66+
void f (String? arg) {
67+
<String>[?arg];
68+
}
69+
''');
70+
}
71+
}

0 commit comments

Comments
 (0)