33f664940a
Change-Id: I04bc285d822a657adb5573c6de3eb38655ab0fcd Reviewed-on: https://dart-review.googlesource.com/c/sdk/+/448232 Reviewed-by: Brian Wilkerson <brianwilkerson@google.com> Commit-Queue: Samuel Rawlins <srawlins@google.com>
215 lines
6.2 KiB
Dart
215 lines
6.2 KiB
Dart
// Copyright (c) 2018, 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:io';
|
|
|
|
import 'package:analysis_server/protocol/protocol.dart';
|
|
import 'package:analysis_server/src/channel/channel.dart';
|
|
import 'package:analysis_server/src/socket_server.dart';
|
|
import 'package:path/path.dart' as path;
|
|
|
|
/// Instances of the class [DevAnalysisServer] implement a simple analysis
|
|
/// server implementation, used to analyze one or more packages and then
|
|
/// terminate the server.
|
|
class DevAnalysisServer {
|
|
static bool get _terminalSupportsAnsi {
|
|
return stdout.supportsAnsiEscapes &&
|
|
stdioType(stdout) == StdioType.terminal;
|
|
}
|
|
|
|
/// An object that can handle either a WebSocket connection or a connection
|
|
/// to the client over stdio.
|
|
final SocketServer socketServer;
|
|
|
|
int _nextId = 0;
|
|
late DevChannel _channel;
|
|
|
|
/// Initialize a newly created stdio server.
|
|
DevAnalysisServer(this.socketServer);
|
|
|
|
void initServer() {
|
|
_channel = DevChannel();
|
|
socketServer.createAnalysisServer(_channel);
|
|
}
|
|
|
|
/// Analyze the given directories and display any results to stdout.
|
|
///
|
|
/// Return a future that will be completed with an exit code when analysis
|
|
/// finishes.
|
|
Future<int> processDirectories(List<String> directories) async {
|
|
var bold = _terminalSupportsAnsi ? '\u001b[1m' : '';
|
|
var none = _terminalSupportsAnsi ? '\u001b[0m' : '';
|
|
|
|
print('Analyzing ${directories.join(', ')}...');
|
|
|
|
var timer = Stopwatch()..start();
|
|
|
|
var whenComplete = Completer<int>();
|
|
var beganAnalysis = false;
|
|
|
|
var exitCode = 0;
|
|
|
|
void handleStatusNotification(Notification notification) {
|
|
var params = notification.params;
|
|
if (params != null && params.containsKey('analysis')) {
|
|
var isAnalyzing =
|
|
(params['analysis'] as Map<String, Object>)['isAnalyzing'] as bool;
|
|
if (isAnalyzing) {
|
|
beganAnalysis = true;
|
|
} else if (beganAnalysis) {
|
|
beganAnalysis = false;
|
|
timer.stop();
|
|
var seconds = timer.elapsedMilliseconds / 1000.0;
|
|
print('Completed in ${seconds.toStringAsFixed(1)}s.');
|
|
whenComplete.complete(exitCode);
|
|
}
|
|
}
|
|
}
|
|
|
|
void handleErrorsNotification(Notification notification) {
|
|
var params = notification.params!;
|
|
var filePath = params['file'] as String;
|
|
var errors = params['errors'] as List<Map>;
|
|
|
|
if (errors.isEmpty) {
|
|
return;
|
|
}
|
|
|
|
filePath = path.relative(filePath);
|
|
|
|
for (var error in errors) {
|
|
if (error['type'] == 'TODO') {
|
|
continue;
|
|
}
|
|
|
|
var severity = (error['severity'] as String).toLowerCase();
|
|
if (severity == 'warning' && exitCode < 1) {
|
|
exitCode = 1;
|
|
} else if (severity == 'error' && exitCode < 2) {
|
|
exitCode = 2;
|
|
}
|
|
|
|
var message = error['message'] as String;
|
|
if (message.endsWith('.')) {
|
|
message = message.substring(0, message.length - 1);
|
|
}
|
|
var code = error['code'] as String;
|
|
var location = error['location'] as Map<Object?, Object?>;
|
|
var line = location['startLine'] as int;
|
|
var column = location['startColumn'] as int;
|
|
|
|
print(
|
|
' $severity • $bold$message$none at $filePath:$line:$column • '
|
|
'($code)',
|
|
);
|
|
}
|
|
}
|
|
|
|
void handleServerError(Notification notification) {
|
|
var params = notification.params!;
|
|
var message = params['message'] as String;
|
|
var stackTrace = params['stackTrace'] as String?;
|
|
|
|
print(message);
|
|
if (stackTrace != null) {
|
|
print(stackTrace);
|
|
}
|
|
|
|
exitCode = 3;
|
|
|
|
// Ensure we terminate training if we get an exception from the analysis
|
|
// server.
|
|
whenComplete.completeError(message);
|
|
}
|
|
|
|
var notificationSubscriptions = _channel.onNotification.listen((
|
|
Notification notification,
|
|
) {
|
|
if (notification.event == 'server.status') {
|
|
handleStatusNotification(notification);
|
|
} else if (notification.event == 'analysis.errors') {
|
|
handleErrorsNotification(notification);
|
|
} else if (notification.event == 'server.error') {
|
|
handleServerError(notification);
|
|
}
|
|
});
|
|
|
|
await _channel.simulateRequestFromClient(
|
|
Request('${_nextId++}', 'server.setSubscriptions', {
|
|
'subscriptions': ['STATUS'],
|
|
}),
|
|
);
|
|
|
|
directories = directories
|
|
.map((dir) => path.normalize(path.absolute(dir)))
|
|
.toList();
|
|
|
|
await _channel.simulateRequestFromClient(
|
|
Request('${_nextId++}', 'analysis.setAnalysisRoots', {
|
|
'included': directories,
|
|
'excluded': [],
|
|
}),
|
|
);
|
|
|
|
try {
|
|
return await whenComplete.future;
|
|
} finally {
|
|
await notificationSubscriptions.cancel();
|
|
|
|
await _channel.simulateRequestFromClient(
|
|
Request('${_nextId++}', 'analysis.setAnalysisRoots', {
|
|
'included': [],
|
|
'excluded': [],
|
|
}),
|
|
);
|
|
}
|
|
}
|
|
}
|
|
|
|
class DevChannel implements ServerCommunicationChannel {
|
|
final StreamController<RequestOrResponse> _requestController =
|
|
StreamController.broadcast();
|
|
|
|
final StreamController<Notification> _notificationController =
|
|
StreamController.broadcast();
|
|
|
|
final Map<String, Completer<Response>> _responseCompleters = {};
|
|
|
|
Stream<Notification> get onNotification => _notificationController.stream;
|
|
|
|
@override
|
|
Stream<RequestOrResponse> get requests => _requestController.stream;
|
|
|
|
@override
|
|
void close() {
|
|
_notificationController.close();
|
|
}
|
|
|
|
@override
|
|
void sendNotification(Notification notification) {
|
|
_notificationController.add(notification);
|
|
}
|
|
|
|
@override
|
|
void sendRequest(Request request) {
|
|
throw UnimplementedError(
|
|
'sendRequest (did you mean simulateRequestFromClient?)',
|
|
);
|
|
}
|
|
|
|
@override
|
|
void sendResponse(Response response) {
|
|
var completer = _responseCompleters.remove(response.id);
|
|
completer?.complete(response);
|
|
}
|
|
|
|
Future<Response> simulateRequestFromClient(Request request) {
|
|
var completer = Completer<Response>();
|
|
_responseCompleters[request.id] = completer;
|
|
_requestController.add(request);
|
|
return completer.future;
|
|
}
|
|
}
|