69dd787ff0
The purpose of this CL is to clean up test code that references `main` in a way that will be invalid when the tests are converted to analyze the test code under null safety by default. This is the first of three such CLs. In all three I only touched test files that contained an invalid use of `main`, but in any file I touched I removed any use of `main` where the name of the function/method was not critical to the test. Change-Id: Ib753302a4c3ffc7eb5f248e9c6000e80dc77fca9 Reviewed-on: https://dart-review.googlesource.com/c/sdk/+/205062 Reviewed-by: Konstantin Shcheglov <scheglov@google.com> Commit-Queue: Brian Wilkerson <brianwilkerson@google.com>
722 lines
17 KiB
Dart
722 lines
17 KiB
Dart
// Copyright (c) 2020, 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 'package:analysis_server/src/analysis_server.dart';
|
|
import 'package:analysis_server/src/services/completion/dart/utilities.dart';
|
|
import 'package:analyzer/src/test_utilities/resource_provider_mixin.dart';
|
|
import 'package:analyzer_plugin/protocol/protocol_common.dart';
|
|
import 'package:meta/meta.dart';
|
|
import 'package:test/test.dart';
|
|
import 'package:test_reflective_loader/test_reflective_loader.dart';
|
|
|
|
import '../services/completion/dart/completion_contributor_util.dart';
|
|
import 'impl/completion_driver.dart';
|
|
|
|
void main() {
|
|
defineReflectiveSuite(() {
|
|
defineReflectiveTests(BasicCompletionTest);
|
|
defineReflectiveTests(CompletionWithSuggestionsTest);
|
|
});
|
|
}
|
|
|
|
abstract class AbstractCompletionDriverTest with ResourceProviderMixin {
|
|
late CompletionDriver driver;
|
|
Map<String, String> packageRoots = {};
|
|
late List<CompletionSuggestion> suggestions;
|
|
|
|
String get projectName => 'project';
|
|
|
|
String get projectPath => '/$projectName';
|
|
|
|
AnalysisServerOptions get serverOptions => AnalysisServerOptions();
|
|
|
|
bool get supportsAvailableSuggestions;
|
|
|
|
String get testFilePath => '$projectPath/bin/test.dart';
|
|
|
|
Future<void> addProjectFile(String relativePath, String content) async {
|
|
newFile('$projectPath/$relativePath', content: content);
|
|
// todo (pq): handle more than lib
|
|
expect(relativePath, startsWith('lib/'));
|
|
var packageRelativePath = relativePath.substring(4);
|
|
var uriStr = 'package:$projectName/$packageRelativePath';
|
|
await driver.waitForSetWithUri(uriStr);
|
|
}
|
|
|
|
Future<List<CompletionSuggestion>> addTestFile(String content,
|
|
{int? offset}) async {
|
|
driver.addTestFile(content, offset: offset);
|
|
await getSuggestions();
|
|
// For sanity, ensure that there are no errors recorded for project files
|
|
// since that may lead to unexpected results.
|
|
_assertNoErrorsInProjectFiles();
|
|
return suggestions;
|
|
}
|
|
|
|
void assertNoSuggestion({
|
|
required String completion,
|
|
ElementKind? element,
|
|
CompletionSuggestionKind? kind,
|
|
String? file,
|
|
}) {
|
|
expect(
|
|
suggestionsWith(
|
|
completion: completion,
|
|
element: element,
|
|
kind: kind,
|
|
file: file,
|
|
),
|
|
isEmpty);
|
|
}
|
|
|
|
void assertSuggestion({
|
|
bool allowMultiple = false,
|
|
required String completion,
|
|
ElementKind? element,
|
|
CompletionSuggestionKind? kind,
|
|
String? file,
|
|
}) {
|
|
expect(
|
|
suggestionWith(
|
|
allowMultiple: allowMultiple,
|
|
completion: completion,
|
|
element: element,
|
|
kind: kind,
|
|
file: file,
|
|
),
|
|
isNotNull);
|
|
}
|
|
|
|
void assertSuggestions({
|
|
required String completion,
|
|
ElementKind? element,
|
|
CompletionSuggestionKind? kind,
|
|
String? file,
|
|
}) {
|
|
expect(
|
|
suggestionWith(
|
|
completion: completion,
|
|
element: element,
|
|
kind: kind,
|
|
file: file,
|
|
),
|
|
isNotNull);
|
|
}
|
|
|
|
Future<List<CompletionSuggestion>> getSuggestions() async {
|
|
if (supportsAvailableSuggestions) {
|
|
// todo (pq): consider moving
|
|
const internalLibs = [
|
|
'dart:async2',
|
|
'dart:_interceptors',
|
|
'dart:_internal',
|
|
];
|
|
for (var lib in driver.sdk.sdkLibraries) {
|
|
var uri = lib.shortName;
|
|
if (!internalLibs.contains(uri)) {
|
|
await driver.waitForSetWithUri(uri);
|
|
}
|
|
}
|
|
}
|
|
|
|
suggestions = await driver.getSuggestions();
|
|
return suggestions;
|
|
}
|
|
|
|
/// Display sorted suggestions.
|
|
void printSuggestions() {
|
|
suggestions.sort(completionComparator);
|
|
for (var s in suggestions) {
|
|
print(
|
|
'[${s.relevance}] ${s.completion} • ${s.element?.kind.name ?? ""} ${s.kind.name} ${s.element?.location?.file ?? ""}');
|
|
}
|
|
}
|
|
|
|
@mustCallSuper
|
|
void setUp() {
|
|
driver = CompletionDriver(
|
|
supportsAvailableSuggestions: supportsAvailableSuggestions,
|
|
projectPath: projectPath,
|
|
testFilePath: testFilePath,
|
|
resourceProvider: resourceProvider,
|
|
serverOptions: serverOptions,
|
|
);
|
|
driver.createProject(packageRoots: packageRoots);
|
|
|
|
newPubspecYamlFile(projectPath, '');
|
|
newDotPackagesFile(projectPath, content: '''
|
|
project:${toUri('$projectPath/lib')}
|
|
''');
|
|
// todo (pq): add logic (possibly to driver) that waits for SDK suggestions
|
|
}
|
|
|
|
SuggestionMatcher suggestionHas({
|
|
required String completion,
|
|
ElementKind? element,
|
|
CompletionSuggestionKind? kind,
|
|
String? file,
|
|
}) =>
|
|
(CompletionSuggestion s) {
|
|
if (s.completion != completion) {
|
|
return false;
|
|
}
|
|
if (element != null && s.element?.kind != element) {
|
|
return false;
|
|
}
|
|
if (kind != null && s.kind != kind) {
|
|
return false;
|
|
}
|
|
|
|
if (file != null && s.element?.location?.file != convertPath(file)) {
|
|
return false;
|
|
}
|
|
return true;
|
|
};
|
|
|
|
Iterable<CompletionSuggestion> suggestionsWith({
|
|
required String completion,
|
|
ElementKind? element,
|
|
CompletionSuggestionKind? kind,
|
|
String? file,
|
|
}) =>
|
|
suggestions.where(suggestionHas(
|
|
completion: completion, element: element, kind: kind, file: file));
|
|
|
|
CompletionSuggestion suggestionWith({
|
|
bool allowMultiple = false,
|
|
required String completion,
|
|
ElementKind? element,
|
|
CompletionSuggestionKind? kind,
|
|
String? file,
|
|
}) {
|
|
final matches = suggestionsWith(
|
|
completion: completion, element: element, kind: kind, file: file);
|
|
if (!allowMultiple) {
|
|
expect(matches, hasLength(1));
|
|
}
|
|
return matches.first;
|
|
}
|
|
|
|
void _assertNoErrorsInProjectFiles() {
|
|
var errors = <AnalysisError>[];
|
|
driver.filesErrors.forEach((file, fileErrors) {
|
|
// Completion test files are likely to be incomplete and so may have
|
|
// errors
|
|
if (file != convertPath(testFilePath)) {
|
|
errors.addAll(fileErrors);
|
|
}
|
|
});
|
|
expect(errors, isEmpty);
|
|
}
|
|
}
|
|
|
|
@reflectiveTest
|
|
class BasicCompletionTest extends AbstractCompletionDriverTest {
|
|
@override
|
|
bool get supportsAvailableSuggestions => false;
|
|
|
|
/// Duplicates (and potentially replaces DeprecatedMemberRelevanceTest).
|
|
Future<void> test_deprecated_member_relevance() async {
|
|
await addTestFile('''
|
|
class A {
|
|
void a1() { }
|
|
@deprecated
|
|
void a2() { }
|
|
}
|
|
|
|
void f() {
|
|
var a = A();
|
|
a.^
|
|
}
|
|
''');
|
|
|
|
expect(
|
|
suggestionWith(
|
|
completion: 'a2',
|
|
element: ElementKind.METHOD,
|
|
kind: CompletionSuggestionKind.INVOCATION)
|
|
.relevance,
|
|
lessThan(suggestionWith(
|
|
completion: 'a1',
|
|
element: ElementKind.METHOD,
|
|
kind: CompletionSuggestionKind.INVOCATION)
|
|
.relevance));
|
|
}
|
|
}
|
|
|
|
@reflectiveTest
|
|
class CompletionWithSuggestionsTest extends AbstractCompletionDriverTest {
|
|
@override
|
|
bool get supportsAvailableSuggestions => true;
|
|
|
|
@override
|
|
String get testFilePath => '$projectPath/lib/test.dart';
|
|
|
|
Future<void> test_project_filterImports_defaultConstructor() async {
|
|
await addProjectFile('lib/a.dart', r'''
|
|
class A {}
|
|
''');
|
|
|
|
await addProjectFile('lib/b.dart', r'''
|
|
export 'a.dart';
|
|
''');
|
|
|
|
await addTestFile('''
|
|
import 'a.dart';
|
|
void f() {
|
|
^
|
|
}
|
|
''');
|
|
|
|
// TODO(brianwilkerson) There should be a single suggestion here after we
|
|
// figure out how to stop the duplication.
|
|
assertSuggestion(
|
|
allowMultiple: true,
|
|
completion: 'A',
|
|
element: ElementKind.CONSTRUCTOR,
|
|
kind: CompletionSuggestionKind.INVOCATION);
|
|
}
|
|
|
|
/// See: https://github.com/dart-lang/sdk/issues/40620
|
|
Future<void> test_project_filterImports_enumValues() async {
|
|
await addProjectFile('lib/a.dart', r'''
|
|
enum E {
|
|
e,
|
|
}
|
|
''');
|
|
|
|
await addProjectFile('lib/b.dart', r'''
|
|
export 'a.dart';
|
|
''');
|
|
|
|
await addTestFile('''
|
|
import 'a.dart';
|
|
void f() {
|
|
^
|
|
}
|
|
''');
|
|
|
|
// TODO(brianwilkerson) There should be a single suggestion here after we
|
|
// figure out how to stop the duplication.
|
|
assertSuggestion(
|
|
allowMultiple: true,
|
|
completion: 'E.e',
|
|
element: ElementKind.ENUM_CONSTANT,
|
|
kind: CompletionSuggestionKind.INVOCATION);
|
|
}
|
|
|
|
/// See: https://github.com/dart-lang/sdk/issues/40620
|
|
Future<void> test_project_filterImports_namedConstructors() async {
|
|
await addProjectFile('lib/a.dart', r'''
|
|
class A {
|
|
A.a();
|
|
}
|
|
''');
|
|
|
|
await addProjectFile('lib/b.dart', r'''
|
|
export 'a.dart';
|
|
''');
|
|
|
|
await addTestFile('''
|
|
import 'a.dart';
|
|
void f() {
|
|
^
|
|
}
|
|
''');
|
|
|
|
// TODO(brianwilkerson) There should be a single suggestion here after we
|
|
// figure out how to stop the duplication.
|
|
assertSuggestion(
|
|
allowMultiple: true,
|
|
completion: 'A.a',
|
|
element: ElementKind.CONSTRUCTOR,
|
|
kind: CompletionSuggestionKind.INVOCATION);
|
|
}
|
|
|
|
Future<void> test_project_filterMultipleImports() async {
|
|
await addProjectFile('lib/a.dart', r'''
|
|
class A {}
|
|
''');
|
|
|
|
await addProjectFile('lib/b.dart', r'''
|
|
export 'a.dart';
|
|
''');
|
|
|
|
await addTestFile('''
|
|
import 'a.dart';
|
|
import 'b.dart';
|
|
void f() {
|
|
^
|
|
}
|
|
''');
|
|
|
|
assertSuggestion(
|
|
completion: 'A',
|
|
element: ElementKind.CLASS,
|
|
kind: CompletionSuggestionKind.INVOCATION);
|
|
}
|
|
|
|
Future<void> test_project_lib() async {
|
|
await addProjectFile('lib/a.dart', r'''
|
|
class A {}
|
|
enum E {
|
|
e,
|
|
}
|
|
extension Ex on A {}
|
|
mixin M { }
|
|
typedef T = Function(Object);
|
|
var v = 0;
|
|
''');
|
|
|
|
await addTestFile('''
|
|
void f() {
|
|
^
|
|
}
|
|
''');
|
|
|
|
assertSuggestion(
|
|
completion: 'A',
|
|
element: ElementKind.CONSTRUCTOR,
|
|
kind: CompletionSuggestionKind.INVOCATION);
|
|
assertSuggestion(
|
|
completion: 'A',
|
|
element: ElementKind.CLASS,
|
|
kind: CompletionSuggestionKind.INVOCATION);
|
|
assertSuggestion(
|
|
completion: 'E',
|
|
element: ElementKind.ENUM,
|
|
kind: CompletionSuggestionKind.INVOCATION);
|
|
assertSuggestion(
|
|
completion: 'Ex',
|
|
element: ElementKind.EXTENSION,
|
|
kind: CompletionSuggestionKind.INVOCATION);
|
|
assertSuggestion(
|
|
completion: 'M',
|
|
element: ElementKind.MIXIN,
|
|
kind: CompletionSuggestionKind.INVOCATION);
|
|
assertSuggestion(
|
|
completion: 'T',
|
|
element: ElementKind.FUNCTION_TYPE_ALIAS,
|
|
kind: CompletionSuggestionKind.INVOCATION);
|
|
assertSuggestion(
|
|
completion: 'v',
|
|
element: ElementKind.TOP_LEVEL_VARIABLE,
|
|
kind: CompletionSuggestionKind.INVOCATION);
|
|
}
|
|
|
|
Future<void> test_project_lib_fields_class() async {
|
|
await addProjectFile('lib/a.dart', r'''
|
|
class A {
|
|
int f = 0;
|
|
}
|
|
''');
|
|
|
|
await addTestFile('''
|
|
void m() {
|
|
^
|
|
}
|
|
''');
|
|
|
|
assertNoSuggestion(completion: 'f');
|
|
}
|
|
|
|
Future<void> test_project_lib_fields_static() async {
|
|
await addProjectFile('lib/a.dart', r'''
|
|
class A {
|
|
static int f = 0;
|
|
}
|
|
''');
|
|
|
|
await addTestFile('''
|
|
void f() {
|
|
^
|
|
}
|
|
''');
|
|
|
|
assertSuggestion(
|
|
completion: 'A.f',
|
|
element: ElementKind.FIELD,
|
|
kind: CompletionSuggestionKind.INVOCATION);
|
|
}
|
|
|
|
Future<void> test_project_lib_getters_class() async {
|
|
await addProjectFile('lib/a.dart', r'''
|
|
class A {
|
|
int get g => 0;
|
|
}
|
|
''');
|
|
|
|
await addTestFile('''
|
|
void f() {
|
|
^
|
|
}
|
|
''');
|
|
|
|
assertNoSuggestion(completion: 'g');
|
|
}
|
|
|
|
Future<void> test_project_lib_getters_static() async {
|
|
await addProjectFile('lib/a.dart', r'''
|
|
class A {
|
|
static int get g => 0;
|
|
}
|
|
''');
|
|
|
|
await addTestFile('''
|
|
void f() {
|
|
^
|
|
}
|
|
''');
|
|
|
|
assertSuggestion(
|
|
completion: 'A.g',
|
|
element: ElementKind.GETTER,
|
|
kind: CompletionSuggestionKind.INVOCATION);
|
|
}
|
|
|
|
/// See: https://github.com/dart-lang/sdk/issues/40626
|
|
Future<void> test_project_lib_getters_topLevel() async {
|
|
await addProjectFile('lib/a.dart', r'''
|
|
int get g => 0;
|
|
''');
|
|
|
|
await addTestFile('''
|
|
void f() {
|
|
^
|
|
}
|
|
''');
|
|
|
|
assertSuggestion(
|
|
completion: 'g',
|
|
element: ElementKind.GETTER,
|
|
kind: CompletionSuggestionKind.INVOCATION);
|
|
}
|
|
|
|
@failingTest
|
|
Future<void> test_project_lib_multipleExports() async {
|
|
await addProjectFile('lib/a.dart', r'''
|
|
class A {}
|
|
''');
|
|
|
|
await addProjectFile('lib/b.dart', r'''
|
|
export 'a.dart';
|
|
''');
|
|
|
|
await addTestFile('''
|
|
void f() {
|
|
^
|
|
}
|
|
''');
|
|
|
|
// Should only have one suggestion.
|
|
assertSuggestion(
|
|
completion: 'A',
|
|
element: ElementKind.CONSTRUCTOR,
|
|
kind: CompletionSuggestionKind.INVOCATION);
|
|
}
|
|
|
|
Future<void> test_project_lib_setters_class() async {
|
|
await addProjectFile('lib/a.dart', r'''
|
|
class A {
|
|
set s(int s) {}
|
|
}
|
|
''');
|
|
|
|
await addTestFile('''
|
|
void f() {
|
|
^
|
|
}
|
|
''');
|
|
|
|
assertNoSuggestion(completion: 's');
|
|
}
|
|
|
|
Future<void> test_project_lib_setters_static() async {
|
|
await addProjectFile('lib/a.dart', r'''
|
|
class A {
|
|
static set g(int g) {}
|
|
}
|
|
''');
|
|
|
|
await addTestFile('''
|
|
void f() {
|
|
^
|
|
}
|
|
''');
|
|
|
|
assertNoSuggestion(completion: 'A.g');
|
|
}
|
|
|
|
/// See: https://github.com/dart-lang/sdk/issues/40626
|
|
Future<void> test_project_lib_setters_topLevel() async {
|
|
await addProjectFile('lib/a.dart', r'''
|
|
set s(int s) {}
|
|
''');
|
|
|
|
await addTestFile('''
|
|
void f() {
|
|
^
|
|
}
|
|
''');
|
|
|
|
assertSuggestion(
|
|
completion: 's',
|
|
element: ElementKind.SETTER,
|
|
kind: CompletionSuggestionKind.INVOCATION);
|
|
}
|
|
|
|
@FailingTest(issue: 'https://github.com/dart-lang/sdk/issues/38739')
|
|
Future<void>
|
|
test_project_suggestionRelevance_constructorParameterType() async {
|
|
await addProjectFile('lib/a.dart', r'''
|
|
import 'b.dart';
|
|
|
|
class A {
|
|
A.b({O o});
|
|
}
|
|
''');
|
|
|
|
await addProjectFile('lib/b.dart', r'''
|
|
class O { }
|
|
''');
|
|
|
|
await addTestFile('''
|
|
import 'a.dart';
|
|
|
|
void f(List<String> args) {
|
|
var a = A.b(o: ^)
|
|
}
|
|
''');
|
|
|
|
expect(
|
|
suggestionWith(
|
|
completion: 'O',
|
|
element: ElementKind.CONSTRUCTOR,
|
|
kind: CompletionSuggestionKind.INVOCATION)
|
|
.relevance,
|
|
greaterThan(suggestionWith(
|
|
completion: 'args',
|
|
element: ElementKind.PARAMETER,
|
|
kind: CompletionSuggestionKind.INVOCATION)
|
|
.relevance));
|
|
}
|
|
|
|
Future<void> test_project_suggestionRelevance_constructorsAndTypes() async {
|
|
await addProjectFile('lib/a.dart', r'''
|
|
class A { }
|
|
''');
|
|
|
|
await addTestFile('''
|
|
import 'a.dart';
|
|
|
|
void f(List<String> args) {
|
|
var a = ^
|
|
}
|
|
''');
|
|
|
|
expect(
|
|
suggestionWith(
|
|
completion: 'A',
|
|
element: ElementKind.CONSTRUCTOR,
|
|
kind: CompletionSuggestionKind.INVOCATION)
|
|
.relevance,
|
|
greaterThan(suggestionWith(
|
|
completion: 'A',
|
|
element: ElementKind.CLASS,
|
|
kind: CompletionSuggestionKind.INVOCATION)
|
|
.relevance));
|
|
}
|
|
|
|
/// See: https://github.com/dart-lang/sdk/issues/35529
|
|
Future<void> test_project_suggestMixins() async {
|
|
await addProjectFile('lib/a.dart', r'''
|
|
mixin M { }
|
|
class A { }
|
|
''');
|
|
|
|
await addTestFile('''
|
|
class C extends Object with ^
|
|
''');
|
|
|
|
assertSuggestion(
|
|
completion: 'M',
|
|
element: ElementKind.MIXIN,
|
|
kind: CompletionSuggestionKind.INVOCATION);
|
|
assertSuggestion(
|
|
completion: 'A',
|
|
element: ElementKind.CLASS,
|
|
kind: CompletionSuggestionKind.INVOCATION);
|
|
}
|
|
|
|
Future<void> test_sdk_lib_future_isNotDuplicated() async {
|
|
await addTestFile('''
|
|
void f() {
|
|
^
|
|
}
|
|
''');
|
|
|
|
// TODO(brianwilkerson) There should be a single suggestion here after we
|
|
// figure out how to stop the duplication.
|
|
expect(
|
|
suggestionsWith(
|
|
completion: 'Future.value',
|
|
file: '/sdk/lib/async/async.dart',
|
|
element: ElementKind.CONSTRUCTOR,
|
|
kind: CompletionSuggestionKind.INVOCATION),
|
|
hasLength(2));
|
|
}
|
|
|
|
Future<void> test_sdk_lib_suggestions() async {
|
|
await addTestFile('''
|
|
void f() {
|
|
^
|
|
}
|
|
''');
|
|
|
|
// A kind-filtered set of SDK suggestions.
|
|
|
|
// Constructors should be visible.
|
|
assertSuggestion(
|
|
completion: 'Timer',
|
|
file: '/sdk/lib/async/async.dart',
|
|
element: ElementKind.CONSTRUCTOR,
|
|
kind: CompletionSuggestionKind.INVOCATION);
|
|
|
|
// But not methods.
|
|
assertNoSuggestion(
|
|
// dart:async (StreamSubscription)
|
|
completion: 'asFuture',
|
|
element: ElementKind.METHOD,
|
|
kind: CompletionSuggestionKind.INVOCATION);
|
|
|
|
// + Functions.
|
|
assertSuggestion(
|
|
completion: 'print',
|
|
file: '/sdk/lib/core/core.dart',
|
|
element: ElementKind.FUNCTION,
|
|
kind: CompletionSuggestionKind.INVOCATION);
|
|
assertSuggestion(
|
|
completion: 'tan',
|
|
file: '/sdk/lib/math/math.dart',
|
|
element: ElementKind.FUNCTION,
|
|
kind: CompletionSuggestionKind.INVOCATION);
|
|
|
|
// + Classes.
|
|
assertSuggestion(
|
|
completion: 'HashMap',
|
|
file: '/sdk/lib/collection/collection.dart',
|
|
element: ElementKind.CLASS,
|
|
kind: CompletionSuggestionKind.INVOCATION);
|
|
|
|
// + Top level variables.
|
|
assertSuggestion(
|
|
completion: 'pi',
|
|
file: '/sdk/lib/math/math.dart',
|
|
element: ElementKind.TOP_LEVEL_VARIABLE,
|
|
kind: CompletionSuggestionKind.INVOCATION);
|
|
|
|
// (No typedefs, enums, extensions defined in the Mock SDK.)
|
|
}
|
|
}
|