Migrate lib/src/protocol_server.dart and related.
Change-Id: Ibb1e5a53aa8ad67fa43b315880c491093b051e20 Reviewed-on: https://dart-review.googlesource.com/c/sdk/+/194201 Reviewed-by: Brian Wilkerson <brianwilkerson@google.com> Commit-Queue: Konstantin Shcheglov <scheglov@google.com>
This commit is contained in:
committed by
commit-bot@chromium.org
parent
cbada0ac1c
commit
d8da40ec52
@@ -2,8 +2,6 @@
|
||||
// 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.
|
||||
|
||||
// @dart = 2.9
|
||||
|
||||
/// Utilities for converting Dart entities into analysis server's protocol
|
||||
/// entities.
|
||||
import 'package:analysis_server/src/protocol_server.dart';
|
||||
@@ -130,7 +128,7 @@ String getElementDisplayName(engine.Element element) {
|
||||
}
|
||||
}
|
||||
|
||||
String _getParametersString(engine.Element element) {
|
||||
String? _getParametersString(engine.Element element) {
|
||||
// TODO(scheglov) expose the corresponding feature from ExecutableElement
|
||||
List<engine.ParameterElement> parameters;
|
||||
if (element is engine.ExecutableElement) {
|
||||
@@ -179,8 +177,8 @@ String _getParametersString(engine.Element element) {
|
||||
return '(' + sb.toString() + ')';
|
||||
}
|
||||
|
||||
String _getTypeParametersString(engine.Element element) {
|
||||
List<engine.TypeParameterElement> typeParameters;
|
||||
String? _getTypeParametersString(engine.Element element) {
|
||||
List<engine.TypeParameterElement>? typeParameters;
|
||||
if (element is engine.ClassElement) {
|
||||
typeParameters = element.typeParameters;
|
||||
} else if (element is engine.TypeAliasElement) {
|
||||
|
||||
@@ -2,13 +2,12 @@
|
||||
// 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.
|
||||
|
||||
// @dart = 2.9
|
||||
|
||||
import 'package:analysis_server/plugin/protocol/protocol_dart.dart';
|
||||
import 'package:analysis_server/protocol/protocol_generated.dart';
|
||||
import 'package:analysis_server/src/services/correction/fix.dart';
|
||||
import 'package:analysis_server/src/services/search/search_engine.dart'
|
||||
as engine;
|
||||
import 'package:analysis_server/src/utilities/extensions/element.dart';
|
||||
import 'package:analyzer/dart/analysis/results.dart' as engine;
|
||||
import 'package:analyzer/dart/ast/ast.dart' as engine;
|
||||
import 'package:analyzer/dart/element/element.dart' as engine;
|
||||
@@ -35,7 +34,7 @@ List<AnalysisError> doAnalysisError_listFromEngine(
|
||||
/// Adds [edit] to the file containing the given [element].
|
||||
void doSourceChange_addElementEdit(
|
||||
SourceChange change, engine.Element element, SourceEdit edit) {
|
||||
var source = element.source;
|
||||
var source = element.source!;
|
||||
doSourceChange_addSourceEdit(change, source, edit);
|
||||
}
|
||||
|
||||
@@ -47,7 +46,7 @@ void doSourceChange_addSourceEdit(
|
||||
change.addEdit(file, isNewFile ? -1 : 0, edit);
|
||||
}
|
||||
|
||||
String getAliasedTypeString(engine.Element element) {
|
||||
String? getAliasedTypeString(engine.Element element) {
|
||||
if (element is engine.TypeAliasElement) {
|
||||
var aliasedType = element.aliasedType;
|
||||
return aliasedType.getDisplayString(withNullability: false);
|
||||
@@ -55,18 +54,16 @@ String getAliasedTypeString(engine.Element element) {
|
||||
return null;
|
||||
}
|
||||
|
||||
String getReturnTypeString(engine.Element element) {
|
||||
String? getReturnTypeString(engine.Element element) {
|
||||
if (element is engine.ExecutableElement) {
|
||||
if (element.kind == engine.ElementKind.SETTER) {
|
||||
return null;
|
||||
} else {
|
||||
return element.returnType?.getDisplayString(withNullability: false);
|
||||
return element.returnType.getDisplayString(withNullability: false);
|
||||
}
|
||||
} else if (element is engine.VariableElement) {
|
||||
var type = element.type;
|
||||
return type != null
|
||||
? type.getDisplayString(withNullability: false)
|
||||
: 'dynamic';
|
||||
return type.getDisplayString(withNullability: false);
|
||||
} else if (element is engine.TypeAliasElement) {
|
||||
var aliasedType = element.aliasedType;
|
||||
if (aliasedType is FunctionType) {
|
||||
@@ -107,7 +104,7 @@ List<T> mapEngineErrors<T>(
|
||||
/// If an [errorSeverity] is specified, it will override the one in [error].
|
||||
AnalysisError newAnalysisError_fromEngine(
|
||||
engine.ResolvedUnitResult result, engine.AnalysisError error,
|
||||
[engine.ErrorSeverity errorSeverity]) {
|
||||
[engine.ErrorSeverity? errorSeverity]) {
|
||||
var errorCode = error.errorCode;
|
||||
// prepare location
|
||||
Location location;
|
||||
@@ -115,23 +112,16 @@ AnalysisError newAnalysisError_fromEngine(
|
||||
var file = error.source.fullName;
|
||||
var offset = error.offset;
|
||||
var length = error.length;
|
||||
var startLine = -1;
|
||||
var startColumn = -1;
|
||||
var endLine = -1;
|
||||
var endColumn = -1;
|
||||
var lineInfo = result.lineInfo;
|
||||
if (lineInfo != null) {
|
||||
var startLocation = lineInfo.getLocation(offset);
|
||||
if (startLocation != null) {
|
||||
startLine = startLocation.lineNumber;
|
||||
startColumn = startLocation.columnNumber;
|
||||
}
|
||||
var endLocation = lineInfo.getLocation(offset + length);
|
||||
if (endLocation != null) {
|
||||
endLine = endLocation.lineNumber;
|
||||
endColumn = endLocation.columnNumber;
|
||||
}
|
||||
}
|
||||
|
||||
var startLocation = lineInfo.getLocation(offset);
|
||||
var startLine = startLocation.lineNumber;
|
||||
var startColumn = startLocation.columnNumber;
|
||||
|
||||
var endLocation = lineInfo.getLocation(offset + length);
|
||||
var endLine = endLocation.lineNumber;
|
||||
var endColumn = endLocation.columnNumber;
|
||||
|
||||
location = Location(
|
||||
file, offset, length, startLine, startColumn, endLine, endColumn);
|
||||
}
|
||||
@@ -144,7 +134,7 @@ AnalysisError newAnalysisError_fromEngine(
|
||||
var type = AnalysisErrorType(errorCode.type.name);
|
||||
var message = error.message;
|
||||
var code = errorCode.name.toLowerCase();
|
||||
List<DiagnosticMessage> contextMessages;
|
||||
List<DiagnosticMessage>? contextMessages;
|
||||
if (error.contextMessages.isNotEmpty) {
|
||||
contextMessages = error.contextMessages
|
||||
.map((message) => newDiagnosticMessage(result, message))
|
||||
@@ -182,7 +172,7 @@ DiagnosticMessage newDiagnosticMessage(
|
||||
}
|
||||
|
||||
/// Create a Location based on an [engine.Element].
|
||||
Location newLocation_fromElement(engine.Element element) {
|
||||
Location? newLocation_fromElement(engine.Element? element) {
|
||||
if (element == null || element.source == null) {
|
||||
return null;
|
||||
}
|
||||
@@ -206,8 +196,8 @@ Location newLocation_fromMatch(engine.SearchMatch match) {
|
||||
|
||||
/// Create a Location based on an [engine.AstNode].
|
||||
Location newLocation_fromNode(engine.AstNode node) {
|
||||
var unit = node.thisOrAncestorOfType<engine.CompilationUnit>();
|
||||
var unitElement = unit.declaredElement;
|
||||
var unit = node.thisOrAncestorOfType<engine.CompilationUnit>()!;
|
||||
var unitElement = unit.declaredElement!;
|
||||
var range = engine.SourceRange(node.offset, node.length);
|
||||
return _locationForArgs(unitElement, range);
|
||||
}
|
||||
@@ -215,13 +205,13 @@ Location newLocation_fromNode(engine.AstNode node) {
|
||||
/// Create a Location based on an [engine.CompilationUnit].
|
||||
Location newLocation_fromUnit(
|
||||
engine.CompilationUnit unit, engine.SourceRange range) {
|
||||
return _locationForArgs(unit.declaredElement, range);
|
||||
return _locationForArgs(unit.declaredElement!, range);
|
||||
}
|
||||
|
||||
/// Construct based on an element from the analyzer engine.
|
||||
OverriddenMember newOverriddenMember_fromEngine(engine.Element member) {
|
||||
var element = convertElement(member);
|
||||
var className = member.enclosingElement.displayName;
|
||||
var className = member.enclosingElement!.displayName;
|
||||
return OverriddenMember(element, className);
|
||||
}
|
||||
|
||||
@@ -258,22 +248,19 @@ SearchResultKind newSearchResultKind_fromEngine(engine.MatchKind kind) {
|
||||
|
||||
/// Construct based on a SourceRange.
|
||||
SourceEdit newSourceEdit_range(engine.SourceRange range, String replacement,
|
||||
{String id}) {
|
||||
{String? id}) {
|
||||
return SourceEdit(range.offset, range.length, replacement, id: id);
|
||||
}
|
||||
|
||||
List<Element> _computePath(engine.Element element) {
|
||||
var path = <Element>[];
|
||||
while (element != null) {
|
||||
path.add(convertElement(element));
|
||||
// go up
|
||||
if (element is engine.PrefixElement) {
|
||||
// imports are library children, but they are physically in the unit
|
||||
engine.LibraryElement library = element.enclosingElement;
|
||||
element = library.definingCompilationUnit;
|
||||
} else {
|
||||
element = element.enclosingElement;
|
||||
}
|
||||
|
||||
if (element is engine.PrefixElement) {
|
||||
element = element.enclosingElement.definingCompilationUnit;
|
||||
}
|
||||
|
||||
for (var e in element.withAncestors) {
|
||||
path.add(convertElement(e));
|
||||
}
|
||||
return path;
|
||||
}
|
||||
@@ -282,18 +269,23 @@ engine.CompilationUnitElement _getUnitElement(engine.Element element) {
|
||||
if (element is engine.CompilationUnitElement) {
|
||||
return element;
|
||||
}
|
||||
if (element?.enclosingElement is engine.LibraryElement) {
|
||||
element = element.enclosingElement;
|
||||
|
||||
var enclosingElement = element.enclosingElement;
|
||||
if (enclosingElement is engine.LibraryElement) {
|
||||
element = enclosingElement;
|
||||
}
|
||||
|
||||
if (element is engine.LibraryElement) {
|
||||
return element.definingCompilationUnit;
|
||||
}
|
||||
for (; element != null; element = element.enclosingElement) {
|
||||
if (element is engine.CompilationUnitElement) {
|
||||
return element;
|
||||
|
||||
for (var e in element.withAncestors) {
|
||||
if (e is engine.CompilationUnitElement) {
|
||||
return e;
|
||||
}
|
||||
}
|
||||
return null;
|
||||
|
||||
throw StateError('No unit: $element');
|
||||
}
|
||||
|
||||
/// Creates a new [Location].
|
||||
|
||||
@@ -40,6 +40,19 @@ extension ElementExtension on Element {
|
||||
return ancestor is CompilationUnitElement &&
|
||||
ancestor.enclosingElement.hasDeprecated;
|
||||
}
|
||||
|
||||
/// Return this element and all its enclosing elements.
|
||||
Iterable<Element> get withAncestors sync* {
|
||||
var current = this;
|
||||
while (true) {
|
||||
yield current;
|
||||
var enclosing = current.enclosingElement;
|
||||
if (enclosing == null) {
|
||||
break;
|
||||
}
|
||||
current = enclosing;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
extension MethodElementExtensions on MethodElement {
|
||||
|
||||
@@ -32,6 +32,7 @@ import 'package:test/test.dart' hide expect;
|
||||
import 'package:test/test.dart' as test show expect;
|
||||
|
||||
import '../mocks.dart';
|
||||
import '../mocks_lsp.dart';
|
||||
import '../src/utilities/mock_packages.dart';
|
||||
|
||||
const dartLanguageId = 'dart';
|
||||
|
||||
@@ -2,27 +2,17 @@
|
||||
// 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.
|
||||
|
||||
// @dart = 2.9
|
||||
|
||||
import 'dart:async';
|
||||
import 'dart:convert';
|
||||
|
||||
import 'package:analysis_server/lsp_protocol/protocol_generated.dart' as lsp;
|
||||
import 'package:analysis_server/lsp_protocol/protocol_generated.dart';
|
||||
import 'package:analysis_server/lsp_protocol/protocol_special.dart' as lsp;
|
||||
import 'package:analysis_server/protocol/protocol.dart';
|
||||
import 'package:analysis_server/protocol/protocol_generated.dart';
|
||||
import 'package:analysis_server/src/lsp/channel/lsp_channel.dart';
|
||||
import 'package:analyzer/src/generated/source.dart';
|
||||
import 'package:analyzer/src/generated/timestamped_data.dart';
|
||||
import 'package:http/http.dart' as http;
|
||||
import 'package:test/test.dart';
|
||||
|
||||
const _jsonEncoder = JsonEncoder.withIndent(' ');
|
||||
|
||||
/// A [Matcher] that check that the given [Response] has an expected identifier
|
||||
/// and has an error. The error code may optionally be checked.
|
||||
Matcher isResponseFailure(String id, [RequestErrorCode code]) =>
|
||||
Matcher isResponseFailure(String id, [RequestErrorCode? code]) =>
|
||||
_IsResponseFailure(id, code);
|
||||
|
||||
/// A [Matcher] that check that the given [Response] has an expected identifier
|
||||
@@ -30,7 +20,7 @@ Matcher isResponseFailure(String id, [RequestErrorCode code]) =>
|
||||
Matcher isResponseSuccess(String id) => _IsResponseSuccess(id);
|
||||
|
||||
class MockHttpClient extends http.BaseClient {
|
||||
Future<http.Response> Function(http.BaseRequest request) sendHandler;
|
||||
late Future<http.Response> Function(http.BaseRequest request) sendHandler;
|
||||
int sendHandlerCalls = 0;
|
||||
bool wasClosed = false;
|
||||
|
||||
@@ -57,242 +47,36 @@ class MockHttpClient extends http.BaseClient {
|
||||
}
|
||||
}
|
||||
|
||||
/// A mock [LspServerCommunicationChannel] for testing [LspAnalysisServer].
|
||||
class MockLspServerChannel implements LspServerCommunicationChannel {
|
||||
final StreamController<lsp.Message> _clientToServer =
|
||||
StreamController<lsp.Message>.broadcast();
|
||||
final StreamController<lsp.Message> _serverToClient =
|
||||
StreamController<lsp.Message>.broadcast();
|
||||
|
||||
String name;
|
||||
|
||||
/// Completer that will be signalled when the input stream is closed.
|
||||
final Completer _closed = Completer();
|
||||
|
||||
/// Errors popups sent to the user.
|
||||
final shownErrors = <lsp.ShowMessageParams>[];
|
||||
|
||||
/// Warning popups sent to the user.
|
||||
final shownWarnings = <lsp.ShowMessageParams>[];
|
||||
|
||||
MockLspServerChannel(bool _printMessages) {
|
||||
if (_printMessages) {
|
||||
_serverToClient.stream
|
||||
.listen((message) => print('<== ' + jsonEncode(message)));
|
||||
_clientToServer.stream
|
||||
.listen((message) => print('==> ' + jsonEncode(message)));
|
||||
}
|
||||
|
||||
// Keep track of any errors/warnings that are sent to the user with
|
||||
// `window/showMessage`.
|
||||
_serverToClient.stream.listen((message) {
|
||||
if (message is lsp.NotificationMessage &&
|
||||
message.method == Method.window_showMessage &&
|
||||
message.params is lsp.ShowMessageParams) {
|
||||
if (message.params?.type == MessageType.Error) {
|
||||
shownErrors.add(message.params);
|
||||
} else if (message.params?.type == MessageType.Warning) {
|
||||
shownWarnings.add(message.params);
|
||||
}
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
/// Future that will be completed when the input stream is closed.
|
||||
class MockSource implements Source {
|
||||
@override
|
||||
Future get closed {
|
||||
return _closed.future;
|
||||
}
|
||||
final String fullName;
|
||||
|
||||
Stream<lsp.Message> get serverToClient => _serverToClient.stream;
|
||||
MockSource({
|
||||
this.fullName = 'mocked.dart',
|
||||
});
|
||||
|
||||
@override
|
||||
void close() {
|
||||
if (!_closed.isCompleted) {
|
||||
_closed.complete();
|
||||
}
|
||||
if (!_serverToClient.isClosed) {
|
||||
_serverToClient.close();
|
||||
}
|
||||
if (!_clientToServer.isClosed) {
|
||||
_clientToServer.close();
|
||||
}
|
||||
}
|
||||
dynamic noSuchMethod(Invocation invocation) => super.noSuchMethod(invocation);
|
||||
|
||||
@override
|
||||
void listen(void Function(lsp.Message message) onMessage,
|
||||
{Function onError, void Function() onDone}) {
|
||||
_clientToServer.stream.listen(onMessage, onError: onError, onDone: onDone);
|
||||
}
|
||||
|
||||
@override
|
||||
void sendNotification(lsp.NotificationMessage notification) {
|
||||
// Don't deliver notifications after the connection is closed.
|
||||
if (_closed.isCompleted) {
|
||||
return;
|
||||
}
|
||||
|
||||
notification = _convertJson(notification, lsp.NotificationMessage.fromJson);
|
||||
|
||||
_serverToClient.add(notification);
|
||||
}
|
||||
|
||||
void sendNotificationToServer(lsp.NotificationMessage notification) {
|
||||
// Don't deliver notifications after the connection is closed.
|
||||
if (_closed.isCompleted) {
|
||||
return;
|
||||
}
|
||||
|
||||
notification = _convertJson(notification, lsp.NotificationMessage.fromJson);
|
||||
|
||||
// Wrap send request in future to simulate WebSocket.
|
||||
Future(() => _clientToServer.add(notification));
|
||||
}
|
||||
|
||||
@override
|
||||
void sendRequest(lsp.RequestMessage request) {
|
||||
// Don't deliver notifications after the connection is closed.
|
||||
if (_closed.isCompleted) {
|
||||
return;
|
||||
}
|
||||
|
||||
request = _convertJson(request, lsp.RequestMessage.fromJson);
|
||||
|
||||
_serverToClient.add(request);
|
||||
}
|
||||
|
||||
/// Send the given [request] to the server and return a future that will
|
||||
/// complete when a response associated with the [request] has been received.
|
||||
/// The value of the future will be the received response.
|
||||
Future<lsp.ResponseMessage> sendRequestToServer(lsp.RequestMessage request) {
|
||||
// No further requests should be sent after the connection is closed.
|
||||
if (_closed.isCompleted) {
|
||||
throw Exception('sendLspRequest after connection closed');
|
||||
}
|
||||
|
||||
request = _convertJson(request, lsp.RequestMessage.fromJson);
|
||||
|
||||
// Wrap send request in future to simulate WebSocket.
|
||||
Future(() => _clientToServer.add(request));
|
||||
return waitForResponse(request);
|
||||
}
|
||||
|
||||
@override
|
||||
void sendResponse(lsp.ResponseMessage response) {
|
||||
// Don't deliver responses after the connection is closed.
|
||||
if (_closed.isCompleted) {
|
||||
return;
|
||||
}
|
||||
|
||||
response = _convertJson(response, lsp.ResponseMessage.fromJson);
|
||||
|
||||
// Wrap send response in future to simulate WebSocket.
|
||||
Future(() => _serverToClient.add(response));
|
||||
}
|
||||
|
||||
void sendResponseToServer(lsp.ResponseMessage response) {
|
||||
// Don't deliver notifications after the connection is closed.
|
||||
if (_closed.isCompleted) {
|
||||
return;
|
||||
}
|
||||
|
||||
response = _convertJson(response, lsp.ResponseMessage.fromJson);
|
||||
|
||||
_clientToServer.add(response);
|
||||
}
|
||||
|
||||
/// Return a future that will complete when a response associated with the
|
||||
/// given [request] has been received. The value of the future will be the
|
||||
/// received response. The returned future will throw an exception if a server
|
||||
/// error is reported before the response has been received.
|
||||
///
|
||||
/// Unlike [sendLspRequest], this method assumes that the [request] has
|
||||
/// already been sent to the server.
|
||||
Future<lsp.ResponseMessage> waitForResponse(
|
||||
lsp.RequestMessage request, {
|
||||
bool throwOnError = true,
|
||||
}) async {
|
||||
final response = await _serverToClient.stream.firstWhere((message) =>
|
||||
(message is lsp.ResponseMessage && message.id == request.id) ||
|
||||
(throwOnError &&
|
||||
message is lsp.NotificationMessage &&
|
||||
message.method == Method.window_showMessage &&
|
||||
lsp.ShowMessageParams.fromJson(message.params).type ==
|
||||
MessageType.Error));
|
||||
|
||||
if (response is lsp.ResponseMessage) {
|
||||
return response;
|
||||
} else {
|
||||
throw 'An error occurred while waiting for a response to ${request.method}: '
|
||||
'${_jsonEncoder.convert(response.toJson())}';
|
||||
}
|
||||
}
|
||||
|
||||
/// Round trips the object to JSON and back to ensure it behaves the same as
|
||||
/// when running over the real STDIO server. Without this, the object passed
|
||||
/// to the handlers will have concrete types as constructed in tests rather
|
||||
/// than the maps as they would be (the server expects to do the conversion).
|
||||
T _convertJson<T>(
|
||||
lsp.ToJsonable message, T Function(Map<String, dynamic>) constructor) {
|
||||
return constructor(jsonDecode(jsonEncode(message.toJson())));
|
||||
}
|
||||
}
|
||||
|
||||
class MockSource extends StringTypedMock implements Source {
|
||||
@override
|
||||
TimestampedData<String> contents;
|
||||
|
||||
@override
|
||||
String encoding;
|
||||
|
||||
@override
|
||||
String fullName;
|
||||
|
||||
@override
|
||||
bool isInSystemLibrary;
|
||||
|
||||
@override
|
||||
Source librarySource;
|
||||
|
||||
@override
|
||||
int modificationStamp;
|
||||
|
||||
@override
|
||||
String shortName;
|
||||
|
||||
@override
|
||||
Source source;
|
||||
|
||||
@override
|
||||
Uri uri;
|
||||
|
||||
@override
|
||||
UriKind uriKind;
|
||||
|
||||
MockSource([String name = 'mocked.dart']) : super(name);
|
||||
|
||||
@override
|
||||
bool exists() => null;
|
||||
String toString() => fullName;
|
||||
}
|
||||
|
||||
class StringTypedMock {
|
||||
final String _toString;
|
||||
final String? _toString;
|
||||
|
||||
StringTypedMock(this._toString);
|
||||
|
||||
@override
|
||||
String toString() {
|
||||
if (_toString != null) {
|
||||
return _toString;
|
||||
}
|
||||
return super.toString();
|
||||
return _toString ?? super.toString();
|
||||
}
|
||||
}
|
||||
|
||||
/// A [Matcher] that check that there are no `error` in a given [Response].
|
||||
class _IsResponseFailure extends Matcher {
|
||||
final String _id;
|
||||
final RequestErrorCode _code;
|
||||
final RequestErrorCode? _code;
|
||||
|
||||
_IsResponseFailure(this._id, this._code);
|
||||
|
||||
@@ -300,8 +84,9 @@ class _IsResponseFailure extends Matcher {
|
||||
Description describe(Description description) {
|
||||
description =
|
||||
description.add('response with identifier "$_id" and an error');
|
||||
if (_code != null) {
|
||||
description = description.add(' with code ${_code.name}');
|
||||
var code = _code;
|
||||
if (code != null) {
|
||||
description = description.add(' with code ${code.name}');
|
||||
}
|
||||
return description;
|
||||
}
|
||||
@@ -316,8 +101,7 @@ class _IsResponseFailure extends Matcher {
|
||||
if (error == null) {
|
||||
mismatchDescription.add(' and has no error');
|
||||
} else {
|
||||
mismatchDescription
|
||||
.add(' and has error code ${response.error.code.name}');
|
||||
mismatchDescription.add(' and has error code ${error.code.name}');
|
||||
}
|
||||
return mismatchDescription;
|
||||
}
|
||||
@@ -325,10 +109,11 @@ class _IsResponseFailure extends Matcher {
|
||||
@override
|
||||
bool matches(item, Map matchState) {
|
||||
Response response = item;
|
||||
if (response.id != _id || response.error == null) {
|
||||
var error = response.error;
|
||||
if (response.id != _id || error == null) {
|
||||
return false;
|
||||
}
|
||||
if (_code != null && response.error.code != _code) {
|
||||
if (_code != null && error.code != _code) {
|
||||
return false;
|
||||
}
|
||||
return true;
|
||||
@@ -350,7 +135,7 @@ class _IsResponseSuccess extends Matcher {
|
||||
@override
|
||||
Description describeMismatch(
|
||||
item, Description mismatchDescription, Map matchState, bool verbose) {
|
||||
Response response = item;
|
||||
Response? response = item;
|
||||
if (response == null) {
|
||||
mismatchDescription.add('is null response');
|
||||
} else {
|
||||
@@ -366,7 +151,7 @@ class _IsResponseSuccess extends Matcher {
|
||||
|
||||
@override
|
||||
bool matches(item, Map matchState) {
|
||||
Response response = item;
|
||||
Response? response = item;
|
||||
return response != null && response.id == _id && response.error == null;
|
||||
}
|
||||
}
|
||||
|
||||
@@ -0,0 +1,196 @@
|
||||
// Copyright (c) 2014, 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.
|
||||
|
||||
// @dart = 2.9
|
||||
|
||||
import 'dart:async';
|
||||
import 'dart:convert';
|
||||
|
||||
import 'package:analysis_server/lsp_protocol/protocol_generated.dart' as lsp;
|
||||
import 'package:analysis_server/lsp_protocol/protocol_generated.dart';
|
||||
import 'package:analysis_server/lsp_protocol/protocol_special.dart' as lsp;
|
||||
import 'package:analysis_server/src/lsp/channel/lsp_channel.dart';
|
||||
|
||||
const _jsonEncoder = JsonEncoder.withIndent(' ');
|
||||
|
||||
/// A mock [LspServerCommunicationChannel] for testing [LspAnalysisServer].
|
||||
class MockLspServerChannel implements LspServerCommunicationChannel {
|
||||
final StreamController<lsp.Message> _clientToServer =
|
||||
StreamController<lsp.Message>.broadcast();
|
||||
final StreamController<lsp.Message> _serverToClient =
|
||||
StreamController<lsp.Message>.broadcast();
|
||||
|
||||
String name;
|
||||
|
||||
/// Completer that will be signalled when the input stream is closed.
|
||||
final Completer _closed = Completer();
|
||||
|
||||
/// Errors popups sent to the user.
|
||||
final shownErrors = <lsp.ShowMessageParams>[];
|
||||
|
||||
/// Warning popups sent to the user.
|
||||
final shownWarnings = <lsp.ShowMessageParams>[];
|
||||
|
||||
MockLspServerChannel(bool _printMessages) {
|
||||
if (_printMessages) {
|
||||
_serverToClient.stream
|
||||
.listen((message) => print('<== ' + jsonEncode(message)));
|
||||
_clientToServer.stream
|
||||
.listen((message) => print('==> ' + jsonEncode(message)));
|
||||
}
|
||||
|
||||
// Keep track of any errors/warnings that are sent to the user with
|
||||
// `window/showMessage`.
|
||||
_serverToClient.stream.listen((message) {
|
||||
if (message is lsp.NotificationMessage &&
|
||||
message.method == Method.window_showMessage &&
|
||||
message.params is lsp.ShowMessageParams) {
|
||||
if (message.params?.type == MessageType.Error) {
|
||||
shownErrors.add(message.params);
|
||||
} else if (message.params?.type == MessageType.Warning) {
|
||||
shownWarnings.add(message.params);
|
||||
}
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
/// Future that will be completed when the input stream is closed.
|
||||
@override
|
||||
Future get closed {
|
||||
return _closed.future;
|
||||
}
|
||||
|
||||
Stream<lsp.Message> get serverToClient => _serverToClient.stream;
|
||||
|
||||
@override
|
||||
void close() {
|
||||
if (!_closed.isCompleted) {
|
||||
_closed.complete();
|
||||
}
|
||||
if (!_serverToClient.isClosed) {
|
||||
_serverToClient.close();
|
||||
}
|
||||
if (!_clientToServer.isClosed) {
|
||||
_clientToServer.close();
|
||||
}
|
||||
}
|
||||
|
||||
@override
|
||||
void listen(void Function(lsp.Message message) onMessage,
|
||||
{Function onError, void Function() onDone}) {
|
||||
_clientToServer.stream.listen(onMessage, onError: onError, onDone: onDone);
|
||||
}
|
||||
|
||||
@override
|
||||
void sendNotification(lsp.NotificationMessage notification) {
|
||||
// Don't deliver notifications after the connection is closed.
|
||||
if (_closed.isCompleted) {
|
||||
return;
|
||||
}
|
||||
|
||||
notification = _convertJson(notification, lsp.NotificationMessage.fromJson);
|
||||
|
||||
_serverToClient.add(notification);
|
||||
}
|
||||
|
||||
void sendNotificationToServer(lsp.NotificationMessage notification) {
|
||||
// Don't deliver notifications after the connection is closed.
|
||||
if (_closed.isCompleted) {
|
||||
return;
|
||||
}
|
||||
|
||||
notification = _convertJson(notification, lsp.NotificationMessage.fromJson);
|
||||
|
||||
// Wrap send request in future to simulate WebSocket.
|
||||
Future(() => _clientToServer.add(notification));
|
||||
}
|
||||
|
||||
@override
|
||||
void sendRequest(lsp.RequestMessage request) {
|
||||
// Don't deliver notifications after the connection is closed.
|
||||
if (_closed.isCompleted) {
|
||||
return;
|
||||
}
|
||||
|
||||
request = _convertJson(request, lsp.RequestMessage.fromJson);
|
||||
|
||||
_serverToClient.add(request);
|
||||
}
|
||||
|
||||
/// Send the given [request] to the server and return a future that will
|
||||
/// complete when a response associated with the [request] has been received.
|
||||
/// The value of the future will be the received response.
|
||||
Future<lsp.ResponseMessage> sendRequestToServer(lsp.RequestMessage request) {
|
||||
// No further requests should be sent after the connection is closed.
|
||||
if (_closed.isCompleted) {
|
||||
throw Exception('sendLspRequest after connection closed');
|
||||
}
|
||||
|
||||
request = _convertJson(request, lsp.RequestMessage.fromJson);
|
||||
|
||||
// Wrap send request in future to simulate WebSocket.
|
||||
Future(() => _clientToServer.add(request));
|
||||
return waitForResponse(request);
|
||||
}
|
||||
|
||||
@override
|
||||
void sendResponse(lsp.ResponseMessage response) {
|
||||
// Don't deliver responses after the connection is closed.
|
||||
if (_closed.isCompleted) {
|
||||
return;
|
||||
}
|
||||
|
||||
response = _convertJson(response, lsp.ResponseMessage.fromJson);
|
||||
|
||||
// Wrap send response in future to simulate WebSocket.
|
||||
Future(() => _serverToClient.add(response));
|
||||
}
|
||||
|
||||
void sendResponseToServer(lsp.ResponseMessage response) {
|
||||
// Don't deliver notifications after the connection is closed.
|
||||
if (_closed.isCompleted) {
|
||||
return;
|
||||
}
|
||||
|
||||
response = _convertJson(response, lsp.ResponseMessage.fromJson);
|
||||
|
||||
_clientToServer.add(response);
|
||||
}
|
||||
|
||||
/// Return a future that will complete when a response associated with the
|
||||
/// given [request] has been received. The value of the future will be the
|
||||
/// received response. The returned future will throw an exception if a server
|
||||
/// error is reported before the response has been received.
|
||||
///
|
||||
/// Unlike [sendLspRequest], this method assumes that the [request] has
|
||||
/// already been sent to the server.
|
||||
Future<lsp.ResponseMessage> waitForResponse(
|
||||
lsp.RequestMessage request, {
|
||||
bool throwOnError = true,
|
||||
}) async {
|
||||
final response = await _serverToClient.stream.firstWhere((message) =>
|
||||
(message is lsp.ResponseMessage && message.id == request.id) ||
|
||||
(throwOnError &&
|
||||
message is lsp.NotificationMessage &&
|
||||
message.method == Method.window_showMessage &&
|
||||
lsp.ShowMessageParams.fromJson(message.params).type ==
|
||||
MessageType.Error));
|
||||
|
||||
if (response is lsp.ResponseMessage) {
|
||||
return response;
|
||||
} else {
|
||||
throw 'An error occurred while waiting for a response to ${request.method}: '
|
||||
'${_jsonEncoder.convert(response.toJson())}';
|
||||
}
|
||||
}
|
||||
|
||||
/// Round trips the object to JSON and back to ensure it behaves the same as
|
||||
/// when running over the real STDIO server. Without this, the object passed
|
||||
/// to the handlers will have concrete types as constructed in tests rather
|
||||
/// than the maps as they would be (the server expects to do the conversion).
|
||||
T _convertJson<T>(
|
||||
lsp.ToJsonable message, T Function(Map<String, dynamic>) constructor) {
|
||||
return constructor(jsonDecode(jsonEncode(message.toJson())));
|
||||
}
|
||||
}
|
||||
@@ -2,8 +2,6 @@
|
||||
// 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.
|
||||
|
||||
// @dart = 2.9
|
||||
|
||||
import 'dart:mirrors';
|
||||
|
||||
import 'package:analysis_server/src/protocol_server.dart'
|
||||
@@ -34,37 +32,45 @@ void main() {
|
||||
|
||||
@reflectiveTest
|
||||
class AnalysisErrorTest {
|
||||
MockSource source = MockSource();
|
||||
MockAnalysisError engineError;
|
||||
ResolvedUnitResult result;
|
||||
late MockSource source;
|
||||
late MockAnalysisError engineError;
|
||||
late ResolvedUnitResult result;
|
||||
|
||||
void setUp() {
|
||||
// prepare Source
|
||||
source.fullName = 'foo.dart';
|
||||
source = MockSource(fullName: 'foo.dart');
|
||||
// prepare AnalysisError
|
||||
engineError = MockAnalysisError(source,
|
||||
engine.CompileTimeErrorCode.AMBIGUOUS_EXPORT, 10, 20, 'my message');
|
||||
engineError = MockAnalysisError(
|
||||
source: source,
|
||||
errorCode: engine.CompileTimeErrorCode.AMBIGUOUS_EXPORT,
|
||||
offset: 10,
|
||||
length: 20,
|
||||
message: 'my message',
|
||||
);
|
||||
// prepare ResolvedUnitResult
|
||||
var lineInfo = engine.LineInfo([0, 5, 9, 20]);
|
||||
result = engine.ResolvedUnitResultImpl(null, 'foo.dart', null, true, null,
|
||||
lineInfo, false, null, [engineError]);
|
||||
}
|
||||
|
||||
void tearDown() {
|
||||
source = null;
|
||||
engineError = null;
|
||||
result = _ResolvedUnitResultImplMock(
|
||||
lineInfo: lineInfo,
|
||||
errors: [engineError],
|
||||
);
|
||||
}
|
||||
|
||||
void test_fromEngine_hasContextMessage() {
|
||||
engineError.contextMessages.add(engine.DiagnosticMessageImpl(
|
||||
filePath: 'bar.dart', offset: 30, length: 5, message: 'context'));
|
||||
var session = MockAnalysisSession();
|
||||
session.addFileResult(engine.FileResultImpl(
|
||||
session, 'bar.dart', null, engine.LineInfo([0, 5, 9, 20]), false));
|
||||
engineError.contextMessages.add(
|
||||
engine.DiagnosticMessageImpl(
|
||||
filePath: 'bar.dart',
|
||||
offset: 30,
|
||||
length: 5,
|
||||
message: 'context',
|
||||
),
|
||||
);
|
||||
var error = newAnalysisError_fromEngine(
|
||||
engine.ResolvedUnitResultImpl(session, 'foo.dart', null, true, null,
|
||||
engine.LineInfo([0, 5, 9, 20]), false, null, [engineError]),
|
||||
engineError);
|
||||
_ResolvedUnitResultImplMock(
|
||||
lineInfo: engine.LineInfo([0, 5, 9, 20]),
|
||||
errors: [engineError],
|
||||
),
|
||||
engineError,
|
||||
);
|
||||
expect(error.toJson(), {
|
||||
SEVERITY: 'ERROR',
|
||||
TYPE: 'COMPILE_TIME_ERROR',
|
||||
@@ -99,7 +105,14 @@ class AnalysisErrorTest {
|
||||
}
|
||||
|
||||
void test_fromEngine_hasCorrection() {
|
||||
engineError.correction = 'my correction';
|
||||
engineError = MockAnalysisError(
|
||||
source: source,
|
||||
errorCode: engine.CompileTimeErrorCode.AMBIGUOUS_EXPORT,
|
||||
offset: 10,
|
||||
length: 20,
|
||||
message: 'my message',
|
||||
correction: 'my correction',
|
||||
);
|
||||
var error = newAnalysisError_fromEngine(result, engineError);
|
||||
expect(error.toJson(), {
|
||||
SEVERITY: 'ERROR',
|
||||
@@ -123,11 +136,12 @@ class AnalysisErrorTest {
|
||||
|
||||
void test_fromEngine_hasUrl() {
|
||||
engineError = MockAnalysisError(
|
||||
source,
|
||||
MockErrorCode(url: 'http://codes.dartlang.org/TEST_ERROR'),
|
||||
10,
|
||||
20,
|
||||
'my message');
|
||||
source: source,
|
||||
errorCode: MockErrorCode(url: 'http://codes.dartlang.org/TEST_ERROR'),
|
||||
offset: 10,
|
||||
length: 20,
|
||||
message: 'my message',
|
||||
);
|
||||
var error = newAnalysisError_fromEngine(result, engineError);
|
||||
expect(error.toJson(), {
|
||||
SEVERITY: 'ERROR',
|
||||
@@ -150,11 +164,12 @@ class AnalysisErrorTest {
|
||||
|
||||
void test_fromEngine_lint() {
|
||||
engineError = MockAnalysisError(
|
||||
source,
|
||||
LintCode('my_lint', 'my message', correction: 'correction'),
|
||||
10,
|
||||
20,
|
||||
'my message');
|
||||
source: source,
|
||||
errorCode: LintCode('my_lint', 'my message', correction: 'correction'),
|
||||
offset: 10,
|
||||
length: 20,
|
||||
message: 'my message',
|
||||
);
|
||||
var error = newAnalysisError_fromEngine(result, engineError);
|
||||
expect(error.toJson(), {
|
||||
SEVERITY: 'INFO',
|
||||
@@ -176,7 +191,13 @@ class AnalysisErrorTest {
|
||||
}
|
||||
|
||||
void test_fromEngine_noCorrection() {
|
||||
engineError.correction = null;
|
||||
engineError = MockAnalysisError(
|
||||
source: source,
|
||||
errorCode: engine.CompileTimeErrorCode.AMBIGUOUS_EXPORT,
|
||||
offset: 10,
|
||||
length: 20,
|
||||
message: 'my message',
|
||||
);
|
||||
var error = newAnalysisError_fromEngine(result, engineError);
|
||||
expect(error.toJson(), {
|
||||
SEVERITY: 'ERROR',
|
||||
@@ -196,31 +217,6 @@ class AnalysisErrorTest {
|
||||
HAS_FIX: false
|
||||
});
|
||||
}
|
||||
|
||||
void test_fromEngine_noLineInfo() {
|
||||
engineError.correction = null;
|
||||
var error = newAnalysisError_fromEngine(
|
||||
engine.ResolvedUnitResultImpl(null, 'foo.dart', null, true, null, null,
|
||||
false, null, [engineError]),
|
||||
engineError);
|
||||
expect(error.toJson(), {
|
||||
SEVERITY: 'ERROR',
|
||||
TYPE: 'COMPILE_TIME_ERROR',
|
||||
LOCATION: {
|
||||
FILE: 'foo.dart',
|
||||
OFFSET: 10,
|
||||
LENGTH: 20,
|
||||
START_LINE: -1,
|
||||
START_COLUMN: -1,
|
||||
END_LINE: -1,
|
||||
END_COLUMN: -1,
|
||||
},
|
||||
MESSAGE: 'my message',
|
||||
CODE: 'ambiguous_export',
|
||||
URL: 'https://dart.dev/tools/diagnostic-messages#ambiguous_export',
|
||||
HAS_FIX: false
|
||||
});
|
||||
}
|
||||
}
|
||||
|
||||
@reflectiveTest
|
||||
@@ -271,7 +267,7 @@ class EnumTester<EngineEnum, ApiEnum> {
|
||||
/// If the corresponding value is an [ApiEnum], then we check that converting
|
||||
/// the given key results in the given value.
|
||||
void run(ApiEnum Function(EngineEnum) convert,
|
||||
{Map<EngineEnum, ApiEnum> exceptions = const {}}) {
|
||||
{Map<EngineEnum, ApiEnum?> exceptions = const {}}) {
|
||||
var engineClass = reflectClass(EngineEnum);
|
||||
engineClass.staticMembers.forEach((Symbol symbol, MethodMirror method) {
|
||||
if (symbol == #values) {
|
||||
@@ -302,49 +298,73 @@ class EnumTester<EngineEnum, ApiEnum> {
|
||||
}
|
||||
|
||||
class MockAnalysisError implements engine.AnalysisError {
|
||||
@override
|
||||
MockSource source;
|
||||
|
||||
@override
|
||||
engine.ErrorCode errorCode;
|
||||
|
||||
@override
|
||||
int offset;
|
||||
|
||||
@override
|
||||
String message;
|
||||
|
||||
@override
|
||||
String correction;
|
||||
|
||||
@override
|
||||
int length;
|
||||
final MockSource? _source;
|
||||
final engine.ErrorCode? _errorCode;
|
||||
final int? _offset;
|
||||
final int? _length;
|
||||
final String? _message;
|
||||
final String? _correction;
|
||||
final DiagnosticMessage? _problemMessage;
|
||||
final String? _correctionMessage;
|
||||
|
||||
@override
|
||||
List<DiagnosticMessage> contextMessages = <DiagnosticMessage>[];
|
||||
|
||||
MockAnalysisError(
|
||||
this.source, this.errorCode, this.offset, this.length, this.message);
|
||||
MockAnalysisError({
|
||||
MockSource? source,
|
||||
engine.ErrorCode? errorCode,
|
||||
int? offset,
|
||||
int? length,
|
||||
String? message,
|
||||
String? correction,
|
||||
DiagnosticMessage? problemMessage,
|
||||
String? correctionMessage,
|
||||
}) : _source = source,
|
||||
_errorCode = errorCode,
|
||||
_offset = offset,
|
||||
_length = length,
|
||||
_message = message,
|
||||
_correction = correction,
|
||||
_problemMessage = problemMessage,
|
||||
_correctionMessage = correctionMessage;
|
||||
|
||||
@override
|
||||
String get correctionMessage => null;
|
||||
String? get correction => _correction;
|
||||
|
||||
@override
|
||||
DiagnosticMessage get problemMessage => null;
|
||||
String? get correctionMessage => _correctionMessage;
|
||||
|
||||
@override
|
||||
Severity get severity => null;
|
||||
engine.ErrorCode get errorCode => _errorCode!;
|
||||
|
||||
@override
|
||||
int get length => _length!;
|
||||
|
||||
@override
|
||||
String get message => _message!;
|
||||
|
||||
@override
|
||||
int get offset => _offset!;
|
||||
|
||||
@override
|
||||
DiagnosticMessage get problemMessage => _problemMessage!;
|
||||
|
||||
@override
|
||||
Severity get severity => throw UnimplementedError();
|
||||
|
||||
@override
|
||||
engine.Source get source => _source!;
|
||||
}
|
||||
|
||||
class MockAnalysisSession implements AnalysisSession {
|
||||
Map<String, FileResult> fileResults = {};
|
||||
|
||||
void addFileResult(FileResult result) {
|
||||
fileResults[result.path] = result;
|
||||
fileResults[result.path!] = result;
|
||||
}
|
||||
|
||||
@override
|
||||
FileResult getFile(String path) => fileResults[path];
|
||||
FileResult getFile(String path) => fileResults[path]!;
|
||||
|
||||
@override
|
||||
dynamic noSuchMethod(Invocation invocation) => super.noSuchMethod(invocation);
|
||||
@@ -361,7 +381,7 @@ class MockErrorCode implements engine.ErrorCode {
|
||||
String name;
|
||||
|
||||
@override
|
||||
String url;
|
||||
String? url;
|
||||
|
||||
MockErrorCode(
|
||||
{this.type = engine.ErrorType.COMPILE_TIME_ERROR,
|
||||
@@ -393,3 +413,19 @@ class MockErrorCode implements engine.ErrorCode {
|
||||
throw StateError('Unexpected invocation of uniqueName');
|
||||
}
|
||||
}
|
||||
|
||||
class _ResolvedUnitResultImplMock implements engine.ResolvedUnitResultImpl {
|
||||
@override
|
||||
final engine.LineInfo lineInfo;
|
||||
|
||||
@override
|
||||
final List<engine.AnalysisError> errors;
|
||||
|
||||
_ResolvedUnitResultImplMock({
|
||||
required this.lineInfo,
|
||||
required this.errors,
|
||||
});
|
||||
|
||||
@override
|
||||
dynamic noSuchMethod(Invocation invocation) => super.noSuchMethod(invocation);
|
||||
}
|
||||
|
||||
+2
-1
@@ -59,7 +59,8 @@ abstract class AbstractTransformOverrideSetParserTest {
|
||||
|
||||
void parse(String content) {
|
||||
errorListener = GatheringErrorListener();
|
||||
var errorReporter = ErrorReporter(errorListener, MockSource('data.yaml'));
|
||||
var errorReporter =
|
||||
ErrorReporter(errorListener, MockSource(fullName: 'data.yaml'));
|
||||
var parser = TransformOverrideSetParser(errorReporter);
|
||||
result = parser.parse(content);
|
||||
}
|
||||
|
||||
+2
-1
@@ -42,7 +42,8 @@ abstract class AbstractTransformSetParserTest {
|
||||
|
||||
void parse(String content) {
|
||||
errorListener = GatheringErrorListener();
|
||||
var errorReporter = ErrorReporter(errorListener, MockSource('data.yaml'));
|
||||
var errorReporter =
|
||||
ErrorReporter(errorListener, MockSource(fullName: 'data.yaml'));
|
||||
var parser = TransformSetParser(errorReporter, 'myPackage');
|
||||
result = parser.parse(content);
|
||||
}
|
||||
|
||||
@@ -489,9 +489,6 @@ class FileResolver {
|
||||
}
|
||||
});
|
||||
|
||||
results.forEach((key, value) {
|
||||
print('$key: $value');
|
||||
});
|
||||
var resolvedUnits = results.values.map((fileResult) {
|
||||
var file = fileResult.file;
|
||||
return ResolvedUnitResultImpl(
|
||||
|
||||
Reference in New Issue
Block a user