[analysis_server] Dot shorthands: Update ChangeTo fix.

Updates the `ChangeTo` fix to handle miswritten dot shorthands.
We use the calculated context type to determine the closest element that
the user might be attempting to write.

Bug: https://github.com/dart-lang/sdk/issues/60994
Change-Id: If69b6b30fa70a9cd047a3f2946ae1e0883f322e8
Reviewed-on: https://dart-review.googlesource.com/c/sdk/+/442168
Reviewed-by: Brian Wilkerson <brianwilkerson@google.com>
Commit-Queue: Kallen Tu <kallentu@google.com>
diff --git a/pkg/analysis_server/lib/src/services/completion/dart/feature_computer.dart b/pkg/analysis_server/lib/src/services/completion/dart/feature_computer.dart
index 0049b2e..3fc5fda 100644
--- a/pkg/analysis_server/lib/src/services/completion/dart/feature_computer.dart
+++ b/pkg/analysis_server/lib/src/services/completion/dart/feature_computer.dart
@@ -698,6 +698,23 @@
   }
 
   @override
+  DartType? visitDotShorthandConstructorInvocation(
+    DotShorthandConstructorInvocation node,
+  ) {
+    return _visitParent(node);
+  }
+
+  @override
+  DartType? visitDotShorthandInvocation(DotShorthandInvocation node) {
+    return _visitParent(node);
+  }
+
+  @override
+  DartType? visitDotShorthandPropertyAccess(DotShorthandPropertyAccess node) {
+    return _visitParent(node);
+  }
+
+  @override
   DartType? visitExpressionFunctionBody(ExpressionFunctionBody node) {
     if (range.endEnd(node.functionDefinition, node).contains(offset)) {
       var parent = node.parent;
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 2b41c66..83d7215 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
@@ -176,6 +176,28 @@
     await _suggest(builder, node, finder._element?.displayName);
   }
 
+  /// Using the context type of the [dotShorthandNode], we'll propose the
+  /// closest element that we believe the user was attempting to write.
+  Future<void> _proposeClassOrMixinMemberForDotShorthand(
+    ChangeBuilder builder,
+    Expression dotShorthandNode,
+    Token dotShorthandIdentifier,
+    _ElementPredicate predicate,
+  ) async {
+    var contextElement = computeDotShorthandContextTypeElement(
+      dotShorthandNode,
+      unitResult.libraryElement,
+    );
+    if (contextElement == null) return;
+
+    var finder = _ClosestElementFinder(
+      dotShorthandIdentifier.lexeme,
+      predicate,
+    );
+    _updateFinderWithClassMembers(finder, contextElement);
+    await _suggest(builder, node, finder._element?.displayName);
+  }
+
   Future<void> _proposeField(ChangeBuilder builder) async {
     var node = this.node;
     if (node is! FieldFormalParameter) return;
@@ -257,9 +279,18 @@
   Future<void> _proposeGetterOrSetter(ChangeBuilder builder) async {
     var node = this.node;
     if (node is SimpleIdentifier) {
+      var parent = node.parent;
+      if (parent is DotShorthandPropertyAccess) {
+        await _proposeClassOrMixinMemberForDotShorthand(
+          builder,
+          parent,
+          node.token,
+          (element) => element is GetterElement || element is FieldElement,
+        );
+      }
+
       // prepare target
       Expression? target;
-      var parent = node.parent;
       if (parent is PrefixedIdentifier) {
         target = parent.prefix;
       } else if (parent is PropertyAccess) {
@@ -285,13 +316,22 @@
   Future<void> _proposeMethod(ChangeBuilder builder) async {
     var node = this.node;
     var parent = node.parent;
-    if (parent is MethodInvocation && node is SimpleIdentifier) {
-      await _proposeClassOrMixinMember(
-        builder,
-        node.token,
-        parent.realTarget,
-        (element) => element is MethodElement && !element.isOperator,
-      );
+    if (node is SimpleIdentifier) {
+      if (parent is DotShorthandInvocation) {
+        await _proposeClassOrMixinMemberForDotShorthand(
+          builder,
+          parent,
+          node.token,
+          (element) => element is MethodElement,
+        );
+      } else if (parent is MethodInvocation) {
+        await _proposeClassOrMixinMember(
+          builder,
+          node.token,
+          parent.realTarget,
+          (element) => element is MethodElement && !element.isOperator,
+        );
+      }
     }
   }
 
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 72b9ac0..f866a11 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
@@ -517,9 +517,9 @@
 CompileTimeErrorCode.DOT_SHORTHAND_MISSING_CONTEXT:
   status: needsEvaluation
 CompileTimeErrorCode.DOT_SHORTHAND_UNDEFINED_GETTER:
-  status: needsEvaluation
+  status: hasFix
 CompileTimeErrorCode.DOT_SHORTHAND_UNDEFINED_INVOCATION:
-  status: needsEvaluation
+  status: hasFix
 CompileTimeErrorCode.DUPLICATE_CONSTRUCTOR_DEFAULT:
   status: noFix
   notes: |-
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 dfdbe2a..29b6888 100644
--- a/pkg/analysis_server/lib/src/services/correction/fix_internal.dart
+++ b/pkg/analysis_server/lib/src/services/correction/fix_internal.dart
@@ -626,6 +626,10 @@
     RemoveDefaultValue.new,
     RemoveRequired.new,
   ],
+  CompileTimeErrorCode.DOT_SHORTHAND_UNDEFINED_GETTER: [
+    ChangeTo.getterOrSetter,
+  ],
+  CompileTimeErrorCode.DOT_SHORTHAND_UNDEFINED_INVOCATION: [ChangeTo.method],
   CompileTimeErrorCode.EMPTY_MAP_PATTERN: [
     ReplaceEmptyMapPattern.any,
     ReplaceEmptyMapPattern.empty,
diff --git a/pkg/analysis_server/lib/src/services/correction/util.dart b/pkg/analysis_server/lib/src/services/correction/util.dart
index b6bc71e..1597259 100644
--- a/pkg/analysis_server/lib/src/services/correction/util.dart
+++ b/pkg/analysis_server/lib/src/services/correction/util.dart
@@ -5,6 +5,7 @@
 import 'dart:math';
 
 import 'package:_fe_analyzer_shared/src/scanner/token.dart';
+import 'package:analysis_server/src/services/completion/dart/feature_computer.dart';
 import 'package:analyzer/dart/ast/precedence.dart';
 import 'package:analyzer/dart/ast/visitor.dart';
 import 'package:analyzer/dart/element/element.dart';
@@ -34,6 +35,26 @@
   }
 }
 
+/// Computes the [InterfaceElement] of the context type of a given
+/// [dotShorthandNode].
+///
+/// Returns `null` if no context type exists or the context type isn't an
+/// [InterfaceType].
+InterfaceElement? computeDotShorthandContextTypeElement(
+  AstNode dotShorthandNode,
+  LibraryElement libraryElement,
+) {
+  var featureComputer = FeatureComputer(
+    libraryElement.typeSystem,
+    libraryElement.typeProvider,
+  );
+  var contextType = featureComputer.computeContextType(
+    dotShorthandNode,
+    dotShorthandNode.offset,
+  );
+  return contextType is InterfaceType ? contextType.element : null;
+}
+
 /// Return references to the [element] inside the [root] node.
 List<AstNode> findImportPrefixElementReferences(
   AstNode root,
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 1fee748..f37e60a 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
@@ -160,6 +160,23 @@
 ''');
   }
 
+  Future<void> test_enum_constant_dotShorthand() async {
+    await resolveTestCode('''
+enum E { ONE }
+
+E e() {
+  return .OEN;
+}
+''');
+    await assertHasFix('''
+enum E { ONE }
+
+E e() {
+  return .ONE;
+}
+''');
+  }
+
   Future<void> test_enum_getter() async {
     await resolveTestCode('''
 enum E { ONE }
@@ -397,6 +414,25 @@
 ''');
   }
 
+  Future<void> test_getter_qualified_static_dotShorthand() async {
+    await resolveTestCode('''
+class A {
+  static int MY_NAME = 1;
+}
+A f() {
+  return .MY_NAM;
+}
+''');
+    await assertHasFix('''
+class A {
+  static int MY_NAME = 1;
+}
+A f() {
+  return .MY_NAME;
+}
+''');
+  }
+
   Future<void> test_getter_static() async {
     await resolveTestCode('''
 extension E on int {
@@ -580,6 +616,44 @@
 ''');
   }
 
+  Future<void> test_method_static_dotShorthand_class() async {
+    await resolveTestCode('''
+class E {
+  static E myMethod() => E();
+}
+E f() {
+  return .myMehod();
+}
+''');
+    await assertHasFix('''
+class E {
+  static E myMethod() => E();
+}
+E f() {
+  return .myMethod();
+}
+''');
+  }
+
+  Future<void> test_method_static_dotShorthand_extensionType() async {
+    await resolveTestCode('''
+extension type E(int x) {
+  static E myMethod() => E(1);
+}
+E f() {
+  return .myMehod();
+}
+''');
+    await assertHasFix('''
+extension type E(int x) {
+  static E myMethod() => E(1);
+}
+E f() {
+  return .myMethod();
+}
+''');
+  }
+
   Future<void> test_method_unqualified_superClass() async {
     await resolveTestCode('''
 class A {