[DAS] Adds 'Create extension method/operator' fixes

R=srawlins@google.com

Fixes: https://github.com/dart-lang/sdk/issues/60203
Change-Id: I2527c130dd68cf678ac4529eda42397ef80ca53c
Reviewed-on: https://dart-review.googlesource.com/c/sdk/+/412060
Commit-Queue: Phil Quitslund <pquitslund@google.com>
Reviewed-by: Phil Quitslund <pquitslund@google.com>
Reviewed-by: Samuel Rawlins <srawlins@google.com>
Auto-Submit: Felipe Morschel <git@fmorschel.dev>
Commit-Queue: Samuel Rawlins <srawlins@google.com>
diff --git a/pkg/analysis_server/lib/src/services/correction/dart/create_extension_member.dart b/pkg/analysis_server/lib/src/services/correction/dart/create_extension_member.dart
index d6293e5..6a4642a 100644
--- a/pkg/analysis_server/lib/src/services/correction/dart/create_extension_member.dart
+++ b/pkg/analysis_server/lib/src/services/correction/dart/create_extension_member.dart
@@ -5,14 +5,18 @@
 import 'package:analysis_server/src/services/correction/fix.dart';
 import 'package:analysis_server/src/services/correction/util.dart';
 import 'package:analysis_server_plugin/edit/dart/correction_producer.dart';
+import 'package:analyzer/dart/ast/token.dart';
+import 'package:analyzer/dart/element/element2.dart';
 import 'package:analyzer/dart/element/type.dart';
 import 'package:analyzer/src/dart/ast/ast.dart';
+import 'package:analyzer/src/dart/element/inheritance_manager3.dart';
 import 'package:analyzer/src/dart/element/type.dart';
 import 'package:analyzer/src/dart/resolver/applicable_extensions.dart';
 import 'package:analyzer/src/utilities/extensions/ast.dart';
 import 'package:analyzer_plugin/utilities/change_builder/change_builder_core.dart';
 import 'package:analyzer_plugin/utilities/change_builder/change_builder_dart.dart';
 import 'package:analyzer_plugin/utilities/fixes/fixes.dart';
+import 'package:collection/collection.dart';
 
 class CreateExtensionGetter extends _CreateExtensionMember {
   String _getterName = '';
@@ -53,6 +57,8 @@
       return;
     }
 
+    // TODO(FMorschel): We should take into account if the target type contains
+    // a setter for the same name and stop the fix from being applied.
     // We need the type for the extension.
     var targetType = target.staticType;
     if (targetType == null ||
@@ -67,7 +73,7 @@
 
     void writeGetter(DartEditBuilder builder) {
       if (fieldType != null) {
-        builder.writeType(fieldType);
+        builder.writeType(fieldType, methodBeingCopied: methodBeingCopied);
         builder.write(' ');
       }
       builder.write('get $_getterName => ');
@@ -89,7 +95,13 @@
       return;
     }
 
-    await _addNewExtension(builder, targetType, nameNode, writeGetter);
+    await _addNewExtension(
+      builder,
+      targetType,
+      nameNode,
+      writeGetter,
+      involvedTypes: [fieldType],
+    );
   }
 }
 
@@ -137,7 +149,11 @@
     var returnType = inferUndefinedExpressionType(invocation);
 
     void writeMethod(DartEditBuilder builder) {
-      if (builder.writeType(returnType, groupName: 'RETURN_TYPE')) {
+      if (builder.writeType(
+        returnType,
+        groupName: 'RETURN_TYPE',
+        methodBeingCopied: methodBeingCopied,
+      )) {
         builder.write(' ');
       }
 
@@ -145,8 +161,20 @@
         builder.write(_methodName);
       });
 
+      builder.writeTypeParameters(
+        [
+              returnType,
+              ...invocation.argumentList.arguments.map((e) => e.staticType),
+            ].typeParameters
+            .whereNot([targetType].typeParameters.contains)
+            .toList(),
+      );
+
       builder.write('(');
-      builder.writeParametersMatchingArguments(invocation.argumentList);
+      builder.writeParametersMatchingArguments(
+        invocation.argumentList,
+        methodBeingCopied: methodBeingCopied,
+      );
       builder.write(') {}');
     }
 
@@ -166,6 +194,136 @@
   }
 }
 
