diff --git a/src/harness/unittests/tsserverProjectSystem.ts b/src/harness/unittests/tsserverProjectSystem.ts index f5a71aa4d8e..2ca7d4e21dd 100644 --- a/src/harness/unittests/tsserverProjectSystem.ts +++ b/src/harness/unittests/tsserverProjectSystem.ts @@ -210,6 +210,8 @@ namespace ts.projectSystem { class TestSession extends server.Session { private seq = 0; + public events: protocol.Event[] = []; + public host: TestServerHost; getProjectService() { return this.projectService; @@ -229,6 +231,16 @@ namespace ts.projectSystem { request.type = "request"; return this.executeCommand(request); } + + public event(body: T, eventName: string) { + this.events.push(server.toEvent(eventName, body)); + super.event(body, eventName); + } + + public clearMessages() { + clear(this.events); + this.host.clearOutput(); + } } export function createSession(host: server.ServerHost, opts: Partial = {}) { @@ -436,48 +448,29 @@ namespace ts.projectSystem { verifyDiagnostics(actual, []); } - function assertEvent(actualOutput: string, expectedEvent: protocol.Event, host: TestServerHost) { - assert.equal(actualOutput, server.formatMessage(expectedEvent, nullLogger, Utils.byteLength, host.newLine)); + function checkErrorMessage(session: TestSession, eventName: "syntaxDiag" | "semanticDiag", diagnostics: protocol.DiagnosticEventBody) { + checkNthEvent(session, ts.server.toEvent(eventName, diagnostics), 0, /*isMostRecent*/ false); } - function checkErrorMessage(host: TestServerHost, eventName: "syntaxDiag" | "semanticDiag", diagnostics: protocol.DiagnosticEventBody) { - const outputs = host.getOutput(); - assert.isTrue(outputs.length >= 1, outputs.toString()); - const event: protocol.Event = { - seq: 0, - type: "event", - event: eventName, - body: diagnostics - }; - assertEvent(outputs[0], event, host); + function checkCompleteEvent(session: TestSession, numberOfCurrentEvents: number, expectedSequenceId: number) { + checkNthEvent(session, ts.server.toEvent("requestCompleted", { request_seq: expectedSequenceId }), numberOfCurrentEvents - 1, /*isMostRecent*/ true); } - function checkCompleteEvent(host: TestServerHost, numberOfCurrentEvents: number, expectedSequenceId: number) { - const outputs = host.getOutput(); - assert.equal(outputs.length, numberOfCurrentEvents, outputs.toString()); - const event: protocol.RequestCompletedEvent = { - seq: 0, - type: "event", - event: "requestCompleted", - body: { - request_seq: expectedSequenceId - } - }; - assertEvent(outputs[numberOfCurrentEvents - 1], event, host); + function checkProjectUpdatedInBackgroundEvent(session: TestSession, openFiles: string[]) { + checkNthEvent(session, ts.server.toEvent("projectsUpdatedInBackground", { openFiles }), 0, /*isMostRecent*/ true); } - function checkProjectUpdatedInBackgroundEvent(host: TestServerHost, openFiles: string[]) { - const outputs = host.getOutput(); - assert.equal(outputs.length, 1, outputs.toString()); - const event: protocol.ProjectsUpdatedInBackgroundEvent = { - seq: 0, - type: "event", - event: "projectsUpdatedInBackground", - body: { - openFiles - } - }; - assertEvent(outputs[0], event, host); + function checkNthEvent(session: TestSession, expectedEvent: protocol.Event, index: number, isMostRecent: boolean) { + const events = session.events; + assert.deepEqual(events[index], expectedEvent); + + const outputs = session.host.getOutput(); + assert.equal(outputs[index], server.formatMessage(expectedEvent, nullLogger, Utils.byteLength, session.host.newLine)); + + if (isMostRecent) { + assert.strictEqual(events.length, index + 1, JSON.stringify(events)); + assert.strictEqual(outputs.length, index + 1, JSON.stringify(outputs)); + } } describe("tsserverProjectSystem", () => { @@ -2891,14 +2884,14 @@ namespace ts.projectSystem { assert.isFalse(hasError); host.checkTimeoutQueueLength(2); - checkErrorMessage(host, "syntaxDiag", { file: untitledFile, diagnostics: [] }); - host.clearOutput(); + checkErrorMessage(session, "syntaxDiag", { file: untitledFile, diagnostics: [] }); + session.clearMessages(); host.runQueuedImmediateCallbacks(); assert.isFalse(hasError); - checkErrorMessage(host, "semanticDiag", { file: untitledFile, diagnostics: [] }); + checkErrorMessage(session, "semanticDiag", { file: untitledFile, diagnostics: [] }); - checkCompleteEvent(host, 2, expectedSequenceId); + checkCompleteEvent(session, 2, expectedSequenceId); } it("has projectRoot", () => { @@ -2942,7 +2935,7 @@ namespace ts.projectSystem { verifyErrorsInApp(); function verifyErrorsInApp() { - host.clearOutput(); + session.clearMessages(); const expectedSequenceId = session.getNextSeq(); session.executeCommandSeq({ command: server.CommandNames.Geterr, @@ -2952,13 +2945,13 @@ namespace ts.projectSystem { } }); host.checkTimeoutQueueLengthAndRun(1); - checkErrorMessage(host, "syntaxDiag", { file: app.path, diagnostics: [] }); - host.clearOutput(); + checkErrorMessage(session, "syntaxDiag", { file: app.path, diagnostics: [] }); + session.clearMessages(); host.runQueuedImmediateCallbacks(); - checkErrorMessage(host, "semanticDiag", { file: app.path, diagnostics: [] }); - checkCompleteEvent(host, 2, expectedSequenceId); - host.clearOutput(); + checkErrorMessage(session, "semanticDiag", { file: app.path, diagnostics: [] }); + checkCompleteEvent(session, 2, expectedSequenceId); + session.clearMessages(); } }); }); @@ -3683,7 +3676,7 @@ namespace ts.projectSystem { } }); checkNumberOfProjects(service, { inferredProjects: 1 }); - host.clearOutput(); + session.clearMessages(); const expectedSequenceId = session.getNextSeq(); session.executeCommandSeq({ command: server.CommandNames.Geterr, @@ -3694,23 +3687,24 @@ namespace ts.projectSystem { }); host.checkTimeoutQueueLengthAndRun(1); - checkErrorMessage(host, "syntaxDiag", { file: file1.path, diagnostics: [] }); - host.clearOutput(); + checkErrorMessage(session, "syntaxDiag", { file: file1.path, diagnostics: [] }); + session.clearMessages(); host.runQueuedImmediateCallbacks(); const moduleNotFound = Diagnostics.Cannot_find_module_0; const startOffset = file1.content.indexOf('"') + 1; - checkErrorMessage(host, "semanticDiag", { + checkErrorMessage(session, "semanticDiag", { file: file1.path, diagnostics: [{ start: { line: 1, offset: startOffset }, end: { line: 1, offset: startOffset + '"pad"'.length }, text: formatStringFromArgs(moduleNotFound.message, ["pad"]), code: moduleNotFound.code, - category: DiagnosticCategory[moduleNotFound.category].toLowerCase() + category: DiagnosticCategory[moduleNotFound.category].toLowerCase(), + source: undefined }] }); - checkCompleteEvent(host, 2, expectedSequenceId); - host.clearOutput(); + checkCompleteEvent(session, 2, expectedSequenceId); + session.clearMessages(); const padIndex: FileOrFolder = { path: `${folderPath}/node_modules/@types/pad/index.d.ts`, @@ -3719,15 +3713,15 @@ namespace ts.projectSystem { files.push(padIndex); host.reloadFS(files, { ignoreWatchInvokedWithTriggerAsFileCreate: true }); host.runQueuedTimeoutCallbacks(); - checkProjectUpdatedInBackgroundEvent(host, [file1.path]); - host.clearOutput(); + checkProjectUpdatedInBackgroundEvent(session, [file1.path]); + session.clearMessages(); host.runQueuedTimeoutCallbacks(); - checkErrorMessage(host, "syntaxDiag", { file: file1.path, diagnostics: [] }); - host.clearOutput(); + checkErrorMessage(session, "syntaxDiag", { file: file1.path, diagnostics: [] }); + session.clearMessages(); host.runQueuedImmediateCallbacks(); - checkErrorMessage(host, "semanticDiag", { file: file1.path, diagnostics: [] }); + checkErrorMessage(session, "semanticDiag", { file: file1.path, diagnostics: [] }); }); }); @@ -4841,7 +4835,7 @@ namespace ts.projectSystem { command: "projectInfo", arguments: { file: f1.path } }); - host.clearOutput(); + session.clearMessages(); // cancel previously issued Geterr cancellationToken.setRequestToCancel(getErrId); @@ -4865,7 +4859,7 @@ namespace ts.projectSystem { assert.equal(host.getOutput().length, 1, "expect 1 message"); const e1 = getMessage(0); assert.equal(e1.event, "syntaxDiag"); - host.clearOutput(); + session.clearMessages(); cancellationToken.setRequestToCancel(getErrId); host.runQueuedImmediateCallbacks(); @@ -4887,7 +4881,7 @@ namespace ts.projectSystem { assert.equal(host.getOutput().length, 1, "expect 1 message"); const e1 = getMessage(0); assert.equal(e1.event, "syntaxDiag"); - host.clearOutput(); + session.clearMessages(); // the semanticDiag message host.runQueuedImmediateCallbacks(); @@ -4910,7 +4904,7 @@ namespace ts.projectSystem { assert.equal(host.getOutput().length, 1, "expect 1 message"); const e1 = getMessage(0); assert.equal(e1.event, "syntaxDiag"); - host.clearOutput(); + session.clearMessages(); session.executeCommandSeq({ command: "geterr", @@ -4924,7 +4918,7 @@ namespace ts.projectSystem { const event = getMessage(n); assert.equal(event.event, "requestCompleted"); assert.equal(event.body.request_seq, expectedSeq, "expectedSeq"); - host.clearOutput(); + session.clearMessages(); } function getMessage(n: number) { @@ -6427,7 +6421,7 @@ namespace ts.projectSystem { }); // Verified the events, reset them - host.clearOutput(); + session.clearMessages(); } } }); diff --git a/src/server/editorServices.ts b/src/server/editorServices.ts index 9ac0997f117..5e49d03509a 100644 --- a/src/server/editorServices.ts +++ b/src/server/editorServices.ts @@ -1447,7 +1447,9 @@ namespace ts.server { } this.seenProjects.set(projectKey, true); - if (!this.eventHandler) return; + if (!this.eventHandler) { + return; + } const data: ProjectInfoTelemetryEventData = { projectId: this.host.createHash(projectKey), diff --git a/src/server/server.ts b/src/server/server.ts index f4faa0d3c77..fb0b3b781af 100644 --- a/src/server/server.ts +++ b/src/server/server.ts @@ -1,4 +1,3 @@ -/// /// /// @@ -7,7 +6,11 @@ namespace ts.server { host: ServerHost; cancellationToken: ServerCancellationToken; canUseEvents: boolean; - installerEventPort: number; + /** + * If defined, specifies the socket used to send events to the client. + * Otherwise, events are sent through the host. + */ + eventPort?: number; useSingleInferredProject: boolean; useInferredProjectPerProjectRoot: boolean; disableAutomaticTypingAcquisition: boolean; @@ -22,10 +25,6 @@ namespace ts.server { allowLocalPluginLoads: boolean; } - const net: { - connect(options: { port: number }, onConnect?: () => void): NodeSocket - } = require("net"); - const childProcess: { fork(modulePath: string, args: string[], options?: { execArgv: string[], env?: MapLike }): NodeChildProcess; execFileSync(file: string, args: string[], options: { stdio: "ignore", env: MapLike }): string | Buffer; @@ -36,6 +35,14 @@ namespace ts.server { tmpdir(): string; } = require("os"); + interface NodeSocket { + write(data: string, encoding: string): boolean; + } + + const net: { + connect(options: { port: number }, onConnect?: () => void): NodeSocket + } = require("net"); + function getGlobalTypingsCacheLocation() { switch (process.platform) { case "win32": { @@ -83,10 +90,6 @@ namespace ts.server { pid: number; } - interface NodeSocket { - write(data: string, encoding: string): boolean; - } - interface ReadLineOptions { input: NodeJS.ReadableStream; output?: NodeJS.WritableStream; @@ -243,10 +246,7 @@ namespace ts.server { class NodeTypingsInstaller implements ITypingsInstaller { private installer: NodeChildProcess; - private installerPidReported = false; - private socket: NodeSocket; private projectService: ProjectService; - private eventSender: EventSender; private activeRequestCount = 0; private requestQueue: QueuedOperation[] = []; private requestMap = createMap(); // Maps operation ID to newest requestQueue entry with that ID @@ -267,18 +267,11 @@ namespace ts.server { private readonly telemetryEnabled: boolean, private readonly logger: server.Logger, private readonly host: ServerHost, - eventPort: number, readonly globalTypingsCacheLocation: string, readonly typingSafeListLocation: string, readonly typesMapLocation: string, private readonly npmLocation: string | undefined, - private newLine: string) { - if (eventPort) { - const s = net.connect({ port: eventPort }, () => { - this.socket = s; - this.reportInstallerProcessId(); - }); - } + private event: Event) { } isKnownTypesPackageName(name: string): boolean { @@ -306,24 +299,6 @@ namespace ts.server { }); } - private reportInstallerProcessId() { - if (this.installerPidReported) { - return; - } - if (this.socket && this.installer) { - this.sendEvent(0, "typingsInstallerPid", { pid: this.installer.pid }); - this.installerPidReported = true; - } - } - - private sendEvent(seq: number, event: string, body: any): void { - this.socket.write(formatMessage({ seq, type: "event", event, body }, this.logger, Buffer.byteLength, this.newLine), "utf8"); - } - - setTelemetrySender(telemetrySender: EventSender) { - this.eventSender = telemetrySender; - } - attach(projectService: ProjectService) { this.projectService = projectService; if (this.logger.hasLevel(LogLevel.requestTime)) { @@ -363,7 +338,8 @@ namespace ts.server { this.installer = childProcess.fork(combinePaths(__dirname, "typingsInstaller.js"), args, { execArgv }); this.installer.on("message", m => this.handleMessage(m)); - this.reportInstallerProcessId(); + + this.event({ pid: this.installer.pid }, "typingsInstallerPid"); process.on("exit", () => { this.installer.kill(); @@ -428,92 +404,81 @@ namespace ts.server { break; } case EventInitializationFailed: - { - if (!this.eventSender) { - break; - } - const body: protocol.TypesInstallerInitializationFailedEventBody = { - message: response.message - }; - const eventName: protocol.TypesInstallerInitializationFailedEventName = "typesInstallerInitializationFailed"; - this.eventSender.event(body, eventName); - break; - } - case EventBeginInstallTypes: - { - if (!this.eventSender) { - break; - } - const body: protocol.BeginInstallTypesEventBody = { - eventId: response.eventId, - packages: response.packagesToInstall, - }; - const eventName: protocol.BeginInstallTypesEventName = "beginInstallTypes"; - this.eventSender.event(body, eventName); - break; - } - case EventEndInstallTypes: - { - if (!this.eventSender) { - break; - } - if (this.telemetryEnabled) { - const body: protocol.TypingsInstalledTelemetryEventBody = { - telemetryEventName: "typingsInstalled", - payload: { - installedPackages: response.packagesToInstall.join(","), - installSuccess: response.installSuccess, - typingsInstallerVersion: response.typingsInstallerVersion - } + { + const body: protocol.TypesInstallerInitializationFailedEventBody = { + message: response.message }; - const eventName: protocol.TelemetryEventName = "telemetry"; - this.eventSender.event(body, eventName); + const eventName: protocol.TypesInstallerInitializationFailedEventName = "typesInstallerInitializationFailed"; + this.event(body, eventName); + break; } + case EventBeginInstallTypes: + { + const body: protocol.BeginInstallTypesEventBody = { + eventId: response.eventId, + packages: response.packagesToInstall, + }; + const eventName: protocol.BeginInstallTypesEventName = "beginInstallTypes"; + this.event(body, eventName); + break; + } + case EventEndInstallTypes: + { + if (this.telemetryEnabled) { + const body: protocol.TypingsInstalledTelemetryEventBody = { + telemetryEventName: "typingsInstalled", + payload: { + installedPackages: response.packagesToInstall.join(","), + installSuccess: response.installSuccess, + typingsInstallerVersion: response.typingsInstallerVersion + } + }; + const eventName: protocol.TelemetryEventName = "telemetry"; + this.event(body, eventName); + } - const body: protocol.EndInstallTypesEventBody = { - eventId: response.eventId, - packages: response.packagesToInstall, - success: response.installSuccess, - }; - const eventName: protocol.EndInstallTypesEventName = "endInstallTypes"; - this.eventSender.event(body, eventName); - break; - } + const body: protocol.EndInstallTypesEventBody = { + eventId: response.eventId, + packages: response.packagesToInstall, + success: response.installSuccess, + }; + const eventName: protocol.EndInstallTypesEventName = "endInstallTypes"; + this.event(body, eventName); + break; + } case ActionInvalidate: - { - this.projectService.updateTypingsForProject(response); - break; - } + { + this.projectService.updateTypingsForProject(response); + break; + } case ActionSet: - { - if (this.activeRequestCount > 0) { - this.activeRequestCount--; - } - else { - Debug.fail("Received too many responses"); - } - - while (this.requestQueue.length > 0) { - const queuedRequest = this.requestQueue.shift(); - if (this.requestMap.get(queuedRequest.operationId) === queuedRequest) { - this.requestMap.delete(queuedRequest.operationId); - this.scheduleRequest(queuedRequest); - break; + { + if (this.activeRequestCount > 0) { + this.activeRequestCount--; + } + else { + Debug.fail("Received too many responses"); } - if (this.logger.hasLevel(LogLevel.verbose)) { - this.logger.info(`Skipping defunct request for: ${queuedRequest.operationId}`); + while (this.requestQueue.length > 0) { + const queuedRequest = this.requestQueue.shift(); + if (this.requestMap.get(queuedRequest.operationId) === queuedRequest) { + this.requestMap.delete(queuedRequest.operationId); + this.scheduleRequest(queuedRequest); + break; + } + + if (this.logger.hasLevel(LogLevel.verbose)) { + this.logger.info(`Skipping defunct request for: ${queuedRequest.operationId}`); + } } + + this.projectService.updateTypingsForProject(response); + + this.event(response, "setTypings"); + + break; } - - this.projectService.updateTypingsForProject(response); - - if (this.socket) { - this.sendEvent(0, "setTypings", response); - } - - break; - } default: assertTypeIsNever(response); } @@ -529,11 +494,30 @@ namespace ts.server { } class IOSession extends Session { + private eventPort: number; + private eventSocket: NodeSocket | undefined; + private socketEventQueue: { body: any, eventName: string }[] | undefined; + private constructed: boolean | undefined; + constructor(options: IoSessionOptions) { - const { host, installerEventPort, globalTypingsCacheLocation, typingSafeListLocation, typesMapLocation, npmLocation, canUseEvents } = options; + const { host, eventPort, globalTypingsCacheLocation, typingSafeListLocation, typesMapLocation, npmLocation, canUseEvents } = options; + + const event: Event | undefined = (body: {}, eventName: string) => { + if (this.constructed) { + this.event(body, eventName); + } + else { + // It is unsafe to dereference `this` before initialization completes, + // so we defer until the next tick. + // + // Construction should finish before the next tick fires, so we do not need to do this recursively. + setImmediate(() => this.event(body, eventName)); + } + }; + const typingsInstaller = disableAutomaticTypingAcquisition ? undefined - : new NodeTypingsInstaller(telemetryEnabled, logger, host, installerEventPort, globalTypingsCacheLocation, typingSafeListLocation, typesMapLocation, npmLocation, host.newLine); + : new NodeTypingsInstaller(telemetryEnabled, logger, host, globalTypingsCacheLocation, typingSafeListLocation, typesMapLocation, npmLocation, event); super({ host, @@ -547,11 +531,49 @@ namespace ts.server { canUseEvents, globalPlugins: options.globalPlugins, pluginProbeLocations: options.pluginProbeLocations, - allowLocalPluginLoads: options.allowLocalPluginLoads }); + allowLocalPluginLoads: options.allowLocalPluginLoads + }); - if (telemetryEnabled && typingsInstaller) { - typingsInstaller.setTelemetrySender(this); + this.eventPort = eventPort; + if (this.canUseEvents && this.eventPort) { + const s = net.connect({ port: this.eventPort }, () => { + this.eventSocket = s; + if (this.socketEventQueue) { + // flush queue. + for (const event of this.socketEventQueue) { + this.writeToEventSocket(event.body, event.eventName); + } + this.socketEventQueue = undefined; + } + }); } + + this.constructed = true; + } + + event(body: T, eventName: string): void { + Debug.assert(this.constructed, "Should only call `IOSession.prototype.event` on an initialized IOSession"); + + if (this.canUseEvents && this.eventPort) { + if (!this.eventSocket) { + if (this.logger.hasLevel(LogLevel.verbose)) { + this.logger.info(`eventPort: event "${eventName}" queued, but socket not yet initialized`); + } + (this.socketEventQueue || (this.socketEventQueue = [])).push({ body, eventName }); + return; + } + else { + Debug.assert(this.socketEventQueue === undefined); + this.writeToEventSocket(body, eventName); + } + } + else { + super.event(body, eventName); + } + } + + private writeToEventSocket(body: any, eventName: string): void { + this.eventSocket.write(formatMessage(toEvent(body, eventName), this.logger, this.byteLength, this.host.newLine), "utf8"); } exit() { @@ -896,7 +918,7 @@ namespace ts.server { cancellationToken = nullCancellationToken; } - let eventPort: number; + let eventPort: number | undefined; { const str = findArgument("--eventPort"); const v = str && parseInt(str); @@ -936,8 +958,8 @@ namespace ts.server { const options: IoSessionOptions = { host: sys, cancellationToken, - installerEventPort: eventPort, - canUseEvents: eventPort === undefined, + eventPort, + canUseEvents: true, useSingleInferredProject, useInferredProjectPerProjectRoot, disableAutomaticTypingAcquisition, diff --git a/src/server/session.ts b/src/server/session.ts index ea95f7a4f42..9e6378ebebf 100644 --- a/src/server/session.ts +++ b/src/server/session.ts @@ -105,10 +105,6 @@ namespace ts.server { project: Project; } - export interface EventSender { - event(payload: T, eventName: string): void; - } - function allEditsBeforePos(edits: ts.TextChange[], pos: number) { for (const edit of edits) { if (textSpanEnd(edit.span) >= pos) { @@ -243,6 +239,22 @@ namespace ts.server { } } + export type Event = (body: T, eventName: string) => void; + + export interface EventSender { + event: Event; + } + + /** @internal */ + export function toEvent(eventName: string, body: {}): protocol.Event { + return { + seq: 0, + type: "event", + event: eventName, + body + }; + } + export interface SessionOptions { host: ServerHost; cancellationToken: ServerCancellationToken; @@ -252,6 +264,9 @@ namespace ts.server { byteLength: (buf: string, encoding?: string) => number; hrtime: (start?: number[]) => number[]; logger: Logger; + /** + * If falsy, all events are suppressed. + */ canUseEvents: boolean; eventHandler?: ProjectServiceEventHandler; throttleWaitMilliseconds?: number; @@ -269,15 +284,15 @@ namespace ts.server { private currentRequestId: number; private errorCheck: MultistepOperation; - private eventHandler: ProjectServiceEventHandler; - - private host: ServerHost; + protected host: ServerHost; private readonly cancellationToken: ServerCancellationToken; protected readonly typingsInstaller: ITypingsInstaller; - private byteLength: (buf: string, encoding?: string) => number; + protected byteLength: (buf: string, encoding?: string) => number; private hrtime: (start?: number[]) => number[]; protected logger: Logger; - private canUseEvents: boolean; + + protected canUseEvents: boolean; + private eventHandler: ProjectServiceEventHandler; constructor(opts: SessionOptions) { this.host = opts.host; @@ -293,7 +308,6 @@ namespace ts.server { this.eventHandler = this.canUseEvents ? opts.eventHandler || (event => this.defaultEventHandler(event)) : undefined; - const multistepOperationHost: MultistepOperationHost = { executeWithRequestId: (requestId, action) => this.executeWithRequestId(requestId, action), getCurrentRequestId: () => this.currentRequestId, @@ -321,13 +335,7 @@ namespace ts.server { } private sendRequestCompletedEvent(requestId: number): void { - const event: protocol.RequestCompletedEvent = { - seq: 0, - type: "event", - event: "requestCompleted", - body: { request_seq: requestId } - }; - this.send(event); + this.event({ request_seq: requestId }, "requestCompleted"); } private defaultEventHandler(event: ProjectServiceEvent) { @@ -401,17 +409,12 @@ namespace ts.server { this.host.write(formatMessage(msg, this.logger, this.byteLength, this.host.newLine)); } - public event(info: T, eventName: string) { - const ev: protocol.Event = { - seq: 0, - type: "event", - event: eventName, - body: info - }; - this.send(ev); + public event(body: T, eventName: string): void { + this.send(toEvent(eventName, body)); } // For backwards-compatibility only. + /** @deprecated */ public output(info: any, cmdName: string, reqSeq?: number, errorMsg?: string): void { this.doOutput(info, cmdName, reqSeq, /*success*/ !errorMsg, errorMsg); } diff --git a/tests/baselines/reference/api/tsserverlibrary.d.ts b/tests/baselines/reference/api/tsserverlibrary.d.ts index 407e5dec707..d3a97c75690 100644 --- a/tests/baselines/reference/api/tsserverlibrary.d.ts +++ b/tests/baselines/reference/api/tsserverlibrary.d.ts @@ -6922,12 +6922,13 @@ declare namespace ts.server { fileName: NormalizedPath; project: Project; } - interface EventSender { - event(payload: T, eventName: string): void; - } type CommandNames = protocol.CommandTypes; const CommandNames: any; function formatMessage(msg: T, logger: server.Logger, byteLength: (s: string, encoding: string) => number, newLine: string): string; + type Event = (body: T, eventName: string) => void; + interface EventSender { + event: Event; + } interface SessionOptions { host: ServerHost; cancellationToken: ServerCancellationToken; @@ -6937,6 +6938,9 @@ declare namespace ts.server { byteLength: (buf: string, encoding?: string) => number; hrtime: (start?: number[]) => number[]; logger: Logger; + /** + * If falsy, all events are suppressed. + */ canUseEvents: boolean; eventHandler?: ProjectServiceEventHandler; throttleWaitMilliseconds?: number; @@ -6950,21 +6954,22 @@ declare namespace ts.server { private changeSeq; private currentRequestId; private errorCheck; - private eventHandler; - private host; + protected host: ServerHost; private readonly cancellationToken; protected readonly typingsInstaller: ITypingsInstaller; - private byteLength; + protected byteLength: (buf: string, encoding?: string) => number; private hrtime; protected logger: Logger; - private canUseEvents; + protected canUseEvents: boolean; + private eventHandler; constructor(opts: SessionOptions); private sendRequestCompletedEvent(requestId); private defaultEventHandler(event); private projectsUpdatedInBackgroundEvent(openFiles); logError(err: Error, cmd: string): void; send(msg: protocol.Message): void; - event(info: T, eventName: string): void; + event(body: T, eventName: string): void; + /** @deprecated */ output(info: any, cmdName: string, reqSeq?: number, errorMsg?: string): void; private doOutput(info, cmdName, reqSeq, success, message?); private semanticCheck(file, project);