[analysis_server] associate `INITIALIZING_FORMAL_FOR_NON_EXISTENT_FIELD` with `ChangeTo`

Fixes #49058

Change-Id: Ie064f0155269282b6c8fa4208f9881ed307c0421
Reviewed-on: https://dart-review.googlesource.com/c/sdk/+/251762
Commit-Queue: Brian Wilkerson <brianwilkerson@google.com>
Reviewed-by: Brian Wilkerson <brianwilkerson@google.com>
diff --git a/pkg/analysis_server/lib/src/services/correction/dart/change_to.dart b/pkg/analysis_server/lib/src/services/correction/dart/change_to.dart
index 1a88115..ff8ea9d 100644
--- a/pkg/analysis_server/lib/src/services/correction/dart/change_to.dart
+++ b/pkg/analysis_server/lib/src/services/correction/dart/change_to.dart
@@ -30,8 +30,8 @@
   /// Initialize a newly created instance that will propose classes and mixins.
   ChangeTo.classOrMixin() : _kind = _ReplacementKind.classOrMixin;
 
-  /// Initialize a newly created instance that will propose formal parameters.
-  ChangeTo.formalParameter() : _kind = _ReplacementKind.formalParameter;
+  /// Initialize a newly created instance that will propose fields.
+  ChangeTo.field() : _kind = _ReplacementKind.field;
 
   /// Initialize a newly created instance that will propose functions.
   ChangeTo.function() : _kind = _ReplacementKind.function;
@@ -42,6 +42,11 @@
   /// Initialize a newly created instance that will propose methods.
   ChangeTo.method() : _kind = _ReplacementKind.method;
 
+  /// Initialize a newly created instance that will propose super formal
+  /// parameters.
+  ChangeTo.superFormalParameter()
+      : _kind = _ReplacementKind.superFormalParameter;
+
   @override
   List<Object> get fixArguments => [_proposedName];
 
@@ -58,14 +63,16 @@
       await _proposeAnnotation(builder);
     } else if (_kind == _ReplacementKind.classOrMixin) {
       await _proposeClassOrMixin(builder, node);
-    } else if (_kind == _ReplacementKind.formalParameter) {
-      await _proposeFormalParameter(builder);
+    } else if (_kind == _ReplacementKind.field) {
+      await _proposeField(builder);
     } else if (_kind == _ReplacementKind.function) {
       await _proposeFunction(builder);
     } else if (_kind == _ReplacementKind.getterOrSetter) {
       await _proposeGetterOrSetter(builder);
     } else if (_kind == _ReplacementKind.method) {
       await _proposeMethod(builder);
+    } else if (_kind == _ReplacementKind.superFormalParameter) {
+      await _proposeSuperFormalParameter(builder);
     }
   }
 
@@ -123,76 +130,69 @@
     }
   }
 
