[analysis_server] Handle missing `null` in missing enum case fix
When switching over a nullable expression, the `null` value should also
be handled for exhaustiveness.
The analyzer reports a lint for missing nulls, but the correction
producer was unable to fix the problem as it only adds missing enum
constants.
This change makes the fix producer add missing null cases.
Change-Id: I92065b36b6a370d5165e302d84391568894c5dc5
Reviewed-on: https://dart-review.googlesource.com/c/sdk/+/294981
Reviewed-by: Konstantin Shcheglov <scheglov@google.com>
Commit-Queue: Konstantin Shcheglov <scheglov@google.com>
diff --git a/pkg/analysis_server/lib/src/services/correction/dart/add_missing_enum_case_clauses.dart b/pkg/analysis_server/lib/src/services/correction/dart/add_missing_enum_case_clauses.dart
index ef59c26..f73a6ea 100644
--- a/pkg/analysis_server/lib/src/services/correction/dart/add_missing_enum_case_clauses.dart
+++ b/pkg/analysis_server/lib/src/services/correction/dart/add_missing_enum_case_clauses.dart
@@ -33,7 +33,13 @@
String? enumName;
var prefix = '';
- var enumConstantNames = <String>[];
+
+ // The missing enum case clauses can be caused by a missing enum case or, if
+ // the expression is nullable, a missing `case null` entry. We first collect
+ // all cases and then remove the ones that are already present.
+ var unhandledEnumCases = <String>[];
+ var unhandledNullValue = false;
+
var expressionType = statement.expression.staticType;
if (expressionType is InterfaceType) {
var enumElement = expressionType.element;
@@ -41,10 +47,14 @@
enumName = enumElement.name;
for (var field in enumElement.fields) {
if (field.isEnumConstant) {
- enumConstantNames.add(field.name);
+ unhandledEnumCases.add(field.name);
}
}
prefix = _importPrefix(enumElement);
+
+ if (typeSystem.isNullable(expressionType)) {
+ unhandledNullValue = true;
+ }
}
}
if (enumName == null) {
@@ -56,12 +66,14 @@
if (expression is Identifier) {
var element = expression.staticElement;
if (element is PropertyAccessorElement) {
- enumConstantNames.remove(element.name);
+ unhandledEnumCases.remove(element.name);
}
+ } else if (expression is NullLiteral) {
+ unhandledNullValue = false;
}
}
}
- if (enumConstantNames.isEmpty) {
+ if (!unhandledNullValue && unhandledEnumCases.isEmpty) {
return;
}
@@ -79,17 +91,11 @@
// TODO(brianwilkerson) Consider inserting the names in order into the
// switch statement.
builder.addInsertion(insertionOffset, (builder) {
- if (isLeftBracketSynthetic) {
- builder.write(' {');
- }
- builder.write(location.prefix);
- for (var constantName in enumConstantNames) {
+ void addMissingCase(String expression) {
builder.write(statementIndent);
builder.write(singleIndent);
builder.write('case ');
- builder.write(enumName_final);
- builder.write('.');
- builder.write(constantName);
+ builder.write(expression);
builder.writeln(':');
builder.write(statementIndent);
builder.write(singleIndent);
@@ -100,6 +106,19 @@
builder.write(singleIndent);
builder.writeln('break;');
}
+
+ if (isLeftBracketSynthetic) {
+ builder.write(' {');
+ }
+ builder.write(location.prefix);
+
+ for (var constantName in unhandledEnumCases) {
+ addMissingCase('$enumName_final.$constantName');
+ }
+ if (unhandledNullValue) {
+ addMissingCase('null');
+ }
+
builder.write(location.suffix);
if (statement.rightBracket.isSynthetic) {
builder.write('}');
diff --git a/pkg/analysis_server/test/src/services/correction/fix/add_missing_enum_case_clauses_test.dart b/pkg/analysis_server/test/src/services/correction/fix/add_missing_enum_case_clauses_test.dart
index c2c56ba..2ebed1c 100644
--- a/pkg/analysis_server/test/src/services/correction/fix/add_missing_enum_case_clauses_test.dart
+++ b/pkg/analysis_server/test/src/services/correction/fix/add_missing_enum_case_clauses_test.dart
@@ -608,6 +608,136 @@
}
@FailingTest(issue: 'https://github.com/dart-lang/sdk/issues/49759')
+ Future<void> test_nullable_handledNull() async {
+ await resolveTestCode('''
+enum E {a, b, c}
+void f(E? e) {
+ switch (e) {
+ case E.a:
+ break;
+ case null:
+ break;
+ }
+}
+''');
+ await assertHasFixWithFilter('''
+enum E {a, b, c}
+void f(E? e) {
+ switch (e) {
+ case E.a:
+ break;
+ case null:
+ break;
+ case E.b:
+ // TODO: Handle this case.
+ break;
+ case E.c:
+ // TODO: Handle this case.
+ break;
+ }
+}
+''');
+ }
+
+ Future<void> test_nullable_handledNull_language219() async {
+ await resolveTestCode('''
+// @dart=2.19
+enum E {a, b, c}
+void f(E? e) {
+ switch (e) {
+ case E.a:
+ break;
+ case null:
+ break;
+ }
+}
+''');
+ await assertHasFixWithFilter('''
+// @dart=2.19
+enum E {a, b, c}
+void f(E? e) {
+ switch (e) {
+ case E.a:
+ break;
+ case null:
+ break;
+ case E.b:
+ // TODO: Handle this case.
+ break;
+ case E.c:
+ // TODO: Handle this case.
+ break;
+ }
+}
+''');
+ }
+
+ @FailingTest(issue: 'https://github.com/dart-lang/sdk/issues/49759')
+ Future<void> test_nullable_unhandledNull() async {
+ await resolveTestCode('''
+enum E {a, b, c}
+void f(E? e) {
+ switch (e) {
+ case E.a:
+ break;
+ case E.b:
+ break;
+ }
+}
+''');
+ await assertHasFixWithFilter('''
+enum E {a, b, c}
+void f(E? e) {
+ switch (e) {
+ case E.a:
+ break;
+ case E.b:
+ break;
+ case E.c:
+ // TODO: Handle this case.
+ break;
+ case null:
+ // TODO: Handle this case.
+ break;
+ }
+}
+''');
+ }
+
+ Future<void> test_nullable_unhandledNull_language219() async {
+ await resolveTestCode('''
+// @dart=2.19
+enum E {a, b, c}
+void f(E? e) {
+ switch (e) {
+ case E.a:
+ break;
+ case E.b:
+ break;
+ }
+}
+''');
+ await assertHasFixWithFilter('''
+// @dart=2.19
+enum E {a, b, c}
+void f(E? e) {
+ switch (e) {
+ case E.a:
+ break;
+ case E.b:
+ break;
+ case E.c:
+ // TODO: Handle this case.
+ break;
+ case null:
+ // TODO: Handle this case.
+ break;
+ }
+}
+''');
+ }
+
+ @FailingTest(issue: 'https://github.com/dart-lang/sdk/issues/49759')
Future<void> test_static() async {
await resolveTestCode('''
enum E {