Add analysis_server_client without analysis_server.

BUG=
R=brianwilkerson@google.com, scheglov@google.com

Review-Url: https://codereview.chromium.org/2994723003 .
This commit is contained in:
Max Kim 2017-08-08 14:57:38 -07:00
parent 91662ce579
commit 4e42dbc1f9
7 changed files with 252 additions and 0 deletions

View file

@ -7,6 +7,7 @@
# Please update this file if you add a package to DEPS or /pkg
#
analysis_server:pkg/analysis_server/lib
analysis_server_client:pkg/analysis_server_client/lib
analyzer:pkg/analyzer/lib
analyzer_cli:pkg/analyzer_cli/lib
analyzer_plugin:pkg/analyzer_plugin/lib

View file

@ -0,0 +1,5 @@
#1.0.1
* Update path on homepage
#1.0.0
* Initial version

View file

@ -0,0 +1,26 @@
Copyright 2017, the Dart project authors. All rights reserved.
Redistribution and use in source and binary forms, with or without
modification, are permitted provided that the following conditions are
met:
* Redistributions of source code must retain the above copyright
notice, this list of conditions and the following disclaimer.
* Redistributions in binary form must reproduce the above
copyright notice, this list of conditions and the following
disclaimer in the documentation and/or other materials provided
with the distribution.
* Neither the name of Google Inc. nor the names of its
contributors may be used to endorse or promote products derived
from this software without specific prior written permission.
THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS
"AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT
LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR
A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT
OWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL,
SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT
LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE,
DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY
THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT
(INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE
OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.

View file

@ -0,0 +1,13 @@
# Analysis Server Client
A client wrapper over Analysis Server. Instances of this client manages
connection to Analysis Server and process and faciliates JSON protocol
communication to and from the server.
Current implementation has no knowledge of the Analysis Server library yet.
Future updates will allow for full class-access of Analysis Server protocol
objects.
Analysis Server process must be instantiated separately and loaded into
Analysis Server Client. To learn how to generate an Analysis Server Process,
refer to the [Analysis Server page.](https://github.com/dart-lang/sdk/tree/master/pkg/analysis_server)

View file

@ -0,0 +1,101 @@
// 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.
import 'dart:async';
import 'dart:convert';
import 'dart:io';
/// Type of callbacks used to process notification.
typedef void NotificationProcessor(String event, Map<String, Object> params);
/// Instances of the class [AnalysisServerClient] manage a connection to an
/// [AnalysisServer] process, and facilitate communication to and from the
/// client/user.
class AnalysisServerClient {
/// AnalysisServer process object, or null if the server has been shut down.
final Process _process;
/// Commands that have been sent to the server but not yet acknowledged,
/// and the [Completer] objects which should be completed when
/// acknowledgement is received.
final Map<String, Completer> _pendingCommands = <String, Completer>{};
/// Number which should be used to compute the 'id' to send to the next
/// command sent to the server.
int _nextId = 0;
AnalysisServerClient(this._process);
/// Return a future that will complete when all commands that have been
/// sent to the server so far have been flushed to the OS buffer.
Future<Null> flushCommands() {
return _process.stdin.flush();
}
void listenToOutput({NotificationProcessor notificationProcessor}) {
_process.stdout
.transform((new Utf8Codec()).decoder)
.transform(new LineSplitter())
.listen((String line) {
String trimmedLine = line.trim();
if (trimmedLine.startsWith('Observatory listening on ')) {
return;
}
final result = JSON.decoder.convert(trimmedLine) as Map;
if (result.containsKey('id')) {
final id = result['id'] as String;
final completer = _pendingCommands.remove(id);
if (result.containsKey('error')) {
completer.completeError(new ServerErrorMessage(result['error']));
} else {
completer.complete(result['result']);
}
} else if (notificationProcessor != null && result.containsKey('event')) {
// Message is a notification. It should have an event and possibly
// params.
notificationProcessor(result['event'], result['params']);
}
});
}
/// Sends a command to the server. An 'id' will be automatically assigned.
/// The returned [Future] will be completed when the server acknowledges
/// the command with a response. If the server acknowledges the command
/// with a normal (non-error) response, the future will be completed
/// with the 'result' field from the response. If the server acknowledges
/// the command with an error response, the future will be completed with an
/// error.
Future send(String method, Map<String, dynamic> params) {
String id = '${_nextId++}';
Map<String, dynamic> command = <String, dynamic>{
'id': id,
'method': method
};
if (params != null) {
command['params'] = params;
}
Completer completer = new Completer();
_pendingCommands[id] = completer;
String commandAsJson = JSON.encode(command);
_process.stdin.add(UTF8.encoder.convert('$commandAsJson\n'));
return completer.future;
}
/// Force kill the server. Returns exit code future.
Future<int> kill() {
_process.kill();
return _process.exitCode;
}
}
class ServerErrorMessage {
final Map errorJson;
ServerErrorMessage(this.errorJson);
String get code => errorJson['code'].toString();
String get message => errorJson['message'];
String get stackTrace => errorJson['stackTrace'];
}

View file

@ -0,0 +1,13 @@
name: analysis_server_client
version: 1.0.1
author: Dart Team <misc@dartlang.org>
description:
A client wrapper over analysis_server. Instances of this client
manage a connection to the analysis_server process and
facilitates communication to and from the server.
homepage: http://github.com/dart-lang/sdk/pkg/analysis_server_client
dev_dependencies:
test: ">=0.12.0 <0.13.0"
mockito: ^2.0.2
environment:
sdk: ">=1.0.0 < 2.0.0-dev.infinity"

View file

@ -0,0 +1,93 @@
// 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.
import 'dart:async';
import 'dart:convert';
import 'dart:io';
import 'package:analysis_server_client/analysis_server_client.dart';
import 'package:mockito/mockito.dart';
import 'package:test/test.dart';
void main() {
Process _process;
AnalysisServerClient serverWrapper;
setUp(() async {
_process = new MockProcess();
serverWrapper = new AnalysisServerClient(_process);
when(_process.stdin).thenReturn(<int>[]);
});
test('test_listenToOutput_good', () async {
when(_process.stdout).thenReturn(_goodMessage());
final future = serverWrapper.send('blahMethod', null);
serverWrapper.listenToOutput();
final response = await future;
expect(response, new isInstanceOf<Map>());
final responseAsMap = response as Map;
expect(responseAsMap['foo'], 'bar');
});
test('test_listenToOutput_error', () async {
when(_process.stdout).thenReturn(_badMessage());
final future = serverWrapper.send('blahMethod', null);
future.catchError((e) {
expect(e, new isInstanceOf<ServerErrorMessage>());
final e2 = e as ServerErrorMessage;
expect(e2.code, 'someErrorCode');
expect(e2.message, 'something went wrong');
expect(e2.stackTrace, 'some long stack trace');
});
serverWrapper.listenToOutput();
});
test('test_listenToOutput_event', () async {
when(_process.stdout).thenReturn(_eventMessage());
void eventHandler(String event, Map<String, Object> params) {
expect(event, 'fooEvent');
expect(params.length, 2);
expect(params['foo'] as String, 'bar');
expect(params['baz'] as String, 'bang');
}
serverWrapper.send('blahMethod', null);
serverWrapper.listenToOutput(notificationProcessor: eventHandler);
});
}
Stream<List<int>> _goodMessage() async* {
yield UTF8.encoder.convert('Observatory listening on foo bar\n');
final sampleJson = {
'id': '0',
'result': {'foo': 'bar'}
};
yield UTF8.encoder.convert(JSON.encode(sampleJson));
}
final _badErrorMessage = {
'code': 'someErrorCode',
'message': 'something went wrong',
'stackTrace': 'some long stack trace'
};
Stream<List<int>> _badMessage() async* {
yield UTF8.encoder.convert('Observatory listening on foo bar\n');
final sampleJson = {'id': '0', 'error': _badErrorMessage};
yield UTF8.encoder.convert(JSON.encode(sampleJson));
}
Stream<List<int>> _eventMessage() async* {
yield UTF8.encoder.convert('Observatory listening on foo bar\n');
final sampleJson = {
'event': 'fooEvent',
'params': {'foo': 'bar', 'baz': 'bang'}
};
yield UTF8.encoder.convert(JSON.encode(sampleJson));
}
class MockProcess extends Mock implements Process {}