| // Copyright (c) 2018, 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:analyzer_testing/package_config_file_builder.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(HoverTest); | |
| }); | |
| } | |
| @reflectiveTest | |
| class HoverTest extends AbstractLspAnalysisServerTest { | |
| /// Checks whether the correct types of documentation are returned in a Hover | |
| /// based on [preference]. | |
| Future<void> assertDocumentation( | |
| String? preference, { | |
| required bool includesSummary, | |
| required bool includesFull, | |
| }) async { | |
| var code = TestCode.parse(''' | |
| /// Summary. | |
| /// | |
| /// Full. | |
| class ^A {} | |
| '''); | |
| await provideConfig(initialize, {'documentation': ?preference}); | |
| await openFile(mainFileUri, code.code); | |
| await workspaceAnalysisComplete(); | |
| var hover = await getHover(mainFileUri, code.position.position); | |
| var hoverContents = _getStringContents(hover!); | |
| if (includesSummary) { | |
| expect(hoverContents, contains('Summary.')); | |
| } else { | |
| expect(hoverContents, isNot(contains('Summary.'))); | |
| } | |
| if (includesFull) { | |
| expect(hoverContents, contains('Full.')); | |
| } else { | |
| expect(hoverContents, isNot(contains('Full.'))); | |
| } | |
| } | |
| Future<void> assertMarkdownContents(String content, Matcher matcher) async { | |
| setHoverContentFormat([MarkupKind.Markdown]); | |
| var code = TestCode.parse(content); | |
| await initialize(); | |
| await openFile(mainFileUri, code.code); | |
| await workspaceAnalysisComplete(); | |
| var hover = await getHover(mainFileUri, code.position.position); | |
| expect(hover, isNotNull); | |
| expect(hover!.range, equals(code.range.range)); | |
| expect(hover.contents, isNotNull); | |
| var markup = _getMarkupContents(hover); | |
| expect(markup.kind, equals(MarkupKind.Markdown)); | |
| expect(markup.value, matcher); | |
| } | |
| Future<void> assertNullHover( | |
| String content, { | |
| bool waitForAnalysis = false, | |
| bool withOpenFile = true, | |
| }) async { | |
| var code = TestCode.parse(content); | |
| await initialize(); | |
| if (withOpenFile) { | |
| await openFile(mainFileUri, code.code); | |
| } else { | |
| newFile(mainFilePath, code.code); | |
| if (waitForAnalysis) { | |
| await pumpEventQueue(times: 5000); // Allow server to see watch event. | |
| } | |
| } | |
| if (waitForAnalysis) { | |
| await workspaceAnalysisComplete(); | |
| } | |
| var hover = await getHover(mainFileUri, code.position.position); | |
| expect(hover, isNull); | |
| } | |
| Future<void> assertPlainTextContents(String content, Matcher matcher) async { | |
| setHoverContentFormat([MarkupKind.PlainText]); | |
| var code = TestCode.parse(content); | |
| await initialize(); | |
| await openFile(mainFileUri, code.code); | |
| await workspaceAnalysisComplete(); | |
| var hover = await getHover(mainFileUri, code.position.position); | |
| expect(hover, isNotNull); | |
| expect(hover!.range, equals(code.range.range)); | |
| expect(hover.contents, isNotNull); | |
| var markup = _getMarkupContents(hover); | |
| expect(markup.kind, equals(MarkupKind.PlainText)); | |
| expect(markup.value, matcher); | |
| } | |
| Future<void> assertStringContents( | |
| String content, | |
| Matcher matcher, { | |
| Uri? fileUri, | |
| bool waitForAnalysis = false, | |
| bool withOpenFile = true, | |
| }) async { | |
| fileUri ??= mainFileUri; | |
| var code = TestCode.parse(content); | |
| await initialize(); | |
| if (withOpenFile) { | |
| await openFile(fileUri, code.code); | |
| } else { | |
| newFile(mainFilePath, code.code); | |
| if (waitForAnalysis) { | |
| await pumpEventQueue(times: 5000); // Allow server to see watch event. | |
| } | |
| } | |
| if (waitForAnalysis) { | |
| await workspaceAnalysisComplete(); | |
| } | |
| var hover = await getHover(fileUri, code.position.position); | |
| expect(hover, isNotNull); | |
| expect(hover!.range, equals(code.range.range)); | |
| expect(hover.contents, isNotNull); | |
| var contents = _getStringContents(hover); | |
| expect(contents, matcher); | |
| } | |
| Future<void> test_dartDoc_macros() => | |
| assertStringContents(waitForAnalysis: true, ''' | |
| /// {@template template_name} | |
| /// This is shared content. | |
| /// {@endtemplate} | |
| const String? foo = null; | |
| /// {@macro template_name} | |
| const String? [!f^oo2!] = null; | |
| ''', endsWith('This is shared content.')); | |
| Future<void> test_dartDocPreference_full() => | |
| assertDocumentation('full', includesSummary: true, includesFull: true); | |
| Future<void> test_dartDocPreference_summary() => assertDocumentation( | |
| 'summary', | |
| includesSummary: true, | |
| // Doc preferences don't apply to single-result requests so always expect | |
| // full docs. | |
| includesFull: true, | |
| ); | |
| /// No preference should result in full docs. | |
| Future<void> test_dartDocPreference_unset() => | |
| assertDocumentation(null, includesSummary: true, includesFull: true); | |
| Future<void> test_declaredIn_classAndLibrary() async { | |
| var content = ''' | |
| class A { | |
| String? [!a^!]; | |
| } | |
| '''; | |
| var expected = ''' | |
| ```dart | |
| String? a | |
| ``` | |
| Type: `String?` | |
| Declared in `A` in *package:test/main.dart*.'''; | |
| await assertStringContents(content, equals(expected)); | |
| } | |
| Future<void> test_declaredIn_emptyClassName() async { | |
| failTestOnErrorDiagnostic = false; | |
| var content = ''' | |
| class { | |
| String? [!a^!]; | |
| } | |
| '''; | |
| var expected = ''' | |
| ```dart | |
| String? a | |
| ``` | |
| Type: `String?` | |
| Declared in *package:test/main.dart*.'''; | |
| await assertStringContents(content, equals(expected)); | |
| } | |
| Future<void> test_declaredIn_nonPackage() async { | |
| // Files in the test folder don't have package: URIs and will show relative | |
| // paths instead. | |
| var fileUri = toUri(join(projectFolderPath, 'test', 'one_test.dart')); | |
| var content = ''' | |
| class A { | |
| String? [!a^!]; | |
| } | |
| '''; | |
| var expected = ''' | |
| ```dart | |
| String? a | |
| ``` | |
| Type: `String?` | |
| Declared in `A` in *test/one_test.dart*.'''; | |
| await assertStringContents(fileUri: fileUri, content, equals(expected)); | |
| } | |
| Future<void> test_declaredIn_nonPackage_pubWorkspace() async { | |
| // Files in the test folder don't have package: URIs and will show relative | |
| // paths instead. In a Pub Workspace, this should be relative to the package | |
| // and not the workspace. | |
| newFile(join(projectFolderPath, 'pubspec.yaml'), ''' | |
| workspace: | |
| - pkgs/a | |
| '''); | |
| newFile(join(projectFolderPath, 'pkgs', 'a', 'pubspec.yaml'), ''' | |
| name: a | |
| environment: | |
| sdk: ^3.7.0 | |
| '''); | |
| var config = PackageConfigFileBuilder(); | |
| config.add(name: 'a', rootFolder: getFolder('$projectFolderPath/pkgs/a')); | |
| writeTestPackageConfig2(config: config); | |
| var fileUri = toUri( | |
| join(projectFolderPath, 'pkgs', 'a', 'test', 'one_test.dart'), | |
| ); | |
| var content = ''' | |
| class A { | |
| String? [!a^!]; | |
| } | |
| '''; | |
| var expected = ''' | |
| ```dart | |
| String? a | |
| ``` | |
| Type: `String?` | |
| Declared in `A` in *test/one_test.dart*.'''; | |
| await assertStringContents(fileUri: fileUri, content, equals(expected)); | |
| } | |
| Future<void> test_declaredIn_onlyLibrary() async { | |
| var content = ''' | |
| class A {} | |
| [!A^!]? a; | |
| '''; | |
| var expected = ''' | |
| ```dart | |
| class A | |
| ``` | |
| Declared in *package:test/main.dart*.'''; | |
| await assertStringContents(content, equals(expected)); | |
| } | |
| Future<void> test_dotShorthand_constructor_named() async { | |
| var content = ''' | |
| class A { | |
| A.named(); | |
| } | |
| void f() { | |
| A a = [!.nam^ed()!]; | |
| } | |
| '''; | |
| var expected = ''' | |
| ```dart | |
| (new) A.named() | |
| ``` | |
| Declared in `A` in *package:test/main.dart*.'''; | |
| await assertStringContents(content, equals(expected)); | |
| } | |
| Future<void> test_dotShorthand_constructor_named_const() async { | |
| var content = ''' | |
| class A { | |
| const A.named(); | |
| } | |
| void f() { | |
| const A a = [!.nam^ed()!]; | |
| } | |
| '''; | |
| var expected = ''' | |
| ```dart | |
| (const) A.named() | |
| ``` | |
| Declared in `A` in *package:test/main.dart*.'''; | |
| await assertStringContents(content, equals(expected)); | |
| } | |
| Future<void> test_dotShorthand_constructor_unnamed() async { | |
| var content = ''' | |
| class A {} | |
| void f() { | |
| A a = [!.ne^w()!]; | |
| } | |
| '''; | |
| var expected = ''' | |
| ```dart | |
| (new) A() | |
| ``` | |
| Declared in `A` in *package:test/main.dart*.'''; | |
| await assertStringContents(content, equals(expected)); | |
| } | |
| Future<void> test_dotShorthand_constructor_unnamed_const() async { | |
| var content = ''' | |
| class A { | |
| const A(); | |
| } | |
| void f() { | |
| const A a = [!.ne^w()!]; | |
| } | |
| '''; | |
| var expected = ''' | |
| ```dart | |
| (const) A() | |
| ``` | |
| Declared in `A` in *package:test/main.dart*.'''; | |
| await assertStringContents(content, equals(expected)); | |
| } | |
| Future<void> test_dotShorthand_method() async { | |
| var content = ''' | |
| class A { | |
| static A method() => A(); | |
| } | |
| void f() { | |
| A a = .[!meth^od!](); | |
| } | |
| '''; | |
| var expected = ''' | |
| ```dart | |
| A method() | |
| ``` | |
| Type: `A Function()` | |
| Declared in `A` in *package:test/main.dart*.'''; | |
| await assertStringContents(content, equals(expected)); | |
| } | |
| Future<void> test_dotShorthand_method_subclass() async { | |
| var content = ''' | |
| class A { | |
| static B method() => B(); | |
| } | |
| class B extends A {} | |
| void g(A a) {} | |
| void f() { | |
| g(.[!met^hod!]()); | |
| } | |
| '''; | |
| var expected = ''' | |
| ```dart | |
| B method() | |
| ``` | |
| Type: `B Function()` | |
| Declared in `A` in *package:test/main.dart*.'''; | |
| await assertStringContents(content, equals(expected)); | |
| } | |
| Future<void> test_dotShorthand_propertyAccess() async { | |
| var content = ''' | |
| class A { | |
| static A field = A(); | |
| } | |
| void f() { | |
| A a = .[!fie^ld!]; | |
| } | |
| '''; | |
| var expected = ''' | |
| ```dart | |
| A get field | |
| ``` | |
| Type: `A` | |
| Declared in `A` in *package:test/main.dart*.'''; | |
| await assertStringContents(content, equals(expected)); | |
| } | |
| Future<void> test_dotShorthand_propertyAccess_enum() async { | |
| var content = ''' | |
| enum A { one } | |
| void f() { | |
| A a = .[!on^e!]; | |
| } | |
| '''; | |
| var expected = ''' | |
| ```dart | |
| A get one | |
| ``` | |
| Type: `A` | |
| Declared in `A` in *package:test/main.dart*.'''; | |
| await assertStringContents(content, equals(expected)); | |
| } | |
| Future<void> test_dotShorthand_propertyAccess_extensionType() async { | |
| var content = ''' | |
| extension type A(int x) { | |
| static A get getter => A(1); | |
| } | |
| void f() { | |
| A a = .[!gette^r!]; | |
| } | |
| '''; | |
| var expected = ''' | |
| ```dart | |
| A get getter | |
| ``` | |
| Type: `A` | |
| Declared in `A` in *package:test/main.dart*.'''; | |
| await assertStringContents(content, equals(expected)); | |
| } | |
| Future<void> test_enum_member() async { | |
| var content = ''' | |
| enum MyEnum { one } | |
| void f() { | |
| MyEnum.[!o^ne!]; | |
| } | |
| '''; | |
| var expected = ''' | |
| ```dart | |
| MyEnum get one | |
| ``` | |
| Type: `MyEnum` | |
| Declared in `MyEnum` in *package:test/main.dart*.'''; | |
| await assertStringContents(content, equals(expected)); | |
| } | |
| Future<void> test_enum_member_declaration() async { | |
| var content = ''' | |
| enum E { | |
| /// Docs | |
| [!on^e!] | |
| } | |
| '''; | |
| var expected = ''' | |
| ```dart | |
| E one | |
| ``` | |
| Type: `E` | |
| Declared in `E` in *package:test/main.dart*. | |
| --- | |
| Docs'''; | |
| await assertStringContents(content, equals(expected)); | |
| } | |
| Future<void> test_enum_values() async { | |
| var content = ''' | |
| enum MyEnum { one } | |
| void f() { | |
| MyEnum.[!va^lues!]; | |
| } | |
| '''; | |
| var expected = ''' | |
| ```dart | |
| List<MyEnum> get values | |
| ``` | |
| Type: `List<MyEnum>` | |
| Declared in `MyEnum` in *package:test/main.dart*.'''; | |
| await assertStringContents(content, equals(expected)); | |
| } | |
| Future<void> test_field_underscore() async { | |
| var content = ''' | |
| class A { | |
| int _ = 1; | |
| int f() => [!^_!]; | |
| } | |
| '''; | |
| var expected = ''' | |
| ```dart | |
| int get _ | |
| ``` | |
| Type: `int` | |
| Declared in `A` in *package:test/main.dart*.'''; | |
| await assertStringContents(content, equals(expected)); | |
| } | |
| Future<void> test_forLoop_declaredVariable() async { | |
| var content = ''' | |
| void f() { | |
| for (var [!ii^i!] in <String>[]) {} | |
| } | |
| '''; | |
| var expected = ''' | |
| ```dart | |
| String iii | |
| ``` | |
| Type: `String` | |
| Declared in `f` in *package:test/main.dart*.'''; | |
| await assertStringContents(content, equals(expected)); | |
| } | |
| Future<void> test_forLoop_variableReference() async { | |
| var content = ''' | |
| void f() { | |
| for (var iii in <String>[]) { | |
| print([!ii^i!]); | |
| } | |
| } | |
| '''; | |
| var expected = ''' | |
| ```dart | |
| String iii | |
| ``` | |
| Type: `String` | |
| Declared in `f` in *package:test/main.dart*.'''; | |
| await assertStringContents(content, equals(expected)); | |
| } | |
| Future<void> test_function_startOfParameterList() => assertStringContents(''' | |
| /// This is a function. | |
| void [!abc!]^() {} | |
| ''', contains('This is a function.')); | |
| Future<void> test_function_startOfTypeParameterList() => | |
| assertStringContents(''' | |
| /// This is a function. | |
| void [!abc!]^<T>(T a) {} | |
| ''', contains('This is a function.')); | |
| Future<void> test_functionType_assignedToVariable() async { | |
| var content = ''' | |
| void f(int p1, int p2) { | |
| var [!^x!] = f; | |
| } | |
| '''; | |
| var expected = | |
| ''' | |
| ```dart | |
| void Function(int p1, int p2) x | |
| ``` | |
| Type: `void Function(int, int)` | |
| Declared in `f` in *package:test/main.dart*. | |
| ''' | |
| .trim(); | |
| await assertStringContents(content, equals(expected)); | |
| } | |
| Future<void> test_functionType_constructorTearOff() async { | |
| var content = ''' | |
| class C { | |
| C(int p1, int p2) {} | |
| } | |
| void f() { | |
| C.[!^new!]; | |
| } | |
| '''; | |
| var expected = | |
| ''' | |
| ```dart | |
| C(int p1, int p2) | |
| ``` | |
| Declared in `C` in *package:test/main.dart*. | |
| ''' | |
| .trim(); | |
| await assertStringContents(content, equals(expected)); | |
| } | |
| Future<void> test_functionType_methodTearOff() async { | |
| var content = ''' | |
| class C { | |
| f(int p1, int p2) {} | |
| } | |
| void f(C c) { | |
| c.[!^f!]; | |
| } | |
| '''; | |
| var expected = | |
| ''' | |
| ```dart | |
| dynamic f(int p1, int p2) | |
| ``` | |
| Type: `dynamic Function(int, int)` | |
| Declared in `C` in *package:test/main.dart*. | |
| ''' | |
| .trim(); | |
| await assertStringContents(content, equals(expected)); | |
| } | |
| Future<void> test_functionType_parameter() async { | |
| var content = ''' | |
| void f(String Function(int p1, int p2) [!^x!]) {} | |
| '''; | |
| var expected = | |
| ''' | |
| ```dart | |
| String Function(int p1, int p2) x | |
| ``` | |
| Type: `String Function(int, int)` | |
| Declared in `f` in *package:test/main.dart*. | |
| ''' | |
| .trim(); | |
| await assertStringContents(content, equals(expected)); | |
| } | |
| Future<void> test_functionType_typedef() async { | |
| failTestOnAnyErrorNotification = false; | |
| var content = ''' | |
| typedef [!^Foo!] = String Function(int p1, int p2); | |
| '''; | |
| var expected = | |
| ''' | |
| ```dart | |
| typedef Foo = String Function(int p1, int p2) | |
| ``` | |
| Declared in *package:test/main.dart*. | |
| ''' | |
| .trim(); | |
| await assertStringContents(content, equals(expected)); | |
| } | |
| Future<void> test_hover_bad_position() async { | |
| await initialize(); | |
| await openFile(mainFileUri, ''); | |
| await expectLater( | |
| () => getHover(mainFileUri, Position(line: 999, character: 999)), | |
| throwsA(isResponseError(ServerErrorCodes.invalidFileLineCol)), | |
| ); | |
| } | |
| Future<void> test_import_alias_dart() async { | |
| var content = ''' | |
| import 'dart:core' as [!l^ib!]; | |
| '''; | |
| var expected = ''' | |
| ```dart | |
| import 'dart:core' as lib; | |
| ``` | |
| Declared in *package:test/main.dart*.'''; | |
| await assertStringContents(content, equals(expected)); | |
| } | |
| Future<void> test_import_alias_interpolation() async { | |
| var content = ''' | |
| // ignore: uri_with_interpolation | |
| import '\$bug.dart' as [!l^ib!]; | |
| const bug = 'bug'; | |
| '''; | |
| var expected = ''' | |
| ```dart | |
| import '<unknown>' as lib; | |
| ``` | |
| Declared in *package:test/main.dart*.'''; | |
| await assertStringContents(content, equals(expected)); | |
| } | |
| Future<void> test_import_alias_multiple_equal() async { | |
| newFile('/home/my_project/lib/lib.dart', ''' | |
| /// This is a string. | |
| library my.lib; | |
| '''); | |
| var content = ''' | |
| import 'dart:math' as lib; | |
| import 'lib.dart' as [!l^ib!]; | |
| '''; | |
| var expected = ''' | |
| ```dart | |
| import 'dart:math' as lib; | |
| import 'lib.dart' as lib; | |
| ``` | |
| Declared in *package:test/main.dart*.'''; | |
| await assertStringContents(content, equals(expected)); | |
| } | |
| Future<void> test_import_alias_package() async { | |
| newFile('/home/my_project/lib/lib.dart', ''' | |
| /// This is a string. | |
| library my.lib; | |
| '''); | |
| var content = ''' | |
| import 'package:test/lib.dart' as [!l^ib!]; | |
| '''; | |
| var expected = ''' | |
| ```dart | |
| import 'package:test/lib.dart' as lib; | |
| ``` | |
| Declared in *package:test/main.dart*.'''; | |
| await assertStringContents(content, equals(expected)); | |
| } | |
| Future<void> test_import_alias_project() async { | |
| newFile('/home/my_project/lib/lib.dart', ''' | |
| '''); | |
| var content = ''' | |
| import 'lib.dart' as [!l^ib!]; | |
| '''; | |
| var expected = ''' | |
| ```dart | |
| import 'lib.dart' as lib; | |
| ``` | |
| Declared in *package:test/main.dart*.'''; | |
| await assertStringContents(content, equals(expected)); | |
| } | |
| Future<void> test_import_prefix() async { | |
| newFile('/home/my_project/lib/lib.dart', ''' | |
| /// This is a string. | |
| library my.lib; | |
| class C {} | |
| '''); | |
| var content = ''' | |
| import 'lib.dart' as lib; | |
| [!li^b!].C? c; | |
| '''; | |
| var expected = ''' | |
| ```dart | |
| import 'lib.dart' as lib; | |
| ``` | |
| Declared in *package:test/main.dart*.'''; | |
| await assertStringContents(content, equals(expected)); | |
| } | |
| Future<void> test_localVariable_wildcard() async { | |
| var content = ''' | |
| f() { | |
| int [!^_!] = 0; | |
| } | |
| '''; | |
| await assertNullHover(content); | |
| } | |
| Future<void> test_localVariable_wildcard_beforeFeature() async { | |
| var content = ''' | |
| // @dart=3.6 | |
| f() { | |
| int [!^_!] = 0; | |
| } | |
| '''; | |
| var expected = ''' | |
| ```dart | |
| int _ | |
| ``` | |
| Type: `int` | |
| Declared in `f` in *package:test/main.dart*.'''; | |
| await assertStringContents(content, equals(expected)); | |
| } | |
| Future<void> test_markdown_isFormattedForDisplay() async { | |
| var content = ''' | |
| /// This is a string. | |
| /// | |
| /// {@template foo} | |
| /// With some [refs] and some | |
| /// [links](https://www.dartlang.org/) | |
| /// {@endtemplate foo} | |
| /// | |
| /// ```dart sample | |
| /// print(); | |
| /// ``` | |
| String? [!a^bc!]; | |
| '''; | |
| var expectedHoverContent = | |
| ''' | |
| ```dart | |
| String? abc | |
| ``` | |
| Type: `String?` | |
| Declared in *package:test/main.dart*. | |
| --- | |
| This is a string. | |
| With some [refs] and some | |
| [links](https://www.dartlang.org/) | |
| ```dart | |
| print(); | |
| ``` | |
| ''' | |
| .trim(); | |
| await assertMarkdownContents(content, equals(expectedHoverContent)); | |
| } | |
| Future<void> test_markdown_simple() => assertMarkdownContents(''' | |
| /// This is a string. | |
| String [!a^bc!] = ''; | |
| ''', contains('This is a string.')); | |
| Future<void> test_method_callMethod_invocation() async { | |
| var content = ''' | |
| /// f doc. | |
| void f(int i) { | |
| f.[!call^!](1); | |
| } | |
| '''; | |
| var expected = ''' | |
| ```dart | |
| void f(int i) | |
| ``` | |
| Type: `void Function(int)` | |
| Declared in *package:test/main.dart*. | |
| --- | |
| f doc.'''; | |
| await assertStringContents(content, equals(expected)); | |
| } | |
| Future<void> test_method_callMethod_tearOff() async { | |
| var content = ''' | |
| /// f doc. | |
| void f(int i) { | |
| f.[!call^!]; | |
| } | |
| '''; | |
| var expected = ''' | |
| ```dart | |
| void f(int i) | |
| ``` | |
| Type: `void Function(int)` | |
| Declared in *package:test/main.dart*. | |
| --- | |
| f doc.'''; | |
| await assertStringContents(content, equals(expected)); | |
| } | |
| Future<void> test_method_mixin_onImplementation() async { | |
| var content = ''' | |
| abstract class A { | |
| /// Documentation for [f]. | |
| void f(); | |
| } | |
| abstract class B implements A {} | |
| mixin M on B { | |
| @override | |
| void [!f^!]() {} | |
| } | |
| '''; | |
| await assertStringContents(content, contains('Documentation for [f].')); | |
| } | |
| Future<void> test_method_startOfParameterList() => assertStringContents(''' | |
| class A { | |
| /// This is a method. | |
| void [!abc!]^() {} | |
| } | |
| ''', contains('This is a method.')); | |
| Future<void> test_method_startOfTypeParameterList() => | |
| assertStringContents(''' | |
| class A { | |
| /// This is a method. | |
| String [!abc!]^<T>(T a) => ''; | |
| } | |
| ''', contains('This is a method.')); | |
| Future<void> test_method_underscore() async { | |
| var content = ''' | |
| class A { | |
| int _() => 1; | |
| int f() => [!^_!](); | |
| } | |
| '''; | |
| var expected = ''' | |
| ```dart | |
| int _() | |
| ``` | |
| Type: `int Function()` | |
| Declared in `A` in *package:test/main.dart*.'''; | |
| await assertStringContents(content, equals(expected)); | |
| } | |
| Future<void> test_noElement() async { | |
| var code = TestCode.parse(''' | |
| String? abc; | |
| ^ | |
| int? a; | |
| '''); | |
| await initialize(); | |
| await openFile(mainFileUri, code.code); | |
| var hover = await getHover(mainFileUri, code.position.position); | |
| expect(hover, isNull); | |
| } | |
| Future<void> test_nonDartFile() async { | |
| await initialize(); | |
| await openFile(pubspecFileUri, simplePubspecContent); | |
| var hover = await getHover(pubspecFileUri, startOfDocPos); | |
| expect(hover, isNull); | |
| } | |
| Future<void> test_nullableTypes() async { | |
| var content = ''' | |
| String? [!a^bc!]; | |
| '''; | |
| var expectedHoverContent = | |
| ''' | |
| ```dart | |
| String? abc | |
| ``` | |
| Type: `String?` | |
| Declared in *package:test/main.dart*. | |
| ''' | |
| .trim(); | |
| await assertStringContents(content, equals(expectedHoverContent)); | |
| } | |
| Future<void> test_parameter_wildcard() async { | |
| var content = ''' | |
| f(int [!^_!]) { } | |
| '''; | |
| await assertNullHover(content); | |
| } | |
| Future<void> test_parameter_wildcard_beforeFeature() async { | |
| var content = ''' | |
| // @dart=3.6 | |
| f(int [!^_!]) { } | |
| '''; | |
| var expected = ''' | |
| ```dart | |
| int _ | |
| ``` | |
| Type: `int` | |
| Declared in `f` in *package:test/main.dart*.'''; | |
| await assertStringContents(content, equals(expected)); | |
| } | |
| Future<void> test_pattern_assignment_left() => assertStringContents(''' | |
| void f(String a, String b) { | |
| (b, [!a^!]) = (a, b); | |
| } | |
| ''', contains('Type: `String`')); | |
| Future<void> test_pattern_assignment_list() => assertStringContents(''' | |
| void f(List<int> x, num a) { | |
| [[!a^!]] = x; | |
| } | |
| ''', contains('num a')); | |
| Future<void> test_pattern_assignment_right() => assertStringContents(''' | |
| void f(String a, String b) { | |
| (b, a) = ([!a^!], b); | |
| } | |
| ''', contains('Type: `String`')); | |
| Future<void> test_pattern_cast_typeName() => assertStringContents(''' | |
| void f((num, Object) record) { | |
| var (i as int, s as [!St^ring!]) = record; | |
| } | |
| ''', contains('class String')); | |
| Future<void> test_pattern_map() => assertStringContents(''' | |
| void f(x) { | |
| switch (x) { | |
| case {0: [!Str^ing!] a}: | |
| break; | |
| } | |
| } | |
| ''', contains('class String')); | |
| Future<void> test_pattern_map_typeArguments() => assertStringContents(''' | |
| void f(x) { | |
| switch (x) { | |
| case <int, [!Str^ing!]>{0: var a}: | |
| break; | |
| } | |
| } | |
| ''', contains('class String')); | |
| Future<void> test_pattern_nullAssert() => assertStringContents(''' | |
| void f((int?, int?) position) { | |
| var ([!x^!]!, y!) = position; | |
| } | |
| ''', contains('Type: `int`')); | |
| Future<void> test_pattern_nullCheck() => assertStringContents(''' | |
| void f(String? maybeString) { | |
| switch (maybeString) { | |
| case var [!s^!]?: | |
| } | |
| } | |
| ''', contains('Type: `String`')); | |
| Future<void> test_pattern_object_fieldName() => assertStringContents(''' | |
| double calculateArea(Shape shape) => | |
| switch (shape) { | |
| Square([!leng^th!]: var l) => l * l, | |
| }; | |
| sealed class Shape { } | |
| class Square extends Shape { | |
| /// The length. | |
| double get length => 0; | |
| } | |
| ''', allOf([contains('double get length'), contains('The length.')])); | |
| Future<void> test_pattern_object_typeName() => assertStringContents(''' | |
| double calculateArea(Shape shape) => | |
| switch (shape) { | |
| [!Squ^are!](length: var l) => l * l, | |
| }; | |
| sealed class Shape { } | |
| /// A square. | |
| class Square extends Shape { | |
| double get length => 0; | |
| } | |
| ''', contains('A square.')); | |
| Future<void> test_pattern_record_fieldName() => assertStringContents(''' | |
| void f(({int foo}) x, num a) { | |
| ([!fo^o!]: a,) = x; | |
| } | |
| ''', contains('Type: `int`')); | |
| Future<void> test_pattern_record_fieldValue() => assertStringContents(''' | |
| void f(({int foo}) x, num a) { | |
| (foo: [!a^!],) = x; | |
| } | |
| ''', contains('Type: `num`')); | |
| Future<void> test_pattern_record_variable() => assertStringContents(''' | |
| void f(({int foo}) x, num a) { | |
| (foo: a,) = [!x^!]; | |
| } | |
| ''', contains('Type: `({int foo})`')); | |
| Future<void> test_pattern_relational_variable() => assertStringContents(''' | |
| String f(int char) { | |
| const zero = 0; | |
| return switch (char) { | |
| == [!ze^ro!] => 'zero', | |
| _ => '', | |
| }; | |
| } | |
| ''', contains('Type: `int`')); | |
| Future<void> test_pattern_variable_wildcard() => assertStringContents(''' | |
| void f() { | |
| var a = (1, 2); | |
| var ([!^_!], _) = a; | |
| } | |
| ''', contains('Type: `int`')); | |
| Future<void> test_pattern_variable_wildcard_annotated() => | |
| assertStringContents(''' | |
| void f() { | |
| var a = (1, 2); | |
| var (int [!^_!], _) = a; | |
| } | |
| ''', contains('Type: `int`')); | |
| Future<void> test_plainText_simple() => assertPlainTextContents(''' | |
| /// This is a string. | |
| String? [!a^bc!]; | |
| ''', contains('This is a string.')); | |
| Future<void> test_promotedTypes() async { | |
| var content = ''' | |
| void f(aaa) { | |
| if (aaa is String) { | |
| print([!aa^a!]); | |
| } | |
| } | |
| '''; | |
| var expectedHoverContent = ''' | |
| ```dart | |
| dynamic aaa | |
| ``` | |
| Type: `String` | |
| Declared in `f` in *package:test/main.dart*.'''; | |
| await assertStringContents(content, equals(expectedHoverContent)); | |
| } | |
| Future<void> test_range_binaryOperator() async { | |
| var content = ''' | |
| var i = 1; | |
| var a = i [!+^!] i; | |
| '''; | |
| var expected = ''' | |
| ```dart | |
| num +(num other) | |
| ``` | |
| Declared in `num` in *dart:core*.'''; | |
| await assertStringContents(content, equals(expected)); | |
| } | |
| Future<void> test_range_compoundAssignment() async { | |
| var content = ''' | |
| void f(int i) { | |
| i [!+=^!] 1; | |
| } | |
| '''; | |
| var expected = ''' | |
| ```dart | |
| num +(num other) | |
| ``` | |
| Declared in `num` in *dart:core*.'''; | |
| await assertStringContents(content, equals(expected)); | |
| } | |
| Future<void> test_range_conditional_colon() async { | |
| var content = ''' | |
| var i = 1; | |
| int f(bool a, int x) { | |
| return a | |
| ? x | |
| [!:^!] x + 1; | |
| } | |
| '''; | |
| var expected = 'Type: `int`'; | |
| await assertStringContents(content, equals(expected)); | |
| } | |
| Future<void> test_range_conditional_question() async { | |
| var content = ''' | |
| var i = 1; | |
| int f(bool a, int x) { | |
| return a | |
| [!?^!] x | |
| : x + 1; | |
| } | |
| '''; | |
| var expected = 'Type: `int`'; | |
| await assertStringContents(content, equals(expected)); | |
| } | |
| Future<void> test_range_multiLineConstructorCall() => assertStringContents(''' | |
| final a = new [!Str^ing.fromCharCodes!]([ | |
| 1, | |
| 2, | |
| ]); | |
| ''', contains('String.fromCharCodes(')); | |
| Future<void> test_range_postfix() async { | |
| var content = ''' | |
| int f(int i) { | |
| return i[!+^+!]; | |
| } | |
| '''; | |
| var expected = ''' | |
| ```dart | |
| num +(num other) | |
| ``` | |
| Declared in `num` in *dart:core*.'''; | |
| await assertStringContents(content, equals(expected)); | |
| } | |
| Future<void> test_range_prefix() async { | |
| var content = ''' | |
| int f(int i) { | |
| return [!+^+!]i; | |
| } | |
| '''; | |
| var expected = ''' | |
| ```dart | |
| num +(num other) | |
| ``` | |
| Declared in `num` in *dart:core*.'''; | |
| await assertStringContents(content, equals(expected)); | |
| } | |
| Future<void> test_recordLiteral_named() => assertStringContents(r''' | |
| void f(({int f1, int f2}) r) { | |
| r.[!f^1!]; | |
| } | |
| ''', contains('Type: `int`')); | |
| Future<void> test_recordLiteral_positional() => assertStringContents(r''' | |
| void f((int, int) r) { | |
| r.[!$^1!]; | |
| } | |
| ''', contains('Type: `int`')); | |
| Future<void> test_recordType_parameter() => assertStringContents(''' | |
| Object f(([!dou^ble!], double) param) { | |
| return (1.0, 1.0); | |
| } | |
| ''', contains('class double')); | |
| Future<void> test_recordType_return() => assertStringContents(''' | |
| ([!dou^ble!], double) f() { | |
| return (1.0, 1.0); | |
| } | |
| ''', contains('class double')); | |
| Future<void> test_signatureFormatting_multiLine() => assertStringContents( | |
| ''' | |
| class Foo { | |
| Foo(String arg1, String arg2, [String? arg3]); | |
| } | |
| void f() { | |
| var a = [!Fo^o!]('', ''); | |
| } | |
| ''', | |
| startsWith(''' | |
| ```dart | |
| (new) Foo( | |
| String arg1, | |
| String arg2, [ | |
| String? arg3, | |
| ]) | |
| ```'''), | |
| ); | |
| Future<void> test_signatureFormatting_singleLine() => assertStringContents( | |
| ''' | |
| class Foo { | |
| Foo(String a, String b); | |
| } | |
| void f() { | |
| var a = [!Fo^o!]('', ''); | |
| } | |
| ''', | |
| startsWith(''' | |
| ```dart | |
| (new) Foo(String a, String b) | |
| ```'''), | |
| ); | |
| Future<void> test_staticType_field() async { | |
| var content = ''' | |
| class A<T> { | |
| late final T? myField; | |
| } | |
| final data = A<String?>().[!myF^ield!]; | |
| '''; | |
| var expectedHoverContent = | |
| ''' | |
| ```dart | |
| String? get myField | |
| ``` | |
| Type: `String?` | |
| Declared in `A` in *package:test/main.dart*. | |
| ''' | |
| .trim(); | |
| await assertStringContents(content, equals(expectedHoverContent)); | |
| } | |
| Future<void> test_staticType_getter() async { | |
| var content = ''' | |
| class A { | |
| String get myGetter => ''; | |
| } | |
| final data = A().[!myG^etter!]; | |
| '''; | |
| var expectedHoverContent = | |
| ''' | |
| ```dart | |
| String get myGetter | |
| ``` | |
| Type: `String` | |
| Declared in `A` in *package:test/main.dart*. | |
| ''' | |
| .trim(); | |
| await assertStringContents(content, equals(expectedHoverContent)); | |
| } | |
| Future<void> test_staticType_getter_generic() async { | |
| var content = ''' | |
| class A<T> { | |
| late final T? myField; | |
| T? get myGetter => myField; | |
| } | |
| final data = A<String?>().[!myG^etter!]; | |
| '''; | |
| var expectedHoverContent = | |
| ''' | |
| ```dart | |
| String? get myGetter | |
| ``` | |
| Type: `String?` | |
| Declared in `A` in *package:test/main.dart*. | |
| ''' | |
| .trim(); | |
| await assertStringContents(content, equals(expectedHoverContent)); | |
| } | |
| Future<void> test_staticType_setter() async { | |
| var content = ''' | |
| class A { | |
| set mySetter(String value) {} | |
| } | |
| void f() { | |
| A().[!myS^etter!] = ''; | |
| } | |
| '''; | |
| var expectedHoverContent = | |
| ''' | |
| ```dart | |
| set mySetter(String value) | |
| ``` | |
| Type: `String` | |
| Declared in `A` in *package:test/main.dart*. | |
| ''' | |
| .trim(); | |
| await assertStringContents(content, equals(expectedHoverContent)); | |
| } | |
| Future<void> test_staticType_setter_generic() async { | |
| var content = ''' | |
| class A<T> { | |
| set mySetter(T value) {} | |
| } | |
| void f() { | |
| A<String>().[!myS^etter!] = ''; | |
| } | |
| '''; | |
| var expectedHoverContent = | |
| ''' | |
| ```dart | |
| set mySetter(String value) | |
| ``` | |
| Type: `String` | |
| Declared in `A` in *package:test/main.dart*. | |
| ''' | |
| .trim(); | |
| await assertStringContents(content, equals(expectedHoverContent)); | |
| } | |
| Future<void> test_string_noDocComment() async { | |
| var content = ''' | |
| String? [!a^bc!]; | |
| '''; | |
| var expectedHoverContent = | |
| ''' | |
| ```dart | |
| String? abc | |
| ``` | |
| Type: `String?` | |
| Declared in *package:test/main.dart*. | |
| ''' | |
| .trim(); | |
| await assertStringContents(content, equals(expectedHoverContent)); | |
| } | |
| Future<void> test_string_reflectsLatestEdits() async { | |
| var original = TestCode.parse(''' | |
| /// Original string. | |
| String? [!a^bc!]; | |
| '''); | |
| var updated = TestCode.parse(''' | |
| /// Updated string. | |
| String? [!a^bc!]; | |
| '''); | |
| await initialize(); | |
| await openFile(mainFileUri, original.code); | |
| var hover = await getHover(mainFileUri, original.position.position); | |
| expect(hover, isNotNull); | |
| var contents = _getStringContents(hover!); | |
| expect(contents, contains('Original')); | |
| await replaceFile(222, mainFileUri, updated.code); | |
| hover = await getHover(mainFileUri, updated.position.position); | |
| expect(hover, isNotNull); | |
| contents = _getStringContents(hover!); | |
| expect(contents, contains('Updated')); | |
| } | |
| Future<void> test_string_simple() async { | |
| var content = ''' | |
| /// This is a string. | |
| String? [!a^bc!]; | |
| '''; | |
| var expected = ''' | |
| ```dart | |
| String? abc | |
| ``` | |
| Type: `String?` | |
| Declared in *package:test/main.dart*. | |
| --- | |
| This is a string.'''; | |
| await assertStringContents(content, equals(expected)); | |
| } | |
| Future<void> test_superFormalParameter_fromField() async { | |
| var content = ''' | |
| class A { | |
| /// The field a. | |
| int a; | |
| A(this.a); | |
| } | |
| class B extends A { | |
| B(super.a); | |
| } | |
| class C extends B { | |
| C(super.[!^a!]); | |
| } | |
| '''; | |
| var expected = ''' | |
| ```dart | |
| int a | |
| ``` | |
| Type: `int` | |
| Declared in `new` in `C` in *package:test/main.dart*. | |
| --- | |
| The field a.'''; | |
| await assertStringContents(content, equals(expected)); | |
| } | |
| Future<void> test_superFormalParameter_notFromChainedConstructor() async { | |
| var content = ''' | |
| class A { | |
| /// The constructor a. | |
| A(int a); | |
| } | |
| class B extends A { | |
| B(super.[!^a!]); | |
| } | |
| '''; | |
| var expected = ''' | |
| ```dart | |
| int a | |
| ``` | |
| Type: `int` | |
| Declared in `new` in `B` in *package:test/main.dart*.'''; | |
| await assertStringContents(content, equals(expected)); | |
| } | |
| Future<void> test_this() async { | |
| var content = ''' | |
| class A { | |
| void a() { | |
| [!thi^s!].a(); | |
| } | |
| } | |
| '''; | |
| var expected = ''' | |
| Type: `A`'''; | |
| await assertStringContents(content, equals(expected)); | |
| } | |
| Future<void> test_this_promoted() async { | |
| var content = ''' | |
| class A { | |
| void a() { | |
| if (this is B) { | |
| [!thi^s!].b(); | |
| } | |
| } | |
| } | |
| class B extends A { | |
| void b() {} | |
| } | |
| '''; | |
| var expected = ''' | |
| Type: `B`'''; | |
| await assertStringContents(content, equals(expected)); | |
| } | |
| Future<void> test_topLevelFunction_underscore() async { | |
| var content = ''' | |
| int _() => 1; | |
| int f() => [!^_!](); | |
| '''; | |
| var expected = ''' | |
| ```dart | |
| int _() | |
| ``` | |
| Type: `int Function()` | |
| Declared in *package:test/main.dart*.'''; | |
| await assertStringContents(content, equals(expected)); | |
| } | |
| Future<void> test_topLevelVariable_underscore() async { | |
| var content = ''' | |
| int _ = 1; | |
| int f() => [!^_!]; | |
| '''; | |
| var expected = ''' | |
| ```dart | |
| int get _ | |
| ``` | |
| Type: `int` | |
| Declared in *package:test/main.dart*.'''; | |
| await assertStringContents(content, equals(expected)); | |
| } | |
| Future<void> test_tryCatch_error() async { | |
| var content = ''' | |
| void foo() { | |
| try {} on Exception catch ([!err^or!], stack) {} | |
| } | |
| '''; | |
| var expected = ''' | |
| ```dart | |
| Exception error | |
| ``` | |
| Type: `Exception` | |
| Declared in `foo` in *package:test/main.dart*.'''; | |
| await assertStringContents(content, equals(expected)); | |
| } | |
| Future<void> test_tryCatch_stack() async { | |
| var content = ''' | |
| void foo() { | |
| try {} on Exception catch (error, [!stac^k!]) {} | |
| } | |
| '''; | |
| var expected = ''' | |
| ```dart | |
| StackTrace stack | |
| ``` | |
| Type: `StackTrace` | |
| Declared in `foo` in *package:test/main.dart*.'''; | |
| await assertStringContents(content, equals(expected)); | |
| } | |
| Future<void> test_typeParameter_class() async { | |
| var content = ''' | |
| class C<T> { | |
| late [!^T!] f; | |
| } | |
| '''; | |
| var expected = ''' | |
| ```dart | |
| <T> | |
| ``` | |
| Declared in `C` in *package:test/main.dart*.'''; | |
| await assertStringContents(content, equals(expected)); | |
| } | |
| Future<void> test_typeParameter_closureInFunction() async { | |
| var content = ''' | |
| void foo() { | |
| <O extends int>() { | |
| late [!^O!] o; | |
| }; | |
| } | |
| '''; | |
| var expected = ''' | |
| ```dart | |
| <O extends int> | |
| ``` | |
| Declared in `<unnamed>` in `foo` in *package:test/main.dart*.'''; | |
| await assertStringContents(content, equals(expected)); | |
| } | |
| Future<void> test_typeParameter_function() async { | |
| var content = ''' | |
| void foo<T, O extends T>() { | |
| foo<T, [!^O!]>(); | |
| } | |
| '''; | |
| var expected = ''' | |
| ```dart | |
| <O extends T> | |
| ``` | |
| Declared in `foo` in *package:test/main.dart*.'''; | |
| await assertStringContents(content, equals(expected)); | |
| } | |
| Future<void> test_typeParameter_functionInFunction() async { | |
| var content = ''' | |
| void foo() { | |
| void f<O extends int>() { | |
| late [!^O!] o; | |
| } | |
| } | |
| '''; | |
| var expected = ''' | |
| ```dart | |
| <O extends int> | |
| ``` | |
| Declared in `f` in `foo` in *package:test/main.dart*.'''; | |
| await assertStringContents(content, equals(expected)); | |
| } | |
| Future<void> test_typeParameter_functionTyped_parameter() async { | |
| var content = ''' | |
| void foo([!^T!] Function<T extends int>() f) {} | |
| '''; | |
| var expected = ''' | |
| ```dart | |
| <T extends int> | |
| ``` | |
| Declared in `f` in `foo` in *package:test/main.dart*.'''; | |
| await assertStringContents(content, equals(expected)); | |
| } | |
| Future<void> test_typeParameter_functionTyped_return() async { | |
| var content = ''' | |
| [!^T!] Function<T extends int>(T p) f() => <T extends int>(T p) => p; | |
| '''; | |
| var expected = ''' | |
| ```dart | |
| <T extends int> | |
| ``` | |
| Declared in `f` in *package:test/main.dart*.'''; | |
| await assertStringContents(content, equals(expected)); | |
| } | |
| Future<void> test_typeParameter_functionTyped_typedef() async { | |
| var content = ''' | |
| typedef F = [!^T!] Function<T extends int>(); | |
| '''; | |
| var expected = ''' | |
| ```dart | |
| <T extends int> | |
| ``` | |
| Declared in `F` in *package:test/main.dart*.'''; | |
| await assertStringContents(content, equals(expected)); | |
| } | |
| Future<void> test_typeParameter_wildcard() async { | |
| var content = ''' | |
| class C<[!^_!]> {} | |
| '''; | |
| await assertNullHover(content); | |
| } | |
| Future<void> test_unnamed_library_directive() async { | |
| var content = ''' | |
| /// This is a string. | |
| [!lib^rary!]; | |
| '''; | |
| var expected = ''' | |
| ```dart | |
| library package:test/main.dart | |
| ``` | |
| Declared in *package:test/main.dart*. | |
| --- | |
| This is a string.'''; | |
| await assertStringContents(content, equals(expected)); | |
| } | |
| Future<void> test_unopenFile() async { | |
| var content = ''' | |
| /// This is a string. | |
| String? [!a^bc!]; | |
| '''; | |
| var expected = ''' | |
| ```dart | |
| String? abc | |
| ``` | |
| Type: `String?` | |
| Declared in *package:test/main.dart*. | |
| --- | |
| This is a string.'''; | |
| await assertStringContents(withOpenFile: false, content, equals(expected)); | |
| } | |
| MarkupContent _getMarkupContents(Hover hover) { | |
| return hover.contents.map( | |
| (t1) => t1, | |
| (t2) => throw 'Hover contents were String, not MarkupContent', | |
| ); | |
| } | |
| String _getStringContents(Hover hover) { | |
| return hover.contents.map( | |
| (t1) => throw 'Hover contents were MarkupContent, not String', | |
| (t2) => t2, | |
| ); | |
| } | |
| } |