[Code completion] Add a SetStateMethodSuggestion for completions of the setState method.

Change-Id: I86a0955d45a7d62b3063c9683c883c47fe5a88dd
Reviewed-on: https://dart-review.googlesource.com/c/sdk/+/380964
Reviewed-by: Brian Wilkerson <brianwilkerson@google.com>
Commit-Queue: Keerti Parthasarathy <keertip@google.com>
diff --git a/pkg/analysis_server/lib/src/services/completion/dart/candidate_suggestion.dart b/pkg/analysis_server/lib/src/services/completion/dart/candidate_suggestion.dart
index 73e7285..d2813ed 100644
--- a/pkg/analysis_server/lib/src/services/completion/dart/candidate_suggestion.dart
+++ b/pkg/analysis_server/lib/src/services/completion/dart/candidate_suggestion.dart
@@ -763,6 +763,75 @@
   String get completion => field.name;
 }
 
+/// The information about a candidate suggestion for Flutter's `setState` method.
+final class SetStateMethodSuggestion extends ExecutableSuggestion
+    with MemberSuggestion {
+  @override
+  final MethodElement element;
+
+  late String _completion;
+
+  /// The element defined by the declaration in which the suggestion is to be
+  /// applied, or `null` if the completion is in a static context.
+  @override
+  final InterfaceElement? referencingInterface;
+
+  /// The identation to be used for a multi-line completion.
+  final String indent;
+
+  // Indicates whether _completion, _displayText, and _selectionOffset have been initialized.
+  bool _initialized = false;
+
+  late int _selectionOffset;
+
+  late String _displayText;
+
+  /// Initialize a newly created candidate suggestion to suggest the [element].
+  SetStateMethodSuggestion(
+      {required this.element,
+      required super.importData,
+      required this.referencingInterface,
+      required super.matcherScore,
+      required this.indent,
+      super.kind = CompletionSuggestionKind.INVOCATION});
+
+  @override
+  String get completion {
+    _init();
+    return _completion;
+  }
+
+  /// Text to be displayed in a completion pop-up.
+  String get displayText {
+    _init();
+    return _displayText;
+  }
+
+  /// The offset, from the beginning of the inserted text, where the cursor
+  /// should be positioned.
+  int get selectionOffset {
+    _init();
+    return _selectionOffset;
+  }
+
+  void _init() {
+    if (_initialized) {
+      return;
+    }
+    // Build the completion and the selection offset.
+    var buffer = StringBuffer();
+    buffer.writeln('setState(() {');
+    buffer.write('$indent  ');
+    _selectionOffset = buffer.length;
+    buffer.writeln();
+    buffer.write('$indent});');
+    _completion = buffer.toString();
+    _displayText = 'setState(() {});';
+
+    _initialized = true;
+  }
+}
+
 /// The information about a candidate suggestion based on a static field in a
 /// location where the name of the field must be qualified by the name of the
 /// enclosing element.
@@ -1066,6 +1135,18 @@
           appendColon: suggestion.appendColon,
           appendComma: suggestion.appendComma,
         );
+      case SetStateMethodSuggestion():
+        var inheritanceDistance =
+            suggestion.inheritanceDistance(request.featureComputer);
+        suggestSetStateMethod(
+          suggestion.element,
+          kind: suggestion.kind,
+          completion: suggestion.completion,
+          displayText: suggestion.displayText,
+          selectionOffset: suggestion.selectionOffset,
+          inheritanceDistance: inheritanceDistance,
+          relevance: relevance,
+        );
       case StaticFieldSuggestion():
         suggestStaticField(suggestion.element,
             prefix: suggestion.prefix, relevance: relevance);
diff --git a/pkg/analysis_server/lib/src/services/completion/dart/declaration_helper.dart b/pkg/analysis_server/lib/src/services/completion/dart/declaration_helper.dart
index 7394dce..517c209 100644
--- a/pkg/analysis_server/lib/src/services/completion/dart/declaration_helper.dart
+++ b/pkg/analysis_server/lib/src/services/completion/dart/declaration_helper.dart
@@ -10,6 +10,7 @@
 import 'package:analysis_server/src/services/completion/dart/not_imported_completion_pass.dart';
 import 'package:analysis_server/src/services/completion/dart/suggestion_collector.dart';
 import 'package:analysis_server/src/services/completion/dart/visibility_tracker.dart';
