Include 'includedSuggestionRelevanceTags' into completion response.

R=brianwilkerson@google.com

Change-Id: Ib2d26a2f4679c19b8fa889b9fabf80f505eaef28
Reviewed-on: https://dart-review.googlesource.com/c/92625
Reviewed-by: Brian Wilkerson <brianwilkerson@google.com>
Commit-Queue: Konstantin Shcheglov <scheglov@google.com>
diff --git a/pkg/analysis_server/lib/src/domain_completion.dart b/pkg/analysis_server/lib/src/domain_completion.dart
index 4cfe840..1972a49 100644
--- a/pkg/analysis_server/lib/src/domain_completion.dart
+++ b/pkg/analysis_server/lib/src/domain_completion.dart
@@ -77,6 +77,7 @@
     CompletionRequestImpl request,
     CompletionGetSuggestionsParams params,
     Set<ElementKind> includedSuggestionKinds,
+    Set<String> includedSuggestionRelevanceTags,
   ) async {
     // TODO(brianwilkerson) Determine whether this await is necessary.
     await null;
@@ -103,6 +104,7 @@
 
       var manager = new DartCompletionManager(
         includedSuggestionKinds: includedSuggestionKinds,
+        includedSuggestionRelevanceTags: includedSuggestionRelevanceTags,
       );
 
       String contributorTag = 'computeSuggestions - ${manager.runtimeType}';
@@ -273,8 +275,10 @@
     // If the client opted into using available suggestion sets,
     // create the kinds set, so signal the completion manager about opt-in.
     Set<ElementKind> includedSuggestionKinds;
+    Set<String> includedSuggestionRelevanceTags;
     if (_subscriptions.contains(CompletionService.AVAILABLE_SUGGESTION_SETS)) {
       includedSuggestionKinds = Set<ElementKind>();
+      includedSuggestionRelevanceTags = Set<String>();
     }
 
     // Compute suggestions in the background
@@ -282,6 +286,7 @@
       completionRequest,
       params,
       includedSuggestionKinds,
+      includedSuggestionRelevanceTags,
     ).then((CompletionResult result) {
       List<IncludedSuggestionSet> includedSuggestionSets;
       if (includedSuggestionKinds != null && resolvedUnit != null) {
@@ -302,6 +307,9 @@
         result.suggestions,
         includedSuggestionSets,
         includedSuggestionKinds?.toList(),
+        computeIncludedSuggestionRelevanceTags(
+          includedSuggestionRelevanceTags,
+        ),
       );
       performance.logElapseTime(SEND_NOTIFICATION_TAG);
 
@@ -339,6 +347,7 @@
     Iterable<CompletionSuggestion> results,
     List<IncludedSuggestionSet> includedSuggestionSets,
     List<ElementKind> includedSuggestionKinds,
+    List<IncludedSuggestionRelevanceTag> includedSuggestionRelevanceTags,
   ) {
     server.sendNotification(
       new CompletionResultsParams(
@@ -349,6 +358,7 @@
         true,
         includedSuggestionSets: includedSuggestionSets,
         includedSuggestionKinds: includedSuggestionKinds,
+        includedSuggestionRelevanceTags: includedSuggestionRelevanceTags,
       ).toNotification(),
     );
   }
diff --git a/pkg/analysis_server/lib/src/domains/completion/available_suggestions.dart b/pkg/analysis_server/lib/src/domains/completion/available_suggestions.dart
index adeea7f..c220ea6 100644
--- a/pkg/analysis_server/lib/src/domains/completion/available_suggestions.dart
+++ b/pkg/analysis_server/lib/src/domains/completion/available_suggestions.dart
@@ -62,6 +62,17 @@
   return includedSetList;
 }
 
+List<protocol.IncludedSuggestionRelevanceTag>
+    computeIncludedSuggestionRelevanceTags(
+  Set<String> includedSuggestionRelevanceTags,
+) {
+  if (includedSuggestionRelevanceTags == null) return null;
+
+  return includedSuggestionRelevanceTags.map((tag) {
+    return protocol.IncludedSuggestionRelevanceTag(tag, 10);
+  }).toList();
+}
+
 /// Convert the [LibraryChange] into the corresponding protocol notification.
 protocol.Notification createCompletionAvailableSuggestionsNotification(
   LibraryChange change,
@@ -90,6 +101,7 @@
     parameterNames: declaration.parameterNames,
     parameterTypes: declaration.parameterTypes,
     requiredParameterCount: declaration.requiredParameterCount,
+    relevanceTags: declaration.relevanceTags,
   );
 }
 
