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,