Skip to content

Commit 1345a35

Browse files
authored
Merge pull request microsoft#26012 from Microsoft/compileOnSaveOptimization
If project name is specified in compile on save, do not update all projects to just use that project
2 parents 4d84bde + 7b405c4 commit 1345a35

File tree

2 files changed

+110
-15
lines changed

2 files changed

+110
-15
lines changed

src/server/session.ts

Lines changed: 14 additions & 15 deletions
Original file line numberDiff line numberDiff line change
@@ -258,10 +258,6 @@ namespace ts.server {
258258
readonly symLinkedProjects: MultiMap<Project>;
259259
};
260260

261-
function isProjectsArray(projects: Projects): projects is ReadonlyArray<Project> {
262-
return !!(<ReadonlyArray<Project>>projects).length;
263-
}
264-
265261
/**
266262
* This helper function processes a list of projects and return the concatenated, sortd and deduplicated output of processing each project.
267263
*/
@@ -273,8 +269,8 @@ namespace ts.server {
273269
comparer?: (a: U, b: U) => number,
274270
areEqual?: (a: U, b: U) => boolean,
275271
): U[] {
276-
const outputs = flatMap(isProjectsArray(projects) ? projects : projects.projects, project => action(project, defaultValue));
277-
if (!isProjectsArray(projects) && projects.symLinkedProjects) {
272+
const outputs = flatMap(isArray(projects) ? projects : projects.projects, project => action(project, defaultValue));
273+
if (!isArray(projects) && projects.symLinkedProjects) {
278274
projects.symLinkedProjects.forEach((projects, path) => {
279275
const value = getValue(path as Path);
280276
outputs.push(...flatMap(projects, project => action(project, value)));
@@ -370,7 +366,7 @@ namespace ts.server {
370366
}
371367

372368
function forEachProjectInProjects(projects: Projects, path: string | undefined, cb: (project: Project, path: string | undefined) => void): void {
373-
for (const project of isProjectsArray(projects) ? projects : projects.projects) {
369+
for (const project of isArray(projects) ? projects : projects.projects) {
374370
cb(project, path);
375371
}
376372
if (!isArray(projects) && projects.symLinkedProjects) {
@@ -1106,7 +1102,7 @@ namespace ts.server {
11061102
return project.getLanguageService().getRenameInfo(file, position);
11071103
}
11081104

1109-
private getProjects(args: protocol.FileRequestArgs): Projects {
1105+
private getProjects(args: protocol.FileRequestArgs, getScriptInfoEnsuringProjectsUptoDate?: boolean, ignoreNoProjectError?: boolean): Projects {
11101106
let projects: ReadonlyArray<Project> | undefined;
11111107
let symLinkedProjects: MultiMap<Project> | undefined;
11121108
if (args.projectFileName) {
@@ -1116,13 +1112,18 @@ namespace ts.server {
11161112
}
11171113
}
11181114
else {
1119-
const scriptInfo = this.projectService.getScriptInfo(args.file)!;
1115+
const scriptInfo = getScriptInfoEnsuringProjectsUptoDate ?
1116+
this.projectService.getScriptInfoEnsuringProjectsUptoDate(args.file) :
1117+
this.projectService.getScriptInfo(args.file);
1118+
if (!scriptInfo) {
1119+
return ignoreNoProjectError ? emptyArray : Errors.ThrowNoProject();
1120+
}
11201121
projects = scriptInfo.containingProjects;
11211122
symLinkedProjects = this.projectService.getSymlinkedProjects(scriptInfo);
11221123
}
11231124
// filter handles case when 'projects' is undefined
11241125
projects = filter(projects, p => p.languageServiceEnabled && !p.isOrphan());
1125-
if ((!projects || !projects.length) && !symLinkedProjects) {
1126+
if (!ignoreNoProjectError && (!projects || !projects.length) && !symLinkedProjects) {
11261127
return Errors.ThrowNoProject();
11271128
}
11281129
return symLinkedProjects ? { projects: projects!, symLinkedProjects } : projects!; // TODO: GH#18217
@@ -1465,18 +1466,16 @@ namespace ts.server {
14651466
}
14661467

14671468
private getCompileOnSaveAffectedFileList(args: protocol.FileRequestArgs): ReadonlyArray<protocol.CompileOnSaveAffectedFileListSingleProject> {
1468-
const info = this.projectService.getScriptInfoEnsuringProjectsUptoDate(args.file);
1469+
const projects = this.getProjects(args, /*getScriptInfoEnsuringProjectsUptoDate*/ true, /*ignoreNoProjectError*/ true);
1470+
const info = this.projectService.getScriptInfo(args.file);
14691471
if (!info) {
14701472
return emptyArray;
14711473
}
14721474

1473-
// if specified a project, we only return affected file list in this project
1474-
const projects = args.projectFileName ? [this.projectService.findProject(args.projectFileName)!] : info.containingProjects;
1475-
const symLinkedProjects = !args.projectFileName && this.projectService.getSymlinkedProjects(info);
14761475
return combineProjectOutput(
14771476
info,
14781477
path => this.projectService.getScriptInfoForPath(path)!,
1479-
symLinkedProjects ? { projects, symLinkedProjects } : projects,
1478+
projects,
14801479
(project, info) => {
14811480
let result: protocol.CompileOnSaveAffectedFileListSingleProject | undefined;
14821481
if (project.compileOnSaveEnabled && project.languageServiceEnabled && !project.isOrphan() && !project.getCompilationSettings().noEmit) {

src/testRunner/unittests/tsserverProjectSystem.ts

Lines changed: 96 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -3495,6 +3495,102 @@ namespace ts.projectSystem {
34953495
openFilesForSession([{ file, projectRootPath }], session);
34963496
}
34973497
});
3498+
3499+
describe("CompileOnSaveAffectedFileListRequest with and without projectFileName in request", () => {
3500+
const projectRoot = "/user/username/projects/myproject";
3501+
const core: File = {
3502+
path: `${projectRoot}/core/core.ts`,
3503+
content: "let z = 10;"
3504+
};
3505+
const app1: File = {
3506+
path: `${projectRoot}/app1/app.ts`,
3507+
content: "let x = 10;"
3508+
};
3509+
const app2: File = {
3510+
path: `${projectRoot}/app2/app.ts`,
3511+
content: "let y = 10;"
3512+
};
3513+
const app1Config: File = {
3514+
path: `${projectRoot}/app1/tsconfig.json`,
3515+
content: JSON.stringify({
3516+
files: ["app.ts", "../core/core.ts"],
3517+
compilerOptions: { outFile : "build/output.js" },
3518+
compileOnSave: true
3519+
})
3520+
};
3521+
const app2Config: File = {
3522+
path: `${projectRoot}/app2/tsconfig.json`,
3523+
content: JSON.stringify({
3524+
files: ["app.ts", "../core/core.ts"],
3525+
compilerOptions: { outFile: "build/output.js" },
3526+
compileOnSave: true
3527+
})
3528+
};
3529+
const files = [libFile, core, app1, app2, app1Config, app2Config];
3530+
3531+
function insertString(session: TestSession, file: File) {
3532+
session.executeCommandSeq<protocol.ChangeRequest>({
3533+
command: protocol.CommandTypes.Change,
3534+
arguments: {
3535+
file: file.path,
3536+
line: 1,
3537+
offset: 1,
3538+
endLine: 1,
3539+
endOffset: 1,
3540+
insertString: "let k = 1"
3541+
}
3542+
});
3543+
}
3544+
3545+
function getSession() {
3546+
const host = createServerHost(files);
3547+
const session = createSession(host);
3548+
openFilesForSession([app1, app2, core], session);
3549+
const service = session.getProjectService();
3550+
checkNumberOfProjects(session.getProjectService(), { configuredProjects: 2 });
3551+
const project1 = service.configuredProjects.get(app1Config.path)!;
3552+
const project2 = service.configuredProjects.get(app2Config.path)!;
3553+
checkProjectActualFiles(project1, [libFile.path, app1.path, core.path, app1Config.path]);
3554+
checkProjectActualFiles(project2, [libFile.path, app2.path, core.path, app2Config.path]);
3555+
insertString(session, app1);
3556+
insertString(session, app2);
3557+
assert.equal(project1.dirty, true);
3558+
assert.equal(project2.dirty, true);
3559+
return session;
3560+
}
3561+
3562+
it("when projectFile is specified", () => {
3563+
const session = getSession();
3564+
const response = session.executeCommandSeq<protocol.CompileOnSaveAffectedFileListRequest>({
3565+
command: protocol.CommandTypes.CompileOnSaveAffectedFileList,
3566+
arguments: {
3567+
file: core.path,
3568+
projectFileName: app1Config.path
3569+
}
3570+
}).response;
3571+
assert.deepEqual(response, [
3572+
{ projectFileName: app1Config.path, fileNames: [core.path, app1.path], projectUsesOutFile: true }
3573+
]);
3574+
assert.equal(session.getProjectService().configuredProjects.get(app1Config.path)!.dirty, false);
3575+
assert.equal(session.getProjectService().configuredProjects.get(app2Config.path)!.dirty, true);
3576+
});
3577+
3578+
it("when projectFile is not specified", () => {
3579+
const session = getSession();
3580+
const response = session.executeCommandSeq<protocol.CompileOnSaveAffectedFileListRequest>({
3581+
command: protocol.CommandTypes.CompileOnSaveAffectedFileList,
3582+
arguments: {
3583+
file: core.path
3584+
}
3585+
}).response;
3586+
assert.deepEqual(response, [
3587+
{ projectFileName: app1Config.path, fileNames: [core.path, app1.path], projectUsesOutFile: true },
3588+
{ projectFileName: app2Config.path, fileNames: [core.path, app2.path], projectUsesOutFile: true }
3589+
]);
3590+
assert.equal(session.getProjectService().configuredProjects.get(app1Config.path)!.dirty, false);
3591+
assert.equal(session.getProjectService().configuredProjects.get(app2Config.path)!.dirty, false);
3592+
});
3593+
});
34983594
});
34993595

35003596
describe("tsserverProjectSystem Proper errors", () => {

0 commit comments

Comments
 (0)