blob: 7511b8bbeeb62c7fb6fcd38eb76a55d3cdb1e280 [file] [log] [blame]
// Copyright (c) 2021, 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/source/line_info.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(SelectionRangeTest);
});
}
/// Additional tests are in
///
/// test/src/computer/selection_range_computer_test.dart
@reflectiveTest
class SelectionRangeTest extends AbstractLspAnalysisServerTest {
Future<void> test_dotShorthand_constructorInvocation() async {
var code = TestCode.parse('''
class A {}
void f() {
A a = .^new();
}
''');
await initialize();
await openFile(mainFileUri, code.code);
var lineInfo = LineInfo.fromContent(code.code);
var regions = await getSelectionRanges(mainFileUri, [
code.position.position,
]);
expect(regions!.length, equals(1));
var regionTexts =
_getSelectionRangeText(lineInfo, code.code, regions.first).toList();
expect(
regionTexts,
equals([
'new',
'.new()',
'a = .new()',
'A a = .new()',
'A a = .new();',
'{\n A a = .new();\n}',
'() {\n A a = .new();\n}',
'void f() {\n A a = .new();\n}',
]),
);
}
Future<void> test_dotShorthand_methodInvocation() async {
var code = TestCode.parse('''
class A {
static A method() => A();
}
void f() {
A a = .me^thod();
}
''');
await initialize();
await openFile(mainFileUri, code.code);
var lineInfo = LineInfo.fromContent(code.code);
var regions = await getSelectionRanges(mainFileUri, [
code.position.position,
]);
expect(regions!.length, equals(1));
var regionTexts =
_getSelectionRangeText(lineInfo, code.code, regions.first).toList();
expect(
regionTexts,
equals([
'method',
'.method()',
'a = .method()',
'A a = .method()',
'A a = .method();',
'{\n A a = .method();\n}',
'() {\n A a = .method();\n}',
'void f() {\n A a = .method();\n}',
]),
);
}
Future<void> test_dotShorthand_propertyAccess() async {
var code = TestCode.parse('''
enum A { a }
void f() {
A a = .^a;
}
''');
await initialize();
await openFile(mainFileUri, code.code);
var lineInfo = LineInfo.fromContent(code.code);
var regions = await getSelectionRanges(mainFileUri, [
code.position.position,
]);
expect(regions!.length, equals(1));
var regionTexts =
_getSelectionRangeText(lineInfo, code.code, regions.first).toList();
expect(
regionTexts,
equals([
'a',
'.a',
'a = .a',
'A a = .a',
'A a = .a;',
'{\n A a = .a;\n}',
'() {\n A a = .a;\n}',
'void f() {\n A a = .a;\n}',
]),
);
}
Future<void> test_multiple() async {
var content = '''
class Foo {
void a() { /*1*/ }
void b() { /*2*/ }
}
''';
await initialize();
await openFile(mainFileUri, content);
var lineInfo = LineInfo.fromContent(content);
// Send a request for two positions.
var regions = await getSelectionRanges(mainFileUri, [
positionFromOffset(content.indexOf('/*1*/'), content),
positionFromOffset(content.indexOf('/*2*/'), content),
]);
expect(regions!.length, equals(2));
var firstTexts =
_getSelectionRangeText(lineInfo, content, regions[0]).toList();
var secondTexts =
_getSelectionRangeText(lineInfo, content, regions[1]).toList();
expect(
firstTexts,
equals([
'{ /*1*/ }',
'void a() { /*1*/ }',
content.trim(), // Whole content minus the trailing newline
]),
);
expect(
secondTexts,
equals([
'{ /*2*/ }',
'void b() { /*2*/ }',
content.trim(), // Whole content minus the trailing newline
]),
);
}
Future<void> test_nullAwareElements_inList() async {
var code = TestCode.parse('''
class Foo<T> {
List<int> a(String b) {
return [?(1 ^+ 2) * 3];
}
}
''');
await initialize();
await openFile(mainFileUri, code.code);
var lineInfo = LineInfo.fromContent(code.code);
// The returned List corresponds to the input list of positions, and not
// the set of ranges - each range within that list has a (recursive) parent
// to walk up all ranges for that position.
var regions = await getSelectionRanges(mainFileUri, [
code.position.position,
]);
expect(regions!.length, equals(1)); // Only one position was sent.
var regionTexts =
_getSelectionRangeText(lineInfo, code.code, regions.first).toList();
expect(
regionTexts,
equals([
'1 + 2',
'(1 + 2)',
'(1 + 2) * 3',
'?(1 + 2) * 3',
'[?(1 + 2) * 3]',
'return [?(1 + 2) * 3];',
'{\n return [?(1 + 2) * 3];\n }',
'List<int> a(String b) {\n return [?(1 + 2) * 3];\n }',
'class Foo<T> {\n List<int> a(String b) {\n return [?(1 + 2) * 3];\n }\n}',
]),
);
}
Future<void> test_nullAwareElements_inMapKey() async {
var code = TestCode.parse('''
class Foo<T> {
Map<int, String> a(String b) {
return {?(1 ^+ 2) * 3: b};
}
}
''');
await initialize();
await openFile(mainFileUri, code.code);
var lineInfo = LineInfo.fromContent(code.code);
// The returned List corresponds to the input list of positions, and not
// the set of ranges - each range within that list has a (recursive) parent
// to walk up all ranges for that position.
var regions = await getSelectionRanges(mainFileUri, [
code.position.position,
]);
expect(regions!.length, equals(1)); // Only one position was sent.
var regionTexts =
_getSelectionRangeText(lineInfo, code.code, regions.first).toList();
expect(
regionTexts,
equals([
'1 + 2',
'(1 + 2)',
'(1 + 2) * 3',
'?(1 + 2) * 3: b',
'{?(1 + 2) * 3: b}',
'return {?(1 + 2) * 3: b};',
'{\n return {?(1 + 2) * 3: b};\n }',
'Map<int, String> a(String b) {\n return {?(1 + 2) * 3: b};\n }',
'class Foo<T> {\n Map<int, String> a(String b) {\n return {?(1 + 2) * 3: b};\n }\n}',
]),
);
}
Future<void> test_nullAwareElements_inMapValue() async {
var code = TestCode.parse('''
class Foo<T> {
Map<String, int> a(String b) {
return {b: ?(1 ^+ 2) * 3};
}
}
''');
await initialize();
await openFile(mainFileUri, code.code);
var lineInfo = LineInfo.fromContent(code.code);
// The returned List corresponds to the input list of positions, and not
// the set of ranges - each range within that list has a (recursive) parent
// to walk up all ranges for that position.
var regions = await getSelectionRanges(mainFileUri, [
code.position.position,
]);
expect(regions!.length, equals(1)); // Only one position was sent.
var regionTexts =
_getSelectionRangeText(lineInfo, code.code, regions.first).toList();
expect(
regionTexts,
equals([
'1 + 2',
'(1 + 2)',
'(1 + 2) * 3',
'b: ?(1 + 2) * 3',
'{b: ?(1 + 2) * 3}',
'return {b: ?(1 + 2) * 3};',
'{\n return {b: ?(1 + 2) * 3};\n }',
'Map<String, int> a(String b) {\n return {b: ?(1 + 2) * 3};\n }',
'class Foo<T> {\n Map<String, int> a(String b) {\n return {b: ?(1 + 2) * 3};\n }\n}',
]),
);
}
Future<void> test_nullAwareElements_inSet() async {
var code = TestCode.parse('''
class Foo<T> {
Set<int> a(String b) {
return {?(1 ^+ 2) * 3};
}
}
''');
await initialize();
await openFile(mainFileUri, code.code);
var lineInfo = LineInfo.fromContent(code.code);
// The returned List corresponds to the input list of positions, and not
// the set of ranges - each range within that list has a (recursive) parent
// to walk up all ranges for that position.
var regions = await getSelectionRanges(mainFileUri, [
code.position.position,
]);
expect(regions!.length, equals(1)); // Only one position was sent.
var regionTexts =
_getSelectionRangeText(lineInfo, code.code, regions.first).toList();
expect(
regionTexts,
equals([
'1 + 2',
'(1 + 2)',
'(1 + 2) * 3',
'?(1 + 2) * 3',
'{?(1 + 2) * 3}',
'return {?(1 + 2) * 3};',
'{\n return {?(1 + 2) * 3};\n }',
'Set<int> a(String b) {\n return {?(1 + 2) * 3};\n }',
'class Foo<T> {\n Set<int> a(String b) {\n return {?(1 + 2) * 3};\n }\n}',
]),
);
}
Future<void> test_single() async {
var code = TestCode.parse('''
class Foo<T> {
void a(String b) {
print((1 ^+ 2) * 3);
}
}
''');
await initialize();
await openFile(mainFileUri, code.code);
var lineInfo = LineInfo.fromContent(code.code);
// The returned List corresponds to the input list of positions, and not
// the set of ranges - each range within that list has a (recursive) parent
// to walk up all ranges for that position.
var regions = await getSelectionRanges(mainFileUri, [
code.position.position,
]);
expect(regions!.length, equals(1)); // Only one position was sent.
var regionTexts =
_getSelectionRangeText(lineInfo, code.code, regions.first).toList();
expect(
regionTexts,
equals([
'1 + 2',
'(1 + 2)',
'(1 + 2) * 3',
'((1 + 2) * 3)',
'print((1 + 2) * 3)',
'print((1 + 2) * 3);',
'{\n print((1 + 2) * 3);\n }',
'void a(String b) {\n print((1 + 2) * 3);\n }',
'class Foo<T> {\n void a(String b) {\n print((1 + 2) * 3);\n }\n}',
]),
);
}
Iterable<String> _getSelectionRangeText(
LineInfo lineInfo,
String content,
SelectionRange range,
) sync* {
yield _rangeOfText(lineInfo, content, range.range);
var parent = range.parent;
if (parent != null) {
yield* _getSelectionRangeText(lineInfo, content, parent);
}
}
String _rangeOfText(LineInfo lineInfo, String content, Range range) {
var startPos = range.start;
var endPos = range.end;
var start = lineInfo.getOffsetOfLine(startPos.line) + startPos.character;
var end = lineInfo.getOffsetOfLine(endPos.line) + endPos.character;
return content.substring(start, end);
}
}