afcfbbeba8
(Part of https://github.com/dart-lang/sdk/issues/63288) This change migrates the packages owned by the developer experience team to use the new constructor declaration syntax, described in https://github.com/dart-lang/language/blob/main/accepted/future-releases/primary-constructors/feature-specification.md#abbreviations-of-in-body-constructor-declarations. This change was performed in an automated fashion, by (a) bumping the packages' SDK constraints to `3.13.0-0`, (b) enabling the lints `unnecessary_type_name_in_constructor` and `unnecessary_const_in_enum_constructor`, (c) fixing the resulting lint failures using `dart fix`, and then (d) reformatting the affected files. To ease code review, I've reverted unrelated formatting changes. Since this change requires bumping SDK constaints to `3.13.0-0`, it was only performed on packages that are *not* published on pub. (Packages that *are* published on pub should remain on lower language versions until at least after the stable version of 3.13 is released, so that we don't block users on the stable channel from receiving updates to those packages.) Change-Id: Ibb4daebafd239da58251e838ea6a3f336a6a6964 Reviewed-on: https://dart-review.googlesource.com/c/sdk/+/505046 Commit-Queue: Paul Berry <paulberry@google.com> Reviewed-by: Brian Wilkerson <brianwilkerson@google.com> SLSA-Policy-Verified: SLSA Policy Verification Service <devtools-gerritcodereview-exitgate@google.com>
227 lines
7.5 KiB
Dart
227 lines
7.5 KiB
Dart
// Copyright (c) 2025, the Dart project authors. Please see the AUTHORS file
|
|
// for details. All rights reserved. Use of this source code is governed by a
|
|
// BSD-style license that can be found in the LICENSE file.
|
|
|
|
import 'dart:async';
|
|
import 'dart:convert';
|
|
import 'dart:io';
|
|
|
|
import 'package:analysis_server/lsp_protocol/protocol.dart' show jsonRpcVersion;
|
|
import 'package:analysis_server/src/lsp/lsp_packet_transformer.dart';
|
|
import 'package:analysis_server/src/server/driver.dart';
|
|
import 'package:analysis_server/src/session_logger/log_entry.dart';
|
|
|
|
/// The driver used to communicate with the analysis server.
|
|
class ServerDriver {
|
|
/// The protocol being used by the server.
|
|
final ServerProtocol _protocol;
|
|
|
|
/// A list of arguments from the command-line used to start the server.
|
|
final List<String> arguments;
|
|
|
|
/// The sink used to send messages from the IDE to the server's stdin, or
|
|
/// `null` if the server has not been started using [start].
|
|
IOSink? _stdinSink;
|
|
|
|
/// The socket used to send messages from DTD to the server, or `null` if the
|
|
/// server has not been connected to DTD using [connectToDtd].
|
|
WebSocket? _dtdSocket;
|
|
|
|
/// Stream controller for analysis server output messages.
|
|
final StreamController<Message> _serverMessagesController =
|
|
StreamController();
|
|
|
|
/// Creates a new driver that can be used to communicate with a server.
|
|
///
|
|
/// When the server is [start]ed, it will use the given [protocol].
|
|
///
|
|
/// The server is run in a separate process.
|
|
// TODO(brianwilkerson): Add a flag controlling whether the server is in the
|
|
// same process as the driver or in a separate process.
|
|
factory({required List<String> arguments}) {
|
|
var parsedArgs = Driver.createArgParser().parse(arguments);
|
|
|
|
var protocolOption = parsedArgs.option(Driver.serverProtocolOption);
|
|
var protocol = switch (protocolOption) {
|
|
Driver.protocolAnalyzer => ServerProtocol.legacy,
|
|
Driver.protocolLsp => ServerProtocol.lsp,
|
|
null => throw StateError('No protocol specified'),
|
|
_ => throw StateError('Unrecognized protocol $protocolOption'),
|
|
};
|
|
|
|
var useFineDependencies = parsedArgs.wasParsed(
|
|
Driver.withFineDependenciesOption,
|
|
);
|
|
|
|
return ServerDriver._(
|
|
arguments: [
|
|
'--${Driver.serverProtocolOption}=${protocol.flagValue}',
|
|
if (useFineDependencies) '--${Driver.withFineDependenciesOption}',
|
|
],
|
|
protocol: protocol,
|
|
);
|
|
}
|
|
|
|
/// Creates a new driver that can be used to communicate with a server.
|
|
///
|
|
/// When the server is [start]ed, it will use the given [_protocol].
|
|
///
|
|
/// The server is run in a separate process.
|
|
new _({required this.arguments, required this._protocol});
|
|
|
|
/// The messages read from the analysis server's stdout.
|
|
Stream<Message> get serverMessages => _serverMessagesController.stream;
|
|
|
|
/// Returns the path to the `dart` executable.
|
|
String get _dartExecutable {
|
|
return Platform.resolvedExecutable;
|
|
}
|
|
|
|
/// Create a websocket through which DTD messages can be sent to the server
|
|
/// and send a request to the server asking it to connect to the socket.
|
|
// ignore: unnecessary_async
|
|
Future<void> connectToDtd() async {
|
|
// TODO(brianwilkerson): Implement this.
|
|
throw UnimplementedError();
|
|
// var socketUri = 'ws://';
|
|
// _dtdSocket = await WebSocket.connect(socketUri);
|
|
}
|
|
|
|
/// Send an exit request to the server, then close the communication channels
|
|
/// used to communicate with the server.
|
|
void exit() {
|
|
if (_protocol == ServerProtocol.lsp) {
|
|
sendMessageFromIde(
|
|
Message({
|
|
'id': 0,
|
|
'method': 'exit',
|
|
'jsonrpc': jsonRpcVersion,
|
|
'clientRequestTime': DateTime.now().millisecondsSinceEpoch,
|
|
}),
|
|
);
|
|
}
|
|
_stdinSink?.close();
|
|
_stdinSink = null;
|
|
_dtdSocket?.close();
|
|
_dtdSocket = null;
|
|
_serverMessagesController.close();
|
|
}
|
|
|
|
void sendMessageFromDTD(Message message) {
|
|
if (_dtdSocket case var socket?) {
|
|
socket.add(json.encode(message));
|
|
} else {
|
|
throw StateError(
|
|
"The method 'connectToDtd' must be invoked before "
|
|
'messages can be sent from DTD.',
|
|
);
|
|
}
|
|
}
|
|
|
|
void sendMessageFromFileWatcher(Message message) {
|
|
// TODO(brianwilkerson): Implementing this will require some additional
|
|
// support in the server. The most likely approach is to add support for a
|
|
// new protocol that will fake receiving a watch event.
|
|
}
|
|
|
|
/// Send the given [message] to the server using the communication channel
|
|
/// used by the IDE.
|
|
void sendMessageFromIde(Message message) {
|
|
if (_stdinSink case IOSink writeSink) {
|
|
var jsonEncodedBody = jsonEncode(message);
|
|
if (_protocol == ServerProtocol.lsp) {
|
|
var utf8EncodedBody = utf8.encode(jsonEncodedBody);
|
|
var header =
|
|
'Content-Length: ${utf8EncodedBody.length}\r\n'
|
|
'Content-Type: application/vscode-jsonrpc; charset=utf-8\r\n\r\n';
|
|
var asciiEncodedHeader = ascii.encode(header);
|
|
|
|
// Header is always ascii, body is always utf8!
|
|
writeSink.add(asciiEncodedHeader);
|
|
writeSink.add(utf8EncodedBody);
|
|
} else {
|
|
writeSink.write(jsonEncodedBody);
|
|
}
|
|
} else {
|
|
throw StateError(
|
|
"The method 'start' must be invoked before "
|
|
'messages can be sent from the IDE.',
|
|
);
|
|
}
|
|
}
|
|
|
|
void sendMessageFromPluginIsolate(Message message) {
|
|
// TODO(brianwilkerson): Implementing this will require some additional
|
|
// support in the server. Two possibilities to consider:
|
|
// 1. Add a protocol to fake receiving a messsage from the plugin isolate.
|
|
// 2. Add a protocol to connect to a web socket as if it were a connection
|
|
// to a plugin isolate.
|
|
// The first is likely to be the easier path forward.
|
|
}
|
|
|
|
/// Send a shutdown request to the server.
|
|
void shutdown() {
|
|
if (_protocol == ServerProtocol.legacy) {
|
|
sendMessageFromIde(
|
|
Message({
|
|
'id': 0,
|
|
'method': 'server.shutdown',
|
|
'clientRequestTime': DateTime.now().millisecondsSinceEpoch,
|
|
}),
|
|
);
|
|
} else if (_protocol == ServerProtocol.lsp) {
|
|
sendMessageFromIde(
|
|
Message({
|
|
'id': 0,
|
|
'method': 'shutdown',
|
|
'jsonrpc': jsonRpcVersion,
|
|
'clientRequestTime': DateTime.now().millisecondsSinceEpoch,
|
|
}),
|
|
);
|
|
}
|
|
}
|
|
|
|
/// Create and start the server.
|
|
Future<void> start() async {
|
|
if (_stdinSink != null) {
|
|
throw StateError(
|
|
'Analysis server already started, only one active instance is allowed '
|
|
'at a time.',
|
|
);
|
|
}
|
|
var process = await Process.start(_dartExecutable, [
|
|
'language-server',
|
|
'--suppress-analytics',
|
|
...arguments,
|
|
]);
|
|
_stdinSink = process.stdin;
|
|
if (_protocol == ServerProtocol.lsp) {
|
|
process.stdout
|
|
.transform(LspPacketTransformer())
|
|
.listen(_receiveMessageFromServer);
|
|
} else {
|
|
process.stdout
|
|
.transform(const Utf8Decoder())
|
|
.transform(const LineSplitter())
|
|
.listen(_receiveMessageFromServer);
|
|
}
|
|
}
|
|
|
|
void _receiveMessageFromServer(String message) {
|
|
if (_serverMessagesController.isClosed) {
|
|
stderr.writeln('Got analysis server message after shutdown:\n$message');
|
|
} else {
|
|
_serverMessagesController.add(jsonDecode(message) as Message);
|
|
}
|
|
}
|
|
}
|
|
|
|
/// An indication of the protocol to be used when communicating with the server.
|
|
enum ServerProtocol {
|
|
legacy('analyzer'),
|
|
lsp('lsp');
|
|
|
|
final String flagValue;
|
|
new(this.flagValue);
|
|
}
|