UNPKG

javascript-typescript-langserver

Version:

Implementation of the Language Server Protocol for JavaScript and TypeScript

382 lines 21.9 kB
"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