| // 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); |
| } |
| } |