From 94033f212dcfca2a351b3b71d9e281d3e3f9789e Mon Sep 17 00:00:00 2001 From: Sheetal Nandi Date: Wed, 25 Jul 2018 13:32:27 -0700 Subject: [PATCH 1/4] If project name is specified in compile on save, do not update all projects to just use that project --- src/server/session.ts | 15 +++++++++++++-- 1 file changed, 13 insertions(+), 2 deletions(-) diff --git a/src/server/session.ts b/src/server/session.ts index aae9ab8980f..ea0bf21dc3e 100644 --- a/src/server/session.ts +++ b/src/server/session.ts @@ -1465,13 +1465,24 @@ namespace ts.server { } private getCompileOnSaveAffectedFileList(args: protocol.FileRequestArgs): ReadonlyArray { - const info = this.projectService.getScriptInfoEnsuringProjectsUptoDate(args.file); + let info: ScriptInfo | undefined; + let project: Project | undefined; + if (args.projectFileName) { + // Do not update all projects if we are looking for specific project to compile on save + project = this.projectService.findProject(args.projectFileName)!; + if (project.dirty) project.updateGraph(); + info = this.projectService.getScriptInfo(args.file); + } + else { + info = this.projectService.getScriptInfoEnsuringProjectsUptoDate(args.file); + } + if (!info) { return emptyArray; } // if specified a project, we only return affected file list in this project - const projects = args.projectFileName ? [this.projectService.findProject(args.projectFileName)!] : info.containingProjects; + const projects = args.projectFileName ? [project!] : info.containingProjects; const symLinkedProjects = !args.projectFileName && this.projectService.getSymlinkedProjects(info); return combineProjectOutput( info, From 399f497a32278e29e1d468905b6f32d7436eee5a Mon Sep 17 00:00:00 2001 From: Sheetal Nandi Date: Fri, 27 Jul 2018 12:47:47 -0700 Subject: [PATCH 2/4] Unify getProjects for compileOnSave and others like (navigation etc) --- src/server/session.ts | 30 +++++++++++------------------- 1 file changed, 11 insertions(+), 19 deletions(-) diff --git a/src/server/session.ts b/src/server/session.ts index ea0bf21dc3e..ca14059a464 100644 --- a/src/server/session.ts +++ b/src/server/session.ts @@ -1106,7 +1106,7 @@ namespace ts.server { return project.getLanguageService().getRenameInfo(file, position); } - private getProjects(args: protocol.FileRequestArgs): Projects { + private getProjects(args: protocol.FileRequestArgs, getScriptInfoEnsuringProjectsUptoDate?: boolean, ignoreNoProjectError?: boolean): Projects { let projects: ReadonlyArray | undefined; let symLinkedProjects: MultiMap | undefined; if (args.projectFileName) { @@ -1116,13 +1116,18 @@ namespace ts.server { } } else { - const scriptInfo = this.projectService.getScriptInfo(args.file)!; + const scriptInfo = getScriptInfoEnsuringProjectsUptoDate ? + this.projectService.getScriptInfoEnsuringProjectsUptoDate(args.file) : + this.projectService.getScriptInfo(args.file); + if (!scriptInfo) { + return ignoreNoProjectError ? emptyArray : Errors.ThrowNoProject(); + } projects = scriptInfo.containingProjects; symLinkedProjects = this.projectService.getSymlinkedProjects(scriptInfo); } // filter handles case when 'projects' is undefined projects = filter(projects, p => p.languageServiceEnabled && !p.isOrphan()); - if ((!projects || !projects.length) && !symLinkedProjects) { + if (!ignoreNoProjectError && (!projects || !projects.length) && !symLinkedProjects) { return Errors.ThrowNoProject(); } return symLinkedProjects ? { projects: projects!, symLinkedProjects } : projects!; // TODO: GH#18217 @@ -1465,29 +1470,16 @@ namespace ts.server { } private getCompileOnSaveAffectedFileList(args: protocol.FileRequestArgs): ReadonlyArray { - let info: ScriptInfo | undefined; - let project: Project | undefined; - if (args.projectFileName) { - // Do not update all projects if we are looking for specific project to compile on save - project = this.projectService.findProject(args.projectFileName)!; - if (project.dirty) project.updateGraph(); - info = this.projectService.getScriptInfo(args.file); - } - else { - info = this.projectService.getScriptInfoEnsuringProjectsUptoDate(args.file); - } - + const projects = this.getProjects(args, /*getScriptInfoEnsuringProjectsUptoDate*/ true, /*ignoreNoProjectError*/ true); + const info = this.projectService.getScriptInfo(args.file); if (!info) { return emptyArray; } - // if specified a project, we only return affected file list in this project - const projects = args.projectFileName ? [project!] : info.containingProjects; - const symLinkedProjects = !args.projectFileName && this.projectService.getSymlinkedProjects(info); return combineProjectOutput( info, path => this.projectService.getScriptInfoForPath(path)!, - symLinkedProjects ? { projects, symLinkedProjects } : projects, + projects, (project, info) => { let result: protocol.CompileOnSaveAffectedFileListSingleProject | undefined; if (project.compileOnSaveEnabled && project.languageServiceEnabled && !project.isOrphan() && !project.getCompilationSettings().noEmit) { From ab1b899cea594d890ec97cecd65b6db72d771451 Mon Sep 17 00:00:00 2001 From: Sheetal Nandi Date: Fri, 27 Jul 2018 12:50:32 -0700 Subject: [PATCH 3/4] Use isArray instead of isProjectsArray --- src/server/session.ts | 10 +++------- 1 file changed, 3 insertions(+), 7 deletions(-) diff --git a/src/server/session.ts b/src/server/session.ts index ca14059a464..7dccda3c82c 100644 --- a/src/server/session.ts +++ b/src/server/session.ts @@ -258,10 +258,6 @@ namespace ts.server { readonly symLinkedProjects: MultiMap; }; - function isProjectsArray(projects: Projects): projects is ReadonlyArray { - return !!(>projects).length; - } - /** * This helper function processes a list of projects and return the concatenated, sortd and deduplicated output of processing each project. */ @@ -273,8 +269,8 @@ namespace ts.server { comparer?: (a: U, b: U) => number, areEqual?: (a: U, b: U) => boolean, ): U[] { - const outputs = flatMap(isProjectsArray(projects) ? projects : projects.projects, project => action(project, defaultValue)); - if (!isProjectsArray(projects) && projects.symLinkedProjects) { + const outputs = flatMap(isArray(projects) ? projects : projects.projects, project => action(project, defaultValue)); + if (!isArray(projects) && projects.symLinkedProjects) { projects.symLinkedProjects.forEach((projects, path) => { const value = getValue(path as Path); outputs.push(...flatMap(projects, project => action(project, value))); @@ -370,7 +366,7 @@ namespace ts.server { } function forEachProjectInProjects(projects: Projects, path: string | undefined, cb: (project: Project, path: string | undefined) => void): void { - for (const project of isProjectsArray(projects) ? projects : projects.projects) { + for (const project of isArray(projects) ? projects : projects.projects) { cb(project, path); } if (!isArray(projects) && projects.symLinkedProjects) { From 7b405c4ad9bed4e32b88b9393f90167acaa48415 Mon Sep 17 00:00:00 2001 From: Sheetal Nandi Date: Mon, 30 Jul 2018 14:10:56 -0700 Subject: [PATCH 4/4] Test that verifies we arent updating all projects unnecessarily when called with compileOnSave and project --- .../unittests/tsserverProjectSystem.ts | 96 +++++++++++++++++++ 1 file changed, 96 insertions(+) diff --git a/src/testRunner/unittests/tsserverProjectSystem.ts b/src/testRunner/unittests/tsserverProjectSystem.ts index 309107923e9..598e5dac6e2 100644 --- a/src/testRunner/unittests/tsserverProjectSystem.ts +++ b/src/testRunner/unittests/tsserverProjectSystem.ts @@ -3479,6 +3479,102 @@ namespace ts.projectSystem { openFilesForSession([{ file, projectRootPath }], session); } }); + + describe("CompileOnSaveAffectedFileListRequest with and without projectFileName in request", () => { + const projectRoot = "/user/username/projects/myproject"; + const core: File = { + path: `${projectRoot}/core/core.ts`, + content: "let z = 10;" + }; + const app1: File = { + path: `${projectRoot}/app1/app.ts`, + content: "let x = 10;" + }; + const app2: File = { + path: `${projectRoot}/app2/app.ts`, + content: "let y = 10;" + }; + const app1Config: File = { + path: `${projectRoot}/app1/tsconfig.json`, + content: JSON.stringify({ + files: ["app.ts", "../core/core.ts"], + compilerOptions: { outFile : "build/output.js" }, + compileOnSave: true + }) + }; + const app2Config: File = { + path: `${projectRoot}/app2/tsconfig.json`, + content: JSON.stringify({ + files: ["app.ts", "../core/core.ts"], + compilerOptions: { outFile: "build/output.js" }, + compileOnSave: true + }) + }; + const files = [libFile, core, app1, app2, app1Config, app2Config]; + + function insertString(session: TestSession, file: File) { + session.executeCommandSeq({ + command: protocol.CommandTypes.Change, + arguments: { + file: file.path, + line: 1, + offset: 1, + endLine: 1, + endOffset: 1, + insertString: "let k = 1" + } + }); + } + + function getSession() { + const host = createServerHost(files); + const session = createSession(host); + openFilesForSession([app1, app2, core], session); + const service = session.getProjectService(); + checkNumberOfProjects(session.getProjectService(), { configuredProjects: 2 }); + const project1 = service.configuredProjects.get(app1Config.path)!; + const project2 = service.configuredProjects.get(app2Config.path)!; + checkProjectActualFiles(project1, [libFile.path, app1.path, core.path, app1Config.path]); + checkProjectActualFiles(project2, [libFile.path, app2.path, core.path, app2Config.path]); + insertString(session, app1); + insertString(session, app2); + assert.equal(project1.dirty, true); + assert.equal(project2.dirty, true); + return session; + } + + it("when projectFile is specified", () => { + const session = getSession(); + const response = session.executeCommandSeq({ + command: protocol.CommandTypes.CompileOnSaveAffectedFileList, + arguments: { + file: core.path, + projectFileName: app1Config.path + } + }).response; + assert.deepEqual(response, [ + { projectFileName: app1Config.path, fileNames: [core.path, app1.path], projectUsesOutFile: true } + ]); + assert.equal(session.getProjectService().configuredProjects.get(app1Config.path)!.dirty, false); + assert.equal(session.getProjectService().configuredProjects.get(app2Config.path)!.dirty, true); + }); + + it("when projectFile is not specified", () => { + const session = getSession(); + const response = session.executeCommandSeq({ + command: protocol.CommandTypes.CompileOnSaveAffectedFileList, + arguments: { + file: core.path + } + }).response; + assert.deepEqual(response, [ + { projectFileName: app1Config.path, fileNames: [core.path, app1.path], projectUsesOutFile: true }, + { projectFileName: app2Config.path, fileNames: [core.path, app2.path], projectUsesOutFile: true } + ]); + assert.equal(session.getProjectService().configuredProjects.get(app1Config.path)!.dirty, false); + assert.equal(session.getProjectService().configuredProjects.get(app2Config.path)!.dirty, false); + }); + }); }); describe("tsserverProjectSystem Proper errors", () => {