| // 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 'dart:async'; |
| |
| 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/element/element.dart'; |
| import 'package:analyzer/src/dart/analysis/driver.dart'; |
| import 'package:analyzer/src/dart/analysis/file_state.dart'; |
| import 'package:analyzer/src/file_system/file_system.dart'; |
| import 'package:analyzer/src/generated/engine.dart'; |
| import 'package:analyzer/src/generated/sdk.dart'; |
| import 'package:analyzer/src/generated/source.dart'; |
| import 'package:analyzer/src/source/package_map_resolver.dart'; |
| import 'package:analyzer/src/test_utilities/resource_provider_mixin.dart'; |
| import 'package:analyzer/src/dart/analysis/byte_store.dart'; |
| import 'package:analyzer/src/dart/analysis/performance_logger.dart'; |
| import 'package:test/test.dart'; |
| import 'package:test_reflective_loader/test_reflective_loader.dart'; |
| |
| import '../../mock_sdk.dart'; |
| |
| main() { |
| defineReflectiveSuite(() { |
| defineReflectiveTests(SearchEngineImplTest); |
| }); |
| } |
| |
| @reflectiveTest |
| class SearchEngineImplTest extends Object with ResourceProviderMixin { |
| DartSdk sdk; |
| final ByteStore byteStore = new MemoryByteStore(); |
| final FileContentOverlay contentOverlay = new FileContentOverlay(); |
| |
| final StringBuffer logBuffer = new StringBuffer(); |
| PerformanceLog logger; |
| |
| AnalysisDriverScheduler scheduler; |
| |
| void setUp() { |
| sdk = new MockSdk(resourceProvider: resourceProvider); |
| logger = new PerformanceLog(logBuffer); |
| scheduler = new AnalysisDriverScheduler(logger); |
| scheduler.start(); |
| } |
| |
| test_membersOfSubtypes_hasMembers() async { |
| var a = newFile('/test/a.dart', content: ''' |
| class A { |
| void a() {} |
| void b() {} |
| void c() {} |
| } |
| ''').path; |
| var b = newFile('/test/b.dart', content: ''' |
| import 'a.dart'; |
| class B extends A { |
| void a() {} |
| } |
| ''').path; |
| var c = newFile('/test/c.dart', content: ''' |
| import 'a.dart'; |
| class C extends A { |
| void b() {} |
| } |
| ''').path; |
| |
| var driver1 = _newDriver(); |
| var driver2 = _newDriver(); |
| |
| driver1.addFile(a); |
| driver2.addFile(b); |
| driver2.addFile(c); |
| await scheduler.waitForIdle(); |
| |
| var resultA = await driver1.getResult(a); |
| ClassElement elementA = resultA.unit.declaredElement.types[0]; |
| |
| var searchEngine = new SearchEngineImpl([driver1, driver2]); |
| Set<String> members = await searchEngine.membersOfSubtypes(elementA); |
| expect(members, unorderedEquals(['a', 'b'])); |
| } |
| |
| test_membersOfSubtypes_noMembers() async { |
| var a = newFile('/test/a.dart', content: ''' |
| class A { |
| void a() {} |
| void b() {} |
| void c() {} |
| } |
| ''').path; |
| var b = newFile('/test/b.dart', content: ''' |
| import 'a.dart'; |
| class B extends A {} |
| ''').path; |
| |
| var driver = _newDriver(); |
| |
| driver.addFile(a); |
| driver.addFile(b); |
| await scheduler.waitForIdle(); |
| |
| var resultA = await driver.getResult(a); |
| ClassElement elementA = resultA.unit.declaredElement.types[0]; |
| |
| var searchEngine = new SearchEngineImpl([driver]); |
| Set<String> members = await searchEngine.membersOfSubtypes(elementA); |
| expect(members, isEmpty); |
| } |
| |
| test_membersOfSubtypes_noSubtypes() async { |
| var a = newFile('/test/a.dart', content: ''' |
| class A { |
| void a() {} |
| void b() {} |
| void c() {} |
| } |
| ''').path; |
| var b = newFile('/test/b.dart', content: ''' |
| import 'a.dart'; |
| class B { |
| void a() {} |
| } |
| ''').path; |
| |
| var driver = _newDriver(); |
| |
| driver.addFile(a); |
| driver.addFile(b); |
| await scheduler.waitForIdle(); |
| |
| var resultA = await driver.getResult(a); |
| ClassElement elementA = resultA.unit.declaredElement.types[0]; |
| |
| var searchEngine = new SearchEngineImpl([driver]); |
| Set<String> members = await searchEngine.membersOfSubtypes(elementA); |
| expect(members, isNull); |
| } |
| |
| test_membersOfSubtypes_private() async { |
| var a = newFile('/test/a.dart', content: ''' |
| class A { |
| void a() {} |
| void _b() {} |
| void _c() {} |
| } |
| class B extends A { |
| void _b() {} |
| } |
| ''').path; |
| var b = newFile('/test/b.dart', content: ''' |
| import 'a.dart'; |
| class C extends A { |
| void a() {} |
| void _c() {} |
| } |
| class D extends B { |
| void _c() {} |
| } |
| ''').path; |
| |
| var driver1 = _newDriver(); |
| var driver2 = _newDriver(); |
| |
| driver1.addFile(a); |
| driver2.addFile(b); |
| await scheduler.waitForIdle(); |
| |
| var resultA = await driver1.getResult(a); |
| ClassElement elementA = resultA.unit.declaredElement.types[0]; |
| |
| var searchEngine = new SearchEngineImpl([driver1, driver2]); |
| Set<String> members = await searchEngine.membersOfSubtypes(elementA); |
| expect(members, unorderedEquals(['a', '_b'])); |
| } |
| |
| test_searchAllSubtypes() async { |
| var p = newFile('/test.dart', content: ''' |
| class T {} |
| class A extends T {} |
| class B extends A {} |
| class C implements B {} |
| ''').path; |
| |
| var driver = _newDriver(); |
| driver.addFile(p); |
| |
| var resultA = await driver.getResult(p); |
| ClassElement element = resultA.unit.declaredElement.types[0]; |
| |
| var searchEngine = new SearchEngineImpl([driver]); |
| Set<ClassElement> subtypes = await searchEngine.searchAllSubtypes(element); |
| expect(subtypes, hasLength(3)); |
| _assertContainsClass(subtypes, 'A'); |
| _assertContainsClass(subtypes, 'B'); |
| _assertContainsClass(subtypes, 'C'); |
| } |
| |
| test_searchAllSubtypes_acrossDrivers() async { |
| var a = newFile('/test/a.dart', content: ''' |
| class T {} |
| class A extends T {} |
| ''').path; |
| var b = newFile('/test/b.dart', content: ''' |
| import 'a.dart'; |
| class B extends A {} |
| class C extends B {} |
| ''').path; |
| |
| var driver1 = _newDriver(); |
| var driver2 = _newDriver(); |
| |
| driver1.addFile(a); |
| driver2.addFile(b); |
| |
| var resultA = await driver1.getResult(a); |
| ClassElement element = resultA.unit.declaredElement.types[0]; |
| |
| var searchEngine = new SearchEngineImpl([driver1, driver2]); |
| Set<ClassElement> subtypes = await searchEngine.searchAllSubtypes(element); |
| expect(subtypes, hasLength(3)); |
| expect(subtypes, contains(predicate((ClassElement e) => e.name == 'A'))); |
| expect(subtypes, contains(predicate((ClassElement e) => e.name == 'B'))); |
| expect(subtypes, contains(predicate((ClassElement e) => e.name == 'C'))); |
| } |
| |
| test_searchAllSubtypes_mixin() async { |
| var p = newFile('/test.dart', content: ''' |
| class T {} |
| |
| mixin A on T {} |
| mixin B implements T {} |
| |
| class C extends T {} |
| |
| mixin D on C {} |
| mixin E implements C {} |
| ''').path; |
| |
| var driver = _newDriver(); |
| driver.addFile(p); |
| |
| var resultA = await driver.getResult(p); |
| ClassElement element = resultA.unit.declaredElement.types[0]; |
| |
| var searchEngine = new SearchEngineImpl([driver]); |
| Set<ClassElement> subtypes = await searchEngine.searchAllSubtypes(element); |
| expect(subtypes, hasLength(5)); |
| _assertContainsClass(subtypes, 'A'); |
| _assertContainsClass(subtypes, 'B'); |
| _assertContainsClass(subtypes, 'C'); |
| _assertContainsClass(subtypes, 'D'); |
| _assertContainsClass(subtypes, 'E'); |
| } |
| |
| test_searchMemberDeclarations() async { |
| var codeA = ''' |
| class A { |
| int test; // 1 |
| int testTwo; |
| } |
| '''; |
| var codeB = ''' |
| class B { |
| void test() {} // 2 |
| void testTwo() {} |
| } |
| int test; |
| '''; |
| |
| var a = newFile('/test/a.dart', content: codeA).path; |
| var b = newFile('/test/b.dart', content: codeB).path; |
| |
| var driver1 = _newDriver(); |
| var driver2 = _newDriver(); |
| |
| driver1.addFile(a); |
| driver2.addFile(b); |
| |
| while (scheduler.isAnalyzing) { |
| await new Future.delayed(new Duration(milliseconds: 1)); |
| } |
| |
| var searchEngine = new SearchEngineImpl([driver1, driver2]); |
| List<SearchMatch> 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')); |
| } |
| |
| test_searchMemberReferences() async { |
| var a = newFile('/test/a.dart', content: ''' |
| class A { |
| int test; |
| } |
| foo(p) { |
| p.test; |
| } |
| ''').path; |
| var b = newFile('/test/b.dart', content: ''' |
| import 'a.dart'; |
| bar(p) { |
| p.test = 1; |
| } |
| ''').path; |
| |
| var driver1 = _newDriver(); |
| var driver2 = _newDriver(); |
| |
| driver1.addFile(a); |
| driver2.addFile(b); |
| |
| var searchEngine = new SearchEngineImpl([driver1, driver2]); |
| List<SearchMatch> 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))); |
| } |
| |
| test_searchReferences() async { |
| var a = newFile('/test/a.dart', content: ''' |
| class T {} |
| T a; |
| ''').path; |
| var b = newFile('/test/b.dart', content: ''' |
| import 'a.dart'; |
| T b; |
| ''').path; |
| |
| var driver1 = _newDriver(); |
| var driver2 = _newDriver(); |
| |
| driver1.addFile(a); |
| driver2.addFile(b); |
| |
| var resultA = await driver1.getResult(a); |
| ClassElement element = resultA.unit.declaredElement.types[0]; |
| |
| var searchEngine = new SearchEngineImpl([driver1, driver2]); |
| List<SearchMatch> 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'))); |
| } |
| |
| test_searchReferences_discover_owned() async { |
| var t = newFile('/test/lib/t.dart', content: ''' |
| import 'package:aaa/a.dart'; |
| int t; |
| ''').path; |
| var a = newFile('/aaa/lib/a.dart', content: ''' |
| int a; |
| ''').path; |
| |
| var driver1 = _newDriver(packageUriResolver: _mapUriResolver('aaa', a)); |
| var driver2 = _newDriver(); |
| |
| driver1.addFile(t); |
| driver2.addFile(a); |
| |
| LibraryElement coreLib = await driver1.getLibraryByUri('dart:core'); |
| ClassElement intElement = coreLib.getType('int'); |
| |
| var searchEngine = new SearchEngineImpl([driver1, driver2]); |
| 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'); |
| } |
| |
| test_searchTopLevelDeclarations() async { |
| var a = newFile('/test/a.dart', content: ''' |
| class A {} |
| int a; |
| ''').path; |
| var b = newFile('/test/b.dart', content: ''' |
| class B {} |
| get b => 42; |
| ''').path; |
| |
| var driver1 = _newDriver(); |
| var driver2 = _newDriver(); |
| |
| driver1.addFile(a); |
| driver2.addFile(b); |
| |
| while (scheduler.isAnalyzing) { |
| await new Future.delayed(new Duration(milliseconds: 1)); |
| } |
| |
| var searchEngine = new SearchEngineImpl([driver1, driver2]); |
| var matches = await searchEngine.searchTopLevelDeclarations('.*'); |
| matches.removeWhere((match) => match.libraryElement.isInSdk); |
| expect(matches, hasLength(4)); |
| |
| void assertHasOneElement(String name) { |
| Iterable<SearchMatch> 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'); |
| } |
| |
| test_searchTopLevelDeclarations_dependentPackage() async { |
| var a = newFile('/a/lib/a.dart', content: ''' |
| class A {} |
| ''').path; |
| var driver1 = _newDriver(); |
| driver1.addFile(a); |
| |
| // The package:b uses the class A from the package:a, |
| // so it sees the declaration the element A. |
| var b = newFile('/b/lib/b.dart', content: ''' |
| import 'package:a/a.dart'; |
| class B extends A {} |
| '''); |
| var driver2 = _newDriver(packageUriResolver: _mapUriResolver('a', a)); |
| driver2.addFile(b.path); |
| |
| while (scheduler.isAnalyzing) { |
| await new Future.delayed(new Duration(milliseconds: 1)); |
| } |
| |
| var searchEngine = new SearchEngineImpl([driver1, driver2]); |
| List<SearchMatch> matches = |
| await searchEngine.searchTopLevelDeclarations('.*'); |
| // We get exactly two items: A and B. |
| // I.e. we get exactly one A. |
| expect( |
| matches.where((match) => !match.libraryElement.isInSdk), hasLength(2)); |
| |
| void assertHasOneElement(String name) { |
| Iterable<SearchMatch> nameMatches = matches.where((SearchMatch m) => |
| m.kind == MatchKind.DECLARATION && m.element.name == name); |
| expect(nameMatches, hasLength(1)); |
| } |
| |
| assertHasOneElement('A'); |
| assertHasOneElement('B'); |
| } |
| |
| UriResolver _mapUriResolver(String packageName, String path) { |
| return new PackageMapUriResolver(resourceProvider, { |
| packageName: [resourceProvider.getFile(path).parent] |
| }); |
| } |
| |
| AnalysisDriver _newDriver({UriResolver packageUriResolver}) { |
| var resolvers = <UriResolver>[ |
| new DartUriResolver(sdk), |
| new ResourceUriResolver(resourceProvider) |
| ]; |
| if (packageUriResolver != null) { |
| resolvers.add(packageUriResolver); |
| } |
| resolvers.add(new ResourceUriResolver(resourceProvider)); |
| |
| return new AnalysisDriver( |
| scheduler, |
| logger, |
| resourceProvider, |
| byteStore, |
| contentOverlay, |
| null, |
| new SourceFactory(resolvers, null, resourceProvider), |
| new AnalysisOptionsImpl()); |
| } |
| |
| static void _assertContainsClass(Set<ClassElement> subtypes, String name) { |
| expect(subtypes, contains(predicate((ClassElement e) => e.name == name))); |
| } |
| } |