blob: 9402493faf9164bcb16057c06004440957e20bca [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: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(WorkspaceSymbolsTest);
});
}
@reflectiveTest
class WorkspaceSymbolsTest extends AbstractLspAnalysisServerTest {
Future<void> test_cancellation() async {
const content = '''
void f() {}
''';
var code = TestCode.parse(content);
newFile(mainFilePath, code.code);
await initialize();
var symbolsRequest1 = makeRequest(
Method.workspace_symbol,
WorkspaceSymbolParams(query: 'f'),
);
var symbolsCancellation1 = makeNotification(
Method.cancelRequest, CancelParams(id: symbolsRequest1.id));
var symbolsRequest2 = makeRequest(
Method.workspace_symbol,
WorkspaceSymbolParams(query: 'f'),
);
var responses = await Future.wait([
sendRequestToServer(symbolsRequest1),
sendNotificationToServer(symbolsCancellation1),
sendRequestToServer(symbolsRequest2),
]);
// Expect the first response was cancelled.
var symbolsResponse1 = responses[0] as ResponseMessage;
expect(symbolsResponse1.result, isNull);
expect(symbolsResponse1.error, isNotNull);
expect(
symbolsResponse1.error,
isResponseError(ErrorCodes.RequestCancelled),
);
// But second to complete normally.
var symbolsResponse2 = responses[2] as ResponseMessage;
expect(symbolsResponse2.result, hasLength(greaterThanOrEqualTo(1)));
expect(symbolsResponse2.error, isNull);
}
Future<void> test_dependencies_excluded() async {
newFile(mainFilePath, 'class LocalClass12345 {}');
await provideConfig(
initialize,
{
'includeDependenciesInWorkspaceSymbols': false,
},
);
expect(await getWorkspaceSymbols('Duration'), isEmpty);
expect(await getWorkspaceSymbols('LocalClass12345'), isNotEmpty);
}
Future<void> test_dependencies_included() async {
newFile(mainFilePath, 'class LocalClass12345 {}');
await provideConfig(
initialize,
{
'includeDependenciesInWorkspaceSymbols': true,
},
);
expect(await getWorkspaceSymbols('Duration'), isNotEmpty);
expect(await getWorkspaceSymbols('LocalClass12345'), isNotEmpty);
}
Future<void> test_dependencies_includedByDefault() async {
newFile(mainFilePath, 'class LocalClass12345 {}');
await initialize();
expect(await getWorkspaceSymbols('Duration'), isNotEmpty);
expect(await getWorkspaceSymbols('LocalClass12345'), isNotEmpty);
}
Future<void> test_extensions() async {
const content = '''
extension StringExtensions on String {}
extension on String {}
''';
var code = TestCode.parse(content);
newFile(mainFilePath, code.code);
await initialize();
var symbols = await getWorkspaceSymbols('S');
var namedExtensions =
symbols.firstWhere((s) => s.name == 'StringExtensions');
expect(namedExtensions.kind, equals(SymbolKind.Class));
expect(namedExtensions.containerName, isNull);
// Unnamed extensions are not returned in Workspace Symbols.
}
Future<void> test_extensionType() async {
const content = r'''
extension type MyExtensionType(int it) {}
''';
var code = TestCode.parse(content);
newFile(mainFilePath, code.code);
await initialize();
var symbols = await getWorkspaceSymbols('MyExt');
var namedExtensions =
symbols.firstWhere((s) => s.name == 'MyExtensionType');
expect(namedExtensions.kind, equals(SymbolKind.Class));
expect(namedExtensions.containerName, isNull);
}
Future<void> test_extensionType_method() async {
const content = r'''
extension type E(int it) {
void foo() {}
}
''';
var code = TestCode.parse(content);
newFile(mainFilePath, code.code);
await initialize();
var symbols = await getWorkspaceSymbols('foo');
var namedExtensions =
symbols.firstWhere((s) => s.name == 'foo()' && s.containerName == 'E');
expect(namedExtensions.kind, equals(SymbolKind.Method));
expect(namedExtensions.containerName, 'E');
}
Future<void> test_fullMatch() async {
const content = '''
[!String topLevel = ''!];
class MyClass {
int myField;
MyClass(this.myField);
myMethod() {}
}
''';
var code = TestCode.parse(content);
newFile(mainFilePath, code.code);
await initialize();
var symbols = await getWorkspaceSymbols('topLevel');
var topLevel = symbols.firstWhere((s) => s.name == 'topLevel');
expect(topLevel.kind, equals(SymbolKind.Variable));
expect(topLevel.containerName, isNull);
expect(topLevel.location.uri, equals(mainFileUri));
expect(topLevel.location.range, equals(code.range.range));
// Ensure we didn't get some things that definitely do not match.
expect(symbols.any((s) => s.name.contains('MyClass')), isFalse);
expect(symbols.any((s) => s.name.contains('myMethod')), isFalse);
}
Future<void> test_fuzzyMatch() async {
const content = '''
String topLevel = '';
class MyClass {
[!int myField!];
MyClass(this.myField);
myMethod() {}
}
''';
var code = TestCode.parse(content);
newFile(mainFilePath, code.code);
await initialize();
// meld should match myField
var symbols = await getWorkspaceSymbols('meld');
var field = symbols.firstWhere((s) => s.name == 'myField');
expect(field.kind, equals(SymbolKind.Field));
expect(field.containerName, equals('MyClass'));
expect(field.location.uri, equals(mainFileUri));
expect(field.location.range, equals(code.range.range));
// Ensure we didn't get some things that definitely do not match.
expect(symbols.any((s) => s.name.contains('MyClass')), isFalse);
expect(symbols.any((s) => s.name.contains('myMethod')), isFalse);
}
Future<void> test_invalidParams() async {
await initialize();
// Create a request that doesn't supply the query param.
var request = RequestMessage(
id: Either2<int, String>.t1(1),
method: Method.workspace_symbol,
params: <String, dynamic>{},
jsonrpc: jsonRpcVersion,
);
var response = await sendRequestToServer(request);
var error = response.error!;
expect(error.code, equals(ErrorCodes.InvalidParams));
// Ensure the error is useful to the client.
expect(
error.message,
equals('Invalid params for workspace/symbol:\n'
'params.query must not be undefined'),
);
}
/// Ensure that multiple projects/drivers do not result in duplicate results
/// for things referenced in both projects.
Future<void> test_overlappingDrivers() async {
// Reference an SDK lib.
const content = "import 'dart:core';";
// Project 1
newFile(mainFilePath, content);
// Project 2
var otherFilePath = convertPath('/home/otherProject/foo.dart');
newFile(otherFilePath, content);
// Initialize with both projects as roots.
await initialize(workspaceFolders: [
projectFolderUri,
pathContext.toUri(convertPath('/home/otherProject')),
]);
// Search for something in the SDK that's referenced by both projects and
// expect it only shows up once.
var symbols = await getWorkspaceSymbols('Duration');
expect(symbols.where((s) => s.name == 'Duration'), hasLength(1));
}
Future<void> test_partialMatch() async {
const content = '''
String topLevel = '';
class MyClass {
/*[0*/int myField/*0]*/;
MyClass(this.myField);
/*[1*/myMethod() {}/*1]*/
/*[2*/myMethodWithArgs(int a) {}/*2]*/
}
''';
var code = TestCode.parse(content);
newFile(mainFilePath, code.code);
await initialize();
var symbols = await getWorkspaceSymbols('my');
var ranges = code.ranges.ranges;
var fieldRange = ranges[0];
var methodRange = ranges[1];
var methodWithArgsRange = ranges[2];
var field = symbols.firstWhere((s) => s.name == 'myField');
expect(field.kind, equals(SymbolKind.Field));
expect(field.containerName, equals('MyClass'));
expect(field.location.uri, equals(mainFileUri));
expect(field.location.range, equals(fieldRange));
var klass = symbols.firstWhere((s) => s.name == 'MyClass');
expect(klass.kind, equals(SymbolKind.Class));
expect(klass.containerName, isNull);
expect(klass.location.uri, equals(mainFileUri));
var method = symbols.firstWhere((s) => s.name == 'myMethod()');
expect(method.kind, equals(SymbolKind.Method));
expect(method.containerName, equals('MyClass'));
expect(method.location.uri, equals(mainFileUri));
expect(method.location.range, equals(methodRange));
var methodWithArgs =
symbols.firstWhere((s) => s.name == 'myMethodWithArgs(…)');
expect(methodWithArgs.kind, equals(SymbolKind.Method));
expect(methodWithArgs.containerName, equals('MyClass'));
expect(methodWithArgs.location.uri, equals(mainFileUri));
expect(methodWithArgs.location.range, equals(methodWithArgsRange));
// Ensure we didn't get some things that definitely do not match.
expect(symbols.any((s) => s.name == 'topLevel'), isFalse);
}
}