From c9c37927475aa7625aaa0cf5573b338a779764c7 Mon Sep 17 00:00:00 2001 From: Ben Lichtman Date: Thu, 23 Jan 2020 12:32:36 -0800 Subject: [PATCH 1/6] Produce redirect info about files when requested --- src/server/editorServices.ts | 17 +++++++++++------ src/server/project.ts | 29 ++++++++++++++++++++++++++--- src/server/protocol.ts | 25 +++++++++++++++++++++---- src/server/session.ts | 2 +- 4 files changed, 59 insertions(+), 14 deletions(-) diff --git a/src/server/editorServices.ts b/src/server/editorServices.ts index 4b47131d10c..50f487b6203 100644 --- a/src/server/editorServices.ts +++ b/src/server/editorServices.ts @@ -3116,19 +3116,24 @@ namespace ts.server { return result; } - private collectChanges(lastKnownProjectVersions: protocol.ProjectVersionInfo[], currentProjects: Project[], result: ProjectFilesWithTSDiagnostics[]): void { + private collectChanges( + lastKnownProjectVersions: protocol.ProjectVersionInfo[], + currentProjects: Project[], + includeProjectReferenceRedirectInfo: boolean | undefined, + result: ProjectFilesWithTSDiagnostics[] + ): void { for (const proj of currentProjects) { const knownProject = find(lastKnownProjectVersions, p => p.projectName === proj.getProjectName()); - result.push(proj.getChangesSinceVersion(knownProject && knownProject.version)); + result.push(proj.getChangesSinceVersion(knownProject && knownProject.version, includeProjectReferenceRedirectInfo)); } } /* @internal */ - synchronizeProjectList(knownProjects: protocol.ProjectVersionInfo[]): ProjectFilesWithTSDiagnostics[] { + synchronizeProjectList(knownProjects: protocol.ProjectVersionInfo[], includeProjectReferenceRedirectInfo?: boolean): ProjectFilesWithTSDiagnostics[] { const files: ProjectFilesWithTSDiagnostics[] = []; - this.collectChanges(knownProjects, this.externalProjects, files); - this.collectChanges(knownProjects, arrayFrom(this.configuredProjects.values()), files); - this.collectChanges(knownProjects, this.inferredProjects, files); + this.collectChanges(knownProjects, this.externalProjects, includeProjectReferenceRedirectInfo, files); + this.collectChanges(knownProjects, arrayFrom(this.configuredProjects.values()), includeProjectReferenceRedirectInfo, files); + this.collectChanges(knownProjects, this.inferredProjects, includeProjectReferenceRedirectInfo, files); return files; } diff --git a/src/server/project.ts b/src/server/project.ts index 08c3b7203a3..8da99ee5149 100644 --- a/src/server/project.ts +++ b/src/server/project.ts @@ -1300,7 +1300,18 @@ namespace ts.server { } /* @internal */ - getChangesSinceVersion(lastKnownVersion?: number): ProjectFilesWithTSDiagnostics { + getChangesSinceVersion(lastKnownVersion?: number, includeProjectReferenceRedirectInfo?: boolean): ProjectFilesWithTSDiagnostics { + const includeProjectReferenceRedirectInfoIfRequested = (files: string[]) => { + if (includeProjectReferenceRedirectInfo) { + return files.map((fileName: string): protocol.FileWithProjectReferenceRedirectInfo => ({ + fileName, + isSourceOfProjectReferenceRedirect: this.program?.isSourceOfProjectReferenceRedirect(fileName) ?? false + })); + } + + return files; + }; + // Update the graph only if initial configured project load is not pending if (!this.isInitialLoadPending()) { updateProjectIfDirty(this); @@ -1343,7 +1354,15 @@ namespace ts.server { }); this.lastReportedFileNames = currentFiles; this.lastReportedVersion = this.projectProgramVersion; - return { info, changes: { added, removed, updated }, projectErrors: this.getGlobalProjectErrors() }; + return { + info, + changes: { + added: includeProjectReferenceRedirectInfoIfRequested(added), + removed: includeProjectReferenceRedirectInfoIfRequested(removed), + updated: includeProjectReferenceRedirectInfoIfRequested(updated) + }, + projectErrors: this.getGlobalProjectErrors() + }; } else { // unknown version - return everything @@ -1352,7 +1371,11 @@ namespace ts.server { const allFiles = projectFileNames.concat(externalFiles); this.lastReportedFileNames = arrayToSet(allFiles); this.lastReportedVersion = this.projectProgramVersion; - return { info, files: allFiles, projectErrors: this.getGlobalProjectErrors() }; + return { + info, + files: includeProjectReferenceRedirectInfoIfRequested(allFiles), + projectErrors: this.getGlobalProjectErrors() + }; } } diff --git a/src/server/protocol.ts b/src/server/protocol.ts index 973d1ddedb2..66fa1ba8ebd 100644 --- a/src/server/protocol.ts +++ b/src/server/protocol.ts @@ -1319,6 +1319,18 @@ namespace ts.server.protocol { lastFileExceededProgramSize?: string; } + export interface FileWithProjectReferenceRedirectInfo { + /** + * Name of file + */ + fileName: string; + + /** + * True if the file is primarily included in a referenced project + */ + isSourceOfProjectReferenceRedirect: boolean; + } + /** * Represents a set of changes that happen in project */ @@ -1326,15 +1338,15 @@ namespace ts.server.protocol { /** * List of added files */ - added: string[]; + added: string[] | FileWithProjectReferenceRedirectInfo[]; /** * List of removed files */ - removed: string[]; + removed: string[] | FileWithProjectReferenceRedirectInfo[]; /** * List of updated files */ - updated: string[]; + updated: string[] | FileWithProjectReferenceRedirectInfo[]; } /** @@ -1353,7 +1365,7 @@ namespace ts.server.protocol { /** * List of files in project (might be omitted if current state of project can be computed using only information from 'changes') */ - files?: string[]; + files?: string[] | FileWithProjectReferenceRedirectInfo[]; /** * Set of changes in project (omitted if the entire set of files in project should be replaced) */ @@ -1616,6 +1628,11 @@ namespace ts.server.protocol { * List of last known projects */ knownProjects: ProjectVersionInfo[]; + /** + * If true, response specifies whether or not each file in each project + * is the result of a project reference redirect + */ + includeProjectReferenceRedirectInfo?: boolean; } /** diff --git a/src/server/session.ts b/src/server/session.ts index 932598104e8..f2b1356c989 100644 --- a/src/server/session.ts +++ b/src/server/session.ts @@ -2254,7 +2254,7 @@ namespace ts.server { return this.requiredResponse(/*response*/ true); }, [CommandNames.SynchronizeProjectList]: (request: protocol.SynchronizeProjectListRequest) => { - const result = this.projectService.synchronizeProjectList(request.arguments.knownProjects); + const result = this.projectService.synchronizeProjectList(request.arguments.knownProjects, request.arguments.includeProjectReferenceRedirectInfo); if (!result.some(p => p.projectErrors && p.projectErrors.length !== 0)) { return this.requiredResponse(result); } From 09528dd6d65d83bdda88072dbe7dd62e60e8a873 Mon Sep 17 00:00:00 2001 From: Ben Lichtman Date: Thu, 23 Jan 2020 14:10:37 -0800 Subject: [PATCH 2/6] Add tests --- src/testRunner/unittests/tsserver/projects.ts | 51 +++++++++++++++++++ .../reference/api/tsserverlibrary.d.ts | 16 ++++-- 2 files changed, 64 insertions(+), 3 deletions(-) diff --git a/src/testRunner/unittests/tsserver/projects.ts b/src/testRunner/unittests/tsserver/projects.ts index 369b0d8905e..33de47aebc9 100644 --- a/src/testRunner/unittests/tsserver/projects.ts +++ b/src/testRunner/unittests/tsserver/projects.ts @@ -1428,6 +1428,57 @@ var x = 10;` host.checkTimeoutQueueLength(0); }); + it("synchronizeProjectList provides redirect info when requested", () => { + const projectRootPath = "/users/username/projects/project"; + const fileA: File = { + path: `${projectRootPath}/A/a.ts`, + content: "export const foo: string = 5;" + }; + const configA: File = { + path: `${projectRootPath}/A/tsconfig.json`, + content: `{ + "compilerOptions": { + "composite": true, + "declaration": true + } +}` + }; + const fileB: File = { + path: `${projectRootPath}/B/b.ts`, + content: "import { foo } from \"../A/a\"; console.log(foo);" + }; + const configB: File = { + path: `${projectRootPath}/B/tsconfig.json`, + content: `{ + "compilerOptions": { + "composite": true, + "declaration": true + }, + "references": [ + { "path": "../A" } + ] +}` + }; + const files = [fileA, fileB, configA, configB, libFile]; + const host = createServerHost(files); + const projectService = createProjectService(host); + projectService.openClientFile(fileA.path); + projectService.openClientFile(fileB.path); + const knownProjects = projectService.synchronizeProjectList([], /*includeProjectReferenceRedirectInfo*/ true); + assert(knownProjects.length === 2, `Expected 2 projects but received ${knownProjects.length}`); + assert(knownProjects[0].files?.length === 3, `Expected project A to have 3 files but received ${knownProjects[0].files?.length}`); + assert(knownProjects[0].files?.every( + (file: string | protocol.FileWithProjectReferenceRedirectInfo) => + typeof file === "object" && !file.isSourceOfProjectReferenceRedirect), + `Expected every file in project A to not be redirected.` + ); + assert(knownProjects[1].files?.length === 4, `Expected project B to have 4 files but received ${knownProjects[1].files?.length}`); + knownProjects[1].files?.forEach( + (file: string | protocol.FileWithProjectReferenceRedirectInfo) => + assert(typeof file === "object" && (!file.isSourceOfProjectReferenceRedirect || file.fileName === fileA.path)) + ); + }); + it("handles delayed directory watch invoke on file creation", () => { const projectRootPath = "/users/username/projects/project"; const fileB: File = { diff --git a/tests/baselines/reference/api/tsserverlibrary.d.ts b/tests/baselines/reference/api/tsserverlibrary.d.ts index 1855fae39b6..faa1808bf64 100644 --- a/tests/baselines/reference/api/tsserverlibrary.d.ts +++ b/tests/baselines/reference/api/tsserverlibrary.d.ts @@ -7052,6 +7052,16 @@ declare namespace ts.server.protocol { * compiler settings. */ type ExternalProjectCompilerOptions = CompilerOptions & CompileOnSaveMixin & WatchOptions; + interface FileWithProjectReferenceRedirectInfo { + /** + * Name of file + */ + fileName: string; + /** + * True if the file is primarily included in a referenced project + */ + isSourceOfProjectReferenceRedirect: boolean; + } /** * Represents a set of changes that happen in project */ @@ -7059,15 +7069,15 @@ declare namespace ts.server.protocol { /** * List of added files */ - added: string[]; + added: string[] | FileWithProjectReferenceRedirectInfo[]; /** * List of removed files */ - removed: string[]; + removed: string[] | FileWithProjectReferenceRedirectInfo[]; /** * List of updated files */ - updated: string[]; + updated: string[] | FileWithProjectReferenceRedirectInfo[]; } /** * Information found in a configure request. From f047111c649a708d398ba7a39b8c635af28ebeb2 Mon Sep 17 00:00:00 2001 From: Ben Lichtman Date: Fri, 24 Jan 2020 15:15:05 -0800 Subject: [PATCH 3/6] Track changes to redirect info --- src/server/project.ts | 83 +++++++++++++++++++++++++++++++----------- src/server/protocol.ts | 9 ++++- 2 files changed, 70 insertions(+), 22 deletions(-) diff --git a/src/server/project.ts b/src/server/project.ts index 8da99ee5149..b9b6e170d7f 100644 --- a/src/server/project.ts +++ b/src/server/project.ts @@ -167,7 +167,7 @@ namespace ts.server { /** * Set of files that was returned from the last call to getChangesSinceVersion. */ - private lastReportedFileNames: Map | undefined; + private lastReportedFileNames: Map | undefined; /** * Last version that was reported. */ @@ -803,6 +803,22 @@ namespace ts.server { return result; } + /* @internal */ + getFileNamesWithRedirectInfo(includeProjectReferenceRedirectInfo: boolean) { + const fileNames = this.getFileNames(); + if (includeProjectReferenceRedirectInfo) { + return fileNames.map((fileName): protocol.FileWithProjectReferenceRedirectInfo => ({ + fileName, + isSourceOfProjectReferenceRedirect: this.isSourceOfProjectReferenceRedirect(fileName) + })); + } + + return fileNames.map((fileName): protocol.FileWithProjectReferenceRedirectInfo => ({ + fileName, + isSourceOfProjectReferenceRedirect: false + })); + } + hasConfigFile(configFilePath: NormalizedPath) { if (this.program && this.languageServiceEnabled) { const configFile = this.program.getCompilerOptions().configFile; @@ -1301,16 +1317,13 @@ namespace ts.server { /* @internal */ getChangesSinceVersion(lastKnownVersion?: number, includeProjectReferenceRedirectInfo?: boolean): ProjectFilesWithTSDiagnostics { - const includeProjectReferenceRedirectInfoIfRequested = (files: string[]) => { - if (includeProjectReferenceRedirectInfo) { - return files.map((fileName: string): protocol.FileWithProjectReferenceRedirectInfo => ({ + const includeProjectReferenceRedirectInfoIfRequested = + includeProjectReferenceRedirectInfo + ? (files: Map) => arrayFrom(files.keys(), (fileName: string): protocol.FileWithProjectReferenceRedirectInfo => ({ fileName, - isSourceOfProjectReferenceRedirect: this.program?.isSourceOfProjectReferenceRedirect(fileName) ?? false - })); - } - - return files; - }; + isSourceOfProjectReferenceRedirect: files.get(fileName)! // fileName guaranteed to be in files + })) + : (files: Map) => arrayFrom(files.keys()); // Update the graph only if initial configured project load is not pending if (!this.isInitialLoadPending()) { @@ -1335,21 +1348,36 @@ namespace ts.server { } // compute and return the difference const lastReportedFileNames = this.lastReportedFileNames; - const externalFiles = this.getExternalFiles().map(f => toNormalizedPath(f)); - const currentFiles = arrayToSet(this.getFileNames().concat(externalFiles)); + const externalFiles = this.getExternalFiles().map((f): protocol.FileWithProjectReferenceRedirectInfo => ({ + fileName: toNormalizedPath(f), + isSourceOfProjectReferenceRedirect: false + })); + const currentFiles = arrayToMap( + this.getFileNamesWithRedirectInfo(!!includeProjectReferenceRedirectInfo).concat(externalFiles), + info => info.fileName, + info => info.isSourceOfProjectReferenceRedirect + ); + + const added: Map = new Map(); + const removed: Map = new Map(); - const added: string[] = []; - const removed: string[] = []; const updated: string[] = updatedFileNames ? arrayFrom(updatedFileNames.keys()) : []; + const updatedRedirects: protocol.FileWithProjectReferenceRedirectInfo[] = []; forEachKey(currentFiles, id => { if (!lastReportedFileNames.has(id)) { - added.push(id); + added.set(id, currentFiles.get(id)!); // id guaranteed to be in currentFiles + } + else if (includeProjectReferenceRedirectInfo && lastReportedFileNames.get(id) !== currentFiles.get(id)){ + updatedRedirects.push({ + fileName: id, + isSourceOfProjectReferenceRedirect: currentFiles.get(id)! // id guaranteed to be in currentFiles + }); } }); forEachKey(lastReportedFileNames, id => { if (!currentFiles.has(id)) { - removed.push(id); + removed.set(id, lastReportedFileNames.get(id)!); // id guaranteed to be in lastReportedFileNames } }); this.lastReportedFileNames = currentFiles; @@ -1359,21 +1387,34 @@ namespace ts.server { changes: { added: includeProjectReferenceRedirectInfoIfRequested(added), removed: includeProjectReferenceRedirectInfoIfRequested(removed), - updated: includeProjectReferenceRedirectInfoIfRequested(updated) + updated: includeProjectReferenceRedirectInfoIfRequested + ? updated.map((fileName): protocol.FileWithProjectReferenceRedirectInfo => ({ + fileName, + isSourceOfProjectReferenceRedirect: this.isSourceOfProjectReferenceRedirect(fileName) + })) + : updated, + updatedRedirects: includeProjectReferenceRedirectInfo ? updatedRedirects : undefined }, projectErrors: this.getGlobalProjectErrors() }; } else { // unknown version - return everything - const projectFileNames = this.getFileNames(); - const externalFiles = this.getExternalFiles().map(f => toNormalizedPath(f)); + const projectFileNames = this.getFileNamesWithRedirectInfo(!!includeProjectReferenceRedirectInfo); + const externalFiles = this.getExternalFiles().map((f): protocol.FileWithProjectReferenceRedirectInfo => ({ + fileName: toNormalizedPath(f), + isSourceOfProjectReferenceRedirect: false + })); const allFiles = projectFileNames.concat(externalFiles); - this.lastReportedFileNames = arrayToSet(allFiles); + this.lastReportedFileNames = arrayToMap( + projectFileNames.concat(externalFiles), + info => info.fileName, + info => info.isSourceOfProjectReferenceRedirect + ); this.lastReportedVersion = this.projectProgramVersion; return { info, - files: includeProjectReferenceRedirectInfoIfRequested(allFiles), + files: includeProjectReferenceRedirectInfo ? allFiles : allFiles.map(f => f.fileName), projectErrors: this.getGlobalProjectErrors() }; } diff --git a/src/server/protocol.ts b/src/server/protocol.ts index 09457ddfbdd..2e33942b5b7 100644 --- a/src/server/protocol.ts +++ b/src/server/protocol.ts @@ -1352,6 +1352,11 @@ namespace ts.server.protocol { * List of updated files */ updated: string[] | FileWithProjectReferenceRedirectInfo[]; + /** + * List of files that have had their project reference redirect status updated + * Only provided when the synchronizeProjectList request has includeProjectReferenceRedirectInfo set to true + */ + updatedRedirects?: FileWithProjectReferenceRedirectInfo[]; } /** @@ -1369,6 +1374,8 @@ namespace ts.server.protocol { info?: ProjectVersionInfo; /** * List of files in project (might be omitted if current state of project can be computed using only information from 'changes') + * This property will have type FileWithProjectReferenceRedirectInfo[] if includeProjectReferenceRedirectInfo is set to true in + * the corresponding SynchronizeProjectList request; otherwise, it will have type string[]. */ files?: string[] | FileWithProjectReferenceRedirectInfo[]; /** @@ -1635,7 +1642,7 @@ namespace ts.server.protocol { knownProjects: ProjectVersionInfo[]; /** * If true, response specifies whether or not each file in each project - * is the result of a project reference redirect + * is a source from a project reference redirect */ includeProjectReferenceRedirectInfo?: boolean; } From 8c18c7683abe4a4e65cd28a180c53a652624ebfe Mon Sep 17 00:00:00 2001 From: Ben Lichtman Date: Fri, 24 Jan 2020 16:02:14 -0800 Subject: [PATCH 4/6] Add tests --- src/testRunner/unittests/tsserver/projects.ts | 70 +++++++++++++++++++ .../reference/api/tsserverlibrary.d.ts | 5 ++ 2 files changed, 75 insertions(+) diff --git a/src/testRunner/unittests/tsserver/projects.ts b/src/testRunner/unittests/tsserver/projects.ts index 38e074ea500..a966e80b2fb 100644 --- a/src/testRunner/unittests/tsserver/projects.ts +++ b/src/testRunner/unittests/tsserver/projects.ts @@ -1482,6 +1482,76 @@ var x = 10;` ); }); + it("synchronizeProjectList provides updates to redirect info when requested", () => { + const projectRootPath = "/users/username/projects/project"; + const fileA: File = { + path: `${projectRootPath}/A/a.ts`, + content: "export const foo: string = 5;" + }; + const configA: File = { + path: `${projectRootPath}/A/tsconfig.json`, + content: `{ + "compilerOptions": { + "composite": true, + "declaration": true + } +}` + }; + const fileB: File = { + path: `${projectRootPath}/B/b.ts`, + content: "import { foo } from \"../B/b2\"; console.log(foo);" + }; + const fileB2: File = { + path: `${projectRootPath}/B/b2.ts`, + content: "export const foo: string = 5;" + }; + const configB: File = { + path: `${projectRootPath}/B/tsconfig.json`, + content: `{ + "compilerOptions": { + "composite": true, + "declaration": true + }, + "references": [ + { "path": "../A" } + ] +}` + }; + const files = [fileA, fileB, fileB2, configA, configB, libFile]; + const host = createServerHost(files); + const projectService = createProjectService(host); + projectService.openClientFile(fileA.path); + projectService.openClientFile(fileB.path); + const knownProjects = projectService.synchronizeProjectList([], /*includeProjectReferenceRedirectInfo*/ true); + assert(knownProjects.length === 2, `Expected 2 projects but received ${knownProjects.length}`); + assert(knownProjects[0].files?.length === 3, `Expected project A to have 3 files but received ${knownProjects[0].files?.length}`); + assert(knownProjects[0].files?.every( + (file: string | protocol.FileWithProjectReferenceRedirectInfo) => + typeof file === "object" && !file.isSourceOfProjectReferenceRedirect), + `Expected every file in project A to not be redirected.` + ); + assert(knownProjects[1].files?.length === 4, `Expected project B to have 4 files but received ${knownProjects[1].files?.length}`); + assert(knownProjects[1].files?.every( + (file: string | protocol.FileWithProjectReferenceRedirectInfo) => + typeof file === "object" && !file.isSourceOfProjectReferenceRedirect), + `Expected every file in project B to not be redirected.` + ); + + host.modifyFile(configA.path, `{ + "compilerOptions": { + "composite": true, + "declaration": true + }, + "include": [ + "**/*", + "../B/b2.ts" + ] +}`); + const newKnownProjects = projectService.synchronizeProjectList(knownProjects.map(proj => proj.info!), /*includeProjectReferenceRedirectInfo*/ true); + assert(newKnownProjects[0].changes?.added.length === 1, `Expected b2.ts to be added to project A`); + assert(newKnownProjects[1].changes?.updatedRedirects?.length === 1, `Expected b2.ts to be updated in project B`); + }); + it("handles delayed directory watch invoke on file creation", () => { const projectRootPath = "/users/username/projects/project"; const fileB: File = { diff --git a/tests/baselines/reference/api/tsserverlibrary.d.ts b/tests/baselines/reference/api/tsserverlibrary.d.ts index 620b57b8330..6451786ae21 100644 --- a/tests/baselines/reference/api/tsserverlibrary.d.ts +++ b/tests/baselines/reference/api/tsserverlibrary.d.ts @@ -7079,6 +7079,11 @@ declare namespace ts.server.protocol { * List of updated files */ updated: string[] | FileWithProjectReferenceRedirectInfo[]; + /** + * List of files that have had their project reference redirect status updated + * Only provided when the synchronizeProjectList request has includeProjectReferenceRedirectInfo set to true + */ + updatedRedirects?: FileWithProjectReferenceRedirectInfo[]; } /** * Information found in a configure request. From e479a9f679467781910919f7ea156e141a473eeb Mon Sep 17 00:00:00 2001 From: Ben Lichtman Date: Fri, 24 Jan 2020 16:23:54 -0800 Subject: [PATCH 5/6] Respond to CR --- src/server/project.ts | 36 ++++++++++++++---------------------- 1 file changed, 14 insertions(+), 22 deletions(-) diff --git a/src/server/project.ts b/src/server/project.ts index b9b6e170d7f..60f23a7abe0 100644 --- a/src/server/project.ts +++ b/src/server/project.ts @@ -805,17 +805,9 @@ namespace ts.server { /* @internal */ getFileNamesWithRedirectInfo(includeProjectReferenceRedirectInfo: boolean) { - const fileNames = this.getFileNames(); - if (includeProjectReferenceRedirectInfo) { - return fileNames.map((fileName): protocol.FileWithProjectReferenceRedirectInfo => ({ - fileName, - isSourceOfProjectReferenceRedirect: this.isSourceOfProjectReferenceRedirect(fileName) - })); - } - - return fileNames.map((fileName): protocol.FileWithProjectReferenceRedirectInfo => ({ + return this.getFileNames().map((fileName): protocol.FileWithProjectReferenceRedirectInfo => ({ fileName, - isSourceOfProjectReferenceRedirect: false + isSourceOfProjectReferenceRedirect: includeProjectReferenceRedirectInfo && this.isSourceOfProjectReferenceRedirect(fileName) })); } @@ -1319,9 +1311,9 @@ namespace ts.server { getChangesSinceVersion(lastKnownVersion?: number, includeProjectReferenceRedirectInfo?: boolean): ProjectFilesWithTSDiagnostics { const includeProjectReferenceRedirectInfoIfRequested = includeProjectReferenceRedirectInfo - ? (files: Map) => arrayFrom(files.keys(), (fileName: string): protocol.FileWithProjectReferenceRedirectInfo => ({ + ? (files: Map) => arrayFrom(files.entries(), ([fileName, isSourceOfProjectReferenceRedirect]): protocol.FileWithProjectReferenceRedirectInfo => ({ fileName, - isSourceOfProjectReferenceRedirect: files.get(fileName)! // fileName guaranteed to be in files + isSourceOfProjectReferenceRedirect })) : (files: Map) => arrayFrom(files.keys()); @@ -1364,20 +1356,20 @@ namespace ts.server { const updated: string[] = updatedFileNames ? arrayFrom(updatedFileNames.keys()) : []; const updatedRedirects: protocol.FileWithProjectReferenceRedirectInfo[] = []; - forEachKey(currentFiles, id => { - if (!lastReportedFileNames.has(id)) { - added.set(id, currentFiles.get(id)!); // id guaranteed to be in currentFiles + forEachEntry(currentFiles, (isSourceOfProjectReferenceRedirect, fileName) => { + if (!lastReportedFileNames.has(fileName)) { + added.set(fileName, isSourceOfProjectReferenceRedirect); } - else if (includeProjectReferenceRedirectInfo && lastReportedFileNames.get(id) !== currentFiles.get(id)){ + else if (includeProjectReferenceRedirectInfo && isSourceOfProjectReferenceRedirect !== lastReportedFileNames.get(fileName)){ updatedRedirects.push({ - fileName: id, - isSourceOfProjectReferenceRedirect: currentFiles.get(id)! // id guaranteed to be in currentFiles + fileName, + isSourceOfProjectReferenceRedirect }); } }); - forEachKey(lastReportedFileNames, id => { - if (!currentFiles.has(id)) { - removed.set(id, lastReportedFileNames.get(id)!); // id guaranteed to be in lastReportedFileNames + forEachEntry(lastReportedFileNames, (isSourceOfProjectReferenceRedirect, fileName) => { + if (!currentFiles.has(fileName)) { + removed.set(fileName, isSourceOfProjectReferenceRedirect); } }); this.lastReportedFileNames = currentFiles; @@ -1407,7 +1399,7 @@ namespace ts.server { })); const allFiles = projectFileNames.concat(externalFiles); this.lastReportedFileNames = arrayToMap( - projectFileNames.concat(externalFiles), + allFiles, info => info.fileName, info => info.isSourceOfProjectReferenceRedirect ); From 2dd89ca57bc7dd93b8772ef1927777d6e63e9d3d Mon Sep 17 00:00:00 2001 From: Ben Lichtman Date: Tue, 28 Jan 2020 11:30:23 -0800 Subject: [PATCH 6/6] Use deepEqual in tests for clarity --- src/testRunner/unittests/tsserver/projects.ts | 103 +++++++++++++----- 1 file changed, 76 insertions(+), 27 deletions(-) diff --git a/src/testRunner/unittests/tsserver/projects.ts b/src/testRunner/unittests/tsserver/projects.ts index a966e80b2fb..49926e0ba54 100644 --- a/src/testRunner/unittests/tsserver/projects.ts +++ b/src/testRunner/unittests/tsserver/projects.ts @@ -1468,18 +1468,38 @@ var x = 10;` projectService.openClientFile(fileA.path); projectService.openClientFile(fileB.path); const knownProjects = projectService.synchronizeProjectList([], /*includeProjectReferenceRedirectInfo*/ true); - assert(knownProjects.length === 2, `Expected 2 projects but received ${knownProjects.length}`); - assert(knownProjects[0].files?.length === 3, `Expected project A to have 3 files but received ${knownProjects[0].files?.length}`); - assert(knownProjects[0].files?.every( - (file: string | protocol.FileWithProjectReferenceRedirectInfo) => - typeof file === "object" && !file.isSourceOfProjectReferenceRedirect), - `Expected every file in project A to not be redirected.` - ); - assert(knownProjects[1].files?.length === 4, `Expected project B to have 4 files but received ${knownProjects[1].files?.length}`); - knownProjects[1].files?.forEach( - (file: string | protocol.FileWithProjectReferenceRedirectInfo) => - assert(typeof file === "object" && (!file.isSourceOfProjectReferenceRedirect || file.fileName === fileA.path)) - ); + assert.deepEqual(knownProjects[0].files, [ + { + fileName: libFile.path, + isSourceOfProjectReferenceRedirect: false + }, + { + fileName: fileA.path, + isSourceOfProjectReferenceRedirect: false + }, + { + fileName: configA.path, + isSourceOfProjectReferenceRedirect: false + } + ]); + assert.deepEqual(knownProjects[1].files, [ + { + fileName: libFile.path, + isSourceOfProjectReferenceRedirect: false + }, + { + fileName: fileA.path, + isSourceOfProjectReferenceRedirect: true, + }, + { + fileName: fileB.path, + isSourceOfProjectReferenceRedirect: false, + }, + { + fileName: configB.path, + isSourceOfProjectReferenceRedirect: false + } + ]); }); it("synchronizeProjectList provides updates to redirect info when requested", () => { @@ -1523,19 +1543,38 @@ var x = 10;` projectService.openClientFile(fileA.path); projectService.openClientFile(fileB.path); const knownProjects = projectService.synchronizeProjectList([], /*includeProjectReferenceRedirectInfo*/ true); - assert(knownProjects.length === 2, `Expected 2 projects but received ${knownProjects.length}`); - assert(knownProjects[0].files?.length === 3, `Expected project A to have 3 files but received ${knownProjects[0].files?.length}`); - assert(knownProjects[0].files?.every( - (file: string | protocol.FileWithProjectReferenceRedirectInfo) => - typeof file === "object" && !file.isSourceOfProjectReferenceRedirect), - `Expected every file in project A to not be redirected.` - ); - assert(knownProjects[1].files?.length === 4, `Expected project B to have 4 files but received ${knownProjects[1].files?.length}`); - assert(knownProjects[1].files?.every( - (file: string | protocol.FileWithProjectReferenceRedirectInfo) => - typeof file === "object" && !file.isSourceOfProjectReferenceRedirect), - `Expected every file in project B to not be redirected.` - ); + assert.deepEqual(knownProjects[0].files, [ + { + fileName: libFile.path, + isSourceOfProjectReferenceRedirect: false + }, + { + fileName: fileA.path, + isSourceOfProjectReferenceRedirect: false + }, + { + fileName: configA.path, + isSourceOfProjectReferenceRedirect: false + } + ]); + assert.deepEqual(knownProjects[1].files, [ + { + fileName: libFile.path, + isSourceOfProjectReferenceRedirect: false + }, + { + fileName: fileB2.path, + isSourceOfProjectReferenceRedirect: false, + }, + { + fileName: fileB.path, + isSourceOfProjectReferenceRedirect: false, + }, + { + fileName: configB.path, + isSourceOfProjectReferenceRedirect: false + } + ]); host.modifyFile(configA.path, `{ "compilerOptions": { @@ -1548,8 +1587,18 @@ var x = 10;` ] }`); const newKnownProjects = projectService.synchronizeProjectList(knownProjects.map(proj => proj.info!), /*includeProjectReferenceRedirectInfo*/ true); - assert(newKnownProjects[0].changes?.added.length === 1, `Expected b2.ts to be added to project A`); - assert(newKnownProjects[1].changes?.updatedRedirects?.length === 1, `Expected b2.ts to be updated in project B`); + assert.deepEqual(newKnownProjects[0].changes?.added, [ + { + fileName: fileB2.path, + isSourceOfProjectReferenceRedirect: false + } + ]); + assert.deepEqual(newKnownProjects[1].changes?.updatedRedirects, [ + { + fileName: fileB2.path, + isSourceOfProjectReferenceRedirect: true + } + ]); }); it("handles delayed directory watch invoke on file creation", () => {