+class CreateExtensionOperator extends _CreateExtensionMember {
+  String _operator = '';
+
+  CreateExtensionOperator({required super.context});
+
+  @override
+  List<String>? get fixArguments => [_operator];
+
+  @override
+  FixKind get fixKind => DartFixKind.CREATE_EXTENSION_OPERATOR;
+
+  @override
+  Future<void> compute(ChangeBuilder builder) async {
+    bool indexSetter = false;
+    bool innerParameter = true;
+    var node = this.node;
+    if (node is! Expression) {
+      return;
+    }
+
+    Expression? target;
+    DartType? parameterType;
+    DartType? assigningType;
+
+    switch (node) {
+      case IndexExpression(:var parent):
+        if (node.target?.staticType?.element3.declaresIndex ?? true) {
+          return;
+        }
+        target = node.target;
+        _operator = TokenType.INDEX.lexeme;
+        parameterType = node.index.staticType;
+        if (parameterType == null) {
+          return;
+        }
+        if (parent case AssignmentExpression(
+          :var leftHandSide,
+          :var rightHandSide,
+        ) when leftHandSide == node) {
+          assigningType = rightHandSide.staticType;
+          indexSetter = true;
+          _operator = TokenType.INDEX_EQ.lexeme;
+        }
+      case BinaryExpression():
+        target = node.leftOperand;
+        _operator = node.operator.lexeme;
+        parameterType = node.rightOperand.staticType;
+        if (parameterType == null) {
+          return;
+        }
+      case PrefixExpression():
+        target = node.operand;
+        _operator = node.operator.lexeme;
+        innerParameter = false;
+    }
+
+    if (target == null) {
+      return;
+    }
+
+    // We need the type for the extension.
+    var targetType = target.staticType;
+    if (targetType == null ||
+        targetType is DynamicType ||
+        targetType is InvalidType) {
+      return;
+    }
+
+    DartType returnType;
+    // If this is an index setter, the return type must be void.
+    if (indexSetter) {
+      returnType = VoidTypeImpl.instance;
+    } else {
+      // Try to find the return type.
+      returnType = inferUndefinedExpressionType(node) ?? VoidTypeImpl.instance;
+    }
+
+    void writeMethod(DartEditBuilder builder) {
+      if (builder.writeType(
+        returnType,
+        groupName: 'RETURN_TYPE',
+        methodBeingCopied: methodBeingCopied,
+      )) {
+        builder.write(' ');
+      }
+
+      builder.write('operator ');
+      builder.write(_operator);
+
+      builder.write('(');
+      if (innerParameter) {
+        builder.writeFormalParameter(
+          indexSetter ? 'index' : 'other',
+          type: parameterType,
+          methodBeingCopied: methodBeingCopied,
+        );
+      }
+      if (indexSetter) {
+        builder.write(', ');
+        builder.writeFormalParameter(
+          'newValue',
+          type: assigningType,
+          methodBeingCopied: methodBeingCopied,
+        );
+      }
+      builder.write(') {}');
+    }
+
+    var updatedExisting = await _updateExistingExtension(builder, targetType, (
+      extension,
+      builder,
+    ) {
+      builder.insertMethod(extension, (builder) {
+        writeMethod(builder);
+      });
+    });
+    if (updatedExisting) {
+      return;
+    }
+
+    await _addNewExtension(
+      builder,
+      targetType,
+      target,
+      writeMethod,
+      involvedTypes: [parameterType, returnType],
+    );
+  }
+}
+
 class CreateExtensionSetter extends _CreateExtensionMember {
   String _setterName = '';
 
@@ -205,6 +363,8 @@
       return;
     }
 
+    // TODO(FMorschel): We should take into account if the target type contains
+    // a setter for the same name and stop the fix from being applied.
     // We need the type for the extension.
     var targetType = target.staticType;
     if (targetType == null ||
@@ -223,6 +383,7 @@
         nameGroupName: 'NAME',
         parameterType: fieldType,
         parameterTypeGroupName: 'TYPE',
+        methodBeingCopied: methodBeingCopied,
       );
     }
 
@@ -238,7 +399,13 @@
       return;
     }
 
-    await _addNewExtension(builder, targetType, nameNode, writeSetter);
+    await _addNewExtension(
+      builder,
+      targetType,
+      nameNode,
+      writeSetter,
+      involvedTypes: [fieldType],
+    );
   }
 }
 
@@ -251,24 +418,50 @@
     return CorrectionApplicability.singleLocation;
   }
 
