blob: c617811985ed73e14e948d26d64a1c4d547fb2c3 [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_generated.dart';
import 'package:analysis_server/lsp_protocol/protocol_special.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(WorkspaceSymbolsTest);
});
}
@reflectiveTest
class WorkspaceSymbolsTest extends AbstractLspAnalysisServerTest {
Future<void> test_cancellation() async {
const content = '''
void f() {}
''';
newFile(mainFilePath, withoutMarkers(content));
await initialize();
final symbolsRequest1 = makeRequest(
Method.workspace_symbol,
WorkspaceSymbolParams(query: 'f'),
);
final symbolsCancellation1 = makeNotification(
Method.cancelRequest, CancelParams(id: symbolsRequest1.id));
final symbolsRequest2 = makeRequest(
Method.workspace_symbol,
WorkspaceSymbolParams(query: 'f'),
);
final responses = await Future.wait([
sendRequestToServer(symbolsRequest1),
sendNotificationToServer(symbolsCancellation1),
sendRequestToServer(symbolsRequest2),
]);
// Expect the first response was cancelled.
final symbolsResponse1 = responses[0] as ResponseMessage;
expect(symbolsResponse1.result, isNull);
expect(symbolsResponse1.error, isNotNull);
expect(
symbolsResponse1.error,
isResponseError(ErrorCodes.RequestCancelled),
);
// But second to complete normally.
final symbolsResponse2 = responses[2] as ResponseMessage;
expect(symbolsResponse2.result, hasLength(greaterThanOrEqualTo(1)));
expect(symbolsResponse2.error, isNull);
}
Future<void> test_extensions() async {
const content = '''
extension StringExtensions on String {}
extension on String {}
''';
newFile(mainFilePath, withoutMarkers(content));
await initialize();
final symbols = await getWorkspaceSymbols('S');
final 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_fullMatch() async {
const content = '''
[[String topLevel = '']];
class MyClass {
int myField;
MyClass(this.myField);
myMethod() {}
}
''';
newFile(mainFilePath, withoutMarkers(content));
await initialize();
final symbols = await getWorkspaceSymbols('topLevel');
final topLevel = symbols.firstWhere((s) => s.name == 'topLevel');
expect(topLevel.kind, equals(SymbolKind.Variable));
expect(topLevel.containerName, isNull);
expect(topLevel.location.uri, equals(mainFileUri.toString()));
expect(topLevel.location.range, equals(rangeFromMarkers(content)));
// 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() {}
}
''';
newFile(mainFilePath, withoutMarkers(content));
await initialize();
// meld should match myField
final symbols = await getWorkspaceSymbols('meld');
final field = symbols.firstWhere((s) => s.name == 'myField');
expect(field.kind, equals(SymbolKind.Field));
expect(field.containerName, equals('MyClass'));
expect(field.location.uri, equals(mainFileUri.toString()));
expect(field.location.range, equals(rangeFromMarkers(content)));
// 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.
final request = RequestMessage(
id: Either2<int, String>.t1(1),
method: Method.workspace_symbol,
params: <String, dynamic>{},
jsonrpc: jsonRpcVersion,
);
final response = await sendRequestToServer(request);
final 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'),
);
}
Future<void> test_partialMatch() async {
const content = '''
String topLevel = '';
class MyClass {
[[int myField]];
MyClass(this.myField);
[[myMethod() {}]]
[[myMethodWithArgs(int a) {}]]
}
''';
newFile(mainFilePath, withoutMarkers(content));
await initialize();
final symbols = await getWorkspaceSymbols('my');
final ranges = rangesFromMarkers(content);
final fieldRange = ranges[0];
final methodRange = ranges[1];
final methodWithArgsRange = ranges[2];
final field = symbols.firstWhere((s) => s.name == 'myField');
expect(field.kind, equals(SymbolKind.Field));
expect(field.containerName, equals('MyClass'));
expect(field.location.uri, equals(mainFileUri.toString()));
expect(field.location.range, equals(fieldRange));
final klass = symbols.firstWhere((s) => s.name == 'MyClass');
expect(klass.kind, equals(SymbolKind.Class));
expect(klass.containerName, isNull);
expect(klass.location.uri, equals(mainFileUri.toString()));
final method = symbols.firstWhere((s) => s.name == 'myMethod()');
expect(method.kind, equals(SymbolKind.Method));
expect(method.containerName, equals('MyClass'));
expect(method.location.uri, equals(mainFileUri.toString()));
expect(method.location.range, equals(methodRange));
final methodWithArgs =
symbols.firstWhere((s) => s.name == 'myMethodWithArgs(…)');
expect(methodWithArgs.kind, equals(SymbolKind.Method));
expect(methodWithArgs.containerName, equals('MyClass'));
expect(methodWithArgs.location.uri, equals(mainFileUri.toString()));
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);
}
}