blob: 542ae07b31d0862221dee0647166d80dbd840b85 [file] [edit]
// 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:analysis_server/src/lsp/constants.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 '../tool/lsp_spec/matchers.dart';
import '../utils/test_code_extensions.dart';
import 'server_abstract.dart';
void main() {
defineReflectiveSuite(() {
defineReflectiveTests(DocumentHighlightsTest);
});
}
@reflectiveTest
class DocumentHighlightsTest extends AbstractLspAnalysisServerTest {
Future<void> test_bound_topLevelVariable_wildcard() async {
await _testMarkedContent('''
var /*[0*/_/*0]*/ = 1;
void f() {
var _ = 2;
print(/*[1*/_/*1]*/);
}
''');
}
Future<void> test_catch_error() async {
await _testMarkedContent(r'''
void foo() {
try {} catch (/*[0*/error/*0]*/, stackTrace) {
print('$/*[1*/error/*1]*/, $stackTrace');
}
}
''');
}
Future<void> test_catch_stack() async {
await _testMarkedContent(r'''
void foo() {
try {} catch (error, /*[0*/stackTrace/*0]*/) {
print('$error, $/*[1*/stackTrace/*1]*/');
}
}
''');
}
Future<void> test_class_field_underscore() async {
await _testMarkedContent('''
class C {
int /*[0*/_/*0]*/ = 0;
}
void f(int _) {
int _ = 1;
C()./*[1*/_/*1]*/;
}
''');
}
Future<void> test_class_getterSetter() async {
await _testMarkedContent('''
class A {
int /*[0*/a/*0]*/ = 1;
}
class B extends A {
int get /*[1*/a/*1]*/ => 1;
set /*[2*/a/*2]*/(int _) {}
}
void f(A a, B b) {
a./*[3*/a/*3]*/ = b./*[4*/a/*4]*/;
b./*[5*/a/*5]*/ = a./*[6*/a/*6]*/;
}
''');
}
Future<void> test_constructor_secondary_className() async {
await _testMarkedContent('''
class Aaa {
/*[0*/Aaa/*0]*/();
}
void f() {
Aaa a = Aaa./*[1*/new/*1]*/();
var b = /*[2*/Aaa/*2]*/();
}
''');
}
/// The factory keyword is considered the constructor when there's no
/// constructor name.
Future<void> test_constructor_secondary_factory() async {
await _testMarkedContent('''
class Aaa {
new _();
/*[0*/factory/*0]*/() => Aaa._();
}
void f() {
Aaa a = Aaa./*[1*/new/*1]*/();
var b = /*[2*/Aaa/*2]*/();
}
''');
}
/// No matches for factory keyword when there is a constructor name.
Future<void>
test_constructor_secondary_factory_withoutType_named_keyword() async {
await _testMarkedContent('''
class Aaa {
new _();
fact^ory named() => Aaa._();
}
void f() {
Aaa a = Aaa.named();
}
''');
}
/// Names match names.
Future<void>
test_constructor_secondary_factory_withoutType_named_name() async {
await _testMarkedContent('''
class Aaa {
new _();
factory /*[0*/named/*0]*/() => Aaa._();
}
void f() {
Aaa a = Aaa./*[1*/named/*1]*/();
}
''');
}
/// No matches for factory keyword when there is a type name because the type
/// name is considered the constructor.
Future<void> test_constructor_secondary_factory_withType_keyword() async {
await _testMarkedContent('''
class Aaa {
new _();
fact^ory Aaa() => Aaa._();
}
void f() {
Aaa a = Aaa();
}
''');
}
/// Type names match type names for invocation/declaration.
Future<void> test_constructor_secondary_factory_withType_typeName() async {
await _testMarkedContent('''
class Aaa {
new _();
factory /*[0*/Aaa/*0]*/() => Aaa._();
}
void f() {
Aaa a = /*[1*/Aaa/*1]*/();
}
''');
}
Future<void> test_constructor_secondary_new() async {
await _testMarkedContent('''
class Aaa {
/*[0*/new/*0]*/();
}
void f() {
Aaa a = Aaa./*[1*/new/*1]*/();
var b = /*[2*/Aaa/*2]*/();
}
''');
}
/// No matches for new keyword when there is a constructor name.
Future<void>
test_constructor_secondary_new_withoutType_named_keyword() async {
await _testMarkedContent('''
class Aaa {
n^ew named();
}
void f() {
Aaa a = Aaa.named();
}
''');
}
/// When `new` is used as the constructor identifier, it's a match for
/// constructor invocations.
Future<void> test_constructor_secondary_new_withType_keyword() async {
await _testMarkedContent('''
class Aaa {
Aaa./*[0*/new/*0]*/();
}
void f() {
Aaa a = /*[1*/Aaa/*1]*/();
Aaa b = Aaa./*[2*/new/*2]*/();
}
''');
}
/// When `new` is used as the constructor identifier, the type name is a
/// match for the type name.
Future<void> test_constructor_secondary_new_withType_typeName() async {
await _testMarkedContent('''
class /*[0*/Aaa/*0]*/ {
/*[1*/Aaa/*1]*/.new();
}
void f() {
/*[2*/Aaa/*2]*/ a = Aaa();
/*[3*/Aaa/*3]*/ b = /*[4*/Aaa/*4]*/.new();
}
''');
}
Future<void> test_constructor_secondary_newNamed() async {
await _testMarkedContent('''
class Aaa {
new /*[0*/named/*0]*/();
}
void f() {
Aaa value = Aaa./*[1*/named/*1]*/();
Aaa./*[2*/named/*2]*/();
}
''');
}
Future<void> test_dartCode_issue5369_field() async {
await _testMarkedContent('''
class A {
var /*[0*/a/*0]*/ = [''].where((_) => true).toList();
List<String> f() {
return /*[1*/a/*1]*/;
}
}
var a; // Not a reference
''');
}
Future<void> test_dartCode_issue5369_functionType() async {
await _testMarkedContent('''
class A {
String m({ required String Function(String input) /*[0*/f/*0]*/ }) {
return /*[1*/f/*1]*/('');
}
}
var f; // Not a reference
''');
}
Future<void> test_dartCode_issue5369_localVariable() async {
await _testMarkedContent('''
class A {
List<String> f() {
var /*[0*/a/*0]*/ = [''].where((_) => true).toList();
return /*[1*/a/*1]*/;
}
}
var a; // Not a reference
''');
}
Future<void> test_dartCode_issue5369_topLevelVariable() async {
await _testMarkedContent('''
var /*[0*/a/*0]*/ = [''].where((_) => true).toList();
var b = /*[1*/a/*1]*/;
''');
}
Future<void> test_dotShorthand_class() async {
await _testMarkedContent('''
A topA = ./*[0*/a/*0]*/;
class A {
static A get /*[1*/a/*1]*/ => A();
}
void fn(A a) => print(a);
void f() {
A a = ./*[2*/a/*2]*/;
fn(./*[3*/a/*3]*/);
A aa = A./*[4*/a/*4]*/;
}
''');
}
Future<void> test_dotShorthand_enum() async {
await _testMarkedContent('''
const A constA = ./*[0*/a/*0]*/;
enum A { /*[1*/a/*1]*/ }
void fn(A a) => print(a);
void f() {
A a = ./*[2*/a/*2]*/;
fn(./*[3*/a/*3]*/);
A aa = A./*[4*/a/*4]*/;
}
''');
}
Future<void> test_dotShorthand_extensionType() async {
await _testMarkedContent('''
A topA = ./*[0*/a/*0]*/;
extension type A(int x) {
static A get /*[1*/a/*1]*/ => A(1);
}
void fn(A a) => print(a);
void f() {
A a = ./*[2*/a/*2]*/;
fn(./*[3*/a/*3]*/);
A aa = A./*[4*/a/*4]*/;
}
''');
}
Future<void> test_enum() async {
await _testMarkedContent('''
enum /*[0*/E/*0]*/ {
v;
}
void f(/*[1*/E/*1]*/ e) {
/*[2*/E/*2]*/.v;
}
''');
}
Future<void> test_enum_constant() async {
await _testMarkedContent('''
enum E {
/*[0*/v/*0]*/;
}
void f() {
E./*[1*/v/*1]*/;
}
''');
}
Future<void> test_enum_field() async {
await _testMarkedContent('''
enum E {
v;
final int /*[0*/foo/*0]*/ = 0;
}
void f(E e) {
e./*[1*/foo/*1]*/;
}
''');
}
Future<void> test_enum_getter() async {
await _testMarkedContent('''
enum E {
v;
int get /*[0*/foo/*0]*/ => 0;
}
void f(E e) {
e./*[1*/foo/*1]*/;
}
''');
}
Future<void> test_enum_method() async {
await _testMarkedContent('''
enum E {
v;
void /*[0*/foo/*0]*/() {}
}
void f(E e) {
e./*[1*/foo/*1]*/();
}
''');
}
Future<void> test_enum_setter() async {
await _testMarkedContent('''
enum E {
v;
set /*[0*/foo/*0]*/(int _) {}
}
void f(E e) {
e./*[1*/foo/*1]*/ = 0;
}
''');
}
Future<void> test_extension() async {
await _testMarkedContent('''
void foo(int i) {
/*[0*/E/*0]*/(i).self;
}
extension /*[1*/E/*1]*/<ThisType> on ThisType {
ThisType get self => this;
}
''');
}
Future<void> test_extensionMember() async {
await _testMarkedContent('''
extension on int {
int get /*[0*/foo/*0]*/ => 0;
}
void f(int v) {
v./*[1*/foo/*1]*/;
}
''');
}
Future<void> test_extensionMember_differentiation() async {
await _testMarkedContent('''
extension on String {
int get foo => 0;
}
extension on int {
int get /*[0*/foo/*0]*/ => 0;
}
void f(int v, String s) {
v./*[1*/foo/*1]*/;
s.foo;
}
''');
}
Future<void> test_extensionType() async {
await _testMarkedContent('''
extension type /*[0*/E/*0]*/(int it) {}
void f(/*[1*/E/*1]*/ e) {}
''');
}
Future<void> test_extensionType_constructor_primary() async {
await _testMarkedContent('''
extension type E./*[0*/named/*0]*/(int it) {}
void f() {
E./*[1*/named/*1]*/(0);
}
''');
}
Future<void> test_extensionType_constructor_secondary() async {
await _testMarkedContent('''
extension type E(int it) {
E./*[0*/named/*0]*/() : this(0);
}
void f() {
E./*[1*/named/*1]*/();
}
''');
}
Future<void> test_extensionType_getter() async {
await _testMarkedContent('''
extension type E(int it) {
int get /*[0*/foo/*0]*/ => 0;
}
void f(E e) {
e./*[1*/foo/*1]*/;
}
''');
}
Future<void> test_extensionType_method() async {
await _testMarkedContent('''
extension type E(int it) {
void /*[0*/foo/*0]*/() {}
}
void f(E e) {
e./*[1*/foo/*1]*/();
}
''');
}
Future<void> test_extensionType_setter() async {
await _testMarkedContent('''
extension type E(int it) {
set /*[0*/foo/*0]*/(int _) {}
}
void f(E e) {
e./*[1*/foo/*1]*/ = 0;
}
''');
}
Future<void> test_field() async {
await _testMarkedContent('''
class A {
int /*[0*/fff/*0]*/;
A(this./*[1*/fff/*1]*/);
void f() {
/*[2*/fff/*2]*/ = 42;
print(/*[3*/fff/*3]*/);
}
}
''');
}
Future<void> test_field_unresolved() async {
failTestOnErrorDiagnostic = false;
// Unresolved fields should not return any highlights (or error).
await _testMarkedContent('''
class A {
A(this.noSuc^hField);
}
''');
}
Future<void> test_forInLoop() async {
await _testMarkedContent('''
void f() {
for (final /*[0*/x/*0]*/ in []) {
/*[1*/x/*1]*/;
}
}
''');
}
Future<void> test_formalParameters_closure() async {
await _testMarkedContent('''
void f(void Function(int) _) {}
void g() => f((/*[0*/variable/*0]*/) {
print(/*[1*/variable/*1]*/);
});
''');
}
Future<void> test_formalParameters_function() async {
await _testMarkedContent('''
void f(int /*[0*/parameter/*0]*/) {
print(/*[1*/parameter/*1]*/);
}
''');
}
Future<void> test_formalParameters_method() async {
await _testMarkedContent('''
class C {
void m(int /*[0*/parameter/*0]*/) {
print(/*[1*/parameter/*1]*/);
}
}
''');
}
Future<void> test_functions() async {
await _testMarkedContent('''
/*[0*/main/*0]*/() {
/*[1*/main/*1]*/();
}
''');
}
Future<void> test_hierarchy_method_extends() async {
await _testMarkedContent('''
abstract class A {
void /*[0*/a/*0]*/();
}
class B extends A {
@override
void /*[1*/a/*1]*/() {}
}
''');
}
Future<void> test_hierarchy_method_extends_implements_fromLeaf() async {
await _testMarkedContent('''
abstract class A {
void /*[0*/a/*0]*/();
}
abstract class I {
void /*[1*/a/*1]*/(); // Included because we started at B.a
}
class B extends A implements I {
@override
void /*[2*/^a/*2]*/() {}
}
void f(A a, I i, B b) {
a./*[3*/a/*3]*/();
i./*[4*/a/*4]*/();
b./*[5*/a/*5]*/();
}
''');
}
Future<void> test_hierarchy_method_extends_implements_fromRoot() async {
await _testMarkedContent('''
abstract class A {
void /*[0*/^a/*0]*/();
}
abstract class I {
void a(); // Not included because we started at A.a
}
class B extends A implements I {
@override
void /*[1*/a/*1]*/() {}
}
void f(A a, I i, B b) {
a./*[2*/a/*2]*/();
i.a();
b./*[3*/a/*3]*/();
}
''');
}
Future<void> test_hierarchy_method_extends_with_fromLeaf() async {
await _testMarkedContent('''
abstract class A {
void /*[0*/a/*0]*/();
}
mixin M {
void /*[1*/a/*1]*/() {} // Included because we started at B.a
}
class B extends A with M {
@override
void /*[2*/^a/*2]*/() {}
}
void f(A a, M m, B b) {
a./*[3*/a/*3]*/();
m./*[4*/a/*4]*/();
b./*[5*/a/*5]*/();
}
''');
}
Future<void> test_hierarchy_method_extends_with_fromRoot() async {
await _testMarkedContent('''
abstract class A {
void /*[0*/a^/*0]*/();
}
mixin M {
void a() {} // Not included because we started at A.a
}
class B extends A with M {
@override
void /*[1*/a/*1]*/() {}
}
void f(A a, M m, B b) {
a./*[2*/a/*2]*/();
m.a();
b./*[3*/a/*3]*/();
}
''');
}
Future<void> test_hierarchy_method_extends_with_implements_fromLeaf() async {
await _testMarkedContent('''
abstract class A {
void /*[0*/a/*0]*/();
}
mixin M {
void /*[1*/a/*1]*/() {} // Included because we started from A.a
}
abstract class I {
void /*[2*/a/*2]*/(); // Included because we started from B.a
}
class B extends A with M implements I {
@override
void /*[3*/^a/*3]*/() {}
}
void f(A a, M m, I i, B b) {
a./*[4*/a/*4]*/();
m./*[5*/a/*5]*/();
i./*[6*/a/*6]*/();
b./*[7*/a/*7]*/();
}
''');
}
Future<void> test_hierarchy_method_extends_with_implements_fromRoot() async {
await _testMarkedContent('''
abstract class A {
void /*[0*/^a/*0]*/();
}
mixin M {
void a() {} // Not included because we started from A.a
}
abstract class I {
void a(); // Not included because we started from A.a
}
class B extends A with M implements I {
@override
void /*[1*/a/*1]*/() {}
}
void f(A a, M m, I i, B b) {
a./*[2*/a/*2]*/();
m.a();
i.a();
b./*[3*/a/*3]*/();
}
''');
}
Future<void> test_hierarchy_method_implements() async {
await _testMarkedContent('''
abstract class A {
void /*[0*/a/*0]*/();
}
class B implements A {
@override
void /*[1*/a/*1]*/() {}
}
''');
}
Future<void> test_hierarchy_method_with() async {
await _testMarkedContent('''
mixin M {
void /*[0*/a/*0]*/() {}
}
class B with M {
@override
void /*[1*/a/*1]*/() {}
}
''');
}
Future<void> test_hierarchy_method_with_implements_fromLeaf() async {
await _testMarkedContent('''
mixin M {
void /*[0*/a/*0]*/() {} // Included because we started at B.a
}
abstract class I {
void /*[1*/a/*1]*/(); // Included because we started at B.a
}
class B with M implements I {
@override
void /*[2*/^a/*2]*/() {}
}
void f(M m, I i, B b) {
m./*[3*/a/*3]*/();
i./*[4*/a/*4]*/();
b./*[5*/a/*5]*/();
}
''');
}
Future<void> test_hierarchy_method_with_implements_fromRoot() async {
await _testMarkedContent('''
mixin M {
void /*[0*/^a/*0]*/() {}
}
abstract class I {
void a(); // Not included because we started at M.a
}
class B with M implements I {
@override
void /*[1*/a/*1]*/() {}
}
void f(M m, I i, B b) {
m./*[2*/a/*2]*/();
i.a();
b./*[3*/a/*3]*/();
}
''');
}
Future<void> test_invalidLineByOne() async {
// Test that requesting a line that's too high by one returns a valid
// error response instead of throwing.
const content = '// single line';
await initialize();
await openFile(mainFileUri, content);
// Lines are zero-based so 1 is invalid.
var pos = Position(line: 1, character: 0);
var request = getDocumentHighlights(mainFileUri, pos);
await expectLater(
request,
throwsA(isResponseError(ServerErrorCodes.invalidFileLineCol)),
);
}
Future<void> test_keyword_loop_do() => _testLoop('do', '', 'while (true);');
Future<void> test_keyword_loop_for() => _testLoop('for', '(;;)', '');
Future<void> test_keyword_loop_while() => _testLoop('while', '(true)', '');
Future<void> test_keyword_loopWithSwitch_loopExit() async {
await _testMarkedContent('''
void f(int i) {
/*[0*/for/*0]*/ (;;) {
/*[1*/break/*1]*/;
/*[2*/continue/*2]*/;
switch (i) {
case 1:
break;
/*[3*/continue/*3]*/;
case 2:
break;
/*[4*/continue/*4]*/;
}
}
}
''');
}
Future<void> test_keyword_loopWithSwitch_switchExit() async {
await _testMarkedContent('''
void f(int i) {
for (;;) {
break;
continue;
/*[0*/switch/*0]*/ (i) {
case 1:
/*[1*/break/*1]*/;
continue;
case 2:
/*[2*/break/*2]*/;
continue;
}
}
}
''');
}
Future<void> test_keyword_return_function() async {
await _testMarkedContent('''
int f() {
if (true) /*[0*/return/*0]*/ 1;
/*[1*/return/*1]*/ 2;
}
''');
}
Future<void> test_keyword_return_insideLoop() async {
await _testMarkedContent('''
int f(int i) {
for (;;) {
switch (i) {
case 1:
/*[0*/return/*0]*/ 1;
case 2:
/*[1*/return/*1]*/ 2;
break;
continue;
}
}
/*[2*/return/*2]*/ 2;
}
''');
}
Future<void> test_keyword_return_method() async {
await _testMarkedContent('''
class C {
int m() {
if (true) /*[0*/return/*0]*/ 1;
/*[1*/return/*1]*/ 2;
}
}
''');
}
Future<void> test_keyword_return_nestedClosure() async {
await _testMarkedContent('''
int outerFunction() {
var a = () {
var b = () {
var c = () {
return 1;
};
if (true) /*[0*/return/*0]*/ 1;
/*[1*/return/*1]*/ 0;
};
return 1;
};
return 1;
}
''');
}
Future<void> test_keyword_return_nestedFunction() async {
await _testMarkedContent('''
int outerFunction() {
int middleFunction() {
int innerFunction() {
return 1;
}
if (true) /*[0*/return/*0]*/ 1;
/*[1*/return/*1]*/ 2;
}
return 1;
}
''');
}
Future<void> test_keyword_yield_asyncGenerator() async {
await _testMarkedContent('''
Stream<int> outerFunction() async* {
Stream<int> middleFunction() async* {
Stream<int> innerFunction() async* {
yield 1;
yield* Stream.value(0);
}
if (true) /*[0*/yield/*0]*/ 1;
if (true) /*[1*/yield/*1]*/* Stream.value(0);
/*[2*/yield/*2]*/ 2;
/*[3*/yield/*3]*/* Stream.value(0);
}
yield 1;
yield* Stream.value(0);
}
''');
}
Future<void> test_keyword_yield_syncGenerator() async {
await _testMarkedContent('''
Iterable<int> outerFunction() sync* {
Iterable<int> middleFunction() sync* {
Iterable<int> innerFunction() sync* {
yield 1;
yield* Iterable.empty();
}
if (true) /*[0*/yield/*0]*/ 1;
if (true) /*[1*/yield/*1]*/* Iterable.empty();
/*[2*/yield/*2]*/ 2;
/*[3*/yield/*3]*/* Iterable.empty();
}
yield 1;
yield* Iterable.empty();
}
''');
}
Future<void> test_localVariable() async {
await _testMarkedContent('''
void f() {
var /*[0*/foo/*0]*/ = 1;
print(/*[1*/foo/*1]*/);
/*[2*/foo/*2]*/ = 2;
}
''');
}
Future<void> test_member_field() async {
await _testMarkedContent('''
class A<T> {
T /*[0*/fff/*0]*/;
A(this./*[1*/fff/*1]*/);
}
void f() {
var a = A<int>(1);
var b = A<String>('');
a./*[2*/fff/*2]*/ = 1;
b./*[3*/fff/*3]*/ = '';
}
''');
}
Future<void> test_member_method() async {
await _testMarkedContent('''
class A<T> {
T /*[0*/mmm/*0]*/() => throw 0;
}
void f() {
var a = A<int>();
var b = A<String>();
a./*[1*/mmm/*1]*/();
b./*[2*/mmm/*2]*/();
}
''');
}
Future<void> test_method_underscore() async {
await _testMarkedContent('''
class C {
/*[0*/_/*0]*/() {
/*[1*/_/*1]*/();
}
}
''');
}
Future<void> test_mixin() async {
await _testMarkedContent('''
mixin /*[0*/A/*0]*/ {
void aaa() {}
}
class B with /*[1*/A/*1]*/ {}
''');
}
Future<void> test_nonDartFile() async {
await initialize();
await openFile(pubspecFileUri, simplePubspecContent);
var highlights = await getDocumentHighlights(pubspecFileUri, startOfDocPos);
// Non-Dart files should return empty results, not errors.
expect(highlights, isEmpty);
}
Future<void> test_noResult() async {
await _testMarkedContent('''
void f() {
// This one is in a ^ comment!
}
''');
}
Future<void> test_onlySelf() async {
await _testMarkedContent('''
void f() {
/*[0*/print/*0]*/('');
}
''');
}
Future<void> test_onlySelf_wildcard() async {
await _testMarkedContent('''
void f() {
var /*[0*/_/*0]*/ = '';
}
''');
}
Future<void> test_parameter_named() async {
await _testMarkedContent('''
void f(int aaa, int bbb, {int? /*[0*/ccc/*0]*/, int? ddd}) {
/*[1*/ccc/*1]*/;
ddd;
}
void g() {
f(0, 1, /*[2*/ccc/*2]*/: 2, ddd: 3);
}
''');
}
Future<void> test_parameter_privateNamed() async {
await _testMarkedContent('''
class C {
int? /*[0*/_aaa/*0]*/;
C({this./*[1*/_aaa/*1]*/});
}
void f() {
C(/*[2*/aaa/*2]*/: 123);
}
''');
}
Future<void> test_parameter_wildcard() async {
await _testMarkedContent('''
void f(int /*[0*/_/*0]*/) {}
''');
}
Future<void> test_pattern_assignment() async {
await _testMarkedContent('''
void f(String /*[0*/a/*0]*/, String b) {
(b, /*[1*/a/*1]*/) = (/*[2*/a/*2]*/, b);
}
''');
}
Future<void> test_pattern_assignment_list() async {
await _testMarkedContent('''
void f(List<int> x, num /*[0*/a/*0]*/) {
[/*[1*/a/*1]*/] = x;
}
''');
}
Future<void> test_pattern_cast_typeName() async {
await _testMarkedContent('''
/*[0*/String/*0]*/ f((num, /*[1*/String/*1]*/) record) {
var (i as int, s as /*[2*/String/*2]*/) = record;
return s;
}
''');
}
Future<void> test_pattern_cast_variable() async {
await _testMarkedContent('''
void f((num, String) record) {
var (i as int, /*[0*/s/*0]*/ as String) = record;
print(/*[1*/s/*1]*/);
}
''');
}
Future<void> test_pattern_map() async {
await _testMarkedContent('''
void f(x) {
switch (x) {
case {0: String /*[0*/a/*0]*/}:
print(/*[1*/a/*1]*/);
break;
}
}
''');
}
Future<void> test_pattern_map_typeArguments() async {
await _testMarkedContent('''
/*[0*/String/*0]*/ f(x) {
switch (x) {
case <int, /*[1*/String/*1]*/>{0: var a}:
return a;
break;
}
return '';
}
''');
}
Future<void> test_pattern_nullAssert() async {
await _testMarkedContent('''
void f((int?, int?) position) {
var (x!, /*[0*/y/*0]*/!) = position;
print(/*[1*/y/*1]*/);
}
''');
}
Future<void> test_pattern_nullCheck() async {
await _testMarkedContent('''
void f(String? maybeString) {
switch (maybeString) {
case var /*[0*/s/*0]*/?:
print(/*[1*/s/*1]*/);
}
}
''');
}
Future<void> test_pattern_object_destructure() async {
await _testMarkedContent('''
void f() {
final MapEntry(:/*[0*/key/*0]*/) = const MapEntry<String, int>('a', 1);
if (const MapEntry('a', 1) case MapEntry(:final /*[1*/ke^y/*1]*/)) {
/*[2*/key/*2]*/;
}
}
''');
}
Future<void> test_pattern_object_destructure_getter() async {
await _testMarkedContent('''
class A {
String? /*[0*/key/*0]*/;
}
void f() {
final A(:/*[1*/key/*1]*/) = A();
}
''');
}
Future<void> test_pattern_object_destructure_variable() async {
await _testMarkedContent('''
class A {
String? /*[0*/key/*0]*/;
}
void f() {
final A(:/*[1*/k^ey/*1]*/) = A();
/*[2*/key/*2]*/;
}
''');
}
Future<void> test_pattern_object_fieldName() async {
await _testMarkedContent('''
double calculateArea(Shape shape) =>
switch (shape) {
Square(/*[0*/length/*0]*/: var l) => l * l,
Shape() => 0.0,
};
class Shape { }
class Square extends Shape {
double get /*[1*/length/*1]*/ => 0;
}
''');
}
Future<void> test_pattern_object_typeName() async {
await _testMarkedContent('''
double calculateArea(Shape shape) =>
switch (shape) {
/*[0*/Square/*0]*/(length: var l) => l * l,
Shape() => 0.0,
};
class Shape { }
class /*[1*/Square/*1]*/ extends Shape {
double get length => 0;
}
''');
}
Future<void> test_pattern_object_variable() async {
await _testMarkedContent('''
double calculateArea(Shape shape) =>
switch (shape) {
Square(length: var /*[0*/l/*0]*/) => /*[1*/l/*1]*/ * /*[2*/l/*2]*/,
Shape() => 0.0,
};
class Shape { }
class Square extends Shape {
double get length => 0;
}
''');
}
Future<void> test_pattern_record_variable() async {
await _testMarkedContent('''
void f(({int foo}) x, num /*[0*/a/*0]*/) {
(foo: /*[1*/a/*1]*/,) = x;
}
''');
}
Future<void> test_pattern_relational_variable() async {
await _testMarkedContent('''
String f(int char) {
const /*[0*/zero/*0]*/ = 0;
return switch (char) {
== /*[1*/zero/*1]*/ => 'zero',
_ => '',
};
}
''');
}
Future<void> test_patternVariable_ifCase_logicalOr() async {
await _testMarkedContent('''
void f(Object? x) {
if (x case int /*[0*/test/*0]*/ || [int /*[1*/test/*1]*/] when /*[2*/test/*2]*/ > 0) {
/*[3*/test/*3]*/;
/*[4*/test/*4]*/ = 1;
/*[5*/test/*5]*/ += 2;
}
}
''');
}
Future<void> test_prefix() async {
await _testMarkedContent('''
import '' as /*[0*/p/*0]*/;
class A {
void m() {
/*[1*/p/*1]*/.foo();
print(/*[2*/p/*2]*/.a);
/*[3*/p/*3]*/.A();
}
}
void foo() {}
/*[4*/p/*4]*/.A? a;
''');
}
Future<void> test_prefix_null() async {
failTestOnErrorDiagnostic = false;
// Note, we use `core` just to have some prefix.
// The actual check is no crash on unresolved `prefix`.
await _testMarkedContent('''
import 'dart:core' as /*[0*/core/*0]*/;
void f(prefix.A? _, /*[1*/core/*1]*/.int _) {}
''');
}
Future<void> test_prefix_wildcard() async {
// Ensure no crash.
await _testMarkedContent('''
import 'dart:io' as /*[0*/_/*0]*/;
''');
}
Future<void> test_prefixed() async {
await _testMarkedContent('''
import '' as p;
class /*[0*/A/*0]*/ {}
p./*[1*/A/*1]*/? a;
''');
}
/// The body/this keyword isn't an occurrence of anything.
Future<void> test_primaryConstructor_body() async {
await _testMarkedContent('''
class Aaa() {
th^is {}
}
Aaa a = Aaa();
''');
}
/// The body/this keyword isn't an occurrence of anything.
Future<void> test_primaryConstructor_named_body() async {
await _testMarkedContent('''
class Aaa.named() {
th^is {}
}
Aaa a = Aaa.named();
''');
}
Future<void> test_primaryConstructor_named_constructorName() async {
await _testMarkedContent('''
class Aaa./*[0*/named/*0]*/() {
this {}
}
Aaa a = Aaa./*[1*/named/*1]*/();
''');
}
Future<void> test_primaryConstructor_named_typeName() async {
await _testMarkedContent('''
class /*[0*/Aaa/*0]*/.named() {
this {}
}
/*[1*/Aaa/*1]*/ a = /*[2*/Aaa/*2]*/.named();
''');
}
Future<void> test_primaryConstructor_typeName() async {
await _testMarkedContent('''
class /*[0*/Aaa/*0]*/() {
this {}
}
/*[1*/Aaa/*1]*/ a = Aaa();
''');
}
Future<void> test_primaryConstructor_typeName_constructorInvocation() async {
await _testMarkedContent('''
class Aaa() {
this {}
}
Aaa a = /*[0*/Aaa/*0]*/();
''');
}
Future<void> test_recordType_typeName() async {
await _testMarkedContent('''
/*[0*/double/*0]*/ f((/*[1*/double/*1]*/, /*[2*/double/*2]*/) param) {
return 0.0;
}
''');
}
Future<void> test_shadow_inner() async {
await _testMarkedContent('''
void f() {
var foo = 1;
func() {
var /*[0*/foo/*0]*/ = 2;
print(/*[1*/foo/*1]*/);
}
}
''');
}
Future<void> test_shadow_outer() async {
await _testMarkedContent('''
void f() {
var /*[0*/foo/*0]*/ = 1;
func() {
var foo = 2;
print(foo);
}
print(/*[1*/foo/*1]*/);
}
''');
}
Future<void> test_superFormalParameter_requiredPositional() async {
await _testMarkedContent('''
class A {
A(int x);
}
class B extends A {
int y;
B(super./*[0*/x/*0]*/) : y = /*[1*/x/*1]*/ * 2;
}
''');
}
Future<void> test_topLevelVariable() async {
await _testMarkedContent('''
String /*[0*/foo/*0]*/ = 'bar';
void f() {
print(/*[1*/foo/*1]*/);
/*[2*/foo/*2]*/ = '';
}
''');
}
Future<void> test_topLevelVariable_underscore() async {
await _testMarkedContent('''
String /*[0*/_/*0]*/ = 'bar';
void f(int _) {
int _ = 1;
print(/*[1*/_/*1]*/);
/*[2*/_/*2]*/ = '';
}
''');
}
Future<void> test_type_class() async {
await _testMarkedContent('''
void f() {
/*[0*/int/*0]*/ a = 1;
/*[1*/int/*1]*/ b = 2;
/*[2*/int/*2]*/ c = 3;
}
/*[3*/int/*3]*/ VVV = 4;
''');
}
Future<void> test_type_class_constructors() async {
await _testMarkedContent('''
class /*[0*/A/*0]*/ {
A(); // Default constructor is own entity
/*[1*/A/*1]*/.named();
}
/*[2*/A/*2]*/ a = A(); // Default constructor is own entity
var b = /*[3*/A/*3]*/.new();
var c = /*[4*/A/*4]*/.new;
''');
}
Future<void> test_type_class_constructors2() async {
await _testMarkedContent('''
class /*[0*/A/*0]*/ {
new(); // Default constructor is own entity
/*[1*/A/*1]*/.named();
}
/*[2*/A/*2]*/ a = A(); // Default constructor is own entity
var b = /*[3*/A/*3]*/.new();
var c = /*[4*/A/*4]*/.new;
''');
}
/// The type name in default constructors are their own entity and not
/// part of the type name.
Future<void> test_type_class_constructors_default() async {
await _testMarkedContent('''
class A {
/*[0*/A/*0]*/();
A.named();
}
A a = /*[1*/A/*1]*/();
var b = A./*[2*/new/*2]*/();
var c = A./*[3*/new/*3]*/;
''');
}
Future<void> test_type_class_definition() async {
await _testMarkedContent('''
class /*[0*/A/*0]*/ {}
/*[1*/A/*1]*/? a;
''');
}
Future<void> test_type_dynamic() async {
await _testMarkedContent('''
void f() {
/*[0*/dynamic/*0]*/ a = 1;
/*[1*/dynamic/*1]*/ b = 2;
}
/*[2*/dynamic/*2]*/ V = 3;
''');
}
Future<void> test_type_void() async {
await _testMarkedContent('''
vo^id f() {}
''');
}
Future<void> test_typeAlias_class() async {
await _testMarkedContent('''
class MyClass {}
mixin MyMixin {}
class /*[0*/MyAlias/*0]*/ = MyClass with MyMixin;
/*[1*/MyAlias/*1]*/? a;
''');
}
Future<void> test_typeAlias_function() async {
await _testMarkedContent('''
typedef /*[0*/myFunc/*0]*/();
/*[1*/myFunc/*1]*/? f;
''');
}
Future<void> test_typeAlias_generic() async {
await _testMarkedContent('''
typedef /*[0*/TD/*0]*/ = String;
/*[1*/TD/*1]*/? a;
''');
}
Future<void> test_typeParameter_class() async {
await _testMarkedContent('''
abstract class A</*[0*/ThisType/*0]*/> {
/*[1*/ThisType/*1]*/ f();
}
''');
}
Future<void> test_typeParameter_enum() async {
await _testMarkedContent('''
enum E</*[0*/ThisType/*0]*/> {
a;
/*[1*/ThisType/*1]*/ get t => throw Error();
}
''');
}
Future<void> test_typeParameter_extension() async {
await _testMarkedContent('''
extension E</*[0*/ThisType/*0]*/> on /*[1*/ThisType/*1]*/ {
/*[2*/ThisType/*2]*/ f() => this;
}
''');
}
Future<void> test_typeParameter_extensionType() async {
await _testMarkedContent('''
extension type Et</*[0*/ThisType/*0]*/>(/*[1*/ThisType/*1]*/ value) {
/*[2*/ThisType/*2]*/ get v => value;
}
''');
}
Future<void> test_typeParameter_function() async {
await _testMarkedContent('''
/*[0*/ThisType/*0]*/ f</*[1*/ThisType/*1]*/>() => 0 as /*[2*/ThisType/*2]*/;
''');
}
Future<void> test_typeParameter_functionParameter() async {
await _testMarkedContent('''
void f(/*[0*/ThisType/*0]*/ Function</*[1*/ThisType/*1]*/>() f) => f();
''');
}
Future<void> test_typeParameter_mixin() async {
await _testMarkedContent('''
mixin M</*[0*/ThisType/*0]*/> {
/*[1*/ThisType/*1]*/ get t;
}
''');
}
Future<void> test_typeParameter_typedef() async {
await _testMarkedContent('''
typedef TypeDef</*[0*/ThisType/*0]*/> = /*[1*/ThisType/*1]*/ Function(/*[2*/ThisType/*2]*/);
''');
}
/// Create three nested loops for this [loopKeyword] (outer/middle/inner)
/// with all combinations of `break`/`continue` (and with.without labels)
/// and verify that the middle [loopKeyword] and all exit keywords that
/// relate to that loop produce mutual ranges including each other.
Future<void> _testLoop(
String loopKeyword,
String loopStart,
String loopEnd,
) async {
var content =
'''
void f() {
outer:
$loopKeyword $loopStart {
middle:
/*[0*/$loopKeyword/*0]*/ $loopStart {
inner:
$loopKeyword $loopStart {
break;
continue;
break inner;
continue inner;
/*[1*/break/*1]*/ middle;
/*[2*/continue/*2]*/ middle;
break outer;
continue outer;
} $loopEnd
/*[3*/break/*3]*/;
/*[4*/continue/*4]*/;
/*[5*/break/*5]*/ middle;
/*[6*/continue/*6]*/ middle;
break outer;
continue outer;
} $loopEnd
break;
continue;
break outer;
continue outer;
} $loopEnd
}
''';
await _testMarkedContent(content);
}
/// Tests highlights in a Dart file using the provided content.
///
/// The content should be marked up using the [TestCode] format.
///
/// If the content contains positions, they will be used to fetch highlights
/// and the resulting ranges verified.
///
/// If the content contains only ranges, then the start and end of every range
/// will be tested to ensure the full set of ranges are returned mutually for
/// each.
Future<void> _testMarkedContent(String content) async {
var code = TestCode.parse(content);
expect(
code.positions.isNotEmpty || code.ranges.isNotEmpty,
isTrue,
reason: 'At least one position or range should be marked in the content',
);
await initialize();
await openFile(mainFileUri, code.code);
var positions = code.positions.isNotEmpty
? code.positions.map((position) => position.position)
: code.ranges.expand((range) => [range.range.start, range.range.end]);
for (var position in positions) {
var highlights = await getDocumentHighlights(mainFileUri, position);
if (code.ranges.isEmpty) {
expect(highlights, isEmpty);
} else {
code.verifyRanges(highlights!.map((highlight) => highlight.range));
}
}
}
}