| // 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); |
| } |
| |
| /// 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', ''' |
| class A { |
| void a() {} |
| void b() {} |
| void c() {} |
| } |
| '''); |
| |
| newFile('$testPackageLibPath/b.dart', ''' |
| import 'a.dart'; |
| class B extends A { |
| void a() {} |
| } |
| '''); |
| |
| newFile('$testPackageLibPath/c.dart', ''' |
| 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', ''' |
| class A { |
| void a() {} |
| void b() {} |
| void c() {} |
| } |
| '''); |
| |
| newFile('$testPackageLibPath/b.dart', ''' |
| 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', ''' |
| class A { |
| void a() {} |
| void b() {} |
| void c() {} |
| } |
| '''); |
| |
| newFile('$testPackageLibPath/b.dart', ''' |
| 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', ''' |
| class A { |
| void a() {} |
| void _b() {} |
| void _c() {} |
| } |
| class B extends A { |
| void _b() {} |
| } |
| '''); |
| |
| newFile('$testPackageLibPath/b.dart', ''' |
| 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', ''' |
| class T {} |
| class A extends T {} |
| '''); |
| |
| newFile('$testPackageLibPath/b.dart', ''' |
| 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', codeA); |
| newFile('$testPackageLibPath/b.dart', 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', ''' |
| class A { |
| int test; |
| } |
| foo(p) { |
| p.test; |
| } |
| '''); |
| |
| newFile('$testPackageLibPath/b.dart', ''' |
| 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', ''' |
| 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', ''' |
| int a; |
| ''').path; |
| |
| var t = newFile('$testPackageLibPath/lib/t.dart', ''' |
| import 'package:aaa/a.dart'; |
| int t; |
| ''').path; |
| |
| var coreLibResult = await driverFor(testFilePath) |
| .getLibraryByUri('dart:core') as LibraryElementResult; |
| var intElement = coreLibResult.element.getClass('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', ''' |
| class A {} |
| int a; |
| '''); |
| |
| newFile('$testPackageLibPath/b.dart', ''' |
| 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', ''' |
| class A {} |
| '''); |
| |
| // The `package:test` uses the class `A` from the `package:aaa`. |
| // So it sees the declaration the element `A`. |
| newFile(testFilePath, ''' |
| 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<InterfaceElement> subtypes, String name) { |
| expect( |
| subtypes, |
| contains( |
| predicate((InterfaceElement 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>'), |
| ]), |
| ); |
| } |
| } |