Merge pull request #10943 from Microsoft/update-external-project

correctly update external project if config file is added or removed
This commit is contained in:
Vladimir Matveev 2016-09-16 14:39:00 -07:00 committed by GitHub
commit 4668c99222
2 changed files with 204 additions and 13 deletions

View file

@ -1747,4 +1747,138 @@ namespace ts.projectSystem {
assert.isTrue(containsNavToItem(items2, "foo", "function"), `Cannot find function symbol "foo".`);
});
});
describe("external projects", () => {
it("correctly handling add/remove tsconfig - 1", () => {
const f1 = {
path: "/a/b/app.ts",
content: "let x = 1;"
};
const f2 = {
path: "/a/b/lib.ts",
content: ""
};
const tsconfig = {
path: "/a/b/tsconfig.json",
content: ""
};
const host = createServerHost([f1, f2]);
const projectService = createProjectService(host);
// open external project
const projectName = "/a/b/proj1";
projectService.openExternalProject({
projectFileName: projectName,
rootFiles: toExternalFiles([f1.path, f2.path]),
options: {}
});
projectService.openClientFile(f1.path);
projectService.checkNumberOfProjects({ externalProjects: 1 });
checkProjectActualFiles(projectService.externalProjects[0], [f1.path, f2.path]);
// rename lib.ts to tsconfig.json
host.reloadFS([f1, tsconfig]);
projectService.openExternalProject({
projectFileName: projectName,
rootFiles: toExternalFiles([f1.path, tsconfig.path]),
options: {}
});
projectService.checkNumberOfProjects({ configuredProjects: 1 });
checkProjectActualFiles(projectService.configuredProjects[0], [f1.path]);
// rename tsconfig.json back to lib.ts
host.reloadFS([f1, f2]);
host.triggerFileWatcherCallback(tsconfig.path, /*removed*/ true);
projectService.openExternalProject({
projectFileName: projectName,
rootFiles: toExternalFiles([f1.path, f2.path]),
options: {}
});
projectService.checkNumberOfProjects({ externalProjects: 1 });
checkProjectActualFiles(projectService.externalProjects[0], [f1.path, f2.path]);
});
it("correctly handling add/remove tsconfig - 2", () => {
const f1 = {
path: "/a/b/app.ts",
content: "let x = 1;"
};
const cLib = {
path: "/a/b/c/lib.ts",
content: ""
};
const cTsconfig = {
path: "/a/b/c/tsconfig.json",
content: "{}"
};
const dLib = {
path: "/a/b/d/lib.ts",
content: ""
};
const dTsconfig = {
path: "/a/b/d/tsconfig.json",
content: "{}"
};
const host = createServerHost([f1, cLib, cTsconfig, dLib, dTsconfig]);
const projectService = createProjectService(host);
// open external project
const projectName = "/a/b/proj1";
projectService.openExternalProject({
projectFileName: projectName,
rootFiles: toExternalFiles([f1.path]),
options: {}
});
projectService.checkNumberOfProjects({ externalProjects: 1 });
checkProjectActualFiles(projectService.externalProjects[0], [f1.path]);
// add two config file as root files
projectService.openExternalProject({
projectFileName: projectName,
rootFiles: toExternalFiles([f1.path, cTsconfig.path, dTsconfig.path]),
options: {}
});
projectService.checkNumberOfProjects({ configuredProjects: 2 });
checkProjectActualFiles(projectService.configuredProjects[0], [cLib.path]);
checkProjectActualFiles(projectService.configuredProjects[1], [dLib.path]);
// remove one config file
projectService.openExternalProject({
projectFileName: projectName,
rootFiles: toExternalFiles([f1.path, dTsconfig.path]),
options: {}
});
projectService.checkNumberOfProjects({ configuredProjects: 1 });
checkProjectActualFiles(projectService.configuredProjects[0], [dLib.path]);
// remove second config file
projectService.openExternalProject({
projectFileName: projectName,
rootFiles: toExternalFiles([f1.path]),
options: {}
});
projectService.checkNumberOfProjects({ externalProjects: 1 });
checkProjectActualFiles(projectService.externalProjects[0], [f1.path]);
// open two config files
// add two config file as root files
projectService.openExternalProject({
projectFileName: projectName,
rootFiles: toExternalFiles([f1.path, cTsconfig.path, dTsconfig.path]),
options: {}
});
projectService.checkNumberOfProjects({ configuredProjects: 2 });
checkProjectActualFiles(projectService.configuredProjects[0], [cLib.path]);
checkProjectActualFiles(projectService.configuredProjects[1], [dLib.path]);
// close all projects - no projects should be opened
projectService.closeExternalProject(projectName);
projectService.checkNumberOfProjects({});
});
});
}

View file

