blob: fc30285932bca543c643c8ac462c2cca774664b7 [file] [log] [blame]
// 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() => _testMarkedContent('''
var /*[0*/^_/*0]*/ = 1;
void f() {
var _ = 2;
print(/*[1*/_/*1]*/);
}
''');
Future<void> test_dartCode_issue5369_field() => _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() => _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() => _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() =>
_testMarkedContent('''
var /*[0*/^a/*0]*/ = [''].where((_) => true).toList();
var b = /*[1*/a/*1]*/;
''');
Future<void> test_dotShorthand_class() => _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() => _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() => _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_forInLoop() => _testMarkedContent('''
void f() {
for (final /*[0*/x^/*0]*/ in []) {
/*[1*/x/*1]*/;
}
}
''');
Future<void> test_formalParameters_closure() => _testMarkedContent('''
void f(void Function(int) _) {}
void g() => f((/*[0*/^variable/*0]*/) {
print(/*[1*/variable/*1]*/);
});
''');
Future<void> test_formalParameters_function() => _testMarkedContent('''
void f(int /*[0*/^parameter/*0]*/) {
print(/*[1*/parameter/*1]*/);
}
''');
Future<void> test_formalParameters_method() => _testMarkedContent('''
class C {
void m(int /*[0*/^parameter/*0]*/) {
print(/*[1*/parameter/*1]*/);
}
}
''');
Future<void> test_functions() => _testMarkedContent('''
/*[0*/main/*0]*/() {
/*[1*/mai^n/*1]*/();
}
''');
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_localVariable() => _testMarkedContent('''
void f() {
var /*[0*/f^oo/*0]*/ = 1;
print(/*[1*/foo/*1]*/);
/*[2*/foo/*2]*/ = 2;
}
''');
Future<void> test_method_underscore() => _testMarkedContent('''
class C {
/*[0*/_/*0]*/() {
/*[1*/^_/*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() => _testMarkedContent('''
void f() {
// This one is in a ^ comment!
}
''');
Future<void> test_onlySelf() => _testMarkedContent('''
void f() {
/*[0*/prin^t/*0]*/('');
}
''');
Future<void> test_onlySelf_wildcard() => _testMarkedContent('''
void f() {
var /*[0*/^_/*0]*/ = '';
}
''');
Future<void> test_pattern_object_destructure() => _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_prefix() => _testMarkedContent('''
import '' as /*[0*/p/*0]*/;
class A {
void m() {
/*[1*/p/*1]*/.foo();
print(/*[2*/p/*2]*/.a);
}
}
void foo() {}
/*[3*/p^/*3]*/.A? a;
''');
Future<void> test_prefixed() => _testMarkedContent('''
import '' as p;
class /*[0*/A^/*0]*/ {}
p./*[1*/A/*1]*/? a;
''');
Future<void> test_shadow_inner() => _testMarkedContent('''
void f() {
var foo = 1;
func() {
var /*[0*/fo^o/*0]*/ = 2;
print(/*[1*/foo/*1]*/);
}
}
''');
Future<void> test_shadow_outer() => _testMarkedContent('''
void f() {
var /*[0*/foo/*0]*/ = 1;
func() {
var foo = 2;
print(foo);
}
print(/*[1*/fo^o/*1]*/);
}
''');
Future<void> test_topLevelVariable() => _testMarkedContent('''
String /*[0*/foo/*0]*/ = 'bar';
void f() {
print(/*[1*/foo/*1]*/);
/*[2*/fo^o/*2]*/ = '';
}
''');
Future<void> test_topLevelVariable_underscore() => _testMarkedContent('''
String /*[0*/_/*0]*/ = 'bar';
void f() {
print(/*[1*/_/*1]*/);
/*[2*/^_/*2]*/ = '';
}
''');
Future<void> test_type_class_constructors() async {
await _testMarkedContent('''
class /*[0*/A^/*0]*/ {
A(); // Unnamed constructor is own entity
/*[1*/A/*1]*/.named();
}
/*[2*/A/*2]*/ a = A(); // Unnamed constructor is own entity
var b = /*[3*/A/*3]*/.new();
var c = /*[4*/A/*4]*/.new;
''');
}
/// The type name in unnamed constructors are their own entity and not
/// part of the type name.
Future<void> test_type_class_constructors_unnamed() 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_typeAlias_class_declaration() => _testMarkedContent('''
class MyClass {}
mixin MyMixin {}
class /*[0*/MyAli^as/*0]*/ = MyClass with MyMixin;
/*[1*/MyAlias/*1]*/? a;
''');
Future<void> test_typeAlias_class_reference() => _testMarkedContent('''
class MyClass {}
mixin MyMixin {}
class /*[0*/MyAlias/*0]*/ = MyClass with MyMixin;
/*[1*/MyAl^ias/*1]*/? a;
''');
Future<void> test_typeAlias_function_declaration() => _testMarkedContent('''
typedef /*[0*/myFu^nc/*0]*/();
/*[1*/myFunc/*1]*/? f;
''');
Future<void> test_typeAlias_function_reference() => _testMarkedContent('''
typedef /*[0*/myFunc/*0]*/();
/*[1*/myFun^c/*1]*/? f;
''');
Future<void> test_typeAlias_generic_declaration() => _testMarkedContent('''
typedef /*[0*/TD^/*0]*/ = String;
/*[1*/TD/*1]*/? a;
''');
Future<void> test_typeAlias_generic_reference() => _testMarkedContent('''
typedef /*[0*/TD/*0]*/ = String;
/*[1*/TD^/*1]*/? a;
''');
/// Tests highlights in a Dart file using the provided content.
///
/// The content should be marked up using the [TestCode] format.
///
/// If the content does not include any ranges then the response is expected
/// to be `null`.
Future<void> _testMarkedContent(String content) async {
var code = TestCode.parse(content);
await initialize();
await openFile(mainFileUri, code.code);
var pos = code.position.position;
var highlights = await getDocumentHighlights(mainFileUri, pos);
if (code.ranges.isEmpty) {
// When there are no ranges, we expect an empty result (not null) because
// a null may cause an editor to fall back to a text search (VS Code
// does) which can lead to confusing results.
expect(highlights, isEmpty);
} else {
var highlightRanges = highlights!.map((h) => h.range).toList();
expect(highlightRanges, equals(code.ranges.map((r) => r.range)));
}
}
}