// Copyright (c) 2017, 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.
//
// This file has been automatically generated. Please do not edit it manually.
// To regenerate the file, use the script
// "pkg/analysis_server/tool/spec/generate_files".

// ignore_for_file: unnecessary_ignore, duplicate_ignore
// ignore_for_file: unnecessary_type_name_in_constructor

/// Convenience methods for running integration tests.
library;

import 'dart:async';

import 'package:analysis_server/protocol/protocol_generated.dart';
import 'package:analysis_server/src/protocol/protocol_internal.dart';
import 'package:analyzer_plugin/src/utilities/client_uri_converter.dart';
import 'package:analyzer_plugin/protocol/protocol_common.dart';
import 'package:path/path.dart' as path;
import 'package:test/test.dart';

import 'integration_tests.dart';
import 'protocol_matchers.dart';

/// Base implementation for running integration tests.
abstract class IntegrationTest {
  Server get server;

  /// The converter used to convert between URI/Paths in server communication.
  final ClientUriConverter uriConverter = ClientUriConverter.noop(path.context);

  /// Return the version number of the analysis server.
  ///
  /// Returns
  ///
  /// * `version: String`
  ///
  ///   The version number of the analysis server.
  Future<ServerGetVersionResult> sendServerGetVersion() async {
    var result = await server.send('server.getVersion', null);
    var decoder = ResponseDecoder(null);
    return ServerGetVersionResult.fromJson(
      decoder,
      'result',
      result,
      clientUriConverter: uriConverter,
    );
  }

  /// Cleanly shutdown the analysis server. Requests that are received after
  /// this request will not be processed. Requests that were received before
  /// this request, but for which a response has not yet been sent, will not be
  /// responded to. No further responses or notifications will be sent after
  /// the response to this request has been sent.
  Future<void> sendServerShutdown() async {
    var result = await server.send('server.shutdown', null);
    outOfTestExpect(result, isNull);
  }

  /// Subscribe for services. All previous subscriptions are replaced by the
  /// given set of services.
  ///
  /// It is an error if any of the elements in the list are not valid services.
  /// If there is an error, then the current subscriptions will remain
  /// unchanged.
  ///
  /// Parameters
  ///
  /// * `subscriptions: List<ServerService>`
  ///
  ///   A list of the services being subscribed to.
  Future<void> sendServerSetSubscriptions(
    List<ServerService> subscriptions,
  ) async {
    var params = ServerSetSubscriptionsParams(
      subscriptions,
    ).toJson(clientUriConverter: uriConverter);
    var result = await server.send('server.setSubscriptions', params);
    outOfTestExpect(result, isNull);
  }

  /// Requests cancellation of a request sent by the client by id. This is
  /// provided on a best-effort basis and there is no guarantee the server will
  /// be able to cancel any specific request. The server will still always
  /// produce a response to the request even in the case of cancellation, but
  /// clients should discard any results of any cancelled request because they
  /// may be incomplete or inaccurate. This request always completes without
  /// error regardless of whether the request is successfully cancelled.
  ///
  /// Parameters
  ///
  /// * `id: String`
  ///
  ///   The id of the request that should be cancelled.
  Future<void> sendServerCancelRequest(String id) async {
    var params = ServerCancelRequestParams(
      id,
    ).toJson(clientUriConverter: uriConverter);
    var result = await server.send('server.cancelRequest', params);
    outOfTestExpect(result, isNull);
  }

  /// Record the capabilities supported by the client. The default values,
  /// documented below, will be assumed until this request is received.
  ///
  /// Parameters
  ///
  /// * `requests: List<String>`
  ///
  ///   The names of the requests that the server can safely send to the
  ///   client. Only requests whose name is in the list will be sent.
  ///
  ///   A request should only be included in the list if the client will
  ///   unconditionally honor the request.
  ///
  ///   The default, used before this request is received, is an empty list.
  ///
  ///   The following is a list of the names of the requests that can be
  ///   specified:
  ///
  ///   - `openUrlRequest`
  ///   - `showMessageRequest`
  ///
  /// * `supportsUris: bool (optional)`
  ///
  ///   True if the client supports the server sending URIs in place of file
  ///   paths.
  ///
  ///   In this mode, the server will use URIs in all protocol fields with the
  ///   type `FilePath`. Returned URIs may be `file://` URIs or custom schemes.
  ///   The client can fetch the file contents for URIs with custom schemes
  ///   (and receive modification events) through the LSP protocol (see the
  ///   "lsp" domain).
  ///
  ///   LSP notifications are automatically enabled when the client sets this
  ///   capability.
  ///
  /// * `lspCapabilities: object (optional)`
  ///
  ///   LSP capabilities of the client as defined by the Language Server
  ///   Protocol specification.
  ///
  ///   If custom LSP capabilities are to be used, the setClientCapabilities
  ///   request should be called before any LSP requests are made to the
  ///   server.
  ///
  ///   If LSP capabilities are not provided or no setClientCapabilities
  ///   request is made, a very basic set of capabilities will be assumed.
  Future<void> sendServerSetClientCapabilities(
    List<String> requests, {
    bool? supportsUris,
    Object? lspCapabilities,
  }) async {
    var params = ServerSetClientCapabilitiesParams(
      requests,
      supportsUris: supportsUris,
      lspCapabilities: lspCapabilities,
    ).toJson(clientUriConverter: uriConverter);
    var result = await server.send('server.setClientCapabilities', params);
    outOfTestExpect(result, isNull);
  }

  /// Note: This is a request from the server to the client.
  ///
  /// Request that a URL be opened.
  ///
  /// The client is expected to open the URL, either within the client's UI or
  /// in the default browser.
  ///
  /// The request will only be sent from the server to the client if the client
  /// has indicated that it supports this request by using the
  /// `setClientCapabilities` request.
  ///
  /// Parameters
  ///
  /// * `url: String`
  ///
  ///   The URL to be opened.
  Future<void> sendServerOpenUrlRequest(String url) async {
    var params = ServerOpenUrlRequestParams(
      url,
    ).toJson(clientUriConverter: uriConverter);
    var result = await server.send('server.openUrlRequest', params);
    outOfTestExpect(result, isNull);
  }

  /// Note: This is a request from the server to the client.
  ///
  /// Request that a message be displayed to the user.
  ///
  /// The client is expected to display the message to the user with one or
  /// more buttons with the specified labels, and to return a response
  /// consisting of the label of the button that was clicked.
  ///
  /// The request will only be sent from the server to the client if the client
  /// has indicated that it supports this request by using the
  /// `setClientCapabilities` request.
  ///
  /// This request is modeled after the same request from the LSP
  /// specification.
  ///
  /// Parameters
  ///
  /// * `type: MessageType`
  ///
  ///   The type of the message.
  ///
  /// * `message: String`
  ///
  ///   The message to be displayed.
  ///
  /// * `actions: List<MessageAction>`
  ///
  ///   The labels of the buttons by which the user can dismiss the message.
  ///
  /// Returns
  ///
  /// * `action: String (optional)`
  ///
  ///   The label of the action that was selected by the user. May be omitted
  ///   or `null` if the user dismissed the message without clicking an action
  ///   button.
  Future<ServerShowMessageRequestResult> sendServerShowMessageRequest(
    MessageType type,
    String message,
    List<MessageAction> actions,
  ) async {
    var params = ServerShowMessageRequestParams(
      type,
      message,
      actions,
    ).toJson(clientUriConverter: uriConverter);
    var result = await server.send('server.showMessageRequest', params);
    var decoder = ResponseDecoder(null);
    return ServerShowMessageRequestResult.fromJson(
      decoder,
      'result',
      result,
      clientUriConverter: uriConverter,
    );
  }

  /// Reports that the server is running. This notification is issued once
  /// after the server has started running but before any requests are
  /// processed to let the client know that it started correctly.
  ///
  /// It is not possible to subscribe to or unsubscribe from this notification.
  ///
  /// Parameters
  ///
  /// * `version: String`
  ///
  ///   The version number of the analysis server.
  ///
  /// * `pid: int`
  ///
  ///   The process id of the analysis server process.
  late final Stream<ServerConnectedParams> onServerConnected =
      _onServerConnected.stream.asBroadcastStream();

  /// Stream controller for [onServerConnected].
  final _onServerConnected = StreamController<ServerConnectedParams>(
    sync: true,
  );

  /// Reports that an unexpected error has occurred while executing the server.
  /// This notification is not used for problems with specific requests (which
  /// are returned as part of the response) but is used for exceptions that
  /// occur while performing other tasks, such as analysis or preparing
  /// notifications.
  ///
  /// It is not possible to subscribe to or unsubscribe from this notification.
  ///
  /// Parameters
  ///
  /// * `isFatal: bool`
  ///
  ///   True if the error is a fatal error, meaning that the server will
  ///   shutdown automatically after sending this notification.
  ///
  /// * `message: String`
  ///
  ///   The error message indicating what kind of error was encountered.
  ///
  /// * `stackTrace: String`
  ///
  ///   The stack trace associated with the generation of the error, used for
  ///   debugging the server.
  late final Stream<ServerErrorParams> onServerError = _onServerError.stream
      .asBroadcastStream();

  /// Stream controller for [onServerError].
  final _onServerError = StreamController<ServerErrorParams>(sync: true);

  /// Reports that an unexpected error has occurred while setting up an
  /// analyzer plugin, or during a plugin's execution.
  ///
  /// It is not possible to subscribe to or unsubscribe from this notification.
  ///
  /// Parameters
  ///
  /// * `message: String`
  ///
  ///   The error message indicating what kind of error was encountered.
  late final Stream<ServerPluginErrorParams> onServerPluginError =
      _onServerPluginError.stream.asBroadcastStream();

  /// Stream controller for [onServerPluginError].
  final _onServerPluginError = StreamController<ServerPluginErrorParams>(
    sync: true,
  );

  /// The stream of entries describing events happened in the server.
  ///
  /// Parameters
  ///
  /// * `entry: ServerLogEntry`
  late final Stream<ServerLogParams> onServerLog = _onServerLog.stream
      .asBroadcastStream();

  /// Stream controller for [onServerLog].
  final _onServerLog = StreamController<ServerLogParams>(sync: true);

  /// Reports the current status of the server. Parameters are omitted if there
  /// has been no change in the status represented by that parameter.
  ///
  /// This notification is not subscribed to by default. Clients can subscribe
  /// by including the value `"STATUS"` in the list of services passed in a
  /// server.setSubscriptions request.
  ///
  /// Parameters
  ///
  /// * `analysis: AnalysisStatus (optional)`
  ///
  ///   The current status of analysis, including whether analysis is being
  ///   performed and if so what is being analyzed.
  ///
  /// * `pub: PubStatus (optional)`
  ///
  ///   The current status of pub execution, indicating whether we are
  ///   currently running pub.
  ///
  ///   Note: this status type is deprecated, and is no longer sent by the
  ///   server.
  late final Stream<ServerStatusParams> onServerStatus = _onServerStatus.stream
      .asBroadcastStream();

  /// Stream controller for [onServerStatus].
  final _onServerStatus = StreamController<ServerStatusParams>(sync: true);

  /// Return the errors associated with the given file. If the errors for the
  /// given file have not yet been computed, or the most recently computed
  /// errors for the given file are out of date, then the response for this
  /// request will be delayed until they have been computed. If some or all of
  /// the errors for the file cannot be computed, then the subset of the errors
  /// that can be computed will be returned and the response will contain an
  /// error to indicate why the errors could not be computed. If the content of
  /// the file changes after this request was received but before a response
  /// could be sent, then an error of type `CONTENT_MODIFIED` will be
  /// generated.
  ///
  /// This request is intended to be used by clients that cannot asynchronously
  /// apply updated error information. Clients that can apply error information
  /// as it becomes available should use the information provided by the
  /// 'analysis.errors' notification.
  ///
  /// If a request is made for a file which does not exist, or which is not
  /// currently subject to analysis (e.g. because it is not associated with any
  /// analysis root specified to analysis.setAnalysisRoots), an error of type
  /// `GET_ERRORS_INVALID_FILE` will be generated.
  ///
  /// Parameters
  ///
  /// * `file: FilePath`
  ///
  ///   The file for which errors are being requested.
  ///
  /// Returns
  ///
  /// * `errors: List<AnalysisError>`
  ///
  ///   The errors associated with the file.
  Future<AnalysisGetErrorsResult> sendAnalysisGetErrors(String file) async {
    var params = AnalysisGetErrorsParams(
      file,
    ).toJson(clientUriConverter: uriConverter);
    var result = await server.send('analysis.getErrors', params);
    var decoder = ResponseDecoder(null);
    return AnalysisGetErrorsResult.fromJson(
      decoder,
      'result',
      result,
      clientUriConverter: uriConverter,
    );
  }

  /// Return the hover information associated with the given location. If some
  /// or all of the hover information is not available at the time this request
  /// is processed the information will be omitted from the response.
  ///
  /// Parameters
  ///
  /// * `file: FilePath`
  ///
  ///   The file in which hover information is being requested.
  ///
  /// * `offset: int`
  ///
  ///   The offset for which hover information is being requested.
  ///
  /// Returns
  ///
  /// * `hovers: List<HoverInformation>`
  ///
  ///   The hover information associated with the location. The list will be
  ///   empty if no information could be determined for the location. The list
  ///   can contain multiple items if the file is being analyzed in multiple
  ///   contexts in conflicting ways (such as a part that is included in
  ///   multiple libraries).
  Future<AnalysisGetHoverResult> sendAnalysisGetHover(
    String file,
    int offset,
  ) async {
    var params = AnalysisGetHoverParams(
      file,
      offset,
    ).toJson(clientUriConverter: uriConverter);
    var result = await server.send('analysis.getHover', params);
    var decoder = ResponseDecoder(null);
    return AnalysisGetHoverResult.fromJson(
      decoder,
      'result',
      result,
      clientUriConverter: uriConverter,
    );
  }

