[analyzer] `RemoveTypeAnnotation` to handle type arguments

Fixes #49227

Change-Id: I0f754d84e3e4e89800abdec26925229587202bca
Reviewed-on: https://dart-review.googlesource.com/c/sdk/+/247968
Reviewed-by: Brian Wilkerson <brianwilkerson@google.com>
Commit-Queue: Brian Wilkerson <brianwilkerson@google.com>
diff --git a/pkg/analysis_server/lib/src/services/correction/dart/remove_type_annotation.dart b/pkg/analysis_server/lib/src/services/correction/dart/remove_type_annotation.dart
index 0b1f3c6..853558be 100644
--- a/pkg/analysis_server/lib/src/services/correction/dart/remove_type_annotation.dart
+++ b/pkg/analysis_server/lib/src/services/correction/dart/remove_type_annotation.dart
@@ -49,8 +49,8 @@
   Future<void> _removeFromDeclarationList(
       ChangeBuilder builder, VariableDeclarationList declarationList) async {
     // we need a type
-    var typeNode = declarationList.type;
-    if (typeNode == null) {
+    var type = declarationList.type;
+    if (type == null) {
       return;
     }
     // ignore if an incomplete variable declaration
@@ -63,19 +63,60 @@
     if (selectionOffset > firstVariable.name.end) {
       return;
     }
+
+    var initializer = firstVariable.initializer;
     // The variable must have an initializer, otherwise there is no other
     // source for its type.
-    if (firstVariable.initializer == null) {
+    if (initializer == null) {
+      return;
+    }
+
+    String? typeArgumentsText;
+    int? typeArgumentsOffset;
+    if (type is NamedType) {
+      var typeArguments = type.typeArguments;
+      if (typeArguments != null) {
+        if (initializer is CascadeExpression) {
+          initializer = initializer.target;
+        }
+        if (initializer is TypedLiteral) {
+          if (initializer.typeArguments == null) {
+            typeArgumentsText = utils.getNodeText(typeArguments);
+            if (initializer is ListLiteral) {
+              typeArgumentsOffset = initializer.leftBracket.offset;
+            } else if (initializer is SetOrMapLiteral) {
+              typeArgumentsOffset = initializer.leftBracket.offset;
+            } else {
+              throw StateError('Unhandled subclass of TypedLiteral');
+            }
+          }
+        } else if (initializer is InstanceCreationExpression) {
+          if (initializer.constructorName.type.typeArguments == null) {
+            typeArgumentsText = utils.getNodeText(typeArguments);
+            typeArgumentsOffset = initializer.constructorName.type.end;
+          }
+        }
+      }
+    }
+    if (initializer is SetOrMapLiteral &&
+        initializer.typeArguments == null &&
+        typeArgumentsText == null) {
+      // This is to prevent the fix from converting a valid map or set literal
+      // into an ambiguous literal. We could apply this in more places
+      // by examining the elements of the collection.
       return;
     }
     var keyword = declarationList.keyword;
     await builder.addDartFileEdit(file, (builder) {
-      var typeRange = range.startStart(typeNode, firstVariable);
+      var typeRange = range.startStart(type, firstVariable);
       if (keyword != null && keyword.lexeme != 'var') {
         builder.addSimpleReplacement(typeRange, '');
       } else {
         builder.addSimpleReplacement(typeRange, 'var ');
       }
+      if (typeArgumentsText != null && typeArgumentsOffset != null) {
+        builder.addSimpleInsertion(typeArgumentsOffset, typeArgumentsText);
+      }
     });
   }
 
diff --git a/pkg/analysis_server/lib/src/services/correction/dart/replace_with_var.dart b/pkg/analysis_server/lib/src/services/correction/dart/replace_with_var.dart
index 49ddf35..3c5a55b 100644
--- a/pkg/analysis_server/lib/src/services/correction/dart/replace_with_var.dart
+++ b/pkg/analysis_server/lib/src/services/correction/dart/replace_with_var.dart
@@ -64,6 +64,8 @@
                 typeArgumentsOffset = initializer.leftBracket.offset;
               } else if (initializer is SetOrMapLiteral) {
                 typeArgumentsOffset = initializer.leftBracket.offset;
+              } else {
+                throw StateError('Unhandled subclass of TypedLiteral');
               }
             }
           } else if (initializer is InstanceCreationExpression) {
@@ -77,9 +79,9 @@
       if (initializer is SetOrMapLiteral &&
           initializer.typeArguments == null &&
           typeArgumentsText == null) {
-        // TODO(brianwilkerson) This is to prevent the fix from converting a
-        //  valid map or set literal into an ambiguous literal. We could apply
-        //  this in more places by examining the elements of the collection.
+        // This is to prevent the fix from converting a valid map or set literal
+        // into an ambiguous literal. We could apply this in more places
+        // by examining the elements of the collection.
         return;
       }
       await builder.addDartFileEdit(file, (builder) {
diff --git a/pkg/analysis_server/test/src/services/correction/assist/remove_type_annotation_test.dart b/pkg/analysis_server/test/src/services/correction/assist/remove_type_annotation_test.dart
index 99f4ab7..d5fdf25 100644
--- a/pkg/analysis_server/test/src/services/correction/assist/remove_type_annotation_test.dart
+++ b/pkg/analysis_server/test/src/services/correction/assist/remove_type_annotation_test.dart
@@ -54,6 +54,45 @@
     await assertNoAssistAt('v');
   }
 
+  Future<void> test_generic_instanceCreation_withoutArguments() async {
+    await resolveTestCode('''
+C<int> c = C();
+class C<T> {}
+''');
+    await assertHasAssistAt('c = ', '''
+var c = C<int>();
+class C<T> {}
+''');
+  }
+
+  Future<void> test_generic_listLiteral() async {
+    await resolveTestCode('''
+List<int> l = [];
+''');
+    await assertHasAssistAt('l = ', '''
+var l = <int>[];
+''');
+  }
+
+  Future<void> test_generic_setLiteral_ambiguous() async {
+    await resolveTestCode('''
+Set f() {
+  /*caret*/Set s = {};
+  return s;
+}
+''');
+    await assertNoAssist();
+  }
+
+  Future<void> test_generic_setLiteral_cascade() async {
+    await resolveTestCode('''
+Set<String> s = {}..addAll([]);
+''');
+    await assertHasAssistAt('s = ', '''
+var s = <String>{}..addAll([]);
+''');
+  }
+
   Future<void> test_instanceCreation_freeStanding() async {
     await resolveTestCode('''
 class A {}