@ -1156,19 +1156,25 @@ namespace ts.server {
}
}
closeExternalProject(uncheckedFileName: string): void {
private closeConfiguredProject(configFile: NormalizedPath): void {
const configuredProject = this.findConfiguredProjectByProjectName(configFile);
if (configuredProject && configuredProject.deleteOpenRef() === 0) {
this.removeProject(configuredProject);
}
}
closeExternalProject(uncheckedFileName: string, suppressRefresh = false): void {
const fileName = toNormalizedPath(uncheckedFileName);
const configFiles = this.externalProjectToConfiguredProjectMap[fileName];
if (configFiles) {
let shouldRefreshInferredProjects = false;
for (const configFile of configFiles) {
const configuredProject = this.findConfiguredProjectByProjectName(configFile);
if (configuredProject && configuredProject.deleteOpenRef() === 0) {
this.removeProject(configuredProject);
if (this.closeConfiguredProject(configFile)) {
shouldRefreshInferredProjects = true;
}
}
if (shouldRefreshInferredProjects) {
delete this.externalProjectToConfiguredProjectMap[fileName];
if (shouldRefreshInferredProjects && !suppressRefresh) {
this.refreshInferredProjects();
}
}
@ -1177,18 +1183,14 @@ namespace ts.server {
const externalProject = this.findExternalProjectByProjectName(uncheckedFileName);
if (externalProject) {
this.removeProject(externalProject);
this.refreshInferredProjects();
if (!suppressRefresh) {
this.refreshInferredProjects();
}
}
}
}
openExternalProject(proj: protocol.ExternalProject): void {
const externalProject = this.findExternalProjectByProjectName(proj.projectFileName);
if (externalProject) {
this.updateNonInferredProject(externalProject, proj.rootFiles, externalFilePropertyReader, proj.options, proj.typingOptions, proj.options.compileOnSave, /*configFileErrors*/ undefined);
return;
}
let tsConfigFiles: NormalizedPath[];
const rootFiles: protocol.ExternalFile[] = [];
for (const file of proj.rootFiles) {
@ -1200,6 +1202,58 @@ namespace ts.server {
rootFiles.push(file);
}
}
// sort config files to simplify comparison later
if (tsConfigFiles) {
tsConfigFiles.sort();
}
const externalProject = this.findExternalProjectByProjectName(proj.projectFileName);
let exisingConfigFiles: string[];
if (externalProject) {
if (!tsConfigFiles) {
// external project already exists and not config files were added - update the project and return;
this.updateNonInferredProject(externalProject, proj.rootFiles, externalFilePropertyReader, proj.options, proj.typingOptions, proj.options.compileOnSave, /*configFileErrors*/ undefined);
return;
}
// some config files were added to external project (that previously were not there)
// close existing project and later we'll open a set of configured projects for these files
this.closeExternalProject(proj.projectFileName, /*suppressRefresh*/ true);
}
else if (this.externalProjectToConfiguredProjectMap[proj.projectFileName]) {
// this project used to include config files
if (!tsConfigFiles) {
// config files were removed from the project - close existing external project which in turn will close configured projects
this.closeExternalProject(proj.projectFileName, /*suppressRefresh*/ true);
}
else {
// project previously had some config files - compare them with new set of files and close all configured projects that correspond to unused files
const oldConfigFiles = this.externalProjectToConfiguredProjectMap[proj.projectFileName];
let iNew = 0;
let iOld = 0;
while (iNew < tsConfigFiles.length && iOld < oldConfigFiles.length) {
const newConfig = tsConfigFiles[iNew];
const oldConfig = oldConfigFiles[iOld];
if (oldConfig < newConfig) {
this.closeConfiguredProject(oldConfig);
iOld++;
}
else if (oldConfig > newConfig) {
iNew++;
}
else {
// record existing config files so avoid extra add-refs
(exisingConfigFiles || (exisingConfigFiles = [])).push(oldConfig);
iOld++;
iNew++;
}
}
for (let i = iOld; i < oldConfigFiles.length; i++) {
// projects for all remaining old config files should be closed
this.closeConfiguredProject(oldConfigFiles[i]);
}
}
}
if (tsConfigFiles) {
// store the list of tsconfig files that belong to the external project
this.externalProjectToConfiguredProjectMap[proj.projectFileName] = tsConfigFiles;
@ -1210,15 +1264,18 @@ namespace ts.server {
// TODO: save errors
project = result.success && result.project;
}
if (project) {
if (project && !contains(exisingConfigFiles, tsconfigFile)) {
// keep project alive even if no documents are opened - its lifetime is bound to the lifetime of containing external project
project.addOpenRef();
}
}
}
else {
// no config files - remove the item from the collection
delete this.externalProjectToConfiguredProjectMap[proj.projectFileName];
this.createAndAddExternalProject(proj.projectFileName, rootFiles, proj.options, proj.typingOptions);
}
this.refreshInferredProjects();
}
}
}