  /// Return a description of all of the elements referenced in a given region
  /// of a given file that come from imported libraries.
  ///
  /// If a request is made for a file that does not exist, or that is not
  /// currently subject to analysis (e.g. because it is not associated with any
  /// analysis root specified via analysis.setAnalysisRoots), an error of type
  /// `GET_IMPORTED_ELEMENTS_INVALID_FILE` will be generated.
  ///
  /// Parameters
  ///
  /// * `file: FilePath`
  ///
  ///   The file in which import information is being requested.
  ///
  /// * `offset: int`
  ///
  ///   The offset of the region for which import information is being
  ///   requested.
  ///
  /// * `length: int`
  ///
  ///   The length of the region for which import information is being
  ///   requested.
  ///
  /// Returns
  ///
  /// * `elements: List<ImportedElements>`
  ///
  ///   The information about the elements that are referenced in the specified
  ///   region of the specified file that come from imported libraries.
  Future<AnalysisGetImportedElementsResult> sendAnalysisGetImportedElements(
    String file,
    int offset,
    int length,
  ) async {
    var params = AnalysisGetImportedElementsParams(
      file,
      offset,
      length,
    ).toJson(clientUriConverter: uriConverter);
    var result = await server.send('analysis.getImportedElements', params);
    var decoder = ResponseDecoder(null);
    return AnalysisGetImportedElementsResult.fromJson(
      decoder,
      'result',
      result,
      clientUriConverter: uriConverter,
    );
  }

  /// Return library dependency information for use in client-side indexing and
  /// package URI resolution.
  ///
  /// Clients that are only using the libraries field should consider using the
  /// analyzedFiles notification instead.
  ///
  /// Returns
  ///
  /// * `libraries: List<FilePath>`
  ///
  ///   A list of the paths of library elements referenced by files in existing
  ///   analysis roots.
  ///
  /// * `packageMap: Map<String, Map<String, List<FilePath>>>`
  ///
  ///   A mapping from context source roots to package maps which map package
  ///   names to source directories for use in client-side package URI
  ///   resolution.
  Future<AnalysisGetLibraryDependenciesResult>
  sendAnalysisGetLibraryDependencies() async {
    var result = await server.send('analysis.getLibraryDependencies', null);
    var decoder = ResponseDecoder(null);
    return AnalysisGetLibraryDependenciesResult.fromJson(
      decoder,
      'result',
      result,
      clientUriConverter: uriConverter,
    );
  }

  /// Return the navigation information associated with the given region of the
  /// given file. If the navigation information for the given file has not yet
  /// been computed, or the most recently computed navigation information for
  /// the given file is out of date, then the response for this request will be
  /// delayed until it has been computed. If the content of the file changes
  /// after this request was received but before a response could be sent, then
  /// an error of type `CONTENT_MODIFIED` will be generated.
  ///
  /// If a navigation region overlaps (but extends either before or after) the
  /// given region of the file it will be included in the result. This means
  /// that it is theoretically possible to get the same navigation region in
  /// response to multiple requests. Clients can avoid this by always choosing
  /// a region that starts at the beginning of a line and ends at the end of a
  /// (possibly different) line in the file.
  ///
  /// If a request is made for a file which does not exist, or which is not
  /// currently subject to analysis (e.g. because it is not associated with any
  /// analysis root specified to analysis.setAnalysisRoots), an error of type
  /// `GET_NAVIGATION_INVALID_FILE` will be generated.
  ///
  /// Parameters
  ///
  /// * `file: FilePath`
  ///
  ///   The file in which navigation information is being requested.
  ///
  /// * `offset: int`
  ///
  ///   The offset of the region for which navigation information is being
  ///   requested.
  ///
  /// * `length: int`
  ///
  ///   The length of the region for which navigation information is being
  ///   requested.
  ///
  /// Returns
  ///
  /// * `files: List<FilePath>`
  ///
  ///   A list of the paths of files that are referenced by the navigation
  ///   targets.
  ///
  /// * `targets: List<NavigationTarget>`
  ///
  ///   A list of the navigation targets that are referenced by the navigation
  ///   regions.
  ///
  /// * `regions: List<NavigationRegion>`
  ///
  ///   A list of the navigation regions within the requested region of the
  ///   file.
  Future<AnalysisGetNavigationResult> sendAnalysisGetNavigation(
    String file,
    int offset,
    int length,
  ) async {
    var params = AnalysisGetNavigationParams(
      file,
      offset,
      length,
    ).toJson(clientUriConverter: uriConverter);
    var result = await server.send('analysis.getNavigation', params);
    var decoder = ResponseDecoder(null);
    return AnalysisGetNavigationResult.fromJson(
      decoder,
      'result',
      result,
      clientUriConverter: uriConverter,
    );
  }

  /// Return the transitive closure of reachable sources for a given file.
  ///
  /// If a request is made for a file which does not exist, or which is not
  /// currently subject to analysis (e.g. because it is not associated with any
  /// analysis root specified to analysis.setAnalysisRoots), an error of type
  /// `GET_REACHABLE_SOURCES_INVALID_FILE` will be generated.
  ///
  /// Parameters
  ///
  /// * `file: FilePath`
  ///
  ///   The file for which reachable source information is being requested.
  ///
  /// Returns
  ///
  /// * `sources: Map<String, List<String>>`
  ///
  ///   A mapping from source URIs to directly reachable source URIs. For
  ///   example, a file "foo.dart" that imports "bar.dart" would have the
  ///   corresponding mapping { "file:///foo.dart" : ["file:///bar.dart"] }. If
  ///   "bar.dart" has further imports (or exports) there will be a mapping
  ///   from the URI "file:///bar.dart" to them. To check if a specific URI is
  ///   reachable from a given file, clients can check for its presence in the
  ///   resulting key set.
  // TODO(srawlins): Provide a deprecation message, or remove.
  // ignore: provide_deprecation_message
  @deprecated
  Future<AnalysisGetReachableSourcesResult> sendAnalysisGetReachableSources(
    String file,
  ) async {
    var params = AnalysisGetReachableSourcesParams(
      file,
    ).toJson(clientUriConverter: uriConverter);
    var result = await server.send('analysis.getReachableSources', params);
    var decoder = ResponseDecoder(null);
    return AnalysisGetReachableSourcesResult.fromJson(
      decoder,
      'result',
      result,
      clientUriConverter: uriConverter,
    );
  }

  /// Return the signature information associated with the given location in
  /// the given file. If the signature information for the given file has not
  /// yet been computed, or the most recently computed signature information
  /// for the given file is out of date, then the response for this request
  /// will be delayed until it has been computed. If a request is made for a
  /// file which does not exist, or which is not currently subject to analysis
  /// (e.g. because it is not associated with any analysis root specified to
  /// analysis.setAnalysisRoots), an error of type `GET_SIGNATURE_INVALID_FILE`
  /// will be generated. If the location given is not inside the argument list
  /// for a function (including method and constructor) invocation, then an
  /// error of type `GET_SIGNATURE_INVALID_OFFSET` will be generated. If the
  /// location is inside an argument list but the function is not defined or
  /// cannot be determined (such as a method invocation where the target has
  /// type 'dynamic') then an error of type `GET_SIGNATURE_UNKNOWN_FUNCTION`
  /// will be generated.
  ///
  /// Parameters
  ///
  /// * `file: FilePath`
  ///
  ///   The file in which signature information is being requested.
  ///
  /// * `offset: int`
  ///
  ///   The location for which signature information is being requested.
  ///
  /// Returns
  ///
  /// * `name: String`
  ///
  ///   The name of the function being invoked at the given offset.
  ///
  /// * `parameters: List<ParameterInfo>`
  ///
  ///   A list of information about each of the parameters of the function
  ///   being invoked.
  ///
  /// * `dartdoc: String (optional)`
  ///
  ///   The dartdoc associated with the function being invoked. Other than the
  ///   removal of the comment delimiters, including leading asterisks in the
  ///   case of a block comment, the dartdoc is unprocessed markdown. This data
  ///   is omitted if there is no referenced element, or if the element has no
  ///   dartdoc.
  Future<AnalysisGetSignatureResult> sendAnalysisGetSignature(
    String file,
    int offset,
  ) async {
    var params = AnalysisGetSignatureParams(
      file,
      offset,
    ).toJson(clientUriConverter: uriConverter);
    var result = await server.send('analysis.getSignature', params);
    var decoder = ResponseDecoder(null);
    return AnalysisGetSignatureResult.fromJson(
      decoder,
      'result',
      result,
      clientUriConverter: uriConverter,
    );
  }

  /// Force re-reading of all potentially changed files, re-resolving of all
  /// referenced URIs, and corresponding re-analysis of everything affected in
  /// the current analysis roots.
  Future<void> sendAnalysisReanalyze() async {
    var result = await server.send('analysis.reanalyze', null);
    outOfTestExpect(result, isNull);
  }

  /// Sets the root paths used to determine which files to analyze. The set of
  /// files to be analyzed are all of the files in one of the root paths that
  /// are not either explicitly or implicitly excluded. A file is explicitly
  /// excluded if it is in one of the excluded paths. A file is implicitly
  /// excluded if it is in a subdirectory of one of the root paths where the
  /// name of the subdirectory starts with a period (that is, a hidden
  /// directory).
  ///
  /// Note that this request determines the set of requested analysis roots.
  /// The actual set of analysis roots at any given time is the intersection of
  /// this set with the set of files and directories actually present on the
  /// filesystem. When the filesystem changes, the actual set of analysis roots
  /// is automatically updated, but the set of requested analysis roots is
  /// unchanged. This means that if the client sets an analysis root before the
  /// root becomes visible to the server in the filesystem, there is no error;
  /// once the server sees the root in the filesystem it will start analyzing
  /// it. Similarly, the server will stop analyzing files that are removed from
  /// the file system but they will remain in the set of requested roots.
  ///
  /// If an included path represents a file, then the server will look in the
  /// directory containing the file for a pubspec.yaml file. If none is found,
  /// then the parents of the directory will be searched until such a file is
  /// found or the root of the file system is reached. If such a file is found,
  /// it will be used to resolve package: URI's within the file.
  ///
  /// Parameters
  ///
  /// * `included: List<FilePath>`
  ///
  ///   A list of the files and directories that should be analyzed.
  ///
  /// * `excluded: List<FilePath>`
  ///
  ///   A list of the files and directories within the included directories
  ///   that should not be analyzed.
  ///
  /// * `packageRoots: Map<FilePath, FilePath> (optional)`
  ///
  ///   A mapping from source directories to package roots that should override
  ///   the normal package: URI resolution mechanism.
  ///
  ///   If a package root is a file, then the analyzer will behave as though
  ///   that file is a ".dart_tool/package_config.json" file in the source
  ///   directory. The effect is the same as specifying the file as a
  ///   "--packages" parameter to the Dart VM when executing any Dart file
  ///   inside the source directory.
  ///
  ///   Files in any directories that are not overridden by this mapping have
  ///   their package: URI's resolved using the normal pubspec.yaml mechanism.
  ///   If this field is absent, or the empty map is specified, that indicates
  ///   that the normal pubspec.yaml mechanism should always be used.
  Future<void> sendAnalysisSetAnalysisRoots(
    List<String> included,
    List<String> excluded, {
    Map<String, String>? packageRoots,
  }) async {
    var params = AnalysisSetAnalysisRootsParams(
      included,
      excluded,
      packageRoots: packageRoots,
    ).toJson(clientUriConverter: uriConverter);
    var result = await server.send('analysis.setAnalysisRoots', params);
    outOfTestExpect(result, isNull);
  }

  /// Subscribe for general services (that is, services that are not specific
  /// to individual files). All previous subscriptions are replaced by the
  /// given set of services.
  ///
  /// It is an error if any of the elements in the list are not valid services.
  /// If there is an error, then the current subscriptions will remain
  /// unchanged.
  ///
  /// Parameters
  ///
  /// * `subscriptions: List<GeneralAnalysisService>`
  ///
  ///   A list of the services being subscribed to.
  Future<void> sendAnalysisSetGeneralSubscriptions(
    List<GeneralAnalysisService> subscriptions,
  ) async {
    var params = AnalysisSetGeneralSubscriptionsParams(
      subscriptions,
    ).toJson(clientUriConverter: uriConverter);
    var result = await server.send('analysis.setGeneralSubscriptions', params);
    outOfTestExpect(result, isNull);
  }

