[Code completion] Some more refactoring in SuggestionBuilder.

Change-Id: I86a1a1211385589ce1e288db5487cc8073b01746
Reviewed-on: https://dart-review.googlesource.com/c/sdk/+/384821
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 a2108a2..2870cef 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
@@ -325,7 +325,14 @@
       required this.referencingInterface});
 
   @override
-  String get completion => element.displayName;
+  String get completion {
+    if (element.isEnumConstant) {
+      var constantName = element.name;
+      var enumName = element.enclosingElement3.name;
+      return '$enumName.$constantName';
+    }
+    return element.displayName;
+  }
 }
 
 /// The information about a candidate suggestion based on a formal parameter.
@@ -856,6 +863,12 @@
   final bool appendColon;
   final bool appendComma;
 
+  late String _completion;
+  late int _selectionOffset;
+
+  // Whether _completion, _displayText, and _selectionOffset have been initialized.
+  bool _initialized = false;
+
   RecordLiteralNamedFieldSuggestion.newField({
     required this.field,
     required this.appendComma,
@@ -869,7 +882,36 @@
         appendComma = false;
 
   @override
-  String get completion => field.name;
+  String get completion {
+    _init();
+    return _completion;
+  }
+
+  /// 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;
+    }
+    var name = field.name;
+
+    var completion = name;
+    if (appendColon) {
+      completion += ': ';
+    }
+    _selectionOffset = completion.length;
+
+    if (appendComma) {
+      completion += ',';
+    }
+    _completion = completion;
+    _initialized = true;
+  }
 }
 
 /// The information about a candidate suggestion for Flutter's `setState` method.
@@ -1048,7 +1090,7 @@
       required super.matcherScore});
 
   @override
-  String get completion => '$completionPrefix${element.name}';
+  String get completion => '$completionPrefix${element.displayName}';
 }
 
 /// The information about a candidate suggestion based on a type parameter.
@@ -1136,8 +1178,8 @@
             prefix: suggestion.prefix, relevance: relevance);
       case EnumConstantSuggestion():
         if (suggestion.includeEnumName) {
-          suggestEnumConstant(suggestion.element,
-              prefix: suggestion.prefix, relevance: relevance);
+          suggestEnumConstant(suggestion.element, suggestion.completion,
+              relevance: relevance);
         } else {
           suggestField(suggestion.element,
               inheritanceDistance: 0.0, relevance: relevance);
@@ -1151,7 +1193,8 @@
       case FieldSuggestion():
         var fieldElement = suggestion.element;
         if (fieldElement.isEnumConstant) {
-          suggestEnumConstant(fieldElement, relevance: relevance);
+          suggestEnumConstant(fieldElement, suggestion.completion,
+              relevance: relevance);
         } else {
           var inheritanceDistance =
               suggestion.inheritanceDistance(request.featureComputer);
@@ -1266,7 +1309,9 @@
         );
       case StaticFieldSuggestion():
         suggestStaticField(suggestion.element,
-            prefix: suggestion.prefix, relevance: relevance);
+            prefix: suggestion.prefix,
+            relevance: relevance,
+            completion: suggestion.completion);
       case SuperParameterSuggestion():
         suggestSuperFormalParameter(suggestion.element);
       case TopLevelFunctionSuggestion():
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 c8ab9c5..2b41367 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
@@ -882,8 +882,8 @@
             matcherScore: matcherScore),
         MixinElement() => MixinSuggestion(
             importData: null, element: element, matcherScore: matcherScore),
-        PropertyAccessorElement() => TopLevelPropertyAccessSuggestion(
-            importData: null, element: element, matcherScore: matcherScore),
+        PropertyAccessorElement() =>
+          _createSuggestionFromTopLevelProperty(element, matcherScore),
         TopLevelVariableElement() => TopLevelVariableSuggestion(
             importData: null, element: element, matcherScore: matcherScore),
         TypeAliasElement() => TypeAliasSuggestion(
@@ -1419,6 +1419,26 @@
     return true;
   }
 
+  ImportableSuggestion? _createSuggestionFromTopLevelProperty(
+      PropertyAccessorElement element, double matcherScore,
+      {ImportData? importData}) {
+    if (element.isSynthetic) {
+      if (element.isGetter) {
+        var variable = element.variable2;
+        if (variable is TopLevelVariableElement) {
+          return TopLevelVariableSuggestion(
+              importData: importData,
+              element: variable,
+              matcherScore: matcherScore);
+        }
+      }
+    } else {
+      return TopLevelPropertyAccessSuggestion(
+          importData: importData, element: element, matcherScore: matcherScore);
+    }
+    return null;
+  }
+
   /// Returns `true` if the [identifier] is a wildcard (a single `_`).
   bool _isWildcard(String? identifier) => identifier == '_';
 
@@ -1938,11 +1958,12 @@
       }
       var matcherScore = state.matcher.score(element.displayName);
       if (matcherScore != -1) {
-        var suggestion = TopLevelPropertyAccessSuggestion(
-            importData: importData,
-            element: element,
-            matcherScore: matcherScore);
-        collector.addSuggestion(suggestion);
+        var suggestion = _createSuggestionFromTopLevelProperty(
+            element, matcherScore,
+            importData: importData);
+        if (suggestion != null) {
+          collector.addSuggestion(suggestion);
+        }
       }
     }
   }
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 f923c6f..ff4d934 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
@@ -273,12 +273,8 @@
 
   /// Add a suggestion for an enum [constant]. If the enum can only be
   /// referenced using a prefix, then the [prefix] should be provided.
