blob: 40b4d2e1407e14785ff2871cbca9f146f22de0df [file] [log] [blame]
// Copyright (c) 2022, 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:analyzer/src/test_utilities/test_code_format.dart';
import 'package:language_server_protocol/protocol_generated.dart';
import 'package:test/test.dart';
import 'package:test_reflective_loader/test_reflective_loader.dart';
import '../utils/test_code_extensions.dart';
import 'server_abstract.dart';
void main() {
defineReflectiveSuite(() {
defineReflectiveTests(PrepareCallHierarchyTest);
defineReflectiveTests(IncomingCallHierarchyTest);
defineReflectiveTests(OutgoingCallHierarchyTest);
});
}
@reflectiveTest
class IncomingCallHierarchyTest extends AbstractLspAnalysisServerTest {
late final String otherFilePath;
late final Uri otherFileUri;
/// Calls textDocument/prepareCallHierarchy at the location of `^` in
/// [maincode.code] and uses the single result to call
/// `callHierarchy/incomingCalls` and ensures the results match
/// [expectedResults].
Future<void> expectResults({
required TestCode mainCode,
TestCode? otherCode,
required List<CallHierarchyIncomingCall> expectedResults,
}) async {
newFile(mainFilePath, mainCode.code);
if (otherCode != null) {
newFile(otherFilePath, otherCode.code);
}
await initialize();
var prepareResult = await prepareCallHierarchy(
mainFileUri,
mainCode.position.position,
);
var result = await callHierarchyIncoming(prepareResult!.single);
expect(result!, unorderedEquals(expectedResults));
}
@override
void setUp() {
super.setUp();
otherFilePath = join(projectFolderPath, 'lib', 'other.dart');
otherFileUri = toUri(otherFilePath);
}
Future<void> test_constructor() async {
var code = TestCode.parse('''
class Foo {
Fo^o();
}
''');
var otherCode = TestCode.parse('''
import 'main.dart';
class Bar {
final foo = Foo();
}
''');
await expectResults(
mainCode: code,
otherCode: otherCode,
expectedResults: [
CallHierarchyIncomingCall(
// Container of the call
from: CallHierarchyItem(
name: 'Bar',
detail: 'other.dart',
kind: SymbolKind.Class,
uri: otherFileUri,
range: rangeOfPattern(
otherCode, RegExp(r'class Bar \{.*\}', dotAll: true)),
selectionRange: rangeOfString(otherCode, 'Bar'),
),
// Ranges of calls within this container
fromRanges: [
rangeOfString(otherCode, 'Foo'),
],
),
],
);
}
Future<void> test_function() async {
var code = TestCode.parse('''
void fo^o() {}
''');
var otherCode = TestCode.parse('''
import 'main.dart';
final x = foo();
''');
await expectResults(
mainCode: code,
otherCode: otherCode,
expectedResults: [
CallHierarchyIncomingCall(
// Container of the call
from: CallHierarchyItem(
name: 'other.dart',
kind: SymbolKind.File,
uri: otherFileUri,
range: entireRange(otherCode.code),
selectionRange: startOfDocRange,
),
// Ranges of calls within this container
fromRanges: [
rangeOfString(otherCode, 'foo'),
],
),
],
);
}
Future<void> test_functionInPattern() async {
var code = TestCode.parse('''
bool gr^eater(int x, int y) => x > y;
''');
var otherCode = TestCode.parse('''
import 'main.dart';
void foo() {
var pair = (1, 2);
switch (pair) {
case (int a, int b) when greater(a, b):
print('First element');
}
}
''');
await expectResults(
mainCode: code,
otherCode: otherCode,
expectedResults: [
CallHierarchyIncomingCall(
// Container of the call
from: CallHierarchyItem(
name: 'foo',
detail: 'other.dart',
kind: SymbolKind.Function,
uri: otherFileUri,
range: rangeOfPattern(
otherCode, RegExp(r'void foo\(\) \{.*\}', dotAll: true)),
selectionRange: rangeOfString(otherCode, 'foo'),
),
// Ranges of calls within this container.
fromRanges: [
rangeOfString(otherCode, 'greater'),
],
),
],
);
}
Future<void> test_implicitConstructor() async {
var code = TestCode.parse('''
import 'other.dart';
void main() {
final foo = Fo^o();
}
''');
var otherCode = TestCode.parse('''
class Foo {}
''');
await expectResults(
mainCode: code,
otherCode: otherCode,
expectedResults: [
CallHierarchyIncomingCall(
// Container of the call
from: CallHierarchyItem(
name: 'main',
detail: 'main.dart',
kind: SymbolKind.Function,
uri: mainFileUri,
range: rangeOfPattern(
code, RegExp(r'void main\(\) \{.*\}', dotAll: true)),
selectionRange: rangeOfString(code, 'main'),
),
// Ranges of calls within this container
fromRanges: [
rangeOfString(code, 'Foo'),
],
),
],
);
}
Future<void> test_method() async {
var code = TestCode.parse('''
class A {
void fo^o() {}
}
''');
var otherCode = TestCode.parse('''
import 'main.dart';
class B {
void bar() {
A().foo();
}
}
''');
await expectResults(
mainCode: code,
otherCode: otherCode,
expectedResults: [
CallHierarchyIncomingCall(
// Container of the call
from: CallHierarchyItem(
name: 'bar',
detail: 'B',
kind: SymbolKind.Method,
uri: otherFileUri,
range: rangeOfPattern(
otherCode, RegExp(r'void bar\(\) \{.*\ }', dotAll: true)),
selectionRange: rangeOfString(otherCode, 'bar'),
),
// Ranges of calls within this container
fromRanges: [
rangeOfString(otherCode, 'foo'),
],
),
],
);
}
Future<void> test_method_extension() async {
var code = TestCode.parse('''
extension type E1(int a) {
void foo^() {}
}
''');
var otherCode = TestCode.parse('''
import 'main.dart';
extension type E2(E1 a) {
void g() {
a.foo();
}
}
''');
await expectResults(
mainCode: code,
otherCode: otherCode,
expectedResults: [
CallHierarchyIncomingCall(
// Container of the call
from: CallHierarchyItem(
name: 'g',
detail: 'E2',
kind: SymbolKind.Method,
uri: otherFileUri,
range: rangeOfPattern(
otherCode, RegExp(r'void g\(\) \{.*\ }', dotAll: true)),
selectionRange: rangeOfString(otherCode, 'g'),
),
// Ranges of calls within this container
fromRanges: [
rangeOfString(otherCode, 'foo'),
],
),
],
);
}
Future<void> test_namedConstructor() async {
var code = TestCode.parse('''
class Foo {
Foo.nam^ed();
}
''');
var otherCode = TestCode.parse('''
import 'main.dart';
class Bar {
final foo = Foo.named();
}
''');
await expectResults(
mainCode: code,
otherCode: otherCode,
expectedResults: [
CallHierarchyIncomingCall(
// Container of the call
from: CallHierarchyItem(
name: 'Bar',
detail: 'other.dart',
kind: SymbolKind.Class,
uri: otherFileUri,
range: rangeOfPattern(
otherCode, RegExp(r'class Bar \{.*\}', dotAll: true)),
selectionRange: rangeOfString(otherCode, 'Bar'),
),
// Ranges of calls within this container
fromRanges: [
rangeOfString(otherCode, 'named'),
],
),
],
);
}
}
@reflectiveTest
class OutgoingCallHierarchyTest extends AbstractLspAnalysisServerTest {
late final String otherFilePath;
late final Uri otherFileUri;
/// Calls textDocument/prepareCallHierarchy at the location of `^` in
/// [maincode.code] and uses the single result to call
/// `callHierarchy/outgoingCalls` and ensures the results match
/// [expectedResults].
Future<void> expectResults({
required TestCode mainCode,
TestCode? otherCode,
required List<CallHierarchyOutgoingCall> expectedResults,
}) async {
newFile(mainFilePath, mainCode.code);
if (otherCode != null) {
newFile(otherFilePath, otherCode.code);
}
await initialize();
var prepareResult = await prepareCallHierarchy(
mainFileUri,
mainCode.position.position,
);
var result = await callHierarchyOutgoing(prepareResult!.single);
expect(result!, unorderedEquals(expectedResults));
}
@override
void setUp() {
super.setUp();
otherFilePath = join(projectFolderPath, 'lib', 'other.dart');
otherFileUri = toUri(otherFilePath);
}
Future<void> test_constructor() async {
var code = TestCode.parse('''
import 'other.dart';
class Foo {
Fo^o() {
final b = Bar();
}
}
''');
var otherCode = TestCode.parse('''
class Bar {
Bar();
}
''');
await expectResults(
mainCode: code,
otherCode: otherCode,
expectedResults: [
CallHierarchyOutgoingCall(
// Target of the call.
to: CallHierarchyItem(
name: 'Bar',
detail: 'Bar',
kind: SymbolKind.Constructor,
uri: otherFileUri,
range: rangeOfString(otherCode, 'Bar();'),
selectionRange:
rangeStartingAtString(otherCode.code, 'Bar();', 'Bar'),
),
// Ranges of the outbound call.
fromRanges: [
rangeOfString(code, 'Bar'),
],
),
],
);
}
Future<void> test_function() async {
var code = TestCode.parse('''
import 'other.dart';
void fo^o() {
bar();
}
''');
var otherCode = TestCode.parse('''
void bar() {}
''');
await expectResults(
mainCode: code,
otherCode: otherCode,
expectedResults: [
CallHierarchyOutgoingCall(
// Target of the call.
to: CallHierarchyItem(
name: 'bar',
detail: 'other.dart',
kind: SymbolKind.Function,
uri: otherFileUri,
range: rangeOfString(otherCode, 'void bar() {}'),
selectionRange: rangeOfString(otherCode, 'bar'),
),
// Ranges of the outbound call.
fromRanges: [
rangeOfString(code, 'bar'),
],
),
],
);
}
Future<void> test_functionInPattern() async {
var code = TestCode.parse('''
import 'other.dart';
void fo^o() {
var pair = (1, 2);
switch (pair) {
case (int a, int b) when greater(a, b):
break;
}
}
''');
var otherCode = TestCode.parse('''
bool greater(int x, int y) => x > y;
''');
await expectResults(
mainCode: code,
otherCode: otherCode,
expectedResults: [
CallHierarchyOutgoingCall(
// Target of the call.
to: CallHierarchyItem(
name: 'greater',
detail: 'other.dart',
kind: SymbolKind.Function,
uri: otherFileUri,
range: rangeOfString(
otherCode, 'bool greater(int x, int y) => x > y;'),
selectionRange: rangeOfString(otherCode, 'greater'),
),
// Ranges of the outbound call.
fromRanges: [
rangeOfString(code, 'greater'),
],
),
],
);
}
Future<void> test_implicitConstructor() async {
var code = TestCode.parse('''
import 'other.dart';
class Foo {
Fo^o() {
final b = Bar();
}
}
''');
var otherCode = TestCode.parse('''
class Bar {}
''');
await expectResults(
mainCode: code,
otherCode: otherCode,
expectedResults: [
CallHierarchyOutgoingCall(
// Target of the call.
to: CallHierarchyItem(
name: 'Bar',
detail: 'Bar',
kind: SymbolKind.Constructor,
uri: otherFileUri,
range: rangeOfString(otherCode, 'class Bar {}'),
selectionRange: rangeOfString(otherCode, 'Bar'),
),
// Ranges of the outbound call.
fromRanges: [
rangeOfString(code, 'Bar'),
],
),
],
);
}
Future<void> test_method() async {
var code = TestCode.parse('''
import 'other.dart';
class Foo {
final b = Bar();
void f^oo() {
b.bar();
}
}
''');
var otherCode = TestCode.parse('''
class Bar {
void bar() {}
}
''');
await expectResults(
mainCode: code,
otherCode: otherCode,
expectedResults: [
CallHierarchyOutgoingCall(
// Target of the call.
to: CallHierarchyItem(
name: 'bar',
detail: 'Bar',
kind: SymbolKind.Method,
uri: otherFileUri,
range: rangeOfString(otherCode, 'void bar() {}'),
selectionRange: rangeOfString(otherCode, 'bar'),
),
// Ranges of the outbound call.
fromRanges: [
rangeOfString(code, 'bar'),
],
),
],
);
}
Future<void> test_method_extensionType() async {
var code = TestCode.parse('''
import 'other.dart';
extension type E2(E1 a) {
void g^() {
a.foo();
}
}
''');
var otherCode = TestCode.parse('''
extension type E1(int a) {
void foo() {}
}
''');
await expectResults(
mainCode: code,
otherCode: otherCode,
expectedResults: [
CallHierarchyOutgoingCall(
// Target of the call.
to: CallHierarchyItem(
name: 'foo',
detail: 'E1',
kind: SymbolKind.Method,
uri: otherFileUri,
range: rangeOfString(otherCode, 'void foo() {}'),
selectionRange: rangeOfString(otherCode, 'foo'),
),
// Ranges of the outbound call.
fromRanges: [
rangeOfString(code, 'foo'),
],
),
],
);
}
Future<void> test_namedConstructor() async {
var code = TestCode.parse('''
import 'other.dart';
class Foo {
Foo.nam^ed() {
final b = Bar.named();
}
}
''');
var otherCode = TestCode.parse('''
class Bar {
Bar.named();
}
''');
await expectResults(
mainCode: code,
otherCode: otherCode,
expectedResults: [
CallHierarchyOutgoingCall(
// Target of the call.
to: CallHierarchyItem(
name: 'Bar.named',
detail: 'Bar',
kind: SymbolKind.Constructor,
uri: otherFileUri,
range: rangeOfString(otherCode, 'Bar.named();'),
selectionRange: rangeOfString(otherCode, 'named'),
),
// Ranges of the outbound call.
fromRanges: [
rangeStartingAtString(code.code, 'named();', 'named'),
],
),
],
);
}
}
@reflectiveTest
class PrepareCallHierarchyTest extends AbstractLspAnalysisServerTest {
late final String otherFilePath;
late final Uri otherFileUri;
/// Calls textDocument/prepareCallHierarchy at the location of `^` in
/// [contents] and expects a null result.
Future<void> expectNullResults(String contents) async {
var code = TestCode.parse(contents);
newFile(mainFilePath, code.code);
await initialize();
var result = await prepareCallHierarchy(
mainFileUri,
code.position.position,
);
expect(result, isNull);
}
/// Calls textDocument/prepareCallHierarchy at the location of `^` in
/// [mainCode.code] and ensures the results match [expectedResults].
Future<void> expectResults({
required TestCode mainCode,
TestCode? otherCode,
required CallHierarchyItem expectedResult,
}) async {
newFile(mainFilePath, mainCode.code);
if (otherCode != null) {
newFile(otherFilePath, otherCode.code);
}
await initialize();
var results = await prepareCallHierarchy(
mainFileUri,
mainCode.position.position,
);
expect(results, isNotNull);
expect(results!, hasLength(1));
expect(results.single, expectedResult);
}
@override
void setUp() {
super.setUp();
otherFilePath = join(projectFolderPath, 'lib', 'other.dart');
otherFileUri = toUri(otherFilePath);
}
Future<void> test_args() async {
await expectNullResults('f(int ^a) {}');
}
Future<void> test_block() async {
await expectNullResults('f() {^}');
}
Future<void> test_comment() async {
await expectNullResults('f() {} // this is a ^comment');
}
Future<void> test_constructor() async {
var code = TestCode.parse('''
class Foo {
[!Fo^o!](String a) {}
}
''');
await expectResults(
mainCode: code,
expectedResult: CallHierarchyItem(
name: 'Foo',
detail: 'Foo', // Containing class name
kind: SymbolKind.Constructor,
uri: mainFileUri,
range: rangeOfString(code, 'Foo(String a) {}'),
selectionRange: code.range.range),
);
}
Future<void> test_constructorCall() async {
var mainCode = TestCode.parse('''
import 'other.dart';
main() {
final foo = Fo^o();
}
''');
var otherCode = TestCode.parse('''
class Foo {
[!Foo!]();
}
''');
await expectResults(
mainCode: mainCode,
otherCode: otherCode,
expectedResult: CallHierarchyItem(
name: 'Foo',
detail: 'Foo', // Containing class name
kind: SymbolKind.Constructor,
uri: otherFileUri,
range: rangeOfString(otherCode, 'Foo();'),
selectionRange: otherCode.range.range));
}
Future<void> test_function() async {
var code = TestCode.parse('''
void myFun^ction() {}
''');
await expectResults(
mainCode: code,
expectedResult: CallHierarchyItem(
name: 'myFunction',
detail: 'main.dart', // Containing file name
kind: SymbolKind.Function,
uri: mainFileUri,
range: rangeOfString(code, 'void myFunction() {}'),
selectionRange: rangeOfString(code, 'myFunction')),
);
}
Future<void> test_functionCall() async {
var code = TestCode.parse('''
import 'other.dart' as f;
main() {
f.myFun^ction();
}
''');
var otherCode = TestCode.parse('''
void myFunction() {}
''');
await expectResults(
mainCode: code,
otherCode: otherCode,
expectedResult: CallHierarchyItem(
name: 'myFunction',
detail: 'other.dart', // Containing file name
kind: SymbolKind.Function,
uri: otherFileUri,
range: rangeOfString(otherCode, 'void myFunction() {}'),
selectionRange: rangeOfString(otherCode, '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).
var code = TestCode.parse('''
import 'other.dart';
main() {
final foo = Fo^o();
}
''');
var otherCode = TestCode.parse('''
class Foo {}
''');
await expectResults(
mainCode: code,
otherCode: otherCode,
expectedResult: CallHierarchyItem(
name: 'Foo',
detail: 'Foo', // Containing class name
kind: SymbolKind.Constructor,
uri: otherFileUri,
range: rangeOfString(otherCode, 'class Foo {}'),
selectionRange: rangeOfString(otherCode, 'Foo')),
);
}
Future<void> test_method() async {
var code = TestCode.parse('''
class Foo {
void myMet^hod() {}
}
''');
await expectResults(
mainCode: code,
expectedResult: CallHierarchyItem(
name: 'myMethod',
detail: 'Foo', // Containing class name
kind: SymbolKind.Method,
uri: mainFileUri,
range: rangeOfString(code, 'void myMethod() {}'),
selectionRange: rangeOfString(code, 'myMethod')),
);
}
Future<void> test_methodCall() async {
var code = TestCode.parse('''
import 'other.dart';
main() {
Foo().myMet^hod();
}
''');
var otherCode = TestCode.parse('''
class Foo {
void myMethod() {}
}
''');
await expectResults(
mainCode: code,
otherCode: otherCode,
expectedResult: CallHierarchyItem(
name: 'myMethod',
detail: 'Foo', // Containing class name
kind: SymbolKind.Method,
uri: otherFileUri,
range: rangeOfString(otherCode, 'void myMethod() {}'),
selectionRange: rangeOfString(otherCode, 'myMethod')),
);
}
Future<void> test_methodCall_extension() async {
var code = TestCode.parse('''
import 'other.dart';
void main() {
E1(1).f^();
}
''');
var otherCode = TestCode.parse('''
extension type E1(int a) {
void f() {}
}
''');
await expectResults(
mainCode: code,
otherCode: otherCode,
expectedResult: CallHierarchyItem(
name: 'f',
detail: 'E1',
kind: SymbolKind.Method,
uri: otherFileUri,
range: rangeOfString(otherCode, 'void f() {}'),
selectionRange: rangeOfString(otherCode, 'f')),
);
}
Future<void> test_namedConstructor() async {
var code = TestCode.parse('''
class Foo {
Foo.Ba^r(String a) {}
}
''');
await expectResults(
mainCode: code,
expectedResult: CallHierarchyItem(
name: 'Foo.Bar',
detail: 'Foo', // Containing class name
kind: SymbolKind.Constructor,
uri: mainFileUri,
range: rangeOfString(code, 'Foo.Bar(String a) {}'),
selectionRange: rangeOfString(code, 'Bar')),
);
}
Future<void> test_namedConstructorCall() async {
var code = TestCode.parse('''
import 'other.dart';
main() {
final foo = Foo.Ba^r();
}
''');
var otherCode = TestCode.parse('''
class Foo {
Foo.Bar();
}
''');
await expectResults(
mainCode: code,
otherCode: otherCode,
expectedResult: CallHierarchyItem(
name: 'Foo.Bar',
detail: 'Foo', // Containing class name
kind: SymbolKind.Constructor,
uri: otherFileUri,
range: rangeOfString(otherCode, 'Foo.Bar();'),
selectionRange: rangeOfString(otherCode, 'Bar')),
);
}
Future<void> test_whitespace() async {
await expectNullResults(' ^ main() {}');
}
}