  /// Set the priority files to the files in the given list. A priority file is
  /// a file that is given priority when scheduling which analysis work to do
  /// first. The list typically contains those files that are visible to the
  /// user and those for which analysis results will have the biggest impact on
  /// the user experience. The order of the files within the list is
  /// significant: the first file will be given higher priority than the
  /// second, the second higher priority than the third, and so on.
  ///
  /// Note that this request determines the set of requested priority files.
  /// The actual set of priority files is the intersection of the requested set
  /// of priority files with the set of files currently subject to analysis.
  /// (See analysis.setSubscriptions for a description of files that are
  /// subject to analysis.)
  ///
  /// If a requested priority file is a directory it is ignored, but remains in
  /// the set of requested priority files so that if it later becomes a file it
  /// can be included in the set of actual priority files.
  ///
  /// Parameters
  ///
  /// * `files: List<FilePath>`
  ///
  ///   The files that are to be a priority for analysis.
  Future<void> sendAnalysisSetPriorityFiles(List<String> files) async {
    var params = AnalysisSetPriorityFilesParams(
      files,
    ).toJson(clientUriConverter: uriConverter);
    var result = await server.send('analysis.setPriorityFiles', params);
    outOfTestExpect(result, isNull);
  }

  /// Subscribe for services that are specific to individual files. All
  /// previous subscriptions are replaced by the current set of subscriptions.
  /// If a given service is not included as a key in the map then no files will
  /// be subscribed to the service, exactly as if the service had been included
  /// in the map with an explicit empty list of files.
  ///
  /// Note that this request determines the set of requested subscriptions. The
  /// actual set of subscriptions at any given time is the intersection of this
  /// set with the set of files currently subject to analysis. The files
  /// currently subject to analysis are the set of files contained within an
  /// actual analysis root but not excluded, plus all of the files transitively
  /// reachable from those files via import, export and part directives. (See
  /// analysis.setAnalysisRoots for an explanation of how the actual analysis
  /// roots are determined.) When the actual analysis roots change, the actual
  /// set of subscriptions is automatically updated, but the set of requested
  /// subscriptions is unchanged.
  ///
  /// If a requested subscription is a directory it is ignored, but remains in
  /// the set of requested subscriptions so that if it later becomes a file it
  /// can be included in the set of actual subscriptions.
  ///
  /// It is an error if any of the keys in the map are not valid services. If
  /// there is an error, then the existing subscriptions will remain unchanged.
  ///
  /// Parameters
  ///
  /// * `subscriptions: Map<AnalysisService, List<FilePath>>`
  ///
  ///   A table mapping services to a list of the files being subscribed to the
  ///   service.
  Future<void> sendAnalysisSetSubscriptions(
    Map<AnalysisService, List<String>> subscriptions,
  ) async {
    var params = AnalysisSetSubscriptionsParams(
      subscriptions,
    ).toJson(clientUriConverter: uriConverter);
    var result = await server.send('analysis.setSubscriptions', params);
    outOfTestExpect(result, isNull);
  }

  /// Update the content of one or more files. Files that were previously
  /// updated but not included in this update remain unchanged. This
  /// effectively represents an overlay of the filesystem. The files whose
  /// content is overridden are therefore seen by the server as being files
  /// with the given content, even if the files do not exist on the filesystem
  /// or if the file path represents the path to a directory on the filesystem.
  ///
  /// Parameters
  ///
  /// * `files: Map<FilePath, AddContentOverlay | ChangeContentOverlay |
  /// RemoveContentOverlay>`
  ///
  ///   A table mapping the files whose content has changed to a description of
  ///   the content change.
  ///
  /// Returns
  Future<AnalysisUpdateContentResult> sendAnalysisUpdateContent(
    Map<String, Object> files,
  ) async {
    var params = AnalysisUpdateContentParams(
      files,
    ).toJson(clientUriConverter: uriConverter);
    var result = await server.send('analysis.updateContent', params);
    var decoder = ResponseDecoder(null);
    return AnalysisUpdateContentResult.fromJson(
      decoder,
      'result',
      result,
      clientUriConverter: uriConverter,
    );
  }

  /// Deprecated: all of the options can be set by users in an analysis options
  /// file.
  ///
  /// Update the options controlling analysis based on the given set of
  /// options. Any options that are not included in the analysis options will
  /// not be changed. If there are options in the analysis options that are not
  /// valid, they will be silently ignored.
  ///
  /// Parameters
  ///
  /// * `options: AnalysisOptions`
  ///
  ///   The options that are to be used to control analysis.
  // TODO(srawlins): Provide a deprecation message, or remove.
  // ignore: provide_deprecation_message
  @deprecated
  Future<void> sendAnalysisUpdateOptions(AnalysisOptions options) async {
    var params = AnalysisUpdateOptionsParams(
      options,
    ).toJson(clientUriConverter: uriConverter);
    var result = await server.send('analysis.updateOptions', params);
    outOfTestExpect(result, isNull);
  }

  /// Reports the paths of the files that are being analyzed.
  ///
  /// This notification is not subscribed to by default. Clients can subscribe
  /// by including the value `"ANALYZED_FILES"` in the list of services passed
  /// in an analysis.setGeneralSubscriptions request.
  ///
  /// Parameters
  ///
  /// * `directories: List<FilePath>`
  ///
  ///   A list of the paths of the files that are being analyzed.
  late final Stream<AnalysisAnalyzedFilesParams> onAnalysisAnalyzedFiles =
      _onAnalysisAnalyzedFiles.stream.asBroadcastStream();

  /// Stream controller for [onAnalysisAnalyzedFiles].
  final _onAnalysisAnalyzedFiles =
      StreamController<AnalysisAnalyzedFilesParams>(sync: true);

  /// Reports closing labels relevant to a given file.
  ///
  /// This notification is not subscribed to by default. Clients can subscribe
  /// by including the value `"CLOSING_LABELS"` in the list of services passed
  /// in an analysis.setSubscriptions request.
  ///
  /// Parameters
  ///
  /// * `file: FilePath`
  ///
  ///   The file the closing labels relate to.
  ///
  /// * `labels: List<ClosingLabel>`
  ///
  ///   Closing labels relevant to the file. Each item represents a useful
  ///   label associated with some range which may be useful to display to the
  ///   user within the editor at the end of the range to indicate what
  ///   construct is closed at that location. Closing labels include
  ///   constructor/method calls and List arguments that span multiple lines.
  ///   Note that the ranges that are returned can overlap each other because
  ///   they may be associated with constructs that can be nested.
  late final Stream<AnalysisClosingLabelsParams> onAnalysisClosingLabels =
      _onAnalysisClosingLabels.stream.asBroadcastStream();

  /// Stream controller for [onAnalysisClosingLabels].
  final _onAnalysisClosingLabels =
      StreamController<AnalysisClosingLabelsParams>(sync: true);

  /// Reports the errors associated with a given file. The set of errors
  /// included in the notification is always a complete list that supersedes
  /// any previously reported errors.
  ///
  /// Parameters
  ///
  /// * `file: FilePath`
  ///
  ///   The file containing the errors.
  ///
  /// * `errors: List<AnalysisError>`
  ///
  ///   The errors contained in the file.
  late final Stream<AnalysisErrorsParams> onAnalysisErrors = _onAnalysisErrors
      .stream
      .asBroadcastStream();

  /// Stream controller for [onAnalysisErrors].
  final _onAnalysisErrors = StreamController<AnalysisErrorsParams>(sync: true);

  /// Reports that any analysis results that were previously associated with
  /// the given files should be considered to be invalid because those files
  /// are no longer being analyzed, either because the analysis root that
  /// contained it is no longer being analyzed or because the file no longer
  /// exists.
  ///
  /// If a file is included in this notification and at some later time a
  /// notification with results for the file is received, clients should assume
  /// that the file is once again being analyzed and the information should be
  /// processed.
  ///
  /// It is not possible to subscribe to or unsubscribe from this notification.
  ///
  /// Parameters
  ///
  /// * `files: List<FilePath>`
  ///
  ///   The files that are no longer being analyzed.
  late final Stream<AnalysisFlushResultsParams> onAnalysisFlushResults =
      _onAnalysisFlushResults.stream.asBroadcastStream();

  /// Stream controller for [onAnalysisFlushResults].
  final _onAnalysisFlushResults = StreamController<AnalysisFlushResultsParams>(
    sync: true,
  );

  /// Reports the folding regions associated with a given file. Folding regions
  /// can be nested, but will not be overlapping. Nesting occurs when a
  /// foldable element, such as a method, is nested inside another foldable
  /// element such as a class.
  ///
  /// This notification is not subscribed to by default. Clients can subscribe
  /// by including the value `"FOLDING"` in the list of services passed in an
  /// analysis.setSubscriptions request.
  ///
  /// Parameters
  ///
  /// * `file: FilePath`
  ///
  ///   The file containing the folding regions.
  ///
  /// * `regions: List<FoldingRegion>`
  ///
  ///   The folding regions contained in the file.
  late final Stream<AnalysisFoldingParams> onAnalysisFolding =
      _onAnalysisFolding.stream.asBroadcastStream();

  /// Stream controller for [onAnalysisFolding].
  final _onAnalysisFolding = StreamController<AnalysisFoldingParams>(
    sync: true,
  );

  /// Reports the highlight regions associated with a given file.
  ///
  /// This notification is not subscribed to by default. Clients can subscribe
  /// by including the value `"HIGHLIGHTS"` in the list of services passed in
  /// an analysis.setSubscriptions request.
  ///
  /// Parameters
  ///
  /// * `file: FilePath`
  ///
  ///   The file containing the highlight regions.
  ///
  /// * `regions: List<HighlightRegion>`
  ///
  ///   The highlight regions contained in the file. Each highlight region
  ///   represents a particular syntactic or semantic meaning associated with
  ///   some range. Note that the highlight regions that are returned can
  ///   overlap other highlight regions if there is more than one meaning
  ///   associated with a particular region.
  late final Stream<AnalysisHighlightsParams> onAnalysisHighlights =
      _onAnalysisHighlights.stream.asBroadcastStream();

  /// Stream controller for [onAnalysisHighlights].
  final _onAnalysisHighlights = StreamController<AnalysisHighlightsParams>(
    sync: true,
  );

  /// Reports the classes that are implemented or extended and class members
  /// that are implemented or overridden in a file.
  ///
  /// This notification is not subscribed to by default. Clients can subscribe
  /// by including the value `"IMPLEMENTED"` in the list of services passed in
  /// an analysis.setSubscriptions request.
  ///
  /// Parameters
  ///
  /// * `file: FilePath`
  ///
  ///   The file with which the implementations are associated.
  ///
  /// * `classes: List<ImplementedClass>`
  ///
  ///   The classes defined in the file that are implemented or extended.
  ///
  /// * `members: List<ImplementedMember>`
  ///
  ///   The members defined in the file that are implemented or overridden.
  late final Stream<AnalysisImplementedParams> onAnalysisImplemented =
      _onAnalysisImplemented.stream.asBroadcastStream();

  /// Stream controller for [onAnalysisImplemented].
  final _onAnalysisImplemented = StreamController<AnalysisImplementedParams>(
    sync: true,
  );

  /// Reports that the navigation information associated with a region of a
  /// single file has become invalid and should be re-requested.
  ///
  /// This notification is not subscribed to by default. Clients can subscribe
  /// by including the value `"INVALIDATE"` in the list of services passed in
  /// an analysis.setSubscriptions request.
  ///
  /// Parameters
  ///
  /// * `file: FilePath`
  ///
  ///   The file whose information has been invalidated.
  ///
  /// * `offset: int`
  ///
  ///   The offset of the invalidated region.
  ///
  /// * `length: int`
  ///
  ///   The length of the invalidated region.
  ///
  /// * `delta: int`
  ///
  ///   The delta to be applied to the offsets in information that follows the
  ///   invalidated region in order to update it so that it doesn't need to be
  ///   re-requested.
  late final Stream<AnalysisInvalidateParams> onAnalysisInvalidate =
      _onAnalysisInvalidate.stream.asBroadcastStream();

  /// Stream controller for [onAnalysisInvalidate].
  final _onAnalysisInvalidate = StreamController<AnalysisInvalidateParams>(
    sync: true,
  );

  /// Reports the navigation targets associated with a given file.
  ///
  /// This notification is not subscribed to by default. Clients can subscribe
  /// by including the value `"NAVIGATION"` in the list of services passed in
  /// an analysis.setSubscriptions request.
  ///
  /// Parameters
  ///
  /// * `file: FilePath`
  ///
  ///   The file containing the navigation regions.
  ///
  /// * `regions: List<NavigationRegion>`
  ///
  ///   The navigation regions contained in the file. The regions are sorted by
  ///   their offsets. Each navigation region represents a list of targets
  ///   associated with some range. The lists will usually contain a single
  ///   target, but can contain more in the case of a part that is included in
  ///   multiple libraries or in Dart code that is compiled against multiple
  ///   versions of a package. Note that the navigation regions that are
  ///   returned do not overlap other navigation regions.
  ///
  /// * `targets: List<NavigationTarget>`
  ///
  ///   The navigation targets referenced in the file. They are referenced by
  ///   `NavigationRegion`s by their index in this array.
  ///
  /// * `files: List<FilePath>`
  ///
  ///   The files containing navigation targets referenced in the file. They
  ///   are referenced by `NavigationTarget`s by their index in this array.
  late final Stream<AnalysisNavigationParams> onAnalysisNavigation =
      _onAnalysisNavigation.stream.asBroadcastStream();

  /// Stream controller for [onAnalysisNavigation].
  final _onAnalysisNavigation = StreamController<AnalysisNavigationParams>(
    sync: true,
  );

