Skip to content

Commit 602bd27

Browse files
scheglovCommit Queue
authored andcommitted
Issue 61676. Fix sorting by relevance in SuggestionCollector.
Bug: #61676 Change-Id: I713a664f4399bee9baa52a1f69b02483acc9f344 Reviewed-on: https://dart-review.googlesource.com/c/sdk/+/453220 Reviewed-by: Brian Wilkerson <[email protected]> Commit-Queue: Konstantin Shcheglov <[email protected]>
1 parent 0022aa1 commit 602bd27

File tree

7 files changed

+95
-20
lines changed

7 files changed

+95
-20
lines changed

pkg/analysis_server/lib/src/services/completion/dart/completion_manager.dart

Lines changed: 3 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -175,7 +175,9 @@ class DartCompletionManager {
175175
}
176176
// Compute relevance, sort and truncate list.
177177
isTruncated = collector.suggestions.length > maxSuggestions;
178-
collector.finalize(RelevanceComputer(request, listener));
178+
collector.finalize(
179+
RelevanceComputer(request, listener, targetPrefix: request.targetPrefix),
180+
);
179181
return collector;
180182
}
181183

pkg/analysis_server/lib/src/services/completion/dart/relevance_computer.dart

Lines changed: 27 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -40,11 +40,17 @@ class RelevanceComputer {
4040
/// computed. In the latter case, [_hasContainingMemberName] will be `false`.
4141
String? _cachedContainingMemberName;
4242

43+
/// The already typed string at the completion location, maybe empty.
44+
/// See [DartCompletionRequest.targetPrefix].
45+
/// It is used to inflate the relevance of perfect matches to `1.0`.
46+
final String targetPrefix;
47+
late final String targetPrefixLower = targetPrefix.toLowerCase();
48+
4349
/// A textual representation of the location at which completion was
4450
/// requested.
4551
String? completionLocation;
4652

47-
RelevanceComputer(this.request, this.listener)
53+
RelevanceComputer(this.request, this.listener, {required this.targetPrefix})
4854
: featureComputer = request.featureComputer;
4955

5056
/// Return the name of the member containing the completion location, or
@@ -101,6 +107,16 @@ class RelevanceComputer {
101107

102108
/// Compute the relevance for the given [CandidateSuggestion].
103109
int computeRelevance(CandidateSuggestion suggestion) {
110+
// See https://github.com/dart-lang/sdk/issues/61679
111+
// We have two checks to distinguish prefix `str` or `STR` when
112+
// candidates are also `str` or `STR`. We want to prefer the same case.
113+
if (_isExactPrefixMatch(suggestion)) {
114+
return maximumRelevance;
115+
}
116+
if (_isExactPrefixMatchToLower(suggestion)) {
117+
return maximumRelevance - 1;
118+
}
119+
104120
var neverType = request.libraryElement.typeProvider.neverType;
105121
switch (suggestion) {
106122
case TypedSuggestionCompletionMixin():
@@ -746,4 +762,14 @@ class RelevanceComputer {
746762
nullabilitySuffix: NullabilitySuffix.none,
747763
);
748764
}
765+
766+
bool _isExactPrefixMatch(CandidateSuggestion suggestion) {
767+
return targetPrefixLower.isNotEmpty &&
768+
suggestion.completion == targetPrefix;
769+
}
770+
771+
bool _isExactPrefixMatchToLower(CandidateSuggestion suggestion) {
772+
return targetPrefixLower.isNotEmpty &&
773+
suggestion.completion.toLowerCase() == targetPrefixLower;
774+
}
749775
}

pkg/analysis_server/lib/src/services/completion/dart/suggestion_builder.dart

Lines changed: 5 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -106,7 +106,11 @@ class SuggestionBuilder {
106106
/// Initialize a newly created suggestion builder to build suggestions for the
107107
/// given [request].
108108
SuggestionBuilder(this.request, {this.listener, required this.useFilter})
109-
: relevanceComputer = RelevanceComputer(request, listener) {
109+
: relevanceComputer = RelevanceComputer(
110+
request,
111+
listener,
112+
targetPrefix: request.targetPrefix,
113+
) {
110114
targetPrefixLower = request.targetPrefix.toLowerCase();
111115
}
112116

pkg/analysis_server/lib/src/services/completion/dart/suggestion_collector.dart

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -99,7 +99,7 @@ class SuggestionCollector {
9999
// score.
100100
suggestions.sort((first, second) {
101101
if (first.matcherScore == second.matcherScore) {
102-
return first.relevanceScore.compareTo(second.relevanceScore);
102+
return second.relevanceScore.compareTo(first.relevanceScore);
103103
}
104104
return second.matcherScore.compareTo(first.matcherScore);
105105
});

pkg/analysis_server/test/domain_completion_test.dart

Lines changed: 47 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -1257,6 +1257,49 @@ suggestions
12571257
''');
12581258
}
12591259

1260+
Future<void> test_numResults_includesLocalVariable() async {
1261+
await _configureWithWorkspaceRoot();
1262+
1263+
// Create many imported declarations to exceed the maxResults cap.
1264+
newFile(
1265+
'$testPackageLibPath/a.dart',
1266+
[
1267+
for (var i = 1; i <= 50; i++) 'class C$i {}',
1268+
for (var i = 1; i <= 50; i++) 'void f$i() {}',
1269+
for (var i = 1; i <= 50; i++) 'var v$i = $i;',
1270+
].join('\n'),
1271+
);
1272+
1273+
// No prefix, no filtering.
1274+
// Local variables 'foo0*' must be included.
1275+
var response = await _getTestCodeSuggestions('''
1276+
import 'a.dart';
1277+
void f() {
1278+
var foo01 = 0;
1279+
var foo02 = 0;
1280+
^
1281+
}
1282+
''', maxResults: 10);
1283+
1284+
// Only include local variables to keep the result stable.
1285+
printerConfiguration.filter = (suggestion) {
1286+
return suggestion.completion.startsWith('foo0');
1287+
};
1288+
1289+
// Note the variables order: the closer, the higher.
1290+
assertResponseText(response, r'''
1291+
suggestions
1292+
foo02
1293+
kind: localVariable
1294+
isNotImported: null
1295+
libraryUri: null
1296+
foo01
1297+
kind: localVariable
1298+
isNotImported: null
1299+
libraryUri: null
1300+
''');
1301+
}
1302+
12601303
Future<void> test_numResults_topLevelVariables() async {
12611304
await _configureWithWorkspaceRoot();
12621305

@@ -2025,16 +2068,16 @@ void f() {
20252068
replacement
20262069
left: 4
20272070
suggestions
2028-
foo01
2071+
foo02
20292072
kind: topLevelVariable
20302073
isNotImported: null
20312074
libraryUri: null
2032-
relevance: 510
2033-
foo02
2075+
relevance: 558
2076+
foo01
20342077
kind: topLevelVariable
20352078
isNotImported: null
20362079
libraryUri: null
2037-
relevance: 558
2080+
relevance: 510
20382081
''');
20392082
}
20402083

pkg/analysis_server/test/lsp/completion_dart_test.dart

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -1378,7 +1378,7 @@ void g() {
13781378
await verifyCompletions(
13791379
mainFileUri,
13801380
content,
1381-
expectCompletions: ['(a, b) {}', '(a, b) =>'],
1381+
expectCompletions: ['(a, b) =>', '(a, b) {}'],
13821382
applyEditsFor: '(a, b) =>',
13831383
expectedContent: expectedContent,
13841384
);
@@ -1406,7 +1406,7 @@ void g() {
14061406
content,
14071407
// Display text does not contain 'required' because it makes the
14081408
// completion much longer, we just include it in the completion text.
1409-
expectCompletions: ['({a, b}) {}', '({a, b}) =>'],
1409+
expectCompletions: ['({a, b}) =>', '({a, b}) {}'],
14101410
applyEditsFor: '({a, b}) =>',
14111411
expectedContent: expectedContent,
14121412
);

pkg/analysis_server/test/services/completion/dart/completion_test.dart

Lines changed: 10 additions & 10 deletions
Original file line numberDiff line numberDiff line change
@@ -2558,12 +2558,12 @@ void f() {
25582558
replacement
25592559
left: 4
25602560
suggestions
2561+
true
2562+
kind: keyword
25612563
falsetrue
25622564
kind: topLevelVariable
25632565
truefalse
25642566
kind: topLevelVariable
2565-
true
2566-
kind: keyword
25672567
''');
25682568
}
25692569

@@ -4726,12 +4726,12 @@ class D {f(){} g(){f^(f);}}
47264726
replacement
47274727
left: 1
47284728
suggestions
4729+
f
4730+
kind: methodInvocation
47294731
if
47304732
kind: keyword
47314733
final
47324734
kind: keyword
4733-
f
4734-
kind: methodInvocation
47354735
for
47364736
kind: keyword
47374737
false
@@ -5176,6 +5176,8 @@ class Bar<T extends Foo> {const Bar(T k);T^ m(T a, T b){}final T f = null;}
51765176
replacement
51775177
left: 1
51785178
suggestions
5179+
T
5180+
kind: typeParameter
51795181
static
51805182
kind: keyword
51815183
const
@@ -5184,8 +5186,6 @@ suggestions
51845186
kind: keyword
51855187
factory
51865188
kind: keyword
5187-
T
5188-
kind: typeParameter
51895189
covariant
51905190
kind: keyword
51915191
get
@@ -8495,10 +8495,10 @@ void f(p) {
84958495
replacement
84968496
left: 3
84978497
suggestions
8498-
str
8499-
kind: localVariable
85008498
STR
85018499
kind: topLevelVariable
8500+
str
8501+
kind: localVariable
85028502
''');
85038503
}
85048504

@@ -8517,12 +8517,12 @@ void f(p) {
85178517
replacement
85188518
left: 3
85198519
suggestions
8520+
STR
8521+
kind: topLevelVariable
85208522
str
85218523
kind: localVariable
85228524
String
85238525
kind: class
8524-
STR
8525-
kind: topLevelVariable
85268526
String.fromCharCode
85278527
kind: constructorInvocation
85288528
String.fromCharCodes

0 commit comments

Comments
 (0)