+import 'package:analysis_server/src/utilities/extensions/flutter.dart';
 import 'package:analyzer/dart/ast/ast.dart';
 import 'package:analyzer/dart/element/element.dart';
 import 'package:analyzer/dart/element/type.dart';
@@ -1662,6 +1663,19 @@
       }
       var matcherScore = state.matcher.score(method.displayName);
       if (matcherScore != -1) {
+        var enclosingElement = method.enclosingElement;
+        if (method.name == 'setState' &&
+            enclosingElement is ClassElement &&
+            enclosingElement.isExactState) {
+          var suggestion = SetStateMethodSuggestion(
+              element: method,
+              importData: importData,
+              referencingInterface: referencingInterface,
+              matcherScore: matcherScore,
+              indent: state.indent);
+          collector.addSuggestion(suggestion);
+          return;
+        }
         var suggestion = MethodSuggestion(
             kind: _executableSuggestionKind,
             element: method,
diff --git a/pkg/analysis_server/lib/src/services/completion/dart/relevance_computer.dart b/pkg/analysis_server/lib/src/services/completion/dart/relevance_computer.dart
index 74185b9..0f74ba0 100644
--- a/pkg/analysis_server/lib/src/services/completion/dart/relevance_computer.dart
+++ b/pkg/analysis_server/lib/src/services/completion/dart/relevance_computer.dart
@@ -191,6 +191,12 @@
         );
       case RecordLiteralNamedFieldSuggestion():
         return Relevance.requiredNamedArgument;
+      case SetStateMethodSuggestion():
+        return _computeMethodRelevance(
+          suggestion.element,
+          suggestion.inheritanceDistance(featureComputer),
+          suggestion.isNotImported,
+        );
       case StaticFieldSuggestion():
         return _computeStaticFieldRelevance(
           suggestion.element,
diff --git a/pkg/analysis_server/lib/src/services/completion/dart/suggestion_builder.dart b/pkg/analysis_server/lib/src/services/completion/dart/suggestion_builder.dart
index 0371d94..53b3508 100644
--- a/pkg/analysis_server/lib/src/services/completion/dart/suggestion_builder.dart
+++ b/pkg/analysis_server/lib/src/services/completion/dart/suggestion_builder.dart
@@ -744,41 +744,6 @@
       inheritanceDistance: inheritanceDistance,
     );
 
-    var enclosingElement = method.enclosingElement;
-    if (method.name == 'setState' &&
-        enclosingElement is ClassElement &&
-        enclosingElement.isExactState) {
-      // TODO(brianwilkerson): Make this more efficient by creating the correct
-      //  suggestion in the first place.
-      // Find the line indentation.
-      var indent = getRequestLineIndent(request);
-
-      // Build the completion and the selection offset.
-      var buffer = StringBuffer();
-      buffer.writeln('setState(() {');
-      buffer.write('$indent  ');
-      var selectionOffset = buffer.length;
-      buffer.writeln();
-      buffer.write('$indent});');
-
-      _addSuggestion(
-        DartCompletionSuggestion(
-          kind,
-          relevance,
-          buffer.toString(),
-          selectionOffset,
-          0,
-          false,
-          false,
-          // Let the user know that we are going to insert a complete statement.
-          displayText: 'setState(() {});',
-          elementLocation: method.location,
-        ),
-        textToMatchOverride: 'setState',
-      );
-      return;
-    }
-
     _addBuilder(
       _createCompletionSuggestionBuilder(
         method,
@@ -1038,6 +1003,31 @@
     );
   }
 
+  /// Add a suggestion for the Flutter's `setState` method.
+  void suggestSetStateMethod(MethodElement method,
+      {required CompletionSuggestionKind kind,
+      required String completion,
+      required String displayText,
+      required int selectionOffset,
+      required double inheritanceDistance,
+      required int relevance}) {
+    _addSuggestion(
+      DartCompletionSuggestion(
+        kind,
+        relevance,
+        completion,
+        selectionOffset,
+        0,
+        false,
+        false,
+        // Let the user know that we are going to insert a complete statement.
+        displayText: displayText,
+        elementLocation: method.location,
+      ),
+      textToMatchOverride: 'setState',
+    );
+  }
+
   /// Add a suggestion for a static field declared within a class or extension.
   /// If the field is synthetic, add the corresponding getter instead.
   ///