  /// Reports the occurrences of references to elements within a single file.
  ///
  /// This notification is not subscribed to by default. Clients can subscribe
  /// by including the value `"OCCURRENCES"` in the list of services passed in
  /// an analysis.setSubscriptions request.
  ///
  /// Parameters
  ///
  /// * `file: FilePath`
  ///
  ///   The file in which the references occur.
  ///
  /// * `occurrences: List<Occurrences>`
  ///
  ///   The occurrences of references to elements within the file.
  late final Stream<AnalysisOccurrencesParams> onAnalysisOccurrences =
      _onAnalysisOccurrences.stream.asBroadcastStream();

  /// Stream controller for [onAnalysisOccurrences].
  final _onAnalysisOccurrences = StreamController<AnalysisOccurrencesParams>(
    sync: true,
  );

  /// Reports the outline associated with a single file.
  ///
  /// This notification is not subscribed to by default. Clients can subscribe
  /// by including the value `"OUTLINE"` in the list of services passed in an
  /// analysis.setSubscriptions request.
  ///
  /// Parameters
  ///
  /// * `file: FilePath`
  ///
  ///   The file with which the outline is associated.
  ///
  /// * `kind: FileKind`
  ///
  ///   The kind of the file.
  ///
  /// * `libraryName: String (optional)`
  ///
  ///   The name of the library defined by the file using a "library"
  ///   directive, or referenced by a "part of" directive. If both "library"
  ///   and "part of" directives are present, then the "library" directive
  ///   takes precedence. This field will be omitted if the file has neither
  ///   "library" nor "part of" directives.
  ///
  /// * `outline: Outline`
  ///
  ///   The outline associated with the file.
  late final Stream<AnalysisOutlineParams> onAnalysisOutline =
      _onAnalysisOutline.stream.asBroadcastStream();

  /// Stream controller for [onAnalysisOutline].
  final _onAnalysisOutline = StreamController<AnalysisOutlineParams>(
    sync: true,
  );

  /// Reports the overriding members in a file.
  ///
  /// This notification is not subscribed to by default. Clients can subscribe
  /// by including the value `"OVERRIDES"` in the list of services passed in an
  /// analysis.setSubscriptions request.
  ///
  /// Parameters
  ///
  /// * `file: FilePath`
  ///
  ///   The file with which the overrides are associated.
  ///
  /// * `overrides: List<Override>`
  ///
  ///   The overrides associated with the file.
  late final Stream<AnalysisOverridesParams> onAnalysisOverrides =
      _onAnalysisOverrides.stream.asBroadcastStream();

  /// Stream controller for [onAnalysisOverrides].
  final _onAnalysisOverrides = StreamController<AnalysisOverridesParams>(
    sync: true,
  );

  /// Request that completion suggestions for the given offset in the given
  /// file be returned. The suggestions will be filtered using fuzzy matching
  /// with the already existing prefix.
  ///
  /// Parameters
  ///
  /// * `file: FilePath`
  ///
  ///   The file containing the point at which suggestions are to be made.
  ///
  /// * `offset: int`
  ///
  ///   The offset within the file at which suggestions are to be made.
  ///
  /// * `maxResults: int`
  ///
  ///   The maximum number of suggestions to return. If the number of
  ///   suggestions after filtering is greater than the `maxResults`, then
  ///   `isIncomplete` is set to `true`.
  ///
  /// * `completionCaseMatchingMode: CompletionCaseMatchingMode (optional)`
  ///
  ///   The mode of code completion being invoked. If no value is provided,
  ///   `MATCH_FIRST_CHAR` will be assumed.
  ///
  /// Returns
  ///
  /// * `replacementOffset: int`
  ///
  ///   The offset of the start of the text to be replaced. This will be
  ///   different from the offset used to request the completion suggestions if
  ///   there was a portion of an identifier before the original offset. In
  ///   particular, the replacementOffset will be the offset of the beginning
  ///   of said identifier.
  ///
  /// * `replacementLength: int`
  ///
  ///   The length of the text to be replaced if the remainder of the
  ///   identifier containing the cursor is to be replaced when the suggestion
  ///   is applied (that is, the number of characters in the existing
  ///   identifier).
  ///
  /// * `suggestions: List<CompletionSuggestion>`
  ///
  ///   The completion suggestions being reported. This list is filtered by the
  ///   already existing prefix, and sorted first by relevance, and (if the
  ///   same) by the suggestion text. The list will have at most `maxResults`
  ///   items. If the user types a new keystroke, the client is expected to
  ///   either do local filtering (when the returned list was complete), or ask
  ///   the server again (if `isIncomplete` was `true`).
  ///
  ///   This list contains suggestions from both imported, and not yet imported
  ///   libraries. Items from not yet imported libraries will have
  ///   `isNotImported` set to `true`.
  ///
  /// * `isIncomplete: bool`
  ///
  ///   True if the number of suggestions after filtering was greater than the
  ///   requested `maxResults`.
  Future<CompletionGetSuggestions2Result> sendCompletionGetSuggestions2(
    String file,
    int offset,
    int maxResults, {
    CompletionCaseMatchingMode? completionCaseMatchingMode,
    CompletionMode? completionMode,
    int? invocationCount,
    int? timeout,
  }) async {
    var params = CompletionGetSuggestions2Params(
      file,
      offset,
      maxResults,
      completionCaseMatchingMode: completionCaseMatchingMode,
      completionMode: completionMode,
      invocationCount: invocationCount,
      timeout: timeout,
    ).toJson(clientUriConverter: uriConverter);
    var result = await server.send('completion.getSuggestions2', params);
    var decoder = ResponseDecoder(null);
    return CompletionGetSuggestions2Result.fromJson(
      decoder,
      'result',
      result,
      clientUriConverter: uriConverter,
    );
  }

  /// The client can make this request to express interest in certain libraries
  /// to receive completion suggestions from based on the client path. If this
  /// request is received before the client has used
  /// 'completion.setSubscriptions' to subscribe to the
  /// `AVAILABLE_SUGGESTION_SETS` service, then an error of type
  /// `NOT_SUBSCRIBED_TO_AVAILABLE_SUGGESTION_SETS` will be generated. All
  /// previous paths are replaced by the given set of paths.
  ///
  /// Parameters
  ///
  /// * `paths: List<LibraryPathSet>`
  ///
  ///   A list of objects each containing a path and the additional libraries
  ///   from which the client is interested in receiving completion
  ///   suggestions. If one configured path is beneath another, the descendant
  ///   will override the ancestors' configured libraries of interest.
  // TODO(srawlins): Provide a deprecation message, or remove.
  // ignore: provide_deprecation_message
  @deprecated
  Future<void> sendCompletionRegisterLibraryPaths(
    List<LibraryPathSet> paths,
  ) async {
    var params = CompletionRegisterLibraryPathsParams(
      paths,
    ).toJson(clientUriConverter: uriConverter);
    var result = await server.send('completion.registerLibraryPaths', params);
    outOfTestExpect(result, isNull);
  }

  /// Clients must make this request when the user has selected a completion
  /// suggestion with the `isNotImported` field set to `true`. The server will
  /// respond with the text to insert, as well as any `SourceChange` that needs
  /// to be applied in case the completion requires an additional import to be
  /// added. The text to insert might be different from the original suggestion
  /// to include an import prefix if the library will be imported with a prefix
  /// to avoid shadowing conflicts in the file.
  ///
  /// Parameters
  ///
  /// * `file: FilePath`
  ///
  ///   The path of the file into which this completion is being inserted.
  ///
  /// * `offset: int`
  ///
  ///   The offset in the file where the completion will be inserted.
  ///
  /// * `completion: String`
  ///
  ///   The `completion` from the selected `CompletionSuggestion`. It could be
  ///   a name of a class, or a name of a constructor in the form
  ///   "typeName.constructorName()", or an enumeration constant in the form
  ///   "enumName.constantName", etc.
  ///
  /// * `libraryUri: String`
  ///
  ///   The URI of the library to import, so that the element referenced in the
  ///   `completion` becomes accessible.
  ///
  /// Returns
  ///
  /// * `completion: String`
  ///
  ///   The full text to insert, which possibly includes now an import prefix.
  ///   The client should insert this text, not the `completion` from the
  ///   selected `CompletionSuggestion`.
  ///
  /// * `change: SourceChange`
  ///
  ///   A change for the client to apply to make the accepted completion
  ///   suggestion available. In most cases the change is to add a new import
  ///   directive to the file.
  Future<CompletionGetSuggestionDetails2Result>
  sendCompletionGetSuggestionDetails2(
    String file,
    int offset,
    String completion,
    String libraryUri,
  ) async {
    var params = CompletionGetSuggestionDetails2Params(
      file,
      offset,
      completion,
      libraryUri,
    ).toJson(clientUriConverter: uriConverter);
    var result = await server.send('completion.getSuggestionDetails2', params);
    var decoder = ResponseDecoder(null);
    return CompletionGetSuggestionDetails2Result.fromJson(
      decoder,
      'result',
      result,
      clientUriConverter: uriConverter,
    );
  }

  /// Reports existing imports in a library. This notification may be sent
  /// multiple times for a library. When a notification is processed, clients
  /// should replace any previous information for the library.
  ///
  /// Parameters
  ///
  /// * `file: FilePath`
  ///
  ///   The defining file of the library.
  ///
  /// * `imports: ExistingImports`
  ///
  ///   The existing imports in the library.
  late final Stream<CompletionExistingImportsParams>
  onCompletionExistingImports = _onCompletionExistingImports.stream
      .asBroadcastStream();

  /// Stream controller for [onCompletionExistingImports].
  final _onCompletionExistingImports =
      StreamController<CompletionExistingImportsParams>(sync: true);

  /// Perform a search for references to the element defined or referenced at
  /// the given offset in the given file.
  ///
  /// An identifier is returned immediately, and individual results will be
  /// returned via the search.results notification as they become available.
  ///
  /// Parameters
  ///
  /// * `file: FilePath`
  ///
  ///   The file containing the declaration of or reference to the element used
  ///   to define the search.
  ///
  /// * `offset: int`
  ///
  ///   The offset within the file of the declaration of or reference to the
  ///   element.
  ///
  /// * `includePotential: bool`
  ///
  ///   True if potential matches are to be included in the results.
  ///
  /// Returns
  ///
  /// * `id: SearchId (optional)`
  ///
  ///   The identifier used to associate results with this search request.
  ///
  ///   If no element was found at the given location, this field will be
  ///   absent, and no results will be reported via the search.results
  ///   notification.
  ///
  /// * `element: Element (optional)`
  ///
  ///   The element referenced or defined at the given offset and whose
  ///   references will be returned in the search results.
  ///
  ///   If no element was found at the given location, this field will be
  ///   absent.
  Future<SearchFindElementReferencesResult> sendSearchFindElementReferences(
    String file,
    int offset,
    bool includePotential,
  ) async {
    var params = SearchFindElementReferencesParams(
      file,
      offset,
      includePotential,
    ).toJson(clientUriConverter: uriConverter);
    var result = await server.send('search.findElementReferences', params);
    var decoder = ResponseDecoder(null);
    return SearchFindElementReferencesResult.fromJson(
      decoder,
      'result',
      result,
      clientUriConverter: uriConverter,
    );
  }

  /// Perform a search for declarations of members whose name is equal to the
  /// given name.
  ///
  /// An identifier is returned immediately, and individual results will be
  /// returned via the search.results notification as they become available.
  ///
  /// Parameters
  ///
  /// * `name: String`
  ///
  ///   The name of the declarations to be found.
  ///
  /// Returns
  ///
  /// * `id: SearchId`
  ///
  ///   The identifier used to associate results with this search request.
  Future<SearchFindMemberDeclarationsResult> sendSearchFindMemberDeclarations(
    String name,
  ) async {
    var params = SearchFindMemberDeclarationsParams(
      name,
    ).toJson(clientUriConverter: uriConverter);
    var result = await server.send('search.findMemberDeclarations', params);
    var decoder = ResponseDecoder(null);
    return SearchFindMemberDeclarationsResult.fromJson(
      decoder,
      'result',
      result,
      clientUriConverter: uriConverter,
    );
  }

  /// Perform a search for references to members whose name is equal to the
  /// given name. This search does not check to see that there is a member
  /// defined with the given name, so it is able to find references to
  /// undefined members as well.
  ///
  /// An identifier is returned immediately, and individual results will be
  /// returned via the search.results notification as they become available.
  ///
  /// Parameters
  ///
  /// * `name: String`
  ///
  ///   The name of the references to be found.
  ///
  /// Returns
  ///
  /// * `id: SearchId`
  ///
  ///   The identifier used to associate results with this search request.
  Future<SearchFindMemberReferencesResult> sendSearchFindMemberReferences(
    String name,
  ) async {
    var params = SearchFindMemberReferencesParams(
      name,
    ).toJson(clientUriConverter: uriConverter);
    var result = await server.send('search.findMemberReferences', params);
    var decoder = ResponseDecoder(null);
    return SearchFindMemberReferencesResult.fromJson(
      decoder,
      'result',
      result,
      clientUriConverter: uriConverter,
    );
  }