-  void suggestEnumConstant(FieldElement constant,
+  void suggestEnumConstant(FieldElement constant, String completion,
       {String? prefix, int? relevance}) {
-    var constantName = constant.name;
-    var enumElement = constant.enclosingElement3;
-    var enumName = enumElement.name;
-    var completion = '$enumName.$constantName';
     relevance ??= relevanceComputer.computeTopLevelRelevance(constant,
         elementType: constant.type, isNotImportedLibrary: isNotImportedLibrary);
     _addBuilder(
@@ -615,20 +611,20 @@
   void suggestNamedRecordField(RecordTypeNamedField field,
       {required bool appendColon,
       required bool appendComma,
-      int? replacementLength}) {
-    var name = field.name;
-    var type = field.type.getDisplayString();
+      int? replacementLength,
+      String? completion,
+      int? selectionOffset}) {
+    if (completion == null || selectionOffset == null) {
+      completion = field.name;
+      if (appendColon) {
+        completion += ': ';
+      }
+      selectionOffset = completion.length;
 
-    var completion = name;
-    if (appendColon) {
-      completion += ': ';
+      if (appendComma) {
+        completion += ',';
+      }
     }
-    var selectionOffset = completion.length;
-
-    if (appendComma) {
-      completion += ',';
-    }
-
     _addSuggestion(
       CompletionSuggestion(
         CompletionSuggestionKind.NAMED_ARGUMENT,
@@ -638,8 +634,8 @@
         0,
         false,
         false,
-        parameterName: name,
-        parameterType: type,
+        parameterName: field.name,
+        parameterType: field.type.getDisplayString(),
         replacementLength: replacementLength,
       ),
     );
@@ -806,14 +802,14 @@
   /// If the enclosing element can only be referenced using a prefix, then
   /// the [prefix] should be provided.
   void suggestStaticField(FieldElement element,
-      {String? prefix, int? relevance}) {
+      {String? prefix, int? relevance, String? completion}) {
     assert(element.isStatic);
     var enclosingPrefix = '';
     var enclosingName = _enclosingClassOrExtensionName(element);
     if (enclosingName != null) {
       enclosingPrefix = '$enclosingName.';
     }
-    var completion = enclosingPrefix + element.name;
+    completion ??= enclosingPrefix + element.name;
     if (_couldMatch(completion, prefix)) {
       relevance ??= relevanceComputer.computeTopLevelRelevance(element,
           elementType: element.type,
@@ -876,52 +872,39 @@
         accessor.enclosingElement3 is CompilationUnitElement,
         'Enclosing element of ${accessor.runtimeType} is '
         '${accessor.enclosingElement3.runtimeType}.');
-    if (accessor.isSynthetic) {
-      // Avoid visiting a field twice. All fields induce a getter, but only
-      // non-final fields induce a setter, so we don't add a suggestion for a
-      // synthetic setter.
-      if (accessor.isGetter) {
-        var variable = accessor.variable2;
-        if (variable is TopLevelVariableElement) {
-          suggestTopLevelVariable(variable);
-        }
-      }
-    } else {
-      var completion = _getCompletionString(accessor);
-      if (completion == null) return;
-      if (_couldMatch(completion, prefix)) {
-        var type = _getPropertyAccessorType(accessor);
-        var featureComputer = request.featureComputer;
-        var contextType =
-            featureComputer.contextTypeFeature(request.contextType, type);
-        var elementKind = _computeElementKind(accessor);
-        var hasDeprecated = featureComputer.hasDeprecatedFeature(accessor);
-        var isConstant = _preferConstants
-            ? featureComputer.isConstantFeature(accessor)
-            : 0.0;
-        var startsWithDollar =
-            featureComputer.startsWithDollarFeature(accessor.name);
-        var superMatches = 0.0;
-        relevance ??= relevanceComputer.computeScore(
-          contextType: contextType,
-          elementKind: elementKind,
-          hasDeprecated: hasDeprecated,
-          isConstant: isConstant,
-          isNotImported: request.featureComputer
-              .isNotImportedFeature(isNotImportedLibrary),
-          startsWithDollar: startsWithDollar,
-          superMatches: superMatches,
-        );
-        _addBuilder(
-          _createCompletionSuggestionBuilder(
-            accessor,
-            kind: CompletionSuggestionKind.IDENTIFIER,
-            prefix: prefix,
-            relevance: relevance,
-            isNotImported: isNotImportedLibrary,
-          ),
-        );
-      }
+    var completion = _getCompletionString(accessor);
+    if (completion == null) return;
+    if (_couldMatch(completion, prefix)) {
+      var type = _getPropertyAccessorType(accessor);
+      var featureComputer = request.featureComputer;
+      var contextType =
+          featureComputer.contextTypeFeature(request.contextType, type);
+      var elementKind = _computeElementKind(accessor);
+      var hasDeprecated = featureComputer.hasDeprecatedFeature(accessor);
+      var isConstant =
+          _preferConstants ? featureComputer.isConstantFeature(accessor) : 0.0;
+      var startsWithDollar =
+          featureComputer.startsWithDollarFeature(accessor.name);
+      var superMatches = 0.0;
+      relevance ??= relevanceComputer.computeScore(
+        contextType: contextType,
+        elementKind: elementKind,
+        hasDeprecated: hasDeprecated,
+        isConstant: isConstant,
+        isNotImported:
+            request.featureComputer.isNotImportedFeature(isNotImportedLibrary),
+        startsWithDollar: startsWithDollar,
+        superMatches: superMatches,
+      );
+      _addBuilder(
+        _createCompletionSuggestionBuilder(
+          accessor,
+          kind: CompletionSuggestionKind.IDENTIFIER,
+          prefix: prefix,
+          relevance: relevance,
+          isNotImported: isNotImportedLibrary,
+        ),
+      );
     }
   }