|  | // 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. | 
|  |  | 
|  | import 'dart:async'; | 
|  | import 'dart:convert'; | 
|  |  | 
|  | import 'package:analysis_server/lsp_protocol/protocol.dart' as lsp; | 
|  | import 'package:analysis_server/src/lsp/channel/lsp_channel.dart'; | 
|  |  | 
|  | /// 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(); | 
|  |  | 
|  | /// Completer that will be signalled when the input stream is closed. | 
|  | final Completer<void> _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 == lsp.Method.window_showMessage) { | 
|  | var params = message.params; | 
|  | if (params is lsp.ShowMessageParams) { | 
|  | if (params.type == lsp.MessageType.Error) { | 
|  | shownErrors.add(params); | 
|  | } else if (params.type == lsp.MessageType.Warning) { | 
|  | shownWarnings.add(params); | 
|  | } | 
|  | } | 
|  | } | 
|  | }); | 
|  | } | 
|  |  | 
|  | /// Future that will be completed when the input stream is closed. | 
|  | @override | 
|  | Future<void> 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 | 
|  | StreamSubscription<void> listen( | 
|  | void Function(lsp.Message message) onMessage, { | 
|  | Function? onError, | 
|  | void Function()? onDone, | 
|  | }) { | 
|  | return _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); | 
|  |  | 
|  | _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('${request.method} request sent after connection closed'); | 
|  | } | 
|  |  | 
|  | request = _convertJson(request, lsp.RequestMessage.fromJson); | 
|  |  | 
|  | _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); | 
|  |  | 
|  | _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 [sendRequestToServer], this method assumes that the [request] has | 
|  | /// already been sent to the server. | 
|  | Future<lsp.ResponseMessage> waitForResponse( | 
|  | lsp.RequestMessage request, | 
|  | ) async { | 
|  | var response = await _serverToClient.stream.firstWhere( | 
|  | (message) => message is lsp.ResponseMessage && message.id == request.id, | 
|  | ); | 
|  |  | 
|  | return response as lsp.ResponseMessage; | 
|  | } | 
|  |  | 
|  | /// 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())) as Map<String, Object?>, | 
|  | ); | 
|  | } | 
|  | } |