  /// Perform a search for declarations of top-level elements (classes,
  /// typedefs, getters, setters, functions and fields) whose name matches the
  /// given pattern.
  ///
  /// An identifier is returned immediately, and individual results will be
  /// returned via the search.results notification as they become available.
  ///
  /// Parameters
  ///
  /// * `pattern: String`
  ///
  ///   The regular expression used to match the names of the declarations to
  ///   be found.
  ///
  /// Returns
  ///
  /// * `id: SearchId`
  ///
  ///   The identifier used to associate results with this search request.
  Future<SearchFindTopLevelDeclarationsResult>
  sendSearchFindTopLevelDeclarations(String pattern) async {
    var params = SearchFindTopLevelDeclarationsParams(
      pattern,
    ).toJson(clientUriConverter: uriConverter);
    var result = await server.send('search.findTopLevelDeclarations', params);
    var decoder = ResponseDecoder(null);
    return SearchFindTopLevelDeclarationsResult.fromJson(
      decoder,
      'result',
      result,
      clientUriConverter: uriConverter,
    );
  }

  /// Return top-level and class member declarations.
  ///
  /// Parameters
  ///
  /// * `file: FilePath (optional)`
  ///
  ///   If this field is provided, return only declarations in this file. If
  ///   this field is missing, return declarations in all files.
  ///
  /// * `pattern: String (optional)`
  ///
  ///   The regular expression used to match the names of declarations. If this
  ///   field is missing, return all declarations.
  ///
  /// * `maxResults: int (optional)`
  ///
  ///   The maximum number of declarations to return. If this field is missing,
  ///   return all matching declarations.
  ///
  /// Returns
  ///
  /// * `declarations: List<ElementDeclaration>`
  ///
  ///   The list of declarations.
  ///
  /// * `files: List<FilePath>`
  ///
  ///   The list of the paths of files with declarations.
  Future<SearchGetElementDeclarationsResult> sendSearchGetElementDeclarations({
    String? file,
    String? pattern,
    int? maxResults,
  }) async {
    var params = SearchGetElementDeclarationsParams(
      file: file,
      pattern: pattern,
      maxResults: maxResults,
    ).toJson(clientUriConverter: uriConverter);
    var result = await server.send('search.getElementDeclarations', params);
    var decoder = ResponseDecoder(null);
    return SearchGetElementDeclarationsResult.fromJson(
      decoder,
      'result',
      result,
      clientUriConverter: uriConverter,
    );
  }

  /// Return the type hierarchy of the class declared or referenced at the
  /// given location.
  ///
  /// Parameters
  ///
  /// * `file: FilePath`
  ///
  ///   The file containing the declaration or reference to the type for which
  ///   a hierarchy is being requested.
  ///
  /// * `offset: int`
  ///
  ///   The offset of the name of the type within the file.
  ///
  /// * `superOnly: bool (optional)`
  ///
  ///   True if the client is only requesting superclasses and interfaces
  ///   hierarchy.
  ///
  /// Returns
  ///
  /// * `hierarchyItems: List<TypeHierarchyItem> (optional)`
  ///
  ///   A list of the types in the requested hierarchy. The first element of
  ///   the list is the item representing the type for which the hierarchy was
  ///   requested. The index of other elements of the list is unspecified, but
  ///   correspond to the integers used to reference supertype and subtype
  ///   items within the items.
  ///
  ///   This field will be absent if the code at the given file and offset does
  ///   not represent a type, or if the file has not been sufficiently analyzed
  ///   to allow a type hierarchy to be produced.
  Future<SearchGetTypeHierarchyResult> sendSearchGetTypeHierarchy(
    String file,
    int offset, {
    bool? superOnly,
  }) async {
    var params = SearchGetTypeHierarchyParams(
      file,
      offset,
      superOnly: superOnly,
    ).toJson(clientUriConverter: uriConverter);
    var result = await server.send('search.getTypeHierarchy', params);
    var decoder = ResponseDecoder(null);
    return SearchGetTypeHierarchyResult.fromJson(
      decoder,
      'result',
      result,
      clientUriConverter: uriConverter,
    );
  }

  /// Reports some or all of the results of performing a requested search.
  /// Unlike other notifications, this notification contains search results
  /// that should be added to any previously received search results associated
  /// with the same search id.
  ///
  /// Parameters
  ///
  /// * `id: SearchId`
  ///
  ///   The id associated with the search.
  ///
  /// * `results: List<SearchResult>`
  ///
  ///   The search results being reported.
  ///
  /// * `isLast: bool`
  ///
  ///   True if this is that last set of results that will be returned for the
  ///   indicated search.
  late final Stream<SearchResultsParams> onSearchResults = _onSearchResults
      .stream
      .asBroadcastStream();

  /// Stream controller for [onSearchResults].
  final _onSearchResults = StreamController<SearchResultsParams>(sync: true);

  /// Format the contents of a single file. The currently selected region of
  /// text is passed in so that the selection can be preserved across the
  /// formatting operation. The updated selection will be as close to matching
  /// the original as possible, but whitespace at the beginning or end of the
  /// selected region will be ignored. If preserving selection information is
  /// not required, zero (0) can be specified for both the selection offset and
  /// selection length.
  ///
  /// If a request is made for a file which does not exist, or which is not
  /// currently subject to analysis (e.g. because it is not associated with any
  /// analysis root specified to analysis.setAnalysisRoots), an error of type
  /// `FORMAT_INVALID_FILE` will be generated. If the source contains syntax
  /// errors, an error of type `FORMAT_WITH_ERRORS` will be generated.
  ///
  /// Parameters
  ///
  /// * `file: FilePath`
  ///
  ///   The file containing the code to be formatted.
  ///
  /// * `selectionOffset: int`
  ///
  ///   The offset of the current selection in the file.
  ///
  /// * `selectionLength: int`
  ///
  ///   The length of the current selection in the file.
  ///
  /// * `lineLength: int (optional)`
  ///
  ///   The line length to be used by the formatter. This value is ignored if a
  ///   `formatter.page_width` has been configured in the relevant
  ///   `analysis_options.yaml` file.
  ///
  /// Returns
  ///
  /// * `edits: List<SourceEdit>`
  ///
  ///   The edit(s) to be applied in order to format the code. The list will be
  ///   empty if the code was already formatted (there are no changes).
  ///
  /// * `selectionOffset: int`
  ///
  ///   The offset of the selection after formatting the code.
  ///
  /// * `selectionLength: int`
  ///
  ///   The length of the selection after formatting the code.
  Future<EditFormatResult> sendEditFormat(
    String file,
    int selectionOffset,
    int selectionLength, {
    int? lineLength,
  }) async {
    var params = EditFormatParams(
      file,
      selectionOffset,
      selectionLength,
      lineLength: lineLength,
    ).toJson(clientUriConverter: uriConverter);
    var result = await server.send('edit.format', params);
    var decoder = ResponseDecoder(null);
    return EditFormatResult.fromJson(
      decoder,
      'result',
      result,
      clientUriConverter: uriConverter,
    );
  }

  /// Format the contents of the files in one or more directories, but only if
  /// the analysis options file for those files has enabled the 'format'
  /// option.
  ///
  /// If any of the specified directories does not exist, that directory will
  /// be ignored. If any of the files that are eligible for being formatted
  /// cannot be formatted because of a syntax error in the file, that file will
  /// be ignored.
  ///
  /// Parameters
  ///
  /// * `directories: List<FilePath>`
  ///
  ///   The paths of the directories containing the code to be formatted.
  ///
  /// Returns
  ///
  /// * `edits: List<SourceFileEdit>`
  ///
  ///   The edit(s) to be applied in order to format the code. The list will be
  ///   empty if none of the files were formatted, whether because they were
  ///   not eligible to be formatted or because they were already formatted.
  Future<EditFormatIfEnabledResult> sendEditFormatIfEnabled(
    List<String> directories,
  ) async {
    var params = EditFormatIfEnabledParams(
      directories,
    ).toJson(clientUriConverter: uriConverter);
    var result = await server.send('edit.formatIfEnabled', params);
    var decoder = ResponseDecoder(null);
    return EditFormatIfEnabledResult.fromJson(
      decoder,
      'result',
      result,
      clientUriConverter: uriConverter,
    );
  }

  /// Return the set of assists that are available at the given location. An
  /// assist is distinguished from a refactoring primarily by the fact that it
  /// affects a single file and does not require user input in order to be
  /// performed.
  ///
  /// Parameters
  ///
  /// * `file: FilePath`
  ///
  ///   The file containing the code for which assists are being requested.
  ///
  /// * `offset: int`
  ///
  ///   The offset of the code for which assists are being requested.
  ///
  /// * `length: int`
  ///
  ///   The length of the code for which assists are being requested.
  ///
  /// Returns
  ///
  /// * `assists: List<SourceChange>`
  ///
  ///   The assists that are available at the given location.
  Future<EditGetAssistsResult> sendEditGetAssists(
    String file,
    int offset,
    int length,
  ) async {
    var params = EditGetAssistsParams(
      file,
      offset,
      length,
    ).toJson(clientUriConverter: uriConverter);
    var result = await server.send('edit.getAssists', params);
    var decoder = ResponseDecoder(null);
    return EditGetAssistsResult.fromJson(
      decoder,
      'result',
      result,
      clientUriConverter: uriConverter,
    );
  }

  /// Get a list of the kinds of refactorings that are valid for the given
  /// selection in the given file.
  ///
  /// Parameters
  ///
  /// * `file: FilePath`
  ///
  ///   The file containing the code on which the refactoring would be based.
  ///
  /// * `offset: int`
  ///
  ///   The offset of the code on which the refactoring would be based.
  ///
  /// * `length: int`
  ///
  ///   The length of the code on which the refactoring would be based.
  ///
  /// Returns
  ///
  /// * `kinds: List<RefactoringKind>`
  ///
  ///   The kinds of refactorings that are valid for the given selection.
  Future<EditGetAvailableRefactoringsResult> sendEditGetAvailableRefactorings(
    String file,
    int offset,
    int length,
  ) async {
    var params = EditGetAvailableRefactoringsParams(
      file,
      offset,
      length,
    ).toJson(clientUriConverter: uriConverter);
    var result = await server.send('edit.getAvailableRefactorings', params);
    var decoder = ResponseDecoder(null);
    return EditGetAvailableRefactoringsResult.fromJson(
      decoder,
      'result',
      result,
      clientUriConverter: uriConverter,
    );
  }

  /// Analyze the specified sources for fixes that can be applied in bulk and
  /// return a set of suggested edits for those sources. These edits may
  /// include changes to sources outside the set of specified sources if a
  /// change in a specified source requires it.
  ///
  /// Parameters
  ///
  /// * `included: List<FilePath>`
  ///
  ///   A list of the files and directories for which edits should be
  ///   suggested.
  ///
  ///   If a request is made with a path that is invalid, e.g. is not absolute
  ///   and normalized, an error of type `INVALID_FILE_PATH_FORMAT` will be
  ///   generated. If a request is made for a file which does not exist, or
  ///   which is not currently subject to analysis (e.g. because it is not
  ///   associated with any analysis root specified to
  ///   analysis.setAnalysisRoots), an error of type `FILE_NOT_ANALYZED` will
  ///   be generated.
  ///
  /// * `inTestMode: bool (optional)`
  ///
  ///   A flag indicating whether the bulk fixes are being run in test mode.
  ///   The only difference is that in test mode the fix processor will look
  ///   for a configuration file that can modify the content of the data file
  ///   used to compute the fixes when data-driven fixes are being considered.
  ///
  ///   If this field is omitted the flag defaults to `false`.
  ///
  /// * `updatePubspec: bool (optional)`
  ///
  ///   A flag indicating whether to validate that the dependencies used by the
  ///   included files are listed in the pubspec file. If specified, the fix
  ///   processor will compute the set of packages imported in the source and
  ///   check to see if they are listed in the corresponding pubspec file, and
  ///   compute the fixes, if any.
  ///
  ///   If this field is omitted the flag defaults to `false`.
  ///
  /// * `codes: List<String> (optional)`
  ///
  ///   A list of diagnostic codes to be fixed.
  ///
  /// Returns
  ///
  /// * `message: String`
  ///
  ///   An optional message explaining unapplied fixes.
  ///
  /// * `edits: List<SourceFileEdit>`
  ///
  ///   A list of source edits to apply the recommended changes.
  ///
  /// * `details: List<BulkFix>`
  ///
  ///   Details that summarize the fixes associated with the recommended
  ///   changes.
  Future<EditBulkFixesResult> sendEditBulkFixes(
    List<String> included, {
    bool? inTestMode,
    bool? updatePubspec,
    List<String>? codes,
  }) async {
    var params = EditBulkFixesParams(
      included,
      inTestMode: inTestMode,
      updatePubspec: updatePubspec,
      codes: codes,
    ).toJson(clientUriConverter: uriConverter);
    var result = await server.send('edit.bulkFixes', params);
    var decoder = ResponseDecoder(null);
    return EditBulkFixesResult.fromJson(
      decoder,
      'result',
      result,
      clientUriConverter: uriConverter,
    );
  }

  /// Return the set of fixes that are available for the errors at a given
  /// offset in a given file.
  ///
  /// If a request is made for a file which does not exist, or which is not
  /// currently subject to analysis (e.g. because it is not associated with any
  /// analysis root specified to analysis.setAnalysisRoots), an error of type
  /// `GET_FIXES_INVALID_FILE` will be generated.
  ///
  /// Parameters
  ///
  /// * `file: FilePath`
  ///
  ///   The file containing the errors for which fixes are being requested.
  ///
  /// * `offset: int`
  ///
  ///   The offset used to select the errors for which fixes will be returned.
  ///
  /// Returns
  ///
  /// * `fixes: List<AnalysisErrorFixes>`
  ///
  ///   The fixes that are available for the errors at the given offset.
  Future<EditGetFixesResult> sendEditGetFixes(String file, int offset) async {
    var params = EditGetFixesParams(
      file,
      offset,
    ).toJson(clientUriConverter: uriConverter);
    var result = await server.send('edit.getFixes', params);
    var decoder = ResponseDecoder(null);
    return EditGetFixesResult.fromJson(
      decoder,
      'result',
      result,
      clientUriConverter: uriConverter,
    );
  }

