Add a quick fix to make a return type nullable
Change-Id: I6e400a5d46f9725586cb5f24601013abf7b3ebb5
Reviewed-on: https://dart-review.googlesource.com/c/sdk/+/157603
Commit-Queue: Brian Wilkerson <brianwilkerson@google.com>
Reviewed-by: Konstantin Shcheglov <scheglov@google.com>
diff --git a/pkg/analysis_server/lib/src/services/correction/dart/make_return_type_nullable.dart b/pkg/analysis_server/lib/src/services/correction/dart/make_return_type_nullable.dart
new file mode 100644
index 0000000..5fdea07
--- /dev/null
+++ b/pkg/analysis_server/lib/src/services/correction/dart/make_return_type_nullable.dart
@@ -0,0 +1,63 @@
+// Copyright (c) 2020, the Dart project authors. Please see the AUTHORS file
+// for details. All rights reserved. Use of this source code is governed by a
+// BSD-style license that can be found in the LICENSE file.
+
+import 'package:analysis_server/src/services/correction/dart/abstract_producer.dart';
+import 'package:analysis_server/src/services/correction/fix.dart';
+import 'package:analyzer/dart/analysis/features.dart';
+import 'package:analyzer/dart/ast/ast.dart';
+import 'package:analyzer_plugin/utilities/change_builder/change_builder_core.dart';
+import 'package:analyzer_plugin/utilities/fixes/fixes.dart';
+
+class MakeReturnTypeNullable extends CorrectionProducer {
+ @override
+ FixKind get fixKind => DartFixKind.MAKE_RETURN_TYPE_NULLABLE;
+
+ @override
+ Future<void> compute(ChangeBuilder builder) async {
+ if (!unit.featureSet.isEnabled(Feature.non_nullable)) {
+ return;
+ }
+ if (node is! Expression) {
+ return;
+ }
+ var body = node.thisOrAncestorOfType<FunctionBody>();
+ TypeAnnotation returnType;
+ var function = body.parent;
+ if (function is FunctionExpression) {
+ function = function.parent;
+ }
+ if (function is MethodDeclaration) {
+ returnType = function.returnType;
+ } else if (function is FunctionDeclaration) {
+ returnType = function.returnType;
+ } else {
+ return;
+ }
+ if (body.isAsynchronous || body.isGenerator) {
+ if (returnType is! NamedType) {
+ return null;
+ }
+ var typeArguments = (returnType as NamedType).typeArguments;
+ if (typeArguments == null) {
+ return null;
+ }
+ var arguments = typeArguments.arguments;
+ if (arguments.length != 1) {
+ return null;
+ }
+ returnType = arguments[0];
+ }
+ if (node is! NullLiteral &&
+ !typeSystem.isAssignableTo(returnType.type,
+ typeSystem.promoteToNonNull((node as Expression).staticType))) {
+ return;
+ }
+ await builder.addDartFileEdit(file, (builder) {
+ builder.addSimpleInsertion(returnType.end, '?');
+ });
+ }
+
+ /// Return an instance of this class. Used as a tear-off in `FixProcessor`.
+ static MakeReturnTypeNullable newInstance() => MakeReturnTypeNullable();
+}
diff --git a/pkg/analysis_server/lib/src/services/correction/fix.dart b/pkg/analysis_server/lib/src/services/correction/fix.dart
index 464a32d..0f67130 100644
--- a/pkg/analysis_server/lib/src/services/correction/fix.dart
+++ b/pkg/analysis_server/lib/src/services/correction/fix.dart
@@ -315,6 +315,8 @@
static const MAKE_FIELD_NOT_FINAL =
FixKind('dart.fix.makeFieldNotFinal', 50, "Make field '{0}' not final");
static const MAKE_FINAL = FixKind('dart.fix.makeFinal', 50, 'Make final');
+ static const MAKE_RETURN_TYPE_NULLABLE = FixKind(
+ 'dart.fix.makeReturnTypeNullable', 50, 'Make the return type nullable');
static const MOVE_TYPE_ARGUMENTS_TO_CLASS = FixKind(
'dart.fix.moveTypeArgumentsToClass',
50,
diff --git a/pkg/analysis_server/lib/src/services/correction/fix_internal.dart b/pkg/analysis_server/lib/src/services/correction/fix_internal.dart
index 0bf5738..ec4ab2e 100644
--- a/pkg/analysis_server/lib/src/services/correction/fix_internal.dart
+++ b/pkg/analysis_server/lib/src/services/correction/fix_internal.dart
@@ -79,6 +79,7 @@
import 'package:analysis_server/src/services/correction/dart/make_class_abstract.dart';
import 'package:analysis_server/src/services/correction/dart/make_field_not_final.dart';
import 'package:analysis_server/src/services/correction/dart/make_final.dart';
+import 'package:analysis_server/src/services/correction/dart/make_return_type_nullable.dart';
import 'package:analysis_server/src/services/correction/dart/make_variable_not_final.dart';
import 'package:analysis_server/src/services/correction/dart/move_type_arguments_to_class.dart';
import 'package:analysis_server/src/services/correction/dart/organize_imports.dart';
@@ -751,6 +752,12 @@
CompileTimeErrorCode.NULLABLE_TYPE_IN_WITH_CLAUSE: [
RemoveQuestionMark.newInstance,
],
+ CompileTimeErrorCode.RETURN_OF_INVALID_TYPE_FROM_FUNCTION: [
+ MakeReturnTypeNullable.newInstance,
+ ],
+ CompileTimeErrorCode.RETURN_OF_INVALID_TYPE_FROM_METHOD: [
+ MakeReturnTypeNullable.newInstance,
+ ],
CompileTimeErrorCode.TYPE_TEST_WITH_UNDEFINED_NAME: [
ChangeTo.classOrMixin,
CreateClass.newInstance,
@@ -843,6 +850,9 @@
MoveTypeArgumentsToClass.newInstance,
RemoveTypeArguments.newInstance,
],
+ CompileTimeErrorCode.YIELD_OF_INVALID_TYPE: [
+ MakeReturnTypeNullable.newInstance,
+ ],
HintCode.CAN_BE_NULL_AFTER_NULL_AWARE: [
ReplaceWithNullAware.newInstance,
diff --git a/pkg/analysis_server/test/src/services/correction/fix/make_return_type_nullable_test.dart b/pkg/analysis_server/test/src/services/correction/fix/make_return_type_nullable_test.dart
new file mode 100644
index 0000000..6181ed4
--- /dev/null
+++ b/pkg/analysis_server/test/src/services/correction/fix/make_return_type_nullable_test.dart
@@ -0,0 +1,150 @@
+// Copyright (c) 2018, the Dart project authors. Please see the AUTHORS file
+// for details. All rights reserved. Use of this source code is governed by a
+// BSD-style license that can be found in the LICENSE file.
+
+import 'package:analysis_server/src/services/correction/fix.dart';
+import 'package:analyzer/src/dart/analysis/experiments.dart';
+import 'package:analyzer_plugin/utilities/fixes/fixes.dart';
+import 'package:test_reflective_loader/test_reflective_loader.dart';
+
+import 'fix_processor.dart';
+
+void main() {
+ defineReflectiveSuite(() {
+ defineReflectiveTests(MakeReturnTypeNullableTest);
+ });
+}
+
+@reflectiveTest
+class MakeReturnTypeNullableTest extends FixProcessorTest {
+ @override
+ List<String> get experiments => [EnableString.non_nullable];
+
+ @override
+ FixKind get kind => DartFixKind.MAKE_RETURN_TYPE_NULLABLE;
+
+ Future<void> test_function_async() async {
+ await resolveTestUnit('''
+Future<String> f(String? s) async {
+ return s;
+}
+''');
+ await assertHasFix('''
+Future<String?> f(String? s) async {
+ return s;
+}
+''');
+ }
+
+ Future<void> test_function_asyncStar() async {
+ await resolveTestUnit('''
+Stream<String> f(String? s) async* {
+ yield s;
+}
+''');
+ await assertHasFix('''
+Stream<String?> f(String? s) async* {
+ yield s;
+}
+''');
+ }
+
+ Future<void> test_function_sync() async {
+ await resolveTestUnit('''
+String f(String? s) {
+ return s;
+}
+''');
+ await assertHasFix('''
+String? f(String? s) {
+ return s;
+}
+''');
+ }
+
+ Future<void> test_function_syncStar() async {
+ await resolveTestUnit('''
+Iterable<String> f(String? s) sync* {
+ yield s;
+}
+''');
+ await assertHasFix('''
+Iterable<String?> f(String? s) sync* {
+ yield s;
+}
+''');
+ }
+
+ Future<void> test_getter_sync() async {
+ await resolveTestUnit('''
+class C {
+ String? f;
+ String get g => f;
+}
+''');
+ await assertHasFix('''
+class C {
+ String? f;
+ String? get g => f;
+}
+''');
+ }
+
+ Future<void> test_incompatibilityIsNotLimitedToNullability() async {
+ await resolveTestUnit('''
+int f() {
+ return '';
+}
+''');
+ await assertNoFix();
+ }
+
+ Future<void> test_localFunction_sync() async {
+ await resolveTestUnit('''
+void f() {
+ String g(String? s) {
+ return s;
+ }
+ g(null);
+}
+''');
+ await assertHasFix('''
+void f() {
+ String? g(String? s) {
+ return s;
+ }
+ g(null);
+}
+''');
+ }
+
+ Future<void> test_method_sync() async {
+ await resolveTestUnit('''
+class C {
+ String m(String? s) {
+ return s;
+ }
+}
+''');
+ await assertHasFix('''
+class C {
+ String? m(String? s) {
+ return s;
+ }
+}
+''');
+ }
+
+ Future<void> test_returnTypeHasTypeArguments() async {
+ await resolveTestUnit('''
+List<String> f() {
+ return null;
+}
+''');
+ await assertHasFix('''
+List<String>? f() {
+ return null;
+}
+''');
+ }
+}
diff --git a/pkg/analysis_server/test/src/services/correction/fix/test_all.dart b/pkg/analysis_server/test/src/services/correction/fix/test_all.dart
index bb9234d..64dde02 100644
--- a/pkg/analysis_server/test/src/services/correction/fix/test_all.dart
+++ b/pkg/analysis_server/test/src/services/correction/fix/test_all.dart
@@ -92,6 +92,7 @@
import 'make_class_abstract_test.dart' as make_class_abstract;
import 'make_field_not_final_test.dart' as make_field_not_final;
import 'make_final_test.dart' as make_final;
+import 'make_return_type_nullable_test.dart' as make_return_type_nullable;
import 'make_variable_not_final_test.dart' as make_variable_not_final;
import 'move_type_arguments_to_class_test.dart' as move_type_arguments_to_class;
import 'organize_imports_test.dart' as organize_imports;
@@ -246,6 +247,7 @@
make_class_abstract.main();
make_field_not_final.main();
make_final.main();
+ make_return_type_nullable.main();
make_variable_not_final.main();
move_type_arguments_to_class.main();
organize_imports.main();