diff --git a/pkg/analysis_server/lib/src/services/completion/dart/completion_manager.dart b/pkg/analysis_server/lib/src/services/completion/dart/completion_manager.dart
index 4da64ab..3fad833 100644
--- a/pkg/analysis_server/lib/src/services/completion/dart/completion_manager.dart
+++ b/pkg/analysis_server/lib/src/services/completion/dart/completion_manager.dart
@@ -4,6 +4,7 @@
 
 import 'dart:async';
 
+import 'package:analysis_server/src/protocol_server.dart';
 import 'package:analysis_server/src/provisional/completion/completion_core.dart'
     show AbortCompletion, CompletionContributor, CompletionRequest;
 import 'package:analysis_server/src/provisional/completion/dart/completion_dart.dart';
@@ -32,6 +33,7 @@
 import 'package:analyzer/dart/analysis/results.dart';
 import 'package:analyzer/dart/analysis/session.dart';
 import 'package:analyzer/dart/ast/ast.dart';
+import 'package:analyzer/dart/ast/token.dart';
 import 'package:analyzer/dart/element/element.dart';
 import 'package:analyzer/dart/element/type.dart';
 import 'package:analyzer/file_system/file_system.dart';
@@ -60,7 +62,15 @@
   /// sets.
   final Set<protocol.ElementKind> includedSuggestionKinds;
 
-  DartCompletionManager({this.includedSuggestionKinds});
+  /// If [includedSuggestionKinds] is not null, must be also not `null`, and
+  /// will be filled with tags for suggestions that should be given higher
+  /// relevance than other included suggestions.
+  final Set<String> includedSuggestionRelevanceTags;
+
+  DartCompletionManager({
+    this.includedSuggestionKinds,
+    this.includedSuggestionRelevanceTags,
+  });
 
   @override
   Future<List<CompletionSuggestion>> computeSuggestions(
@@ -113,6 +123,7 @@
 
     if (includedSuggestionKinds != null) {
       _addIncludedSuggestionKinds(dartRequest);
+      _addIncludedSuggestionRelevanceTags(dartRequest);
     } else {
       contributors.add(new ImportedReferenceContributor());
     }
@@ -166,6 +177,44 @@
     return suggestions;
   }
 
+  void _addIncludedSuggestionRelevanceTags(DartCompletionRequestImpl request) {
+    var target = request.target;
+
+    void addTypeTag(DartType type) {
+      if (type is InterfaceType) {
+        var element = type.element;
+        includedSuggestionRelevanceTags.add(
+          '${element.librarySource.uri}::${element.name}',
+        );
+      }
+    }
+
+    var parameter = target.parameterElement;
+    if (parameter != null) {
+      addTypeTag(parameter.type);
+    }
+
+    var containingNode = target.containingNode;
+
+    if (containingNode is AssignmentExpression &&
+        containingNode.operator.type == TokenType.EQ &&
+        target.offset >= containingNode.operator.end) {
+      addTypeTag(containingNode.leftHandSide.staticType);
+    }
+
+    if (containingNode is ListLiteral &&
+        target.offset >= containingNode.leftBracket.end &&
+        target.offset <= containingNode.rightBracket.offset) {
+      var type = containingNode.staticType;
+      if (type is InterfaceType) {
+        var typeArguments = type.typeArguments;
+        if (typeArguments.isNotEmpty) {
+          addTypeTag(typeArguments[0]);
+        }
+      }
+    }
+  }
+
   void _addIncludedSuggestionKinds(DartCompletionRequestImpl request) {
     var opType = request.opType;
 
diff --git a/pkg/analysis_server/test/src/domains/completion/available_suggestion_sets_test.dart b/pkg/analysis_server/test/src/domains/completion/available_suggestion_sets_test.dart
index a13ebcc..0703883 100644
--- a/pkg/analysis_server/test/src/domains/completion/available_suggestion_sets_test.dart
+++ b/pkg/analysis_server/test/src/domains/completion/available_suggestion_sets_test.dart
@@ -2,6 +2,7 @@
 // for details. All rights reserved. Use of this source code is governed by a
 // BSD-style license that can be found in the LICENSE file.
 
+import 'package:analysis_server/src/protocol_server.dart';
 import 'package:test/test.dart';
 import 'package:test_reflective_loader/test_reflective_loader.dart';
 
@@ -45,4 +46,172 @@
     deleteFile(path);
     waitForSetWithUriRemoved(uriStr);
   }