-  Future<void> _proposeClassOrMixinMember(ChangeBuilder builder,
-      Expression? target, _ElementPredicate predicate) async {
-    final node = this.node;
+  Future<void> _proposeClassOrMixinMember(
+      ChangeBuilder builder,
+      SimpleIdentifier node,
+      Expression? target,
+      _ElementPredicate predicate) async {
     var targetIdentifierElement =
         target is Identifier ? target.staticElement : null;
-    if (node is SimpleIdentifier) {
-      var finder = _ClosestElementFinder(node.name, predicate);
-      // unqualified invocation
-      if (target == null) {
-        var clazz = node.thisOrAncestorOfType<ClassDeclaration>();
-        if (clazz != null) {
-          var classElement = clazz.declaredElement!;
-          _updateFinderWithClassMembers(finder, classElement);
-        }
-      } else if (target is ExtensionOverride) {
-        _updateFinderWithExtensionMembers(finder, target.staticElement);
-      } else if (targetIdentifierElement is ExtensionElement) {
-        _updateFinderWithExtensionMembers(finder, targetIdentifierElement);
-      } else {
-        var classElement = getTargetClassElement(target);
-        if (classElement != null) {
-          _updateFinderWithClassMembers(finder, classElement);
-        }
+    var finder = _ClosestElementFinder(node.name, predicate);
+    // unqualified invocation
+    if (target == null) {
+      var clazz = node.thisOrAncestorOfType<ClassDeclaration>();
+      if (clazz != null) {
+        var classElement = clazz.declaredElement!;
+        _updateFinderWithClassMembers(finder, classElement);
       }
-      // if we have close enough element, suggest to use it
-      await _suggest(builder, node, finder._element?.displayName);
+    } else if (target is ExtensionOverride) {
+      _updateFinderWithExtensionMembers(finder, target.staticElement);
+    } else if (targetIdentifierElement is ExtensionElement) {
+      _updateFinderWithExtensionMembers(finder, targetIdentifierElement);
+    } else {
+      var classElement = getTargetClassElement(target);
+      if (classElement != null) {
+        _updateFinderWithClassMembers(finder, classElement);
+      }
     }
+    // if we have close enough element, suggest to use it
+    await _suggest(builder, node, finder._element?.displayName);
   }
 
-  Future<void> _proposeFormalParameter(ChangeBuilder builder) async {
-    var parent = node.parent;
-    if (parent is! SuperFormalParameter) return;
+  Future<void> _proposeField(ChangeBuilder builder) async {
+    final node = this.node;
+    if (node is! FieldFormalParameter) return;
 
+    var exclusions = <String>{};
     var constructorDeclaration =
-        parent.thisOrAncestorOfType<ConstructorDeclaration>();
-    if (constructorDeclaration == null) return;
-
-    var formalParameters = constructorDeclaration.parameters.parameters
-        .whereType<DefaultFormalParameter>();
-
-    var finder =
-        _ClosestElementFinder(parent.identifier.name, (Element e) => true);
-
-    var superInvocation = constructorDeclaration.initializers.lastOrNull;
-
-    if (superInvocation is SuperConstructorInvocation) {
-      var staticElement = superInvocation.staticElement;
-      if (staticElement == null) return;
-
-      var list = _formalParameterSuggestions(staticElement, formalParameters);
-      finder._updateList(list);
-    } else {
-      var targetClassNode = parent.thisOrAncestorOfType<ClassDeclaration>();
-      if (targetClassNode == null) return;
-
-      var targetClassElement = targetClassNode.declaredElement!;
-      var superType = targetClassElement.supertype;
-      if (superType == null) return;
-
-      for (var constructor in superType.constructors) {
-        if (constructor.name.isEmpty) {
-          var list = _formalParameterSuggestions(constructor, formalParameters);
-          finder._updateList(list);
-          break;
+        node.thisOrAncestorOfType<ConstructorDeclaration>();
+    var initializers = constructorDeclaration?.initializers;
+    if (initializers != null) {
+      for (var initializer in initializers) {
+        if (initializer is ConstructorFieldInitializer) {
+          exclusions.add(initializer.fieldName.name);
+        }
+      }
+    }
+    var formalParameterList = node.thisOrAncestorOfType<FormalParameterList>();
+    if (formalParameterList != null) {
+      for (var parameter in formalParameterList.parameters) {
+        var name = parameter.identifier?.name;
+        if (name != null) {
+          exclusions.add(name);
         }
       }
     }
 
-    // If we have a close enough element, suggest to use it.
-    await _suggest(builder, node, finder._element?.name);
+    var type = node.type?.type;
+    await _proposeClassOrMixinMember(builder, node.identifier, null,
+        (Element element) {
+      return element is FieldElement &&
+          !exclusions.contains(element.name) &&
+          !element.isSynthetic &&
+          !element.isExternal &&
+          (type == null || typeSystem.isAssignableTo(type, element.type));
+    });
   }
 
   Future<void> _proposeFunction(ChangeBuilder builder) async {
@@ -245,7 +245,8 @@
       // find getter or setter
       var wantGetter = node.inGetterContext();
       var wantSetter = node.inSetterContext();
-      await _proposeClassOrMixinMember(builder, target, (Element element) {
+      await _proposeClassOrMixinMember(builder, node, target,
+          (Element element) {
         if (element is PropertyAccessorElement) {
           return wantGetter && element.isGetter ||
               wantSetter && element.isSetter;
@@ -259,13 +260,57 @@
   }
 
   Future<void> _proposeMethod(ChangeBuilder builder) async {
+    final node = this.node;
     var parent = node.parent;
-    if (parent is MethodInvocation) {
-      await _proposeClassOrMixinMember(builder, parent.realTarget,
+    if (parent is MethodInvocation && node is SimpleIdentifier) {
+      await _proposeClassOrMixinMember(builder, node, parent.realTarget,
           (Element element) => element is MethodElement && !element.isOperator);
     }
   }
 
+  Future<void> _proposeSuperFormalParameter(ChangeBuilder builder) async {
+    var parent = node.parent;
+    if (parent is! SuperFormalParameter) return;
+
+    var constructorDeclaration =
+        parent.thisOrAncestorOfType<ConstructorDeclaration>();
+    if (constructorDeclaration == null) return;
+
+    var formalParameters = constructorDeclaration.parameters.parameters
+        .whereType<DefaultFormalParameter>();
+
+    var finder =
+        _ClosestElementFinder(parent.identifier.name, (Element e) => true);
+
+    var superInvocation = constructorDeclaration.initializers.lastOrNull;
+
+    if (superInvocation is SuperConstructorInvocation) {
+      var staticElement = superInvocation.staticElement;
+      if (staticElement == null) return;
+
+      var list = _formalParameterSuggestions(staticElement, formalParameters);
+      finder._updateList(list);
+    } else {
+      var targetClassNode = parent.thisOrAncestorOfType<ClassDeclaration>();
+      if (targetClassNode == null) return;
+
+      var targetClassElement = targetClassNode.declaredElement!;
+      var superType = targetClassElement.supertype;
+      if (superType == null) return;
+
+      for (var constructor in superType.constructors) {
+        if (constructor.name.isEmpty) {
+          var list = _formalParameterSuggestions(constructor, formalParameters);
+          finder._updateList(list);
+          break;
+        }
+      }
+    }
+
+    // If we have a close enough element, suggest to use it.
+    await _suggest(builder, node, finder._element?.name);
+  }
+
   Future<void> _suggest(
       ChangeBuilder builder, AstNode node, String? name) async {
     if (name != null) {
@@ -333,8 +378,9 @@
 enum _ReplacementKind {
   annotation,
   classOrMixin,
-  formalParameter,
+  field,
   function,
   getterOrSetter,
-  method
+  method,
+  superFormalParameter,
 }
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 c3cc63d..57a79eb 100644
--- a/pkg/analysis_server/lib/src/services/correction/fix_internal.dart
+++ b/pkg/analysis_server/lib/src/services/correction/fix_internal.dart
@@ -960,6 +960,7 @@
       CreateClass.new,
     ],
     CompileTimeErrorCode.INITIALIZING_FORMAL_FOR_NON_EXISTENT_FIELD: [
+      ChangeTo.field,
       CreateField.new,
     ],
     CompileTimeErrorCode.INSTANCE_ACCESS_TO_STATIC_MEMBER: [
@@ -1070,7 +1071,7 @@
       ReplaceReturnType.new,
     ],
     CompileTimeErrorCode.SUPER_FORMAL_PARAMETER_WITHOUT_ASSOCIATED_NAMED: [
-      ChangeTo.formalParameter,
+      ChangeTo.superFormalParameter,
     ],
     CompileTimeErrorCode.SWITCH_CASE_COMPLETES_NORMALLY: [
       AddSwitchCaseBreak.new,
diff --git a/pkg/analysis_server/test/src/services/correction/fix/change_to_test.dart b/pkg/analysis_server/test/src/services/correction/fix/change_to_test.dart
index c652290..0f5c01d 100644
--- a/pkg/analysis_server/test/src/services/correction/fix/change_to_test.dart
+++ b/pkg/analysis_server/test/src/services/correction/fix/change_to_test.dart
@@ -194,6 +194,78 @@
 ''');
   }
 
+  Future<void> test_fieldFormalParameter_external() async {
+    await resolveTestCode('''
+class C {
+  external int one;
+  C(this.oen);
+}
+''');
+    await assertNoFix();
+  }
+
+  Future<void> test_fieldFormalParameter_getter() async {
+    await resolveTestCode('''
+class C {
+  int get one => 1;
+  C(this.oen);
+}
+''');
+    await assertNoFix();
+  }
+
+  Future<void> test_fieldFormalParameter_initializer() async {
+    await resolveTestCode('''
+class C {
+  int one;
+  C(this.oen): one = 1;
+}
+''');
+    await assertNoFix();
+  }
+
+  Future<void> test_fieldFormalParameter_used() async {
+    await resolveTestCode('''
+class C {
+  int one;
+  C(this.one, this.oen);
+}
+''');
+    await assertNoFix();
+  }
+
+  Future<void> test_fieldFormalParameter_withoutType() async {
+    await resolveTestCode('''
+class C {
+  var one;
+  C(this.oen);
+}
+''');
+    await assertHasFix('''
+class C {
+  var one;
+  C(this.one);
+}
+''');
+  }
+
+  Future<void> test_fieldFormalParameter_withType() async {
+    await resolveTestCode('''
+class C {
+  int one = 1;
+  var done = '';
+  C(String this.on);
+}
+''');
+    await assertHasFix('''
+class C {
+  int one = 1;
+  var done = '';
+  C(String this.done);
+}
+''');
+  }
+
   Future<void> test_function_fromImport() async {
     await resolveTestCode('''
 void f() {