[analysis_server] Fix missing selection in override completions

When completing an override, the `throw UnimplementedError()` code should be selected automatically. I think this regressed recently because it broke Dart-Code's integration tests (but apparently we didn't have a test for it here). This fixes the issue and adds a test.

Fixes https://github.com/Dart-Code/Dart-Code/issues/5652

Change-Id: I9576f2494249c70968566d13fdf95854f0285944
Reviewed-on: https://dart-review.googlesource.com/c/sdk/+/448180
Commit-Queue: Brian Wilkerson <brianwilkerson@google.com>
Reviewed-by: Brian Wilkerson <brianwilkerson@google.com>
Reviewed-by: Samuel Rawlins <srawlins@google.com>
This commit is contained in:
Danny Tuppeny
2025-09-15 11:38:36 -07:00
committed by Commit Queue
parent a9e1a9211a
commit 6ac7afdeed
2 changed files with 55 additions and 65 deletions
@@ -324,19 +324,20 @@ lsp.CompletionItem? toLspCompletionItem(
}
var completion = suggestion.completion;
var selectionOffset = (suggestion is KeywordSuggestion)
? suggestion.selectionOffset
: completion.length;
var selectionLength = 0;
if (suggestion is SuggestionData) {
selectionOffset = (suggestion as SuggestionData).selectionOffset;
} else if (suggestion case TypedSuggestion(
data: TypeImportData(:var selectionOffset?, :var selectionLength?),
)) {
selectionOffset = selectionOffset;
selectionLength = selectionLength;
}
var (selectionOffset, selectionLength) = switch (suggestion) {
KeywordSuggestion(:var selectionOffset) => (selectionOffset, 0),
SuggestionData(:var selectionOffset) => (selectionOffset, 0),
TypedSuggestion(
data: TypeImportData(:var selectionOffset?, :var selectionLength?),
) =>
(selectionOffset, selectionLength),
OverrideSuggestion(
data: TypeImportData(:var selectionOffset?, :var selectionLength?),
) =>
(selectionOffset, selectionLength),
_ => (completion.length, 0),
};
var insertTextInfo = buildInsertText(
supportsSnippets: supportsSnippets,
@@ -2420,12 +2420,7 @@ void main() {
var completionLabel = 'MyEnum.value1';
await _checkCompletionEdits(
mainFileUri,
content,
completionLabel,
expectedContent,
);
await _checkCompletionEdits(mainFileUri, completionLabel, expectedContent);
}
Future<void> test_importedSymbol_libraryImported_showingEnum() async {
@@ -2454,12 +2449,7 @@ void main() {
var completionLabel = 'MyEnum.value1';
await _checkCompletionEdits(
mainFileUri,
content,
completionLabel,
expectedContent,
);
await _checkCompletionEdits(mainFileUri, completionLabel, expectedContent);
}
Future<void> test_insertReplaceRanges() async {
@@ -3267,6 +3257,35 @@ class B extends A {
expect(labels, contains('override unary-() { … }'));
}
Future<void> test_override() async {
setCompletionItemSnippetSupport();
content = '''
abstract class Base {
String get name;
}
class Derived extends Base {
^
}
''';
var expectedContent = r'''
abstract class Base {
String get name;
}
class Derived extends Base {
@override
// TODO: implement name
String get name => ${0:throw UnimplementedError()};
}
''';
var completionLabel = 'override name => …';
await _checkCompletionEdits(mainFileUri, completionLabel, expectedContent);
}
Future<void> test_plainText() async {
content = '''
class MyClass {
@@ -3894,12 +3913,7 @@ void f() {
var completionLabel = 'MyClass';
await _checkCompletionEdits(
mainFileUri,
content,
completionLabel,
expectedContent,
);
await _checkCompletionEdits(mainFileUri, completionLabel, expectedContent);
}
Future<void>
@@ -3925,12 +3939,7 @@ void f() {
''';
var completionLabel = 'myExtensionMethod()';
await _checkCompletionEdits(
mainFileUri,
content,
completionLabel,
expectedContent,
);
await _checkCompletionEdits(mainFileUri, completionLabel, expectedContent);
}
Future<void>
@@ -4199,12 +4208,7 @@ void f() {
var completionLabel = 'MyClass1';
await _checkCompletionEdits(
mainFileUri,
content,
completionLabel,
expectedContent,
);
await _checkCompletionEdits(mainFileUri, completionLabel, expectedContent);
}
Future<void> test_unimportedSymbols_libraryImported_hidingOne() async {
@@ -4229,12 +4233,7 @@ void f() {
var completionLabel = 'MyClass1';
await _checkCompletionEdits(
mainFileUri,
content,
completionLabel,
expectedContent,
);
await _checkCompletionEdits(mainFileUri, completionLabel, expectedContent);
}
Future<void> test_unimportedSymbols_libraryImported_showingOther() async {
@@ -4259,12 +4258,7 @@ void f() {
var completionLabel = 'MyClass1';
await _checkCompletionEdits(
mainFileUri,
content,
completionLabel,
expectedContent,
);
await _checkCompletionEdits(mainFileUri, completionLabel, expectedContent);
}
// Code completion doesn't include prefixes for auto-imports so when an
@@ -4293,12 +4287,7 @@ void f() {
var completionLabel = 'MyClass1';
await _checkCompletionEdits(
mainFileUri,
content,
completionLabel,
expectedContent,
);
await _checkCompletionEdits(mainFileUri, completionLabel, expectedContent);
}
/// This test reproduces a bug where the pathKey hash used in
@@ -4495,7 +4484,6 @@ void f() {
await _checkCompletionEdits(
importingFileUri,
content,
completionLabel,
expectedContent,
);
@@ -4541,7 +4529,6 @@ void f() {
await _checkCompletionEdits(
importingFileUri,
content,
completionLabel,
expectedContent,
);
@@ -4630,9 +4617,11 @@ void f() {
///
/// [content] should contain a `^` at the location where completion should be
/// invoked/accepted.
///
/// [expectedContent] can contain LSP snippet syntax if supported and expected
/// in the result.
Future<void> _checkCompletionEdits(
Uri fileUri,
String content,
String completionLabel,
String expectedContent,
) async {
@@ -4641,7 +4630,7 @@ void f() {
await initialAnalysis;
var res = await getCompletion(fileUri, code.position.position);
var completion = res.where((c) => c.label == completionLabel).single;
var completion = res.singleWhere((c) => c.label == completionLabel);
var resolvedCompletion = await resolveCompletion(completion);
// Apply both the main completion edit and the additionalTextEdits atomically.
@@ -4649,7 +4638,7 @@ void f() {
code.code,
[
toTextEdit(resolvedCompletion.textEdit!),
].followedBy(resolvedCompletion.additionalTextEdits!).toList(),
].followedBy(resolvedCompletion.additionalTextEdits ?? []).toList(),
);
expect(newContent, equalsNormalized(expectedContent));