+
+  test_suggestion_class() async {
+    var path = '/home/test/lib/a.dart';
+    var uriStr = 'package:test/a.dart';
+
+    newFile(path, content: r'''
+class A {}
+''');
+
+    var set = await waitForSetWithUri(uriStr);
+    assertJsonText(_getSuggestion(set, 'A'), r'''
+{
+  "label": "A",
+  "element": {
+    "kind": "CLASS",
+    "name": "A",
+    "location": {
+      "file": "/home/test/lib/a.dart",
+      "offset": 6,
+      "length": 0,
+      "startLine": 1,
+      "startColumn": 7
+    },
+    "flags": 0
+  },
+  "relevanceTags": [
+    "package:test/a.dart::A"
+  ]
+}
+''');
+  }
+
+  test_suggestion_enum() async {
+    var path = '/home/test/lib/a.dart';
+    var uriStr = 'package:test/a.dart';
+
+    newFile(path, content: r'''
+enum MyEnum {
+  aaa,
+  bbb,
+}
+''');
+
+    var set = await waitForSetWithUri(uriStr);
+    assertJsonText(_getSuggestion(set, 'MyEnum'), r'''
+{
+  "label": "MyEnum",
+  "element": {
+    "kind": "ENUM",
+    "name": "MyEnum",
+    "location": {
+      "file": "/home/test/lib/a.dart",
+      "offset": 5,
+      "length": 0,
+      "startLine": 1,
+      "startColumn": 6
+    },
+    "flags": 0
+  },
+  "relevanceTags": [
+    "package:test/a.dart::MyEnum"
+  ]
+}
+''');
+  }
+
+  test_suggestion_topLevelVariable() async {
+    var path = '/home/test/lib/a.dart';
+    var uriStr = 'package:test/a.dart';
+
+    newFile(path, content: r'''
+var boolV = false;
+var intV = 0;
+var doubleV = 0.1;
+var stringV = 'hi';
+''');
+
+    var set = await waitForSetWithUri(uriStr);
+    assertJsonText(_getSuggestion(set, 'boolV'), r'''
+{
+  "label": "boolV",
+  "element": {
+    "kind": "TOP_LEVEL_VARIABLE",
+    "name": "boolV",
+    "location": {
+      "file": "/home/test/lib/a.dart",
+      "offset": 4,
+      "length": 0,
+      "startLine": 1,
+      "startColumn": 5
+    },
+    "flags": 0,
+    "returnType": ""
+  },
+  "relevanceTags": [
+    "dart:core::bool"
+  ]
+}
+''');
+    assertJsonText(_getSuggestion(set, 'intV'), r'''
+{
+  "label": "intV",
+  "element": {
+    "kind": "TOP_LEVEL_VARIABLE",
+    "name": "intV",
+    "location": {
+      "file": "/home/test/lib/a.dart",
+      "offset": 23,
+      "length": 0,
+      "startLine": 2,
+      "startColumn": 5
+    },
+    "flags": 0,
+    "returnType": ""
+  },
+  "relevanceTags": [
+    "dart:core::int"
+  ]
+}
+''');
+    assertJsonText(_getSuggestion(set, 'doubleV'), r'''
+{
+  "label": "doubleV",
+  "element": {
+    "kind": "TOP_LEVEL_VARIABLE",
+    "name": "doubleV",
+    "location": {
+      "file": "/home/test/lib/a.dart",
+      "offset": 37,
+      "length": 0,
+      "startLine": 3,
+      "startColumn": 5
+    },
+    "flags": 0,
+    "returnType": ""
+  },
+  "relevanceTags": [
+    "dart:core::double"
+  ]
+}
+''');
+    assertJsonText(_getSuggestion(set, 'stringV'), r'''
+{
+  "label": "stringV",
+  "element": {
+    "kind": "TOP_LEVEL_VARIABLE",
+    "name": "stringV",
+    "location": {
+      "file": "/home/test/lib/a.dart",
+      "offset": 56,
+      "length": 0,
+      "startLine": 4,
+      "startColumn": 5
+    },
+    "flags": 0,
+    "returnType": ""
+  },
+  "relevanceTags": [
+    "dart:core::String"
+  ]
+}
+''');
+  }
+
+  static AvailableSuggestion _getSuggestion(
+      AvailableSuggestionSet set, String label) {
+    return set.items.singleWhere((s) => s.label == label);
+  }
 }
