Add quick fixes for invalid return types for generator functions

Change-Id: I23a3befd37946c02aadc8c0ebdf75e60cf6767c2
Reviewed-on: https://dart-review.googlesource.com/c/sdk/+/225426
Reviewed-by: Konstantin Shcheglov <scheglov@google.com>
diff --git a/pkg/analysis_server/lib/src/services/correction/dart/replace_return_type_future.dart b/pkg/analysis_server/lib/src/services/correction/dart/replace_return_type_future.dart
index fb29e4f..9d3b8dd 100644
--- a/pkg/analysis_server/lib/src/services/correction/dart/replace_return_type_future.dart
+++ b/pkg/analysis_server/lib/src/services/correction/dart/replace_return_type_future.dart
@@ -9,6 +9,12 @@
 import 'package:analyzer_plugin/utilities/fixes/fixes.dart';
 
 class ReplaceReturnTypeFuture extends CorrectionProducer {
+  /// The text for the type argument to 'Future'.
+  String _typeArgument = '';
+
+  @override
+  List<Object>? get fixArguments => [_typeArgument];
+
   @override
   FixKind get fixKind => DartFixKind.REPLACE_RETURN_TYPE_FUTURE;
 
@@ -19,6 +25,7 @@
     if (typeAnnotation == null) {
       return;
     }
+    _typeArgument = utils.getNodeText(typeAnnotation);
 
     await builder.addDartFileEdit(file, (builder) {
       builder.replaceTypeWithFuture(typeAnnotation, typeProvider);
diff --git a/pkg/analysis_server/lib/src/services/correction/dart/replace_return_type_iterable.dart b/pkg/analysis_server/lib/src/services/correction/dart/replace_return_type_iterable.dart
new file mode 100644
index 0000000..cc491aa
--- /dev/null
+++ b/pkg/analysis_server/lib/src/services/correction/dart/replace_return_type_iterable.dart
@@ -0,0 +1,44 @@
+// 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/ast/ast.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';
+
+class ReplaceReturnTypeIterable extends CorrectionProducer {
+  /// The text for the type argument to 'Iterable'.
+  String _typeArgument = '';
+
+  @override
+  List<Object>? get fixArguments => [_typeArgument];
+
+  @override
+  FixKind get fixKind => DartFixKind.REPLACE_RETURN_TYPE_ITERABLE;
+
+  @override
+  Future<void> compute(ChangeBuilder builder) async {
+    // prepare the existing type
+    var typeAnnotation = node.thisOrAncestorOfType<TypeAnnotation>();
+    if (typeAnnotation == null) {
+      return;
+    }
+    var type = typeAnnotation.type;
+    if (type == null || type.isDynamic || type.isDartCoreIterable) {
+      return;
+    }
+    _typeArgument = utils.getNodeText(typeAnnotation);
+
+    await builder.addDartFileEdit(file, (builder) {
+      builder.addReplacement(range.node(typeAnnotation), (builder) {
+        builder.writeType(typeProvider.iterableType(type));
+      });
+    });
+  }
+
+  /// Return an instance of this class. Used as a tear-off in `FixProcessor`.
+  static ReplaceReturnTypeIterable newInstance() => ReplaceReturnTypeIterable();
+}
diff --git a/pkg/analysis_server/lib/src/services/correction/dart/replace_return_type_stream.dart b/pkg/analysis_server/lib/src/services/correction/dart/replace_return_type_stream.dart
new file mode 100644
index 0000000..829ebe6
--- /dev/null
+++ b/pkg/analysis_server/lib/src/services/correction/dart/replace_return_type_stream.dart
@@ -0,0 +1,44 @@
+// 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/ast/ast.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';
+
+class ReplaceReturnTypeStream extends CorrectionProducer {
+  /// The text for the type argument to 'Stream'.
+  String _typeArgument = '';
+
+  @override
+  List<Object>? get fixArguments => [_typeArgument];
+
+  @override
+  FixKind get fixKind => DartFixKind.REPLACE_RETURN_TYPE_STREAM;
+
+  @override
+  Future<void> compute(ChangeBuilder builder) async {
+    // prepare the existing type
+    var typeAnnotation = node.thisOrAncestorOfType<TypeAnnotation>();
+    if (typeAnnotation == null) {
+      return;
+    }
+    var type = typeAnnotation.type;
+    if (type == null || type.isDynamic || type.isDartAsyncStream) {
+      return;
+    }
+    _typeArgument = utils.getNodeText(typeAnnotation);
+
+    await builder.addDartFileEdit(file, (builder) {
+      builder.addReplacement(range.node(typeAnnotation), (builder) {
+        builder.writeType(typeProvider.streamType(type));
+      });
+    });
+  }
+
+  /// Return an instance of this class. Used as a tear-off in `FixProcessor`.
+  static ReplaceReturnTypeStream newInstance() => ReplaceReturnTypeStream();
+}
diff --git a/pkg/analysis_server/lib/src/services/correction/fix.dart b/pkg/analysis_server/lib/src/services/correction/fix.dart
index 59471b3..666ea13 100644
--- a/pkg/analysis_server/lib/src/services/correction/fix.dart
+++ b/pkg/analysis_server/lib/src/services/correction/fix.dart
@@ -1261,7 +1261,17 @@
   static const REPLACE_RETURN_TYPE_FUTURE = FixKind(
     'dart.fix.replace.returnTypeFuture',
     DartFixKindPriority.DEFAULT,
-    "Return 'Future' from 'async' function",
+    "Return 'Future<{0}>'",
+  );
+  static const REPLACE_RETURN_TYPE_ITERABLE = FixKind(
+    'dart.fix.replace.returnTypeIterable',
+    DartFixKindPriority.DEFAULT,
+    "Return 'Iterable<{0}>'",
+  );
+  static const REPLACE_RETURN_TYPE_STREAM = FixKind(
+    'dart.fix.replace.returnTypeStream',
+    DartFixKindPriority.DEFAULT,
+    "Return 'Stream<{0}>'",
   );
   static const REPLACE_CONTAINER_WITH_SIZED_BOX = FixKind(
     'dart.fix.replace.containerWithSizedBox',
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 128e4eb..7abed7a 100644
--- a/pkg/analysis_server/lib/src/services/correction/fix_internal.dart
+++ b/pkg/analysis_server/lib/src/services/correction/fix_internal.dart
@@ -151,6 +151,8 @@
 import 'package:analysis_server/src/services/correction/dart/replace_null_with_closure.dart';
 import 'package:analysis_server/src/services/correction/dart/replace_return_type.dart';
 import 'package:analysis_server/src/services/correction/dart/replace_return_type_future.dart';
+import 'package:analysis_server/src/services/correction/dart/replace_return_type_iterable.dart';
+import 'package:analysis_server/src/services/correction/dart/replace_return_type_stream.dart';
 import 'package:analysis_server/src/services/correction/dart/replace_var_with_dynamic.dart';
 import 'package:analysis_server/src/services/correction/dart/replace_with_brackets.dart';
 import 'package:analysis_server/src/services/correction/dart/replace_with_conditional_assignment.dart';
@@ -842,9 +844,15 @@
     CompileTimeErrorCode.FINAL_NOT_INITIALIZED_CONSTRUCTOR_3_PLUS: [
       AddFieldFormalParameters.newInstance,
     ],
+    CompileTimeErrorCode.ILLEGAL_ASYNC_GENERATOR_RETURN_TYPE: [
+      ReplaceReturnTypeStream.newInstance,
+    ],
     CompileTimeErrorCode.ILLEGAL_ASYNC_RETURN_TYPE: [
       ReplaceReturnTypeFuture.newInstance,
     ],
+    CompileTimeErrorCode.ILLEGAL_SYNC_GENERATOR_RETURN_TYPE: [
+      ReplaceReturnTypeIterable.newInstance,
+    ],
     CompileTimeErrorCode.IMPLEMENTS_NON_CLASS: [
       ChangeTo.classOrMixin,
       CreateClass.newInstance,
diff --git a/pkg/analysis_server/test/src/services/correction/fix/replace_return_type_future_test.dart b/pkg/analysis_server/test/src/services/correction/fix/replace_return_type_future_test.dart
index 9cfafa7..818dad5 100644
--- a/pkg/analysis_server/test/src/services/correction/fix/replace_return_type_future_test.dart
+++ b/pkg/analysis_server/test/src/services/correction/fix/replace_return_type_future_test.dart
@@ -20,38 +20,14 @@
   @override
   FixKind get kind => DartFixKind.REPLACE_RETURN_TYPE_FUTURE;
 
-  Future<void> test_adjacentNodes_withImport() async {
-    await resolveTestCode('''
-import 'dart:async';
-var v;int main() async => 0;
-''');
-    await assertHasFix('''
-import 'dart:async';
-var v;Future<int> main() async => 0;
-''', errorFilter: (error) {
-      return error.errorCode == CompileTimeErrorCode.ILLEGAL_ASYNC_RETURN_TYPE;
-    });
-  }
-
-  Future<void> test_adjacentNodes_withoutImport() async {
-    await resolveTestCode('''
-var v;int main() async => 0;
-''');
-    await assertHasFix('''
-var v;Future<int> main() async => 0;
-''');
-  }
-
   Future<void> test_complexTypeName_withImport() async {
     await resolveTestCode('''
 import 'dart:async';
-List<int> main() async {
-}
+List<int> f() async {}
 ''');
     await assertHasFix('''
 import 'dart:async';
-Future<List<int>> main() async {
-}
+Future<List<int>> f() async {}
 ''', errorFilter: (error) {
       return error.errorCode == CompileTimeErrorCode.ILLEGAL_ASYNC_RETURN_TYPE;
     });
@@ -59,25 +35,21 @@
 
   Future<void> test_complexTypeName_withoutImport() async {
     await resolveTestCode('''
-List<int> main() async {
-}
+List<int> f() async {}
 ''');
     await assertHasFix('''
-Future<List<int>> main() async {
-}
+Future<List<int>> f() async {}
 ''');
   }
 
   Future<void> test_importedWithPrefix() async {
     await resolveTestCode('''
 import 'dart:async' as al;
-int main() async {
-}
+int f() async {}
 ''');
     await assertHasFix('''
 import 'dart:async' as al;
-al.Future<int> main() async {
-}
+al.Future<int> f() async {}
 ''', errorFilter: (error) {
       return error.errorCode == CompileTimeErrorCode.ILLEGAL_ASYNC_RETURN_TYPE;
     });
@@ -86,11 +58,11 @@
   Future<void> test_simpleTypeName_withImport() async {
     await resolveTestCode('''
 import 'dart:async';
-int main() async => 0;
+int f() async {}
 ''');
     await assertHasFix('''
 import 'dart:async';
-Future<int> main() async => 0;
+Future<int> f() async {}
 ''', errorFilter: (error) {
       return error.errorCode == CompileTimeErrorCode.ILLEGAL_ASYNC_RETURN_TYPE;
     });
@@ -98,40 +70,10 @@
 
   Future<void> test_simpleTypeName_withoutImport() async {
     await resolveTestCode('''
-int main() async => 0;
+int f() async {}
 ''');
     await assertHasFix('''
-Future<int> main() async => 0;
-''');
-  }
-
-  Future<void> test_withLibraryDirective_withImport() async {
-    await resolveTestCode('''
-library main;
-import 'dart:async';
-int main() async {
-}
-''');
-    await assertHasFix('''
-library main;
-import 'dart:async';
-Future<int> main() async {
-}
-''', errorFilter: (error) {
-      return error.errorCode == CompileTimeErrorCode.ILLEGAL_ASYNC_RETURN_TYPE;
-    });
-  }
-
-  Future<void> test_withLibraryDirective_withoutImport() async {
-    await resolveTestCode('''
-library main;
-int main() async {
-}
-''');
-    await assertHasFix('''
-library main;
-Future<int> main() async {
-}
+Future<int> f() async {}
 ''');
   }
 }
diff --git a/pkg/analysis_server/test/src/services/correction/fix/replace_return_type_iterable_test.dart b/pkg/analysis_server/test/src/services/correction/fix/replace_return_type_iterable_test.dart
new file mode 100644
index 0000000..e83a01c
--- /dev/null
+++ b/pkg/analysis_server/test/src/services/correction/fix/replace_return_type_iterable_test.dart
@@ -0,0 +1,50 @@
+// 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_plugin/utilities/fixes/fixes.dart';
+import 'package:test_reflective_loader/test_reflective_loader.dart';
+
+import 'fix_processor.dart';
+
+void main() {
+  defineReflectiveSuite(() {
+    defineReflectiveTests(ReplaceReturnTypeIterableTest);
+  });
+}
+
+@reflectiveTest
+class ReplaceReturnTypeIterableTest extends FixProcessorTest {
+  @override
+  FixKind get kind => DartFixKind.REPLACE_RETURN_TYPE_ITERABLE;
+
+  Future<void> test_complexTypeName() async {
+    await resolveTestCode('''
+List<int> f() sync* {}
+''');
+    await assertHasFix('''
+Iterable<List<int>> f() sync* {}
+''');
+  }
+
+  Future<void> test_importedWithPrefix() async {
+    await resolveTestCode('''
+import 'dart:core' as c;
+c.int f() sync* {}
+''');
+    await assertHasFix('''
+import 'dart:core' as c;
+c.Iterable<c.int> f() sync* {}
+''');
+  }
+
+  Future<void> test_simpleTypeName() async {
+    await resolveTestCode('''
+int f() sync* {}
+''');
+    await assertHasFix('''
+Iterable<int> f() sync* {}
+''');
+  }
+}
diff --git a/pkg/analysis_server/test/src/services/correction/fix/replace_return_type_stream_test.dart b/pkg/analysis_server/test/src/services/correction/fix/replace_return_type_stream_test.dart
new file mode 100644
index 0000000..e564385
--- /dev/null
+++ b/pkg/analysis_server/test/src/services/correction/fix/replace_return_type_stream_test.dart
@@ -0,0 +1,82 @@
+// 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/error/codes.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(ReplaceReturnTypeStreamTest);
+  });
+}
+
+@reflectiveTest
+class ReplaceReturnTypeStreamTest extends FixProcessorTest {
+  @override
+  FixKind get kind => DartFixKind.REPLACE_RETURN_TYPE_STREAM;
+
+  Future<void> test_complexTypeName_withImport() async {
+    await resolveTestCode('''
+import 'dart:async';
+List<int> f() async* {}
+''');
+    await assertHasFix('''
+import 'dart:async';
+Stream<List<int>> f() async* {}
+''', errorFilter: (error) {
+      return error.errorCode ==
+          CompileTimeErrorCode.ILLEGAL_ASYNC_GENERATOR_RETURN_TYPE;
+    });
+  }
+
+  Future<void> test_complexTypeName_withoutImport() async {
+    await resolveTestCode('''
+List<int> f() async* {}
+''');
+    await assertHasFix('''
+Stream<List<int>> f() async* {}
+''');
+  }
+
+  Future<void> test_importedWithPrefix() async {
+    await resolveTestCode('''
+import 'dart:async' as al;
+int f() async* {}
+''');
+    await assertHasFix('''
+import 'dart:async' as al;
+al.Stream<int> f() async* {}
+''', errorFilter: (error) {
+      return error.errorCode ==
+          CompileTimeErrorCode.ILLEGAL_ASYNC_GENERATOR_RETURN_TYPE;
+    });
+  }
+
+  Future<void> test_simpleTypeName_withImport() async {
+    await resolveTestCode('''
+import 'dart:async';
+int f() async* {}
+''');
+    await assertHasFix('''
+import 'dart:async';
+Stream<int> f() async* {}
+''', errorFilter: (error) {
+      return error.errorCode ==
+          CompileTimeErrorCode.ILLEGAL_ASYNC_GENERATOR_RETURN_TYPE;
+    });
+  }
+
+  Future<void> test_simpleTypeName_withoutImport() async {
+    await resolveTestCode('''
+int f() async* {}
+''');
+    await assertHasFix('''
+Stream<int> f() async* {}
+''');
+  }
+}
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 46e09ff..3174635 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
@@ -184,6 +184,8 @@
 import 'replace_new_with_const_test.dart' as replace_new_with_const;
 import 'replace_null_with_closure_test.dart' as replace_null_with_closure;
 import 'replace_return_type_future_test.dart' as replace_return_type_future;
+import 'replace_return_type_iterable_test.dart' as replace_return_type_iterable;
+import 'replace_return_type_stream_test.dart' as replace_return_type_stream;
 import 'replace_return_type_test.dart' as replace_return_type;
 import 'replace_var_with_dynamic_test.dart' as replace_var_with_dynamic;
 import 'replace_with_brackets_test.dart' as replace_with_brackets;
@@ -372,6 +374,8 @@
     replace_null_with_void.main();
     replace_return_type.main();
     replace_return_type_future.main();
+    replace_return_type_iterable.main();
+    replace_return_type_stream.main();
     replace_var_with_dynamic.main();
     replace_with_brackets.main();
     replace_with_conditional_assignment.main();
diff --git a/pkg/analyzer/lib/dart/element/type.dart b/pkg/analyzer/lib/dart/element/type.dart
index 923cc3f..e3f102e 100644
--- a/pkg/analyzer/lib/dart/element/type.dart
+++ b/pkg/analyzer/lib/dart/element/type.dart
@@ -57,6 +57,10 @@
   /// the dart:async library.
   bool get isDartAsyncFutureOr;
 
+  /// Return `true` if this type represents the type 'Stream' defined in the
+  /// dart:async library.
+  bool get isDartAsyncStream;
+
   /// Return `true` if this type represents the type 'bool' defined in the
   /// dart:core library.
   bool get isDartCoreBool;
diff --git a/pkg/analyzer/lib/src/dart/element/type.dart b/pkg/analyzer/lib/src/dart/element/type.dart
index bbfa073..70f398c 100644
--- a/pkg/analyzer/lib/src/dart/element/type.dart
+++ b/pkg/analyzer/lib/src/dart/element/type.dart
@@ -517,6 +517,11 @@
   }
 
   @override
+  bool get isDartAsyncStream {
+    return element.name == "Stream" && element.library.isDartAsync;
+  }
+
+  @override
   bool get isDartCoreBool {
     return element.name == "bool" && element.library.isDartCore;
   }
@@ -974,6 +979,9 @@
   bool get isDartAsyncFutureOr => false;
 
   @override
+  bool get isDartAsyncStream => false;
+
+  @override
   bool get isDartCoreBool => false;
 
   @override
diff --git a/pkg/analyzer_plugin/lib/src/utilities/change_builder/change_builder_dart.dart b/pkg/analyzer_plugin/lib/src/utilities/change_builder/change_builder_dart.dart
index 104fc79..23110a9 100644
--- a/pkg/analyzer_plugin/lib/src/utilities/change_builder/change_builder_dart.dart
+++ b/pkg/analyzer_plugin/lib/src/utilities/change_builder/change_builder_dart.dart
@@ -1486,9 +1486,9 @@
       return;
     }
 
-    addReplacement(range.node(typeAnnotation!), (EditBuilder builder) {
+    addReplacement(range.node(typeAnnotation!), (builder) {
       var futureType = typeProvider.futureType(type);
-      if (!(builder as DartEditBuilder).writeType(futureType)) {
+      if (!builder.writeType(futureType)) {
         builder.write('void');
       }
     });