blob: 0097edba404b43c81b4daf23698dc9f87a007568 [file] [log] [blame]
// Copyright (c) 2016, 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/services/search/search_engine.dart';
import 'package:analysis_server/src/services/search/search_engine_internal.dart';
import 'package:analyzer/dart/analysis/results.dart';
import 'package:analyzer/dart/element/element.dart';
import 'package:analyzer/src/test_utilities/find_element.dart';
import 'package:analyzer/src/test_utilities/find_node.dart';
import 'package:analyzer/src/test_utilities/package_config_file_builder.dart';
import 'package:test/test.dart';
import 'package:test_reflective_loader/test_reflective_loader.dart';
import '../../abstract_context.dart';
void main() {
defineReflectiveSuite(() {
defineReflectiveTests(SearchEngineImplTest);
defineReflectiveTests(SearchEngineImplWithNonFunctionTypeAliasesTest);
});
}
/// TODO(scheglov) This class does not really belong here.
/// Consider merging it into [AbstractContextTest].
class PubPackageResolutionTest extends AbstractContextTest {
late ResolvedUnitResult result;
late FindNode findNode;
late FindElement findElement;
String get testFilePath => '$testPackageLibPath/test.dart';
void addTestFile(String content) {
newFile(testFilePath, content: content);
}
/// Resolve the file with the [path] into [result].
Future<void> resolveFile2(String path) async {
path = convertPath(path);
result = await resolveFile(path);
findNode = FindNode(result.content, result.unit);
findElement = FindElement(result.unit);
}
/// Put the [code] into the test file, and resolve it.
Future<void> resolveTestCode(String code) {
addTestFile(code);
return resolveTestFile();
}
Future<void> resolveTestFile() {
return resolveFile2(testFilePath);
}
}
@reflectiveTest
class SearchEngineImplTest extends PubPackageResolutionTest {
SearchEngineImpl get searchEngine {
return SearchEngineImpl(allDrivers);
}
Future<void> test_membersOfSubtypes_classByClass_hasMembers() async {
newFile('$testPackageLibPath/a.dart', content: '''
class A {
void a() {}
void b() {}
void c() {}
}
''');
newFile('$testPackageLibPath/b.dart', content: '''
import 'a.dart';
class B extends A {
void a() {}
}
''');
newFile('$testPackageLibPath/c.dart', content: '''
import 'a.dart';
class C extends A {
void b() {}
}
''');
await resolveFile2('$testPackageLibPath/a.dart');
var A = findElement.class_('A');
var members = await searchEngine.membersOfSubtypes(A);
expect(members, unorderedEquals(['a', 'b']));
}
Future<void> test_membersOfSubtypes_enum_implements_hasMembers() async {
await resolveTestCode('''
class A {
void foo() {}
}
enum E implements A {
v;
void foo() {}
}
''');
var A = findElement.class_('A');
var members = await searchEngine.membersOfSubtypes(A);
expect(members, unorderedEquals(['foo']));
}
Future<void> test_membersOfSubtypes_enum_with_hasMembers() async {
await resolveTestCode('''
mixin M {
void foo() {}
}
enum E with M {
v;
void foo() {}
}
''');
var M = findElement.mixin('M');
var members = await searchEngine.membersOfSubtypes(M);
expect(members, unorderedEquals(['foo']));
}
Future<void> test_membersOfSubtypes_noMembers() async {
newFile('$testPackageLibPath/a.dart', content: '''
class A {
void a() {}
void b() {}
void c() {}
}
''');
newFile('$testPackageLibPath/b.dart', content: '''
import 'a.dart';
class B extends A {}
''');
await resolveFile2('$testPackageLibPath/a.dart');
var A = findElement.class_('A');
var members = await searchEngine.membersOfSubtypes(A);
expect(members, isEmpty);
}
Future<void> test_membersOfSubtypes_noSubtypes() async {
newFile('$testPackageLibPath/a.dart', content: '''
class A {
void a() {}
void b() {}
void c() {}
}
''');
newFile('$testPackageLibPath/b.dart', content: '''
import 'a.dart';
class B {
void a() {}
}
''');
await resolveFile2('$testPackageLibPath/a.dart');
var A = findElement.class_('A');
var members = await searchEngine.membersOfSubtypes(A);
expect(members, isNull);
}
Future<void> test_membersOfSubtypes_private() async {
newFile('$testPackageLibPath/a.dart', content: '''
class A {
void a() {}
void _b() {}
void _c() {}
}
class B extends A {
void _b() {}
}
''');
newFile('$testPackageLibPath/b.dart', content: '''
import 'a.dart';
class C extends A {
void a() {}
void _c() {}
}
class D extends B {
void _c() {}
}
''');
await resolveFile2('$testPackageLibPath/a.dart');
var A = findElement.class_('A');
var members = await searchEngine.membersOfSubtypes(A);
expect(members, unorderedEquals(['a', '_b']));
}
Future<void> test_searchAllSubtypes() async {
await resolveTestCode('''
class T {}
class A extends T {}
class B extends A {}
class C implements B {}
''');
var element = findElement.class_('T');
var subtypes = await searchEngine.searchAllSubtypes(element);
expect(subtypes, hasLength(3));
_assertContainsClass(subtypes, 'A');
_assertContainsClass(subtypes, 'B');
_assertContainsClass(subtypes, 'C');
}
Future<void> test_searchAllSubtypes_acrossDrivers() async {
var aaaRootPath = _configureForPackage_aaa();
newFile('$aaaRootPath/lib/a.dart', content: '''
class T {}
class A extends T {}
''');
newFile('$testPackageLibPath/b.dart', content: '''
import 'package:aaa/a.dart';
class B extends A {}
class C extends B {}
''');
await resolveFile2('$aaaRootPath/lib/a.dart');
var element = findElement.class_('T');
var subtypes = await searchEngine.searchAllSubtypes(element);
expect(subtypes, hasLength(3));
_assertContainsClass(subtypes, 'A');
_assertContainsClass(subtypes, 'B');
_assertContainsClass(subtypes, 'C');
}
Future<void> test_searchAllSubtypes_mixin() async {
await resolveTestCode('''
class T {}
mixin A on T {}
mixin B implements T {}
class C extends T {}
mixin D on C {}
mixin E implements C {}
''');
var element = findElement.class_('T');
var subtypes = await searchEngine.searchAllSubtypes(element);
expect(subtypes, hasLength(5));
_assertContainsClass(subtypes, 'A');
_assertContainsClass(subtypes, 'B');
_assertContainsClass(subtypes, 'C');
_assertContainsClass(subtypes, 'D');
_assertContainsClass(subtypes, 'E');
}
Future<void> test_searchMemberDeclarations() async {
var codeA = '''
class A {
int test; // 1
int testTwo;
}
''';
var codeB = '''
class B {
void test() {} // 2
void testTwo() {}
}
int test;
''';
newFile('$testPackageLibPath/a.dart', content: codeA);
newFile('$testPackageLibPath/b.dart', content: codeB);
var matches = await searchEngine.searchMemberDeclarations('test');
expect(matches, hasLength(2));
void assertHasElement(String name, int nameOffset) {
expect(
matches,
contains(predicate((SearchMatch m) =>
m.kind == MatchKind.DECLARATION &&
m.element.name == name &&
m.element.nameOffset == nameOffset)));
}
assertHasElement('test', codeA.indexOf('test; // 1'));
assertHasElement('test', codeB.indexOf('test() {} // 2'));
}
Future<void> test_searchMemberReferences() async {
newFile('$testPackageLibPath/a.dart', content: '''
class A {
int test;
}
foo(p) {
p.test;
}
''');
newFile('$testPackageLibPath/b.dart', content: '''
import 'a.dart';
bar(p) {
p.test = 1;
}
''');
var matches = await searchEngine.searchMemberReferences('test');
expect(matches, hasLength(2));
expect(
matches,
contains(predicate((SearchMatch m) =>
m.element.name == 'foo' || m.kind == MatchKind.READ)));
expect(
matches,
contains(predicate((SearchMatch m) =>
m.element.name == 'bar' || m.kind == MatchKind.WRITE)));
}
Future<void> test_searchReferences() async {
var aaaRootPath = _configureForPackage_aaa();
newFile('$aaaRootPath/lib/a.dart', content: '''
class T {}
T a;
''');
await resolveTestCode('''
import 'package:aaa/a.dart';
T b;
''');
var element = findElement.importFind('package:aaa/a.dart').class_('T');
var matches = await searchEngine.searchReferences(element);
expect(matches, hasLength(2));
expect(
matches, contains(predicate((SearchMatch m) => m.element.name == 'a')));
expect(
matches, contains(predicate((SearchMatch m) => m.element.name == 'b')));
}
Future<void> test_searchReferences_discover_owned() async {
var aaaRootPath = _configureForPackage_aaa();
var a = newFile('$aaaRootPath/lib/a.dart', content: '''
int a;
''').path;
var t = newFile('$testPackageLibPath/lib/t.dart', content: '''
import 'package:aaa/a.dart';
int t;
''').path;
var coreLibResult = await driverFor(testFilePath)
.getLibraryByUri('dart:core') as LibraryElementResult;
var intElement = coreLibResult.element.getType('int')!;
var matches = await searchEngine.searchReferences(intElement);
void assertHasOne(String path, String name) {
expect(matches.where((m) {
var element = m.element;
return element.name == name && element.source!.fullName == path;
}), hasLength(1));
}
assertHasOne(t, 't');
assertHasOne(a, 'a');
}
Future<void> test_searchReferences_enum_constructor_named() async {
var code = '''
enum E {
v.named(); // 1
const E.named();
}
''';
await resolveTestCode(code);
var element = findElement.constructor('named');
var matches = await searchEngine.searchReferences(element);
expect(
matches,
unorderedEquals([
predicate((SearchMatch m) {
return m.kind == MatchKind.INVOCATION &&
identical(m.element, findElement.field('v')) &&
m.sourceRange.offset == code.indexOf('.named(); // 1') &&
m.sourceRange.length == '.named'.length;
}),
]),
);
}
Future<void> test_searchReferences_enum_constructor_unnamed() async {
var code = '''
enum E {
v1, // 1
v2(), // 2
v3.new(), // 3
}
''';
await resolveTestCode(code);
var element = findElement.unnamedConstructor('E');
var matches = await searchEngine.searchReferences(element);
expect(
matches,
unorderedEquals([
predicate((SearchMatch m) {
return m.kind ==
MatchKind.INVOCATION_BY_ENUM_CONSTANT_WITHOUT_ARGUMENTS &&
identical(m.element, findElement.field('v1')) &&
m.sourceRange.offset == code.indexOf(', // 1') &&
m.sourceRange.length == 0;
}),
predicate((SearchMatch m) {
return m.kind == MatchKind.INVOCATION &&
identical(m.element, findElement.field('v2')) &&
m.sourceRange.offset == code.indexOf('(), // 2') &&
m.sourceRange.length == 0;
}),
predicate((SearchMatch m) {
return m.kind == MatchKind.INVOCATION &&
identical(m.element, findElement.field('v3')) &&
m.sourceRange.offset == code.indexOf('.new(), // 3') &&
m.sourceRange.length == '.new'.length;
}),
]),
);
}
Future<void>
test_searchReferences_parameter_ofConstructor_super_named() async {
var code = '''
class A {
A({required int a});
}
class B extends A {
B({required super.a}); // ref
}
''';
await resolveTestCode(code);
var element = findElement.unnamedConstructor('A').parameter('a');
var matches = await searchEngine.searchReferences(element);
expect(
matches,
unorderedEquals([
predicate((SearchMatch m) {
return m.kind == MatchKind.REFERENCE &&
identical(
m.element,
findElement.unnamedConstructor('B').superFormalParameter('a'),
) &&
m.sourceRange.offset == code.indexOf('a}); // ref') &&
m.sourceRange.length == 1;
}),
]),
);
}
Future<void>
test_searchReferences_topFunction_parameter_optionalNamed_anywhere() async {
var code = '''
void foo(int a, int b, {int? test}) {}
void g() {
foo(1, test: 0, 2);
}
''';
await resolveTestCode(code);
var element = findElement.parameter('test');
var matches = await searchEngine.searchReferences(element);
expect(
matches,
unorderedEquals([
predicate((SearchMatch m) {
return m.kind == MatchKind.REFERENCE &&
identical(m.element, findElement.topFunction('g')) &&
m.sourceRange.offset == code.indexOf('test: 0') &&
m.sourceRange.length == 'test'.length;
}),
]),
);
}
Future<void> test_searchTopLevelDeclarations() async {
newFile('$testPackageLibPath/a.dart', content: '''
class A {}
int a;
''');
newFile('$testPackageLibPath/b.dart', content: '''
class B {}
get b => 42;
''');
await _ensureContainedFilesKnown();
var matches = await searchEngine.searchTopLevelDeclarations('.*');
matches.removeWhere((match) => match.libraryElement.isInSdk);
expect(matches, hasLength(4));
void assertHasOneElement(String name) {
var nameMatches = matches.where((SearchMatch m) =>
m.kind == MatchKind.DECLARATION && m.element.name == name);
expect(nameMatches, hasLength(1));
}
assertHasOneElement('A');
assertHasOneElement('a');
assertHasOneElement('B');
assertHasOneElement('b');
}
Future<void> test_searchTopLevelDeclarations_dependentPackage() async {
var aaaRootPath = _configureForPackage_aaa();
newFile('$aaaRootPath/lib/a.dart', content: '''
class A {}
''');
// The `package:test` uses the class `A` from the `package:aaa`.
// So it sees the declaration the element `A`.
newFile('$testFilePath', content: '''
import 'package:aaa/a.dart';
class B extends A {}
''');
await _ensureContainedFilesKnown();
var matches = await searchEngine.searchTopLevelDeclarations('.*');
matches.removeWhere((match) => match.libraryElement.isInSdk);
// We get exactly two items: `A` and `B`.
// Specifically, we get exactly one `A`.
expect(matches, hasLength(2));
void assertHasOneElement(String name) {
var nameMatches = matches.where((SearchMatch m) =>
m.kind == MatchKind.DECLARATION && m.element.name == name);
expect(nameMatches, hasLength(1));
}
assertHasOneElement('A');
assertHasOneElement('B');
}
String _configureForPackage_aaa() {
var aaaRootPath = '$workspaceRootPath/aaa';
writePackageConfig(
'$aaaRootPath/.dart_tool/package_config.json',
PackageConfigFileBuilder()..add(name: 'aaa', rootPath: aaaRootPath),
);
writeTestPackageConfig(
config: PackageConfigFileBuilder()
..add(name: 'aaa', rootPath: aaaRootPath),
);
return aaaRootPath;
}
Future _ensureContainedFilesKnown() async {
for (var driver in allDrivers) {
var contextRoot = driver.analysisContext!.contextRoot;
for (var file in contextRoot.analyzedFiles()) {
if (file.endsWith('.dart')) {
await driver.getUnitElement(file);
}
}
}
}
static void _assertContainsClass(Set<ClassElement> subtypes, String name) {
expect(subtypes, contains(predicate((ClassElement e) => e.name == name)));
}
}
@reflectiveTest
class SearchEngineImplWithNonFunctionTypeAliasesTest
extends SearchEngineImplTest {
Future<void> test_searchReferences_typeAlias_interfaceType() async {
await resolveTestCode('''
typedef A<T> = Map<T, String>;
void f(A<int> a, A<double> b) {}
''');
var element = findElement.typeAlias('A');
var matches = await searchEngine.searchReferences(element);
Matcher hasOne(Element element, String search) {
return predicate((SearchMatch match) {
return match.element == element &&
match.sourceRange.offset == findNode.offset(search);
});
}
expect(
matches,
unorderedMatches([
hasOne(findElement.parameter('a'), 'A<int>'),
hasOne(findElement.parameter('b'), 'A<double>'),
]),
);
}
}