  /// Get the changes required to convert the postfix template at the given
  /// location into the template's expanded form.
  ///
  /// Parameters
  ///
  /// * `file: FilePath`
  ///
  ///   The file containing the postfix template to be expanded.
  ///
  /// * `key: String`
  ///
  ///   The unique name that identifies the template in use.
  ///
  /// * `offset: int`
  ///
  ///   The offset used to identify the code to which the template will be
  ///   applied.
  ///
  /// Returns
  ///
  /// * `change: SourceChange`
  ///
  ///   The change to be applied in order to complete the statement.
  Future<EditGetPostfixCompletionResult> sendEditGetPostfixCompletion(
    String file,
    String key,
    int offset,
  ) async {
    var params = EditGetPostfixCompletionParams(
      file,
      key,
      offset,
    ).toJson(clientUriConverter: uriConverter);
    var result = await server.send('edit.getPostfixCompletion', params);
    var decoder = ResponseDecoder(null);
    return EditGetPostfixCompletionResult.fromJson(
      decoder,
      'result',
      result,
      clientUriConverter: uriConverter,
    );
  }

  /// Get the changes required to perform a refactoring.
  ///
  /// If another refactoring request is received during the processing of this
  /// one, an error of type `REFACTORING_REQUEST_CANCELLED` will be generated.
  ///
  /// Parameters
  ///
  /// * `kind: RefactoringKind`
  ///
  ///   The kind of refactoring to be performed.
  ///
  /// * `file: FilePath`
  ///
  ///   The file containing the code involved in the refactoring.
  ///
  /// * `offset: int`
  ///
  ///   The offset of the region involved in the refactoring.
  ///
  /// * `length: int`
  ///
  ///   The length of the region involved in the refactoring.
  ///
  /// * `validateOnly: bool`
  ///
  ///   True if the client is only requesting that the values of the options be
  ///   validated and no change be generated.
  ///
  /// * `options: RefactoringOptions (optional)`
  ///
  ///   Data used to provide values provided by the user. The structure of the
  ///   data is dependent on the kind of refactoring being performed. The data
  ///   that is expected is documented in the section titled Refactorings,
  ///   labeled as "Options". This field can be omitted if the refactoring does
  ///   not require any options or if the values of those options are not
  ///   known.
  ///
  /// Returns
  ///
  /// * `initialProblems: List<RefactoringProblem>`
  ///
  ///   The initial status of the refactoring, i.e. problems related to the
  ///   context in which the refactoring is requested. The array will be empty
  ///   if there are no known problems.
  ///
  /// * `optionsProblems: List<RefactoringProblem>`
  ///
  ///   The options validation status, i.e. problems in the given options, such
  ///   as light-weight validation of a new name, flags compatibility, etc. The
  ///   array will be empty if there are no known problems.
  ///
  /// * `finalProblems: List<RefactoringProblem>`
  ///
  ///   The final status of the refactoring, i.e. problems identified in the
  ///   result of a full, potentially expensive validation and / or change
  ///   creation. The array will be empty if there are no known problems.
  ///
  /// * `feedback: RefactoringFeedback (optional)`
  ///
  ///   Data used to provide feedback to the user. The structure of the data is
  ///   dependent on the kind of refactoring being created. The data that is
  ///   returned is documented in the section titled Refactorings, labeled as
  ///   "Feedback".
  ///
  /// * `change: SourceChange (optional)`
  ///
  ///   The changes that are to be applied to affect the refactoring. This
  ///   field will be omitted if there are problems that prevent a set of
  ///   changes from being computed, such as having no options specified for a
  ///   refactoring that requires them, or if only validation was requested.
  ///
  /// * `potentialEdits: List<String> (optional)`
  ///
  ///   The ids of source edits that are not known to be valid. An edit is not
  ///   known to be valid if there was insufficient type information for the
  ///   server to be able to determine whether or not the code needs to be
  ///   modified, such as when a member is being renamed and there is a
  ///   reference to a member from an unknown type. This field will be omitted
  ///   if the change field is omitted or if there are no potential edits for
  ///   the refactoring.
  Future<EditGetRefactoringResult> sendEditGetRefactoring(
    RefactoringKind kind,
    String file,
    int offset,
    int length,
    bool validateOnly, {
    RefactoringOptions? options,
  }) async {
    var params = EditGetRefactoringParams(
      kind,
      file,
      offset,
      length,
      validateOnly,
      options: options,
    ).toJson(clientUriConverter: uriConverter);
    var result = await server.send('edit.getRefactoring', params);
    var decoder = ResponseDecoder(kind);
    return EditGetRefactoringResult.fromJson(
      decoder,
      'result',
      result,
      clientUriConverter: uriConverter,
    );
  }

  /// Get the changes required to convert the partial statement at the given
  /// location into a syntactically valid statement. If the current statement
  /// is already valid the change will insert a newline plus appropriate
  /// indentation at the end of the line containing the offset. If a change
  /// that makes the statement valid cannot be determined (perhaps because it
  /// has not yet been implemented) the statement will be considered already
  /// valid and the appropriate change returned.
  ///
  /// Parameters
  ///
  /// * `file: FilePath`
  ///
  ///   The file containing the statement to be completed.
  ///
  /// * `offset: int`
  ///
  ///   The offset used to identify the statement to be completed.
  ///
  /// Returns
  ///
  /// * `change: SourceChange`
  ///
  ///   The change to be applied in order to complete the statement.
  ///
  /// * `whitespaceOnly: bool`
  ///
  ///   Will be true if the change contains nothing but whitespace characters,
  ///   or is empty.
  Future<EditGetStatementCompletionResult> sendEditGetStatementCompletion(
    String file,
    int offset,
  ) async {
    var params = EditGetStatementCompletionParams(
      file,
      offset,
    ).toJson(clientUriConverter: uriConverter);
    var result = await server.send('edit.getStatementCompletion', params);
    var decoder = ResponseDecoder(null);
    return EditGetStatementCompletionResult.fromJson(
      decoder,
      'result',
      result,
      clientUriConverter: uriConverter,
    );
  }

  /// Determine if the request postfix completion template is applicable at the
  /// given location in the given file.
  ///
  /// Parameters
  ///
  /// * `file: FilePath`
  ///
  ///   The file containing the postfix template to be expanded.
  ///
  /// * `key: String`
  ///
  ///   The unique name that identifies the template in use.
  ///
  /// * `offset: int`
  ///
  ///   The offset used to identify the code to which the template will be
  ///   applied.
  ///
  /// Returns
  ///
  /// * `value: bool`
  ///
  ///   True if the template can be expanded at the given location.
  Future<EditIsPostfixCompletionApplicableResult>
  sendEditIsPostfixCompletionApplicable(
    String file,
    String key,
    int offset,
  ) async {
    var params = EditIsPostfixCompletionApplicableParams(
      file,
      key,
      offset,
    ).toJson(clientUriConverter: uriConverter);
    var result = await server.send(
      'edit.isPostfixCompletionApplicable',
      params,
    );
    var decoder = ResponseDecoder(null);
    return EditIsPostfixCompletionApplicableResult.fromJson(
      decoder,
      'result',
      result,
      clientUriConverter: uriConverter,
    );
  }

  /// Return a list of all postfix templates currently available.
  ///
  /// Returns
  ///
  /// * `templates: List<PostfixTemplateDescriptor>`
  ///
  ///   The list of available templates.
  Future<EditListPostfixCompletionTemplatesResult>
  sendEditListPostfixCompletionTemplates() async {
    var result = await server.send('edit.listPostfixCompletionTemplates', null);
    var decoder = ResponseDecoder(null);
    return EditListPostfixCompletionTemplatesResult.fromJson(
      decoder,
      'result',
      result,
      clientUriConverter: uriConverter,
    );
  }

  /// Return a list of edits that would need to be applied in order to ensure
  /// that all of the elements in the specified list of imported elements are
  /// accessible within the library.
  ///
  /// If a request is made for a file that does not exist, or that is not
  /// currently subject to analysis (e.g. because it is not associated with any
  /// analysis root specified via analysis.setAnalysisRoots), an error of type
  /// `IMPORT_ELEMENTS_INVALID_FILE` will be generated.
  ///
  /// Parameters
  ///
  /// * `file: FilePath`
  ///
  ///   The file in which the specified elements are to be made accessible.
  ///
  /// * `elements: List<ImportedElements>`
  ///
  ///   The elements to be made accessible in the specified file.
  ///
  /// * `offset: int (optional)`
  ///
  ///   The offset at which the specified elements need to be made accessible.
  ///   If provided, this is used to guard against adding imports for text that
  ///   would be inserted into a comment, string literal, or other location
  ///   where the imports would not be necessary.
  ///
  /// Returns
  ///
  /// * `edit: SourceFileEdit (optional)`
  ///
  ///   The edits to be applied in order to make the specified elements
  ///   accessible. The file to be edited will be the defining compilation unit
  ///   of the library containing the file specified in the request, which can
  ///   be different than the file specified in the request if the specified
  ///   file is a part file. This field will be omitted if there are no edits
  ///   that need to be applied.
  Future<EditImportElementsResult> sendEditImportElements(
    String file,
    List<ImportedElements> elements, {
    int? offset,
  }) async {
    var params = EditImportElementsParams(
      file,
      elements,
      offset: offset,
    ).toJson(clientUriConverter: uriConverter);
    var result = await server.send('edit.importElements', params);
    var decoder = ResponseDecoder(null);
    return EditImportElementsResult.fromJson(
      decoder,
      'result',
      result,
      clientUriConverter: uriConverter,
    );
  }

  /// Sort all of the directives, unit and class members of the given Dart
  /// file.
  ///
  /// If a request is made for a file that does not exist, does not belong to
  /// an analysis root or is not a Dart file, `SORT_MEMBERS_INVALID_FILE` will
  /// be generated.
  ///
  /// If the Dart file has scan or parse errors, `SORT_MEMBERS_PARSE_ERRORS`
  /// will be generated.
  ///
  /// Parameters
  ///
  /// * `file: FilePath`
  ///
  ///   The Dart file to sort.
  ///
  /// Returns
  ///
  /// * `edit: SourceFileEdit`
  ///
  ///   The file edit that is to be applied to the given file to affect the
  ///   sorting.
  Future<EditSortMembersResult> sendEditSortMembers(String file) async {
    var params = EditSortMembersParams(
      file,
    ).toJson(clientUriConverter: uriConverter);
    var result = await server.send('edit.sortMembers', params);
    var decoder = ResponseDecoder(null);
    return EditSortMembersResult.fromJson(
      decoder,
      'result',
      result,
      clientUriConverter: uriConverter,
    );
  }

  /// Organizes all of the directives - removes unused imports and sorts
  /// directives of the given Dart file according to the Dart Style Guide.
  ///
  /// If a request is made for a file that does not exist, does not belong to
  /// an analysis root or is not a Dart file, `FILE_NOT_ANALYZED` will be
  /// generated.
  ///
  /// If directives of the Dart file cannot be organized, for example because
  /// it has scan or parse errors, or by other reasons,
  /// `ORGANIZE_DIRECTIVES_ERROR` will be generated. The message will provide
  /// details about the reason.
  ///
  /// Parameters
  ///
  /// * `file: FilePath`
  ///
  ///   The Dart file to organize directives in.
  ///
  /// Returns
  ///
  /// * `edit: SourceFileEdit`
  ///
  ///   The file edit that is to be applied to the given file to affect the
  ///   organizing.
  Future<EditOrganizeDirectivesResult> sendEditOrganizeDirectives(
    String file,
  ) async {
    var params = EditOrganizeDirectivesParams(
      file,
    ).toJson(clientUriConverter: uriConverter);
    var result = await server.send('edit.organizeDirectives', params);
    var decoder = ResponseDecoder(null);
    return EditOrganizeDirectivesResult.fromJson(
      decoder,
      'result',
      result,
      clientUriConverter: uriConverter,
    );
  }

  /// Create an execution context for the executable file with the given path.
  /// The context that is created will persist until execution.deleteContext is
  /// used to delete it. Clients, therefore, are responsible for managing the
  /// lifetime of execution contexts.
  ///
  /// Parameters
  ///
  /// * `contextRoot: FilePath`
  ///
  ///   The path of the Dart or HTML file that will be launched, or the path of
  ///   the directory containing the file.
  ///
  /// Returns
  ///
  /// * `id: ExecutionContextId`
  ///
  ///   The identifier used to refer to the execution context that was created.
  Future<ExecutionCreateContextResult> sendExecutionCreateContext(
    String contextRoot,
  ) async {
    var params = ExecutionCreateContextParams(
      contextRoot,
    ).toJson(clientUriConverter: uriConverter);
    var result = await server.send('execution.createContext', params);
    var decoder = ResponseDecoder(null);
    return ExecutionCreateContextResult.fromJson(
      decoder,
      'result',
      result,
      clientUriConverter: uriConverter,
    );
  }