diff --git a/pkg/analysis_server/test/src/domains/completion/available_suggestions_base.dart b/pkg/analysis_server/test/src/domains/completion/available_suggestions_base.dart
index 5ff9a63..01a6817 100644
--- a/pkg/analysis_server/test/src/domains/completion/available_suggestions_base.dart
+++ b/pkg/analysis_server/test/src/domains/completion/available_suggestions_base.dart
@@ -3,6 +3,7 @@
 // BSD-style license that can be found in the LICENSE file.
 
 import 'dart:async';
+import 'dart:convert';
 
 import 'package:analysis_server/protocol/protocol.dart';
 import 'package:analysis_server/protocol/protocol_constants.dart';
@@ -21,6 +22,17 @@
   final Map<String, AvailableSuggestionSet> uriToSetMap = {};
   final Map<String, CompletionResultsParams> idToSuggestions = {};
 
+  void assertJsonText(Object object, String expected) {
+    expected = expected.trimRight();
+    var actual = JsonEncoder.withIndent('  ').convert(object);
+    if (actual != expected) {
+      print('-----');
+      print(actual);
+      print('-----');
+    }
+    expect(actual, expected);
+  }
+
   @override
   void processNotification(Notification notification) {
     super.processNotification(notification);
diff --git a/pkg/analysis_server/test/src/domains/completion/get_suggestions_available_test.dart b/pkg/analysis_server/test/src/domains/completion/get_suggestions_available_test.dart
index b8356ee..b341b6a 100644
--- a/pkg/analysis_server/test/src/domains/completion/get_suggestions_available_test.dart
+++ b/pkg/analysis_server/test/src/domains/completion/get_suggestions_available_test.dart
@@ -41,6 +41,99 @@
     expect(serverErrors, isEmpty);
   }
 
+  test_relevanceTags_argumentList_named() async {
+    addTestFile(r'''
+void foo({int a, String b}) {}
+
+main() {
+  foo(b: ); // ref
+}
+''');
+
+    var results = await _getSuggestions(
+      testFile,
+      testCode.indexOf('); // ref'),
+    );
+
+    assertJsonText(results.includedSuggestionRelevanceTags, r'''
+[
+  {
+    "tag": "dart:core::String",
+    "relevanceBoost": 10
+  }
+]
+''');
+  }
+
+  test_relevanceTags_argumentList_positional() async {
+    addTestFile(r'''
+void foo(double a) {}
+
+main() {
+  foo(); // ref
+}
+''');
+
+    var results = await _getSuggestions(
+      testFile,
+      testCode.indexOf('); // ref'),
+    );
+
+    assertJsonText(results.includedSuggestionRelevanceTags, r'''
+[
+  {
+    "tag": "dart:core::double",
+    "relevanceBoost": 10
+  }
+]
+''');
+  }
+
+  test_relevanceTags_assignment() async {
+    addTestFile(r'''
+main() {
+  int v;
+  v = // ref;
+}
+''');
+
+    var results = await _getSuggestions(
+      testFile,
+      testCode.indexOf(' // ref'),
+    );
+
+    assertJsonText(results.includedSuggestionRelevanceTags, r'''
+[
+  {
+    "tag": "dart:core::int",
+    "relevanceBoost": 10
+  }
+]
+''');
+  }
+
+  test_relevanceTags_listLiteral() async {
+    addTestFile(r'''
+main() {
+  var v = [0, ]; // ref
+}
+''');
+
+    var results = await _getSuggestions(
+      testFile,
+      testCode.indexOf(']; // ref'),
+    );
+
+    assertJsonText(results.includedSuggestionRelevanceTags, r'''
+[
+  {
+    "tag": "dart:core::int",
+    "relevanceBoost": 10
+  }
+]
+''');
+  }
+
   Future<CompletionResultsParams> _getSuggestions(
     String path,
     int offset,