Add disableReferencedProjectLoad to stop loading child projects to allow users to disable loading large solutions (#39593)

* Use disableReferencedProjectLoad to stop loading child projects to allow users to disable loading large solutions
Fixes #39144

* Handle indirect references

* PR feedback
This commit is contained in:
Sheetal Nandi
2020-07-21 14:24:15 -07:00
committed by GitHub
parent 5484687384
commit e92afacc44
10 changed files with 302 additions and 66 deletions

View File

@@ -432,34 +432,55 @@ namespace ts.server {
/*@internal*/
export function forEachResolvedProjectReferenceProject<T>(
project: ConfiguredProject,
cb: (child: ConfiguredProject, configFileName: NormalizedPath) => T | undefined,
projectReferenceProjectLoadKind: ProjectReferenceProjectLoadKind.Find | ProjectReferenceProjectLoadKind.FindCreate
cb: (child: ConfiguredProject) => T | undefined,
projectReferenceProjectLoadKind: ProjectReferenceProjectLoadKind.Find | ProjectReferenceProjectLoadKind.FindCreate,
): T | undefined;
/*@internal*/
export function forEachResolvedProjectReferenceProject<T>(
project: ConfiguredProject,
cb: (child: ConfiguredProject, configFileName: NormalizedPath) => T | undefined,
projectReferenceProjectLoadKind: ProjectReferenceProjectLoadKind.FindCreateLoad,
cb: (child: ConfiguredProject) => T | undefined,
projectReferenceProjectLoadKind: ProjectReferenceProjectLoadKind,
reason: string
): T | undefined;
export function forEachResolvedProjectReferenceProject<T>(
project: ConfiguredProject,
cb: (child: ConfiguredProject, configFileName: NormalizedPath) => T | undefined,
cb: (child: ConfiguredProject) => T | undefined,
projectReferenceProjectLoadKind: ProjectReferenceProjectLoadKind,
reason?: string
): T | undefined {
return forEachResolvedProjectReference(project, ref => {
if (!ref) return undefined;
const configFileName = toNormalizedPath(ref.sourceFile.fileName);
const child = project.projectService.findConfiguredProjectByProjectName(configFileName) || (
projectReferenceProjectLoadKind === ProjectReferenceProjectLoadKind.FindCreate ?
project.projectService.createConfiguredProject(configFileName) :
projectReferenceProjectLoadKind === ProjectReferenceProjectLoadKind.FindCreateLoad ?
project.projectService.createAndLoadConfiguredProject(configFileName, reason!) :
undefined
);
return child && cb(child, configFileName);
});
let seenResolvedRefs: ESMap<string, ProjectReferenceProjectLoadKind> | undefined;
return worker(project.getCurrentProgram()?.getResolvedProjectReferences(), project.getCompilerOptions());
function worker(resolvedProjectReferences: readonly (ResolvedProjectReference | undefined)[] | undefined, parentOptions: CompilerOptions): T | undefined {
const loadKind = parentOptions.disableReferencedProjectLoad ? ProjectReferenceProjectLoadKind.Find : projectReferenceProjectLoadKind;
return forEach(resolvedProjectReferences, ref => {
if (!ref) return undefined;
const configFileName = toNormalizedPath(ref.sourceFile.fileName);
const canonicalPath = project.projectService.toCanonicalFileName(configFileName);
const seenValue = seenResolvedRefs?.get(canonicalPath);
if (seenValue !== undefined && seenValue >= loadKind) {
return undefined;
}
const child = project.projectService.findConfiguredProjectByProjectName(configFileName) || (
loadKind === ProjectReferenceProjectLoadKind.Find ?
undefined :
loadKind === ProjectReferenceProjectLoadKind.FindCreate ?
project.projectService.createConfiguredProject(configFileName) :
loadKind === ProjectReferenceProjectLoadKind.FindCreateLoad ?
project.projectService.createAndLoadConfiguredProject(configFileName, reason!) :
Debug.assertNever(loadKind)
);
const result = child && cb(child);
if (result) {
return result;
}
(seenResolvedRefs || (seenResolvedRefs = new Map())).set(canonicalPath, loadKind);
return worker(ref.references, ref.commandLine.options);
});
}
}
/*@internal*/
@@ -2773,6 +2794,12 @@ namespace ts.server {
*/
private reloadConfiguredProjectForFiles<T>(openFiles: ESMap<Path, T>, delayReload: boolean, shouldReloadProjectFor: (openFileValue: T) => boolean, reason: string) {
const updatedProjects = new Map<string, true>();
const reloadChildProject = (child: ConfiguredProject) => {
if (!updatedProjects.has(child.canonicalConfigFilePath)) {
updatedProjects.set(child.canonicalConfigFilePath, true);
this.reloadConfiguredProject(child, reason);
}
};
// try to reload config file for all open files
openFiles.forEach((openFileValue, path) => {
// Filter out the files that need to be ignored
@@ -2801,17 +2828,22 @@ namespace ts.server {
this.reloadConfiguredProject(project, reason);
// If this is solution, reload the project till the reloaded project contains the script info directly
if (!project.containsScriptInfo(info) && project.isSolution()) {
forEachResolvedProjectReferenceProject(
const referencedProject = forEachResolvedProjectReferenceProject(
project,
child => {
if (!updatedProjects.has(child.canonicalConfigFilePath)) {
updatedProjects.set(child.canonicalConfigFilePath, true);
this.reloadConfiguredProject(child, reason);
}
reloadChildProject(child);
return projectContainsInfoDirectly(child, info);
},
ProjectReferenceProjectLoadKind.FindCreate
);
if (referencedProject) {
// Reload the project's tree that is already present
forEachResolvedProjectReferenceProject(
project,
reloadChildProject,
ProjectReferenceProjectLoadKind.Find
);
}
}
}
}
@@ -2913,7 +2945,7 @@ namespace ts.server {
const info = this.getScriptInfo(fileName);
return info && projectContainsInfoDirectly(child, info) ? child : undefined;
},
ProjectReferenceProjectLoadKind.FindCreateLoad,
configuredProject.getCompilerOptions().disableReferencedProjectLoad ? ProjectReferenceProjectLoadKind.Find : ProjectReferenceProjectLoadKind.FindCreateLoad,
`Creating project referenced in solution ${configuredProject.projectName} to find possible configured project for original file: ${originalFileInfo.fileName}${location !== originalLocation ? " for location: " + location.fileName : ""}`
);
if (!configuredProject) return undefined;
@@ -2965,8 +2997,9 @@ namespace ts.server {
let configFileName: NormalizedPath | undefined;
let configFileErrors: readonly Diagnostic[] | undefined;
let project: ConfiguredProject | ExternalProject | undefined = this.findExternalProjectContainingOpenScriptInfo(info);
let defaultConfigProject: ConfiguredProject | undefined;
let retainProjects: ConfiguredProject[] | ConfiguredProject | undefined;
let projectForConfigFileDiag: ConfiguredProject | undefined;
let defaultConfigProjectIsCreated = false;
if (this.syntaxOnly) {
// Invalidate resolutions in the file since this file is now open
info.containingProjects.forEach(project => {
@@ -2981,30 +3014,22 @@ namespace ts.server {
project = this.findConfiguredProjectByProjectName(configFileName);
if (!project) {
project = this.createLoadAndUpdateConfiguredProject(configFileName, `Creating possible configured project for ${info.fileName} to open`);
// Send the event only if the project got created as part of this open request and info is part of the project
if (!project.containsScriptInfo(info)) {
// Since the file isnt part of configured project, do not send config file info
configFileName = undefined;
}
else {
configFileErrors = project.getAllProjectErrors();
this.sendConfigFileDiagEvent(project, info.fileName);
}
defaultConfigProjectIsCreated = true;
}
else {
// Ensure project is ready to check if it contains opened script info
updateProjectIfDirty(project);
}
defaultConfigProject = project;
retainProjects = defaultConfigProject;
projectForConfigFileDiag = project.containsScriptInfo(info) ? project : undefined;
retainProjects = project;
// If this configured project doesnt contain script info but
// it is solution with project references, try those project references
if (!project.containsScriptInfo(info) && project.isSolution()) {
if (project.isSolution()) {
forEachResolvedProjectReferenceProject(
project,
(child, childConfigFileName) => {
child => {
updateProjectIfDirty(child);
// Retain these projects
if (!isArray(retainProjects)) {
@@ -3016,20 +3041,35 @@ namespace ts.server {
// If script info belongs to this child project, use this as default config project
if (projectContainsInfoDirectly(child, info)) {
configFileName = childConfigFileName;
configFileErrors = child.getAllProjectErrors();
this.sendConfigFileDiagEvent(child, info.fileName);
projectForConfigFileDiag = child;
return child;
}
// If this project uses the script info (even through project reference), if default project is not found, use this for configFileDiag
if (!projectForConfigFileDiag && child.containsScriptInfo(info)) {
projectForConfigFileDiag = child;
}
},
ProjectReferenceProjectLoadKind.FindCreateLoad,
`Creating project referenced in solution ${project.projectName} to find possible configured project for ${info.fileName} to open`
);
}
else {
// Create ancestor configured project
this.createAncestorProjects(info, defaultConfigProject || project);
// Send the event only if the project got created as part of this open request and info is part of the project
if (projectForConfigFileDiag) {
configFileName = projectForConfigFileDiag.getConfigFilePath();
if (projectForConfigFileDiag !== project || defaultConfigProjectIsCreated) {
configFileErrors = projectForConfigFileDiag.getAllProjectErrors();
this.sendConfigFileDiagEvent(projectForConfigFileDiag, info.fileName);
}
}
else {
// Since the file isnt part of configured project, do not send config file info
configFileName = undefined;
}
// Create ancestor configured project
this.createAncestorProjects(info, project);
}
}

View File

@@ -2264,9 +2264,16 @@ namespace ts.server {
// The project is referenced only if open files impacted by this project are present in this project
return forEachEntry(
configFileExistenceInfo.openFilesImpactedByConfigFile,
(_value, infoPath) => isSolution ?
!!this.getDefaultChildProjectFromSolution(this.projectService.getScriptInfoForPath(infoPath)!) :
this.containsScriptInfo(this.projectService.getScriptInfoForPath(infoPath)!)
(_value, infoPath) => {
const info = this.projectService.getScriptInfoForPath(infoPath)!;
return isSolution ?
!!forEachResolvedProjectReferenceProject(
this,
child => child.containsScriptInfo(info),
ProjectReferenceProjectLoadKind.Find
) :
this.containsScriptInfo(info);
}
) || false;
}