416050694c
Change-Id: I3d478721b4d2178cffe73dad66e3c95099e65532 Reviewed-on: https://dart-review.googlesource.com/c/sdk/+/481423 Commit-Queue: Brian Wilkerson <brianwilkerson@google.com> Reviewed-by: Brian Wilkerson <brianwilkerson@google.com> Reviewed-by: Samuel Rawlins <srawlins@google.com>
338 lines
8.3 KiB
Dart
338 lines
8.3 KiB
Dart
// Copyright (c) 2019, 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/lsp_protocol/protocol.dart';
|
|
import 'package:analyzer/src/test_utilities/test_code_format.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(ImplementationTest);
|
|
});
|
|
}
|
|
|
|
@reflectiveTest
|
|
class ImplementationTest extends AbstractLspAnalysisServerTest {
|
|
Future<void> test_class_excludesSelf() => _testMarkedContent('''
|
|
abstract class ^[!A!] {}
|
|
class B extends A {}
|
|
class C extends A {}
|
|
''', expectResults: false);
|
|
|
|
Future<void> test_class_excludesSuper() => _testMarkedContent('''
|
|
abstract class [!A!] {}
|
|
class ^B extends A {}
|
|
class C extends A {}
|
|
''', expectResults: false);
|
|
|
|
Future<void> test_class_sub() => _testMarkedContent('''
|
|
abstract class ^A {}
|
|
class /*[0*/B/*0]*/ extends A {}
|
|
class /*[1*/C/*1]*/ extends A {}
|
|
''');
|
|
|
|
Future<void> test_class_sub_underscore() => _testMarkedContent('''
|
|
abstract class ^_ {}
|
|
class /*[0*/B/*0]*/ extends _ {}
|
|
class /*[1*/C/*1]*/ extends _ {}
|
|
''');
|
|
|
|
Future<void> test_class_subSub() => _testMarkedContent('''
|
|
abstract class ^A {}
|
|
class /*[0*/B/*0]*/ extends A {}
|
|
class /*[1*/C/*1]*/ extends A {}
|
|
class /*[2*/D/*2]*/ extends B {}
|
|
class /*[3*/E/*3]*/ extends B {}
|
|
class /*[4*/F/*4]*/ extends E {}
|
|
''');
|
|
|
|
Future<void> test_emptyResults() async {
|
|
var content = '';
|
|
|
|
await initialize();
|
|
await openFile(mainFileUri, content);
|
|
var res = await getImplementations(mainFileUri, startOfDocPos);
|
|
|
|
expect(res, isEmpty);
|
|
}
|
|
|
|
Future<void> test_getter_declaring_implementedByDeclaring() =>
|
|
_testMarkedContent('''
|
|
abstract class A(final int val^ue);
|
|
|
|
class B(final int /*[0*/value/*0]*/) implements A;
|
|
''');
|
|
|
|
Future<void> test_getter_declaring_implementedByExplicit() =>
|
|
_testMarkedContent('''
|
|
abstract class A(final int va^lue);
|
|
|
|
class B implements A {
|
|
final int /*[0*/value/*0]*/;
|
|
B(this.value);
|
|
}
|
|
|
|
class C implements A {
|
|
int get /*[1*/value/*1]*/ => 0;
|
|
}
|
|
''');
|
|
|
|
Future<void> test_getter_explicit_implementedByDeclaring() =>
|
|
_testMarkedContent('''
|
|
abstract class A {
|
|
int get c^ount;
|
|
}
|
|
|
|
class B(final int /*[0*/count/*0]*/) implements A;
|
|
class C({required final int /*[1*/count/*1]*/}) implements A;
|
|
''');
|
|
|
|
Future<void> test_getter_overriddenByField() => _testMarkedContent('''
|
|
class B extends A {
|
|
final String? [!a!] = null;
|
|
}
|
|
|
|
abstract class A {
|
|
String? get a^;
|
|
}
|
|
''');
|
|
|
|
Future<void> test_getter_overriddenByField_underscore() =>
|
|
_testMarkedContent('''
|
|
class B extends A {
|
|
final String? [!_!] = null;
|
|
}
|
|
|
|
abstract class A {
|
|
String? get _^;
|
|
}
|
|
''');
|
|
|
|
Future<void> test_method_excludesClassesWithoutImplementations() =>
|
|
_testMarkedContent('''
|
|
abstract class A {
|
|
void ^b() {}
|
|
}
|
|
|
|
class B extends A {}
|
|
|
|
class [!E!] extends B {}
|
|
''', expectResults: false);
|
|
|
|
Future<void> test_method_excludesSelf() => _testMarkedContent('''
|
|
abstract class A {
|
|
void ^[!b!]();
|
|
}
|
|
|
|
class B extends A {
|
|
void b() {}
|
|
}
|
|
''', expectResults: false);
|
|
|
|
Future<void> test_method_excludesSuper() => _testMarkedContent('''
|
|
abstract class A {
|
|
void [!b!]();
|
|
}
|
|
|
|
class B extends A {
|
|
void ^b() {}
|
|
}
|
|
''', expectResults: false);
|
|
|
|
Future<void> test_method_fromCallSite() => _testMarkedContent('''
|
|
abstract class A {
|
|
void b();
|
|
}
|
|
|
|
class B extends A {
|
|
void /*[0*/b/*0]*/() {}
|
|
}
|
|
|
|
class C extends A {
|
|
void /*[1*/b/*1]*/() {}
|
|
}
|
|
|
|
class D extends B {
|
|
void /*[2*/b/*2]*/() {}
|
|
}
|
|
|
|
class E extends B {}
|
|
|
|
class F extends E {
|
|
void /*[3*/b/*3]*/() {}
|
|
}
|
|
|
|
void fromCallSite() {
|
|
A e = new E();
|
|
e.^b();
|
|
}
|
|
''');
|
|
|
|
Future<void> test_method_startOfParameterList() => _testMarkedContent('''
|
|
abstract class A {
|
|
void m^();
|
|
}
|
|
|
|
class B extends A {
|
|
void [!m!]() {}
|
|
}
|
|
''');
|
|
|
|
Future<void> test_method_startOfTypeParameterList() => _testMarkedContent('''
|
|
abstract class A {
|
|
void m^<T>();
|
|
}
|
|
|
|
class B extends A {
|
|
void [!m!]<T>() {}
|
|
}
|
|
''');
|
|
|
|
Future<void> test_method_sub() => _testMarkedContent('''
|
|
abstract class A {
|
|
void ^b();
|
|
}
|
|
|
|
class B extends A {
|
|
void /*[0*/b/*0]*/() {}
|
|
}
|
|
|
|
class C extends A {
|
|
void /*[1*/b/*1]*/() {}
|
|
}
|
|
''');
|
|
|
|
Future<void> test_method_sub_underscore() => _testMarkedContent('''
|
|
abstract class A {
|
|
void ^_();
|
|
}
|
|
|
|
class B extends A {
|
|
void /*[0*/_/*0]*/() {}
|
|
}
|
|
|
|
class C extends A {
|
|
void /*[1*/_/*1]*/() {}
|
|
}
|
|
''');
|
|
|
|
Future<void> test_method_subSub() => _testMarkedContent('''
|
|
abstract class A {
|
|
void ^b();
|
|
}
|
|
|
|
class B extends A {
|
|
void /*[0*/b/*0]*/() {}
|
|
}
|
|
|
|
class D extends B {
|
|
void /*[1*/b/*1]*/() {}
|
|
}
|
|
|
|
class E extends B {}
|
|
|
|
class F extends E {
|
|
void /*[2*/b/*2]*/() {}
|
|
}
|
|
''');
|
|
|
|
/// Check that implementations that come from mixins in other files return the
|
|
/// correct location for the implementation.
|
|
Future<void> test_mixins() async {
|
|
var mixinsContent = r'''
|
|
import 'main.dart';
|
|
|
|
mixin MyMixin implements MyInterface {
|
|
String get [!interfaceField!] => '';
|
|
}
|
|
''';
|
|
var content = r'''
|
|
import 'other.dart';
|
|
|
|
class A with MyMixin {}
|
|
class B with MyMixin {}
|
|
class C implements MyInterface {
|
|
String get [!interfaceField!] => '';
|
|
}
|
|
|
|
abstract class MyInterface {
|
|
String get interf^aceField;
|
|
}
|
|
''';
|
|
|
|
await _testMarkedContent(content, otherContent: mixinsContent);
|
|
}
|
|
|
|
Future<void> test_never_issue61420() => _testMarkedContent('''
|
|
Ne^ver value = throw '';
|
|
''', expectResults: false);
|
|
|
|
Future<void> test_nonDartFile() async {
|
|
newFile(pubspecFilePath, simplePubspecContent);
|
|
await initialize();
|
|
|
|
var res = await getImplementations(pubspecFileUri, startOfDocPos);
|
|
expect(res, isEmpty);
|
|
}
|
|
|
|
Future<void> test_setter_explicit_implementedByDeclaring() =>
|
|
_testMarkedContent('''
|
|
abstract class A {
|
|
set c^ount(int _);
|
|
}
|
|
|
|
class B(var int /*[0*/count/*0]*/) implements A;
|
|
class C({required var int /*[1*/count/*1]*/}) implements A;
|
|
''');
|
|
|
|
/// Parses [content] using as [TestCode] and invokes the
|
|
/// `textDocument/implementations` command at the marked position to verify
|
|
/// the marked regions are returned as implementations.
|
|
///
|
|
/// If [otherContent] is provided, will be written as `other.dart` alongside
|
|
/// the file and any marked regions will also be verified.
|
|
///
|
|
/// If [expectResults] is `false` then expects none of the ranges marked with
|
|
/// `/*[0*/brackets/*0]*/` to be present in the results instead.
|
|
Future<void> _testMarkedContent(
|
|
String content, {
|
|
String? otherContent,
|
|
bool expectResults = true,
|
|
}) async {
|
|
var otherFilePath = join(projectFolderPath, 'lib', 'other.dart');
|
|
var otherFileUri = pathContext.toUri(otherFilePath);
|
|
var code = TestCode.parse(content);
|
|
var otherCode = otherContent != null ? TestCode.parse(otherContent) : null;
|
|
if (otherCode != null) {
|
|
newFile(otherFilePath, otherCode.code);
|
|
}
|
|
newFile(mainFilePath, code.code);
|
|
|
|
await initialize();
|
|
|
|
var res = await getImplementations(mainFileUri, code.position.position);
|
|
|
|
var expectedLocations = [
|
|
for (final range in code.ranges)
|
|
Location(uri: mainFileUri, range: range.range),
|
|
if (otherCode != null)
|
|
for (final range in otherCode.ranges)
|
|
Location(uri: otherFileUri, range: range.range),
|
|
];
|
|
|
|
if (expectResults) {
|
|
expect(expectedLocations, isNotEmpty);
|
|
expect(res, unorderedEquals(expectedLocations));
|
|
} else {
|
|
for (var location in expectedLocations) {
|
|
expect(res, isNot(contains(location)));
|
|
}
|
|
}
|
|
}
|
|
}
|