Merge pull request #19138 from Microsoft/configuredProjectRef

Handle the configured project lifetime to account for files added to the project after config file gets reloaded
This commit is contained in:
Sheetal Nandi
2017-10-18 14:23:25 -07:00
committed by GitHub
4 changed files with 299 additions and 62 deletions

View File

@@ -569,6 +569,11 @@ namespace ts.server {
});
}
/*@internal*/
hasPendingProjectUpdate(project: Project) {
return this.pendingProjectUpdates.has(project.getProjectName());
}
private sendProjectsUpdatedInBackgroundEvent() {
if (!this.eventHandler) {
return;
@@ -810,8 +815,14 @@ namespace ts.server {
);
}
/** Gets the config file existence info for the configured project */
/*@internal*/
getConfigFileExistenceInfo(project: ConfiguredProject) {
return this.configFileExistenceInfoCache.get(project.canonicalConfigFilePath);
}
private onConfigChangedForConfiguredProject(project: ConfiguredProject, eventKind: FileWatcherEventKind) {
const configFileExistenceInfo = this.configFileExistenceInfoCache.get(project.canonicalConfigFilePath);
const configFileExistenceInfo = this.getConfigFileExistenceInfo(project);
if (eventKind === FileWatcherEventKind.Deleted) {
// Update the cached status
// We arent updating or removing the cached config file presence info as that will be taken care of by
@@ -913,18 +924,6 @@ namespace ts.server {
return project;
}
private addToListOfOpenFiles(info: ScriptInfo) {
Debug.assert(!info.isOrphan());
for (const p of info.containingProjects) {
// file is the part of configured project, addref the project
if (p.projectKind === ProjectKind.Configured) {
((<ConfiguredProject>p)).addOpenRef();
}
}
this.openFiles.push(info);
}
/**
* Remove this file from the set of open, non-configured files.
* @param info The file that has been closed or newly configured
@@ -947,10 +946,8 @@ namespace ts.server {
if (info.hasMixedContent) {
info.registerFileUpdate();
}
// Delete the reference to the open configured projects but
// do not remove the project so that we can reuse this project
// Do not remove the project so that we can reuse this project
// if it would need to be re-created with next file open
(<ConfiguredProject>p).deleteOpenRef();
}
else if (p.projectKind === ProjectKind.Inferred && p.isRoot(info)) {
// If this was the open root file of inferred project
@@ -1040,7 +1037,7 @@ namespace ts.server {
}
private setConfigFileExistenceByNewConfiguredProject(project: ConfiguredProject) {
const configFileExistenceInfo = this.configFileExistenceInfoCache.get(project.canonicalConfigFilePath);
const configFileExistenceInfo = this.getConfigFileExistenceInfo(project);
if (configFileExistenceInfo) {
Debug.assert(configFileExistenceInfo.exists);
// close existing watcher
@@ -1069,7 +1066,7 @@ namespace ts.server {
}
private setConfigFileExistenceInfoByClosedConfiguredProject(closedProject: ConfiguredProject) {
const configFileExistenceInfo = this.configFileExistenceInfoCache.get(closedProject.canonicalConfigFilePath);
const configFileExistenceInfo = this.getConfigFileExistenceInfo(closedProject);
Debug.assert(!!configFileExistenceInfo);
if (configFileExistenceInfo.openFilesImpactedByConfigFile.size) {
const configFileName = closedProject.getConfigFilePath();
@@ -1958,7 +1955,8 @@ namespace ts.server {
this.assignOrphanScriptInfoToInferredProject(info, projectRootPath);
}
this.addToListOfOpenFiles(info);
Debug.assert(!info.isOrphan());
this.openFiles.push(info);
if (sendConfigFileDiagEvent) {
configFileErrors = project.getAllProjectErrors();
@@ -2058,11 +2056,14 @@ namespace ts.server {
}
}
private closeConfiguredProject(configFile: NormalizedPath): boolean {
private closeConfiguredProjectReferencedFromExternalProject(configFile: NormalizedPath): boolean {
const configuredProject = this.findConfiguredProjectByProjectName(configFile);
if (configuredProject && configuredProject.deleteOpenRef() === 0) {
this.removeProject(configuredProject);
return true;
if (configuredProject) {
configuredProject.deleteExternalProjectReference();
if (!configuredProject.hasOpenRef()) {
this.removeProject(configuredProject);
return true;
}
}
return false;
}
@@ -2073,7 +2074,7 @@ namespace ts.server {
if (configFiles) {
let shouldRefreshInferredProjects = false;
for (const configFile of configFiles) {
if (this.closeConfiguredProject(configFile)) {
if (this.closeConfiguredProjectReferencedFromExternalProject(configFile)) {
shouldRefreshInferredProjects = true;
}
}
@@ -2268,7 +2269,7 @@ namespace ts.server {
const newConfig = tsConfigFiles[iNew];
const oldConfig = oldConfigFiles[iOld];
if (oldConfig < newConfig) {
this.closeConfiguredProject(oldConfig);
this.closeConfiguredProjectReferencedFromExternalProject(oldConfig);
iOld++;
}
else if (oldConfig > newConfig) {
@@ -2283,7 +2284,7 @@ namespace ts.server {
}
for (let i = iOld; i < oldConfigFiles.length; i++) {
// projects for all remaining old config files should be closed
this.closeConfiguredProject(oldConfigFiles[i]);
this.closeConfiguredProjectReferencedFromExternalProject(oldConfigFiles[i]);
}
}
}
@@ -2298,7 +2299,7 @@ namespace ts.server {
}
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();
project.addExternalProjectReference();
}
}
}

View File

@@ -908,9 +908,7 @@ namespace ts.server {
}
getScriptInfoForNormalizedPath(fileName: NormalizedPath) {
const scriptInfo = this.projectService.getOrCreateScriptInfoNotOpenedByClientForNormalizedPath(
fileName, /*scriptKind*/ undefined, /*hasMixedContent*/ undefined, this.directoryStructureHost
);
const scriptInfo = this.projectService.getScriptInfoForNormalizedPath(fileName);
if (scriptInfo && !scriptInfo.isAttached(this)) {
return Errors.ThrowProjectDoesNotContainDocument(fileName, this);
}
@@ -918,7 +916,7 @@ namespace ts.server {
}
getScriptInfo(uncheckedFileName: string) {
return this.getScriptInfoForNormalizedPath(toNormalizedPath(uncheckedFileName));
return this.projectService.getScriptInfo(uncheckedFileName);
}
filesToString(writeProjectFileNames: boolean) {
@@ -1146,8 +1144,8 @@ namespace ts.server {
private plugins: PluginModule[] = [];
/** Used for configured projects which may have multiple open roots */
openRefCount = 0;
/** Ref count to the project when opened from external project */
private externalProjectRefCount = 0;
private projectErrors: Diagnostic[];
@@ -1358,17 +1356,43 @@ namespace ts.server {
super.close();
}
addOpenRef() {
this.openRefCount++;
/* @internal */
addExternalProjectReference() {
this.externalProjectRefCount++;
}
deleteOpenRef() {
this.openRefCount--;
return this.openRefCount;
/* @internal */
deleteExternalProjectReference() {
this.externalProjectRefCount--;
}
/** Returns true if the project is needed by any of the open script info/external project */
/* @internal */
hasOpenRef() {
return !!this.openRefCount;
if (!!this.externalProjectRefCount) {
return true;
}
// Closed project doesnt have any reference
if (this.isClosed()) {
return false;
}
const configFileExistenceInfo = this.projectService.getConfigFileExistenceInfo(this);
if (this.projectService.hasPendingProjectUpdate(this)) {
// If there is pending update for this project,
// we dont know if this project would be needed by any of the open files impacted by this config file
// In that case keep the project alive if there are open files impacted by this project
return !!configFileExistenceInfo.openFilesImpactedByConfigFile.size;
}
// If there is no pending update for this project,
// We know exact set of open files that get impacted by this configured project as the files in the project
// The project is referenced only if open files impacted by this project are present in this project
return forEachEntry(
configFileExistenceInfo.openFilesImpactedByConfigFile,
(_value, infoPath) => this.containsScriptInfo(this.projectService.getScriptInfoForPath(infoPath as Path))
) || false;
}
getEffectiveTypeRoots() {