| // 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:analysis_server/src/computer/computer_call_hierarchy.dart'; |
| import 'package:analysis_server/src/services/search/search_engine.dart'; |
| import 'package:analysis_server/src/services/search/search_engine_internal.dart'; |
| import 'package:analyzer/source/source_range.dart'; |
| import 'package:analyzer/src/test_utilities/test_code_format.dart'; |
| import 'package:matcher/expect.dart'; |
| import 'package:test/test.dart'; |
| import 'package:test_reflective_loader/test_reflective_loader.dart'; |
| |
| import '../../abstract_single_unit.dart'; |
| |
| void main() { |
| defineReflectiveSuite(() { |
| defineReflectiveTests(CallHierarchyComputerFindTargetTest); |
| defineReflectiveTests(CallHierarchyComputerIncomingCallsTest); |
| defineReflectiveTests(CallHierarchyComputerOutgoingCallsTest); |
| }); |
| } |
| |
| /// Matches a [CallHierarchyItem] with the given name/kind/file. |
| Matcher _isItem( |
| CallHierarchyKind kind, |
| String displayName, |
| String file, { |
| required String? containerName, |
| required SourceRange nameRange, |
| required SourceRange codeRange, |
| }) => TypeMatcher<CallHierarchyItem>() |
| .having((e) => e.kind, 'kind', kind) |
| .having((e) => e.displayName, 'displayName', displayName) |
| .having((e) => e.containerName, 'containerName', containerName) |
| .having((e) => e.file, 'file', file) |
| .having((e) => e.nameRange, 'nameRange', nameRange) |
| .having((e) => e.codeRange, 'codeRange', codeRange); |
| |
| /// Matches a [CallHierarchyCalls] result with the given element/ranges. |
| Matcher _isResult( |
| CallHierarchyKind kind, |
| String displayName, |
| String file, { |
| required String? containerName, |
| required SourceRange nameRange, |
| required SourceRange codeRange, |
| List<SourceRange>? ranges, |
| }) { |
| var matcher = TypeMatcher<CallHierarchyCalls>().having( |
| (c) => c.item, |
| 'item', |
| _isItem( |
| kind, |
| displayName, |
| file, |
| containerName: containerName, |
| nameRange: nameRange, |
| codeRange: codeRange, |
| ), |
| ); |
| |
| if (ranges != null) { |
| matcher = matcher.having((c) => c.ranges, 'ranges', ranges); |
| } |
| |
| return matcher; |
| } |
| |
| abstract class AbstractCallHierarchyTest extends AbstractSingleUnitTest { |
| final startOfFile = SourceRange(0, 0); |
| |
| /// Gets the entire range for [code]. |
| SourceRange entireRange(TestCode code) => SourceRange(0, code.code.length); |
| |
| Future<CallHierarchyItem?> findTarget(TestCode code) async { |
| var offset = code.position.offset; |
| expect(offset, greaterThanOrEqualTo(0)); |
| addTestSource(code.code); |
| |
| var result = await getResolvedUnit(testFile); |
| |
| return DartCallHierarchyComputer(result).findTarget(offset); |
| } |
| |
| /// Gets the expected range that follows the string [prefix] in [content] with a |
| /// length of [match.length]. |
| SourceRange rangeAfterPrefix(String prefix, TestCode code, String match) => |
| SourceRange(code.code.indexOf(prefix) + prefix.length, match.length); |
| |
| /// Gets the expected range that starts at [search] in [code] with a |
| /// length of [match.length]. |
| SourceRange rangeAtSearch(String search, TestCode code, [String? match]) { |
| var offset = code.code.indexOf(search); |
| expect(offset, greaterThanOrEqualTo(0)); |
| return SourceRange(offset, (match ?? search).length); |
| } |
| |
| @override |
| void setUp() { |
| useLineEndingsForPlatform = false; |
| super.setUp(); |
| } |
| } |
| |
| @reflectiveTest |
| class CallHierarchyComputerFindTargetTest extends AbstractCallHierarchyTest { |
| late String otherFile; |
| |
| Future<void> expectNoTarget(TestCode code) async { |
| await expectTarget(code, isNull); |
| } |
| |
| Future<void> expectTarget(TestCode code, Matcher matcher) async { |
| var target = await findTarget(code); |
| expect(target, matcher); |
| } |
| |
| @override |
| void setUp() { |
| super.setUp(); |
| otherFile = convertPath('$testPackageLibPath/other.dart'); |
| } |
| |
| Future<void> test_args() async { |
| await expectNoTarget(TestCode.parse('f(int ^a) {}')); |
| } |
| |
| Future<void> test_block() async { |
| await expectNoTarget(TestCode.parse('f() {^}')); |
| } |
| |
| Future<void> test_comment() async { |
| await expectNoTarget(TestCode.parse('f() {} // this is a ^comment')); |
| } |
| |
| Future<void> test_constructor() async { |
| var code = TestCode.parse(''' |
| class Foo { |
| [!Fo^o(String a) {}!] |
| } |
| '''); |
| |
| var target = await findTarget(code); |
| expect( |
| target, |
| _isItem( |
| CallHierarchyKind.constructor, |
| 'Foo', |
| testFile.path, |
| containerName: 'Foo', |
| nameRange: rangeAtSearch('Foo(', code, 'Foo'), |
| codeRange: code.range.sourceRange, |
| ), |
| ); |
| } |
| |
| Future<void> test_constructorCall() async { |
| var code = TestCode.parse(''' |
| import 'other.dart'; |
| |
| void f() { |
| final foo = Fo^o(); |
| } |
| '''); |
| |
| var otherCode = TestCode.parse(''' |
| class Foo { |
| [!Foo();!] |
| } |
| '''); |
| |
| newFile(otherFile, otherCode.code); |
| await expectTarget( |
| code, |
| _isItem( |
| CallHierarchyKind.constructor, |
| 'Foo', |
| otherFile, |
| containerName: 'Foo', |
| nameRange: rangeAtSearch('Foo(', otherCode, 'Foo'), |
| codeRange: otherCode.range.sourceRange, |
| ), |
| ); |
| } |
| |
| @SkippedTest() // TODO(scheglov): implement augmentation |
| Future<void> test_constructorCall_to_augmentation() async { |
| var code = TestCode.parse(''' |
| part 'other.dart'; |
| |
| class Foo {} |
| |
| void f() { |
| Foo.na^med(); |
| } |
| '''); |
| |
| var otherCode = TestCode.parse(''' |
| part of 'test.dart'; |
| augment class Foo { |
| [!Foo.named(){}!] |
| } |
| '''); |
| |
| newFile(otherFile, otherCode.code); |
| await expectTarget( |
| code, |
| _isItem( |
| CallHierarchyKind.constructor, |
| 'Foo.named', |
| otherFile, |
| containerName: 'Foo', |
| nameRange: rangeAtSearch('named', otherCode), |
| codeRange: otherCode.range.sourceRange, |
| ), |
| ); |
| } |
| |
| Future<void> test_dotShorthand_constructor_named() async { |
| var code = TestCode.parse(''' |
| import 'other.dart'; |
| |
| void f() { |
| Foo foo = .nam^ed(); |
| } |
| '''); |
| |
| var otherCode = TestCode.parse(''' |
| class Foo { |
| [!Foo.named();!] |
| } |
| '''); |
| |
| newFile(otherFile, otherCode.code); |
| await expectTarget( |
| code, |
| _isItem( |
| CallHierarchyKind.constructor, |
| 'Foo.named', |
| otherFile, |
| containerName: 'Foo', |
| nameRange: rangeAtSearch('named', otherCode), |
| codeRange: otherCode.range.sourceRange, |
| ), |
| ); |
| } |
| |
| Future<void> test_dotShorthand_constructor_unnamed() async { |
| var code = TestCode.parse(''' |
| import 'other.dart'; |
| |
| void f() { |
| Foo foo = .ne^w(); |
| } |
| '''); |
| |
| var otherCode = TestCode.parse(''' |
| [!class Foo {}!] |
| '''); |
| |
| newFile(otherFile, otherCode.code); |
| await expectTarget( |
| code, |
| _isItem( |
| CallHierarchyKind.constructor, |
| 'Foo', |
| otherFile, |
| containerName: 'Foo', |
| nameRange: rangeAtSearch('Foo {', otherCode, 'Foo'), |
| codeRange: otherCode.range.sourceRange, |
| ), |
| ); |
| } |
| |
| Future<void> test_dotShorthand_extensionType() async { |
| var code = TestCode.parse(''' |
| import 'other.dart'; |
| |
| void f() { |
| Foo foo = .ba^r; |
| } |
| '''); |
| |
| var otherCode = TestCode.parse(''' |
| extension type Foo(int x) { |
| [!static Foo get bar => Foo(1);!] |
| } |
| '''); |
| |
| newFile(otherFile, otherCode.code); |
| await expectTarget( |
| code, |
| _isItem( |
| CallHierarchyKind.property, |
| 'get bar', |
| otherFile, |
| containerName: 'Foo', |
| nameRange: rangeAtSearch('bar', otherCode), |
| codeRange: otherCode.range.sourceRange, |
| ), |
| ); |
| } |
| |
| Future<void> test_dotShorthand_getter() async { |
| var code = TestCode.parse(''' |
| import 'other.dart'; |
| |
| void f() { |
| Foo foo = .ba^r; |
| } |
| '''); |
| |
| var otherCode = TestCode.parse(''' |
| class Foo { |
| [!static Foo get bar => Foo();!] |
| } |
| '''); |
| |
| newFile(otherFile, otherCode.code); |
| await expectTarget( |
| code, |
| _isItem( |
| CallHierarchyKind.property, |
| 'get bar', |
| otherFile, |
| containerName: 'Foo', |
| nameRange: rangeAtSearch('bar', otherCode), |
| codeRange: otherCode.range.sourceRange, |
| ), |
| ); |
| } |
| |
| Future<void> test_dotShorthand_method() async { |
| var code = TestCode.parse(''' |
| import 'other.dart'; |
| |
| void f() { |
| Foo foo = .ba^r(); |
| } |
| '''); |
| |
| var otherCode = TestCode.parse(''' |
| class Foo { |
| [!static Foo bar() => Foo();!] |
| } |
| '''); |
| |
| newFile(otherFile, otherCode.code); |
| await expectTarget( |
| code, |
| _isItem( |
| CallHierarchyKind.method, |
| 'bar', |
| otherFile, |
| containerName: 'Foo', |
| nameRange: rangeAtSearch('bar', otherCode), |
| codeRange: otherCode.range.sourceRange, |
| ), |
| ); |
| } |
| |
| Future<void> test_extension_method() async { |
| var code = TestCode.parse(''' |
| extension StringExtension on String { |
| [!void myMet^hod() {}!] |
| } |
| '''); |
| |
| await expectTarget( |
| code, |
| _isItem( |
| CallHierarchyKind.method, |
| 'myMethod', |
| testFile.path, |
| containerName: 'StringExtension', |
| nameRange: rangeAtSearch('myMethod', code), |
| codeRange: code.range.sourceRange, |
| ), |
| ); |
| } |
| |
| Future<void> test_extension_methodCall() async { |
| var code = TestCode.parse(''' |
| import 'other.dart'; |
| |
| void f() { |
| ''.myMet^hod(); |
| } |
| '''); |
| |
| var otherCode = TestCode.parse(''' |
| extension StringExtension on String { |
| [!void myMethod() {}!] |
| } |
| '''); |
| |
| newFile(otherFile, otherCode.code); |
| await expectTarget( |
| code, |
| _isItem( |
| CallHierarchyKind.method, |
| 'myMethod', |
| otherFile, |
| containerName: 'StringExtension', |
| nameRange: rangeAtSearch('myMethod', otherCode), |
| codeRange: otherCode.range.sourceRange, |
| ), |
| ); |
| } |
| |
| Future<void> test_function() async { |
| var code = TestCode.parse(''' |
| [!void myFun^ction() {}!] |
| '''); |
| |
| await expectTarget( |
| code, |
| _isItem( |
| CallHierarchyKind.function, |
| 'myFunction', |
| testFile.path, |
| containerName: 'test.dart', |
| nameRange: rangeAtSearch('myFunction', code), |
| codeRange: code.range.sourceRange, |
| ), |
| ); |
| } |
| |
| Future<void> test_function_startOfParameterList() async { |
| var code = TestCode.parse(''' |
| [!void myFunction^() {}!] |
| '''); |
| |
| await expectTarget( |
| code, |
| _isItem( |
| CallHierarchyKind.function, |
| 'myFunction', |
| testFile.path, |
| containerName: 'test.dart', |
| nameRange: rangeAtSearch('myFunction', code), |
| codeRange: code.range.sourceRange, |
| ), |
| ); |
| } |
| |
| Future<void> test_function_startOfTypeParameterList() async { |
| var code = TestCode.parse(''' |
| [!void myFunction^<T>() {}!] |
| '''); |
| |
| await expectTarget( |
| code, |
| _isItem( |
| CallHierarchyKind.function, |
| 'myFunction', |
| testFile.path, |
| containerName: 'test.dart', |
| nameRange: rangeAtSearch('myFunction', code), |
| codeRange: code.range.sourceRange, |
| ), |
| ); |
| } |
| |
| Future<void> test_functionCall() async { |
| var code = TestCode.parse(''' |
| import 'other.dart' as other; |
| |
| void f() { |
| other.myFun^ction(); |
| } |
| '''); |
| |
| var otherCode = TestCode.parse(''' |
| [!void myFunction() {}!] |
| '''); |
| |
| newFile(otherFile, otherCode.code); |
| await expectTarget( |
| code, |
| _isItem( |
| CallHierarchyKind.function, |
| 'myFunction', |
| otherFile, |
| containerName: 'other.dart', |
| nameRange: rangeAtSearch('myFunction', otherCode), |
| codeRange: otherCode.range.sourceRange, |
| ), |
| ); |
| } |
| |
| Future<void> test_functionCallInNullAwareElementInList() async { |
| var code = TestCode.parse(''' |
| import 'other.dart' as other; |
| |
| void f() { |
| <String>[?other.myFun^ction()]; |
| } |
| '''); |
| |
| var otherCode = TestCode.parse(''' |
| [!String? myFunction() => null;!] |
| '''); |
| |
| newFile(otherFile, otherCode.code); |
| await expectTarget( |
| code, |
| _isItem( |
| CallHierarchyKind.function, |
| 'myFunction', |
| otherFile, |
| containerName: 'other.dart', |
| nameRange: rangeAtSearch('myFunction', otherCode), |
| codeRange: otherCode.range.sourceRange, |
| ), |
| ); |
| } |
| |
| Future<void> test_functionCallInNullAwareElementInMapKey() async { |
| var code = TestCode.parse(''' |
| import 'other.dart' as other; |
| |
| void f() { |
| <String, int>{?other.myFun^ction(): 0}; |
| } |
| '''); |
| |
| var otherCode = TestCode.parse(''' |
| [!String? myFunction() => null;!] |
| '''); |
| |
| newFile(otherFile, otherCode.code); |
| await expectTarget( |
| code, |
| _isItem( |
| CallHierarchyKind.function, |
| 'myFunction', |
| otherFile, |
| containerName: 'other.dart', |
| nameRange: rangeAtSearch('myFunction', otherCode), |
| codeRange: otherCode.range.sourceRange, |
| ), |
| ); |
| } |
| |
| Future<void> test_functionCallInNullAwareElementInMapValue() async { |
| var code = TestCode.parse(''' |
| import 'other.dart' as other; |
| |
| void f() { |
| <int, String>{0: ?other.myFun^ction()}; |
| } |
| '''); |
| |
| var otherCode = TestCode.parse(''' |
| [!String? myFunction() => null;!] |
| '''); |
| |
| newFile(otherFile, otherCode.code); |
| await expectTarget( |
| code, |
| _isItem( |
| CallHierarchyKind.function, |
| 'myFunction', |
| otherFile, |
| containerName: 'other.dart', |
| nameRange: rangeAtSearch('myFunction', otherCode), |
| codeRange: otherCode.range.sourceRange, |
| ), |
| ); |
| } |
| |
| Future<void> test_functionCallInNullAwareElementInSet() async { |
| var code = TestCode.parse(''' |
| import 'other.dart' as other; |
| |
| void f() { |
| <String>{?other.myFun^ction()}; |
| } |
| '''); |
| |
| var otherCode = TestCode.parse(''' |
| [!String? myFunction() => null;!] |
| '''); |
| |
| newFile(otherFile, otherCode.code); |
| await expectTarget( |
| code, |
| _isItem( |
| CallHierarchyKind.function, |
| 'myFunction', |
| otherFile, |
| containerName: 'other.dart', |
| nameRange: rangeAtSearch('myFunction', otherCode), |
| codeRange: otherCode.range.sourceRange, |
| ), |
| ); |
| } |
| |
| Future<void> test_getter() async { |
| var code = TestCode.parse(''' |
| class Foo { |
| [!String get fo^o => '';!] |
| } |
| '''); |
| |
| await expectTarget( |
| code, |
| _isItem( |
| CallHierarchyKind.property, |
| 'get foo', |
| testFile.path, |
| containerName: 'Foo', |
| nameRange: rangeAtSearch('foo', code), |
| codeRange: code.range.sourceRange, |
| ), |
| ); |
| } |
| |
| Future<void> test_getterCall() async { |
| var code = TestCode.parse(''' |
| import 'other.dart'; |
| |
| void f() { |
| final foo = ba^r; |
| } |
| '''); |
| |
| var otherCode = TestCode.parse(''' |
| [!String get bar => '';!] |
| '''); |
| |
| newFile(otherFile, otherCode.code); |
| await expectTarget( |
| code, |
| _isItem( |
| CallHierarchyKind.property, |
| 'get bar', |
| otherFile, |
| containerName: 'other.dart', |
| nameRange: rangeAtSearch('bar', otherCode), |
| codeRange: otherCode.range.sourceRange, |
| ), |
| ); |
| } |
| |
| Future<void> test_implicitConstructorCall() async { |
| // Even if a constructor is implicit, we might want to be able to get the |
| // incoming calls, so we should return the class location as a stand-in |
| // (although with the Kind still set to constructor). |
| var code = TestCode.parse(''' |
| import 'other.dart'; |
| |
| void f() { |
| final foo = Fo^o(); |
| } |
| '''); |
| |
| var otherCode = TestCode.parse(''' |
| [!class Foo {}!] |
| '''); |
| |
| newFile(otherFile, otherCode.code); |
| await expectTarget( |
| code, |
| _isItem( |
| CallHierarchyKind.constructor, |
| 'Foo', |
| otherFile, |
| containerName: 'Foo', |
| nameRange: rangeAtSearch('Foo {', otherCode, 'Foo'), |
| codeRange: otherCode.range.sourceRange, |
| ), |
| ); |
| } |
| |
| Future<void> test_method() async { |
| var code = TestCode.parse(''' |
| class Foo { |
| [!void myMet^hod() {}!] |
| } |
| '''); |
| |
| await expectTarget( |
| code, |
| _isItem( |
| CallHierarchyKind.method, |
| 'myMethod', |
| testFile.path, |
| containerName: 'Foo', |
| nameRange: rangeAtSearch('myMethod', code), |
| codeRange: code.range.sourceRange, |
| ), |
| ); |
| } |
| |
| Future<void> test_method_startOfParameterList() async { |
| var code = TestCode.parse(''' |
| class Foo { |
| [!void myMethod^() {}!] |
| } |
| '''); |
| |
| await expectTarget( |
| code, |
| _isItem( |
| CallHierarchyKind.method, |
| 'myMethod', |
| testFile.path, |
| containerName: 'Foo', |
| nameRange: rangeAtSearch('myMethod', code), |
| codeRange: code.range.sourceRange, |
| ), |
| ); |
| } |
| |
| Future<void> test_method_startOfTypeParameterList() async { |
| var code = TestCode.parse(''' |
| class Foo { |
| [!void myMethod^<T>() {}!] |
| } |
| '''); |
| |
| await expectTarget( |
| code, |
| _isItem( |
| CallHierarchyKind.method, |
| 'myMethod', |
| testFile.path, |
| containerName: 'Foo', |
| nameRange: rangeAtSearch('myMethod', code), |
| codeRange: code.range.sourceRange, |
| ), |
| ); |
| } |
| |
| Future<void> test_methodCall() async { |
| var code = TestCode.parse(''' |
| import 'other.dart'; |
| |
| void f() { |
| Foo().myMet^hod(); |
| } |
| '''); |
| |
| var otherCode = TestCode.parse(''' |
| class Foo { |
| [!void myMethod() {}!] |
| } |
| '''); |
| |
| newFile(otherFile, otherCode.code); |
| await expectTarget( |
| code, |
| _isItem( |
| CallHierarchyKind.method, |
| 'myMethod', |
| otherFile, |
| containerName: 'Foo', |
| nameRange: rangeAtSearch('myMethod', otherCode), |
| codeRange: otherCode.range.sourceRange, |
| ), |
| ); |
| } |
| |
| @FailingTest() // TODO(scheglov): implement augmentation |
| Future<void> test_methodCall_to_augmentation() async { |
| var code = TestCode.parse(''' |
| part 'other.dart'; |
| |
| class Foo {} |
| |
| void f() { |
| Foo().myMet^hod(); |
| } |
| '''); |
| |
| var otherCode = TestCode.parse(''' |
| part of 'test.dart'; |
| |
| augment class Foo { |
| [!void myMethod() {}!] |
| } |
| '''); |
| |
| newFile(otherFile, otherCode.code); |
| await expectTarget( |
| code, |
| _isItem( |
| CallHierarchyKind.method, |
| 'myMethod', |
| otherFile, |
| containerName: 'Foo', |
| nameRange: rangeAtSearch('myMethod', otherCode), |
| codeRange: otherCode.range.sourceRange, |
| ), |
| ); |
| } |
| |
| Future<void> test_mixin_method() async { |
| var code = TestCode.parse(''' |
| mixin Bar { |
| [!void myMet^hod() {}!] |
| } |
| '''); |
| |
| await expectTarget( |
| code, |
| _isItem( |
| CallHierarchyKind.method, |
| 'myMethod', |
| testFile.path, |
| containerName: 'Bar', |
| nameRange: rangeAtSearch('myMethod', code), |
| codeRange: code.range.sourceRange, |
| ), |
| ); |
| } |
| |
| Future<void> test_mixin_methodCall() async { |
| var code = TestCode.parse(''' |
| import 'other.dart'; |
| |
| void f() { |
| Foo().myMet^hod(); |
| } |
| '''); |
| |
| var otherCode = TestCode.parse(''' |
| class Bar { |
| [!void myMethod() {}!] |
| } |
| |
| class Foo with Bar {} |
| '''); |
| |
| newFile(otherFile, otherCode.code); |
| await expectTarget( |
| code, |
| _isItem( |
| CallHierarchyKind.method, |
| 'myMethod', |
| otherFile, |
| containerName: 'Bar', |
| nameRange: rangeAtSearch('myMethod', otherCode), |
| codeRange: otherCode.range.sourceRange, |
| ), |
| ); |
| } |
| |
| Future<void> test_namedConstructor() async { |
| var code = TestCode.parse(''' |
| class Foo { |
| [!Foo.Ba^r(String a) {}!] |
| } |
| '''); |
| |
| await expectTarget( |
| code, |
| _isItem( |
| CallHierarchyKind.constructor, |
| 'Foo.Bar', |
| testFile.path, |
| containerName: 'Foo', |
| nameRange: rangeAtSearch('Bar', code), |
| codeRange: code.range.sourceRange, |
| ), |
| ); |
| } |
| |
| Future<void> test_namedConstructor_typeName() async { |
| var code = TestCode.parse(''' |
| class Foo { |
| Fo^o.Bar(String a) {} |
| } |
| '''); |
| |
| await expectNoTarget(code); |
| } |
| |
| Future<void> test_namedConstructorCall() async { |
| var code = TestCode.parse(''' |
| import 'other.dart'; |
| |
| void f() { |
| final foo = Foo.Ba^r(); |
| } |
| '''); |
| |
| var otherCode = TestCode.parse(''' |
| class Foo { |
| [!Foo.Bar();!] |
| } |
| '''); |
| |
| newFile(otherFile, otherCode.code); |
| await expectTarget( |
| code, |
| _isItem( |
| CallHierarchyKind.constructor, |
| 'Foo.Bar', |
| otherFile, |
| containerName: 'Foo', |
| nameRange: rangeAtSearch('Bar', otherCode), |
| codeRange: otherCode.range.sourceRange, |
| ), |
| ); |
| } |
| |
| Future<void> test_namedConstructorCall_typeName() async { |
| var code = TestCode.parse(''' |
| import 'other.dart'; |
| |
| void f() { |
| final foo = Fo^o.Bar(); |
| } |
| '''); |
| |
| var otherCode = TestCode.parse(''' |
| class Foo { |
| Foo.Bar(); |
| } |
| '''); |
| |
| newFile(otherFile, otherCode.code); |
| await expectNoTarget(code); |
| } |
| |
| Future<void> test_setter() async { |
| var code = TestCode.parse(''' |
| class Foo { |
| [!set fo^o(String value) {}!] |
| } |
| '''); |
| |
| await expectTarget( |
| code, |
| _isItem( |
| CallHierarchyKind.property, |
| 'set foo', |
| testFile.path, |
| containerName: 'Foo', |
| nameRange: rangeAtSearch('foo', code), |
| codeRange: code.range.sourceRange, |
| ), |
| ); |
| } |
| |
| Future<void> test_setterCall() async { |
| var code = TestCode.parse(''' |
| import 'other.dart'; |
| |
| void f() { |
| ba^r = ''; |
| } |
| '''); |
| |
| var otherCode = TestCode.parse(''' |
| [!set bar(String value) {}!] |
| '''); |
| |
| newFile(otherFile, otherCode.code); |
| await expectTarget( |
| code, |
| _isItem( |
| CallHierarchyKind.property, |
| 'set bar', |
| otherFile, |
| containerName: 'other.dart', |
| nameRange: rangeAtSearch('bar', otherCode), |
| codeRange: otherCode.range.sourceRange, |
| ), |
| ); |
| } |
| |
| Future<void> test_whitespace() async { |
| await expectNoTarget(TestCode.parse(' ^ void f() {}')); |
| } |
| |
| Future<void> test_wildcardVariable() async { |
| var code = TestCode.parse(''' |
| f() { |
| [!^_() {}!] |
| } |
| '''); |
| |
| var target = await findTarget(code); |
| expect( |
| target, |
| _isItem( |
| CallHierarchyKind.function, |
| '_', |
| testFile.path, |
| containerName: 'f', |
| nameRange: SourceRange(8, 1), |
| codeRange: code.range.sourceRange, |
| ), |
| ); |
| } |
| |
| Future<void> test_wildcardVariable_preWildcards() async { |
| var code = TestCode.parse(''' |
| // @dart = 3.4 |
| // (pre wildcard-variables) |
| |
| f() { |
| [!_() {}!] |
| ^_(); |
| } |
| '''); |
| |
| var target = await findTarget(code); |
| expect( |
| target, |
| _isItem( |
| CallHierarchyKind.function, |
| '_', |
| testFile.path, |
| containerName: 'f', |
| nameRange: SourceRange(52, 1), |
| codeRange: code.range.sourceRange, |
| ), |
| ); |
| } |
| } |
| |
| @reflectiveTest |
| class CallHierarchyComputerIncomingCallsTest extends AbstractCallHierarchyTest { |
| late String otherFile; |
| late SearchEngine searchEngine; |
| |
| Future<List<CallHierarchyCalls>> findIncomingCalls(TestCode code) async { |
| var target = (await findTarget(code))!; |
| return findIncomingCallsForTarget(target); |
| } |
| |
| Future<List<CallHierarchyCalls>> findIncomingCallsForTarget( |
| CallHierarchyItem target, |
| ) async { |
| var targetFile = getFile(target.file); |
| var result = await getResolvedUnit(targetFile); |
| expect(result.diagnostics, isEmpty); |
| |
| return DartCallHierarchyComputer( |
| result, |
| ).findIncomingCalls(target, searchEngine); |
| } |
| |
| @override |
| void setUp() { |
| super.setUp(); |
| otherFile = convertPath('$testPackageLibPath/other.dart'); |
| searchEngine = SearchEngineImpl([driverFor(testFile)]); |
| } |
| |
| Future<void> test_constructor() async { |
| var code = TestCode.parse(''' |
| class Foo { |
| Fo^o(); |
| } |
| '''); |
| |
| var otherCode = TestCode.parse(''' |
| import 'test.dart'; |
| |
| final foo1 = Foo(); |
| /*[0*/class Bar { |
| final foo2 = Foo(); |
| /*[1*/Foo get foo3 => Foo();/*1]*/ |
| /*[2*/Bar() { |
| final foo4 = Foo(); |
| }/*2]*/ |
| /*[3*/void bar() { |
| final foo5 = Foo(); |
| final foo6 = Foo(); |
| }/*3]*/ |
| }/*0]*/ |
| '''); |
| |
| // Gets the expected range that follows the string [prefix]. |
| SourceRange rangeAfter(String prefix) => |
| rangeAfterPrefix(prefix, otherCode, 'Foo'); |
| |
| newFile(otherFile, otherCode.code); |
| var calls = await findIncomingCalls(code); |
| expect( |
| calls, |
| unorderedEquals([ |
| _isResult( |
| CallHierarchyKind.file, |
| 'other.dart', |
| otherFile, |
| containerName: null, |
| nameRange: startOfFile, |
| codeRange: entireRange(otherCode), |
| ranges: [rangeAfter('foo1 = ')], |
| ), |
| _isResult( |
| CallHierarchyKind.class_, |
| 'Bar', |
| otherFile, |
| containerName: 'other.dart', |
| nameRange: rangeAtSearch('Bar {', otherCode, 'Bar'), |
| codeRange: otherCode.ranges[0].sourceRange, |
| ranges: [rangeAfter('foo2 = ')], |
| ), |
| _isResult( |
| CallHierarchyKind.property, |
| 'get foo3', |
| otherFile, |
| containerName: 'Bar', |
| nameRange: rangeAtSearch('foo3', otherCode), |
| codeRange: otherCode.ranges[1].sourceRange, |
| ranges: [rangeAfter('foo3 => ')], |
| ), |
| _isResult( |
| CallHierarchyKind.constructor, |
| 'Bar', |
| otherFile, |
| containerName: 'Bar', |
| nameRange: rangeAtSearch('Bar() {', otherCode, 'Bar'), |
| codeRange: otherCode.ranges[2].sourceRange, |
| ranges: [rangeAfter('foo4 = ')], |
| ), |
| _isResult( |
| CallHierarchyKind.method, |
| 'bar', |
| otherFile, |
| containerName: 'Bar', |
| nameRange: rangeAtSearch('bar() {', otherCode, 'bar'), |
| codeRange: otherCode.ranges[3].sourceRange, |
| ranges: [rangeAfter('foo5 = '), rangeAfter('foo6 = ')], |
| ), |
| ]), |
| ); |
| } |
| |
| Future<void> test_dotShorthand_constructor_named() async { |
| var code = TestCode.parse(''' |
| // ignore_for_file: unused_local_variable |
| import 'other.dart'; |
| |
| [!void f() { |
| Foo foo1 = .nam^ed(); |
| }!] |
| '''); |
| |
| var otherCode = TestCode.parse(''' |
| class Foo { |
| Foo.named(); |
| } |
| |
| Foo foo2 = .named(); |
| '''); |
| |
| // Gets the expected range that follows the string [prefix]. |
| SourceRange rangeAfter(String prefix, TestCode code) => |
| rangeAfterPrefix(prefix, code, 'named'); |
| |
| newFile(otherFile, otherCode.code); |
| var calls = await findIncomingCalls(code); |
| expect( |
| calls, |
| unorderedEquals([ |
| _isResult( |
| CallHierarchyKind.function, |
| 'f', |
| testFile.path, |
| containerName: 'test.dart', |
| nameRange: rangeAtSearch('f() {', code, 'f'), |
| codeRange: code.range.sourceRange, |
| ranges: [rangeAfter('foo1 = .', code)], |
| ), |
| _isResult( |
| CallHierarchyKind.file, |
| 'other.dart', |
| otherFile, |
| containerName: null, |
| nameRange: startOfFile, |
| codeRange: entireRange(otherCode), |
| ranges: [rangeAfter('foo2 = .', otherCode)], |
| ), |
| ]), |
| ); |
| } |
| |
| Future<void> test_dotShorthand_constructor_unnamed() async { |
| var code = TestCode.parse(''' |
| // ignore_for_file: unused_local_variable |
| import 'other.dart'; |
| |
| [!void f() { |
| Foo foo1 = .ne^w(); |
| }!] |
| '''); |
| |
| var otherCode = TestCode.parse(''' |
| class Foo {} |
| |
| Foo foo2 = .new(); |
| '''); |
| |
| // Gets the expected range that follows the string [prefix]. |
| SourceRange rangeAfter(String prefix, TestCode code) => |
| rangeAfterPrefix(prefix, code, 'new'); |
| |
| newFile(otherFile, otherCode.code); |
| var calls = await findIncomingCalls(code); |
| expect( |
| calls, |
| unorderedEquals([ |
| _isResult( |
| CallHierarchyKind.function, |
| 'f', |
| testFile.path, |
| containerName: 'test.dart', |
| nameRange: rangeAtSearch('f() {', code, 'f'), |
| codeRange: code.range.sourceRange, |
| ranges: [rangeAfter('foo1 = .', code)], |
| ), |
| _isResult( |
| CallHierarchyKind.file, |
| 'other.dart', |
| otherFile, |
| containerName: null, |
| nameRange: startOfFile, |
| codeRange: entireRange(otherCode), |
| ranges: [rangeAfter('foo2 = .', otherCode)], |
| ), |
| ]), |
| ); |
| } |
| |
| Future<void> test_dotShorthand_extensionType() async { |
| var code = TestCode.parse(''' |
| // ignore_for_file: unused_local_variable |
| import 'other.dart'; |
| |
| [!void f() { |
| Foo foo1 = .gett^er; |
| }!] |
| '''); |
| |
| var otherCode = TestCode.parse(''' |
| extension type Foo(int x) { |
| static Foo get getter => Foo(1); |
| } |
| |
| Foo foo2 = .getter; |
| '''); |
| |
| // Gets the expected range that follows the string [prefix]. |
| SourceRange rangeAfter(String prefix, TestCode code) => |
| rangeAfterPrefix(prefix, code, 'getter'); |
| |
| newFile(otherFile, otherCode.code); |
| var calls = await findIncomingCalls(code); |
| expect( |
| calls, |
| unorderedEquals([ |
| _isResult( |
| CallHierarchyKind.function, |
| 'f', |
| testFile.path, |
| containerName: 'test.dart', |
| nameRange: rangeAtSearch('f() {', code, 'f'), |
| codeRange: code.range.sourceRange, |
| ranges: [rangeAfter('foo1 = .', code)], |
| ), |
| _isResult( |
| CallHierarchyKind.file, |
| 'other.dart', |
| otherFile, |
| containerName: null, |
| nameRange: startOfFile, |
| codeRange: entireRange(otherCode), |
| ranges: [rangeAfter('foo2 = .', otherCode)], |
| ), |
| ]), |
| ); |
| } |
| |
| Future<void> test_dotShorthand_getter() async { |
| var code = TestCode.parse(''' |
| // ignore_for_file: unused_local_variable |
| import 'other.dart'; |
| |
| [!void f() { |
| Foo foo1 = .gett^er; |
| }!] |
| '''); |
| |
| var otherCode = TestCode.parse(''' |
| class Foo { |
| static Foo get getter => Foo(); |
| } |
| |
| Foo foo2 = .getter; |
| '''); |
| |
| // Gets the expected range that follows the string [prefix]. |
| SourceRange rangeAfter(String prefix, TestCode code) => |
| rangeAfterPrefix(prefix, code, 'getter'); |
| |
| newFile(otherFile, otherCode.code); |
| var calls = await findIncomingCalls(code); |
| expect( |
| calls, |
| unorderedEquals([ |
| _isResult( |
| CallHierarchyKind.function, |
| 'f', |
| testFile.path, |
| containerName: 'test.dart', |
| nameRange: rangeAtSearch('f() {', code, 'f'), |
| codeRange: code.range.sourceRange, |
| ranges: [rangeAfter('foo1 = .', code)], |
| ), |
| _isResult( |
| CallHierarchyKind.file, |
| 'other.dart', |
| otherFile, |
| containerName: null, |
| nameRange: startOfFile, |
| codeRange: entireRange(otherCode), |
| ranges: [rangeAfter('foo2 = .', otherCode)], |
| ), |
| ]), |
| ); |
| } |
| |
| Future<void> test_dotShorthand_method() async { |
| var code = TestCode.parse(''' |
| // ignore_for_file: unused_local_variable |
| import 'other.dart'; |
| |
| [!void f() { |
| Foo foo1 = .meth^od(); |
| }!] |
| '''); |
| |
| var otherCode = TestCode.parse(''' |
| class Foo { |
| static Foo method() => Foo(); |
| } |
| |
| Foo foo2 = .method(); |
| '''); |
| |
| // Gets the expected range that follows the string [prefix]. |
| SourceRange rangeAfter(String prefix, TestCode code) => |
| rangeAfterPrefix(prefix, code, 'method'); |
| |
| newFile(otherFile, otherCode.code); |
| var calls = await findIncomingCalls(code); |
| expect( |
| calls, |
| unorderedEquals([ |
| _isResult( |
| CallHierarchyKind.function, |
| 'f', |
| testFile.path, |
| containerName: 'test.dart', |
| nameRange: rangeAtSearch('f() {', code, 'f'), |
| codeRange: code.range.sourceRange, |
| ranges: [rangeAfter('foo1 = .', code)], |
| ), |
| _isResult( |
| CallHierarchyKind.file, |
| 'other.dart', |
| otherFile, |
| containerName: null, |
| nameRange: startOfFile, |
| codeRange: entireRange(otherCode), |
| ranges: [rangeAfter('foo2 = .', otherCode)], |
| ), |
| ]), |
| ); |
| } |
| |
| Future<void> test_extension_method() async { |
| var code = TestCode.parse(''' |
| extension StringExtension on String { |
| void myMet^hod() {} |
| } |
| '''); |
| |
| var otherCode = TestCode.parse(''' |
| import 'test.dart'; |
| |
| [!void f() { |
| ''.myMethod(); |
| }!] |
| '''); |
| |
| // Gets the expected range that follows the string [prefix]. |
| SourceRange rangeAfter(String prefix) => |
| rangeAfterPrefix(prefix, otherCode, 'myMethod'); |
| |
| newFile(otherFile, otherCode.code); |
| var calls = await findIncomingCalls(code); |
| expect( |
| calls, |
| unorderedEquals([ |
| _isResult( |
| CallHierarchyKind.function, |
| 'f', |
| otherFile, |
| containerName: 'other.dart', |
| nameRange: rangeAtSearch('f() {', otherCode, 'f'), |
| codeRange: otherCode.range.sourceRange, |
| ranges: [rangeAfter("''.")], |
| ), |
| ]), |
| ); |
| } |
| |
| Future<void> test_fileModifications() async { |
| var code = TestCode.parse(''' |
| void o^ne() {} |
| void two() { |
| one(); |
| } |
| '''); |
| |
| var target = (await findTarget(code))!; |
| |
| // Ensure there are some results before modification. |
| var calls = await findIncomingCallsForTarget(target); |
| expect(calls, isNotEmpty); |
| |
| // Modify the file so that the target offset is no longer the original item. |
| updateTestSource(testCode.replaceAll('one()', 'three()')); |
| |
| // Ensure there are now no results for the original target. |
| calls = await findIncomingCallsForTarget(target); |
| expect(calls, isEmpty); |
| } |
| |
| Future<void> test_function() async { |
| var code = TestCode.parse(''' |
| String myFun^ction() => ''; |
| '''); |
| |
| var otherCode = TestCode.parse(''' |
| import 'test.dart'; |
| |
| final foo1 = myFunction(); |
| |
| /*[0*/class Bar { |
| final foo2 = myFunction(); |
| /*[1*/String get foo3 => myFunction();/*1]*/ |
| /*[2*/Bar() { |
| final foo4 = myFunction(); |
| }/*2]*/ |
| /*[3*/void bar() { |
| final foo5 = myFunction(); |
| final foo6 = myFunction(); |
| }/*3]*/ |
| }/*0]*/ |
| '''); |
| |
| // Gets the expected range that follows the string [prefix]. |
| SourceRange rangeAfter(String prefix) => |
| rangeAfterPrefix(prefix, otherCode, 'myFunction'); |
| |
| newFile(otherFile, otherCode.code); |
| var calls = await findIncomingCalls(code); |
| expect( |
| calls, |
| unorderedEquals([ |
| _isResult( |
| CallHierarchyKind.file, |
| 'other.dart', |
| otherFile, |
| containerName: null, |
| nameRange: startOfFile, |
| codeRange: entireRange(otherCode), |
| ranges: [rangeAfter('foo1 = ')], |
| ), |
| _isResult( |
| CallHierarchyKind.class_, |
| 'Bar', |
| otherFile, |
| containerName: 'other.dart', |
| nameRange: rangeAtSearch('Bar {', otherCode, 'Bar'), |
| codeRange: otherCode.ranges[0].sourceRange, |
| ranges: [rangeAfter('foo2 = ')], |
| ), |
| _isResult( |
| CallHierarchyKind.property, |
| 'get foo3', |
| otherFile, |
| containerName: 'Bar', |
| nameRange: rangeAtSearch('foo3', otherCode), |
| codeRange: otherCode.ranges[1].sourceRange, |
| ranges: [rangeAfter('foo3 => ')], |
| ), |
| _isResult( |
| CallHierarchyKind.constructor, |
| 'Bar', |
| otherFile, |
| containerName: 'Bar', |
| nameRange: rangeAtSearch('Bar() {', otherCode, 'Bar'), |
| codeRange: otherCode.ranges[2].sourceRange, |
| ranges: [rangeAfter('foo4 = ')], |
| ), |
| _isResult( |
| CallHierarchyKind.method, |
| 'bar', |
| otherFile, |
| containerName: 'Bar', |
| nameRange: rangeAtSearch('bar() {', otherCode, 'bar'), |
| codeRange: otherCode.ranges[3].sourceRange, |
| ranges: [rangeAfter('foo5 = '), rangeAfter('foo6 = ')], |
| ), |
| ]), |
| ); |
| } |
| |
| Future<void> test_getter() async { |
| var code = TestCode.parse(''' |
| String get f^oo => ''; |
| '''); |
| |
| var otherCode = TestCode.parse(''' |
| import 'test.dart'; |
| |
| final foo1 = foo; |
| /*[0*/class Bar { |
| final foo2 = foo; |
| /*[1*/Foo get foo3 => foo;/*1]*/ |
| /*[2*/Bar() { |
| final foo4 = foo; |
| }/*2]*/ |
| /*[3*/void bar() { |
| final foo5 = foo; |
| final foo6 = foo; |
| }/*3]*/ |
| }/*0]*/ |
| '''); |
| |
| // Gets the expected range that follows the string [prefix]. |
| SourceRange rangeAfter(String prefix) => |
| rangeAfterPrefix(prefix, otherCode, 'foo'); |
| |
| newFile(otherFile, otherCode.code); |
| var calls = await findIncomingCalls(code); |
| expect( |
| calls, |
| unorderedEquals([ |
| _isResult( |
| CallHierarchyKind.file, |
| 'other.dart', |
| otherFile, |
| containerName: null, |
| nameRange: startOfFile, |
| codeRange: entireRange(otherCode), |
| ranges: [rangeAfter('foo1 = ')], |
| ), |
| _isResult( |
| CallHierarchyKind.class_, |
| 'Bar', |
| otherFile, |
| containerName: 'other.dart', |
| nameRange: rangeAtSearch('Bar {', otherCode, 'Bar'), |
| codeRange: otherCode.ranges[0].sourceRange, |
| ranges: [rangeAfter('foo2 = ')], |
| ), |
| _isResult( |
| CallHierarchyKind.property, |
| 'get foo3', |
| otherFile, |
| containerName: 'Bar', |
| nameRange: rangeAtSearch('foo3', otherCode), |
| codeRange: otherCode.ranges[1].sourceRange, |
| ranges: [rangeAfter('foo3 => ')], |
| ), |
| _isResult( |
| CallHierarchyKind.constructor, |
| 'Bar', |
| otherFile, |
| containerName: 'Bar', |
| nameRange: rangeAtSearch('Bar() {', otherCode, 'Bar'), |
| codeRange: otherCode.ranges[2].sourceRange, |
| ranges: [rangeAfter('foo4 = ')], |
| ), |
| _isResult( |
| CallHierarchyKind.method, |
| 'bar', |
| otherFile, |
| containerName: 'Bar', |
| nameRange: rangeAtSearch('bar() {', otherCode, 'bar'), |
| codeRange: otherCode.ranges[3].sourceRange, |
| ranges: [rangeAfter('foo5 = '), rangeAfter('foo6 = ')], |
| ), |
| ]), |
| ); |
| } |
| |
| Future<void> test_implicitConstructor() async { |
| // We still expect to be able to navigate with implicit constructors. This |
| // is done by the target being the class, but with a kind of Constructor. |
| var code = TestCode.parse(''' |
| // ignore_for_file: unused_local_variable |
| import 'other.dart'; |
| |
| [!void f() { |
| final foo1 = Fo^o(); |
| }!] |
| '''); |
| |
| var otherCode = TestCode.parse(''' |
| class Foo {} |
| |
| final foo2 = Foo(); |
| '''); |
| |
| // Gets the expected range that follows the string [prefix]. |
| SourceRange rangeAfter(String prefix, TestCode code) => |
| rangeAfterPrefix(prefix, code, 'Foo'); |
| |
| newFile(otherFile, otherCode.code); |
| var calls = await findIncomingCalls(code); |
| expect( |
| calls, |
| unorderedEquals([ |
| _isResult( |
| CallHierarchyKind.function, |
| 'f', |
| testFile.path, |
| containerName: 'test.dart', |
| nameRange: rangeAtSearch('f() {', code, 'f'), |
| codeRange: code.range.sourceRange, |
| ranges: [rangeAfter('foo1 = ', code)], |
| ), |
| _isResult( |
| CallHierarchyKind.file, |
| 'other.dart', |
| otherFile, |
| containerName: null, |
| nameRange: startOfFile, |
| codeRange: entireRange(otherCode), |
| ranges: [rangeAfter('foo2 = ', otherCode)], |
| ), |
| ]), |
| ); |
| } |
| |
| Future<void> test_method() async { |
| var code = TestCode.parse(''' |
| class Foo { |
| void myMet^hod() {} |
| } |
| '''); |
| |
| var otherCode = TestCode.parse(''' |
| import 'test.dart'; |
| |
| [!void f() { |
| Foo().myMethod(); |
| final tearoff = Foo().myMethod; |
| }!] |
| '''); |
| |
| // Gets the expected range that follows the string [prefix]. |
| SourceRange rangeAfter(String prefix) => |
| rangeAfterPrefix(prefix, otherCode, 'myMethod'); |
| |
| newFile(otherFile, otherCode.code); |
| var calls = await findIncomingCalls(code); |
| expect( |
| calls, |
| unorderedEquals([ |
| _isResult( |
| CallHierarchyKind.function, |
| 'f', |
| otherFile, |
| containerName: 'other.dart', |
| nameRange: rangeAtSearch('f() {', otherCode, 'f'), |
| codeRange: otherCode.range.sourceRange, |
| ranges: [rangeAfter('Foo().'), rangeAfter('tearoff = Foo().')], |
| ), |
| ]), |
| ); |
| } |
| |
| Future<void> test_method_from_augmentation() async { |
| var code = TestCode.parse(''' |
| part 'other.dart'; |
| |
| class Foo { |
| void myMet^hod() {} |
| } |
| '''); |
| |
| var otherCode = TestCode.parse(''' |
| part of 'test.dart'; |
| |
| augment class Foo { |
| [!void f() { |
| myMethod(); |
| }!] |
| } |
| '''); |
| |
| newFile(otherFile, otherCode.code); |
| var calls = await findIncomingCalls(code); |
| expect(calls, [ |
| _isResult( |
| CallHierarchyKind.method, |
| 'f', |
| otherFile, |
| containerName: 'Foo', |
| nameRange: rangeAtSearch('f() {', otherCode, 'f'), |
| codeRange: otherCode.range.sourceRange, |
| ), |
| ]); |
| } |
| |
| Future<void> test_methodInNullAwareElementInList() async { |
| var code = TestCode.parse(''' |
| class Foo { |
| bool? myMet^hod() => null; |
| } |
| '''); |
| |
| var otherCode = TestCode.parse(''' |
| import 'test.dart'; |
| |
| [!void f() { |
| dynamic tearoff; |
| <bool>[ |
| ?Foo().myMethod(), |
| ?tearoff = Foo().myMethod, |
| ]; |
| }!] |
| '''); |
| |
| // Gets the expected range that follows the string [prefix]. |
| SourceRange rangeAfter(String prefix) => |
| rangeAfterPrefix(prefix, otherCode, 'myMethod'); |
| |
| newFile(otherFile, otherCode.code); |
| var calls = await findIncomingCalls(code); |
| expect( |
| calls, |
| unorderedEquals([ |
| _isResult( |
| CallHierarchyKind.function, |
| 'f', |
| otherFile, |
| containerName: 'other.dart', |
| nameRange: rangeAtSearch('f() {', otherCode, 'f'), |
| codeRange: otherCode.range.sourceRange, |
| ranges: [rangeAfter('Foo().'), rangeAfter('tearoff = Foo().')], |
| ), |
| ]), |
| ); |
| } |
| |
| Future<void> test_methodInNullAwareElementInMapKey() async { |
| var code = TestCode.parse(''' |
| class Foo { |
| bool? myMet^hod() => null; |
| } |
| '''); |
| |
| var otherCode = TestCode.parse(''' |
| import 'test.dart'; |
| |
| [!void f() { |
| dynamic tearoff; |
| <bool, num>{ |
| ?Foo().myMethod(): 0, |
| ?tearoff = Foo().myMethod: 1, |
| }; |
| }!] |
| '''); |
| |
| // Gets the expected range that follows the string [prefix]. |
| SourceRange rangeAfter(String prefix) => |
| rangeAfterPrefix(prefix, otherCode, 'myMethod'); |
| |
| newFile(otherFile, otherCode.code); |
| var calls = await findIncomingCalls(code); |
| expect( |
| calls, |
| unorderedEquals([ |
| _isResult( |
| CallHierarchyKind.function, |
| 'f', |
| otherFile, |
| containerName: 'other.dart', |
| nameRange: rangeAtSearch('f() {', otherCode, 'f'), |
| codeRange: otherCode.range.sourceRange, |
| ranges: [rangeAfter('Foo().'), rangeAfter('tearoff = Foo().')], |
| ), |
| ]), |
| ); |
| } |
| |
| Future<void> test_methodInNullAwareElementInMapValue() async { |
| var code = TestCode.parse(''' |
| class Foo { |
| bool? myMet^hod() => null; |
| } |
| '''); |
| |
| var otherCode = TestCode.parse(''' |
| import 'test.dart'; |
| |
| [!void f() { |
| dynamic tearoff; |
| <String, bool>{ |
| "foo": ?Foo().myMethod(), |
| "bar": ?tearoff = Foo().myMethod, |
| }; |
| }!] |
| '''); |
| |
| // Gets the expected range that follows the string [prefix]. |
| SourceRange rangeAfter(String prefix) => |
| rangeAfterPrefix(prefix, otherCode, 'myMethod'); |
| |
| newFile(otherFile, otherCode.code); |
| var calls = await findIncomingCalls(code); |
| expect( |
| calls, |
| unorderedEquals([ |
| _isResult( |
| CallHierarchyKind.function, |
| 'f', |
| otherFile, |
| containerName: 'other.dart', |
| nameRange: rangeAtSearch('f() {', otherCode, 'f'), |
| codeRange: otherCode.range.sourceRange, |
| ranges: [rangeAfter('Foo().'), rangeAfter('tearoff = Foo().')], |
| ), |
| ]), |
| ); |
| } |
| |
| Future<void> test_methodInNullAwareElementInSet() async { |
| var code = TestCode.parse(''' |
| class Foo { |
| bool? myMet^hod() => null; |
| } |
| '''); |
| |
| var otherCode = TestCode.parse(''' |
| import 'test.dart'; |
| |
| [!void f() { |
| dynamic tearoff; |
| <bool>{ |
| ?Foo().myMethod(), |
| ?tearoff = Foo().myMethod, |
| }; |
| }!] |
| '''); |
| |
| // Gets the expected range that follows the string [prefix]. |
| SourceRange rangeAfter(String prefix) => |
| rangeAfterPrefix(prefix, otherCode, 'myMethod'); |
| |
| newFile(otherFile, otherCode.code); |
| var calls = await findIncomingCalls(code); |
| expect( |
| calls, |
| unorderedEquals([ |
| _isResult( |
| CallHierarchyKind.function, |
| 'f', |
| otherFile, |
| containerName: 'other.dart', |
| nameRange: rangeAtSearch('f() {', otherCode, 'f'), |
| codeRange: otherCode.range.sourceRange, |
| ranges: [rangeAfter('Foo().'), rangeAfter('tearoff = Foo().')], |
| ), |
| ]), |
| ); |
| } |
| |
| Future<void> test_mixin_method() async { |
| var code = TestCode.parse(''' |
| mixin Bar { |
| void myMet^hod() {} |
| } |
| |
| class Foo with Bar {} |
| '''); |
| |
| var otherCode = TestCode.parse(''' |
| import 'test.dart'; |
| |
| [!void f() { |
| Foo().myMethod(); |
| }!] |
| '''); |
| |
| // Gets the expected range that follows the string [prefix]. |
| SourceRange rangeAfter(String prefix) => |
| rangeAfterPrefix(prefix, otherCode, 'myMethod'); |
| |
| newFile(otherFile, otherCode.code); |
| var calls = await findIncomingCalls(code); |
| expect( |
| calls, |
| unorderedEquals([ |
| _isResult( |
| CallHierarchyKind.function, |
| 'f', |
| otherFile, |
| containerName: 'other.dart', |
| nameRange: rangeAtSearch('f() {', otherCode, 'f'), |
| codeRange: otherCode.range.sourceRange, |
| ranges: [rangeAfter('Foo().')], |
| ), |
| ]), |
| ); |
| } |
| |
| Future<void> test_namedConstructor() async { |
| var code = TestCode.parse(''' |
| class Foo { |
| Foo.B^ar(); |
| } |
| '''); |
| |
| var otherCode = TestCode.parse(''' |
| import 'test.dart'; |
| |
| [!void f() { |
| final foo = Foo.Bar(); |
| }!] |
| '''); |
| |
| // Gets the expected range that follows the string [prefix]. |
| SourceRange rangeAfter(String prefix) => |
| rangeAfterPrefix(prefix, otherCode, 'Bar'); |
| |
| newFile(otherFile, otherCode.code); |
| var calls = await findIncomingCalls(code); |
| expect( |
| calls, |
| unorderedEquals([ |
| _isResult( |
| CallHierarchyKind.function, |
| 'f', |
| otherFile, |
| containerName: 'other.dart', |
| nameRange: rangeAtSearch('f() {', otherCode, 'f'), |
| codeRange: otherCode.range.sourceRange, |
| ranges: [rangeAfter('foo = Foo.')], |
| ), |
| ]), |
| ); |
| } |
| |
| Future<void> test_setter() async { |
| var code = TestCode.parse(''' |
| set fo^o(String value) {} |
| '''); |
| |
| var otherCode = TestCode.parse(''' |
| import 'test.dart'; |
| |
| class Bar { |
| /*[0*/Bar() { |
| /*a*/foo = ''; |
| }/*0]*/ |
| /*[1*/void bar() { |
| /*b*/foo = ''; |
| /*c*/foo = ''; |
| }/*1]*/ |
| } |
| '''); |
| |
| // Gets the expected range that follows the string [prefix]. |
| SourceRange rangeAfter(String prefix) => |
| rangeAfterPrefix(prefix, otherCode, 'foo'); |
| |
| newFile(otherFile, otherCode.code); |
| var calls = await findIncomingCalls(code); |
| expect( |
| calls, |
| unorderedEquals([ |
| _isResult( |
| CallHierarchyKind.constructor, |
| 'Bar', |
| otherFile, |
| containerName: 'Bar', |
| nameRange: rangeAtSearch('Bar() {', otherCode, 'Bar'), |
| codeRange: otherCode.ranges[0].sourceRange, |
| ranges: [rangeAfter('/*a*/')], |
| ), |
| _isResult( |
| CallHierarchyKind.method, |
| 'bar', |
| otherFile, |
| containerName: 'Bar', |
| nameRange: rangeAtSearch('bar() {', otherCode, 'bar'), |
| codeRange: otherCode.ranges[1].sourceRange, |
| ranges: [rangeAfter('/*b*/'), rangeAfter('/*c*/')], |
| ), |
| ]), |
| ); |
| } |
| } |
| |
| @reflectiveTest |
| class CallHierarchyComputerOutgoingCallsTest extends AbstractCallHierarchyTest { |
| late String otherFile; |
| |
| Future<List<CallHierarchyCalls>> findOutgoingCalls(TestCode code) async { |
| var target = (await findTarget(code))!; |
| return findOutgoingCallsForTarget(target); |
| } |
| |
| Future<List<CallHierarchyCalls>> findOutgoingCallsForTarget( |
| CallHierarchyItem target, |
| ) async { |
| var targetFile = getFile(target.file); |
| var result = await getResolvedUnit(targetFile); |
| expect(result.diagnostics, isEmpty); |
| |
| return DartCallHierarchyComputer(result).findOutgoingCalls(target); |
| } |
| |
| @override |
| void setUp() { |
| super.setUp(); |
| otherFile = convertPath('$testPackageLibPath/other.dart'); |
| } |
| |
| Future<void> test_constructor() async { |
| var code = TestCode.parse(''' |
| // ignore_for_file: unused_local_variable |
| import 'other.dart'; |
| |
| class Foo { |
| Fo^o() { |
| final a = A(); |
| final constructorTearoffA = A.new; |
| final b = B(); |
| final constructorTearoffB = B.new; |
| } |
| } |
| '''); |
| |
| var otherCode = TestCode.parse(''' |
| class A { |
| /*[0*/A();/*0]*/ |
| } |
| |
| /*[1*/class B { |
| }/*1]*/ |
| '''); |
| |
| newFile(otherFile, otherCode.code); |
| var calls = await findOutgoingCalls(code); |
| expect( |
| calls, |
| unorderedEquals([ |
| _isResult( |
| CallHierarchyKind.constructor, |
| 'A', |
| otherFile, |
| containerName: 'A', |
| nameRange: rangeAtSearch('A();', otherCode, 'A'), |
| codeRange: otherCode.ranges[0].sourceRange, |
| ranges: [ |
| rangeAtSearch('A()', code, 'A'), |
| rangeAfterPrefix('constructorTearoffA = A.', code, 'new'), |
| ], |
| ), |
| _isResult( |
| CallHierarchyKind.constructor, |
| 'B', |
| otherFile, |
| containerName: 'B', |
| nameRange: rangeAtSearch('B {', otherCode, 'B'), |
| codeRange: otherCode.ranges[1].sourceRange, |
| ranges: [ |
| rangeAtSearch('B()', code, 'B'), |
| rangeAfterPrefix('constructorTearoffB = B.', code, 'new'), |
| ], |
| ), |
| ]), |
| ); |
| } |
| |
| @SkippedTest() // TODO(scheglov): implement augmentation |
| Future<void> test_constructor_from_augmentation() async { |
| var code = TestCode.parse(''' |
| part 'other.dart'; |
| |
| class Foo {} |
| |
| void ba^r() { |
| Foo.named(); |
| } |
| '''); |
| |
| var otherCode = TestCode.parse(''' |
| part of 'test.dart'; |
| |
| augment class Foo { |
| [!Foo.named() { |
| }!] |
| } |
| '''); |
| |
| newFile(otherFile, otherCode.code); |
| var calls = await findOutgoingCalls(code); |
| expect(calls, [ |
| _isResult( |
| CallHierarchyKind.constructor, |
| 'Foo.named', |
| otherFile, |
| containerName: 'Foo', |
| nameRange: rangeAtSearch('named() {', otherCode, 'named'), |
| codeRange: otherCode.range.sourceRange, |
| ), |
| ]); |
| } |
| |
| Future<void> test_dotShorthand_constructor_named() async { |
| var code = TestCode.parse(''' |
| // ignore_for_file: unused_local_variable |
| import 'other.dart'; |
| |
| class Foo { |
| Foo.b^ar() { |
| A a = .named(); |
| } |
| } |
| '''); |
| |
| var otherCode = TestCode.parse(''' |
| class A { |
| [!A.named();!] |
| } |
| '''); |
| |
| newFile(otherFile, otherCode.code); |
| var calls = await findOutgoingCalls(code); |
| expect( |
| calls, |
| unorderedEquals([ |
| _isResult( |
| CallHierarchyKind.constructor, |
| 'A.named', |
| otherFile, |
| containerName: 'A', |
| nameRange: rangeAtSearch('named', otherCode), |
| codeRange: otherCode.range.sourceRange, |
| ranges: [rangeAfterPrefix('a = .', code, 'named')], |
| ), |
| ]), |
| ); |
| } |
| |
| Future<void> test_dotShorthand_constructor_unnamed() async { |
| var code = TestCode.parse(''' |
| // ignore_for_file: unused_local_variable |
| import 'other.dart'; |
| |
| class Foo { |
| Foo.b^ar() { |
| A a = .new(); |
| } |
| } |
| '''); |
| |
| var otherCode = TestCode.parse(''' |
| [!class A {}!] |
| '''); |
| |
| newFile(otherFile, otherCode.code); |
| var calls = await findOutgoingCalls(code); |
| expect( |
| calls, |
| unorderedEquals([ |
| _isResult( |
| CallHierarchyKind.constructor, |
| 'A', |
| otherFile, |
| containerName: 'A', |
| nameRange: rangeAtSearch('A {', otherCode, 'A'), |
| codeRange: otherCode.range.sourceRange, |
| ranges: [rangeAfterPrefix('a = .', code, 'new')], |
| ), |
| ]), |
| ); |
| } |
| |
| Future<void> test_dotShorthand_extensionType() async { |
| var code = TestCode.parse(''' |
| // ignore_for_file: unused_local_variable |
| import 'other.dart'; |
| |
| class Foo { |
| Foo.b^ar() { |
| A a = .getter; |
| } |
| } |
| '''); |
| |
| var otherCode = TestCode.parse(''' |
| extension type A(int x) { |
| [!static A get getter => A(1);!] |
| } |
| '''); |
| |
| newFile(otherFile, otherCode.code); |
| var calls = await findOutgoingCalls(code); |
| expect( |
| calls, |
| unorderedEquals([ |
| _isResult( |
| CallHierarchyKind.property, |
| 'get getter', |
| otherFile, |
| containerName: 'A', |
| nameRange: rangeAtSearch('getter', otherCode), |
| codeRange: otherCode.range.sourceRange, |
| ranges: [rangeAfterPrefix('a = .', code, 'getter')], |
| ), |
| ]), |
| ); |
| } |
| |
| Future<void> test_dotShorthand_getter() async { |
| var code = TestCode.parse(''' |
| // ignore_for_file: unused_local_variable |
| import 'other.dart'; |
| |
| class Foo { |
| Foo.b^ar() { |
| A a = .getter; |
| } |
| } |
| '''); |
| |
| var otherCode = TestCode.parse(''' |
| class A { |
| [!static A get getter => A();!] |
| } |
| '''); |
| |
| newFile(otherFile, otherCode.code); |
| var calls = await findOutgoingCalls(code); |
| expect( |
| calls, |
| unorderedEquals([ |
| _isResult( |
| CallHierarchyKind.property, |
| 'get getter', |
| otherFile, |
| containerName: 'A', |
| nameRange: rangeAtSearch('getter', otherCode), |
| codeRange: otherCode.range.sourceRange, |
| ranges: [rangeAfterPrefix('a = .', code, 'getter')], |
| ), |
| ]), |
| ); |
| } |
| |
| Future<void> test_dotShorthand_method() async { |
| var code = TestCode.parse(''' |
| // ignore_for_file: unused_local_variable |
| import 'other.dart'; |
| |
| class Foo { |
| Foo.b^ar() { |
| A a = .method(); |
| } |
| } |
| '''); |
| |
| var otherCode = TestCode.parse(''' |
| class A { |
| [!static A method() => A();!] |
| } |
| '''); |
| |
| newFile(otherFile, otherCode.code); |
| var calls = await findOutgoingCalls(code); |
| expect( |
| calls, |
| unorderedEquals([ |
| _isResult( |
| CallHierarchyKind.method, |
| 'method', |
| otherFile, |
| containerName: 'A', |
| nameRange: rangeAtSearch('method', otherCode), |
| codeRange: otherCode.range.sourceRange, |
| ranges: [rangeAfterPrefix('a = .', code, 'method')], |
| ), |
| ]), |
| ); |
| } |
| |
| Future<void> test_extension_method() async { |
| var code = TestCode.parse(''' |
| // ignore_for_file: unused_local_variable |
| import 'other.dart'; |
| |
| extension StringExtension on String { |
| void fo^o() { |
| ''.bar(); |
| final tearoff = ''.bar; |
| } |
| } |
| '''); |
| |
| var otherCode = TestCode.parse(''' |
| extension StringExtension on String { |
| [!void bar() {}!] |
| } |
| '''); |
| |
| newFile(otherFile, otherCode.code); |
| var calls = await findOutgoingCalls(code); |
| expect( |
| calls, |
| unorderedEquals([ |
| _isResult( |
| CallHierarchyKind.method, |
| 'bar', |
| otherFile, |
| containerName: 'StringExtension', |
| nameRange: rangeAtSearch('bar() {', otherCode, 'bar'), |
| codeRange: otherCode.range.sourceRange, |
| ranges: [ |
| rangeAtSearch('bar();', code, 'bar'), |
| rangeAtSearch('bar;', code, 'bar'), |
| ], |
| ), |
| ]), |
| ); |
| } |
| |
| Future<void> test_fileModifications() async { |
| var code = TestCode.parse(''' |
| void o^ne() { |
| two(); |
| } |
| void two() {} |
| '''); |
| |
| var target = (await findTarget(code))!; |
| |
| // Ensure there are some results before modification. |
| var calls = await findOutgoingCallsForTarget(target); |
| expect(calls, isNotEmpty); |
| |
| // Modify the file so that the target offset is no longer the original item. |
| updateTestSource(testCode.replaceAll('one()', 'three()')); |
| |
| // Ensure there are now no results for the original target. |
| calls = await findOutgoingCallsForTarget(target); |
| expect(calls, isEmpty); |
| } |
| |
| Future<void> test_function() async { |
| var code = TestCode.parse(''' |
| // ignore_for_file: unused_local_variable |
| import 'other.dart'; |
| |
| void fo^o() { |
| [!void nested() { |
| f(); // not a call of 'foo' |
| }!] |
| f(); // 1 |
| final tearoff = f; |
| nested(); |
| final nestedTearoff = nested; |
| } |
| '''); |
| |
| var otherCode = TestCode.parse(''' |
| [!void f() {}!] |
| '''); |
| |
| newFile(otherFile, otherCode.code); |
| var calls = await findOutgoingCalls(code); |
| expect( |
| calls, |
| unorderedEquals([ |
| _isResult( |
| CallHierarchyKind.function, |
| 'f', |
| otherFile, |
| containerName: 'other.dart', |
| nameRange: rangeAtSearch('f() {', otherCode, 'f'), |
| codeRange: otherCode.range.sourceRange, |
| ranges: [ |
| rangeAtSearch('f(); // 1', code, 'f'), |
| rangeAfterPrefix('tearoff = ', code, 'f'), |
| ], |
| ), |
| _isResult( |
| CallHierarchyKind.function, |
| 'nested', |
| testFile.path, |
| containerName: 'foo', |
| nameRange: rangeAtSearch('nested() {', code, 'nested'), |
| codeRange: code.range.sourceRange, |
| ranges: [ |
| rangeAtSearch('nested();', code, 'nested'), |
| rangeAfterPrefix('nestedTearoff = ', code, 'nested'), |
| ], |
| ), |
| ]), |
| ); |
| } |
| |
| Future<void> test_getter() async { |
| var code = TestCode.parse(''' |
| // ignore_for_file: unused_local_variable |
| import 'other.dart'; |
| |
| String get fo^o { |
| final a = A(); |
| final b = a.b; |
| final c = A().b; |
| return ''; |
| } |
| '''); |
| |
| var otherCode = TestCode.parse(''' |
| /*[0*/class A { |
| /*[1*/String get b => '';/*1]*/ |
| }/*0]*/ |
| '''); |
| |
| newFile(otherFile, otherCode.code); |
| var calls = await findOutgoingCalls(code); |
| expect( |
| calls, |
| unorderedEquals([ |
| _isResult( |
| CallHierarchyKind.constructor, |
| 'A', |
| otherFile, |
| containerName: 'A', |
| nameRange: rangeAtSearch('A {', otherCode, 'A'), |
| codeRange: otherCode.ranges[0].sourceRange, |
| ), |
| _isResult( |
| CallHierarchyKind.property, |
| 'get b', |
| otherFile, |
| containerName: 'A', |
| nameRange: rangeAtSearch('b => ', otherCode, 'b'), |
| codeRange: otherCode.ranges[1].sourceRange, |
| ranges: [ |
| rangeAfterPrefix('a.', code, 'b'), |
| rangeAfterPrefix('A().', code, 'b'), |
| ], |
| ), |
| ]), |
| ); |
| } |
| |
| Future<void> test_implicitConstructor() async { |
| // We can still begin navigating from an implicit constructor (so we can |
| // search for inbound calls), so we should ensure that trying to fetch |
| // outbound calls returns empty (and doesn't fail). |
| var code = TestCode.parse(''' |
| // ignore_for_file: unused_local_variable |
| import 'other.dart'; |
| |
| void f() { |
| final foo1 = Fo^o(); |
| } |
| '''); |
| |
| var otherCode = TestCode.parse(''' |
| class Foo {} |
| '''); |
| |
| newFile(otherFile, otherCode.code); |
| var calls = await findOutgoingCalls(code); |
| expect(calls, isEmpty); |
| } |
| |
| Future<void> test_method() async { |
| var code = TestCode.parse(''' |
| // ignore_for_file: unused_local_variable |
| import 'other.dart'; |
| |
| class Foo { |
| void fo^o() { |
| final a = A(); |
| a.bar(); |
| final tearoff = a.bar; |
| // non-calls |
| var x = 1; |
| var y = x; |
| a.field; |
| } |
| } |
| '''); |
| |
| var otherCode = TestCode.parse(''' |
| /*[0*/class A { |
| String field; |
| /*[1*/void bar() {}/*1]*/ |
| }/*0]*/ |
| '''); |
| |
| newFile(otherFile, otherCode.code); |
| var calls = await findOutgoingCalls(code); |
| expect( |
| calls, |
| unorderedEquals([ |
| _isResult( |
| CallHierarchyKind.constructor, |
| 'A', |
| otherFile, |
| containerName: 'A', |
| nameRange: rangeAtSearch('A {', otherCode, 'A'), |
| codeRange: otherCode.ranges[0].sourceRange, |
| ), |
| _isResult( |
| CallHierarchyKind.method, |
| 'bar', |
| otherFile, |
| containerName: 'A', |
| nameRange: rangeAtSearch('bar() {', otherCode, 'bar'), |
| codeRange: otherCode.ranges[1].sourceRange, |
| ranges: [ |
| rangeAfterPrefix('a.', code, 'bar'), |
| rangeAfterPrefix('tearoff = a.', code, 'bar'), |
| ], |
| ), |
| ]), |
| ); |
| } |
| |
| @FailingTest() // TODO(scheglov): implement augmentation |
| Future<void> test_method_from_augmentation() async { |
| var code = TestCode.parse(''' |
| part 'other.dart'; |
| |
| class Foo {} |
| |
| void ba^r() { |
| Foo().myMethod(); |
| } |
| '''); |
| |
| var otherCode = TestCode.parse(''' |
| part of 'test.dart'; |
| |
| augment class Foo { |
| [!void myMethod() { |
| }!] |
| } |
| '''); |
| |
| newFile(otherFile, otherCode.code); |
| var calls = await findOutgoingCalls(code); |
| expect( |
| calls, |
| contains( |
| _isResult( |
| CallHierarchyKind.method, |
| 'myMethod', |
| otherFile, |
| containerName: 'Foo', |
| nameRange: rangeAtSearch('myMethod() {', otherCode, 'myMethod'), |
| codeRange: otherCode.range.sourceRange, |
| ), |
| ), |
| ); |
| } |
| |
| Future<void> test_mixin_method() async { |
| var code = TestCode.parse(''' |
| // ignore_for_file: unused_local_variable |
| import 'other.dart'; |
| |
| mixin MyMixin { |
| void f^() { |
| final a = A(); |
| a.foo(); |
| A().foo(); |
| final tearoff = a.foo; |
| } |
| } |
| '''); |
| |
| var otherCode = TestCode.parse(''' |
| mixin OtherMixin { |
| /*[0*/void foo() {}/*0]*/ |
| } |
| /*[1*/class A with OtherMixin {}/*1]*/ |
| '''); |
| |
| newFile(otherFile, otherCode.code); |
| var calls = await findOutgoingCalls(code); |
| expect( |
| calls, |
| unorderedEquals([ |
| _isResult( |
| CallHierarchyKind.constructor, |
| 'A', |
| otherFile, |
| containerName: 'A', |
| nameRange: rangeAtSearch('A with', otherCode, 'A'), |
| codeRange: otherCode.ranges[1].sourceRange, |
| ), |
| _isResult( |
| CallHierarchyKind.method, |
| 'foo', |
| otherFile, |
| containerName: 'OtherMixin', |
| nameRange: rangeAtSearch('foo() {', otherCode, 'foo'), |
| codeRange: otherCode.ranges[0].sourceRange, |
| ranges: [ |
| rangeAfterPrefix('a.', code, 'foo'), |
| rangeAfterPrefix('A().', code, 'foo'), |
| rangeAfterPrefix('tearoff = a.', code, 'foo'), |
| ], |
| ), |
| ]), |
| ); |
| } |
| |
| Future<void> test_namedConstructor() async { |
| var code = TestCode.parse(''' |
| // ignore_for_file: unused_local_variable |
| import 'other.dart'; |
| |
| class Foo { |
| Foo.B^ar() { |
| final a = A.named(); |
| final constructorTearoff = A.named; |
| } |
| } |
| '''); |
| |
| var otherCode = TestCode.parse(''' |
| void f() {} |
| class A { |
| [!A.named();!] |
| } |
| '''); |
| |
| newFile(otherFile, otherCode.code); |
| var calls = await findOutgoingCalls(code); |
| expect( |
| calls, |
| unorderedEquals([ |
| _isResult( |
| CallHierarchyKind.constructor, |
| 'A.named', |
| otherFile, |
| containerName: 'A', |
| nameRange: rangeAtSearch('named', otherCode), |
| codeRange: otherCode.range.sourceRange, |
| ranges: [ |
| rangeAfterPrefix('a = A.', code, 'named'), |
| rangeAfterPrefix('constructorTearoff = A.', code, 'named'), |
| ], |
| ), |
| ]), |
| ); |
| } |
| |
| Future<void> test_namedConstructorInNullAwareElementInList() async { |
| var code = TestCode.parse(''' |
| // ignore_for_file: unused_local_variable |
| import 'other.dart'; |
| |
| class Foo { |
| Foo.B^ar(bool b) { |
| dynamic a1; |
| dynamic a2; |
| <Object>[ |
| ?a1 = b ? A.named() : null, |
| ?a2 = b ? A.named : null, |
| ]; |
| } |
| } |
| '''); |
| |
| var otherCode = TestCode.parse(''' |
| void f() {} |
| class A { |
| [!A.named();!] |
| } |
| '''); |
| |
| newFile(otherFile, otherCode.code); |
| var calls = await findOutgoingCalls(code); |
| expect( |
| calls, |
| unorderedEquals([ |
| _isResult( |
| CallHierarchyKind.constructor, |
| 'A.named', |
| otherFile, |
| containerName: 'A', |
| nameRange: rangeAtSearch('named', otherCode), |
| codeRange: otherCode.range.sourceRange, |
| ranges: [ |
| rangeAfterPrefix('?a1 = b ? A.', code, 'named'), |
| rangeAfterPrefix('?a2 = b ? A.', code, 'named'), |
| ], |
| ), |
| ]), |
| ); |
| } |
| |
| Future<void> test_namedConstructorInNullAwareElementInMapKey() async { |
| var code = TestCode.parse(''' |
| // ignore_for_file: unused_local_variable |
| import 'other.dart'; |
| |
| class Foo { |
| Foo.B^ar(bool b) { |
| dynamic a1; |
| dynamic a2; |
| <Object, Symbol>{ |
| ?a1 = b ? A.named() : null: #foo, |
| ?a2 = b ? A.named : null: #bar, |
| }; |
| } |
| } |
| '''); |
| |
| var otherCode = TestCode.parse(''' |
| void f() {} |
| class A { |
| [!A.named();!] |
| } |
| '''); |
| |
| newFile(otherFile, otherCode.code); |
| var calls = await findOutgoingCalls(code); |
| expect( |
| calls, |
| unorderedEquals([ |
| _isResult( |
| CallHierarchyKind.constructor, |
| 'A.named', |
| otherFile, |
| containerName: 'A', |
| nameRange: rangeAtSearch('named', otherCode), |
| codeRange: otherCode.range.sourceRange, |
| ranges: [ |
| rangeAfterPrefix('?a1 = b ? A.', code, 'named'), |
| rangeAfterPrefix('?a2 = b ? A.', code, 'named'), |
| ], |
| ), |
| ]), |
| ); |
| } |
| |
| Future<void> test_namedConstructorInNullAwareElementInMapValue() async { |
| var code = TestCode.parse(''' |
| // ignore_for_file: unused_local_variable |
| import 'other.dart'; |
| |
| class Foo { |
| Foo.B^ar(bool b) { |
| dynamic a1; |
| dynamic a2; |
| <Symbol, Object>{ |
| #foo: ?a1 = b ? A.named() : null, |
| #bar: ?a2 = b ? A.named : null, |
| }; |
| } |
| } |
| '''); |
| |
| var otherCode = TestCode.parse(''' |
| void f() {} |
| class A { |
| [!A.named();!] |
| } |
| '''); |
| |
| newFile(otherFile, otherCode.code); |
| var calls = await findOutgoingCalls(code); |
| expect( |
| calls, |
| unorderedEquals([ |
| _isResult( |
| CallHierarchyKind.constructor, |
| 'A.named', |
| otherFile, |
| containerName: 'A', |
| nameRange: rangeAtSearch('named', otherCode), |
| codeRange: otherCode.range.sourceRange, |
| ranges: [ |
| rangeAfterPrefix('?a1 = b ? A.', code, 'named'), |
| rangeAfterPrefix('?a2 = b ? A.', code, 'named'), |
| ], |
| ), |
| ]), |
| ); |
| } |
| |
| Future<void> test_namedConstructorInNullAwareElementInSet() async { |
| var code = TestCode.parse(''' |
| // ignore_for_file: unused_local_variable |
| import 'other.dart'; |
| |
| class Foo { |
| Foo.B^ar(bool b) { |
| dynamic a1; |
| dynamic a2; |
| <Object>{ |
| ?a1 = b ? A.named() : null, |
| ?a2 = b ? A.named : null, |
| }; |
| } |
| } |
| '''); |
| |
| var otherCode = TestCode.parse(''' |
| void f() {} |
| class A { |
| [!A.named();!] |
| } |
| '''); |
| |
| newFile(otherFile, otherCode.code); |
| var calls = await findOutgoingCalls(code); |
| expect( |
| calls, |
| unorderedEquals([ |
| _isResult( |
| CallHierarchyKind.constructor, |
| 'A.named', |
| otherFile, |
| containerName: 'A', |
| nameRange: rangeAtSearch('named', otherCode), |
| codeRange: otherCode.range.sourceRange, |
| ranges: [ |
| rangeAfterPrefix('?a1 = b ? A.', code, 'named'), |
| rangeAfterPrefix('?a2 = b ? A.', code, 'named'), |
| ], |
| ), |
| ]), |
| ); |
| } |
| |
| Future<void> test_prefixedTypes() async { |
| // Prefixed type names that are not tear-offs should never be included. |
| var code = TestCode.parse(''' |
| // ignore_for_file: unused_local_variable |
| import 'dart:io' as io; |
| |
| void ^f(io.File f) { |
| io.Directory? d; |
| } |
| '''); |
| |
| var calls = await findOutgoingCalls(code); |
| expect(calls, isEmpty); |
| } |
| |
| Future<void> test_setter() async { |
| var code = TestCode.parse(''' |
| import 'other.dart'; |
| |
| set fo^o(String value) { |
| final a = A(); |
| a.b = ''; |
| A().b = ''; |
| } |
| '''); |
| |
| var otherCode = TestCode.parse(''' |
| /*[0*/class A { |
| /*[1*/set b(String value) {}/*1]*/ |
| }/*0]*/ |
| '''); |
| |
| newFile(otherFile, otherCode.code); |
| var calls = await findOutgoingCalls(code); |
| expect( |
| calls, |
| unorderedEquals([ |
| _isResult( |
| CallHierarchyKind.constructor, |
| 'A', |
| otherFile, |
| containerName: 'A', |
| nameRange: rangeAtSearch('A {', otherCode, 'A'), |
| codeRange: otherCode.ranges[0].sourceRange, |
| ), |
| _isResult( |
| CallHierarchyKind.property, |
| 'set b', |
| otherFile, |
| containerName: 'A', |
| nameRange: rangeAtSearch('b(String ', otherCode, 'b'), |
| codeRange: otherCode.ranges[1].sourceRange, |
| ranges: [ |
| rangeAfterPrefix('a.', code, 'b'), |
| rangeAfterPrefix('A().', code, 'b'), |
| ], |
| ), |
| ]), |
| ); |
| } |
| } |