[analyzer] CreateConstructor to handle undefined enum constructor

Fixes #48481

Change-Id: I237bcbf1cb3202435c9bda31467a76f27e3dc896
Reviewed-on: https://dart-review.googlesource.com/c/sdk/+/245169
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/create_constructor.dart b/pkg/analysis_server/lib/src/services/correction/dart/create_constructor.dart
index 439f0f4..769ed53 100644
--- a/pkg/analysis_server/lib/src/services/correction/dart/create_constructor.dart
+++ b/pkg/analysis_server/lib/src/services/correction/dart/create_constructor.dart
@@ -6,6 +6,7 @@
 import 'package:analysis_server/src/services/correction/fix.dart';
 import 'package:analysis_server/src/services/correction/util.dart';
 import 'package:analyzer/dart/ast/ast.dart';
+import 'package:analyzer/dart/element/element.dart';
 import 'package:analyzer/dart/element/type.dart';
 import 'package:analyzer_plugin/utilities/change_builder/change_builder_core.dart';
 import 'package:analyzer_plugin/utilities/fixes/fixes.dart';
@@ -14,7 +15,7 @@
 class CreateConstructor extends CorrectionProducer {
   /// The name of the constructor being created.
   /// TODO(migration) We set this node when we have the change.
-  late ConstructorName _constructorName;
+  late String _constructorName;
 
   @override
   List<Object> get fixArguments => [_constructorName];
@@ -24,6 +25,7 @@
 
   @override
   Future<void> compute(ChangeBuilder builder) async {
+    var node = this.node;
     final argumentList = node.parent is ArgumentList ? node.parent : node;
     if (argumentList is ArgumentList) {
       var instanceCreation = argumentList.parent;
@@ -31,28 +33,32 @@
         await _proposeFromInstanceCreation(builder, instanceCreation);
       }
     } else {
-      await _proposeFromConstructorName(builder);
+      if (node is SimpleIdentifier) {
+        var parent = node.parent;
+        if (parent is ConstructorName) {
+          await _proposeFromConstructorName(builder, node, parent);
+          return;
+        }
+      }
+      var parent = node.thisOrAncestorOfType<EnumConstantDeclaration>();
+      if (parent != null) {
+        await _proposeFromEnumConstantDeclaration(builder, parent.name, parent);
+      }
     }
   }
 
