Add quick fix to remove unnecessary null-aware assignments
Fixes https://github.com/dart-lang/sdk/issues/41514
Change-Id: I29398feae3ad3a83ced722a876c6f6416ef46da8
Reviewed-on: https://dart-review.googlesource.com/c/sdk/+/169581
Commit-Queue: Samuel Rawlins <srawlins@google.com>
Reviewed-by: Konstantin Shcheglov <scheglov@google.com>
diff --git a/pkg/analysis_server/lib/src/services/correction/dart/remove_dead_if_null.dart b/pkg/analysis_server/lib/src/services/correction/dart/remove_dead_if_null.dart
index bf576ec..17e9a2d 100644
--- a/pkg/analysis_server/lib/src/services/correction/dart/remove_dead_if_null.dart
+++ b/pkg/analysis_server/lib/src/services/correction/dart/remove_dead_if_null.dart
@@ -6,6 +6,8 @@
import 'package:analysis_server/src/services/correction/fix.dart';
import 'package:analyzer/dart/ast/ast.dart';
import 'package:analyzer/dart/ast/token.dart';
+import 'package:analyzer/dart/element/element.dart';
+import 'package:analyzer/source/source_range.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';
@@ -16,41 +18,44 @@
@override
Future<void> compute(ChangeBuilder builder) async {
- //
- // Find the dead if-null expression.
- //
- BinaryExpression findIfNull() {
- var child = node;
- var parent = node.parent;
- while (parent != null) {
- if (parent is BinaryExpression &&
- parent.operator.type == TokenType.QUESTION_QUESTION &&
- parent.rightOperand == child) {
- return parent;
- }
- child = parent;
- parent = parent.parent;
- }
- return null;
- }
-
- var expression = findIfNull();
- if (expression == null) {
+ var sourceRange = findIfNull();
+ if (sourceRange == null) {
return;
}
- //
- // Extract the information needed to build the edit.
- //
- var sourceRange =
- range.endEnd(expression.leftOperand, expression.rightOperand);
- //
- // Build the edit.
- //
+
await builder.addDartFileEdit(file, (builder) {
builder.addDeletion(sourceRange);
});
}
- /// Return an instance of this class. Used as a tear-off in `FixProcessor`.
+ /// Finds the dead if-null expression above [node].
+ SourceRange findIfNull() {
+ var child = node;
+ var parent = node.parent;
+ while (parent != null) {
+ if (parent is BinaryExpression &&
+ parent.operator.type == TokenType.QUESTION_QUESTION &&
+ parent.rightOperand == child) {
+ return range.endEnd(parent.leftOperand, parent.rightOperand);
+ }
+ if (parent is AssignmentExpression &&
+ parent.operator.type == TokenType.QUESTION_QUESTION_EQ &&
+ parent.rightHandSide == child) {
+ var assignee = parent.leftHandSide;
+ if (parent.parent is ExpressionStatement &&
+ assignee is SimpleIdentifier &&
+ assignee.staticElement is PromotableElement) {
+ return utils.getLinesRange(range.node(parent.parent));
+ } else {
+ return range.endEnd(parent.leftHandSide, parent.rightHandSide);
+ }
+ }
+ child = parent;
+ parent = parent.parent;
+ }
+ return null;
+ }
+
+ /// Returns an instance of this class. Used as a tear-off in `FixProcessor`.
static RemoveDeadIfNull newInstance() => RemoveDeadIfNull();
}
diff --git a/pkg/analysis_server/test/src/services/correction/fix/remove_if_null_operator_test.dart b/pkg/analysis_server/test/src/services/correction/fix/remove_if_null_operator_test.dart
index 3797ce9..3f86e17 100644
--- a/pkg/analysis_server/test/src/services/correction/fix/remove_if_null_operator_test.dart
+++ b/pkg/analysis_server/test/src/services/correction/fix/remove_if_null_operator_test.dart
@@ -12,12 +12,78 @@
void main() {
defineReflectiveSuite(() {
+ defineReflectiveTests(DeadNullAwareAssignmentExpressionTest);
defineReflectiveTests(DeadNullAwareExpressionTest);
defineReflectiveTests(UnnecessaryNullInIfNullOperatorsTest);
});
}
@reflectiveTest
+class DeadNullAwareAssignmentExpressionTest extends FixProcessorTest
+ with WithNullSafetyMixin {
+ @override
+ FixKind get kind => DartFixKind.REMOVE_IF_NULL_OPERATOR;
+
+ Future<void> test_assignmentExpression_nonPromotable() async {
+ await resolveTestCode('''
+class C {
+ int a = 1;
+ void f(int b) {
+ a ??= b;
+ }
+}
+''');
+ await assertHasFix('''
+class C {
+ int a = 1;
+ void f(int b) {
+ a;
+ }
+}
+''');
+ }
+
+ Future<void> test_assignmentExpression_promotable() async {
+ await resolveTestCode('''
+void f(int a, int b) {
+ a ??= b;
+}
+''');
+ await assertHasFix('''
+void f(int a, int b) {
+}
+''');
+ }
+
+ Future<void> test_immediateChild() async {
+ await resolveTestCode('''
+void f(int a, int b) => a ??= b;
+''');
+ await assertHasFix('''
+void f(int a, int b) => a;
+''');
+ }
+
+ Future<void> test_nestedChild() async {
+ await resolveTestCode('''
+void f(int a, int b) => a ??= b * 2 + 1;
+''');
+ await assertHasFix('''
+void f(int a, int b) => a;
+''');
+ }
+
+ Future<void> test_nestedChild_onRight() async {
+ await resolveTestCode('''
+void f(int a, int b, int c) => a = b ??= c;
+''');
+ await assertHasFix('''
+void f(int a, int b, int c) => a = b;
+''');
+ }
+}
+
+@reflectiveTest
class DeadNullAwareExpressionTest extends FixProcessorTest
with WithNullSafetyMixin {
@override