javascript-typescript-langserver
Version:
Implementation of the Language Server Protocol for JavaScript and TypeScript
382 lines • 21.9 kB
JavaScript
"use strict";
var __awaiter = (this && this.__awaiter) || function (thisArg, _arguments, P, generator) {
return new (P || (P = Promise))(function (resolve, reject) {
function fulfilled(value) { try { step(generator.next(value)); } catch (e) { reject(e); } }
function rejected(value) { try { step(generator["throw"](value)); } catch (e) { reject(e); } }
function step(result) { result.done ? resolve(result.value) : new P(function (resolve) { resolve(result.value); }).then(fulfilled, rejected); }
step((generator = generator.apply(thisArg, _arguments || [])).next());
});
};
Object.defineProperty(exports, "__esModule", { value: true });
const assert = require("assert");
const events_1 = require("events");
const opentracing_1 = require("opentracing");
const rxjs_1 = require("rxjs");
const sinon = require("sinon");
const stream_1 = require("stream");
const vscode_jsonrpc_1 = require("vscode-jsonrpc");
const connection_1 = require("../connection");
const logging_1 = require("../logging");
const typescript_service_1 = require("../typescript-service");
describe('connection', () => {
describe('registerLanguageHandler()', () => {
it('should return MethodNotFound error when the method does not exist on handler', () => __awaiter(this, void 0, void 0, function* () {
const handler = Object.create(typescript_service_1.TypeScriptService.prototype);
const emitter = new events_1.EventEmitter();
const writer = {
write: sinon.spy(),
};
connection_1.registerLanguageHandler(emitter, writer, handler);
const params = [1, 1];
emitter.emit('message', { jsonrpc: '2.0', id: 1, method: 'whatever', params });
sinon.assert.calledOnce(writer.write);
sinon.assert.calledWithExactly(writer.write, sinon.match({ jsonrpc: '2.0', id: 1, error: { code: vscode_jsonrpc_1.ErrorCodes.MethodNotFound } }));
}));
it('should return MethodNotFound error when the method is prefixed with an underscore', () => __awaiter(this, void 0, void 0, function* () {
const handler = { _privateMethod: sinon.spy() };
const emitter = new events_1.EventEmitter();
const writer = {
write: sinon.spy(),
};
connection_1.registerLanguageHandler(emitter, writer, handler);
const params = [1, 1];
emitter.emit('message', { jsonrpc: '2.0', id: 1, method: 'textDocument/hover', params });
sinon.assert.notCalled(handler._privateMethod);
sinon.assert.calledOnce(writer.write);
sinon.assert.calledWithExactly(writer.write, sinon.match({ jsonrpc: '2.0', id: 1, error: { code: vscode_jsonrpc_1.ErrorCodes.MethodNotFound } }));
}));
it('should call a handler on request and send the result of the returned Promise', () => __awaiter(this, void 0, void 0, function* () {
const handler = sinon.createStubInstance(typescript_service_1.TypeScriptService);
handler.initialize.returns(Promise.resolve({ op: 'add', path: '', value: { capabilities: {} } }));
handler.textDocumentHover.returns(Promise.resolve(2));
const emitter = new events_1.EventEmitter();
const writer = {
write: sinon.spy(),
};
connection_1.registerLanguageHandler(emitter, writer, handler);
emitter.emit('message', { jsonrpc: '2.0', id: 1, method: 'initialize', params: { capabilities: {} } });
yield new Promise(resolve => setTimeout(resolve, 0));
sinon.assert.calledOnce(handler.initialize);
sinon.assert.calledWithExactly(writer.write, sinon.match({ jsonrpc: '2.0', id: 1, result: { capabilities: {} } }));
}));
it('should ignore exit notifications', () => __awaiter(this, void 0, void 0, function* () {
const handler = {
exit: sinon.spy(),
};
const emitter = new events_1.EventEmitter();
const writer = {
write: sinon.spy(),
};
connection_1.registerLanguageHandler(emitter, writer, handler);
emitter.emit('message', { jsonrpc: '2.0', id: 1, method: 'exit' });
sinon.assert.notCalled(handler.exit);
sinon.assert.notCalled(writer.write);
}));
it('should ignore responses', () => __awaiter(this, void 0, void 0, function* () {
const handler = {
whatever: sinon.spy(),
};
const emitter = new events_1.EventEmitter();
const writer = {
write: sinon.spy(),
};
connection_1.registerLanguageHandler(emitter, writer, handler);
emitter.emit('message', { jsonrpc: '2.0', id: 1, method: 'whatever', result: 123 });
sinon.assert.notCalled(handler.whatever);
}));
it('should log invalid messages', () => __awaiter(this, void 0, void 0, function* () {
const handler = {
whatever: sinon.spy(),
};
const emitter = new events_1.EventEmitter();
const writer = {
write: sinon.spy(),
};
const logger = new logging_1.NoopLogger();
sinon.stub(logger, 'error');
connection_1.registerLanguageHandler(emitter, writer, handler, { logger });
emitter.emit('message', { jsonrpc: '2.0', id: 1 });
sinon.assert.calledOnce(logger.error);
}));
it('should call a handler on request and send the result of the returned Observable', () => __awaiter(this, void 0, void 0, function* () {
const handler = Object.create(typescript_service_1.TypeScriptService.prototype);
const hoverStub = sinon
.stub(handler, 'textDocumentHover')
.returns(rxjs_1.Observable.of({ op: 'add', path: '', value: [] }, { op: 'add', path: '/-', value: 123 }));
const emitter = new events_1.EventEmitter();
const writer = {
write: sinon.spy(),
};
connection_1.registerLanguageHandler(emitter, writer, handler);
const params = [1, 1];
emitter.emit('message', { jsonrpc: '2.0', id: 1, method: 'textDocument/hover', params });
sinon.assert.calledOnce(hoverStub);
sinon.assert.calledWithExactly(hoverStub, params, sinon.match.instanceOf(opentracing_1.Span));
sinon.assert.calledWithExactly(writer.write, sinon.match({ jsonrpc: '2.0', id: 1, result: [123] }));
}));
it('should call a handler on request and send the thrown error of the returned Observable', () => __awaiter(this, void 0, void 0, function* () {
const handler = Object.create(typescript_service_1.TypeScriptService.prototype);
const hoverStub = sinon.stub(handler, 'textDocumentHover').returns(rxjs_1.Observable.throw(Object.assign(new Error('Something happened'), {
code: vscode_jsonrpc_1.ErrorCodes.serverErrorStart,
whatever: 123,
})));
const emitter = new events_1.EventEmitter();
const writer = {
write: sinon.spy(),
};
connection_1.registerLanguageHandler(emitter, writer, handler);
const params = [1, 1];
emitter.emit('message', { jsonrpc: '2.0', id: 1, method: 'textDocument/hover', params });
sinon.assert.calledOnce(hoverStub);
sinon.assert.calledWithExactly(hoverStub, params, sinon.match.instanceOf(opentracing_1.Span));
sinon.assert.calledOnce(writer.write);
sinon.assert.calledWithExactly(writer.write, sinon.match({
jsonrpc: '2.0',
id: 1,
error: {
message: 'Something happened',
code: vscode_jsonrpc_1.ErrorCodes.serverErrorStart,
data: { whatever: 123 },
},
}));
}));
it('should call a handler on request and send the returned synchronous value', () => __awaiter(this, void 0, void 0, function* () {
const handler = Object.create(typescript_service_1.TypeScriptService.prototype);
const hoverStub = sinon
.stub(handler, 'textDocumentHover')
.returns(rxjs_1.Observable.of({ op: 'add', path: '', value: 2 }));
const emitter = new events_1.EventEmitter();
const writer = {
write: sinon.spy(),
};
connection_1.registerLanguageHandler(emitter, writer, handler);
emitter.emit('message', { jsonrpc: '2.0', id: 1, method: 'textDocument/hover', params: [1, 2] });
sinon.assert.calledOnce(hoverStub);
sinon.assert.calledWithExactly(hoverStub, [1, 2], sinon.match.instanceOf(opentracing_1.Span));
sinon.assert.calledWithExactly(writer.write, sinon.match({ jsonrpc: '2.0', id: 1, result: 2 }));
}));
it('should call a handler on request and send the result of the returned Observable', () => __awaiter(this, void 0, void 0, function* () {
const handler = Object.create(typescript_service_1.TypeScriptService.prototype);
const hoverStub = sinon
.stub(handler, 'textDocumentHover')
.returns(rxjs_1.Observable.of({ op: 'add', path: '', value: 2 }));
const emitter = new events_1.EventEmitter();
const writer = {
write: sinon.spy(),
};
connection_1.registerLanguageHandler(emitter, writer, handler);
const params = [1, 1];
emitter.emit('message', { jsonrpc: '2.0', id: 1, method: 'textDocument/hover', params });
sinon.assert.calledOnce(hoverStub);
sinon.assert.calledWithExactly(hoverStub, params, sinon.match.instanceOf(opentracing_1.Span));
sinon.assert.calledWithExactly(writer.write, sinon.match({ jsonrpc: '2.0', id: 1, result: 2 }));
}));
it('should unsubscribe from the returned Observable when $/cancelRequest was sent and return a RequestCancelled error', () => __awaiter(this, void 0, void 0, function* () {
const handler = Object.create(typescript_service_1.TypeScriptService.prototype);
const unsubscribeHandler = sinon.spy();
const hoverStub = sinon
.stub(handler, 'textDocumentHover')
.returns(new rxjs_1.Observable(subscriber => unsubscribeHandler));
const emitter = new events_1.EventEmitter();
const writer = {
write: sinon.spy(),
};
connection_1.registerLanguageHandler(emitter, writer, handler);
const params = [1, 1];
emitter.emit('message', { jsonrpc: '2.0', id: 1, method: 'textDocument/hover', params });
sinon.assert.calledOnce(hoverStub);
sinon.assert.calledWithExactly(hoverStub, params, sinon.match.instanceOf(opentracing_1.Span));
emitter.emit('message', { jsonrpc: '2.0', method: '$/cancelRequest', params: { id: 1 } });
sinon.assert.calledOnce(unsubscribeHandler);
sinon.assert.calledOnce(writer.write);
sinon.assert.calledWithExactly(writer.write, sinon.match({ jsonrpc: '2.0', id: 1, error: { code: vscode_jsonrpc_1.ErrorCodes.RequestCancelled } }));
}));
it('should unsubscribe from the returned Observable when the connection was closed', () => __awaiter(this, void 0, void 0, function* () {
const handler = Object.create(typescript_service_1.TypeScriptService.prototype);
const unsubscribeHandler = sinon.spy();
const hoverStub = sinon
.stub(handler, 'textDocumentHover')
.returns(new rxjs_1.Observable(subscriber => unsubscribeHandler));
const emitter = new events_1.EventEmitter();
const writer = {
write: sinon.spy(),
};
connection_1.registerLanguageHandler(emitter, writer, handler);
const params = [1, 1];
emitter.emit('message', { jsonrpc: '2.0', id: 1, method: 'textDocument/hover', params });
sinon.assert.calledOnce(hoverStub);
emitter.emit('close');
sinon.assert.calledOnce(unsubscribeHandler);
}));
it('should unsubscribe from the returned Observable on exit notification', () => __awaiter(this, void 0, void 0, function* () {
const handler = Object.create(typescript_service_1.TypeScriptService.prototype);
const unsubscribeHandler = sinon.spy();
const hoverStub = sinon
.stub(handler, 'textDocumentHover')
.returns(new rxjs_1.Observable(subscriber => unsubscribeHandler));
const emitter = new events_1.EventEmitter();
const writer = {
write: sinon.spy(),
};
connection_1.registerLanguageHandler(emitter, writer, handler);
const params = [1, 1];
emitter.emit('message', { jsonrpc: '2.0', id: 1, method: 'textDocument/hover', params });
sinon.assert.calledOnce(hoverStub);
emitter.emit('message', { jsonrpc: '2.0', method: 'exit' });
sinon.assert.calledOnce(unsubscribeHandler);
}));
for (const event of ['close', 'error']) {
it(`should call shutdown on ${event} if the service was initialized`, () => __awaiter(this, void 0, void 0, function* () {
const handler = {
initialize: sinon
.stub()
.returns(rxjs_1.Observable.of({ op: 'add', path: '', value: { capabilities: {} } })),
shutdown: sinon.stub().returns(rxjs_1.Observable.of({ op: 'add', path: '', value: null })),
};
const emitter = new events_1.EventEmitter();
const writer = {
write: sinon.spy(),
};
connection_1.registerLanguageHandler(emitter, writer, handler);
emitter.emit('message', { jsonrpc: '2.0', id: 1, method: 'initialize', params: { capabilities: {} } });
sinon.assert.calledOnce(handler.initialize);
emitter.emit(event);
sinon.assert.calledOnce(handler.shutdown);
}));
it(`should not call shutdown on ${event} if the service was not initialized`, () => __awaiter(this, void 0, void 0, function* () {
const handler = {
initialize: sinon
.stub()
.returns(rxjs_1.Observable.of({ op: 'add', path: '', value: { capabilities: {} } })),
shutdown: sinon.stub().returns(rxjs_1.Observable.of({ op: 'add', path: '', value: null })),
};
const emitter = new events_1.EventEmitter();
const writer = {
write: sinon.spy(),
};
connection_1.registerLanguageHandler(emitter, writer, handler);
emitter.emit(event);
sinon.assert.notCalled(handler.shutdown);
}));
it(`should not call shutdown again on ${event} if shutdown was already called`, () => __awaiter(this, void 0, void 0, function* () {
const handler = {
initialize: sinon
.stub()
.returns(rxjs_1.Observable.of({ op: 'add', path: '', value: { capabilities: {} } })),
shutdown: sinon.stub().returns(rxjs_1.Observable.of({ op: 'add', path: '', value: null })),
};
const emitter = new events_1.EventEmitter();
const writer = {
write: sinon.spy(),
};
connection_1.registerLanguageHandler(emitter, writer, handler);
emitter.emit('message', { jsonrpc: '2.0', id: 1, method: 'shutdown', params: {} });
sinon.assert.calledOnce(handler.shutdown);
emitter.emit(event);
sinon.assert.calledOnce(handler.shutdown);
}));
}
describe('Client with streaming support', () => {
it('should call a handler on request and send partial results of the returned Observable', () => __awaiter(this, void 0, void 0, function* () {
const handler = sinon.createStubInstance(typescript_service_1.TypeScriptService);
handler.initialize.returns(rxjs_1.Observable.of({ op: 'add', path: '', value: { capabilities: { streaming: true } } }));
const hoverSubject = new rxjs_1.Subject();
handler.textDocumentHover.returns(hoverSubject);
const emitter = new events_1.EventEmitter();
const writer = {
write: sinon.spy(),
};
connection_1.registerLanguageHandler(emitter, writer, handler);
// Send initialize
emitter.emit('message', {
jsonrpc: '2.0',
id: 1,
method: 'initialize',
params: { capabilities: { streaming: true } },
});
assert.deepEqual(writer.write.args[0], [
{
jsonrpc: '2.0',
method: '$/partialResult',
params: {
id: 1,
patch: [{ op: 'add', path: '', value: { capabilities: { streaming: true } } }],
},
},
], 'Expected to send partial result for initialize');
assert.deepEqual(writer.write.args[1], [
{
jsonrpc: '2.0',
id: 1,
result: { capabilities: { streaming: true } },
},
], 'Expected to send final result for initialize');
// Send hover
emitter.emit('message', { jsonrpc: '2.0', id: 2, method: 'textDocument/hover', params: [1, 2] });
sinon.assert.calledOnce(handler.textDocumentHover);
// Simulate initializing JSON Patch Operation
hoverSubject.next({ op: 'add', path: '', value: [] });
assert.deepEqual(writer.write.args[2], [
{
jsonrpc: '2.0',
method: '$/partialResult',
params: { id: 2, patch: [{ op: 'add', path: '', value: [] }] },
},
], 'Expected to send partial result that initializes array');
// Simulate streamed value
hoverSubject.next({ op: 'add', path: '/-', value: 123 });
assert.deepEqual(writer.write.args[3], [
{
jsonrpc: '2.0',
method: '$/partialResult',
params: { id: 2, patch: [{ op: 'add', path: '/-', value: 123 }] },
},
], 'Expected to send partial result that adds 123 to array');
// Complete Subject to trigger final response
hoverSubject.complete();
assert.deepEqual(writer.write.args[4], [
{
jsonrpc: '2.0',
id: 2,
result: [123],
},
], 'Expected to send final result [123]');
}));
});
});
describe('MessageEmitter', () => {
it('should log messages if enabled', () => __awaiter(this, void 0, void 0, function* () {
const logger = new logging_1.NoopLogger();
sinon.stub(logger, 'log');
const emitter = new connection_1.MessageEmitter(new stream_1.PassThrough(), { logMessages: true, logger });
emitter.emit('message', { jsonrpc: '2.0', method: 'whatever' });
sinon.assert.calledOnce(logger.log);
sinon.assert.calledWith(logger.log, '-->');
}));
it('should not log messages if disabled', () => __awaiter(this, void 0, void 0, function* () {
const logger = new logging_1.NoopLogger();
sinon.stub(logger, 'log');
const emitter = new connection_1.MessageEmitter(new stream_1.PassThrough(), { logMessages: false, logger });
emitter.emit('message', { jsonrpc: '2.0', method: 'whatever' });
sinon.assert.notCalled(logger.log);
}));
});
describe('MessageWriter', () => {
it('should log messages if enabled', () => __awaiter(this, void 0, void 0, function* () {
const logger = new logging_1.NoopLogger();
sinon.stub(logger, 'log');
const writer = new connection_1.MessageWriter(new stream_1.PassThrough(), { logMessages: true, logger });
writer.write({ jsonrpc: '2.0', method: 'whatever' });
sinon.assert.calledOnce(logger.log);
sinon.assert.calledWith(logger.log, '<--');
}));
it('should not log messages if disabled', () => __awaiter(this, void 0, void 0, function* () {
const logger = new logging_1.NoopLogger();
sinon.stub(logger, 'log');
const writer = new connection_1.MessageWriter(new stream_1.PassThrough(), { logMessages: false, logger });
writer.write({ jsonrpc: '2.0', method: 'whatever' });
sinon.assert.notCalled(logger.log);
}));
});
});
//# sourceMappingURL=connection.test.js.map