-  Future<void> _proposeFromConstructorName(ChangeBuilder builder) async {
-    var name = node;
-    if (name is! SimpleIdentifier) {
-      return;
-    }
-
+  Future<void> _proposeFromConstructorName(ChangeBuilder builder,
+      SimpleIdentifier name, ConstructorName constructorName) async {
     InstanceCreationExpression? instanceCreation;
-    if (name.parent is ConstructorName) {
-      _constructorName = name.parent as ConstructorName;
-      if (_constructorName.name == name) {
-        // Type.name
-        if (_constructorName.parent is InstanceCreationExpression) {
-          instanceCreation =
-              _constructorName.parent as InstanceCreationExpression;
-          // new Type.name()
-          if (instanceCreation.constructorName != _constructorName) {
-            return;
-          }
+    _constructorName = constructorName.toSource();
+    if (constructorName.name == name) {
+      var grandParent = constructorName.parent;
+      // Type.name
+      if (grandParent is InstanceCreationExpression) {
+        instanceCreation = grandParent;
+        // new Type.name()
+        if (grandParent.constructorName != constructorName) {
+          return;
         }
       }
     }
@@ -63,7 +69,7 @@
     }
 
     // prepare target interface type
-    var targetType = _constructorName.type.type;
+    var targetType = constructorName.type.type;
     if (targetType is! InterfaceType) {
       return;
     }
@@ -91,28 +97,65 @@
       return;
     }
 
-    var targetFile = targetElement.source.fullName;
-    final instanceCreation_final = instanceCreation;
-    await builder.addDartFileEdit(targetFile, (builder) {
-      builder.addInsertion(targetLocation.offset, (builder) {
-        builder.write(targetLocation.prefix);
-        builder.writeConstructorDeclaration(targetElement.name,
-            argumentList: instanceCreation_final.argumentList,
-            constructorName: name,
-            constructorNameGroupName: 'NAME');
-        builder.write(targetLocation.suffix);
-      });
-      if (targetFile == file) {
-        builder.addLinkedPosition(range.node(name), 'NAME');
-      }
-    });
+    await _write(builder, name, targetElement, targetLocation,
+        constructorName: name, argumentList: instanceCreation.argumentList);
+  }
+
+  Future<void> _proposeFromEnumConstantDeclaration(ChangeBuilder builder,
+      SimpleIdentifier name, EnumConstantDeclaration parent) async {
+    var grandParent = parent.parent;
+    if (grandParent is! EnumDeclaration) {
+      return;
+    }
+    var targetElement = grandParent.declaredElement;
+    if (targetElement == null) {
+      return;
+    }
+
+    // prepare target interface type
+    var targetResult = await sessionHelper.getElementDeclaration(targetElement);
+    if (targetResult == null) {
+      return;
+    }
+
+    var targetNode = targetResult.node;
+    if (targetNode is! EnumDeclaration) {
+      return;
+    }
+
+    var targetUnit = targetResult.resolvedUnit;
+    if (targetUnit == null) {
+      return;
+    }
+
+    // prepare location
+    var targetLocation = CorrectionUtils(targetUnit)
+        .prepareEnumNewConstructorLocation(targetNode);
+    if (targetLocation == null) {
+      return;
+    }
+
+    var arguments = parent.arguments;
+    _constructorName =
+        '${targetNode.name}${arguments?.constructorSelector ?? ''}';
+
+    await _write(
+      builder,
+      name,
+      targetElement,
+      targetLocation,
+      isConst: true,
+      constructorName: arguments?.constructorSelector?.name,
+      argumentList: arguments?.argumentList,
+    );
   }
 
   Future<void> _proposeFromInstanceCreation(ChangeBuilder builder,
       InstanceCreationExpression instanceCreation) async {
-    _constructorName = instanceCreation.constructorName;
+    var constructorName = instanceCreation.constructorName;
+    _constructorName = constructorName.toSource();
     // should be synthetic default constructor
-    var constructorElement = _constructorName.staticElement;
+    var constructorElement = constructorName.staticElement;
     if (constructorElement == null ||
         !constructorElement.isDefaultConstructor ||
         !constructorElement.isSynthetic) {
@@ -153,4 +196,30 @@
       });
     });
   }
+
+  Future<void> _write(
+    ChangeBuilder builder,
+    SimpleIdentifier name,
+    ClassElement targetElement,
+    InsertionLocation targetLocation, {
+    SimpleIdentifier? constructorName,
+    bool isConst = false,
+    ArgumentList? argumentList,
+  }) async {
+    var targetFile = targetElement.source.fullName;
+    await builder.addDartFileEdit(targetFile, (builder) {
+      builder.addInsertion(targetLocation.offset, (builder) {
+        builder.write(targetLocation.prefix);
+        builder.writeConstructorDeclaration(targetElement.name,
+            isConst: isConst,
+            argumentList: argumentList,
+            constructorName: constructorName,
+            constructorNameGroupName: 'NAME');
+        builder.write(targetLocation.suffix);
+      });
+      if (targetFile == file) {
+        builder.addLinkedPosition(range.node(name), 'NAME');
+      }
+    });
+  }
 }
diff --git a/pkg/analysis_server/lib/src/services/correction/error_fix_status.yaml b/pkg/analysis_server/lib/src/services/correction/error_fix_status.yaml
index 2faa043..5ceef0d 100644
--- a/pkg/analysis_server/lib/src/services/correction/error_fix_status.yaml
+++ b/pkg/analysis_server/lib/src/services/correction/error_fix_status.yaml
@@ -327,7 +327,7 @@
   status: needsEvaluation
 CompileTimeErrorCode.ENUM_CONSTANT_SAME_NAME_AS_ENCLOSING:
   status: needsEvaluation
-CompileTimeErrorCode.ENUM_CONSTANT_WITH_NON_CONST_CONSTRUCTOR: 
+CompileTimeErrorCode.ENUM_CONSTANT_WITH_NON_CONST_CONSTRUCTOR:
   status: noFix
   since: 2.17
 CompileTimeErrorCode.ENUM_INSTANTIATED_TO_BOUNDS_IS_NOT_WELL_BOUNDED:
