From 878b7a21ce7324974d94a9777d349a5b8ab421a9 Mon Sep 17 00:00:00 2001 From: Sheetal Nandi Date: Mon, 29 Oct 2018 15:40:14 -0700 Subject: [PATCH 1/5] Fix incorrect event --- src/server/session.ts | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/server/session.ts b/src/server/session.ts index 49db5562992..da4808c31f6 100644 --- a/src/server/session.ts +++ b/src/server/session.ts @@ -576,7 +576,7 @@ namespace ts.server { break; case ProjectLoadingFinishEvent: const { project: finishProject } = event.data; - this.event({ projectName: finishProject.getProjectName() }, ProjectLoadingStartEvent); + this.event({ projectName: finishProject.getProjectName() }, ProjectLoadingFinishEvent); break; case LargeFileReferencedEvent: const { file, fileSize, maxFileSize } = event.data; From f7189e17f4ac118de2c5a9cbb44a06061f5771d6 Mon Sep 17 00:00:00 2001 From: Sheetal Nandi Date: Wed, 31 Oct 2018 12:51:31 -0700 Subject: [PATCH 2/5] Some reorg in creating sessions for testing --- src/server/protocol.ts | 4 ++ .../unittests/tsserverProjectSystem.ts | 62 +++++++++++-------- 2 files changed, 40 insertions(+), 26 deletions(-) diff --git a/src/server/protocol.ts b/src/server/protocol.ts index aad7ba085de..acffd6bde5c 100644 --- a/src/server/protocol.ts +++ b/src/server/protocol.ts @@ -2401,6 +2401,7 @@ namespace ts.server.protocol { */ export interface DiagnosticEvent extends Event { body?: DiagnosticEventBody; + event: DiagnosticEventKind; } export interface ConfigFileDiagnosticEventBody { @@ -2520,6 +2521,9 @@ namespace ts.server.protocol { maxFileSize: number; } + export type AnyEvent = RequestCompletedEvent | DiagnosticEvent | ConfigFileDiagnosticEvent | ProjectLanguageServiceStateEvent | TelemetryEvent | + ProjectsUpdatedInBackgroundEvent | ProjectLoadingStartEvent | ProjectLoadingFinishEvent | SurveyReadyEvent | LargeFileReferencedEvent; + /** * Arguments for reload request. */ diff --git a/src/testRunner/unittests/tsserverProjectSystem.ts b/src/testRunner/unittests/tsserverProjectSystem.ts index 9f569f46d4d..f266caf7f4d 100644 --- a/src/testRunner/unittests/tsserverProjectSystem.ts +++ b/src/testRunner/unittests/tsserverProjectSystem.ts @@ -330,12 +330,12 @@ namespace ts.projectSystem { return new TestSession({ ...sessionOptions, ...opts }); } - function createSessionWithEventTracking(host: server.ServerHost, eventName: T["eventName"], eventName2?: U["eventName"]) { - const events: (T | U)[] = []; + function createSessionWithEventTracking(host: server.ServerHost, eventName: T["eventName"], ...eventNames: T["eventName"][]) { + const events: T[] = []; const session = createSession(host, { eventHandler: e => { - if (e.eventName === eventName || (eventName2 && e.eventName === eventName2)) { - events.push(e as T | U); + if (e.eventName === eventName || eventNames.some(eventName => e.eventName === eventName)) { + events.push(e as T); } } }); @@ -343,6 +343,33 @@ namespace ts.projectSystem { return { session, events }; } + function createSessionWithDefaultEventHandler(host: TestServerHost, eventName: T["event"], opts: Partial = {}, ...eventNames: T["event"][]) { + const session = createSession(host, { canUseEvents: true, ...opts }); + + return { + session, + getEvents, + clearEvents + }; + + function getEvents() { + const outputEventRegex = /Content\-Length: [\d]+\r\n\r\n/; + return filter( + map( + host.getOutput(), s => convertToObject( + parseJsonText("json.json", s.replace(outputEventRegex, "")), + [] + ) + ), + e => e.event === eventName || eventNames.some(eventName => e.event === eventName) + ) as T[]; + } + + function clearEvents() { + session.clearMessages(); + } + } + interface CreateProjectServiceParameters { cancellationToken?: HostCancellationToken; logger?: server.Logger; @@ -8062,15 +8089,7 @@ namespace ts.projectSystem { verifyProjectsUpdatedInBackgroundEvent(createSessionWithProjectChangedEventHandler); function createSessionWithProjectChangedEventHandler(host: TestServerHost): ProjectsUpdatedInBackgroundEventVerifier { - const projectChangedEvents: server.ProjectsUpdatedInBackgroundEvent[] = []; - const session = createSession(host, { - eventHandler: e => { - if (e.eventName === server.ProjectsUpdatedInBackgroundEvent) { - projectChangedEvents.push(e); - } - } - }); - + const { session, events: projectChangedEvents } = createSessionWithEventTracking(host, server.ProjectsUpdatedInBackgroundEvent); return { session, verifyProjectsUpdatedInBackgroundEventHandler, @@ -8110,7 +8129,7 @@ namespace ts.projectSystem { function createSessionThatUsesEvents(host: TestServerHost, noGetErrOnBackgroundUpdate?: boolean): ProjectsUpdatedInBackgroundEventVerifier { - const session = createSession(host, { canUseEvents: true, noGetErrOnBackgroundUpdate }); + const { session, getEvents, clearEvents } = createSessionWithDefaultEventHandler(host, server.ProjectsUpdatedInBackgroundEvent, { noGetErrOnBackgroundUpdate }); return { session, @@ -8124,16 +8143,7 @@ namespace ts.projectSystem { openFiles: e.data.openFiles }; }); - const outputEventRegex = /Content\-Length: [\d]+\r\n\r\n/; - const events: protocol.ProjectsUpdatedInBackgroundEvent[] = filter( - map( - host.getOutput(), s => convertToObject( - parseJsonText("json.json", s.replace(outputEventRegex, "")), - [] - ) - ), - e => e.event === server.ProjectsUpdatedInBackgroundEvent - ); + const events = getEvents(); assert.equal(events.length, expectedEvents.length, `Incorrect number of events Actual: ${map(events, e => e.body)} Expected: ${expectedEvents}`); forEach(events, (actualEvent, i) => { const expectedEvent = expectedEvents[i]; @@ -8141,7 +8151,7 @@ namespace ts.projectSystem { }); // Verified the events, reset them - session.clearMessages(); + clearEvents(); if (events.length) { host.checkTimeoutQueueLength(noGetErrOnBackgroundUpdate ? 0 : 1); // Error checking queued only if not noGetErrOnBackgroundUpdate @@ -9467,7 +9477,7 @@ export const x = 10;` } return originalReadFile.call(host, file); }; - const { session, events } = createSessionWithEventTracking(host, server.ProjectLoadingStartEvent, server.ProjectLoadingFinishEvent); + const { session, events } = createSessionWithEventTracking(host, server.ProjectLoadingStartEvent, server.ProjectLoadingFinishEvent); const service = session.getProjectService(); return { host, session, verifyEvent, verifyEventWithOpenTs, service, events }; From 677f04b165a6a5b5c5bf787824fa5984271f6e7f Mon Sep 17 00:00:00 2001 From: Sheetal Nandi Date: Wed, 31 Oct 2018 13:37:00 -0700 Subject: [PATCH 3/5] Test to verify project loading events with default event handler --- .../unittests/tsserverProjectSystem.ts | 271 ++++++++++-------- 1 file changed, 155 insertions(+), 116 deletions(-) diff --git a/src/testRunner/unittests/tsserverProjectSystem.ts b/src/testRunner/unittests/tsserverProjectSystem.ts index f266caf7f4d..9722905bbdb 100644 --- a/src/testRunner/unittests/tsserverProjectSystem.ts +++ b/src/testRunner/unittests/tsserverProjectSystem.ts @@ -9467,141 +9467,180 @@ export const x = 10;` const configBPath = `${projectRoot}/b/tsconfig.json`; const files = [libFile, aTs, configA]; - function createSessionWithEventHandler(files: ReadonlyArray) { - const host = createServerHost(files); + function verifyProjectLoadingStartAndFinish(createSession: (host: TestServerHost) => { + session: TestSession; + getNumberOfEvents: () => number; + clearEvents: () => void; + verifyProjectLoadEvents: (expected: [server.ProjectLoadingStartEvent, server.ProjectLoadingFinishEvent]) => void; + }) { + function createSessionToVerifyEvent(files: ReadonlyArray) { + const host = createServerHost(files); + const originalReadFile = host.readFile; + const { session, getNumberOfEvents, clearEvents, verifyProjectLoadEvents } = createSession(host); + host.readFile = file => { + if (file === configA.path || file === configBPath) { + assert.equal(getNumberOfEvents(), 1, "Event for loading is sent before reading config file"); + } + return originalReadFile.call(host, file); + }; + const service = session.getProjectService(); + return { host, session, verifyEvent, verifyEventWithOpenTs, service, getNumberOfEvents }; - const originalReadFile = host.readFile; - host.readFile = file => { - if (file === configA.path || file === configBPath) { - assert.equal(events.length, 1, "Event for loading is sent before reading config file"); + function verifyEvent(project: server.Project, reason: string) { + verifyProjectLoadEvents([ + { eventName: server.ProjectLoadingStartEvent, data: { project, reason } }, + { eventName: server.ProjectLoadingFinishEvent, data: { project } } + ]); + clearEvents(); } - return originalReadFile.call(host, file); - }; - const { session, events } = createSessionWithEventTracking(host, server.ProjectLoadingStartEvent, server.ProjectLoadingFinishEvent); - const service = session.getProjectService(); - return { host, session, verifyEvent, verifyEventWithOpenTs, service, events }; - function verifyEvent(project: server.Project, reason: string) { - assert.deepEqual(events, [ - { eventName: server.ProjectLoadingStartEvent, data: { project, reason } }, - { eventName: server.ProjectLoadingFinishEvent, data: { project } } - ]); - events.length = 0; + function verifyEventWithOpenTs(file: File, configPath: string, configuredProjects: number) { + openFilesForSession([file], session); + checkNumberOfProjects(service, { configuredProjects }); + const project = service.configuredProjects.get(configPath)!; + assert.isDefined(project); + verifyEvent(project, `Creating possible configured project for ${file.path} to open`); + } } - function verifyEventWithOpenTs(file: File, configPath: string, configuredProjects: number) { - openFilesForSession([file], session); - checkNumberOfProjects(service, { configuredProjects }); - const project = service.configuredProjects.get(configPath)!; - assert.isDefined(project); - verifyEvent(project, `Creating possible configured project for ${file.path} to open`); - } - } + it("when project is created by open file", () => { + const bTs: File = { + path: bTsPath, + content: "export class B {}" + }; + const configB: File = { + path: configBPath, + content: "{}" + }; + const { verifyEventWithOpenTs } = createSessionToVerifyEvent(files.concat(bTs, configB)); + verifyEventWithOpenTs(aTs, configA.path, 1); + verifyEventWithOpenTs(bTs, configB.path, 2); + }); - it("when project is created by open file", () => { - const bTs: File = { - path: bTsPath, - content: "export class B {}" - }; - const configB: File = { - path: configBPath, - content: "{}" - }; - const { verifyEventWithOpenTs } = createSessionWithEventHandler(files.concat(bTs, configB)); - verifyEventWithOpenTs(aTs, configA.path, 1); - verifyEventWithOpenTs(bTs, configB.path, 2); - }); + it("when change is detected in the config file", () => { + const { host, verifyEvent, verifyEventWithOpenTs, service } = createSessionToVerifyEvent(files); + verifyEventWithOpenTs(aTs, configA.path, 1); - it("when change is detected in the config file", () => { - const { host, verifyEvent, verifyEventWithOpenTs, service } = createSessionWithEventHandler(files); - verifyEventWithOpenTs(aTs, configA.path, 1); + host.writeFile(configA.path, configA.content); + host.checkTimeoutQueueLengthAndRun(2); + const project = service.configuredProjects.get(configA.path)!; + verifyEvent(project, `Change in config file detected`); + }); - host.writeFile(configA.path, configA.content); - host.checkTimeoutQueueLengthAndRun(2); - const project = service.configuredProjects.get(configA.path)!; - verifyEvent(project, `Change in config file detected`); - }); - - it("when opening original location project", () => { - const aDTs: File = { - path: `${projectRoot}/a/a.d.ts`, - content: `export declare class A { + it("when opening original location project", () => { + const aDTs: File = { + path: `${projectRoot}/a/a.d.ts`, + content: `export declare class A { } //# sourceMappingURL=a.d.ts.map ` - }; - const aDTsMap: File = { - path: `${projectRoot}/a/a.d.ts.map`, - content: `{"version":3,"file":"a.d.ts","sourceRoot":"","sources":["./a.ts"],"names":[],"mappings":"AAAA,qBAAa,CAAC;CAAI"}` - }; - const bTs: File = { - path: bTsPath, - content: `import {A} from "../a/a"; new A();` - }; - const configB: File = { - path: configBPath, - content: JSON.stringify({ - references: [{ path: "../a" }] - }) - }; + }; + const aDTsMap: File = { + path: `${projectRoot}/a/a.d.ts.map`, + content: `{"version":3,"file":"a.d.ts","sourceRoot":"","sources":["./a.ts"],"names":[],"mappings":"AAAA,qBAAa,CAAC;CAAI"}` + }; + const bTs: File = { + path: bTsPath, + content: `import {A} from "../a/a"; new A();` + }; + const configB: File = { + path: configBPath, + content: JSON.stringify({ + references: [{ path: "../a" }] + }) + }; - const { service, session, verifyEventWithOpenTs, verifyEvent } = createSessionWithEventHandler(files.concat(aDTs, aDTsMap, bTs, configB)); - verifyEventWithOpenTs(bTs, configB.path, 1); + const { service, session, verifyEventWithOpenTs, verifyEvent } = createSessionToVerifyEvent(files.concat(aDTs, aDTsMap, bTs, configB)); + verifyEventWithOpenTs(bTs, configB.path, 1); - session.executeCommandSeq({ - command: protocol.CommandTypes.References, - arguments: { - file: bTs.path, - ...protocolLocationFromSubstring(bTs.content, "A()") - } + session.executeCommandSeq({ + command: protocol.CommandTypes.References, + arguments: { + file: bTs.path, + ...protocolLocationFromSubstring(bTs.content, "A()") + } + }); + + checkNumberOfProjects(service, { configuredProjects: 2 }); + const project = service.configuredProjects.get(configA.path)!; + assert.isDefined(project); + verifyEvent(project, `Creating project for original file: ${aTs.path} for location: ${aDTs.path}`); }); - checkNumberOfProjects(service, { configuredProjects: 2 }); - const project = service.configuredProjects.get(configA.path)!; - assert.isDefined(project); - verifyEvent(project, `Creating project for original file: ${aTs.path} for location: ${aDTs.path}`); + describe("with external projects and config files ", () => { + const projectFileName = `${projectRoot}/a/project.csproj`; + + function createSession(lazyConfiguredProjectsFromExternalProject: boolean) { + const { session, service, verifyEvent: verifyEventWorker, getNumberOfEvents } = createSessionToVerifyEvent(files); + service.setHostConfiguration({ preferences: { lazyConfiguredProjectsFromExternalProject } }); + service.openExternalProject({ + projectFileName, + rootFiles: toExternalFiles([aTs.path, configA.path]), + options: {} + }); + checkNumberOfProjects(service, { configuredProjects: 1 }); + return { session, service, verifyEvent, getNumberOfEvents }; + + function verifyEvent() { + const projectA = service.configuredProjects.get(configA.path)!; + assert.isDefined(projectA); + verifyEventWorker(projectA, `Creating configured project in external project: ${projectFileName}`); + } + } + + it("when lazyConfiguredProjectsFromExternalProject is false", () => { + const { verifyEvent } = createSession(/*lazyConfiguredProjectsFromExternalProject*/ false); + verifyEvent(); + }); + + it("when lazyConfiguredProjectsFromExternalProject is true and file is opened", () => { + const { verifyEvent, getNumberOfEvents, session } = createSession(/*lazyConfiguredProjectsFromExternalProject*/ true); + assert.equal(getNumberOfEvents(), 0); + + openFilesForSession([aTs], session); + verifyEvent(); + }); + + it("when lazyConfiguredProjectsFromExternalProject is disabled", () => { + const { verifyEvent, getNumberOfEvents, service } = createSession(/*lazyConfiguredProjectsFromExternalProject*/ true); + assert.equal(getNumberOfEvents(), 0); + + service.setHostConfiguration({ preferences: { lazyConfiguredProjectsFromExternalProject: false } }); + verifyEvent(); + }); + }); + } + + describe("when using event handler", () => { + verifyProjectLoadingStartAndFinish(host => { + const { session, events } = createSessionWithEventTracking(host, server.ProjectLoadingStartEvent, server.ProjectLoadingFinishEvent); + return { + session, + getNumberOfEvents: () => events.length, + clearEvents: () => events.length = 0, + verifyProjectLoadEvents: expected => assert.deepEqual(events, expected) + }; + }); }); - describe("with external projects and config files ", () => { - const projectFileName = `${projectRoot}/a/project.csproj`; + describe("when using default event handler", () => { + verifyProjectLoadingStartAndFinish(host => { + const { session, getEvents, clearEvents } = createSessionWithDefaultEventHandler(host, server.ProjectLoadingStartEvent, {}, server.ProjectLoadingFinishEvent); + return { + session, + getNumberOfEvents: () => getEvents().length, + clearEvents, + verifyProjectLoadEvents + }; - function createSession(lazyConfiguredProjectsFromExternalProject: boolean) { - const { session, service, verifyEvent: verifyEventWorker, events } = createSessionWithEventHandler(files); - service.setHostConfiguration({ preferences: { lazyConfiguredProjectsFromExternalProject } }); - service.openExternalProject({ - projectFileName, - rootFiles: toExternalFiles([aTs.path, configA.path]), - options: {} - }); - checkNumberOfProjects(service, { configuredProjects: 1 }); - return { session, service, verifyEvent, events }; - - function verifyEvent() { - const projectA = service.configuredProjects.get(configA.path)!; - assert.isDefined(projectA); - verifyEventWorker(projectA, `Creating configured project in external project: ${projectFileName}`); + function verifyProjectLoadEvents(expected: [server.ProjectLoadingStartEvent, server.ProjectLoadingFinishEvent]) { + const actual = getEvents().map(e => ({ eventName: e.event, data: e.body })); + const mappedExpected = expected.map(e => { + const { project, ...rest } = e.data; + return { eventName: e.eventName, data: { projectName: project.getProjectName(), ...rest } }; + }); + assert.deepEqual(actual, mappedExpected); } - } - - it("when lazyConfiguredProjectsFromExternalProject is false", () => { - const { verifyEvent } = createSession(/*lazyConfiguredProjectsFromExternalProject*/ false); - verifyEvent(); - }); - - it("when lazyConfiguredProjectsFromExternalProject is true and file is opened", () => { - const { verifyEvent, events, session } = createSession(/*lazyConfiguredProjectsFromExternalProject*/ true); - assert.equal(events.length, 0); - - openFilesForSession([aTs], session); - verifyEvent(); - }); - - it("when lazyConfiguredProjectsFromExternalProject is disabled", () => { - const { verifyEvent, events, service } = createSession(/*lazyConfiguredProjectsFromExternalProject*/ true); - assert.equal(events.length, 0); - - service.setHostConfiguration({ preferences: { lazyConfiguredProjectsFromExternalProject: false } }); - verifyEvent(); }); }); }); From a373029f54e8703582c3f13c2fa6fd79dd5437b6 Mon Sep 17 00:00:00 2001 From: Sheetal Nandi Date: Wed, 31 Oct 2018 15:10:08 -0700 Subject: [PATCH 4/5] Public API --- src/server/protocol.ts | 1 + tests/baselines/reference/api/tsserverlibrary.d.ts | 1 + 2 files changed, 2 insertions(+) diff --git a/src/server/protocol.ts b/src/server/protocol.ts index acffd6bde5c..e956290181a 100644 --- a/src/server/protocol.ts +++ b/src/server/protocol.ts @@ -2521,6 +2521,7 @@ namespace ts.server.protocol { maxFileSize: number; } + /*@internal*/ export type AnyEvent = RequestCompletedEvent | DiagnosticEvent | ConfigFileDiagnosticEvent | ProjectLanguageServiceStateEvent | TelemetryEvent | ProjectsUpdatedInBackgroundEvent | ProjectLoadingStartEvent | ProjectLoadingFinishEvent | SurveyReadyEvent | LargeFileReferencedEvent; diff --git a/tests/baselines/reference/api/tsserverlibrary.d.ts b/tests/baselines/reference/api/tsserverlibrary.d.ts index ccc9557e948..b5ef0cac750 100644 --- a/tests/baselines/reference/api/tsserverlibrary.d.ts +++ b/tests/baselines/reference/api/tsserverlibrary.d.ts @@ -7486,6 +7486,7 @@ declare namespace ts.server.protocol { */ interface DiagnosticEvent extends Event { body?: DiagnosticEventBody; + event: DiagnosticEventKind; } interface ConfigFileDiagnosticEventBody { /** From 72aec56fe8e1b9d2341a8548493e439385810738 Mon Sep 17 00:00:00 2001 From: Sheetal Nandi Date: Wed, 31 Oct 2018 15:57:32 -0700 Subject: [PATCH 5/5] Use mapDefined and combine event name inputs --- .../unittests/tsserverProjectSystem.ts | 20 +++++++++---------- 1 file changed, 9 insertions(+), 11 deletions(-) diff --git a/src/testRunner/unittests/tsserverProjectSystem.ts b/src/testRunner/unittests/tsserverProjectSystem.ts index 9722905bbdb..ae3a3901ea1 100644 --- a/src/testRunner/unittests/tsserverProjectSystem.ts +++ b/src/testRunner/unittests/tsserverProjectSystem.ts @@ -343,7 +343,7 @@ namespace ts.projectSystem { return { session, events }; } - function createSessionWithDefaultEventHandler(host: TestServerHost, eventName: T["event"], opts: Partial = {}, ...eventNames: T["event"][]) { + function createSessionWithDefaultEventHandler(host: TestServerHost, eventNames: T["event"] | T["event"][], opts: Partial = {}) { const session = createSession(host, { canUseEvents: true, ...opts }); return { @@ -354,15 +354,13 @@ namespace ts.projectSystem { function getEvents() { const outputEventRegex = /Content\-Length: [\d]+\r\n\r\n/; - return filter( - map( - host.getOutput(), s => convertToObject( - parseJsonText("json.json", s.replace(outputEventRegex, "")), - [] - ) - ), - e => e.event === eventName || eventNames.some(eventName => e.event === eventName) - ) as T[]; + return mapDefined(host.getOutput(), s => { + const e = convertToObject( + parseJsonText("json.json", s.replace(outputEventRegex, "")), + [] + ); + return (isArray(eventNames) ? eventNames.some(eventName => e.event === eventName) : e.event === eventNames) ? e as T : undefined; + }); } function clearEvents() { @@ -9625,7 +9623,7 @@ export const x = 10;` describe("when using default event handler", () => { verifyProjectLoadingStartAndFinish(host => { - const { session, getEvents, clearEvents } = createSessionWithDefaultEventHandler(host, server.ProjectLoadingStartEvent, {}, server.ProjectLoadingFinishEvent); + const { session, getEvents, clearEvents } = createSessionWithDefaultEventHandler(host, [server.ProjectLoadingStartEvent, server.ProjectLoadingFinishEvent]); return { session, getNumberOfEvents: () => getEvents().length,