correctly update external project if config file is added or removed
This commit is contained in:
parent
3a9d8506eb
commit
5c604f92d7
2 changed files with 204 additions and 13 deletions
|
@ -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({});
|
||||
});
|
||||
});
|
||||
}
|
|
@ -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();
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
Loading…
Reference in a new issue