| // 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?>, | 
 |     ); | 
 |   } | 
 | } |