  /// Delete the execution context with the given identifier. The context id is
  /// no longer valid after this command. The server is allowed to re-use ids
  /// when they are no longer valid.
  ///
  /// Parameters
  ///
  /// * `id: ExecutionContextId`
  ///
  ///   The identifier of the execution context that is to be deleted.
  Future<void> sendExecutionDeleteContext(String id) async {
    var params = ExecutionDeleteContextParams(
      id,
    ).toJson(clientUriConverter: uriConverter);
    var result = await server.send('execution.deleteContext', params);
    outOfTestExpect(result, isNull);
  }

  /// Request completion suggestions for the given runtime context.
  ///
  /// It might take one or two requests of this type to get completion
  /// suggestions. The first request should have only "code", "offset", and
  /// "variables", but not "expressions". If there are sub-expressions that can
  /// have different runtime types, and are considered to be safe to evaluate
  /// at runtime (e.g. getters), so using their actual runtime types can
  /// improve completion results, the server will not include the "suggestions"
  /// field in the response, and instead will return the "expressions" field.
  /// The client will use debug API to get current runtime types for these
  /// sub-expressions and send another request, this time with "expressions".
  /// If there are no interesting sub-expressions to get runtime types for, or
  /// when the "expressions" field is provided by the client, the server will
  /// return "suggestions" in the response.
  ///
  /// Parameters
  ///
  /// * `code: String`
  ///
  ///   The code to get suggestions in.
  ///
  /// * `offset: int`
  ///
  ///   The offset within the code to get suggestions at.
  ///
  /// * `contextFile: FilePath`
  ///
  ///   The path of the context file, e.g. the file of the current debugger
  ///   frame. The combination of the context file and context offset can be
  ///   used to ensure that all variables of the context are available for
  ///   completion (with their static types).
  ///
  /// * `contextOffset: int`
  ///
  ///   The offset in the context file, e.g. the line offset in the current
  ///   debugger frame.
  ///
  /// * `variables: List<RuntimeCompletionVariable>`
  ///
  ///   The runtime context variables that are potentially referenced in the
  ///   code.
  ///
  /// * `expressions: List<RuntimeCompletionExpression> (optional)`
  ///
  ///   The list of sub-expressions in the code for which the client wants to
  ///   provide runtime types. It does not have to be the full list of
  ///   expressions requested by the server, for missing expressions their
  ///   static types will be used.
  ///
  ///   When this field is omitted, the server will return completion
  ///   suggestions only when there are no interesting sub-expressions in the
  ///   given code. The client may provide an empty list, in this case the
  ///   server will return completion suggestions.
  ///
  /// Returns
  ///
  /// * `suggestions: List<CompletionSuggestion> (optional)`
  ///
  ///   The completion suggestions. In contrast to usual completion requests,
  ///   suggestions for private elements also will be provided.
  ///
  ///   If there are sub-expressions that can have different runtime types, and
  ///   are considered to be safe to evaluate at runtime (e.g. getters), so
  ///   using their actual runtime types can improve completion results, the
  ///   server omits this field in the response, and instead will return the
  ///   "expressions" field.
  ///
  /// * `expressions: List<RuntimeCompletionExpression> (optional)`
  ///
  ///   The list of sub-expressions in the code for which the server would like
  ///   to know runtime types to provide better completion suggestions.
  ///
  ///   This field is omitted the field "suggestions" is returned.
  Future<ExecutionGetSuggestionsResult> sendExecutionGetSuggestions(
    String code,
    int offset,
    String contextFile,
    int contextOffset,
    List<RuntimeCompletionVariable> variables, {
    List<RuntimeCompletionExpression>? expressions,
  }) async {
    var params = ExecutionGetSuggestionsParams(
      code,
      offset,
      contextFile,
      contextOffset,
      variables,
      expressions: expressions,
    ).toJson(clientUriConverter: uriConverter);
    var result = await server.send('execution.getSuggestions', params);
    var decoder = ResponseDecoder(null);
    return ExecutionGetSuggestionsResult.fromJson(
      decoder,
      'result',
      result,
      clientUriConverter: uriConverter,
    );
  }

  /// Map a URI from the execution context to the file that it corresponds to,
  /// or map a file to the URI that it corresponds to in the execution context.
  ///
  /// Exactly one of the file and uri fields must be provided. If both fields
  /// are provided, then an error of type `INVALID_PARAMETER` will be
  /// generated. Similarly, if neither field is provided, then an error of type
  /// `INVALID_PARAMETER` will be generated.
  ///
  /// If the file field is provided and the value is not the path of a file
  /// (either the file does not exist or the path references something other
  /// than a file), then an error of type `INVALID_PARAMETER` will be
  /// generated.
  ///
  /// If the uri field is provided and the value is not a valid URI or if the
  /// URI references something that is not a file (either a file that does not
  /// exist or something other than a file), then an error of type
  /// `INVALID_PARAMETER` will be generated.
  ///
  /// If the contextRoot used to create the execution context does not exist,
  /// then an error of type `INVALID_EXECUTION_CONTEXT` will be generated.
  ///
  /// Parameters
  ///
  /// * `id: ExecutionContextId`
  ///
  ///   The identifier of the execution context in which the URI is to be
  ///   mapped.
  ///
  /// * `file: FilePath (optional)`
  ///
  ///   The path of the file to be mapped into a URI.
  ///
  /// * `uri: String (optional)`
  ///
  ///   The URI to be mapped into a file path.
  ///
  /// Returns
  ///
  /// * `file: FilePath (optional)`
  ///
  ///   The file to which the URI was mapped. This field is omitted if the uri
  ///   field was not given in the request.
  ///
  /// * `uri: String (optional)`
  ///
  ///   The URI to which the file path was mapped. This field is omitted if the
  ///   file field was not given in the request.
  Future<ExecutionMapUriResult> sendExecutionMapUri(
    String id, {
    String? file,
    String? uri,
  }) async {
    var params = ExecutionMapUriParams(
      id,
      file: file,
      uri: uri,
    ).toJson(clientUriConverter: uriConverter);
    var result = await server.send('execution.mapUri', params);
    var decoder = ResponseDecoder(null);
    return ExecutionMapUriResult.fromJson(
      decoder,
      'result',
      result,
      clientUriConverter: uriConverter,
    );
  }

  /// Deprecated: the analysis server no longer fires `LAUNCH_DATA` events.
  ///
  /// Subscribe for services. All previous subscriptions are replaced by the
  /// given set of services.
  ///
  /// It is an error if any of the elements in the list are not valid services.
  /// If there is an error, then the current subscriptions will remain
  /// unchanged.
  ///
  /// Parameters
  ///
  /// * `subscriptions: List<ExecutionService>`
  ///
  ///   A list of the services being subscribed to.
  // TODO(srawlins): Provide a deprecation message, or remove.
  // ignore: provide_deprecation_message
  @deprecated
  Future<void> sendExecutionSetSubscriptions(
    List<ExecutionService> subscriptions,
  ) async {
    var params = ExecutionSetSubscriptionsParams(
      subscriptions,
    ).toJson(clientUriConverter: uriConverter);
    var result = await server.send('execution.setSubscriptions', params);
    outOfTestExpect(result, isNull);
  }

  /// Reports information needed to allow a single file to be launched.
  ///
  /// This notification is not subscribed to by default. Clients can subscribe
  /// by including the value "LAUNCH_DATA" in the list of services passed in an
  /// `execution.setSubscriptions` request.
  ///
  /// Parameters
  ///
  /// * `file: FilePath`
  ///
  ///   The file for which launch data is being provided. This will either be a
  ///   Dart library or an HTML file.
  ///
  /// * `kind: ExecutableKind (optional)`
  ///
  ///   The kind of the executable file. This field is omitted if the file is
  ///   not a Dart file.
  ///
  /// * `referencedFiles: List<FilePath> (optional)`
  ///
  ///   A list of the Dart files that are referenced by the file. This field is
  ///   omitted if the file is not an HTML file.
  late final Stream<ExecutionLaunchDataParams> onExecutionLaunchData =
      _onExecutionLaunchData.stream.asBroadcastStream();

  /// Stream controller for [onExecutionLaunchData].
  final _onExecutionLaunchData = StreamController<ExecutionLaunchDataParams>(
    sync: true,
  );

  /// Return server diagnostics.
  ///
  /// Returns
  ///
  /// * `contexts: List<ContextData>`
  ///
  ///   The list of analysis contexts.
  Future<DiagnosticGetDiagnosticsResult> sendDiagnosticGetDiagnostics() async {
    var result = await server.send('diagnostic.getDiagnostics', null);
    var decoder = ResponseDecoder(null);
    return DiagnosticGetDiagnosticsResult.fromJson(
      decoder,
      'result',
      result,
      clientUriConverter: uriConverter,
    );
  }

  /// Return the port of the diagnostic web server. If the server is not
  /// running this call will start the server. If unable to start the
  /// diagnostic web server, this call will return an error of
  /// `DEBUG_PORT_COULD_NOT_BE_OPENED`.
  ///
  /// Returns
  ///
  /// * `port: int`
  ///
  ///   The diagnostic server port.
  Future<DiagnosticGetServerPortResult> sendDiagnosticGetServerPort() async {
    var result = await server.send('diagnostic.getServerPort', null);
    var decoder = ResponseDecoder(null);
    return DiagnosticGetServerPortResult.fromJson(
      decoder,
      'result',
      result,
      clientUriConverter: uriConverter,
    );
  }

  /// Deprecated: this flag is no longer supported.
  ///
  /// Query whether analytics is enabled.
  ///
  /// This flag controls whether the analysis server sends any analytics data
  /// to the cloud. If disabled, the analysis server does not send any
  /// analytics data, and any data sent to it by clients (from `sendEvent` and
  /// `sendTiming`) will be ignored.
  ///
  /// The value of this flag can be changed by other tools outside of the
  /// analysis server's process. When you query the flag, you get the value of
  /// the flag at a given moment. Clients should not use the value returned to
  /// decide whether or not to send the `sendEvent` and `sendTiming` requests.
  /// Those requests should be used unconditionally and server will determine
  /// whether or not it is appropriate to forward the information to the cloud
  /// at the time each request is received.
  ///
  /// Returns
  ///
  /// * `enabled: bool`
  ///
  ///   Whether sending analytics is enabled or not.
  // TODO(srawlins): Provide a deprecation message, or remove.
  // ignore: provide_deprecation_message
  @deprecated
  Future<AnalyticsIsEnabledResult> sendAnalyticsIsEnabled() async {
    var result = await server.send('analytics.isEnabled', null);
    var decoder = ResponseDecoder(null);
    return AnalyticsIsEnabledResult.fromJson(
      decoder,
      'result',
      result,
      clientUriConverter: uriConverter,
    );
  }

  /// Deprecated: this option is no longer supported.
  ///
  /// Enable or disable the sending of analytics data. Note that there are
  /// other ways for users to change this setting, so clients cannot assume
  /// that they have complete control over this setting. In particular, there
  /// is no guarantee that the result returned by the `isEnabled` request will
  /// match the last value set via this request.
  ///
  /// Parameters
  ///
  /// * `value: bool`
  ///
  ///   Enable or disable analytics.
  // TODO(srawlins): Provide a deprecation message, or remove.
  // ignore: provide_deprecation_message
  @deprecated
  Future<void> sendAnalyticsEnable(bool value) async {
    var params = AnalyticsEnableParams(
      value,
    ).toJson(clientUriConverter: uriConverter);
    var result = await server.send('analytics.enable', params);
    outOfTestExpect(result, isNull);
  }

  /// Send information about client events.
  ///
  /// Ask the analysis server to include the fact that an action was performed
  /// in the client as part of the analytics data being sent. The data will
  /// only be included if the sending of analytics data is enabled at the time
  /// the request is processed. The action that was performed is indicated by
  /// the value of the `action` field.
  ///
  /// The value of the action field should not include the identity of the
  /// client. The analytics data sent by server will include the client id
  /// passed in using the `--client-id` command-line argument. The request will
  /// be ignored if the client id was not provided when server was started.
  ///
  /// Parameters
  ///
  /// * `action: String`
  ///
  ///   The value used to indicate which action was performed.
  Future<void> sendAnalyticsSendEvent(String action) async {
    var params = AnalyticsSendEventParams(
      action,
    ).toJson(clientUriConverter: uriConverter);
    var result = await server.send('analytics.sendEvent', params);
    outOfTestExpect(result, isNull);
  }

  /// Send timing information for client events (e.g. code completions).
  ///
  /// Ask the analysis server to include the fact that a timed event occurred
  /// as part of the analytics data being sent. The data will only be included
  /// if the sending of analytics data is enabled at the time the request is
  /// processed.
  ///
  /// The value of the event field should not include the identity of the
  /// client. The analytics data sent by server will include the client id
  /// passed in using the `--client-id` command-line argument. The request will
  /// be ignored if the client id was not provided when server was started.
  ///
  /// Parameters
  ///
  /// * `event: String`
  ///
  ///   The name of the event.
  ///
  /// * `millis: int`
  ///
  ///   The duration of the event in milliseconds.
  Future<void> sendAnalyticsSendTiming(String event, int millis) async {
    var params = AnalyticsSendTimingParams(
      event,
      millis,
    ).toJson(clientUriConverter: uriConverter);
    var result = await server.send('analytics.sendTiming', params);
    outOfTestExpect(result, isNull);
  }