+  ExecutableElement2? get methodBeingCopied =>
+      _enclosingFunction?.declaredFragment?.element;
+
+  FunctionDeclaration? get _enclosingFunction => node.thisOrAncestorOfType();
+
+  /// Creates a change for creating a new extension on the given [targetType].
+  ///
+  /// The new extension should be added after the [nameNode].
+  ///
+  /// The [write] function is used to write the body of the new extension.
+  /// Meaning a method, getter, setter or operator.
+  ///
+  /// The [involvedTypes] are the types that are used in the new extension and
+  /// it's member. This is used to determine the type parameters of the new
+  /// extension.
   Future<void> _addNewExtension(
     ChangeBuilder builder,
     DartType targetType,
-    SimpleIdentifier nameNode,
-    void Function(DartEditBuilder builder) write,
-  ) async {
+    AstNode nameNode,
+    void Function(DartEditBuilder builder) write, {
+    List<DartType?> involvedTypes = const [],
+  }) async {
     // The new extension should be added after it.
     var enclosingUnitChild = nameNode.enclosingUnitChild;
     if (enclosingUnitChild == null) {
       return;
     }
 
+    var extensionTypeParameters = [targetType, ...involvedTypes].typeParameters;
+
     await builder.addDartFileEdit(file, (builder) {
       builder.addInsertion(enclosingUnitChild.end, (builder) {
         builder.writeln();
         builder.writeln();
-        builder.write('extension on ');
-        builder.writeType(targetType);
+        builder.write('extension ');
+        if (extensionTypeParameters.isNotEmpty) {
+          builder.writeTypeParameters(
+            extensionTypeParameters,
+            methodBeingCopied: methodBeingCopied,
+          );
+          builder.write(' ');
+        }
+        builder.write('on ');
+        builder.writeType(targetType, methodBeingCopied: methodBeingCopied);
         builder.writeln(' {');
         builder.write('  ');
         write(builder);
@@ -312,3 +505,50 @@
     return true;
   }
 }
+
+extension on List<DartType?> {
+  /// Returns a list of type parameters that are used in the types.
+  ///
+  /// Iterates over every type in the list:
+  /// - If it is itself a [TypeParameterType], it is added to the list.
+  /// - If it is itself a [TypeParameterType] and it has a
+  /// [TypeParameterType.bound], we get the type parameters with this getter.
+  /// - If it is an [InterfaceType], we get the [InterfaceType.typeArguments]
+  /// it uses and get any type parameters they use by using this same getter.
+  ///
+  /// These types are added internally to a set so that we don't add duplicates.
+  List<TypeParameterElement2> get typeParameters =>
+      {
+        for (var type in whereType<TypeParameterType>()) ...[
+          type.element3,
+          ...[type.bound].typeParameters,
+        ],
+        for (var type in whereType<InterfaceType>())
+          ...type.typeArguments.typeParameters,
+      }.toList();
+}
+
+extension on Element2? {
+  bool get declaresIndex {
+    var element = this;
+    if (element is! InterfaceElement2) {
+      return false;
+    }
+    var inheritanceManager3 = InheritanceManager3();
+    var interface = inheritanceManager3.getInterface2(element);
+    var bracesGetter =
+        interface.map2[Name.forLibrary(
+          element.library2,
+          TokenType.INDEX.lexeme,
+        )];
+    if (bracesGetter != null) {
+      return true;
+    }
+    var bracesSetter =
+        interface.map2[Name.forLibrary(
+          element.library2,
+          TokenType.INDEX_EQ.lexeme,
+        )];
+    return bracesSetter != null;
+  }
+}
diff --git a/pkg/analysis_server/lib/src/services/correction/fix.dart b/pkg/analysis_server/lib/src/services/correction/fix.dart
index 31bda72..05a3611 100644
--- a/pkg/analysis_server/lib/src/services/correction/fix.dart
+++ b/pkg/analysis_server/lib/src/services/correction/fix.dart
@@ -758,6 +758,11 @@
     DartFixKindPriority.standard - 20,
     "Create extension method '{0}'",
   );
+  static const CREATE_EXTENSION_OPERATOR = FixKind(
+    'dart.fix.create.extension.operator',
+    DartFixKindPriority.standard - 20,
+    "Create extension operator '{0}'",
+  );
   static const CREATE_EXTENSION_SETTER = FixKind(
     'dart.fix.create.extension.setter',
     DartFixKindPriority.standard - 20,
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 71ddaa3..1b5ca0f 100644
--- a/pkg/analysis_server/lib/src/services/correction/fix_internal.dart
+++ b/pkg/analysis_server/lib/src/services/correction/fix_internal.dart
@@ -1038,14 +1038,18 @@
     AddNullCheck.new,
     ExtractLocalVariable.new,
     ReplaceWithNullAware.single,
+    CreateExtensionMethod.new,
   ],
   CompileTimeErrorCode.UNCHECKED_OPERATOR_INVOCATION_OF_NULLABLE_VALUE: [
     AddNullCheck.new,
+    CreateExtensionOperator.new,
   ],
   CompileTimeErrorCode.UNCHECKED_PROPERTY_ACCESS_OF_NULLABLE_VALUE: [
     AddNullCheck.new,
     ExtractLocalVariable.new,
     ReplaceWithNullAware.single,
+    CreateExtensionGetter.new,
+    CreateExtensionSetter.new,
   ],
   CompileTimeErrorCode.UNCHECKED_USE_OF_NULLABLE_VALUE_AS_CONDITION: [
     AddNullCheck.new,
@@ -1086,6 +1090,7 @@
   ],
   CompileTimeErrorCode.UNDEFINED_EXTENSION_METHOD: [
     ChangeTo.method,
+    CreateExtensionMethod.new,
     CreateMethod.method,
   ],
   CompileTimeErrorCode.UNDEFINED_EXTENSION_SETTER: [
@@ -1131,6 +1136,7 @@
     ConvertFlutterChild.new,
     ConvertFlutterChildren.new,
   ],
+  CompileTimeErrorCode.UNDEFINED_OPERATOR: [CreateExtensionOperator.new],
   CompileTimeErrorCode.UNDEFINED_SETTER: [
     ChangeTo.getterOrSetter,
     CreateExtensionSetter.new,
diff --git a/pkg/analysis_server/test/src/services/correction/fix/create_extension_member_test.dart b/pkg/analysis_server/test/src/services/correction/fix/create_extension_member_test.dart
index b53a26a..310e255 100644
--- a/pkg/analysis_server/test/src/services/correction/fix/create_extension_member_test.dart
+++ b/pkg/analysis_server/test/src/services/correction/fix/create_extension_member_test.dart
@@ -12,6 +12,7 @@
   defineReflectiveSuite(() {
     defineReflectiveTests(CreateExtensionGetterTest);
     defineReflectiveTests(CreateExtensionMethodTest);
+    defineReflectiveTests(CreateExtensionOperatorTest);
     defineReflectiveTests(CreateExtensionSetterTest);
   });
 }
@@ -21,6 +22,19 @@
   @override
   FixKind get kind => DartFixKind.CREATE_EXTENSION_GETTER;
 
+  @FailingTest(reason: 'Should not be a fix because it will conflict with a')
+  Future<void> test_conflicting_setter() async {
+    await resolveTestCode('''
+class A {
+  void set a(int value) {}
+}
+void f() {
+  A().a;
+}
+''');
+    await assertNoFix();
+  }
+
   Future<void> test_contextType() async {
     await resolveTestCode('''
 void f() {
@@ -184,6 +198,40 @@
 ''');
   }
 
+  Future<void> test_nullableTargetType() async {
+    await resolveTestCode('''
+void f(int? p) {
+  int _ = p.test;
+}
+''');
+    await assertHasFix('''
+void f(int? p) {
+  int _ = p.test;
+}
+
+extension on int? {
+  int get test => null;
+}
+''');
+  }
+
+  Future<void> test_parameterType() async {
+    await resolveTestCode('''
+void f<T>(T t) {
+  int _ = t.test;
+}
+''');
+    await assertHasFix('''
+void f<T>(T t) {
+  int _ = t.test;
+}
+
+extension <T> on T {
+  int get test => null;
+}
+''');
+  }
+
   Future<void> test_parent_nothing() async {
     await resolveTestCode('''
 void f() {
@@ -343,6 +391,25 @@
 ''');
   }
 
+  Future<void> test_existingExtension_generic_matching2() async {
+    await resolveTestCode('''
+void f(List<int> a) {
+  a.test();
+}
+
+extension E<T extends Iterable<int>> on T {}
+''');
+    await assertHasFix('''
+void f(List<int> a) {
+  a.test();
+}
+
+extension E<T extends Iterable<int>> on T {
+  void test() {}
+}
+''');
+  }
+
   Future<void> test_existingExtension_generic_notMatching() async {
     await resolveTestCode('''
 void f(List<int> a) {
@@ -364,6 +431,27 @@
 ''');
   }
 
+  Future<void> test_existingExtension_generic_notMatching2() async {
+    await resolveTestCode('''
+void f(List<int> a) {
+  a.test();
+}
+
+extension E<T extends Iterable<int>> on Iterable<T> {}
+''');
+    await assertHasFix('''
+void f(List<int> a) {
+  a.test();
+}
+
+extension on List<int> {
+  void test() {}
+}
+
+extension E<T extends Iterable<int>> on Iterable<T> {}
+''');
+  }
+
   Future<void> test_existingExtension_hasMethod() async {
     await resolveTestCode('''
 void f() {
@@ -429,6 +517,66 @@
 ''');
   }
 
+  Future<void> test_nullableTargetType() async {
+    await resolveTestCode('''
+void f(int? p) {
+  int _ = p.test();
+}
+''');
+    await assertHasFix('''
+void f(int? p) {
+  int _ = p.test();
+}
+
+extension on int? {
+  int test() {}
+}
+''');
+  }
+
+  Future<void> test_parameters() async {
+    await resolveTestCode('''
+void f() {
+  ''.test(1, '');
+}
+''');
+    await assertHasFix('''
+void f() {
+  ''.test(1, '');
+}
+
+extension on String {
+  void test(int i, String s) {}
+}
+''');
+  }
+
+  Future<void> test_parent_nothing() async {
+    await resolveTestCode('''
+void f() {
+  test();
+}
+''');
+    await assertNoFix();
+  }
+
+  Future<void> test_parent_prefixedIdentifier() async {
+    await resolveTestCode('''
+void f(String a) {
+  a.test();
+}
+''');
+    await assertHasFix('''
+void f(String a) {
+  a.test();
+}
+
+extension on String {
+  void test() {}
+}
+''');
+  }
+
   Future<void> test_target_identifier() async {
     await resolveTestCode('''
 void f(String a) {
@@ -472,6 +620,23 @@
     await assertNoFix();
   }
 
+  Future<void> test_targetType_functionType() async {
+    await resolveTestCode('''
+void f(void Function(int p) a) {
+  a.test();
+}
+''');
+    await assertHasFix('''
+void f(void Function(int p) a) {
+  a.test();
+}
+
+extension on void Function(int p) {
+  void test() {}
+}
+''');
+  }
+
   Future<void> test_targetType_hasTypeArguments() async {
     await resolveTestCode('''
 void f(List<int> a) {
@@ -488,6 +653,351 @@
 }
 ''');
   }
+
+  Future<void> test_targetType_prefixed1() async {
+    await resolveTestCode('''
+import 'dart:math' as math;
+
+void f(math.Random a) {
+  a.test();
+}
+''');
+    await assertHasFix('''
+import 'dart:math' as math;
+
+void f(math.Random a) {
+  a.test();
+}
+
+extension on math.Random {
+  void test() {}
+}
+''');
+  }
+
+  Future<void> test_targetType_prefixed2() async {
+    await resolveTestCode('''
+import 'dart:math' as math;
+
+void f(List<math.Random> a) {
+  a.test();
+}
+''');
+    await assertHasFix('''
+import 'dart:math' as math;
+
+void f(List<math.Random> a) {
+  a.test();
+}
+
+extension on List<math.Random> {
+  void test() {}
+}
+''');
+  }
+
+  Future<void> test_targetType_record() async {
+    await resolveTestCode('''
+void f((int, String) a) {
+  a.test();
+}
+''');
+    await assertHasFix('''
+void f((int, String) a) {
+  a.test();
+}
+
+extension on (int, String) {
+  void test() {}
+}
+''');
+  }
+
+  Future<void> test_targetType_typeVariable1() async {
+    await resolveTestCode('''
+void f<T>(T a) {
+  a.test();
+}
+''');
+    await assertHasFix('''
+void f<T>(T a) {
+  a.test();
+}
+
+extension <T> on T {
+  void test() {}
+}
+''');
+  }
+
+  Future<void> test_targetType_typeVariable2() async {
+    await resolveTestCode('''
+void f<T>(List<T> a, T b) {
+  a.test(b);
+}
+''');
+    await assertHasFix('''
+void f<T>(List<T> a, T b) {
+  a.test(b);
+}
+
+extension <T> on List<T> {
+  void test(T b) {}
+}
+''');
+  }
+
+  Future<void> test_targetType_typeVariable_bound() async {
+    await resolveTestCode('''
+void f<T extends num>(T a) {
+  a.test();
+}
+''');
+    await assertHasFix('''
+void f<T extends num>(T a) {
+  a.test();
+}
+
+extension <T extends num> on T {
+  void test() {}
+}
+''');
+  }
+
+  Future<void> test_typeArgument_parameter() async {
+    await resolveTestCode('''
+void f<T>(T p) {
+  ''.test(p);
+}
+''');
+    await assertHasFix('''
+void f<T>(T p) {
+  ''.test(p);
+}
+
+extension on String {
+  void test<T>(T p) {}
+}
+''');
+  }
+
+  Future<void> test_typeArgument_return() async {
+    await resolveTestCode('''
+void f<T>() {
+  T _ = ''.test(0, 1.2);
+}
+''');
+    await assertHasFix('''
+void f<T>() {
+  T _ = ''.test(0, 1.2);
+}
+
+extension on String {
+  T test<T>(int i, double d) {}
+}
+''');
+  }
+
+  Future<void> test_typeArguments_bounded() async {
+    await resolveTestCode('''
+void f<T, R extends T>(R target) {
+  R _ = target.test();
+}
+''');
+    await assertHasFix('''
+void f<T, R extends T>(R target) {
+  R _ = target.test();
+}
+
+extension <R extends T, T> on R {
+  R test() {}
+}
+''');
+  }
+
+  Future<void> test_typeArguments_everywhere() async {
+    await resolveTestCode('''
+void f<R, T, T2>(T2 target, T value) {
+  R _ = target.test(value);
+}
+''');
+    await assertHasFix('''
+void f<R, T, T2>(T2 target, T value) {
+  R _ = target.test(value);
+}
+
+extension <T2> on T2 {
+  R test<R, T>(T value) {}
+}
+''');
+  }
+
+  Future<void> test_typeArguments_same() async {
+    await resolveTestCode('''
+void f<T>(T target) {
+  T _ = target.test();
+}
+''');
+    await assertHasFix('''
+void f<T>(T target) {
+  T _ = target.test();
+}
+
+extension <T> on T {
+  T test() {}
+}
+''');
+  }
+}
+
+@reflectiveTest
+class CreateExtensionOperatorTest extends FixProcessorTest {
+  @override
+  FixKind get kind => DartFixKind.CREATE_EXTENSION_OPERATOR;
+
+  Future<void> test_binary() async {
+    await resolveTestCode('''
+void f() {
+  '' / 1;
+}
+''');
+    await assertHasFix('''
+void f() {
+  '' / 1;
+}
+
+extension on String {
+  void operator /(int other) {}
+}
+''');
+  }
+
+  Future<void> test_index_getter() async {
+    await resolveTestCode('''
+class A {
+  void operator []=(String index, bool newValue) {}
+}
+
+void f() {
+  A()[0];
+}
+''');
+    // Because A already has a []= operator, we don't add a new getter for it.
+    await assertNoFix();
+  }
+
+  Future<void> test_index_getter_ok() async {
+    await resolveTestCode('''
+void f() {
+  String _ = 0[false];
+}
+''');
+    await assertHasFix('''
+void f() {
+  String _ = 0[false];
+}
+
+extension on int {
+  String operator [](bool other) {}
+}
+''');
+  }
+
+  Future<void> test_index_setter() async {
+    await resolveTestCode('''
+void f() {
+  ''[0] = false;
+}
+''');
+    // Because String already has a [] operator, we don't add a new setter for
+    // it.
+    await assertNoFix();
+  }
+
+  Future<void> test_index_setter_ok() async {
+    await resolveTestCode('''
+void f() {
+  0[''] = false;
+}
+''');
+    await assertHasFix('''
+void f() {
+  0[''] = false;
+}
+
+extension on int {
+  void operator []=(String index, bool newValue) {}
+}
+''');
+  }
+
+  Future<void> test_nullableTargetType() async {
+    await resolveTestCode('''
+void f(int? p) {
+  p + 0;
+}
+''');
+    await assertHasFix('''
+void f(int? p) {
+  p + 0;
+}
+
+extension on int? {
+  void operator +(int other) {}
+}
+''');
+  }
+
+  Future<void> test_returnType() async {
+    await resolveTestCode('''
+void f() {
+  int _ = '' / 1;
+}
+''');
+    await assertHasFix('''
+void f() {
+  int _ = '' / 1;
+}
+
+extension on String {
+  int operator /(int other) {}
+}
+''');
+  }
+
+  Future<void> test_typeParameter() async {
+    await resolveTestCode('''
+void f<T>(T a) {
+  a / a;
+}
+''');
+    await assertHasFix('''
+void f<T>(T a) {
+  a / a;
+}
+
+extension <T> on T {
+  void operator /(T other) {}
+}
+''');
+  }
+
+  Future<void> test_unary() async {
+    await resolveTestCode('''
+void f() {
+  int _ = ~'';
+}
+''');
+    await assertHasFix('''
+void f() {
+  int _ = ~'';
+}
+
+extension on String {
+  int operator ~() {}
+}
+''');
+  }
 }
 
 @reflectiveTest
@@ -495,6 +1005,19 @@
   @override
   FixKind get kind => DartFixKind.CREATE_EXTENSION_SETTER;
 
+  @FailingTest(reason: 'Should not be a fix because it will conflict with a')
+  Future<void> test_conflicting_getter() async {
+    await resolveTestCode('''
+class A {
+  int get a() {}
+}
+void f() {
+  A().a = 0;
+}
+''');
+    await assertNoFix();
+  }
+
   Future<void> test_existingExtension() async {
     await resolveTestCode('''
 void f() {
@@ -619,6 +1142,23 @@
 ''');
   }
 
+  Future<void> test_nullableTargetType() async {
+    await resolveTestCode('''
+void f(int? p) {
+  p.test = 0;
+}
+''');
+    await assertHasFix('''
+void f(int? p) {
+  p.test = 0;
+}
+
+extension on int? {
+  set test(int test) {}
+}
+''');
+  }
+
   Future<void> test_parent_nothing() async {
     await resolveTestCode('''
 void f() {
@@ -678,4 +1218,21 @@
 }
 ''');
   }
+
+  Future<void> test_typeParameter() async {
+    await resolveTestCode('''
+void f<T>(T a) {
+  a.test = 0;
+}
+''');
+    await assertHasFix('''
+void f<T>(T a) {
+  a.test = 0;
+}
+
+extension <T> on T {
+  set test(int test) {}
+}
+''');
+  }
 }
diff --git a/pkg/analysis_server/test/src/services/correction/fix/create_getter_test.dart b/pkg/analysis_server/test/src/services/correction/fix/create_getter_test.dart
index 4a35456..869e4ea 100644
--- a/pkg/analysis_server/test/src/services/correction/fix/create_getter_test.dart
+++ b/pkg/analysis_server/test/src/services/correction/fix/create_getter_test.dart
@@ -10,225 +10,12 @@
 
 void main() {
   defineReflectiveSuite(() {
-    defineReflectiveTests(CreateExtensionGetterTest);
     defineReflectiveTests(CreateGetterTest);
     defineReflectiveTests(CreateGetterMixinTest);
   });
 }
 
 @reflectiveTest
-class CreateExtensionGetterTest extends FixProcessorTest {
-  @override
-  FixKind get kind => DartFixKind.CREATE_EXTENSION_GETTER;
-
-  Future<void> test_contextType() async {
-    await resolveTestCode('''
-void f() {
-  // ignore:unused_local_variable
-  int v = ''.test;
-}
-''');
-    await assertHasFix('''
-void f() {
-  // ignore:unused_local_variable
-  int v = ''.test;
-}
-
-extension on String {
-  int get test => null;
-}
-''');
-  }
-
-  Future<void> test_contextType_no() async {
-    await resolveTestCode('''
-void f() {
-  ''.test;
-}
-''');
-    await assertHasFix('''
-void f() {
-  ''.test;
-}
-
-extension on String {
-  get test => null;
-}
-''');
-    assertLinkedGroup(change.linkedEditGroups[0], ['null']);
-  }
-
-  Future<void> test_existingExtension_contextType() async {
-    await resolveTestCode('''
-void f() {
-  // ignore:unused_local_variable
-  int v = ''.test;
-}
-
-extension on String {}
-''');
-    await assertHasFix('''
-void f() {
-  // ignore:unused_local_variable
-  int v = ''.test;
-}
-
-extension on String {
-  int get test => null;
-}
-''');
-  }
-
-  Future<void> test_existingExtension_generic_matching() async {
-    await resolveTestCode('''
-void f(List<int> a) {
-  a.test;
-}
-
-extension E<T> on Iterable<T> {}
-''');
-    await assertHasFix('''
-void f(List<int> a) {
-  a.test;
-}
-
-extension E<T> on Iterable<T> {
-  get test => null;
-}
-''');
-  }
-
-  Future<void> test_existingExtension_generic_notMatching() async {
-    await resolveTestCode('''
-void f(List<int> a) {
-  a.test;
-}
-
-extension E<K, V> on Map<K, V> {}
-''');
-    await assertHasFix('''
-void f(List<int> a) {
-  a.test;
-}
-
-extension on List<int> {
-  get test => null;
-}
-
-extension E<K, V> on Map<K, V> {}
-''');
-  }
-
-  Future<void> test_existingExtension_hasMethod() async {
-    await resolveTestCode('''
-void f() {
-  ''.test;
-}
-
-extension E on String {
-  // ignore:unused_element
-  void foo() {}
-}
-''');
-    await assertHasFix('''
-void f() {
-  ''.test;
-}
-
-extension E on String {
-  get test => null;
-
-  // ignore:unused_element
-  void foo() {}
-}
-''');
-  }
-
-  Future<void> test_existingExtension_notGeneric_matching() async {
-    await resolveTestCode('''
-void f() {
-  ''.test;
-}
-
-extension on String {}
-''');
-    await assertHasFix('''
-void f() {
-  ''.test;
-}
-
-extension on String {
-  get test => null;
-}
-''');
-  }
-
-  Future<void> test_existingExtension_notGeneric_notMatching() async {
-    await resolveTestCode('''
-void f() {
-  ''.test;
-}
-
-extension on int {}
-''');
-    await assertHasFix('''
-void f() {
-  ''.test;
-}
-
-extension on String {
-  get test => null;
-}
-
-extension on int {}
-''');
-  }
-
-  Future<void> test_parent_nothing() async {
-    await resolveTestCode('''
-void f() {
-  test;
-}
-''');
-    await assertNoFix();
-  }
-
-  Future<void> test_parent_prefixedIdentifier() async {
-    await resolveTestCode('''
-void f(String a) {
-  a.test;
-}
-''');
-    await assertHasFix('''
-void f(String a) {
-  a.test;
-}
-
-extension on String {
-  get test => null;
-}
-''');
-  }
-
-  Future<void> test_targetType_hasTypeArguments() async {
-    await resolveTestCode('''
-void f(List<int> a) {
-  a.test;
-}
-''');
-    await assertHasFix('''
-void f(List<int> a) {
-  a.test;
-}
-
-extension on List<int> {
-  get test => null;
-}
-''');
-  }
-}
-
-@reflectiveTest
 class CreateGetterMixinTest extends FixProcessorTest {
   @override
   FixKind get kind => DartFixKind.CREATE_GETTER;
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 2884a41..0ed5248 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
@@ -680,7 +680,8 @@
 
   @override
   void writeParameterMatchingArgument(
-      Expression argument, int index, Set<String> usedNames) {
+      Expression argument, int index, Set<String> usedNames,
+      {ExecutableElement2? methodBeingCopied}) {
     // Append type name.
     var type = argument.staticType;
     if (type == null || type.isBottom || type.isDartCoreNull) {
@@ -690,7 +691,12 @@
         type.nullabilitySuffix == NullabilitySuffix.none) {
       write('required ');
     }
-    if (writeType(type, addSupertypeProposals: true, groupName: 'TYPE$index')) {
+    if (writeType(
+      type,
+      addSupertypeProposals: true,
+      groupName: 'TYPE$index',
+      methodBeingCopied: methodBeingCopied,
+    )) {
       write(' ');
     }
     // Append parameter name.
@@ -707,7 +713,8 @@
   }
 
   @override
-  void writeParametersMatchingArguments(ArgumentList argumentList) {
+  void writeParametersMatchingArguments(ArgumentList argumentList,
+      {ExecutableElement2? methodBeingCopied}) {
     // TODO(brianwilkerson): Handle the case when there are required parameters
     // after named parameters.
     var usedNames = <String>{};
@@ -722,7 +729,8 @@
         hasNamedParameters = true;
         write('{');
       }
-      writeParameterMatchingArgument(argument, i, usedNames);
+      writeParameterMatchingArgument(argument, i, usedNames,
+          methodBeingCopied: methodBeingCopied);
     }
     if (hasNamedParameters) {
       write('}');
@@ -738,12 +746,15 @@
   }
 
   @override
-  void writeSetterDeclaration(String name,
-      {void Function()? bodyWriter,
-      bool isStatic = false,
-      String? nameGroupName,
-      DartType? parameterType,
-      String? parameterTypeGroupName}) {
+  void writeSetterDeclaration(
+    String name, {
+    void Function()? bodyWriter,
+    bool isStatic = false,
+    String? nameGroupName,
+    DartType? parameterType,
+    String? parameterTypeGroupName,
+    ExecutableElement2? methodBeingCopied,
+  }) {
     if (isStatic) {
       write(Keyword.STATIC.lexeme);
       write(' ');
@@ -757,7 +768,9 @@
     }
     write('(');
     if (parameterType != null && parameterType is! DynamicType) {
-      if (writeType(parameterType, groupName: parameterTypeGroupName)) {
+      if (writeType(parameterType,
+          groupName: parameterTypeGroupName,
+          methodBeingCopied: methodBeingCopied)) {
         write(' ');
       }
     }
diff --git a/pkg/analyzer_plugin/lib/utilities/change_builder/change_builder_dart.dart b/pkg/analyzer_plugin/lib/utilities/change_builder/change_builder_dart.dart
index 985c5e0..9107688 100644
--- a/pkg/analyzer_plugin/lib/utilities/change_builder/change_builder_dart.dart
+++ b/pkg/analyzer_plugin/lib/utilities/change_builder/change_builder_dart.dart
@@ -286,13 +286,20 @@
   /// ensure that the name is unique (and the chosen name will be added to the
   /// set).
   void writeParameterMatchingArgument(
-      Expression argument, int index, Set<String> usedNames);
+    Expression argument,
+    int index,
+    Set<String> usedNames, {
+    ExecutableElement2? methodBeingCopied,
+  });
 
   /// Writes the code for a list of parameters that would match the given list
   /// of [arguments].
   ///
   /// The surrounding parentheses are *not* written.
-  void writeParametersMatchingArguments(ArgumentList arguments);
+  void writeParametersMatchingArguments(
+    ArgumentList arguments, {
+    ExecutableElement2? methodBeingCopied,
+  });
 
   /// Writes the code that references the [element].
   ///
@@ -310,12 +317,15 @@
   /// [parameterType] is provided, then it will be used as the type of the
   /// parameter. If a [parameterTypeGroupName] is provided, then if a parameter
   /// type was written it will be in the linked edit group with that name.
-  void writeSetterDeclaration(String name,
-      {void Function()? bodyWriter,
-      bool isStatic = false,
-      String? nameGroupName,
-      DartType? parameterType,
-      String? parameterTypeGroupName});
+  void writeSetterDeclaration(
+    String name, {
+    void Function()? bodyWriter,
+    bool isStatic = false,
+    String? nameGroupName,
+    DartType? parameterType,
+    String? parameterTypeGroupName,
+    ExecutableElement2? methodBeingCopied,
+  });
 
   /// Writes the code for a type annotation for the given [type].
   ///