| // Copyright (c) 2022, the Dart project authors. Please see the FooUTHORS file |
| // for details. Fooll 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/lsp_protocol/protocol_generated.dart'; |
| import 'package:test/test.dart'; |
| import 'package:test_reflective_loader/test_reflective_loader.dart'; |
| |
| import 'server_abstract.dart'; |
| |
| void main() { |
| defineReflectiveSuite(() { |
| defineReflectiveTests(PrepareCallHierarchyTest); |
| defineReflectiveTests(IncomingCallHierarchyTest); |
| defineReflectiveTests(OutgoingCallHierarchyTest); |
| }); |
| } |
| |
| @reflectiveTest |
| class IncomingCallHierarchyTest extends AbstractLspAnalysisServerTest { |
| late final Uri otherFileUri; |
| |
| /// Calls textDocument/prepareCallHierarchy at the location of `^` in |
| /// [mainContents] and uses the single result to call |
| /// `callHierarchy/incomingCalls` and ensures the results match |
| /// [expectedResults]. |
| Future<void> expectResults({ |
| required String mainContents, |
| String? otherContents, |
| required List<CallHierarchyIncomingCall> expectedResults, |
| }) async { |
| await initialize(); |
| await openFile(mainFileUri, withoutMarkers(mainContents)); |
| |
| if (otherContents != null) { |
| await openFile(otherFileUri, withoutMarkers(otherContents)); |
| } |
| |
| final prepareResult = await prepareCallHierarchy( |
| mainFileUri, |
| positionFromMarker(mainContents), |
| ); |
| final result = await callHierarchyIncoming(prepareResult!.single); |
| |
| expect(result!, unorderedEquals(expectedResults)); |
| } |
| |
| @override |
| void setUp() { |
| super.setUp(); |
| otherFileUri = Uri.file(join(projectFolderPath, 'lib', 'other.dart')); |
| } |
| |
| Future<void> test_constructor() async { |
| final contents = ''' |
| class Foo { |
| Fo^o(); |
| } |
| '''; |
| |
| final otherContents = ''' |
| import 'main.dart'; |
| |
| class Bar { |
| final foo = Foo(); |
| } |
| '''; |
| |
| await expectResults( |
| mainContents: contents, |
| otherContents: otherContents, |
| expectedResults: [ |
| CallHierarchyIncomingCall( |
| // Container of the call |
| from: CallHierarchyItem( |
| name: 'Bar', |
| detail: 'other.dart', |
| kind: SymbolKind.Class, |
| uri: otherFileUri.toString(), |
| range: rangeOfPattern( |
| otherContents, RegExp(r'class Bar \{.*\}', dotAll: true)), |
| selectionRange: rangeOfString(otherContents, 'Bar'), |
| ), |
| // Ranges of calls within this container |
| fromRanges: [ |
| rangeOfString(otherContents, 'Foo'), |
| ], |
| ), |
| ], |
| ); |
| } |
| |
| Future<void> test_function() async { |
| final contents = ''' |
| String fo^o() {} |
| '''; |
| |
| final otherContents = ''' |
| import 'main.dart'; |
| |
| final x = foo(); |
| '''; |
| |
| await expectResults( |
| mainContents: contents, |
| otherContents: otherContents, |
| expectedResults: [ |
| CallHierarchyIncomingCall( |
| // Container of the call |
| from: CallHierarchyItem( |
| name: 'other.dart', |
| detail: null, |
| kind: SymbolKind.File, |
| uri: otherFileUri.toString(), |
| range: entireRange(otherContents), |
| selectionRange: startOfDocRange, |
| ), |
| // Ranges of calls within this container |
| fromRanges: [ |
| rangeOfString(otherContents, 'foo'), |
| ], |
| ), |
| ], |
| ); |
| } |
| |
| Future<void> test_implicitConstructor() async { |
| final contents = ''' |
| import 'other.dart'; |
| |
| void main() { |
| final foo = Fo^o(); |
| } |
| '''; |
| |
| final otherContents = ''' |
| class Foo {} |
| '''; |
| |
| await expectResults( |
| mainContents: contents, |
| otherContents: otherContents, |
| expectedResults: [ |
| CallHierarchyIncomingCall( |
| // Container of the call |
| from: CallHierarchyItem( |
| name: 'main', |
| detail: 'main.dart', |
| kind: SymbolKind.Function, |
| uri: mainFileUri.toString(), |
| range: rangeOfPattern( |
| contents, RegExp(r'void main\(\) \{.*\}', dotAll: true)), |
| selectionRange: rangeOfString(contents, 'main'), |
| ), |
| // Ranges of calls within this container |
| fromRanges: [ |
| rangeOfString(contents, 'Foo'), |
| ], |
| ), |
| ], |
| ); |
| } |
| |
| Future<void> test_method() async { |
| final contents = ''' |
| class A { |
| String fo^o() {} |
| } |
| '''; |
| |
| final otherContents = ''' |
| import 'main.dart'; |
| |
| class B { |
| String bar() { |
| A().foo(); |
| } |
| } |
| '''; |
| |
| await expectResults( |
| mainContents: contents, |
| otherContents: otherContents, |
| expectedResults: [ |
| CallHierarchyIncomingCall( |
| // Container of the call |
| from: CallHierarchyItem( |
| name: 'bar', |
| detail: 'B', |
| kind: SymbolKind.Method, |
| uri: otherFileUri.toString(), |
| range: rangeOfPattern(otherContents, |
| RegExp(r'String bar\(\) \{.*\ }', dotAll: true)), |
| selectionRange: rangeOfString(otherContents, 'bar'), |
| ), |
| // Ranges of calls within this container |
| fromRanges: [ |
| rangeOfString(otherContents, 'foo'), |
| ], |
| ), |
| ], |
| ); |
| } |
| |
| Future<void> test_namedConstructor() async { |
| final contents = ''' |
| class Foo { |
| Foo.nam^ed(); |
| } |
| '''; |
| |
| final otherContents = ''' |
| import 'main.dart'; |
| |
| class Bar { |
| final foo = Foo.named(); |
| } |
| '''; |
| |
| await expectResults( |
| mainContents: contents, |
| otherContents: otherContents, |
| expectedResults: [ |
| CallHierarchyIncomingCall( |
| // Container of the call |
| from: CallHierarchyItem( |
| name: 'Bar', |
| detail: 'other.dart', |
| kind: SymbolKind.Class, |
| uri: otherFileUri.toString(), |
| range: rangeOfPattern( |
| otherContents, RegExp(r'class Bar \{.*\}', dotAll: true)), |
| selectionRange: rangeOfString(otherContents, 'Bar'), |
| ), |
| // Ranges of calls within this container |
| fromRanges: [ |
| rangeOfString(otherContents, 'named'), |
| ], |
| ), |
| ], |
| ); |
| } |
| } |
| |
| @reflectiveTest |
| class OutgoingCallHierarchyTest extends AbstractLspAnalysisServerTest { |
| late final Uri otherFileUri; |
| |
| /// Calls textDocument/prepareCallHierarchy at the location of `^` in |
| /// [mainContents] and uses the single result to call |
| /// `callHierarchy/outgoingCalls` and ensures the results match |
| /// [expectedResults]. |
| Future<void> expectResults({ |
| required String mainContents, |
| String? otherContents, |
| required List<CallHierarchyOutgoingCall> expectedResults, |
| }) async { |
| await initialize(); |
| await openFile(mainFileUri, withoutMarkers(mainContents)); |
| |
| if (otherContents != null) { |
| await openFile(otherFileUri, withoutMarkers(otherContents)); |
| } |
| |
| final prepareResult = await prepareCallHierarchy( |
| mainFileUri, |
| positionFromMarker(mainContents), |
| ); |
| final result = await callHierarchyOutgoing(prepareResult!.single); |
| |
| expect(result!, unorderedEquals(expectedResults)); |
| } |
| |
| @override |
| void setUp() { |
| super.setUp(); |
| otherFileUri = Uri.file(join(projectFolderPath, 'lib', 'other.dart')); |
| } |
| |
| Future<void> test_constructor() async { |
| final contents = ''' |
| import 'other.dart'; |
| |
| class Foo { |
| Fo^o() { |
| final b = Bar(); |
| } |
| } |
| '''; |
| |
| final otherContents = ''' |
| class Bar { |
| Bar(); |
| } |
| '''; |
| |
| await expectResults( |
| mainContents: contents, |
| otherContents: otherContents, |
| expectedResults: [ |
| CallHierarchyOutgoingCall( |
| // Target of the call. |
| to: CallHierarchyItem( |
| name: 'Bar', |
| detail: 'Bar', |
| kind: SymbolKind.Constructor, |
| uri: otherFileUri.toString(), |
| range: rangeOfString(otherContents, 'Bar();'), |
| selectionRange: |
| rangeStartingAtString(otherContents, 'Bar();', 'Bar'), |
| ), |
| // Ranges of the outbound call. |
| fromRanges: [ |
| rangeOfString(contents, 'Bar'), |
| ], |
| ), |
| ], |
| ); |
| } |
| |
| Future<void> test_function() async { |
| final contents = ''' |
| import 'other.dart'; |
| |
| void fo^o() { |
| bar(); |
| } |
| '''; |
| |
| final otherContents = ''' |
| void bar() {} |
| '''; |
| |
| await expectResults( |
| mainContents: contents, |
| otherContents: otherContents, |
| expectedResults: [ |
| CallHierarchyOutgoingCall( |
| // Target of the call. |
| to: CallHierarchyItem( |
| name: 'bar', |
| detail: 'other.dart', |
| kind: SymbolKind.Function, |
| uri: otherFileUri.toString(), |
| range: rangeOfString(otherContents, 'void bar() {}'), |
| selectionRange: rangeOfString(otherContents, 'bar'), |
| ), |
| // Ranges of the outbound call. |
| fromRanges: [ |
| rangeOfString(contents, 'bar'), |
| ], |
| ), |
| ], |
| ); |
| } |
| |
| Future<void> test_implicitConstructor() async { |
| final contents = ''' |
| import 'other.dart'; |
| |
| class Foo { |
| Fo^o() { |
| final b = Bar(); |
| } |
| } |
| '''; |
| |
| final otherContents = ''' |
| class Bar {} |
| '''; |
| |
| await expectResults( |
| mainContents: contents, |
| otherContents: otherContents, |
| expectedResults: [ |
| CallHierarchyOutgoingCall( |
| // Target of the call. |
| to: CallHierarchyItem( |
| name: 'Bar', |
| detail: 'Bar', |
| kind: SymbolKind.Constructor, |
| uri: otherFileUri.toString(), |
| range: rangeOfString(otherContents, 'class Bar {}'), |
| selectionRange: rangeOfString(otherContents, 'Bar'), |
| ), |
| // Ranges of the outbound call. |
| fromRanges: [ |
| rangeOfString(contents, 'Bar'), |
| ], |
| ), |
| ], |
| ); |
| } |
| |
| Future<void> test_method() async { |
| final contents = ''' |
| import 'other.dart'; |
| |
| class Foo { |
| final b = Bar(); |
| void f^oo() { |
| b.bar(); |
| } |
| } |
| '''; |
| |
| final otherContents = ''' |
| class Bar { |
| void bar() {} |
| } |
| '''; |
| |
| await expectResults( |
| mainContents: contents, |
| otherContents: otherContents, |
| expectedResults: [ |
| CallHierarchyOutgoingCall( |
| // Target of the call. |
| to: CallHierarchyItem( |
| name: 'bar', |
| detail: 'Bar', |
| kind: SymbolKind.Method, |
| uri: otherFileUri.toString(), |
| range: rangeOfString(otherContents, 'void bar() {}'), |
| selectionRange: rangeOfString(otherContents, 'bar'), |
| ), |
| // Ranges of the outbound call. |
| fromRanges: [ |
| rangeOfString(contents, 'bar'), |
| ], |
| ), |
| ], |
| ); |
| } |
| |
| Future<void> test_namedConstructor() async { |
| final contents = ''' |
| import 'other.dart'; |
| |
| class Foo { |
| Foo.nam^ed() { |
| final b = Bar.named(); |
| } |
| } |
| '''; |
| |
| final otherContents = ''' |
| class Bar { |
| Bar.named(); |
| } |
| '''; |
| |
| await expectResults( |
| mainContents: contents, |
| otherContents: otherContents, |
| expectedResults: [ |
| CallHierarchyOutgoingCall( |
| // Target of the call. |
| to: CallHierarchyItem( |
| name: 'Bar.named', |
| detail: 'Bar', |
| kind: SymbolKind.Constructor, |
| uri: otherFileUri.toString(), |
| range: rangeOfString(otherContents, 'Bar.named();'), |
| selectionRange: rangeOfString(otherContents, 'named'), |
| ), |
| // Ranges of the outbound call. |
| fromRanges: [ |
| rangeStartingAtString(contents, 'named();', 'named'), |
| ], |
| ), |
| ], |
| ); |
| } |
| } |
| |
| @reflectiveTest |
| class PrepareCallHierarchyTest extends AbstractLspAnalysisServerTest { |
| late final Uri otherFileUri; |
| |
| /// Calls textDocument/prepareCallHierarchy at the location of `^` in |
| /// [mainContents] and expects a null result. |
| Future<void> expectNullResults(String mainContents) async { |
| await initialize(); |
| await openFile(mainFileUri, withoutMarkers(mainContents)); |
| final result = await prepareCallHierarchy( |
| mainFileUri, |
| positionFromMarker(mainContents), |
| ); |
| expect(result, isNull); |
| } |
| |
| /// Calls textDocument/prepareCallHierarchy at the location of `^` in |
| /// [mainContents] and ensures the results match [expectedResults]. |
| Future<void> expectResults({ |
| required String mainContents, |
| String? otherContents, |
| required CallHierarchyItem expectedResult, |
| }) async { |
| await initialize(); |
| await openFile(mainFileUri, withoutMarkers(mainContents)); |
| |
| if (otherContents != null) { |
| await openFile(otherFileUri, withoutMarkers(otherContents)); |
| } |
| |
| final results = await prepareCallHierarchy( |
| mainFileUri, |
| positionFromMarker(mainContents), |
| ); |
| |
| expect(results, isNotNull); |
| expect(results!, hasLength(1)); |
| expect(results.single, expectedResult); |
| } |
| |
| @override |
| void setUp() { |
| super.setUp(); |
| otherFileUri = Uri.file(join(projectFolderPath, 'lib', 'other.dart')); |
| } |
| |
| Future<void> test_args() async { |
| await expectNullResults('main(int ^a) {}'); |
| } |
| |
| Future<void> test_block() async { |
| await expectNullResults('main() {^}'); |
| } |
| |
| Future<void> test_comment() async { |
| await expectNullResults('main() {} // this is a ^comment'); |
| } |
| |
| Future<void> test_constructor() async { |
| final contents = ''' |
| class Foo { |
| [[Fo^o]](String a) {} |
| } |
| '''; |
| |
| await expectResults( |
| mainContents: contents, |
| expectedResult: CallHierarchyItem( |
| name: 'Foo', |
| detail: 'Foo', // Containing class name |
| kind: SymbolKind.Constructor, |
| uri: mainFileUri.toString(), |
| range: rangeOfString(contents, 'Foo(String a) {}'), |
| selectionRange: rangeFromMarkers(contents)), |
| ); |
| } |
| |
| Future<void> test_constructorCall() async { |
| final contents = ''' |
| import 'other.dart'; |
| |
| main() { |
| final foo = Fo^o(); |
| } |
| '''; |
| |
| final otherContents = ''' |
| class Foo { |
| [[Foo]](); |
| } |
| '''; |
| |
| await expectResults( |
| mainContents: contents, |
| otherContents: otherContents, |
| expectedResult: CallHierarchyItem( |
| name: 'Foo', |
| detail: 'Foo', // Containing class name |
| kind: SymbolKind.Constructor, |
| uri: otherFileUri.toString(), |
| range: rangeOfString(otherContents, 'Foo();'), |
| selectionRange: rangeFromMarkers(otherContents))); |
| } |
| |
| Future<void> test_function() async { |
| final contents = ''' |
| void myFun^ction() {} |
| '''; |
| |
| await expectResults( |
| mainContents: contents, |
| expectedResult: CallHierarchyItem( |
| name: 'myFunction', |
| detail: 'main.dart', // Containing file name |
| kind: SymbolKind.Function, |
| uri: mainFileUri.toString(), |
| range: rangeOfString(contents, 'void myFunction() {}'), |
| selectionRange: rangeOfString(contents, 'myFunction')), |
| ); |
| } |
| |
| Future<void> test_functionCall() async { |
| final contents = ''' |
| import 'other.dart' as f; |
| |
| main() { |
| f.myFun^ction(); |
| } |
| '''; |
| |
| final otherContents = ''' |
| void myFunction() {} |
| '''; |
| |
| await expectResults( |
| mainContents: contents, |
| otherContents: otherContents, |
| expectedResult: CallHierarchyItem( |
| name: 'myFunction', |
| detail: 'other.dart', // Containing file name |
| kind: SymbolKind.Function, |
| uri: otherFileUri.toString(), |
| range: rangeOfString(otherContents, 'void myFunction() {}'), |
| selectionRange: rangeOfString(otherContents, 'myFunction')), |
| ); |
| } |
| |
| Future<void> test_implicitConstructorCall() async { |
| // Even if a constructor is implicit, we might want to be able to get the |
| // incoming calls, so invoking it here should still return an element |
| // (the class). |
| final contents = ''' |
| import 'other.dart'; |
| |
| main() { |
| final foo = Fo^o(); |
| } |
| '''; |
| |
| final otherContents = ''' |
| class Foo {} |
| '''; |
| |
| await expectResults( |
| mainContents: contents, |
| otherContents: otherContents, |
| expectedResult: CallHierarchyItem( |
| name: 'Foo', |
| detail: 'Foo', // Containing class name |
| kind: SymbolKind.Constructor, |
| uri: otherFileUri.toString(), |
| range: rangeOfString(otherContents, 'class Foo {}'), |
| selectionRange: rangeOfString(otherContents, 'Foo')), |
| ); |
| } |
| |
| Future<void> test_method() async { |
| final contents = ''' |
| class Foo { |
| void myMet^hod() {} |
| } |
| '''; |
| |
| await expectResults( |
| mainContents: contents, |
| expectedResult: CallHierarchyItem( |
| name: 'myMethod', |
| detail: 'Foo', // Containing class name |
| kind: SymbolKind.Method, |
| uri: mainFileUri.toString(), |
| range: rangeOfString(contents, 'void myMethod() {}'), |
| selectionRange: rangeOfString(contents, 'myMethod')), |
| ); |
| } |
| |
| Future<void> test_methodCall() async { |
| final contents = ''' |
| import 'other.dart'; |
| |
| main() { |
| Foo().myMet^hod(); |
| } |
| '''; |
| |
| final otherContents = ''' |
| class Foo { |
| void myMethod() {} |
| } |
| '''; |
| |
| await expectResults( |
| mainContents: contents, |
| otherContents: otherContents, |
| expectedResult: CallHierarchyItem( |
| name: 'myMethod', |
| detail: 'Foo', // Containing class name |
| kind: SymbolKind.Method, |
| uri: otherFileUri.toString(), |
| range: rangeOfString(otherContents, 'void myMethod() {}'), |
| selectionRange: rangeOfString(otherContents, 'myMethod')), |
| ); |
| } |
| |
| Future<void> test_namedConstructor() async { |
| final contents = ''' |
| class Foo { |
| Foo.Ba^r(String a) {} |
| } |
| '''; |
| |
| await expectResults( |
| mainContents: contents, |
| expectedResult: CallHierarchyItem( |
| name: 'Foo.Bar', |
| detail: 'Foo', // Containing class name |
| kind: SymbolKind.Constructor, |
| uri: mainFileUri.toString(), |
| range: rangeOfString(contents, 'Foo.Bar(String a) {}'), |
| selectionRange: rangeOfString(contents, 'Bar')), |
| ); |
| } |
| |
| Future<void> test_namedConstructorCall() async { |
| final contents = ''' |
| import 'other.dart'; |
| |
| main() { |
| final foo = Foo.Ba^r(); |
| } |
| '''; |
| |
| final otherContents = ''' |
| class Foo { |
| Foo.Bar(); |
| } |
| '''; |
| |
| await expectResults( |
| mainContents: contents, |
| otherContents: otherContents, |
| expectedResult: CallHierarchyItem( |
| name: 'Foo.Bar', |
| detail: 'Foo', // Containing class name |
| kind: SymbolKind.Constructor, |
| uri: otherFileUri.toString(), |
| range: rangeOfString(otherContents, 'Foo.Bar();'), |
| selectionRange: rangeOfString(otherContents, 'Bar')), |
| ); |
| } |
| |
| Future<void> test_whitespace() async { |
| await expectNullResults(' ^ main() {}'); |
| } |
| } |