  /// Return the description of the widget instance at the given location.
  ///
  /// If the location does not have a support widget, an error of type
  /// `FLUTTER_GET_WIDGET_DESCRIPTION_NO_WIDGET` will be generated.
  ///
  /// If a change to a file happens while widget descriptions are computed, an
  /// error of type `FLUTTER_GET_WIDGET_DESCRIPTION_CONTENT_MODIFIED` will be
  /// generated.
  ///
  /// Parameters
  ///
  /// * `file: FilePath`
  ///
  ///   The file where the widget instance is created.
  ///
  /// * `offset: int`
  ///
  ///   The offset in the file where the widget instance is created.
  ///
  /// Returns
  ///
  /// * `properties: List<FlutterWidgetProperty>`
  ///
  ///   The list of properties of the widget. Some of the properties might be
  ///   read only, when their `editor` is not set. This might be because they
  ///   have type that we don't know how to edit, or for compound properties
  ///   that work as containers for sub-properties.
  Future<FlutterGetWidgetDescriptionResult> sendFlutterGetWidgetDescription(
    String file,
    int offset,
  ) async {
    var params = FlutterGetWidgetDescriptionParams(
      file,
      offset,
    ).toJson(clientUriConverter: uriConverter);
    var result = await server.send('flutter.getWidgetDescription', params);
    var decoder = ResponseDecoder(null);
    return FlutterGetWidgetDescriptionResult.fromJson(
      decoder,
      'result',
      result,
      clientUriConverter: uriConverter,
    );
  }

  /// Set the value of a property, or remove it.
  ///
  /// The server will generate a change that the client should apply to the
  /// project to get the value of the property set to the new value. The
  /// complexity of the change might be from updating a single literal value in
  /// the code, to updating multiple files to get libraries imported, and new
  /// intermediate widgets instantiated.
  ///
  /// Parameters
  ///
  /// * `id: int`
  ///
  ///   The identifier of the property, previously returned as a part of a
  ///   `FlutterWidgetProperty`.
  ///
  ///   An error of type `FLUTTER_SET_WIDGET_PROPERTY_VALUE_INVALID_ID` is
  ///   generated if the identifier is not valid.
  ///
  /// * `value: FlutterWidgetPropertyValue (optional)`
  ///
  ///   The new value to set for the property.
  ///
  ///   If absent, indicates that the property should be removed. If the
  ///   property corresponds to an optional parameter, the corresponding named
  ///   argument is removed. If the property `isRequired` is true,
  ///   `FLUTTER_SET_WIDGET_PROPERTY_VALUE_IS_REQUIRED` error is generated.
  ///
  ///   If the `expression` is not a syntactically valid Dart code, then
  ///   `FLUTTER_SET_WIDGET_PROPERTY_VALUE_INVALID_EXPRESSION` is reported.
  ///
  /// Returns
  ///
  /// * `change: SourceChange`
  ///
  ///   The change that should be applied.
  Future<FlutterSetWidgetPropertyValueResult> sendFlutterSetWidgetPropertyValue(
    int id, {
    FlutterWidgetPropertyValue? value,
  }) async {
    var params = FlutterSetWidgetPropertyValueParams(
      id,
      value: value,
    ).toJson(clientUriConverter: uriConverter);
    var result = await server.send('flutter.setWidgetPropertyValue', params);
    var decoder = ResponseDecoder(null);
    return FlutterSetWidgetPropertyValueResult.fromJson(
      decoder,
      'result',
      result,
      clientUriConverter: uriConverter,
    );
  }

  /// Subscribe for services that are specific to individual files. All
  /// previous subscriptions are replaced by the current set of subscriptions.
  /// If a given service is not included as a key in the map then no files will
  /// be subscribed to the service, exactly as if the service had been included
  /// in the map with an explicit empty list of files.
  ///
  /// Note that this request determines the set of requested subscriptions. The
  /// actual set of subscriptions at any given time is the intersection of this
  /// set with the set of files currently subject to analysis. The files
  /// currently subject to analysis are the set of files contained within an
  /// actual analysis root but not excluded, plus all of the files transitively
  /// reachable from those files via import, export and part directives. (See
  /// analysis.setAnalysisRoots for an explanation of how the actual analysis
  /// roots are determined.) When the actual analysis roots change, the actual
  /// set of subscriptions is automatically updated, but the set of requested
  /// subscriptions is unchanged.
  ///
  /// If a requested subscription is a directory it is ignored, but remains in
  /// the set of requested subscriptions so that if it later becomes a file it
  /// can be included in the set of actual subscriptions.
  ///
  /// It is an error if any of the keys in the map are not valid services. If
  /// there is an error, then the existing subscriptions will remain unchanged.
  ///
  /// Parameters
  ///
  /// * `subscriptions: Map<FlutterService, List<FilePath>>`
  ///
  ///   A table mapping services to a list of the files being subscribed to the
  ///   service.
  Future<void> sendFlutterSetSubscriptions(
    Map<FlutterService, List<String>> subscriptions,
  ) async {
    var params = FlutterSetSubscriptionsParams(
      subscriptions,
    ).toJson(clientUriConverter: uriConverter);
    var result = await server.send('flutter.setSubscriptions', params);
    outOfTestExpect(result, isNull);
  }

  /// Reports the Flutter outline associated with a single file.
  ///
  /// This notification is not subscribed to by default. Clients can subscribe
  /// by including the value `"OUTLINE"` in the list of services passed in an
  /// flutter.setSubscriptions request.
  ///
  /// Parameters
  ///
  /// * `file: FilePath`
  ///
  ///   The file with which the outline is associated.
  ///
  /// * `outline: FlutterOutline`
  ///
  ///   The outline associated with the file.
  late final Stream<FlutterOutlineParams> onFlutterOutline = _onFlutterOutline
      .stream
      .asBroadcastStream();

  /// Stream controller for [onFlutterOutline].
  final _onFlutterOutline = StreamController<FlutterOutlineParams>(sync: true);

  /// Call an LSP handler. Message can be requests or notifications.
  ///
  /// This request can be called in either direction, either by the client to
  /// the server, or by the server to the client. The server will only call the
  /// client if the client has indicated it supports the associated LSP request
  /// via `lspCapabilities` in the `setClientCapabilities` request.
  ///
  /// Parameters
  ///
  /// * `lspMessage: object`
  ///
  ///   The LSP RequestMessage.
  ///
  /// Returns
  ///
  /// * `lspResponse: object`
  ///
  ///   The LSP ResponseMessage returned by the handler.
  Future<LspHandleResult> sendLspHandle(Object lspMessage) async {
    var params = LspHandleParams(
      lspMessage,
    ).toJson(clientUriConverter: uriConverter);
    var result = await server.send('lsp.handle', params);
    var decoder = ResponseDecoder(null);
    return LspHandleResult.fromJson(
      decoder,
      'result',
      result,
      clientUriConverter: uriConverter,
    );
  }

  /// Reports an LSP notification from the server.
  ///
  /// Parameters
  ///
  /// * `lspNotification: object`
  ///
  ///   The LSP NotificationMessage sent by the server.
  late final Stream<LspNotificationParams> onLspNotification =
      _onLspNotification.stream.asBroadcastStream();

  /// Stream controller for [onLspNotification].
  final _onLspNotification = StreamController<LspNotificationParams>(
    sync: true,
  );

  /// Dispatch the notification named [event], and containing parameters
  /// [params], to the appropriate stream.
  void dispatchNotification(String event, params) {
    var decoder = ResponseDecoder(null);
    switch (event) {
      case 'server.connected':
        outOfTestExpect(params, isServerConnectedParams);
        _onServerConnected.add(
          ServerConnectedParams.fromJson(
            decoder,
            'params',
            params,
            clientUriConverter: uriConverter,
          ),
        );
      case 'server.error':
        outOfTestExpect(params, isServerErrorParams);
        _onServerError.add(
          ServerErrorParams.fromJson(
            decoder,
            'params',
            params,
            clientUriConverter: uriConverter,
          ),
        );
      case 'server.pluginError':
        outOfTestExpect(params, isServerPluginErrorParams);
        _onServerPluginError.add(
          ServerPluginErrorParams.fromJson(
            decoder,
            'params',
            params,
            clientUriConverter: uriConverter,
          ),
        );
      case 'server.log':
        outOfTestExpect(params, isServerLogParams);
        _onServerLog.add(
          ServerLogParams.fromJson(
            decoder,
            'params',
            params,
            clientUriConverter: uriConverter,
          ),
        );
      case 'server.status':
        outOfTestExpect(params, isServerStatusParams);
        _onServerStatus.add(
          ServerStatusParams.fromJson(
            decoder,
            'params',
            params,
            clientUriConverter: uriConverter,
          ),
        );
      case 'analysis.analyzedFiles':
        outOfTestExpect(params, isAnalysisAnalyzedFilesParams);
        _onAnalysisAnalyzedFiles.add(
          AnalysisAnalyzedFilesParams.fromJson(
            decoder,
            'params',
            params,
            clientUriConverter: uriConverter,
          ),
        );
      case 'analysis.closingLabels':
        outOfTestExpect(params, isAnalysisClosingLabelsParams);
        _onAnalysisClosingLabels.add(
          AnalysisClosingLabelsParams.fromJson(
            decoder,
            'params',
            params,
            clientUriConverter: uriConverter,
          ),
        );
      case 'analysis.errors':
        outOfTestExpect(params, isAnalysisErrorsParams);
        _onAnalysisErrors.add(
          AnalysisErrorsParams.fromJson(
            decoder,
            'params',
            params,
            clientUriConverter: uriConverter,
          ),
        );
      case 'analysis.flushResults':
        outOfTestExpect(params, isAnalysisFlushResultsParams);
        _onAnalysisFlushResults.add(
          AnalysisFlushResultsParams.fromJson(
            decoder,
            'params',
            params,
            clientUriConverter: uriConverter,
          ),
        );
      case 'analysis.folding':
        outOfTestExpect(params, isAnalysisFoldingParams);
        _onAnalysisFolding.add(
          AnalysisFoldingParams.fromJson(
            decoder,
            'params',
            params,
            clientUriConverter: uriConverter,
          ),
        );
      case 'analysis.highlights':
        outOfTestExpect(params, isAnalysisHighlightsParams);
        _onAnalysisHighlights.add(
          AnalysisHighlightsParams.fromJson(
            decoder,
            'params',
            params,
            clientUriConverter: uriConverter,
          ),
        );
      case 'analysis.implemented':
        outOfTestExpect(params, isAnalysisImplementedParams);
        _onAnalysisImplemented.add(
          AnalysisImplementedParams.fromJson(
            decoder,
            'params',
            params,
            clientUriConverter: uriConverter,
          ),
        );
      case 'analysis.invalidate':
        outOfTestExpect(params, isAnalysisInvalidateParams);
        _onAnalysisInvalidate.add(
          AnalysisInvalidateParams.fromJson(
            decoder,
            'params',
            params,
            clientUriConverter: uriConverter,
          ),
        );
      case 'analysis.navigation':
        outOfTestExpect(params, isAnalysisNavigationParams);
        _onAnalysisNavigation.add(
          AnalysisNavigationParams.fromJson(
            decoder,
            'params',
            params,
            clientUriConverter: uriConverter,
          ),
        );
      case 'analysis.occurrences':
        outOfTestExpect(params, isAnalysisOccurrencesParams);
        _onAnalysisOccurrences.add(
          AnalysisOccurrencesParams.fromJson(
            decoder,
            'params',
            params,
            clientUriConverter: uriConverter,
          ),
        );
      case 'analysis.outline':
        outOfTestExpect(params, isAnalysisOutlineParams);
        _onAnalysisOutline.add(
          AnalysisOutlineParams.fromJson(
            decoder,
            'params',
            params,
            clientUriConverter: uriConverter,
          ),
        );
      case 'analysis.overrides':
        outOfTestExpect(params, isAnalysisOverridesParams);
        _onAnalysisOverrides.add(
          AnalysisOverridesParams.fromJson(
            decoder,
            'params',
            params,
            clientUriConverter: uriConverter,
          ),
        );
      case 'completion.existingImports':
        outOfTestExpect(params, isCompletionExistingImportsParams);
        _onCompletionExistingImports.add(
          CompletionExistingImportsParams.fromJson(
            decoder,
            'params',
            params,
            clientUriConverter: uriConverter,
          ),
        );
      case 'search.results':
        outOfTestExpect(params, isSearchResultsParams);
        _onSearchResults.add(
          SearchResultsParams.fromJson(
            decoder,
            'params',
            params,
            clientUriConverter: uriConverter,
          ),
        );
      case 'execution.launchData':
        outOfTestExpect(params, isExecutionLaunchDataParams);
        _onExecutionLaunchData.add(
          ExecutionLaunchDataParams.fromJson(
            decoder,
            'params',
            params,
            clientUriConverter: uriConverter,
          ),
        );
      case 'flutter.outline':
        outOfTestExpect(params, isFlutterOutlineParams);
        _onFlutterOutline.add(
          FlutterOutlineParams.fromJson(
            decoder,
            'params',
            params,
            clientUriConverter: uriConverter,
          ),
        );
      case 'lsp.notification':
        outOfTestExpect(params, isLspNotificationParams);
        _onLspNotification.add(
          LspNotificationParams.fromJson(
            decoder,
            'params',
            params,
            clientUriConverter: uriConverter,
          ),
        );
      default:
        fail('Unexpected notification: $event');
    }
  }
}
