From 1904650d0e9168f387f499ff977a3c9d98dabd8f Mon Sep 17 00:00:00 2001 From: Nathan Shively-Sanders <293473+sandersn@users.noreply.github.com> Date: Thu, 9 Aug 2018 16:28:48 -0700 Subject: [PATCH 1/7] Start adding survey event --- src/server/editorServices.ts | 27 ++++++++++++++++++- .../unittests/tsserverProjectSystem.ts | 2 +- 2 files changed, 27 insertions(+), 2 deletions(-) diff --git a/src/server/editorServices.ts b/src/server/editorServices.ts index 56026bb70d46f..f2a64f195e6a4 100644 --- a/src/server/editorServices.ts +++ b/src/server/editorServices.ts @@ -5,6 +5,7 @@ namespace ts.server { // tslint:disable variable-name export const ProjectsUpdatedInBackgroundEvent = "projectsUpdatedInBackground"; + export const SurveyReady = "surveyReady"; export const ConfigFileDiagEvent = "configFileDiag"; export const ProjectLanguageServiceStateEvent = "projectLanguageServiceState"; export const ProjectInfoTelemetryEvent = "projectInfo"; @@ -16,6 +17,11 @@ namespace ts.server { data: { openFiles: string[]; }; } + export interface SurveyReady { + eventName: typeof SurveyReady; + data: { surveyId: string; url: string }; + } + export interface ConfigFileDiagEvent { eventName: typeof ConfigFileDiagEvent; data: { triggerFile: string, configFileName: string, diagnostics: ReadonlyArray }; @@ -92,7 +98,11 @@ namespace ts.server { readonly checkJs: boolean; } - export type ProjectServiceEvent = ProjectsUpdatedInBackgroundEvent | ConfigFileDiagEvent | ProjectLanguageServiceStateEvent | ProjectInfoTelemetryEvent | OpenFileInfoTelemetryEvent; + export type ProjectServiceEvent = ProjectsUpdatedInBackgroundEvent | ConfigFileDiagEvent | ProjectLanguageServiceStateEvent | ProjectInfoTelemetryEvent | OpenFileInfoTelemetryEvent | SurveyReady; + // 1. notice that the survey should be ready + // 2. prepare the data structure + // 3. fire off the data structure to ??? + // "project configuration change" might be the place I'm looking for, and Sheetal might the person who knows about it export type ProjectServiceEventHandler = (event: ProjectServiceEvent) => void; @@ -645,6 +655,21 @@ namespace ts.server { this.eventHandler(event); } + /* @internal */ + sendSurveyReadyEvent() { + if (!this.eventHandler) { + return; + } + const event: SurveyReady = { + eventName: SurveyReady, + data: { + surveyId: "checkjs", + url: "https://surveymonkey.egg" + } + }; + this.eventHandler(event); + } + /* @internal */ delayUpdateProjectGraphAndEnsureProjectStructureForOpenFiles(project: Project) { this.delayUpdateProjectGraph(project); diff --git a/src/testRunner/unittests/tsserverProjectSystem.ts b/src/testRunner/unittests/tsserverProjectSystem.ts index c89d29ffc5b51..999bb61a4964f 100644 --- a/src/testRunner/unittests/tsserverProjectSystem.ts +++ b/src/testRunner/unittests/tsserverProjectSystem.ts @@ -2843,7 +2843,7 @@ namespace ts.projectSystem { const session = createSession(host, { canUseEvents: true, eventHandler: e => { - if (e.eventName === server.ConfigFileDiagEvent || e.eventName === server.ProjectsUpdatedInBackgroundEvent || e.eventName === server.ProjectInfoTelemetryEvent || e.eventName === server.OpenFileInfoTelemetryEvent) { + if (e.eventName === server.ConfigFileDiagEvent || e.eventName === server.ProjectsUpdatedInBackgroundEvent || e.eventName === server.ProjectInfoTelemetryEvent || e.eventName === server.OpenFileInfoTelemetryEvent || e.eventName === server.SurveyReady) { return; } assert.equal(e.eventName, server.ProjectLanguageServiceStateEvent); From 87ba7fcf8d9d828866ad7e922a78c22848c7c057 Mon Sep 17 00:00:00 2001 From: Nathan Shively-Sanders <293473+sandersn@users.noreply.github.com> Date: Tue, 14 Aug 2018 13:58:52 -0700 Subject: [PATCH 2/7] Add surveyReady event --- src/server/editorServices.ts | 16 +++++++--------- src/server/protocol.ts | 14 ++++++++++++++ src/server/session.ts | 4 ++++ 3 files changed, 25 insertions(+), 9 deletions(-) diff --git a/src/server/editorServices.ts b/src/server/editorServices.ts index f2a64f195e6a4..3677ee6ce2953 100644 --- a/src/server/editorServices.ts +++ b/src/server/editorServices.ts @@ -656,18 +656,11 @@ namespace ts.server { } /* @internal */ - sendSurveyReadyEvent() { + sendSurveyReadyEvent(surveyId: string, url: string) { if (!this.eventHandler) { return; } - const event: SurveyReady = { - eventName: SurveyReady, - data: { - surveyId: "checkjs", - url: "https://surveymonkey.egg" - } - }; - this.eventHandler(event); + this.eventHandler({ eventName: SurveyReady, data: { surveyId, url } }); } /* @internal */ @@ -905,6 +898,11 @@ namespace ts.server { this.logConfigFileWatchUpdate(project.getConfigFilePath(), project.canonicalConfigFilePath, configFileExistenceInfo, ConfigFileWatcherStatus.ReloadingInferredRootFiles); project.pendingReload = ConfigFileProgramReloadLevel.Full; this.delayUpdateProjectGraph(project); + if (project.getCompilerOptions().checkJs !== undefined) { + const name = "checkJs"; + project.projectService.logger.info(`Survey ${name} is ready`); + project.projectService.sendSurveyReadyEvent(name, "http://NOT READY YET"); + } // As we scheduled the update on configured project graph, // we would need to schedule the project reload for only the root of inferred projects this.delayReloadConfiguredProjectForFiles(configFileExistenceInfo, /*ignoreIfNotInferredProjectRoot*/ true); diff --git a/src/server/protocol.ts b/src/server/protocol.ts index a04b7fe5da8b8..45d14e0b735dc 100644 --- a/src/server/protocol.ts +++ b/src/server/protocol.ts @@ -2436,6 +2436,20 @@ namespace ts.server.protocol { openFiles: string[]; } + export type SurveyReadyEventName = "surveyReady"; + + export interface SurveyReadyEvent extends Event { + event: SurveyReadyEventName; + body: SurveyReadyEventBody; + } + + export interface SurveyReadyEventBody { + /** Name of the survey. This is an internal machine- and programmer-friendly name */ + surveyId: string; + /** Url of the survey */ + url: string; + } + /** * Arguments for reload request. */ diff --git a/src/server/session.ts b/src/server/session.ts index 2bb87ee25d43d..1872e6e233f13 100644 --- a/src/server/session.ts +++ b/src/server/session.ts @@ -568,6 +568,10 @@ namespace ts.server { diagnostics: bakedDiags }, "configFileDiag"); break; + case SurveyReady: + const { surveyId, url } = event.data; + this.event({ surveyId, url }, "surveyReady"); + break; case ProjectLanguageServiceStateEvent: { const eventName: protocol.ProjectLanguageServiceStateEventName = "projectLanguageServiceState"; this.event({ From fccf6a224dae703f8c8d6ded2e3b60e4025536c6 Mon Sep 17 00:00:00 2001 From: Nathan Shively-Sanders <293473+sandersn@users.noreply.github.com> Date: Tue, 14 Aug 2018 14:04:37 -0700 Subject: [PATCH 3/7] Remove old notes --- src/server/editorServices.ts | 4 ---- 1 file changed, 4 deletions(-) diff --git a/src/server/editorServices.ts b/src/server/editorServices.ts index 3677ee6ce2953..5a0deba5c714e 100644 --- a/src/server/editorServices.ts +++ b/src/server/editorServices.ts @@ -99,10 +99,6 @@ namespace ts.server { } export type ProjectServiceEvent = ProjectsUpdatedInBackgroundEvent | ConfigFileDiagEvent | ProjectLanguageServiceStateEvent | ProjectInfoTelemetryEvent | OpenFileInfoTelemetryEvent | SurveyReady; - // 1. notice that the survey should be ready - // 2. prepare the data structure - // 3. fire off the data structure to ??? - // "project configuration change" might be the place I'm looking for, and Sheetal might the person who knows about it export type ProjectServiceEventHandler = (event: ProjectServiceEvent) => void; From e28c24052eb90286e7a103c27c9787fc7b56430f Mon Sep 17 00:00:00 2001 From: Nathan Shively-Sanders <293473+sandersn@users.noreply.github.com> Date: Wed, 15 Aug 2018 16:09:19 -0700 Subject: [PATCH 4/7] Move event, simplify type, add test 1. Move the survey event to sendProjectTelemetry so that it happens on open instead of on editing tsconfig. 2. Remove URL from the survey type; VS code should control this information. 3. Add test based on large files event test. I'm not sure it's in the right place, though. --- src/server/editorServices.ts | 21 ++-- src/server/protocol.ts | 4 +- src/server/session.ts | 4 +- .../unittests/tsserverProjectSystem.ts | 114 ++++++++++++++++++ 4 files changed, 129 insertions(+), 14 deletions(-) diff --git a/src/server/editorServices.ts b/src/server/editorServices.ts index b27c0f275fdfe..5f1b6fcdc1804 100644 --- a/src/server/editorServices.ts +++ b/src/server/editorServices.ts @@ -20,7 +20,7 @@ namespace ts.server { export interface SurveyReady { eventName: typeof SurveyReady; - data: { surveyId: string; url: string }; + data: { surveyId: string; }; } export interface LargeFileReferencedEvent { @@ -658,11 +658,11 @@ namespace ts.server { } /* @internal */ - sendSurveyReadyEvent(surveyId: string, url: string) { + sendSurveyReadyEvent(surveyId: string) { if (!this.eventHandler) { return; } - this.eventHandler({ eventName: SurveyReady, data: { surveyId, url } }); + this.eventHandler({ eventName: SurveyReady, data: { surveyId } }); } /* @internal */ @@ -913,11 +913,6 @@ namespace ts.server { this.logConfigFileWatchUpdate(project.getConfigFilePath(), project.canonicalConfigFilePath, configFileExistenceInfo, ConfigFileWatcherStatus.ReloadingInferredRootFiles); project.pendingReload = ConfigFileProgramReloadLevel.Full; this.delayUpdateProjectGraph(project); - if (project.getCompilerOptions().checkJs !== undefined) { - const name = "checkJs"; - project.projectService.logger.info(`Survey ${name} is ready`); - project.projectService.sendSurveyReadyEvent(name, "http://NOT READY YET"); - } // As we scheduled the update on configured project graph, // we would need to schedule the project reload for only the root of inferred projects this.delayReloadConfiguredProjectForFiles(configFileExistenceInfo, /*ignoreIfNotInferredProjectRoot*/ true); @@ -1532,7 +1527,15 @@ namespace ts.server { } this.seenProjects.set(projectKey, true); - if (!this.eventHandler || !this.host.createSHA256Hash) { + if (!this.eventHandler) { + return; + } + if (project.getCompilerOptions().checkJs !== undefined) { + const name = "checkJs"; + this.logger.info(`Survey ${name} is ready`); + this.sendSurveyReadyEvent(name); + } + if (!this.host.createSHA256Hash) { return; } diff --git a/src/server/protocol.ts b/src/server/protocol.ts index ad5873aa9140e..89417082b4a72 100644 --- a/src/server/protocol.ts +++ b/src/server/protocol.ts @@ -2446,10 +2446,8 @@ namespace ts.server.protocol { export interface SurveyReadyEventBody { /** Name of the survey. This is an internal machine- and programmer-friendly name */ surveyId: string; - /** Url of the survey */ - url: string; } - + export type LargeFileReferencedEventName = "largeFileReferenced"; export interface LargeFileReferencedEvent extends Event { event: LargeFileReferencedEventName; diff --git a/src/server/session.ts b/src/server/session.ts index c6ff2c5ed096b..97541d1995552 100644 --- a/src/server/session.ts +++ b/src/server/session.ts @@ -573,8 +573,8 @@ namespace ts.server { }, "configFileDiag"); break; case SurveyReady: - const { surveyId, url } = event.data; - this.event({ surveyId, url }, "surveyReady"); + const { surveyId } = event.data; + this.event({ surveyId }, "surveyReady"); break; case ProjectLanguageServiceStateEvent: { const eventName: protocol.ProjectLanguageServiceStateEventName = "projectLanguageServiceState"; diff --git a/src/testRunner/unittests/tsserverProjectSystem.ts b/src/testRunner/unittests/tsserverProjectSystem.ts index ab097ece57cd2..7de79b4befa07 100644 --- a/src/testRunner/unittests/tsserverProjectSystem.ts +++ b/src/testRunner/unittests/tsserverProjectSystem.ts @@ -642,6 +642,51 @@ namespace ts.projectSystem { checkWatchedDirectories(host, [combinePaths(getDirectoryPath(appFile.path), nodeModulesAtTypes)], /*recursive*/ true); }); + function createSessionWithEventHandler(host: TestServerHost) { + const surveyEvents: server.SurveyReady[] = []; + const session = createSession(host, { + eventHandler: e => { + if (e.eventName === server.SurveyReady) { + surveyEvents.push(e); + } + } + }); + + return { session, verifySurveyReadyEvent }; + + function verifySurveyReadyEvent() { + assert.equal(surveyEvents.length, 1); + assert.deepEqual(surveyEvents, [{ + eventName: server.SurveyReady, + data: { surveyId: "checkJs" } + }]); + } + } + + + it("logs an event when checkJs is set", () => { + const projectRoot = "/user/username/projects/project"; + const file: File = { + path: `${projectRoot}/src/file.ts`, + content: "export var y = 10;" + }; + const tsconfig: File = { + path: `${projectRoot}/tsconfig.json`, + content: JSON.stringify({ compilerOptions: { checkJs: true } }), + }; + const host = createServerHost([file, tsconfig]); + const { session, verifySurveyReadyEvent } = createSessionWithEventHandler(host); + const service = session.getProjectService(); + openFilesForSession([file], session); + checkNumberOfProjects(service, { configuredProjects: 1 }); + const project = service.configuredProjects.get(tsconfig.path)!; + checkProjectActualFiles(project, [file.path, tsconfig.path]); + + host.runQueuedTimeoutCallbacks(); + + verifySurveyReadyEvent(); + }); + it("can handle tsconfig file name with difference casing", () => { const f1 = { path: "/a/b/app.ts", @@ -9090,6 +9135,75 @@ export const x = 10;` }); }); + describe("tsserverProjectSystem with large file", () => { + const projectRoot = "/user/username/projects/project"; + const largeFile: File = { + path: `${projectRoot}/src/large.ts`, + content: "export var x = 10;", + fileSize: server.maxFileSize + 1 + }; + + function createSessionWithEventHandler(host: TestServerHost) { + const largeFileReferencedEvents: server.LargeFileReferencedEvent[] = []; + const session = createSession(host, { + eventHandler: e => { + if (e.eventName === server.LargeFileReferencedEvent) { + largeFileReferencedEvents.push(e); + } + } + }); + + return { session, verifyLargeFileReferencedEvent }; + + function verifyLargeFileReferencedEvent() { + assert.equal(largeFileReferencedEvents.length, 1); + assert.deepEqual(largeFileReferencedEvents, [{ + eventName: server.LargeFileReferencedEvent, + data: { file: largeFile.path, fileSize: largeFile.fileSize, maxFileSize: server.maxFileSize } + }]); + } + } + + it("when large file is included by tsconfig", () => { + const file: File = { + path: `${projectRoot}/src/file.ts`, + content: "export var y = 10;" + }; + const tsconfig: File = { + path: `${projectRoot}/tsconfig.json`, + content: JSON.stringify({ files: ["src/file.ts", "src/large.ts"] }) + }; + const files = [file, largeFile, libFile, tsconfig]; + const host = createServerHost(files); + const { session, verifyLargeFileReferencedEvent } = createSessionWithEventHandler(host); + const service = session.getProjectService(); + openFilesForSession([file], session); + checkNumberOfProjects(service, { configuredProjects: 1 }); + const project = service.configuredProjects.get(tsconfig.path)!; + checkProjectActualFiles(project, [file.path, libFile.path, largeFile.path, tsconfig.path]); + const info = service.getScriptInfo(largeFile.path)!; + assert.equal(info.cacheSourceFile.sourceFile.text, ""); + verifyLargeFileReferencedEvent(); + }); + + it("when large file is included by module resolution", () => { + const file: File = { + path: `${projectRoot}/src/file.ts`, + content: `export var y = 10;import {x} from "./large"` + }; + const files = [file, largeFile, libFile]; + const host = createServerHost(files); + const { session, verifyLargeFileReferencedEvent } = createSessionWithEventHandler(host); + const service = session.getProjectService(); + openFilesForSession([file], session); + checkNumberOfProjects(service, { inferredProjects: 1 }); + const project = service.inferredProjects[0]; + checkProjectActualFiles(project, [file.path, libFile.path, largeFile.path]); + const info = service.getScriptInfo(largeFile.path)!; + assert.equal(info.cacheSourceFile.sourceFile.text, ""); + verifyLargeFileReferencedEvent(); + }); + }) describe("tsserverProjectSystem syntax operations", () => { function navBarFull(session: TestSession, file: File) { return JSON.stringify(session.executeCommandSeq({ From 17780b39dfa95cd22c86677d6e681f42f5ec2594 Mon Sep 17 00:00:00 2001 From: Nathan Shively-Sanders <293473+sandersn@users.noreply.github.com> Date: Thu, 16 Aug 2018 16:20:55 -0700 Subject: [PATCH 5/7] Fix tests and update API baseline --- .../unittests/tsserverProjectSystem.ts | 156 +++++------------- .../reference/api/tsserverlibrary.d.ts | 18 +- 2 files changed, 59 insertions(+), 115 deletions(-) diff --git a/src/testRunner/unittests/tsserverProjectSystem.ts b/src/testRunner/unittests/tsserverProjectSystem.ts index 7de79b4befa07..46488a4203628 100644 --- a/src/testRunner/unittests/tsserverProjectSystem.ts +++ b/src/testRunner/unittests/tsserverProjectSystem.ts @@ -642,51 +642,6 @@ namespace ts.projectSystem { checkWatchedDirectories(host, [combinePaths(getDirectoryPath(appFile.path), nodeModulesAtTypes)], /*recursive*/ true); }); - function createSessionWithEventHandler(host: TestServerHost) { - const surveyEvents: server.SurveyReady[] = []; - const session = createSession(host, { - eventHandler: e => { - if (e.eventName === server.SurveyReady) { - surveyEvents.push(e); - } - } - }); - - return { session, verifySurveyReadyEvent }; - - function verifySurveyReadyEvent() { - assert.equal(surveyEvents.length, 1); - assert.deepEqual(surveyEvents, [{ - eventName: server.SurveyReady, - data: { surveyId: "checkJs" } - }]); - } - } - - - it("logs an event when checkJs is set", () => { - const projectRoot = "/user/username/projects/project"; - const file: File = { - path: `${projectRoot}/src/file.ts`, - content: "export var y = 10;" - }; - const tsconfig: File = { - path: `${projectRoot}/tsconfig.json`, - content: JSON.stringify({ compilerOptions: { checkJs: true } }), - }; - const host = createServerHost([file, tsconfig]); - const { session, verifySurveyReadyEvent } = createSessionWithEventHandler(host); - const service = session.getProjectService(); - openFilesForSession([file], session); - checkNumberOfProjects(service, { configuredProjects: 1 }); - const project = service.configuredProjects.get(tsconfig.path)!; - checkProjectActualFiles(project, [file.path, tsconfig.path]); - - host.runQueuedTimeoutCallbacks(); - - verifySurveyReadyEvent(); - }); - it("can handle tsconfig file name with difference casing", () => { const f1 = { path: "/a/b/app.ts", @@ -3538,6 +3493,48 @@ namespace ts.projectSystem { } }); + function createSessionWithEventHandler(host: TestServerHost) { + const surveyEvents: server.SurveyReady[] = []; + const session = createSession(host, { + eventHandler: e => { + if (e.eventName === server.SurveyReady) { + surveyEvents.push(e); + } + } + }); + + return { session, verifySurveyReadyEvent }; + + function verifySurveyReadyEvent() { + assert.equal(surveyEvents.length, 1); + assert.deepEqual(surveyEvents, [{ + eventName: server.SurveyReady, + data: { surveyId: "checkJs" } + }]); + } + } + + it("logs an event when checkJs is set", () => { + const projectRoot = "/user/username/projects/project"; + const file: File = { + path: `${projectRoot}/src/file.ts`, + content: "export var y = 10;" + }; + const tsconfig: File = { + path: `${projectRoot}/tsconfig.json`, + content: JSON.stringify({ compilerOptions: { checkJs: true } }), + }; + const host = createServerHost([file, tsconfig]); + const { session, verifySurveyReadyEvent } = createSessionWithEventHandler(host); + const service = session.getProjectService(); + openFilesForSession([file], session); + checkNumberOfProjects(service, { configuredProjects: 1 }); + const project = service.configuredProjects.get(tsconfig.path)!; + checkProjectActualFiles(project, [file.path, tsconfig.path]); + + verifySurveyReadyEvent(); + }); + describe("CompileOnSaveAffectedFileListRequest with and without projectFileName in request", () => { const projectRoot = "/user/username/projects/myproject"; const core: File = { @@ -9135,75 +9132,6 @@ export const x = 10;` }); }); - describe("tsserverProjectSystem with large file", () => { - const projectRoot = "/user/username/projects/project"; - const largeFile: File = { - path: `${projectRoot}/src/large.ts`, - content: "export var x = 10;", - fileSize: server.maxFileSize + 1 - }; - - function createSessionWithEventHandler(host: TestServerHost) { - const largeFileReferencedEvents: server.LargeFileReferencedEvent[] = []; - const session = createSession(host, { - eventHandler: e => { - if (e.eventName === server.LargeFileReferencedEvent) { - largeFileReferencedEvents.push(e); - } - } - }); - - return { session, verifyLargeFileReferencedEvent }; - - function verifyLargeFileReferencedEvent() { - assert.equal(largeFileReferencedEvents.length, 1); - assert.deepEqual(largeFileReferencedEvents, [{ - eventName: server.LargeFileReferencedEvent, - data: { file: largeFile.path, fileSize: largeFile.fileSize, maxFileSize: server.maxFileSize } - }]); - } - } - - it("when large file is included by tsconfig", () => { - const file: File = { - path: `${projectRoot}/src/file.ts`, - content: "export var y = 10;" - }; - const tsconfig: File = { - path: `${projectRoot}/tsconfig.json`, - content: JSON.stringify({ files: ["src/file.ts", "src/large.ts"] }) - }; - const files = [file, largeFile, libFile, tsconfig]; - const host = createServerHost(files); - const { session, verifyLargeFileReferencedEvent } = createSessionWithEventHandler(host); - const service = session.getProjectService(); - openFilesForSession([file], session); - checkNumberOfProjects(service, { configuredProjects: 1 }); - const project = service.configuredProjects.get(tsconfig.path)!; - checkProjectActualFiles(project, [file.path, libFile.path, largeFile.path, tsconfig.path]); - const info = service.getScriptInfo(largeFile.path)!; - assert.equal(info.cacheSourceFile.sourceFile.text, ""); - verifyLargeFileReferencedEvent(); - }); - - it("when large file is included by module resolution", () => { - const file: File = { - path: `${projectRoot}/src/file.ts`, - content: `export var y = 10;import {x} from "./large"` - }; - const files = [file, largeFile, libFile]; - const host = createServerHost(files); - const { session, verifyLargeFileReferencedEvent } = createSessionWithEventHandler(host); - const service = session.getProjectService(); - openFilesForSession([file], session); - checkNumberOfProjects(service, { inferredProjects: 1 }); - const project = service.inferredProjects[0]; - checkProjectActualFiles(project, [file.path, libFile.path, largeFile.path]); - const info = service.getScriptInfo(largeFile.path)!; - assert.equal(info.cacheSourceFile.sourceFile.text, ""); - verifyLargeFileReferencedEvent(); - }); - }) describe("tsserverProjectSystem syntax operations", () => { function navBarFull(session: TestSession, file: File) { return JSON.stringify(session.executeCommandSeq({ diff --git a/tests/baselines/reference/api/tsserverlibrary.d.ts b/tests/baselines/reference/api/tsserverlibrary.d.ts index a16288c6c2725..5674234c07dd5 100644 --- a/tests/baselines/reference/api/tsserverlibrary.d.ts +++ b/tests/baselines/reference/api/tsserverlibrary.d.ts @@ -7660,6 +7660,15 @@ declare namespace ts.server.protocol { */ openFiles: string[]; } + type SurveyReadyEventName = "surveyReady"; + interface SurveyReadyEvent extends Event { + event: SurveyReadyEventName; + body: SurveyReadyEventBody; + } + interface SurveyReadyEventBody { + /** Name of the survey. This is an internal machine- and programmer-friendly name */ + surveyId: string; + } type LargeFileReferencedEventName = "largeFileReferenced"; interface LargeFileReferencedEvent extends Event { event: LargeFileReferencedEventName; @@ -8399,6 +8408,7 @@ declare namespace ts.server { declare namespace ts.server { const maxProgramSizeForNonTsFiles: number; const ProjectsUpdatedInBackgroundEvent = "projectsUpdatedInBackground"; + const SurveyReady = "surveyReady"; const LargeFileReferencedEvent = "largeFileReferenced"; const ConfigFileDiagEvent = "configFileDiag"; const ProjectLanguageServiceStateEvent = "projectLanguageServiceState"; @@ -8410,6 +8420,12 @@ declare namespace ts.server { openFiles: string[]; }; } + interface SurveyReady { + eventName: typeof SurveyReady; + data: { + surveyId: string; + }; + } interface LargeFileReferencedEvent { eventName: typeof LargeFileReferencedEvent; data: { @@ -8488,7 +8504,7 @@ declare namespace ts.server { interface OpenFileInfo { readonly checkJs: boolean; } - type ProjectServiceEvent = LargeFileReferencedEvent | ProjectsUpdatedInBackgroundEvent | ConfigFileDiagEvent | ProjectLanguageServiceStateEvent | ProjectInfoTelemetryEvent | OpenFileInfoTelemetryEvent; + type ProjectServiceEvent = LargeFileReferencedEvent | SurveyReady | ProjectsUpdatedInBackgroundEvent | ConfigFileDiagEvent | ProjectLanguageServiceStateEvent | ProjectInfoTelemetryEvent | OpenFileInfoTelemetryEvent; type ProjectServiceEventHandler = (event: ProjectServiceEvent) => void; interface SafeList { [name: string]: { From 002efcfac497c08665f807cfd06bbb20a66f21a8 Mon Sep 17 00:00:00 2001 From: Nathan Shively-Sanders <293473+sandersn@users.noreply.github.com> Date: Tue, 28 Aug 2018 14:02:17 -0700 Subject: [PATCH 6/7] Split survey sending from telemetry Based on tests requested during review. --- src/server/editorServices.ts | 26 ++++-- src/server/project.ts | 2 + .../unittests/tsserverProjectSystem.ts | 85 +++++++++++++++++-- .../reference/api/tsserverlibrary.d.ts | 2 + 4 files changed, 99 insertions(+), 16 deletions(-) diff --git a/src/server/editorServices.ts b/src/server/editorServices.ts index 183c8c6743239..4bbd1b7d6b88a 100644 --- a/src/server/editorServices.ts +++ b/src/server/editorServices.ts @@ -462,6 +462,9 @@ namespace ts.server { /** Tracks projects that we have already sent telemetry for. */ private readonly seenProjects = createMap(); + /** Tracks projects that we have already sent survey events for. */ + private readonly seenSurveyProjects = createMap(); + /*@internal*/ readonly watchFactory: WatchFactory; @@ -1486,23 +1489,28 @@ namespace ts.server { } /*@internal*/ - sendProjectTelemetry(project: ExternalProject | ConfiguredProject): void { - if (this.seenProjects.has(project.projectName)) { - setProjectOptionsUsed(project); + sendSurveyReady(project: ExternalProject | ConfiguredProject): void { + if (this.seenSurveyProjects.has(project.projectName)) { return; } - this.seenProjects.set(project.projectName, true); - if (!this.eventHandler) { - setProjectOptionsUsed(project); - return; - } if (project.getCompilerOptions().checkJs !== undefined) { const name = "checkJs"; this.logger.info(`Survey ${name} is ready`); this.sendSurveyReadyEvent(name); + this.seenSurveyProjects.set(project.projectName, true); } - if (!this.host.createSHA256Hash) { + } + + /*@internal*/ + sendProjectTelemetry(project: ExternalProject | ConfiguredProject): void { + if (this.seenProjects.has(project.projectName)) { + setProjectOptionsUsed(project); + return; + } + this.seenProjects.set(project.projectName, true); + + if (!this.eventHandler || !this.host.createSHA256Hash) { setProjectOptionsUsed(project); return; } diff --git a/src/server/project.ts b/src/server/project.ts index ff2c504a54a0a..59c1e082039c8 100644 --- a/src/server/project.ts +++ b/src/server/project.ts @@ -1366,6 +1366,7 @@ namespace ts.server { result = super.updateGraph(); } this.projectService.sendProjectTelemetry(this); + this.projectService.sendSurveyReady(this); return result; } @@ -1565,6 +1566,7 @@ namespace ts.server { updateGraph() { const result = super.updateGraph(); this.projectService.sendProjectTelemetry(this); + this.projectService.sendSurveyReady(this); return result; } diff --git a/src/testRunner/unittests/tsserverProjectSystem.ts b/src/testRunner/unittests/tsserverProjectSystem.ts index 8546787ce3bd8..45b5c7bfc4f28 100644 --- a/src/testRunner/unittests/tsserverProjectSystem.ts +++ b/src/testRunner/unittests/tsserverProjectSystem.ts @@ -3508,16 +3508,17 @@ namespace ts.projectSystem { return { session, verifySurveyReadyEvent }; - function verifySurveyReadyEvent() { - assert.equal(surveyEvents.length, 1); - assert.deepEqual(surveyEvents, [{ + function verifySurveyReadyEvent(numberOfEvents: number) { + assert.equal(surveyEvents.length, numberOfEvents); + const expectedEvents = numberOfEvents === 0 ? [] : [{ eventName: server.SurveyReady, data: { surveyId: "checkJs" } - }]); + }]; + assert.deepEqual(surveyEvents, expectedEvents); } } - it("logs an event when checkJs is set", () => { + it("doesn't log an event when checkJs isn't set", () => { const projectRoot = "/user/username/projects/project"; const file: File = { path: `${projectRoot}/src/file.ts`, @@ -3525,7 +3526,7 @@ namespace ts.projectSystem { }; const tsconfig: File = { path: `${projectRoot}/tsconfig.json`, - content: JSON.stringify({ compilerOptions: { checkJs: true } }), + content: JSON.stringify({ compilerOptions: { } }), }; const host = createServerHost([file, tsconfig]); const { session, verifySurveyReadyEvent } = createSessionWithEventHandler(host); @@ -3535,7 +3536,77 @@ namespace ts.projectSystem { const project = service.configuredProjects.get(tsconfig.path)!; checkProjectActualFiles(project, [file.path, tsconfig.path]); - verifySurveyReadyEvent(); + verifySurveyReadyEvent(0); + }); + + it("logs an event when checkJs is set", () => { + const projectRoot = "/user/username/projects/project"; + const file: File = { + path: `${projectRoot}/src/file.ts`, + content: "export var y = 10;" + }; + const tsconfig: File = { + path: `${projectRoot}/tsconfig.json`, + content: JSON.stringify({ compilerOptions: { checkJs: true } }), + }; + const host = createServerHost([file, tsconfig]); + const { session, verifySurveyReadyEvent } = createSessionWithEventHandler(host); + openFilesForSession([file], session); + + verifySurveyReadyEvent(1); + }); + + it("logs an event when checkJs is set, only the first time", () => { + const projectRoot = "/user/username/projects/project"; + const file: File = { + path: `${projectRoot}/src/file.ts`, + content: "export var y = 10;" + }; + const rando: File = { + path: `/rando/calrissian.ts`, + content: "export function f() { }" + }; + const tsconfig: File = { + path: `${projectRoot}/tsconfig.json`, + content: JSON.stringify({ compilerOptions: { checkJs: true } }), + }; + const host = createServerHost([file, tsconfig]); + const { session, verifySurveyReadyEvent } = createSessionWithEventHandler(host); + openFilesForSession([file], session); + + closeFilesForSession([file], session); + openFilesForSession([rando], session); + openFilesForSession([file], session); + + verifySurveyReadyEvent(1); + }); + + it("logs an event when checkJs is set after closing and reopening", () => { + const projectRoot = "/user/username/projects/project"; + const file: File = { + path: `${projectRoot}/src/file.ts`, + content: "export var y = 10;" + }; + const rando: File = { + path: `/rando/calrissian.ts`, + content: "export function f() { }" + }; + const tsconfig: File = { + path: `${projectRoot}/tsconfig.json`, + content: JSON.stringify({ }), + }; + const host = createServerHost([file, tsconfig]); + const { session, verifySurveyReadyEvent } = createSessionWithEventHandler(host); + openFilesForSession([file], session); + + verifySurveyReadyEvent(0); + + closeFilesForSession([file], session); + openFilesForSession([rando], session); + host.writeFile(tsconfig.path, JSON.stringify({ compilerOptions: { checkJs: true } })); + openFilesForSession([file], session); + + verifySurveyReadyEvent(1); }); describe("CompileOnSaveAffectedFileListRequest with and without projectFileName in request", () => { diff --git a/tests/baselines/reference/api/tsserverlibrary.d.ts b/tests/baselines/reference/api/tsserverlibrary.d.ts index 5400d6060f1a4..ccf65bb705e85 100644 --- a/tests/baselines/reference/api/tsserverlibrary.d.ts +++ b/tests/baselines/reference/api/tsserverlibrary.d.ts @@ -8425,6 +8425,8 @@ declare namespace ts.server { readonly syntaxOnly?: boolean; /** Tracks projects that we have already sent telemetry for. */ private readonly seenProjects; + /** Tracks projects that we have already sent survey events for. */ + private readonly seenSurveyProjects; constructor(opts: ProjectServiceOptions); toPath(fileName: string): Path; private loadTypesMap; From 058b5fb2f7cfbc405174cefd7b5de9d238181be6 Mon Sep 17 00:00:00 2001 From: Nathan Shively-Sanders <293473+sandersn@users.noreply.github.com> Date: Tue, 28 Aug 2018 17:18:01 -0700 Subject: [PATCH 7/7] Add additional assertion --- src/testRunner/unittests/tsserverProjectSystem.ts | 2 ++ 1 file changed, 2 insertions(+) diff --git a/src/testRunner/unittests/tsserverProjectSystem.ts b/src/testRunner/unittests/tsserverProjectSystem.ts index 45b5c7bfc4f28..6c98db8f0a0cf 100644 --- a/src/testRunner/unittests/tsserverProjectSystem.ts +++ b/src/testRunner/unittests/tsserverProjectSystem.ts @@ -3574,6 +3574,8 @@ namespace ts.projectSystem { const { session, verifySurveyReadyEvent } = createSessionWithEventHandler(host); openFilesForSession([file], session); + verifySurveyReadyEvent(1); + closeFilesForSession([file], session); openFilesForSession([rando], session); openFilesForSession([file], session);