| // 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_generated.dart'; |
| import 'package:analysis_server/src/lsp/constants.dart'; |
| import 'package:test/test.dart'; |
| import 'package:test_reflective_loader/test_reflective_loader.dart'; |
| |
| import '../tool/lsp_spec/matchers.dart'; |
| import 'server_abstract.dart'; |
| |
| void main() { |
| defineReflectiveSuite(() { |
| defineReflectiveTests(DocumentSymbolsTest); |
| }); |
| } |
| |
| @reflectiveTest |
| class DocumentSymbolsTest extends AbstractLspAnalysisServerTest { |
| Future<void> test_enumMember_notSupported() async { |
| const content = ''' |
| enum Theme { |
| light, |
| } |
| '''; |
| newFile(mainFilePath, content: content); |
| await initialize(); |
| |
| final result = await getDocumentSymbols(mainFileUri.toString()); |
| final symbols = result.map( |
| (docsymbols) => throw 'Expected SymbolInformations, got DocumentSymbols', |
| (symbolInfos) => symbolInfos, |
| ); |
| expect(symbols, hasLength(2)); |
| |
| final themeEnum = symbols[0]; |
| expect(themeEnum.name, equals('Theme')); |
| expect(themeEnum.kind, equals(SymbolKind.Enum)); |
| expect(themeEnum.containerName, isNull); |
| |
| final enumValue = symbols[1]; |
| expect(enumValue.name, equals('light')); |
| // EnumMember is not in the original LSP list, so unless the client explicitly |
| // advertises support, we will fall back to Enum. |
| expect(enumValue.kind, equals(SymbolKind.Enum)); |
| expect(enumValue.containerName, 'Theme'); |
| } |
| |
| Future<void> test_enumMember_supported() async { |
| const content = ''' |
| enum Theme { |
| light, |
| } |
| '''; |
| newFile(mainFilePath, content: content); |
| await initialize( |
| textDocumentCapabilities: withDocumentSymbolKinds( |
| emptyTextDocumentClientCapabilities, |
| [SymbolKind.Enum, SymbolKind.EnumMember], |
| ), |
| ); |
| |
| final result = await getDocumentSymbols(mainFileUri.toString()); |
| final symbols = result.map( |
| (docsymbols) => throw 'Expected SymbolInformations, got DocumentSymbols', |
| (symbolInfos) => symbolInfos, |
| ); |
| expect(symbols, hasLength(2)); |
| |
| final themeEnum = symbols[0]; |
| expect(themeEnum.name, equals('Theme')); |
| expect(themeEnum.kind, equals(SymbolKind.Enum)); |
| expect(themeEnum.containerName, isNull); |
| |
| final enumValue = symbols[1]; |
| expect(enumValue.name, equals('light')); |
| expect(enumValue.kind, equals(SymbolKind.EnumMember)); |
| expect(enumValue.containerName, 'Theme'); |
| } |
| |
| Future<void> test_flat() async { |
| const content = ''' |
| String topLevel = ''; |
| class MyClass { |
| int myField; |
| MyClass(this.myField); |
| myMethod() {} |
| } |
| extension StringExtensions on String {} |
| extension on String {} |
| '''; |
| newFile(mainFilePath, content: content); |
| await initialize(); |
| |
| final result = await getDocumentSymbols(mainFileUri.toString()); |
| final symbols = result.map( |
| (docsymbols) => throw 'Expected SymbolInformations, got DocumentSymbols', |
| (symbolInfos) => symbolInfos, |
| ); |
| expect(symbols, hasLength(7)); |
| |
| final topLevel = symbols[0]; |
| expect(topLevel.name, equals('topLevel')); |
| expect(topLevel.kind, equals(SymbolKind.Variable)); |
| expect(topLevel.containerName, isNull); |
| |
| final myClass = symbols[1]; |
| expect(myClass.name, equals('MyClass')); |
| expect(myClass.kind, equals(SymbolKind.Class)); |
| expect(myClass.containerName, isNull); |
| |
| final field = symbols[2]; |
| expect(field.name, equals('myField')); |
| expect(field.kind, equals(SymbolKind.Field)); |
| expect(field.containerName, equals(myClass.name)); |
| |
| final constructor = symbols[3]; |
| expect(constructor.name, equals('MyClass')); |
| expect(constructor.kind, equals(SymbolKind.Constructor)); |
| expect(constructor.containerName, equals(myClass.name)); |
| |
| final method = symbols[4]; |
| expect(method.name, equals('myMethod')); |
| expect(method.kind, equals(SymbolKind.Method)); |
| expect(method.containerName, equals(myClass.name)); |
| |
| final namedExtension = symbols[5]; |
| expect(namedExtension.name, equals('StringExtensions')); |
| expect(namedExtension.containerName, isNull); |
| |
| final unnamedExtension = symbols[6]; |
| expect(unnamedExtension.name, equals('<unnamed extension>')); |
| expect(unnamedExtension.containerName, isNull); |
| } |
| |
| Future<void> test_hierarchical() async { |
| const content = ''' |
| String topLevel = ''; |
| class MyClass { |
| int myField; |
| MyClass(this.myField); |
| myMethod() {} |
| } |
| '''; |
| newFile(mainFilePath, content: content); |
| await initialize( |
| textDocumentCapabilities: withHierarchicalDocumentSymbolSupport( |
| emptyTextDocumentClientCapabilities)); |
| |
| final result = await getDocumentSymbols(mainFileUri.toString()); |
| final symbols = result.map( |
| (docsymbols) => docsymbols, |
| (symbolInfos) => throw 'Expected DocumentSymbols, got SymbolInformations', |
| ); |
| |
| expect(symbols, hasLength(2)); |
| |
| final topLevel = symbols[0]; |
| expect(topLevel.name, equals('topLevel')); |
| expect(topLevel.kind, equals(SymbolKind.Variable)); |
| |
| final myClass = symbols[1]; |
| expect(myClass.name, equals('MyClass')); |
| expect(myClass.kind, equals(SymbolKind.Class)); |
| expect(myClass.children, hasLength(3)); |
| |
| final field = myClass.children![0]; |
| expect(field.name, equals('myField')); |
| expect(field.kind, equals(SymbolKind.Field)); |
| |
| final constructor = myClass.children![1]; |
| expect(constructor.name, equals('MyClass')); |
| expect(constructor.kind, equals(SymbolKind.Constructor)); |
| |
| final method = myClass.children![2]; |
| expect(method.name, equals('myMethod')); |
| expect(method.kind, equals(SymbolKind.Method)); |
| } |
| |
| Future<void> test_noAnalysisRoot_openedFile() async { |
| // When there are no analysis roots and we open a file, it should be added as |
| // a temporary root allowing us to service requests for it. |
| const content = 'class MyClass {}'; |
| newFile(mainFilePath, content: content); |
| await initialize(allowEmptyRootUri: true); |
| await openFile(mainFileUri, content); |
| |
| final result = await getDocumentSymbols(mainFileUri.toString()); |
| final symbols = result.map( |
| (docsymbols) => throw 'Expected SymbolInformations, got DocumentSymbols', |
| (symbolInfos) => symbolInfos, |
| ); |
| expect(symbols, hasLength(1)); |
| |
| final myClass = symbols[0]; |
| expect(myClass.name, equals('MyClass')); |
| expect(myClass.kind, equals(SymbolKind.Class)); |
| expect(myClass.containerName, isNull); |
| } |
| |
| Future<void> test_noAnalysisRoot_unopenedFile() async { |
| // When there are no analysis roots and we receive requests for a file that |
| // was not opened, we will reject the file due to not being analyzed. |
| const content = 'class MyClass {}'; |
| newFile(mainFilePath, content: content); |
| await initialize(allowEmptyRootUri: true); |
| |
| await expectLater( |
| getDocumentSymbols(mainFileUri.toString()), |
| throwsA(isResponseError(ServerErrorCodes.FileNotAnalyzed, |
| message: 'File is not being analyzed'))); |
| } |
| |
| Future<void> test_nonDartFile() async { |
| newFile(pubspecFilePath, content: simplePubspecContent); |
| await initialize(); |
| |
| final result = await getDocumentSymbols(pubspecFileUri.toString()); |
| // Since the list is empty, it will deserialise into whatever the first |
| // type is, so just accept both types. |
| final symbols = result.map( |
| (docsymbols) => docsymbols, |
| (symbolInfos) => symbolInfos, |
| ); |
| expect(symbols, isEmpty); |
| } |
| } |