@@ -956,15 +956,12 @@
   status: hasFix
 CompileTimeErrorCode.UNDEFINED_ENUM_CONSTANT:
   status: hasFix
-  issue: https://github.com/dart-lang/sdk/issues/47643
 CompileTimeErrorCode.UNDEFINED_ENUM_CONSTRUCTOR_NAMED:
-  status: needsFix
+  status: hasFix
   since: 2.17
-  issue: https://github.com/dart-lang/sdk/issues/48481
 CompileTimeErrorCode.UNDEFINED_ENUM_CONSTRUCTOR_UNNAMED:
-  status: needsFix
+  status: hasFix
   since: 2.17
-  issue: https://github.com/dart-lang/sdk/issues/48481
 CompileTimeErrorCode.UNDEFINED_EXTENSION_GETTER:
   status: hasFix
 CompileTimeErrorCode.UNDEFINED_EXTENSION_METHOD:
@@ -1881,7 +1878,7 @@
 LintCode.use_decorated_box:
   status: needsEvaluation
 LintCode.use_enums:
-    status: hasFix
+  status: hasFix
 LintCode.use_full_hex_values_for_flutter_colors:
   status: hasFix
 LintCode.use_function_type_syntax_for_parameters:
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 dcb96bf..69727ef 100644
--- a/pkg/analysis_server/lib/src/services/correction/fix_internal.dart
+++ b/pkg/analysis_server/lib/src/services/correction/fix_internal.dart
@@ -230,6 +230,7 @@
 /// Computer for Dart "fix all in file" fixes.
 class FixInFileProcessor {
   final DartFixContext context;
+
   FixInFileProcessor(this.context);
 
   Future<List<Fix>> compute() async {
@@ -1090,6 +1091,12 @@
       AddEnumConstant.new,
       ChangeTo.getterOrSetter,
     ],
+    CompileTimeErrorCode.UNDEFINED_ENUM_CONSTRUCTOR_NAMED: [
+      CreateConstructor.new,
+    ],
+    CompileTimeErrorCode.UNDEFINED_ENUM_CONSTRUCTOR_UNNAMED: [
+      CreateConstructor.new,
+    ],
     CompileTimeErrorCode.UNDEFINED_EXTENSION_GETTER: [
       ChangeTo.getterOrSetter,
       CreateGetter.new,
diff --git a/pkg/analysis_server/test/src/services/correction/fix/create_constructor_test.dart b/pkg/analysis_server/test/src/services/correction/fix/create_constructor_test.dart
index 196a197..c00ab29 100644
--- a/pkg/analysis_server/test/src/services/correction/fix/create_constructor_test.dart
+++ b/pkg/analysis_server/test/src/services/correction/fix/create_constructor_test.dart
@@ -168,4 +168,56 @@
 ''');
     assertLinkedGroup(change.linkedEditGroups[0], ['named(int ', 'named(1']);
   }
+
+  Future<void> test_undefined_enum_constructor_named() async {
+    await resolveTestCode('''
+enum E {
+  c.x();
+  const E.y();
+}
+''');
+    await assertHasFix('''
+enum E {
+  c.x();
+  const E.y();
+
+  const E.x();
+}
+''', matchFixMessage: "Create constructor 'E.x'");
+  }
+
+  Future<void> test_undefined_enum_constructor_unnamed() async {
+    await resolveTestCode('''
+enum E {
+  c;
+  const E.x();
+}
+''');
+    await assertHasFix('''
+enum E {
+  c;
+  const E.x();
+
+  const E();
+}
+''', matchFixMessage: "Create constructor 'E'");
+  }
+
+  @FailingTest(reason: 'parameter types should be inferred')
+  Future<void> test_undefined_enum_constructor_unnamed_parameters() async {
+    await resolveTestCode('''
+enum E {
+  c(1);
+  const E.x();
+}
+''');
+    await assertHasFix('''
+enum E {
+  c(1);
+  const E.x();
+
+  const E(int i);
+}
+''');
+  }
 }