Files
sdk/pkg/analysis_server/test/domain_completion_test.dart
Paul Berry afcfbbeba8 Migrate developer experience packages to new constructor decl syntax.
(Part of https://github.com/dart-lang/sdk/issues/63288)

This change migrates the packages owned by the developer experience
team to use the new constructor declaration syntax, described in
https://github.com/dart-lang/language/blob/main/accepted/future-releases/primary-constructors/feature-specification.md#abbreviations-of-in-body-constructor-declarations.

This change was performed in an automated fashion, by (a) bumping the
packages' SDK constraints to `3.13.0-0`, (b) enabling the lints
`unnecessary_type_name_in_constructor` and
`unnecessary_const_in_enum_constructor`, (c) fixing the resulting lint
failures using `dart fix`, and then (d) reformatting the affected
files.

To ease code review, I've reverted unrelated formatting changes.

Since this change requires bumping SDK constaints to `3.13.0-0`, it
was only performed on packages that are *not* published on
pub. (Packages that *are* published on pub should remain on lower
language versions until at least after the stable version of 3.13 is
released, so that we don't block users on the stable channel from
receiving updates to those packages.)

Change-Id: Ibb4daebafd239da58251e838ea6a3f336a6a6964
Reviewed-on: https://dart-review.googlesource.com/c/sdk/+/505046
Commit-Queue: Paul Berry <paulberry@google.com>
Reviewed-by: Brian Wilkerson <brianwilkerson@google.com>
SLSA-Policy-Verified: SLSA Policy Verification Service <devtools-gerritcodereview-exitgate@google.com>
2026-05-27 14:52:58 -07:00

2392 lines
49 KiB
Dart

// Copyright (c) 2014, the Dart project authors. Please see the AUTHORS file
// 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 'dart:async';
import 'package:analysis_server/src/protocol_server.dart';
import 'package:analyzer/file_system/file_system.dart';
import 'package:analyzer/src/test_utilities/platform.dart';
import 'package:analyzer/src/test_utilities/test_code_format.dart';
import 'package:analyzer_testing/package_config_file_builder.dart';
import 'package:test/test.dart';
import 'package:test_reflective_loader/test_reflective_loader.dart';
import 'analysis_server_base.dart';
import 'mocks.dart';
import 'services/completion/dart/completion_check.dart';
import 'services/completion/dart/completion_printer.dart' as printer;
import 'services/completion/dart/text_expectations.dart';
void main() {
defineReflectiveSuite(() {
defineReflectiveTests(CompletionDomainHandlerGetSuggestionDetails2Test);
defineReflectiveTests(CompletionDomainHandlerGetSuggestions2Test);
});
}
@reflectiveTest
class CompletionDomainHandlerGetSuggestionDetails2Test
extends PubPackageAnalysisServerTest {
String get testEolEscaped =>
testEol.replaceAll('\r', r'\r').replaceAll('\n', r'\n');
void assertDetailsText(
CompletionGetSuggestionDetails2Result result,
String expected, {
bool printIfFailed = true,
}) {
var buffer = StringBuffer();
_SuggestionDetailsPrinter(
resourceProvider: resourceProvider,
fileDisplayMap: {testFile: 'testFile'},
buffer: buffer,
result: result,
).writeResult();
var actual = buffer.toString();
if (actual != expected) {
if (printIfFailed) {
print(actual);
}
TextExpectationsCollector.add(actual);
}
expect(actual, expected);
}
Future<void> test_alreadyImported() async {
await _configureWithWorkspaceRoot();
var details = await _getTestCodeDetails(
'''
import 'dart:math';
void f() {
Rand^
}
''',
completion: 'Random',
libraryUri: 'dart:math',
);
assertDetailsText(details, r'''
completion: Random
change
''');
}
Future<void> test_import_dart() async {
await _configureWithWorkspaceRoot();
var details = await _getTestCodeDetails(
'''
void f() {
R^
}
''',
completion: 'Random',
libraryUri: 'dart:math',
);
assertDetailsText(details, '''
completion: Random
change
testFile
offset: 0
length: 0
replacement: import 'dart:math';$testEolEscaped$testEolEscaped
''');
}
Future<void> test_import_package_dependencies() async {
writeTestPackagePubspecYamlFile(r'''
name: test
dependencies:
aaa: any
''');
var aaaRoot = getFolder('$workspaceRootPath/packages/aaa');
newFile('${aaaRoot.path}/lib/f.dart', '''
class Test {}
''');
writeTestPackageConfig(
config: PackageConfigFileBuilder()..add(name: 'aaa', rootFolder: aaaRoot),
);
await _configureWithWorkspaceRoot();
var details = await _getTestCodeDetails(
'''
void f() {
T^
}
''',
completion: 'Test',
libraryUri: 'package:aaa/a.dart',
);
assertDetailsText(details, '''
completion: Test
change
testFile
offset: 0
length: 0
replacement: import 'package:aaa/a.dart';$testEolEscaped$testEolEscaped
''');
}
Future<void> test_import_package_this() async {
newFile('$testPackageLibPath/a.dart', '''
class Test {}
''');
await _configureWithWorkspaceRoot();
var details = await _getTestCodeDetails(
'''
void f() {
T^
}
''',
completion: 'Test',
libraryUri: 'package:test/a.dart',
);
assertDetailsText(details, '''
completion: Test
change
testFile
offset: 0
length: 0
replacement: import 'package:test/a.dart';$testEolEscaped$testEolEscaped
''');
}
Future<void> test_invalidLibraryUri() async {
await _configureWithWorkspaceRoot();
var request = CompletionGetSuggestionDetails2Params(
testFile.path,
0,
'Random',
'[foo]:bar',
).toRequest('0', clientUriConverter: server.uriConverter);
var response = await handleRequest(request);
expect(response.error?.code, RequestErrorCode.INVALID_PARAMETER);
// TODO(scheglov): Check that says "libraryUri".
}
Future<void> test_invalidPath() async {
await _configureWithWorkspaceRoot();
var request = CompletionGetSuggestionDetails2Params(
'foo',
0,
'Random',
'dart:math',
).toRequest('0', clientUriConverter: server.uriConverter);
var response = await handleRequest(request);
expect(response.error?.code, RequestErrorCode.INVALID_FILE_PATH_FORMAT);
}
Future<void> _configureWithWorkspaceRoot() async {
await setRoots(included: [workspaceRootPath], excluded: []);
await server.onAnalysisComplete;
}
Future<CompletionGetSuggestionDetails2Result> _getCodeDetails({
required String path,
required String content,
required String completion,
required String libraryUri,
}) async {
var code = TestCode.parse(content);
var completionOffset = code.position.offset;
newFile(path, code.code);
return await _getDetails(
path: path,
completionOffset: completionOffset,
completion: completion,
libraryUri: libraryUri,
);
}
Future<CompletionGetSuggestionDetails2Result> _getDetails({
required String path,
required int completionOffset,
required String completion,
required String libraryUri,
}) async {
var request = CompletionGetSuggestionDetails2Params(
path,
completionOffset,
completion,
libraryUri,
).toRequest('0', clientUriConverter: server.uriConverter);
var response = await handleSuccessfulRequest(request);
return CompletionGetSuggestionDetails2Result.fromResponse(
response,
clientUriConverter: server.uriConverter,
);
}
Future<CompletionGetSuggestionDetails2Result> _getTestCodeDetails(
String content, {
required String completion,
required String libraryUri,
}) {
return _getCodeDetails(
path: convertPath(testFilePath),
content: content,
completion: completion,
libraryUri: libraryUri,
);
}
}
@reflectiveTest
class CompletionDomainHandlerGetSuggestions2Test
extends PubPackageAnalysisServerTest {
printer.Configuration printerConfiguration = printer.Configuration(
filter: (suggestion) {
var completion = suggestion.completion;
if (completion.startsWith('A0')) {
return suggestion.isClass;
}
return const {'foo0'}.any(completion.startsWith);
},
withIsNotImported: true,
withLibraryUri: true,
);
/// Asserts that the [response] has the [expected] textual dump produced
/// using [printerConfiguration].
void assertResponseText(
CompletionResponseForTesting response,
String expected, {
bool printIfFailed = true,
}) {
// The generated response will always be using the EOLs being tested so
// normalize the expected test to the same to ensure that all actual output
// uses the expected line endings.
expected = normalizeNewlinesForPlatform(expected);
var buffer = StringBuffer();
printer.CompletionResponsePrinter(
buffer: buffer,
configuration: printerConfiguration,
response: response,
).writeResponse();
var actual = buffer.toString();
// The source code in our test files will always be written with only `\n`
// (due to git settings) so strip any `\r` that might be in the actual
// result for any output intended to go into source files.
var actualForSourceCode = actual.replaceAll('\r', '');
if (actual != expected) {
if (printIfFailed) {
print(actualForSourceCode);
}
TextExpectationsCollector.add(actualForSourceCode);
}
expect(actual, expected);
}
@override
void setUp() {
super.setUp();
server.completionState.budgetDuration = const Duration(seconds: 30);
}
Future<void> test_abort_onAnotherCompletionRequest() async {
var abortedIdSet = <String>{};
server.discardedRequests.stream.listen((request) {
abortedIdSet.add(request.id);
});
newFile(testFilePath, '');
await _configureWithWorkspaceRoot();
// Send three requests, the first two should be aborted.
var request0 = _sendTestCompletionRequest('0', 0);
var request1 = _sendTestCompletionRequest('1', 0);
var request2 = _sendTestCompletionRequest('2', 0);
// Wait for all three.
var response0 = await request0.toResponse();
var response1 = await request1.toResponse();
var response2 = await request2.toResponse();
// The first two should be aborted.
expect(abortedIdSet, {'0', '1'});
printerConfiguration.filter = (suggestion) {
if (suggestion.isClass) {
return const {'int'}.contains(suggestion.completion);
}
return false;
};
assertResponseText(response0, r'''
suggestions
''');
assertResponseText(response1, r'''
suggestions
''');
assertResponseText(response2, r'''
suggestions
int
kind: class
isNotImported: null
libraryUri: dart:core
''');
}
Future<void> test_abort_onUpdateContent() async {
var abortedIdSet = <String>{};
server.discardedRequests.stream.listen((request) {
abortedIdSet.add(request.id);
});
newFile(testFilePath, '');
await _configureWithWorkspaceRoot();
// Schedule a completion request.
var request = _sendTestCompletionRequest('0', 0);
// Simulate typing in the IDE.
await handleSuccessfulRequest(
AnalysisUpdateContentParams({
testFile.path: AddContentOverlay('void f() {}'),
}).toRequest('1', clientUriConverter: server.uriConverter),
);
// The request should be aborted.
var response = await request.toResponse();
expect(abortedIdSet, {'0'});
assertResponseText(response, r'''
suggestions
''');
}
Future<void> test_applyPendingFileChanges() async {
await _configureWithWorkspaceRoot();
// Request with the empty content.
await _getTestCodeSuggestions('^');
// Change the file, and request again.
// Should apply pending file changes before resolving.
var response = await _getTestCodeSuggestions('Str^');
printerConfiguration.filter = (suggestion) {
if (suggestion.isClass) {
return const {'String'}.contains(suggestion.completion);
}
return false;
};
assertResponseText(response, r'''
replacement
left: 3
suggestions
String
kind: class
isNotImported: null
libraryUri: dart:core
''');
}
Future<void> test_isNotImportedFeature_prefixed_classInstanceMethod() async {
newFile('$testPackageLibPath/a.dart', '''
class A {
void foo01() {}
}
''');
newFile('$testPackageLibPath/b.dart', '''
import 'a.dart';
class B extends A {
void foo02() {}
}
''');
await _configureWithWorkspaceRoot();
var response = await _getTestCodeSuggestions('''
import 'b.dart';
void f(B b) {
b.foo0^
}
''');
// The fact that `b.dart` is imported, and `a.dart` is not, does not affect
// the order of suggestions added with an expression prefix. We are not
// going to import anything, so this does not matter.
assertResponseText(response, r'''
replacement
left: 4
suggestions
foo01
kind: methodInvocation
isNotImported: null
libraryUri: null
foo02
kind: methodInvocation
isNotImported: null
libraryUri: null
''');
}
Future<void> test_notImported_dart() async {
await _configureWithWorkspaceRoot();
var response = await _getTestCodeSuggestions('''
void f() {
Rand^
}
''');
printerConfiguration.filter = (suggestion) {
return suggestion.isClass;
};
assertResponseText(response, r'''
replacement
left: 4
suggestions
Random
kind: class
isNotImported: true
libraryUri: dart:math
''');
}
Future<void> test_notImported_emptyBudget() async {
await _configureWithWorkspaceRoot();
// Empty budget, so no not yet imported libraries.
server.completionState.budgetDuration = const Duration();
var response = await _getTestCodeSuggestions('''
void f() {
Rand^
}
''');
printerConfiguration.filter = (_) => true;
assertResponseText(response, r'''
replacement
left: 4
suggestions
''');
}
Future<void> test_notImported_lowerRelevance_extension_getter() async {
await _configureWithWorkspaceRoot();
printerConfiguration.sorting =
printer.Sorting.relevanceThenCompletionThenKind;
newFile('$testPackageLibPath/a.dart', '''
extension E1 on int {
int get foo01 => 0;
}
''');
newFile('$testPackageLibPath/b.dart', '''
extension E2 on int {
int get foo02 => 0;
}
''');
var response = await _getTestCodeSuggestions(r'''
import 'b.dart';
void f() {
0.foo0^
}
''');
// `foo01` relevance is decreased because it is not yet imported.
assertResponseText(response, r'''
replacement
left: 4
suggestions
foo02
kind: getter
isNotImported: null
libraryUri: null
foo01
kind: getter
isNotImported: true
libraryUri: package:test/a.dart
''');
}
Future<void> test_notImported_lowerRelevance_extension_method() async {
await _configureWithWorkspaceRoot();
printerConfiguration.sorting =
printer.Sorting.relevanceThenCompletionThenKind;
newFile('$testPackageLibPath/a.dart', '''
extension E1 on int {
void foo01() {}
}
''');
newFile('$testPackageLibPath/b.dart', '''
extension E2 on int {
void foo02() {}
}
''');
var response = await _getTestCodeSuggestions(r'''
import 'b.dart';
void f() {
0.foo0^
}
''');
// `foo01` relevance is decreased because it is not yet imported.
assertResponseText(response, r'''
replacement
left: 4
suggestions
foo02
kind: methodInvocation
isNotImported: null
libraryUri: null
foo01
kind: methodInvocation
isNotImported: true
libraryUri: package:test/a.dart
''');
}
Future<void> test_notImported_lowerRelevance_extension_setter() async {
await _configureWithWorkspaceRoot();
printerConfiguration.sorting =
printer.Sorting.relevanceThenCompletionThenKind;
newFile('$testPackageLibPath/a.dart', '''
extension E1 on int {
set foo01(int _) {}
}
''');
newFile('$testPackageLibPath/b.dart', '''
extension E2 on int {
set foo02(int _) {}
}
''');
var response = await _getTestCodeSuggestions(r'''
import 'b.dart';
void f() {
0.foo0^
}
''');
// `foo01` relevance is decreased because it is not yet imported.
assertResponseText(response, r'''
replacement
left: 4
suggestions
foo02
kind: setter
isNotImported: null
libraryUri: null
foo01
kind: setter
isNotImported: true
libraryUri: package:test/a.dart
''');
}
Future<void> test_notImported_lowerRelevance_topLevel_class() async {
newFile('$testPackageLibPath/a.dart', '''
class A01 {}
''');
newFile('$testPackageLibPath/b.dart', '''
class A02 {}
''');
await _configureWithWorkspaceRoot();
printerConfiguration.sorting =
printer.Sorting.relevanceThenCompletionThenKind;
var response = await _getTestCodeSuggestions('''
import 'b.dart';
void f() {
A0^
}
''');
// `A01` relevance is decreased because it is not yet imported.
assertResponseText(response, r'''
replacement
left: 2
suggestions
A02
kind: class
isNotImported: null
libraryUri: package:test/b.dart
A01
kind: class
isNotImported: true
libraryUri: package:test/a.dart
''');
}
Future<void> test_notImported_lowerRelevance_topLevel_getter() async {
newFile('$testPackageLibPath/a.dart', '''
int get foo01 => 0;
''');
newFile('$testPackageLibPath/b.dart', '''
int get foo02 => 0;
''');
await _configureWithWorkspaceRoot();
printerConfiguration.sorting =
printer.Sorting.relevanceThenCompletionThenKind;
var response = await _getTestCodeSuggestions('''
import 'b.dart';
void f() {
foo0^
}
''');
// `foo01` relevance is decreased because it is not yet imported.
assertResponseText(response, r'''
replacement
left: 4
suggestions
foo02
kind: getter
isNotImported: null
libraryUri: package:test/b.dart
foo01
kind: getter
isNotImported: true
libraryUri: package:test/a.dart
''');
}
Future<void> test_notImported_lowerRelevance_topLevel_setter() async {
newFile('$testPackageLibPath/a.dart', '''
set foo01(int _) {}
''');
newFile('$testPackageLibPath/b.dart', '''
set foo02(int _) {}
''');
await _configureWithWorkspaceRoot();
printerConfiguration.sorting =
printer.Sorting.relevanceThenCompletionThenKind;
var response = await _getTestCodeSuggestions('''
import 'b.dart';
void f() {
foo0^
}
''');
// `foo01` relevance is decreased because it is not yet imported.
assertResponseText(response, r'''
replacement
left: 4
suggestions
foo02
kind: setter
isNotImported: null
libraryUri: package:test/b.dart
foo01
kind: setter
isNotImported: true
libraryUri: package:test/a.dart
''');
}
Future<void> test_notImported_lowerRelevance_topLevel_variable() async {
newFile('$testPackageLibPath/a.dart', '''
var foo01 = 0;
''');
newFile('$testPackageLibPath/b.dart', '''
var foo02 = 0;
''');
await _configureWithWorkspaceRoot();
printerConfiguration.sorting =
printer.Sorting.relevanceThenCompletionThenKind;
var response = await _getTestCodeSuggestions('''
import 'b.dart';
void f() {
foo0^
}
''');
// `foo01` relevance is decreased because it is not yet imported.
assertResponseText(response, r'''
replacement
left: 4
suggestions
foo02
kind: topLevelVariable
isNotImported: null
libraryUri: package:test/b.dart
foo01
kind: topLevelVariable
isNotImported: true
libraryUri: package:test/a.dart
''');
}
Future<void> test_notImported_pub_dependencies_inBin() async {
writeTestPackagePubspecYamlFile(r'''
name: test
dependencies:
aaa: any
dev_dependencies:
bbb: any
''');
var aaaRoot = getFolder('$workspaceRootPath/packages/aaa');
newFile('${aaaRoot.path}/lib/f.dart', '''
class A01 {}
''');
var bbbRoot = getFolder('$workspaceRootPath/packages/bbb');
newFile('${bbbRoot.path}/lib/f.dart', '''
class A02 {}
''');
writeTestPackageConfig(
config: PackageConfigFileBuilder()
..add(name: 'aaa', rootFolder: aaaRoot)
..add(name: 'bbb', rootFolder: bbbRoot),
);
await _configureWithWorkspaceRoot();
var binPath = convertPath('$testPackageRootPath/bin/main.dart');
var response = await _getCodeSuggestions(
path: binPath,
content: '''
void f() {
A0^
}
''',
);
assertResponseText(response, r'''
replacement
left: 2
suggestions
A01
kind: class
isNotImported: true
libraryUri: package:aaa/f.dart
''');
}
Future<void> test_notImported_pub_dependencies_inLib() async {
writeTestPackagePubspecYamlFile(r'''
name: test
dependencies:
aaa: any
dev_dependencies:
bbb: any
''');
var aaaRoot = getFolder('$workspaceRootPath/packages/aaa');
newFile('${aaaRoot.path}/lib/f.dart', '''
class A01 {}
''');
newFile('${aaaRoot.path}/lib/src/f.dart', '''
class A02 {}
''');
var bbbRoot = getFolder('$workspaceRootPath/packages/bbb');
newFile('${bbbRoot.path}/lib/f.dart', '''
class A03 {}
''');
newFile('${bbbRoot.path}/lib/src/f.dart', '''
class A04 {}
''');
writeTestPackageConfig(
config: PackageConfigFileBuilder()
..add(name: 'aaa', rootFolder: aaaRoot)
..add(name: 'bbb', rootFolder: bbbRoot),
);
await _configureWithWorkspaceRoot();
var response = await _getTestCodeSuggestions('''
void f() {
A0^
}
''');
assertResponseText(response, r'''
replacement
left: 2
suggestions
A01
kind: class
isNotImported: true
libraryUri: package:aaa/f.dart
''');
}
Future<void> test_notImported_pub_dependencies_inTest() async {
writeTestPackagePubspecYamlFile(r'''
name: test
dependencies:
aaa: any
dev_dependencies:
bbb: any
''');
var aaaRoot = getFolder('$workspaceRootPath/packages/aaa');
newFile('${aaaRoot.path}/lib/f.dart', '''
class A01 {}
''');
newFile('${aaaRoot.path}/lib/src/f.dart', '''
class A02 {}
''');
var bbbRoot = getFolder('$workspaceRootPath/packages/bbb');
newFile('${bbbRoot.path}/lib/f.dart', '''
class A03 {}
''');
newFile('${bbbRoot.path}/lib/src/f.dart', '''
class A04 {}
''');
writeTestPackageConfig(
config: PackageConfigFileBuilder()
..add(name: 'aaa', rootFolder: aaaRoot)
..add(name: 'bbb', rootFolder: bbbRoot),
);
await _configureWithWorkspaceRoot();
var test_path = convertPath('$testPackageTestPath/test.dart');
var response = await _getCodeSuggestions(
path: test_path,
content: '''
void f() {
A0^
}
''',
);
assertResponseText(response, r'''
replacement
left: 2
suggestions
A01
kind: class
isNotImported: true
libraryUri: package:aaa/f.dart
A03
kind: class
isNotImported: true
libraryUri: package:bbb/f.dart
''');
}
Future<void> test_notImported_pub_dependencies_inWeb() async {
writeTestPackagePubspecYamlFile(r'''
name: test
dependencies:
aaa: any
dev_dependencies:
bbb: any
''');
var aaaRoot = getFolder('$workspaceRootPath/packages/aaa');
newFile('${aaaRoot.path}/lib/f.dart', '''
class A01 {}
''');
var bbbRoot = getFolder('$workspaceRootPath/packages/bbb');
newFile('${bbbRoot.path}/lib/f.dart', '''
class A02 {}
''');
writeTestPackageConfig(
config: PackageConfigFileBuilder()
..add(name: 'aaa', rootFolder: aaaRoot)
..add(name: 'bbb', rootFolder: bbbRoot),
);
await _configureWithWorkspaceRoot();
var webPath = convertPath('$testPackageRootPath/web/main.dart');
var response = await _getCodeSuggestions(
path: webPath,
content: '''
void f() {
A0^
}
''',
);
assertResponseText(response, r'''
replacement
left: 2
suggestions
A01
kind: class
isNotImported: true
libraryUri: package:aaa/f.dart
''');
}
Future<void> test_notImported_pub_this() async {
newFile('$testPackageLibPath/a.dart', '''
class A01 {}
''');
newFile('$testPackageLibPath/b.dart', '''
class A02 {}
''');
await _configureWithWorkspaceRoot();
var response = await _getTestCodeSuggestions('''
void f() {
A0^
}
''');
assertResponseText(response, r'''
replacement
left: 2
suggestions
A01
kind: class
isNotImported: true
libraryUri: package:test/a.dart
A02
kind: class
isNotImported: true
libraryUri: package:test/b.dart
''');
}
Future<void> test_notImported_pub_this_hasImport() async {
newFile('$testPackageLibPath/a.dart', '''
class A01 {}
class A02 {}
''');
newFile('$testPackageLibPath/b.dart', '''
class A03 {}
''');
await _configureWithWorkspaceRoot();
var response = await _getTestCodeSuggestions('''
import 'a.dart';
void f() {
A0^
}
''');
assertResponseText(response, r'''
replacement
left: 2
suggestions
A01
kind: class
isNotImported: null
libraryUri: package:test/a.dart
A02
kind: class
isNotImported: null
libraryUri: package:test/a.dart
A03
kind: class
isNotImported: true
libraryUri: package:test/b.dart
''');
}
Future<void> test_notImported_pub_this_hasImport_hasShow() async {
newFile('$testPackageLibPath/a.dart', '''
class A01 {}
class A02 {}
''');
newFile('$testPackageLibPath/b.dart', '''
class A03 {}
''');
await _configureWithWorkspaceRoot();
printerConfiguration.sorting =
printer.Sorting.relevanceThenCompletionThenKind;
var response = await _getTestCodeSuggestions('''
import 'a.dart' show A02;
void f() {
A0^
}
''');
// Note:
// 1. A02 is the first, because it is already imported.
// 2. A01 is still suggested, but with lower relevance.
// 3. A03 has the same relevance (not tested), but sorted by name.
assertResponseText(response, r'''
replacement
left: 2
suggestions
A02
kind: class
isNotImported: null
libraryUri: package:test/a.dart
A01
kind: class
isNotImported: true
libraryUri: package:test/a.dart
A03
kind: class
isNotImported: true
libraryUri: package:test/b.dart
''');
}
Future<void> test_notImported_pub_this_inLib_excludesTest() async {
writeTestPackagePubspecYamlFile(r'''
name: test
''');
newFile('$testPackageLibPath/a.dart', '''
class A01 {}
''');
newFile('$testPackageTestPath/b.dart', '''
class A02 {}
''');
await _configureWithWorkspaceRoot();
var response = await _getTestCodeSuggestions('''
void f() {
A0^
}
''');
assertResponseText(response, r'''
replacement
left: 2
suggestions
A01
kind: class
isNotImported: true
libraryUri: package:test/a.dart
''');
}
Future<void> test_notImported_pub_this_inLib_includesThisSrc() async {
writeTestPackagePubspecYamlFile(r'''
name: test
''');
newFile('$testPackageLibPath/f.dart', '''
class A01 {}
''');
newFile('$testPackageLibPath/src/f.dart', '''
class A02 {}
''');
await _configureWithWorkspaceRoot();
var response = await _getTestCodeSuggestions('''
void f() {
A0^
}
''');
assertResponseText(response, r'''
replacement
left: 2
suggestions
A01
kind: class
isNotImported: true
libraryUri: package:test/f.dart
A02
kind: class
isNotImported: true
libraryUri: package:test/src/f.dart
''');
}
Future<void> test_notImported_pub_this_inTest_includesTest() async {
writeTestPackagePubspecYamlFile(r'''
name: test
''');
newFile('$testPackageLibPath/a.dart', '''
class A01 {}
''');
var b = newFile('$testPackageTestPath/b.dart', '''
class A02 {}
''');
var b_uriStr = toUriStr(b.path);
await _configureWithWorkspaceRoot();
var test_path = convertPath('$testPackageTestPath/test.dart');
var response = await _getCodeSuggestions(
path: test_path,
content: '''
void f() {
A0^
}
''',
);
assertResponseText(response, '''
replacement
left: 2
suggestions
A01
kind: class
isNotImported: true
libraryUri: package:test/a.dart
A02
kind: class
isNotImported: true
libraryUri: $b_uriStr
''');
}
Future<void> test_notImported_pub_this_inTest_includesThisSrc() async {
writeTestPackagePubspecYamlFile(r'''
name: test
''');
newFile('$testPackageLibPath/f.dart', '''
class A01 {}
''');
newFile('$testPackageLibPath/src/f.dart', '''
class A02 {}
''');
await _configureWithWorkspaceRoot();
var test_path = convertPath('$testPackageTestPath/test.dart');
var response = await _getCodeSuggestions(
path: test_path,
content: '''
void f() {
A0^
}
''',
);
assertResponseText(response, r'''
replacement
left: 2
suggestions
A01
kind: class
isNotImported: true
libraryUri: package:test/f.dart
A02
kind: class
isNotImported: true
libraryUri: package:test/src/f.dart
''');
}
Future<void> test_numResults_class_methods() async {
await _configureWithWorkspaceRoot();
var response = await _getTestCodeSuggestions('''
class A {
void foo01() {}
void foo02() {}
void foo03() {}
}
void f(A a) {
a.foo0^
}
''', maxResults: 2);
assertResponseText(response, r'''
replacement
left: 4
suggestions
foo01
kind: methodInvocation
isNotImported: null
libraryUri: null
foo02
kind: methodInvocation
isNotImported: null
libraryUri: null
''');
}
Future<void> test_numResults_includesLocalVariable() async {
await _configureWithWorkspaceRoot();
// Create many imported declarations to exceed the maxResults cap.
newFile(
'$testPackageLibPath/a.dart',
[
for (var i = 1; i <= 50; i++) 'class C$i {}',
for (var i = 1; i <= 50; i++) 'void f$i() {}',
for (var i = 1; i <= 50; i++) 'var v$i = $i;',
].join('\n'),
);
// No prefix, no filtering.
// Local variables 'foo0*' must be included.
var response = await _getTestCodeSuggestions('''
import 'a.dart';
void f() {
var foo01 = 0;
var foo02 = 0;
^
}
''', maxResults: 10);
// Only include local variables to keep the result stable.
printerConfiguration.filter = (suggestion) {
return suggestion.completion.startsWith('foo0');
};
// Note the variables order: the closer, the higher.
assertResponseText(response, r'''
suggestions
foo02
kind: localVariable
isNotImported: null
libraryUri: null
foo01
kind: localVariable
isNotImported: null
libraryUri: null
''');
}
Future<void> test_numResults_topLevelVariables() async {
await _configureWithWorkspaceRoot();
var response = await _getTestCodeSuggestions('''
var foo01 = 0;
var foo02 = 0;
var foo03 = 0;
void f() {
foo0^
}
''', maxResults: 2);
assertResponseText(response, r'''
replacement
left: 4
suggestions
foo01
kind: topLevelVariable
isNotImported: null
libraryUri: null
foo02
kind: topLevelVariable
isNotImported: null
libraryUri: null
''');
}
Future<void> test_numResults_topLevelVariables_imported_withPrefix() async {
await _configureWithWorkspaceRoot();
newFile('$testPackageLibPath/a.dart', '''
var foo01 = 0;
var foo02 = 0;
var foo03 = 0;
''');
var response = await _getTestCodeSuggestions('''
import 'a.dart' as prefix;
void f() {
prefix.^
}
''', maxResults: 2);
assertResponseText(response, r'''
suggestions
foo01
kind: topLevelVariable
isNotImported: null
libraryUri: package:test/a.dart
foo02
kind: topLevelVariable
isNotImported: null
libraryUri: package:test/a.dart
''');
}
Future<void> test_prefixed_class_constructors() async {
await _configureWithWorkspaceRoot();
var response = await _getTestCodeSuggestions('''
class A {
A.foo01();
A.foo02();
}
void f() {
A.foo0^
}
''');
assertResponseText(response, r'''
replacement
left: 4
suggestions
foo01
kind: constructorInvocation
isNotImported: null
libraryUri: null
foo02
kind: constructorInvocation
isNotImported: null
libraryUri: null
''');
}
Future<void> test_prefixed_class_getters() async {
await _configureWithWorkspaceRoot();
var response = await _getTestCodeSuggestions('''
class A {
int get foo01 => 0;
int get foo02 => 0;
}
void f(A a) {
a.foo0^
}
''');
assertResponseText(response, r'''
replacement
left: 4
suggestions
foo01
kind: getter
isNotImported: null
libraryUri: null
foo02
kind: getter
isNotImported: null
libraryUri: null
''');
}
Future<void> test_prefixed_class_methods_instance() async {
await _configureWithWorkspaceRoot();
var response = await _getTestCodeSuggestions('''
class A {
void foo01() {}
void foo02() {}
}
void f(A a) {
a.foo0^
}
''');
assertResponseText(response, r'''
replacement
left: 4
suggestions
foo01
kind: methodInvocation
isNotImported: null
libraryUri: null
foo02
kind: methodInvocation
isNotImported: null
libraryUri: null
''');
}
Future<void> test_prefixed_class_methods_static() async {
await _configureWithWorkspaceRoot();
var response = await _getTestCodeSuggestions('''
class A {
static void foo01() {}
static void foo02() {}
}
void f() {
A.foo0^
}
''');
assertResponseText(response, r'''
replacement
left: 4
suggestions
foo01
kind: methodInvocation
isNotImported: null
libraryUri: null
foo02
kind: methodInvocation
isNotImported: null
libraryUri: null
''');
}
Future<void> test_prefixed_expression_extensionGetters() async {
await _configureWithWorkspaceRoot();
var response = await _getTestCodeSuggestions(r'''
extension E1 on int {
int get foo01 => 0;
int get foo02 => 0;
int get bar => 0;
}
extension E2 on double {
int get foo03 => 0;
}
void f() {
0.foo0^
}
''');
assertResponseText(response, r'''
replacement
left: 4
suggestions
foo01
kind: getter
isNotImported: null
libraryUri: null
foo02
kind: getter
isNotImported: null
libraryUri: null
''');
}
Future<void> test_prefixed_expression_extensionGetters_notImported() async {
await _configureWithWorkspaceRoot();
newFile('$testPackageLibPath/a.dart', '''
extension E1 on int {
int get foo01 => 0;
int get bar => 0;
}
extension E2 on int {
int get foo02 => 0;
}
extension E3 on double {
int get foo03 => 0;
}
''');
var response = await _getTestCodeSuggestions(r'''
void f() {
0.foo0^
}
''');
assertResponseText(response, r'''
replacement
left: 4
suggestions
foo01
kind: getter
isNotImported: true
libraryUri: package:test/a.dart
foo02
kind: getter
isNotImported: true
libraryUri: package:test/a.dart
''');
}
Future<void>
test_prefixed_expression_extensionGetters_notImported_private() async {
await _configureWithWorkspaceRoot();
newFile('$testPackageLibPath/a.dart', '''
extension E1 on int {
int get foo01 => 0;
}
extension _E2 on int {
int get foo02 => 0;
}
extension on int {
int get foo03 => 0;
}
''');
var response = await _getTestCodeSuggestions(r'''
void f() {
0.foo0^
}
''');
assertResponseText(response, r'''
replacement
left: 4
suggestions
foo01
kind: getter
isNotImported: true
libraryUri: package:test/a.dart
''');
}
Future<void> test_prefixed_expression_extensionMethods() async {
await _configureWithWorkspaceRoot();
var response = await _getTestCodeSuggestions(r'''
extension E1 on int {
void foo01() {}
void foo02() {}
void bar() {}
}
extension E2 on double {
void foo03() {}
}
void f() {
0.foo0^
}
''');
assertResponseText(response, r'''
replacement
left: 4
suggestions
foo01
kind: methodInvocation
isNotImported: null
libraryUri: null
foo02
kind: methodInvocation
isNotImported: null
libraryUri: null
''');
}
Future<void> test_prefixed_expression_extensionMethods_notImported() async {
await _configureWithWorkspaceRoot();
newFile('$testPackageLibPath/a.dart', '''
extension E1 on int {
void foo01() {}
void bar() {}
}
extension E2 on int {
void foo02() {}
}
extension E3 on double {
void foo03() {}
}
''');
var response = await _getTestCodeSuggestions(r'''
void f() {
0.foo0^
}
''');
assertResponseText(response, r'''
replacement
left: 4
suggestions
foo01
kind: methodInvocation
isNotImported: true
libraryUri: package:test/a.dart
foo02
kind: methodInvocation
isNotImported: true
libraryUri: package:test/a.dart
''');
}
Future<void> test_prefixed_expression_extensionSetters() async {
await _configureWithWorkspaceRoot();
var response = await _getTestCodeSuggestions(r'''
extension E1 on int {
set foo01(int _) {}
set foo02(int _) {}
set bar(int _) {}
}
extension E2 on double {
set foo03(int _) {}
}
void f() {
0.foo0^
}
''');
assertResponseText(response, r'''
replacement
left: 4
suggestions
foo01
kind: setter
isNotImported: null
libraryUri: null
foo02
kind: setter
isNotImported: null
libraryUri: null
''');
}
Future<void> test_prefixed_expression_extensionSetters_notImported() async {
await _configureWithWorkspaceRoot();
newFile('$testPackageLibPath/a.dart', '''
extension E1 on int {
set foo01(int _) {}
set bar(int _) {}
}
extension E2 on int {
set foo02(int _) {}
}
extension E3 on double {
set foo03(int _) {}
}
''');
var response = await _getTestCodeSuggestions(r'''
void f() {
0.foo0^
}
''');
assertResponseText(response, r'''
replacement
left: 4
suggestions
foo01
kind: setter
isNotImported: true
libraryUri: package:test/a.dart
foo02
kind: setter
isNotImported: true
libraryUri: package:test/a.dart
''');
}
Future<void>
test_prefixed_expression_extensionSetters_notImported_private() async {
await _configureWithWorkspaceRoot();
newFile('$testPackageLibPath/a.dart', '''
extension E1 on int {
set foo01(int _) {}
}
extension _E2 on int {
set foo02(int _) {}
}
extension on int {
set foo03(int _) {}
}
''');
var response = await _getTestCodeSuggestions(r'''
void f() {
0.foo0^
}
''');
assertResponseText(response, r'''
replacement
left: 4
suggestions
foo01
kind: setter
isNotImported: true
libraryUri: package:test/a.dart
''');
}
Future<void> test_prefixed_extensionGetters_imported() async {
await _configureWithWorkspaceRoot();
newFile('$testPackageLibPath/a.dart', '''
extension E1 on int {
int get foo01 => 0;
int get foo02 => 0;
int get bar => 0;
}
extension E2 on double {
int get foo03 => 0;
}
''');
var response = await _getTestCodeSuggestions(r'''
import 'a.dart';
void f() {
0.foo0^
}
''');
assertResponseText(response, r'''
replacement
left: 4
suggestions
foo01
kind: getter
isNotImported: null
libraryUri: null
foo02
kind: getter
isNotImported: null
libraryUri: null
''');
}
Future<void> test_prefixed_extensionOverride_extensionGetters() async {
await _configureWithWorkspaceRoot();
var response = await _getTestCodeSuggestions(r'''
extension E1 on int {
int get foo01 => 0;
}
extension E2 on int {
int get foo02 => 0;
}
void f() {
E1(0).foo0^
}
''');
assertResponseText(response, r'''
replacement
left: 4
suggestions
foo01
kind: getter
isNotImported: null
libraryUri: null
''');
}
Future<void> test_prefixed_extensionOverride_extensionMethods() async {
await _configureWithWorkspaceRoot();
var response = await _getTestCodeSuggestions(r'''
extension E1 on int {
void foo01() {}
}
extension E2 on int {
void foo01() {}
}
void f() {
E1(0).foo0^
}
''');
assertResponseText(response, r'''
replacement
left: 4
suggestions
foo01
kind: methodInvocation
isNotImported: null
libraryUri: null
''');
}
Future<void> test_prefixed_importPrefix_class() async {
await _configureWithWorkspaceRoot();
var response = await _getTestCodeSuggestions('''
import 'dart:math' as math;
void f() {
math.Rand^
}
''');
printerConfiguration.filter = (_) => true;
assertResponseText(response, r'''
replacement
left: 4
suggestions
Random
kind: class
isNotImported: null
libraryUri: dart:math
Random
kind: constructorInvocation
isNotImported: null
libraryUri: dart:math
''');
}
Future<void> test_unprefixed_filters() async {
await _configureWithWorkspaceRoot();
var response = await _getTestCodeSuggestions(r'''
var foo01 = 0;
var foo02 = 0;
var bar01 = 0;
var bar02 = 0;
void f() {
foo0^
}
''');
assertResponseText(response, r'''
replacement
left: 4
suggestions
foo01
kind: topLevelVariable
isNotImported: null
libraryUri: null
foo02
kind: topLevelVariable
isNotImported: null
libraryUri: null
''');
}
Future<void> test_unprefixed_imported_class() async {
await _configureWithWorkspaceRoot();
newFile('$testPackageLibPath/a.dart', '''
class A01 {}
''');
newFile('$testPackageLibPath/b.dart', '''
class A02 {}
''');
var response = await _getTestCodeSuggestions('''
import 'a.dart';
import 'b.dart';
void f() {
A0^
}
''');
assertResponseText(response, r'''
replacement
left: 2
suggestions
A01
kind: class
isNotImported: null
libraryUri: package:test/a.dart
A02
kind: class
isNotImported: null
libraryUri: package:test/b.dart
''');
}
Future<void> test_unprefixed_imported_topLevelVariable() async {
await _configureWithWorkspaceRoot();
newFile('$testPackageLibPath/a.dart', '''
var foo01 = 0;
''');
newFile('$testPackageLibPath/b.dart', '''
var foo02 = 0;
''');
var response = await _getTestCodeSuggestions('''
import 'a.dart';
import 'b.dart';
void f() {
foo0^
}
''');
assertResponseText(response, r'''
replacement
left: 4
suggestions
foo01
kind: topLevelVariable
isNotImported: null
libraryUri: package:test/a.dart
foo02
kind: topLevelVariable
isNotImported: null
libraryUri: package:test/b.dart
''');
}
Future<void> test_unprefixed_imported_withPrefix_class() async {
await _configureWithWorkspaceRoot();
var response = await _getTestCodeSuggestions('''
import 'dart:math' as math;
void f() {
Rand^
}
''');
printerConfiguration.filter = (suggestion) {
return suggestion.isClass;
};
// No suggestion without the `math` prefix.
assertResponseText(response, r'''
replacement
left: 4
suggestions
math.Random
kind: class
isNotImported: null
libraryUri: dart:math
''');
}
Future<void> test_unprefixed_sorts_byScore() async {
await _configureWithWorkspaceRoot();
var response = await _getTestCodeSuggestions(r'''
var fooAB = 0;
var fooBB = 0;
void f() {
fooB^
}
''');
printerConfiguration
..filter = (suggestion) {
return suggestion.completion.startsWith('foo');
}
..sorting = printer.Sorting.asIs
..withRelevance = true;
// `fooBB` has better score than `fooAB` - prefix match
assertResponseText(response, r'''
replacement
left: 4
suggestions
fooBB
kind: topLevelVariable
isNotImported: null
libraryUri: null
relevance: 503
fooAB
kind: topLevelVariable
isNotImported: null
libraryUri: null
relevance: 503
''');
}
Future<void> test_unprefixed_sorts_byType() async {
await _configureWithWorkspaceRoot();
var response = await _getTestCodeSuggestions(r'''
var foo01 = 0.0;
var foo02 = 0;
void f() {
int v = foo0^
}
''');
printerConfiguration
..sorting = printer.Sorting.asIs
..withRelevance = true;
assertResponseText(response, r'''
replacement
left: 4
suggestions
foo02
kind: topLevelVariable
isNotImported: null
libraryUri: null
relevance: 558
foo01
kind: topLevelVariable
isNotImported: null
libraryUri: null
relevance: 510
''');
}
Future<void> test_yaml_analysisOptions_formatter() async {
await _configureWithWorkspaceRoot();
var path = convertPath('$testPackageRootPath/analysis_options.yaml');
var response = await _getCodeSuggestions(
path: path,
content: '''
formatter:
^
''',
);
printerConfiguration
..filter = ((_) => true)
..withIsNotImported = false
..withLibraryUri = false;
assertResponseText(response, r'''
suggestions
|page_width: |
kind: identifier
|trailing_commas: |
kind: identifier
''');
}
Future<void> test_yaml_analysisOptions_root() async {
await _configureWithWorkspaceRoot();
var path = convertPath('$testPackageRootPath/analysis_options.yaml');
var response = await _getCodeSuggestions(path: path, content: '^');
printerConfiguration
..filter = ((_) => true)
..withIsNotImported = false
..withLibraryUri = false;
assertResponseText(response, r'''
suggestions
|analyzer: |
kind: identifier
|code-style: |
kind: identifier
|formatter: |
kind: identifier
|include: |
kind: identifier
|linter: |
kind: identifier
|plugins: |
kind: identifier
''');
}
Future<void> test_yaml_fixData_root() async {
await _configureWithWorkspaceRoot();
var path = convertPath('$testPackageRootPath/fix_data.yaml');
var response = await _getCodeSuggestions(path: path, content: '^');
printerConfiguration
..filter = ((_) => true)
..withIsNotImported = false
..withLibraryUri = false;
assertResponseText(response, r'''
suggestions
transforms:
kind: identifier
|version: |
kind: identifier
''');
}
Future<void> test_yaml_pubspec_root() async {
await _configureWithWorkspaceRoot();
var path = convertPath('$testPackageRootPath/pubspec.yaml');
var response = await _getCodeSuggestions(path: path, content: '^');
printerConfiguration
..filter = ((_) => true)
..withIsNotImported = false
..withLibraryUri = false;
assertResponseText(response, r'''
suggestions
|dependencies: |
kind: identifier
|dependency_overrides: |
kind: identifier
|description: |
kind: identifier
|dev_dependencies: |
kind: identifier
|documentation: |
kind: identifier
|environment: |
kind: identifier
|executables: |
kind: identifier
|flutter: |
kind: identifier
|homepage: |
kind: identifier
|issue_tracker: |
kind: identifier
|name: |
kind: identifier
|publish_to: |
kind: identifier
|repository: |
kind: identifier
|resolution: |
kind: identifier
screenshots:
kind: identifier
|topics: |
kind: identifier
|version: |
kind: identifier
workspace:
kind: identifier
''');
}
Future<void> _configureWithWorkspaceRoot() async {
await setRoots(included: [workspaceRootPath], excluded: []);
await server.onAnalysisComplete;
}
Future<CompletionResponseForTesting> _getCodeSuggestions({
required String path,
required String content,
int maxResults = 1 << 10,
}) async {
var code = TestCode.parseNormalized(content);
var completionOffset = code.position.offset;
newFile(path, code.code);
return await _getSuggestions(
path: path,
completionOffset: completionOffset,
maxResults: maxResults,
);
}
Future<CompletionResponseForTesting> _getSuggestions({
required String path,
required int completionOffset,
required int maxResults,
}) async {
var request = CompletionGetSuggestions2Params(
path,
completionOffset,
maxResults,
).toRequest('0', clientUriConverter: server.uriConverter);
var response = await handleSuccessfulRequest(request);
var result = CompletionGetSuggestions2Result.fromResponse(
response,
clientUriConverter: server.uriConverter,
);
// Extract the internal request object.
var dartRequest = server.completionState.currentRequest;
return CompletionResponseForTesting(
requestOffset: completionOffset,
requestLocationName: dartRequest?.collectorLocationName,
opTypeLocationName: dartRequest?.opType.completionLocation,
replacementOffset: result.replacementOffset,
replacementLength: result.replacementLength,
isIncomplete: result.isIncomplete,
suggestions: result.suggestions,
);
}
Future<CompletionResponseForTesting> _getTestCodeSuggestions(
String content, {
int maxResults = 1 << 10,
}) {
return _getCodeSuggestions(
path: convertPath(testFilePath),
content: content,
maxResults: maxResults,
);
}
RequestWithFutureResponse _sendTestCompletionRequest(String id, int offset) {
var request = CompletionGetSuggestions2Params(
testFile.path,
0,
1 << 10,
).toRequest(id, clientUriConverter: server.uriConverter);
var futureResponse = handleRequest(request);
return RequestWithFutureResponse(offset, request, futureResponse);
}
}
class RequestWithFutureResponse {
final int offset;
final Request request;
final Future<Response> futureResponse;
new(this.offset, this.request, this.futureResponse);
Future<CompletionResponseForTesting> toResponse() async {
var response = await futureResponse;
expect(response, isResponseSuccess(request.id));
var result = CompletionGetSuggestions2Result.fromResponse(
response,
clientUriConverter: null,
);
return CompletionResponseForTesting(
requestOffset: offset,
requestLocationName: null,
opTypeLocationName: null,
replacementOffset: result.replacementOffset,
replacementLength: result.replacementLength,
isIncomplete: result.isIncomplete,
suggestions: result.suggestions,
);
}
}
class _SuggestionDetailsPrinter {
final StringBuffer buffer;
final CompletionGetSuggestionDetails2Result result;
final ResourceProvider resourceProvider;
final Map<File, String> fileDisplayMap;
String _indent = '';
new({
required this.buffer,
required this.result,
required this.resourceProvider,
required this.fileDisplayMap,
});
void writeResult() {
_writelnWithIndent('completion: ${result.completion}');
_withIndent(() {
_writeChange(result.change);
});
}
void _withIndent(void Function() f) {
var indent = _indent;
_indent = '$_indent ';
f();
_indent = indent;
}
void _writeChange(SourceChange change) {
_writelnWithIndent('change');
_withIndent(() {
for (var fileEdit in change.edits) {
_writeSourceFileEdit(fileEdit);
}
});
}
void _writelnWithIndent(String line) {
buffer.write(_indent);
buffer.writeln(line);
}
void _writeSourceEdit(SourceEdit edit) {
_writelnWithIndent('offset: ${edit.offset}');
_writelnWithIndent('length: ${edit.length}');
var replacementStr = edit.replacement
.replaceAll('\n', r'\n')
.replaceAll('\r', r'\r');
_writelnWithIndent('replacement: $replacementStr');
}
void _writeSourceFileEdit(SourceFileEdit fileEdit) {
var file = resourceProvider.getFile(fileEdit.file);
var fileStr = fileDisplayMap[file] ?? fail('No display name: $file');
_writelnWithIndent(fileStr);
_withIndent(() {
for (var edit in fileEdit.edits) {
_writeSourceEdit(edit);
}
});
}
}
extension on CompletionSuggestion {
bool get isClass {
return kind == CompletionSuggestionKind.IDENTIFIER &&
element?.kind == ElementKind.CLASS;
}
}