Revert "Update dataprotocol client" (#500)

* Revert "Fix #494 Connection error when connecting to an Azure SQL Server with no firewall rule (#497)"

This reverts commit edd867b6fc.

* Revert "Update dataprotocol client (#418)"

This reverts commit 7808496416.
This commit is contained in:
Anthony Dresser
2018-01-16 15:55:31 -08:00
committed by GitHub
parent edd867b6fc
commit 0cc7c540a9
110 changed files with 11677 additions and 3904 deletions

View File

@@ -0,0 +1,24 @@
{
"rules": {
"indent": [
2,
"tab"
],
"quotes": [
2,
"single"
],
"linebreak-style": [
2,
"windows"
],
"semi": [
2,
"always"
]
},
"env": {
"node": true
},
"extends": "eslint:recommended"
}

View File

@@ -0,0 +1,9 @@
.vscode/
lib/test/
lib/*.map
src/
test/
.eslintrc
.gitignore
gulpfile.js
tsd.json

View File

@@ -0,0 +1,32 @@
{
"version": "0.1.0",
// List of configurations. Add new configurations or edit existing ones.
// ONLY "node" and "mono" are supported, change "type" to switch.
"configurations": [
{
"request": "launch",
// Name of configuration; appears in the launch configuration drop down menu.
"name": "Mocha",
// Type of configuration. Possible values: "node", "mono".
"type": "node",
// Workspace relative or absolute path to the program.
"program": "${workspaceRoot}/node_modules/mocha/bin/_mocha",
// Automatically stop program after launch.
"stopOnEntry": false,
// Command line arguments passed to the program.
"args": ["--timeout", "999999"],
// Workspace relative or absolute path to the working directory of the program being debugged. Default is the current workspace.
"cwd": "${workspaceRoot}",
// Workspace relative or absolute path to the runtime executable to be used. Default is the runtime executable on the PATH.
"runtimeExecutable": null,
// Optional arguments passed to the runtime executable.
"runtimeArgs": [],
// Environment variables passed to the program.
"env": { },
// Use JavaScript source maps (if they exist).
"sourceMaps": true,
// If JavaScript source maps are enabled, the generated code is expected in this directory.
"outDir": "${workspaceRoot}/lib"
}
]
}

View File

@@ -0,0 +1,9 @@
// Place your settings in this file to overwrite default and user settings.
{
"javascript.validate.enable": false,
"files.trimTrailingWhitespace": true,
"eslint.enable": false,
"editor.insertSpaces": false,
"editor.tabSize": 4,
"typescript.tsdk": "./node_modules/typescript/lib"
}

View File

@@ -0,0 +1,9 @@
{
"version": "0.1.0",
"command": "npm",
"isShellCommand": true,
"args": ["run", "watch"],
"showOutput": "silent",
"isWatching": true,
"problemMatcher": "$tsc-watch"
}

View File

@@ -0,0 +1,4 @@
# Microsoft Data Management Protocol - Node
## License
[MIT](https://github.com/Microsoft/carbon/blob/dev/license.txt)

View File

@@ -0,0 +1,29 @@
{
"name": "dataprotocol-jsonrpc",
"description": "A json rpc implementation over streams",
"version": "2.4.0",
"author": "Microsoft Corporation",
"license": "MIT",
"repository": {
"type": "git",
"url": "https://github.com/Microsoft/vscode-languageserver-node.git"
},
"bugs": {
"url": "https://github.com/Microsoft/vscode-languageserver-node/issues"
},
"engines": {
"node": ">=4.0.0 || >=6.0.0"
},
"main": "./lib/main.js",
"typings": "./lib/main",
"devDependencies": {
"mocha": "^3.0.2",
"typescript": "2.0.3"
},
"scripts": {
"prepublish": "tsc -p ./src",
"compile": "tsc -p ./src",
"watch": "tsc -w -p ./src",
"test": "mocha"
}
}

View File

@@ -0,0 +1,97 @@
/*---------------------------------------------------------------------------------------------
* Copyright (c) Microsoft Corporation. All rights reserved.
* Licensed under the Source EULA. See License.txt in the project root for license information.
*--------------------------------------------------------------------------------------------*/
'use strict';
import { Event, Emitter } from './events';
export interface CancellationToken {
/**
* Is `true` when the token has been cancelled, `false` otherwise.
*/
isCancellationRequested: boolean;
/**
* An [event](#Event) which fires upon cancellation.
*/
onCancellationRequested: Event<any>;
}
export namespace CancellationToken {
export const None: CancellationToken = Object.freeze({
isCancellationRequested: false,
onCancellationRequested: Event.None
});
export const Cancelled: CancellationToken = Object.freeze({
isCancellationRequested: true,
onCancellationRequested: Event.None
});
}
const shortcutEvent: Event<any> = Object.freeze(function (callback, context?) {
let handle = setTimeout(callback.bind(context), 0);
return { dispose() { clearTimeout(handle); } };
});
class MutableToken implements CancellationToken {
private _isCancelled: boolean = false;
private _emitter: Emitter<any>;
public cancel() {
if (!this._isCancelled) {
this._isCancelled = true;
if (this._emitter) {
this._emitter.fire(undefined);
this._emitter = undefined;
}
}
}
get isCancellationRequested(): boolean {
return this._isCancelled;
}
get onCancellationRequested(): Event<any> {
if (this._isCancelled) {
return shortcutEvent;
}
if (!this._emitter) {
this._emitter = new Emitter<any>();
}
return this._emitter.event;
}
}
export class CancellationTokenSource {
private _token: CancellationToken;
get token(): CancellationToken {
if (!this._token) {
// be lazy and create the token only when
// actually needed
this._token = new MutableToken();
}
return this._token;
}
cancel(): void {
if (!this._token) {
// save an object by returning the default
// cancelled token when cancellation happens
// before someone asks for the token
this._token = CancellationToken.Cancelled;
} else {
(<MutableToken>this._token).cancel();
}
}
dispose(): void {
this.cancel();
}
}

View File

@@ -0,0 +1,213 @@
/* --------------------------------------------------------------------------------------------
* Copyright (c) Microsoft Corporation. All rights reserved.
* Licensed under the Source EULA. See License.txt in the project root for license information.
* ------------------------------------------------------------------------------------------ */
'use strict';
export interface Disposable {
/**
* Dispose this object.
*/
dispose();
}
/**
* Represents a typed event.
*/
export interface Event<T> {
/**
*
* @param listener The listener function will be call when the event happens.
* @param thisArgs The 'this' which will be used when calling the event listener.
* @param disposables An array to which a {{IDisposable}} will be added. The
* @return
*/
(listener: (e: T) => any, thisArgs?: any, disposables?: Disposable[]): Disposable;
}
export namespace Event {
const _disposable = { dispose() { } };
export const None: Event<any> = function () { return _disposable; };
}
/**
* Represents a type which can release resources, such
* as event listening or a timer.
*/
class DisposableImpl implements Disposable {
/**
* Combine many disposable-likes into one. Use this method
* when having objects with a dispose function which are not
* instances of Disposable.
*
* @return Returns a new disposable which, upon dispose, will
* dispose all provides disposable-likes.
*/
static from(...disposables: { dispose(): any }[]): DisposableImpl {
return new DisposableImpl(function () {
if (disposables) {
for (let disposable of disposables) {
disposable.dispose();
}
disposables = undefined;
}
});
}
private _callOnDispose: Function;
constructor(callOnDispose: Function) {
this._callOnDispose = callOnDispose;
}
/**
* Dispose this object.
*/
dispose(): any {
if (typeof this._callOnDispose === 'function') {
this._callOnDispose();
this._callOnDispose = undefined;
}
}
}
class CallbackList {
private _callbacks: Function[];
private _contexts: any[];
public add(callback: Function, context: any = null, bucket?: Disposable[]): void {
if (!this._callbacks) {
this._callbacks = [];
this._contexts = [];
}
this._callbacks.push(callback);
this._contexts.push(context);
if (Array.isArray(bucket)) {
bucket.push({ dispose: () => this.remove(callback, context) });
}
}
public remove(callback: Function, context: any = null): void {
if (!this._callbacks) {
return;
}
var foundCallbackWithDifferentContext = false;
for (var i = 0, len = this._callbacks.length; i < len; i++) {
if (this._callbacks[i] === callback) {
if (this._contexts[i] === context) {
// callback & context match => remove it
this._callbacks.splice(i, 1);
this._contexts.splice(i, 1);
return;
} else {
foundCallbackWithDifferentContext = true;
}
}
}
if (foundCallbackWithDifferentContext) {
throw new Error('When adding a listener with a context, you should remove it with the same context');
}
}
public invoke(...args: any[]): any[] {
if (!this._callbacks) {
return;
}
var ret: any[] = [],
callbacks = this._callbacks.slice(0),
contexts = this._contexts.slice(0);
for (var i = 0, len = callbacks.length; i < len; i++) {
try {
ret.push(callbacks[i].apply(contexts[i], args));
} catch (e) {
console.error(e);
}
}
return ret;
}
public isEmpty(): boolean {
return !this._callbacks || this._callbacks.length === 0;
}
public dispose(): void {
this._callbacks = undefined;
this._contexts = undefined;
}
}
export interface EmitterOptions {
onFirstListenerAdd?: Function;
onLastListenerRemove?: Function;
}
export class Emitter<T> {
private static _noop = function () { };
private _event: Event<T>;
private _callbacks: CallbackList;
constructor(private _options?: EmitterOptions) {
}
/**
* For the public to allow to subscribe
* to events from this Emitter
*/
get event(): Event<T> {
if (!this._event) {
this._event = (listener: (e: T) => any, thisArgs?: any, disposables?: Disposable[]) => {
if (!this._callbacks) {
this._callbacks = new CallbackList();
}
if (this._options && this._options.onFirstListenerAdd && this._callbacks.isEmpty()) {
this._options.onFirstListenerAdd(this);
}
this._callbacks.add(listener, thisArgs);
let result: Disposable;
result = {
dispose: () => {
this._callbacks.remove(listener, thisArgs);
result.dispose = Emitter._noop;
if (this._options && this._options.onLastListenerRemove && this._callbacks.isEmpty()) {
this._options.onLastListenerRemove(this);
}
}
};
if (Array.isArray(disposables)) {
disposables.push(result);
}
return result;
};
}
return this._event;
}
/**
* To be kept private to fire an event to
* subscribers
*/
fire(event: T): any {
if (this._callbacks) {
this._callbacks.invoke.call(this._callbacks, event);
}
}
dispose() {
if (this._callbacks) {
this._callbacks.dispose();
this._callbacks = undefined;
}
}
}

View File

@@ -0,0 +1,47 @@
/* --------------------------------------------------------------------------------------------
* Copyright (c) Microsoft Corporation. All rights reserved.
* Licensed under the Source EULA. See License.txt in the project root for license information.
* ------------------------------------------------------------------------------------------ */
'use strict';
const toString = Object.prototype.toString;
export function defined(value: any): boolean {
return typeof value !== 'undefined';
}
export function undefined(value: any): boolean {
return typeof value === 'undefined';
}
export function nil(value: any): boolean {
return value === null;
}
export function boolean(value: any): value is boolean {
return value === true || value === false;
}
export function string(value: any): value is string {
return toString.call(value) === '[object String]';
}
export function number(value: any): value is number {
return toString.call(value) === '[object Number]';
}
export function error(value: any): value is Error {
return toString.call(value) === '[object Error]';
}
export function func(value: any): value is Function {
return toString.call(value) === '[object Function]';
}
export function array<T>(value: any): value is T[] {
return Array.isArray(value);
}
export function stringArray(value: any): value is string[] {
return array(value) && (<any[]>value).every(elem => string(elem));
}

View File

@@ -0,0 +1,576 @@
/* --------------------------------------------------------------------------------------------
* Copyright (c) Microsoft Corporation. All rights reserved.
* Licensed under the Source EULA. See License.txt in the project root for license information.
* ------------------------------------------------------------------------------------------ */
'use strict';
import * as is from './is';
import {
Message,
RequestMessage, RequestType, isRequestMessage,
ResponseMessage, isReponseMessage, ResponseError, ErrorCodes,
NotificationMessage, NotificationType, isNotificationMessage
} from './messages';
import { MessageReader, DataCallback, StreamMessageReader, IPCMessageReader } from './messageReader';
import { MessageWriter, StreamMessageWriter, IPCMessageWriter } from './messageWriter';
import { Disposable, Event, Emitter } from './events';
import { CancellationTokenSource, CancellationToken } from './cancellation';
export {
Message, ErrorCodes, ResponseError,
RequestMessage, RequestType,
NotificationMessage, NotificationType,
MessageReader, DataCallback, StreamMessageReader, IPCMessageReader,
MessageWriter, StreamMessageWriter, IPCMessageWriter,
CancellationTokenSource, CancellationToken,
Disposable, Event, Emitter
}
interface CancelParams {
/**
* The request id to cancel.
*/
id: number | string;
}
namespace CancelNotification {
export const type: NotificationType<CancelParams> = { get method() { return '$/cancelRequest'; } };
}
export interface RequestHandler<P, R, E> {
(params: P, token: CancellationToken): R | ResponseError<E> | Thenable<R | ResponseError<E>>;
}
export interface NotificationHandler<P> {
(params: P): void;
}
export interface Logger {
error(message: string): void;
warn(message: string): void;
info(message: string): void;
log(message: string): void;
}
export enum Trace {
Off, Messages, Verbose
}
export type TraceValues = 'off' | 'messages' | 'verbose';
export namespace Trace {
export function fromString(value: string): Trace {
value = value.toLowerCase();
switch (value) {
case 'off':
return Trace.Off;
case 'messages':
return Trace.Messages;
case 'verbose':
return Trace.Verbose;
default:
return Trace.Off;
}
}
export function toString(value: Trace): TraceValues {
switch (value) {
case Trace.Off:
return 'off';
case Trace.Messages:
return 'messages';
case Trace.Verbose:
return 'verbose';
default:
return 'off';
}
}
}
export interface SetTraceParams {
value: TraceValues;
}
export namespace SetTraceNotification {
export const type: NotificationType<SetTraceParams> = { get method() { return '$/setTraceNotification'; } };
}
export interface LogTraceParams {
message: string;
verbose?: string;
}
export namespace LogTraceNotification {
export const type: NotificationType<LogTraceParams> = { get method() { return '$/logTraceNotification'; } };
}
export interface Tracer {
log(message: string, data?: string): void;
}
export interface MessageConnection {
sendRequest<P, R, E>(type: RequestType<P, R, E>, params: P, token?: CancellationToken): Thenable<R>;
onRequest<P, R, E>(type: RequestType<P, R, E>, handler: RequestHandler<P, R, E>): void;
sendNotification<P>(type: NotificationType<P>, params?: P): void;
onNotification<P>(type: NotificationType<P>, handler: NotificationHandler<P>): void;
trace(value: Trace, tracer: Tracer, sendNotification?: boolean): void;
onError: Event<[Error, Message, number]>;
onClose: Event<void>;
onUnhandledNotification: Event<NotificationMessage>;
listen();
onDispose: Event<void>;
dispose(): void;
}
export interface ServerMessageConnection extends MessageConnection {
}
export interface ClientMessageConnection extends MessageConnection {
}
interface ResponsePromise {
method: string;
timerStart: number;
resolve: (response) => void;
reject: (error: any) => void
}
enum ConnectionState {
New = 1,
Listening = 2,
Closed = 3,
Disposed = 4
}
function createMessageConnection<T extends MessageConnection>(messageReader: MessageReader, messageWriter: MessageWriter, logger: Logger, client: boolean = false): T {
let sequenceNumber = 0;
const version: string = '2.0';
let requestHandlers: { [name: string]: RequestHandler<any, any, any> } = Object.create(null);
let eventHandlers: { [name: string]: NotificationHandler<any> } = Object.create(null);
let responsePromises: { [name: string]: ResponsePromise } = Object.create(null);
let requestTokens: { [id: string]: CancellationTokenSource } = Object.create(null);
let trace: Trace = Trace.Off;
let tracer: Tracer;
let state: ConnectionState = ConnectionState.New;
let errorEmitter: Emitter<[Error, Message, number]> = new Emitter<[Error, Message, number]>();
let closeEmitter: Emitter<void> = new Emitter<void>();
let unhandledNotificationEmitter: Emitter<NotificationMessage> = new Emitter<NotificationMessage>();
let disposeEmitter: Emitter<void> = new Emitter<void>();
function isListening(): boolean {
return state === ConnectionState.Listening;
}
function isClosed(): boolean {
return state === ConnectionState.Closed;
}
function isDisposed(): boolean {
return state === ConnectionState.Disposed;
}
function closeHandler(): void {
if (state === ConnectionState.New || state === ConnectionState.Listening) {
state = ConnectionState.Closed;
closeEmitter.fire(undefined);
}
// If the connection is disposed don't sent close events.
};
function readErrorHandler(error: Error): void {
errorEmitter.fire([error, undefined, undefined]);
}
function writeErrorHandler(data: [Error, Message, number]): void {
errorEmitter.fire(data);
}
messageReader.onClose(closeHandler);
messageReader.onError(readErrorHandler);
messageWriter.onClose(closeHandler);
messageWriter.onError(writeErrorHandler);
function handleRequest(requestMessage: RequestMessage) {
if (isDisposed()) {
// we return here silently since we fired an event when the
// connection got disposed.
return;
}
function reply(resultOrError: any | ResponseError<any>): void {
let message: ResponseMessage = {
jsonrpc: version,
id: requestMessage.id
};
if (resultOrError instanceof ResponseError) {
message.error = (<ResponseError<any>>resultOrError).toJson();
} else {
message.result = is.undefined(resultOrError) ? null : resultOrError;
}
messageWriter.write(message);
}
function replyError(error: ResponseError<any>) {
let message: ResponseMessage = {
jsonrpc: version,
id: requestMessage.id,
error: error.toJson()
};
messageWriter.write(message);
}
function replySuccess(result: any) {
// The JSON RPC defines that a response must either have a result or an error
// So we can't treat undefined as a valid response result.
if (is.undefined(result)) {
result = null;
}
let message: ResponseMessage = {
jsonrpc: version,
id: requestMessage.id,
result: result
};
messageWriter.write(message);
}
let requestHandler = requestHandlers[requestMessage.method];
if (requestHandler) {
let cancellationSource = new CancellationTokenSource();
let tokenKey = String(requestMessage.id);
requestTokens[tokenKey] = cancellationSource;
try {
let handlerResult = requestHandler(requestMessage.params, cancellationSource.token);
let promise = <Thenable<any | ResponseError<any>>>handlerResult;
if (!handlerResult) {
delete requestTokens[tokenKey];
replySuccess(handlerResult);
} else if (promise.then) {
promise.then((resultOrError): any | ResponseError<any> => {
delete requestTokens[tokenKey];
reply(resultOrError);
}, error => {
delete requestTokens[tokenKey];
if (error instanceof ResponseError) {
replyError(<ResponseError<any>>error);
} else if (error && is.string(error.message)) {
replyError(new ResponseError<void>(ErrorCodes.InternalError, `Request ${requestMessage.method} failed with message: ${error.message}`));
} else {
replyError(new ResponseError<void>(ErrorCodes.InternalError, `Request ${requestMessage.method} failed unexpectedly without providing any details.`));
}
});
} else {
delete requestTokens[tokenKey];
reply(handlerResult);
}
} catch (error) {
delete requestTokens[tokenKey];
if (error instanceof ResponseError) {
reply(<ResponseError<any>>error);
} else if (error && is.string(error.message)) {
replyError(new ResponseError<void>(ErrorCodes.InternalError, `Request ${requestMessage.method} failed with message: ${error.message}`));
} else {
replyError(new ResponseError<void>(ErrorCodes.InternalError, `Request ${requestMessage.method} failed unexpectedly without providing any details.`));
}
}
} else {
replyError(new ResponseError<void>(ErrorCodes.MethodNotFound, `Unhandled method ${requestMessage.method}`));
}
}
function handleResponse(responseMessage: ResponseMessage) {
if (isDisposed()) {
// See handle request.
return;
}
let key = String(responseMessage.id);
let responsePromise = responsePromises[key];
if (trace != Trace.Off && tracer) {
traceResponse(responseMessage, responsePromise);
}
if (responsePromise) {
delete responsePromises[key];
try {
if (is.defined(responseMessage.error)) {
let error = responseMessage.error;
responsePromise.reject(new ResponseError(error.code, error.message, error.data));
} else if (is.defined(responseMessage.result)) {
responsePromise.resolve(responseMessage.result);
} else {
throw new Error('Should never happen.');
}
} catch (error) {
if (error.message) {
logger.error(`Response handler '${responsePromise.method}' failed with message: ${error.message}`);
} else {
logger.error(`Response handler '${responsePromise.method}' failed unexpectedly.`);
}
}
}
}
function handleNotification(message: NotificationMessage) {
if (isDisposed()) {
// See handle request.
return;
}
let eventHandler: NotificationHandler<any>;
if (message.method === CancelNotification.type.method) {
eventHandler = (params: CancelParams) => {
let id = params.id;
let source = requestTokens[String(id)];
if (source) {
source.cancel();
}
}
} else {
eventHandler = eventHandlers[message.method];
}
if (eventHandler) {
try {
if (trace != Trace.Off && tracer) {
traceReceivedNotification(message);
}
eventHandler(message.params);
} catch (error) {
if (error.message) {
logger.error(`Notification handler '${message.method}' failed with message: ${error.message}`);
} else {
logger.error(`Notification handler '${message.method}' failed unexpectedly.`);
}
}
} else {
unhandledNotificationEmitter.fire(message);
}
}
function handleInvalidMessage(message: Message) {
if (!message) {
logger.error('Received empty message.');
return;
}
logger.error(`Received message which is neither a response nor a notification message:\n${JSON.stringify(message, null, 4)}`);
// Test whether we find an id to reject the promise
let responseMessage: ResponseMessage = message as ResponseMessage;
if (is.string(responseMessage.id) || is.number(responseMessage.id)) {
let key = String(responseMessage.id);
let responseHandler = responsePromises[key];
if (responseHandler) {
responseHandler.reject(new Error('The received response has neither a result nor an error property.'));
}
}
}
function traceRequest(message: RequestMessage): void {
let data: string = undefined;
if (trace === Trace.Verbose && message.params) {
data = `Params: ${JSON.stringify(message.params, null, 4)}\n\n`;
}
tracer.log(`Sending request '${message.method} - (${message.id})'.`, data);
}
function traceSendNotification(message: NotificationMessage): void {
let data: string = undefined;
if (trace === Trace.Verbose) {
if (message.params) {
data = `Params: ${JSON.stringify(message.params, null, 4)}\n\n`;
} else {
data = 'No parameters provided.\n\n';
}
}
tracer.log(`Sending notification '${message.method}'.`, data);
}
function traceReceivedNotification(message: NotificationMessage): void {
if (message.method === LogTraceNotification.type.method) {
return;
}
let data: string = undefined;
if (trace === Trace.Verbose) {
if (message.params) {
data = `Params: ${JSON.stringify(message.params, null, 4)}\n\n`;
} else {
data = 'No parameters provided.\n\n';
}
}
tracer.log(`Received notification '${message.method}'.`, data);
}
function traceResponse(message: ResponseMessage, responsePromise: ResponsePromise): void {
let data: string = undefined;
if (trace === Trace.Verbose) {
if (message.error && message.error.data) {
data = `Error data: ${JSON.stringify(message.error.data, null, 4)}\n\n`;
} else {
if (message.result) {
data = `Result: ${JSON.stringify(message.result, null, 4)}\n\n`;
} else if (is.undefined(message.error)) {
data = 'No result returned.\n\n';
}
}
}
if (responsePromise) {
let error = message.error ? ` Request failed: ${message.error.message} (${message.error.code}).` : '';
tracer.log(`Received response '${responsePromise.method} - (${message.id})' in ${Date.now() - responsePromise.timerStart}ms.${error}`, data);
} else {
tracer.log(`Received response ${message.id} without active response promise.`, data);
}
}
let callback: DataCallback = (message) => {
if (isRequestMessage(message)) {
handleRequest(message);
} else if (isReponseMessage(message)) {
handleResponse(message)
} else if (isNotificationMessage(message)) {
handleNotification(message);
} else {
handleInvalidMessage(message);
}
};
function throwIfClosedOrDisposed() {
if (isClosed()) {
throw new Error('Connection is closed.');
}
if (isDisposed()) {
throw new Error('Connection is disposed.');
}
}
function throwIfListening() {
if (isListening()) {
throw new Error('Connection is already listening');
}
}
let connection: MessageConnection = {
sendNotification: <P>(type: NotificationType<P>, params): void => {
throwIfClosedOrDisposed();
let notificatioMessage: NotificationMessage = {
jsonrpc: version,
method: type.method,
params: params
}
if (trace != Trace.Off && tracer) {
traceSendNotification(notificatioMessage);
}
messageWriter.write(notificatioMessage);
},
onNotification: <P>(type: NotificationType<P>, handler: NotificationHandler<P>) => {
throwIfClosedOrDisposed();
eventHandlers[type.method] = handler;
},
sendRequest: <P, R, E>(type: RequestType<P, R, E>, params: P, token?: CancellationToken) => {
throwIfClosedOrDisposed();
let id = sequenceNumber++;
let result = new Promise<R | ResponseError<E>>((resolve, reject) => {
let requestMessage: RequestMessage = {
jsonrpc: version,
id: id,
method: type.method,
params: params
}
let responsePromise: ResponsePromise = { method: type.method, timerStart: Date.now(), resolve, reject };
if (trace != Trace.Off && tracer) {
traceRequest(requestMessage);
}
try {
messageWriter.write(requestMessage);
} catch (e) {
// Writing the message failed. So we need to reject the promise.
responsePromise.reject(new ResponseError<void>(ErrorCodes.MessageWriteError, e.message ? e.message : 'Unknown reason'));
responsePromise = null;
}
if (responsePromise) {
responsePromises[String(id)] = responsePromise;
}
});
if (token) {
token.onCancellationRequested((event) => {
connection.sendNotification(CancelNotification.type, { id });
});
}
return result;
},
onRequest: <P, R, E>(type: RequestType<P, R, E>, handler: RequestHandler<P, R, E>) => {
throwIfClosedOrDisposed();
requestHandlers[type.method] = handler;
},
trace: (_value: Trace, _tracer: Tracer, sendNotification: boolean = false) => {
trace = _value;
if (trace === Trace.Off) {
tracer = null;
} else {
tracer = _tracer;
}
if (sendNotification && !isClosed() && !isDisposed()) {
connection.sendNotification(SetTraceNotification.type, { value: Trace.toString(_value) });
}
},
onError: errorEmitter.event,
onClose: closeEmitter.event,
onUnhandledNotification: unhandledNotificationEmitter.event,
onDispose: disposeEmitter.event,
dispose: () => {
if (isDisposed()) {
return;
}
state = ConnectionState.Disposed;
disposeEmitter.fire(undefined);
let error = new Error('Connection got disposed.');
Object.keys(responsePromises).forEach((key) => {
responsePromises[key].reject(error);
});
responsePromises = Object.create(null);
requestTokens = Object.create(null);
},
listen: () => {
throwIfClosedOrDisposed();
throwIfListening();
state = ConnectionState.Listening;
messageReader.listen(callback);
}
};
connection.onNotification(LogTraceNotification.type, (params) => {
if (trace === Trace.Off) {
return;
}
tracer.log(params.message, trace === Trace.Verbose ? params.verbose : undefined);
});
return connection as T;
}
function isMessageReader(value: any): value is MessageReader {
return is.defined(value.listen) && is.undefined(value.read);
}
function isMessageWriter(value: any): value is MessageWriter {
return is.defined(value.write) && is.undefined(value.end);
}
export function createServerMessageConnection(reader: MessageReader, writer: MessageWriter, logger: Logger): ServerMessageConnection;
export function createServerMessageConnection(inputStream: NodeJS.ReadableStream, outputStream: NodeJS.WritableStream, logger: Logger): ServerMessageConnection;
export function createServerMessageConnection(input: MessageReader | NodeJS.ReadableStream, output: MessageWriter | NodeJS.WritableStream, logger: Logger): ServerMessageConnection {
let reader = isMessageReader(input) ? input : new StreamMessageReader(input);
let writer = isMessageWriter(output) ? output : new StreamMessageWriter(output);
return createMessageConnection<ServerMessageConnection>(reader, writer, logger);
}
export function createClientMessageConnection(reader: MessageReader, writer: MessageWriter, logger: Logger): ClientMessageConnection;
export function createClientMessageConnection(inputStream: NodeJS.ReadableStream, outputStream: NodeJS.WritableStream, logger: Logger): ClientMessageConnection;
export function createClientMessageConnection(input: MessageReader | NodeJS.ReadableStream, output: MessageWriter | NodeJS.WritableStream, logger: Logger): ClientMessageConnection {
let reader = isMessageReader(input) ? input : new StreamMessageReader(input);
let writer = isMessageWriter(output) ? output : new StreamMessageWriter(output);
return createMessageConnection<ClientMessageConnection>(reader, writer, logger, true);
}

View File

@@ -0,0 +1,265 @@
/* --------------------------------------------------------------------------------------------
* Copyright (c) Microsoft Corporation. All rights reserved.
* Licensed under the Source EULA. See License.txt in the project root for license information.
* ------------------------------------------------------------------------------------------ */
'use strict';
import { ChildProcess } from 'child_process';
import { Message } from './messages';
import { Event, Emitter } from './events';
import * as is from './is';
let DefaultSize: number = 8192;
let CR: number = new Buffer('\r', 'ascii')[0];
let LF: number = new Buffer('\n', 'ascii')[0];
let CRLF: string = '\r\n';
class MessageBuffer {
private encoding: string;
private index: number;
private buffer: Buffer;
constructor(encoding: string = 'utf-8') {
this.encoding = encoding;
this.index = 0;
this.buffer = new Buffer(DefaultSize);
}
public append(chunk: Buffer | String): void {
var toAppend: Buffer = <Buffer>chunk;
if (typeof (chunk) == 'string') {
var str = <string>chunk;
toAppend = new Buffer(str.length);
toAppend.write(str, 0, str.length, this.encoding);
}
if (this.buffer.length - this.index >= toAppend.length) {
toAppend.copy(this.buffer, this.index, 0, toAppend.length);
} else {
var newSize = (Math.ceil((this.index + toAppend.length) / DefaultSize) + 1) * DefaultSize;
if (this.index === 0) {
this.buffer = new Buffer(newSize);
toAppend.copy(this.buffer, 0, 0, toAppend.length);
} else {
this.buffer = Buffer.concat([this.buffer.slice(0, this.index), toAppend], newSize);
}
}
this.index += toAppend.length;
}
public tryReadHeaders(): { [key: string]: string; } {
let result: { [key: string]: string; } = undefined;
let current = 0;
while (current + 3 < this.index && (this.buffer[current] !== CR || this.buffer[current + 1] !== LF || this.buffer[current + 2] !== CR || this.buffer[current + 3] !== LF)) {
current++;
}
// No header / body separator found (e.g CRLFCRLF)
if (current + 3 >= this.index) {
return result;
}
result = Object.create(null);
let headers = this.buffer.toString('ascii', 0, current).split(CRLF);
headers.forEach((header) => {
let index: number = header.indexOf(':');
if (index === -1) {
throw new Error('Message header must separate key and value using :');
}
let key = header.substr(0, index);
let value = header.substr(index + 1).trim();
result[key] = value;
})
let nextStart = current + 4;
this.buffer = this.buffer.slice(nextStart);
this.index = this.index - nextStart;
return result;
}
public tryReadContent(length: number): string {
if (this.index < length) {
return null;
}
let result = this.buffer.toString(this.encoding, 0, length);
let nextStart = length;
this.buffer.copy(this.buffer, 0, nextStart);
this.index = this.index - nextStart;
return result;
}
public get numberOfBytes(): number {
return this.index;
}
}
export interface DataCallback {
(data: Message): void;
}
export interface PartialMessageInfo {
messageToken: number;
waitingTime: number;
}
export interface MessageReader {
onError: Event<Error>;
onClose: Event<void>;
onPartialMessage: Event<PartialMessageInfo>;
listen(callback: DataCallback): void;
}
export abstract class AbstractMessageReader {
private errorEmitter: Emitter<Error>;
private closeEmitter: Emitter<void>;
private partialMessageEmitter: Emitter<PartialMessageInfo>;
constructor() {
this.errorEmitter = new Emitter<Error>();
this.closeEmitter = new Emitter<void>();
this.partialMessageEmitter = new Emitter<PartialMessageInfo>();
}
public get onError(): Event<Error> {
return this.errorEmitter.event;
}
protected fireError(error: any): void {
this.errorEmitter.fire(this.asError(error));
}
public get onClose(): Event<void> {
return this.closeEmitter.event;
}
protected fireClose(): void {
this.closeEmitter.fire(undefined);
}
public get onPartialMessage(): Event<PartialMessageInfo> {
return this.partialMessageEmitter.event;
}
protected firePartialMessage(info: PartialMessageInfo): void {
this.partialMessageEmitter.fire(info);
}
private asError(error: any): Error {
if (error instanceof Error) {
return error;
} else {
return new Error(`Reader recevied error. Reason: ${is.string(error.message) ? error.message : 'unknown'}`);
}
}
}
export class StreamMessageReader extends AbstractMessageReader implements MessageReader {
private readable: NodeJS.ReadableStream;
private callback: DataCallback;
private buffer: MessageBuffer;
private nextMessageLength: number;
private messageToken: number;
private partialMessageTimer: NodeJS.Timer;
private _partialMessageTimeout: number;
public constructor(readable: NodeJS.ReadableStream, encoding: string = 'utf-8') {
super();
this.readable = readable;
this.buffer = new MessageBuffer(encoding);
this._partialMessageTimeout = 10000;
}
public set partialMessageTimeout(timeout: number) {
this._partialMessageTimeout = timeout;
}
public get partialMessageTimeout(): number {
return this._partialMessageTimeout;
}
public listen(callback: DataCallback): void {
this.nextMessageLength = -1;
this.messageToken = 0;
this.partialMessageTimer = undefined;
this.callback = callback;
this.readable.on('data', (data: Buffer) => {
this.onData(data);
});
this.readable.on('error', (error: any) => this.fireError(error));
this.readable.on('close', () => this.fireClose());
}
private onData(data: Buffer | String): void {
this.buffer.append(data);
while (true) {
if (this.nextMessageLength === -1) {
let headers = this.buffer.tryReadHeaders();
if (!headers) {
return;
}
let contentLength = headers['Content-Length'];
if (!contentLength) {
throw new Error('Header must provide a Content-Length property.');
}
let length = parseInt(contentLength);
if (isNaN(length)) {
throw new Error('Content-Length value must be a number.');
}
this.nextMessageLength = length;
}
var msg = this.buffer.tryReadContent(this.nextMessageLength);
if (msg === null) {
/** We haven't recevied the full message yet. */
this.setPartialMessageTimer();
return;
}
this.clearPartialMessageTimer();
this.nextMessageLength = -1;
this.messageToken++;
var json = JSON.parse(msg);
this.callback(json);
}
}
private clearPartialMessageTimer(): void {
if (this.partialMessageTimer) {
clearTimeout(this.partialMessageTimer);
this.partialMessageTimer = undefined;
}
}
private setPartialMessageTimer(): void {
this.clearPartialMessageTimer();
if (this._partialMessageTimeout <= 0) {
return;
}
this.partialMessageTimer = setTimeout((token, timeout) => {
this.partialMessageTimer = undefined;
if (token === this.messageToken) {
this.firePartialMessage({ messageToken: token, waitingTime: timeout });
this.setPartialMessageTimer();
}
}, this._partialMessageTimeout, this.messageToken, this._partialMessageTimeout);
}
}
export class IPCMessageReader extends AbstractMessageReader implements MessageReader {
private process: NodeJS.Process | ChildProcess;
public constructor(process: NodeJS.Process | ChildProcess) {
super();
this.process = process;
let eventEmitter: NodeJS.EventEmitter = <NodeJS.EventEmitter>this.process;
eventEmitter.on('error', (error: any) => this.fireError(error));
eventEmitter.on('close', () => this.fireClose());
}
public listen(callback: DataCallback): void {
let eventEmitter: NodeJS.EventEmitter = <NodeJS.EventEmitter>this.process;
eventEmitter.on('message', callback);
}
}

View File

@@ -0,0 +1,118 @@
/* --------------------------------------------------------------------------------------------
* Copyright (c) Microsoft Corporation. All rights reserved.
* Licensed under the Source EULA. See License.txt in the project root for license information.
* ------------------------------------------------------------------------------------------ */
'use strict';
import { ChildProcess } from 'child_process';
import { Message } from './messages';
import { Event, Emitter } from './events';
import * as is from './is';
let ContentLength: string = 'Content-Length: ';
let CRLF = '\r\n';
export interface MessageWriter {
onError: Event<[Error, Message, number]>;
onClose: Event<void>;
write(msg: Message): void;
}
export abstract class AbstractMessageWriter {
private errorEmitter: Emitter<[Error, Message, number]>;
private closeEmitter: Emitter<void>;
constructor() {
this.errorEmitter = new Emitter<[Error, Message, number]>();
this.closeEmitter = new Emitter<void>();
}
public get onError(): Event<[Error, Message, number]> {
return this.errorEmitter.event;
}
protected fireError(error: any, message?: Message, count?: number): void {
this.errorEmitter.fire([this.asError(error), message, count]);
}
public get onClose(): Event<void> {
return this.closeEmitter.event;
}
protected fireClose(): void {
this.closeEmitter.fire(undefined);
}
private asError(error: any): Error {
if (error instanceof Error) {
return error;
} else {
return new Error(`Writer recevied error. Reason: ${is.string(error.message) ? error.message : 'unknown'}`);
}
}
}
export class StreamMessageWriter extends AbstractMessageWriter implements MessageWriter {
private writable: NodeJS.WritableStream;
private encoding: string;
private errorCount: number;
public constructor(writable: NodeJS.WritableStream, encoding: string = 'utf8') {
super();
this.writable = writable;
this.encoding = encoding;
this.errorCount = 0;
this.writable.on('error', (error) => this.fireError(error));
this.writable.on('close', () => this.fireClose());
}
public write(msg: Message): void {
let json = JSON.stringify(msg);
let contentLength = Buffer.byteLength(json, this.encoding);
let headers: string[] = [
ContentLength, contentLength.toString(), CRLF,
CRLF
];
try {
// Header must be written in ASCII encoding
this.writable.write(headers.join(''), 'ascii');
// Now write the content. This can be written in any encoding
this.writable.write(json, this.encoding);
this.errorCount = 0;
} catch (error) {
this.errorCount++;
this.fireError(error, msg, this.errorCount);
}
}
}
export class IPCMessageWriter extends AbstractMessageWriter implements MessageWriter {
private process: NodeJS.Process | ChildProcess;
private errorCount: number;
public constructor(process: NodeJS.Process | ChildProcess) {
super();
this.process = process;
this.errorCount = 0;
let eventEmitter: NodeJS.EventEmitter = <NodeJS.EventEmitter>this.process;
eventEmitter.on('error', (error) => this.fireError(error));
eventEmitter.on('close', () => this.fireClose);
}
public write(msg: Message): void {
try {
(this.process.send as Function)(msg);
this.errorCount = 0;
} catch (error) {
this.errorCount++;
this.fireError(error, msg, this.errorCount);
}
}
}

View File

@@ -0,0 +1,175 @@
/* --------------------------------------------------------------------------------------------
* Copyright (c) Microsoft Corporation. All rights reserved.
* Licensed under the Source EULA. See License.txt in the project root for license information.
* ------------------------------------------------------------------------------------------ */
'use strict';
import * as is from './is';
/**
* A language server message
*/
export interface Message {
jsonrpc: string;
}
/**
* Request message
*/
export interface RequestMessage extends Message {
/**
* The request id.
*/
id: number | string;
/**
* The method to be invoked.
*/
method: string;
/**
* The method's params.
*/
params?: any
}
/**
* Predefined error codes.
*/
export namespace ErrorCodes {
// Defined by JSON RPC
export const ParseError: number = -32700;
export const InvalidRequest: number = -32600;
export const MethodNotFound: number = -32601;
export const InvalidParams: number = -32602;
export const InternalError: number = -32603;
export const serverErrorStart: number = -32099
export const serverErrorEnd: number = -32000;
// Defined by VSCode.
export const MessageWriteError: number = 1;
export const MessageReadError: number = 2;
}
export interface ResponseErrorLiteral<D> {
/**
* A number indicating the error type that occured.
*/
code: number;
/**
* A string providing a short decription of the error.
*/
message: string;
/**
* A Primitive or Structured value that contains additional
* information about the error. Can be omitted.
*/
data?: D;
}
/**
* A error object return in a response in case a request
* has failed.
*/
export class ResponseError<D> extends Error {
public code: number;
public message: string;
public data: D;
constructor(code: number, message: string, data?: D) {
super(message);
this.code = code;
this.message = message;
if (is.defined(data)) {
this.data = data;
}
}
public toJson(): ResponseErrorLiteral<D> {
let result: ResponseErrorLiteral<D> = {
code: this.code,
message: this.message
};
if (is.defined(this.data)) {
result.data = this.data
};
return result;
}
}
/**
* A response message.
*/
export interface ResponseMessage extends Message {
/**
* The request id.
*/
id: number | string;
/**
* The result of a request. This can be omitted in
* the case of an error.
*/
result?: any;
/**
* The error object in case a request fails.
*/
error?: ResponseErrorLiteral<any>;
}
/**
* A interface to type the request parameter / response pair
*/
export interface RequestType<P, R, E> {
method: string;
}
/**
* Notification Message
*/
export interface NotificationMessage extends Message {
/**
* The method to be invoked.
*/
method: string;
/**
* The notification's params.
*/
params?: any
}
export interface NotificationType<P> {
method: string;
}
/**
* Tests if the given message is a request message
*/
export function isRequestMessage(message: Message): message is RequestMessage {
let candidate = <RequestMessage>message;
return candidate && is.string(candidate.method) && (is.string(candidate.id) || is.number(candidate.id));
}
/**
* Tests if the given message is a notification message
*/
export function isNotificationMessage(message: Message): message is NotificationMessage {
let candidate = <NotificationMessage>message;
return candidate && is.string(candidate.method) && is.undefined((<any>message).id);
}
/**
* Tests if the given message is a response message
*/
export function isReponseMessage(message: Message): message is ResponseMessage {
let candidate = <ResponseMessage>message;
return candidate && (is.defined(candidate.result) || is.defined(candidate.error)) && (is.string(candidate.id) || is.number(candidate.id));
}

View File

@@ -0,0 +1,11 @@
{
"compilerOptions": {
"target": "ES5",
"module": "commonjs",
"moduleResolution": "node",
"sourceMap": true,
"declaration": true,
"stripInternal": true,
"outDir": "../lib"
}
}

View File

@@ -0,0 +1,112 @@
/*! *****************************************************************************
Copyright (c) Microsoft Corporation. All rights reserved.
Licensed under the Apache License, Version 2.0 (the "License"); you may not use
this file except in compliance with the License. You may obtain a copy of the
License at http://www.apache.org/licenses/LICENSE-2.0
THIS CODE IS PROVIDED ON AN *AS IS* BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
KIND, EITHER EXPRESS OR IMPLIED, INCLUDING WITHOUT LIMITATION ANY IMPLIED
WARRANTIES OR CONDITIONS OF TITLE, FITNESS FOR A PARTICULAR PURPOSE,
MERCHANTABLITY OR NON-INFRINGEMENT.
See the Apache Version 2.0 License for specific language governing permissions
and limitations under the License.
***************************************************************************** */
/**
* The Thenable (E.g. PromiseLike) and Promise declarions are taken from TypeScript's
* lib.core.es6.d.ts file. See above Copyright notice.
*/
/**
* Thenable is a common denominator between ES6 promises, Q, jquery.Deferred, WinJS.Promise,
* and others. This API makes no assumption about what promise libary is being used which
* enables reusing existing code without migrating to a specific promise implementation. Still,
* we recommand the use of native promises which are available in VS Code.
*/
interface Thenable<R> {
/**
* Attaches callbacks for the resolution and/or rejection of the Promise.
* @param onfulfilled The callback to execute when the Promise is resolved.
* @param onrejected The callback to execute when the Promise is rejected.
* @returns A Promise for the completion of which ever callback is executed.
*/
then<TResult>(onfulfilled?: (value: R) => TResult | Thenable<TResult>, onrejected?: (reason: any) => TResult | Thenable<TResult>): Thenable<TResult>;
then<TResult>(onfulfilled?: (value: R) => TResult | Thenable<TResult>, onrejected?: (reason: any) => void): Thenable<TResult>;
}
/**
* Represents the completion of an asynchronous operation
*/
interface Promise<T> extends Thenable<T> {
/**
* Attaches callbacks for the resolution and/or rejection of the Promise.
* @param onfulfilled The callback to execute when the Promise is resolved.
* @param onrejected The callback to execute when the Promise is rejected.
* @returns A Promise for the completion of which ever callback is executed.
*/
then<TResult>(onfulfilled?: (value: T) => TResult | Thenable<TResult>, onrejected?: (reason: any) => TResult | Thenable<TResult>): Promise<TResult>;
then<TResult>(onfulfilled?: (value: T) => TResult | Thenable<TResult>, onrejected?: (reason: any) => void): Promise<TResult>;
/**
* Attaches a callback for only the rejection of the Promise.
* @param onrejected The callback to execute when the Promise is rejected.
* @returns A Promise for the completion of the callback.
*/
catch(onrejected?: (reason: any) => T | Thenable<T>): Promise<T>;
}
interface PromiseConstructor {
/**
* Creates a new Promise.
* @param executor A callback used to initialize the promise. This callback is passed two arguments:
* a resolve callback used resolve the promise with a value or the result of another promise,
* and a reject callback used to reject the promise with a provided reason or error.
*/
new <T>(executor: (resolve: (value?: T | Thenable<T>) => void, reject: (reason?: any) => void) => void): Promise<T>;
/**
* Creates a Promise that is resolved with an array of results when all of the provided Promises
* resolve, or rejected when any Promise is rejected.
* @param values An array of Promises.
* @returns A new Promise.
*/
all<T>(values: Array<T | Thenable<T>>): Promise<T[]>;
/**
* Creates a Promise that is resolved or rejected when any of the provided Promises are resolved
* or rejected.
* @param values An array of Promises.
* @returns A new Promise.
*/
race<T>(values: Array<T | Thenable<T>>): Promise<T>;
/**
* Creates a new rejected promise for the provided reason.
* @param reason The reason the promise was rejected.
* @returns A new rejected Promise.
*/
reject(reason: any): Promise<void>;
/**
* Creates a new rejected promise for the provided reason.
* @param reason The reason the promise was rejected.
* @returns A new rejected Promise.
*/
reject<T>(reason: any): Promise<T>;
/**
* Creates a new resolved promise for the provided value.
* @param value A promise.
* @returns A promise whose internal state matches the provided promise.
*/
resolve<T>(value: T | Thenable<T>): Promise<T>;
/**
* Creates a new resolved promise .
* @returns A resolved promise.
*/
resolve(): Promise<void>;
}
declare var Promise: PromiseConstructor;

View File

@@ -0,0 +1,31 @@
THIRD-PARTY SOFTWARE NOTICES AND INFORMATION
For Microsoft vscode-jsonrpc
This project incorporates material from the project(s) listed below (collectively, “Third Party Code”).
Microsoft is not the original author of the Third Party Code. The original copyright notice and license
under which Microsoft received such Third Party Code are set out below. This Third Party Code is licensed
to you under their original license terms set forth below. Microsoft reserves all other rights not expressly
granted, whether by implication, estoppel or otherwise.
1. DefinitelyTyped version 0.0.1 (https://github.com/borisyankov/DefinitelyTyped)
This project is licensed under the MIT license.
Copyrights are respective of each contributor listed at the beginning of each definition file.
Permission is hereby granted, free of charge, to any person obtaining a copy
of this software and associated documentation files (the "Software"), to deal
in the Software without restriction, including without limitation the rights
to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
copies of the Software, and to permit persons to whom the Software is
furnished to do so, subject to the following conditions:
The above copyright notice and this permission notice shall be included in
all copies or substantial portions of the Software.
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN
THE SOFTWARE.