Optimizes project loading in few scenarios (#41126)

* Some refactoring of forEachResolvedProjectReference

* More refactoring

* Test before the change

* When loading project tree, load projects that directly or indirectly reference the projects we are looking for

* Optimize finding project in solution scenario by directly finding possible default project through projectReferenceRedirect
This helps in avoiding loading indirect projects when solution indirectly referenced default project
This commit is contained in:
Sheetal Nandi
2020-10-19 12:59:59 -07:00
committed by GitHub
parent 08e4f369fb
commit 15cec9d1f7
5 changed files with 267 additions and 172 deletions

View File

@@ -444,64 +444,105 @@ namespace ts.server {
/*@internal*/
export function forEachResolvedProjectReferenceProject<T>(
project: ConfiguredProject,
fileName: string | undefined,
cb: (child: ConfiguredProject) => T | undefined,
projectReferenceProjectLoadKind: ProjectReferenceProjectLoadKind.Find | ProjectReferenceProjectLoadKind.FindCreate,
): T | undefined;
/*@internal*/
export function forEachResolvedProjectReferenceProject<T>(
project: ConfiguredProject,
fileName: string | undefined,
cb: (child: ConfiguredProject) => T | undefined,
projectReferenceProjectLoadKind: ProjectReferenceProjectLoadKind,
reason: string
): T | undefined;
export function forEachResolvedProjectReferenceProject<T>(
project: ConfiguredProject,
fileName: string | undefined,
cb: (child: ConfiguredProject) => T | undefined,
projectReferenceProjectLoadKind: ProjectReferenceProjectLoadKind,
reason?: string
): T | undefined {
const resolvedRefs = project.getCurrentProgram()?.getResolvedProjectReferences();
if (!resolvedRefs) return undefined;
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 possibleDefaultRef = fileName ? project.getResolvedProjectReferenceToRedirect(fileName) : undefined;
if (possibleDefaultRef) {
// Try to find the name of the file directly through resolved project references
const configFileName = toNormalizedPath(possibleDefaultRef.sourceFile.fileName);
const child = project.projectService.findConfiguredProjectByProjectName(configFileName);
if (child) {
const result = cb(child);
if (result) return result;
}
else if (projectReferenceProjectLoadKind !== ProjectReferenceProjectLoadKind.Find) {
seenResolvedRefs = new Map();
// Try to see if this project can be loaded
const result = forEachResolvedProjectReferenceProjectWorker(
resolvedRefs,
project.getCompilerOptions(),
(ref, loadKind) => possibleDefaultRef === ref ? callback(ref, loadKind) : undefined,
projectReferenceProjectLoadKind,
project.projectService,
seenResolvedRefs
);
if (result) return result;
// Cleanup seenResolvedRefs
seenResolvedRefs.clear();
}
}
const result = child && cb(child);
if (result) {
return result;
}
return forEachResolvedProjectReferenceProjectWorker(
resolvedRefs,
project.getCompilerOptions(),
(ref, loadKind) => possibleDefaultRef !== ref ? callback(ref, loadKind) : undefined,
projectReferenceProjectLoadKind,
project.projectService,
seenResolvedRefs
);
(seenResolvedRefs || (seenResolvedRefs = new Map())).set(canonicalPath, loadKind);
return worker(ref.references, ref.commandLine.options);
});
function callback(ref: ResolvedProjectReference, loadKind: ProjectReferenceProjectLoadKind) {
const configFileName = toNormalizedPath(ref.sourceFile.fileName);
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)
);
return child && cb(child);
}
}
/*@internal*/
export function forEachResolvedProjectReference<T>(
project: ConfiguredProject,
cb: (resolvedProjectReference: ResolvedProjectReference | undefined, resolvedProjectReferencePath: Path) => T | undefined
function forEachResolvedProjectReferenceProjectWorker<T>(
resolvedProjectReferences: readonly (ResolvedProjectReference | undefined)[],
parentOptions: CompilerOptions,
cb: (resolvedRef: ResolvedProjectReference, loadKind: ProjectReferenceProjectLoadKind) => T | undefined,
projectReferenceProjectLoadKind: ProjectReferenceProjectLoadKind,
projectService: ProjectService,
seenResolvedRefs: ESMap<string, ProjectReferenceProjectLoadKind> | undefined,
): T | undefined {
const program = project.getCurrentProgram();
return program && program.forEachResolvedProjectReference(cb);
const loadKind = parentOptions.disableReferencedProjectLoad ? ProjectReferenceProjectLoadKind.Find : projectReferenceProjectLoadKind;
return forEach(resolvedProjectReferences, ref => {
if (!ref) return undefined;
const configFileName = toNormalizedPath(ref.sourceFile.fileName);
const canonicalPath = projectService.toCanonicalFileName(configFileName);
const seenValue = seenResolvedRefs?.get(canonicalPath);
if (seenValue !== undefined && seenValue >= loadKind) {
return undefined;
}
const result = cb(ref, loadKind);
if (result) {
return result;
}
(seenResolvedRefs || (seenResolvedRefs = new Map())).set(canonicalPath, loadKind);
return ref.references && forEachResolvedProjectReferenceProjectWorker(ref.references, ref.commandLine.options, cb, loadKind, projectService, seenResolvedRefs);
});
}
function forEachPotentialProjectReference<T>(
@@ -514,12 +555,12 @@ namespace ts.server {
function forEachAnyProjectReferenceKind<T>(
project: ConfiguredProject,
cb: (resolvedProjectReference: ResolvedProjectReference | undefined, resolvedProjectReferencePath: Path) => T | undefined,
cb: (resolvedProjectReference: ResolvedProjectReference) => T | undefined,
cbProjectRef: (projectReference: ProjectReference) => T | undefined,
cbPotentialProjectRef: (potentialProjectReference: Path) => T | undefined
): T | undefined {
return project.getCurrentProgram() ?
forEachResolvedProjectReference(project, cb) :
project.forEachResolvedProjectReference(cb) :
project.isInitialLoadPending() ?
forEachPotentialProjectReference(project, cbPotentialProjectRef) :
forEach(project.getProjectReferences(), cbProjectRef);
@@ -540,8 +581,8 @@ namespace ts.server {
): T | undefined {
return forEachAnyProjectReferenceKind(
project,
resolvedRef => callbackRefProject(project, cb, resolvedRef && resolvedRef.sourceFile.path),
projectRef => callbackRefProject(project, cb, project.toPath(projectRef.path)),
resolvedRef => callbackRefProject(project, cb, resolvedRef.sourceFile.path),
projectRef => callbackRefProject(project, cb, project.toPath(resolveProjectReferencePath(projectRef))),
potentialProjectRef => callbackRefProject(project, cb, potentialProjectRef)
);
}
@@ -2875,6 +2916,7 @@ namespace ts.server {
if (!projectContainsInfoDirectly(project, info)) {
const referencedProject = forEachResolvedProjectReferenceProject(
project,
info.path,
child => {
reloadChildProject(child);
return projectContainsInfoDirectly(child, info);
@@ -2885,6 +2927,7 @@ namespace ts.server {
// Reload the project's tree that is already present
forEachResolvedProjectReferenceProject(
project,
/*fileName*/ undefined,
reloadChildProject,
ProjectReferenceProjectLoadKind.Find
);
@@ -2990,11 +3033,12 @@ namespace ts.server {
// Find the project that is referenced from this solution that contains the script info directly
configuredProject = forEachResolvedProjectReferenceProject(
configuredProject,
fileName,
child => {
updateProjectIfDirty(child);
return projectContainsOriginalInfo(child) ? child : undefined;
},
configuredProject.getCompilerOptions().disableReferencedProjectLoad ? ProjectReferenceProjectLoadKind.Find : ProjectReferenceProjectLoadKind.FindCreateLoad,
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;
@@ -3070,6 +3114,7 @@ namespace ts.server {
if (!projectContainsInfoDirectly(project, info)) {
forEachResolvedProjectReferenceProject(
project,
info.path,
child => {
updateProjectIfDirty(child);
// Retain these projects
@@ -3184,32 +3229,37 @@ namespace ts.server {
// Work on array copy as we could add more projects as part of callback
for (const project of arrayFrom(this.configuredProjects.values())) {
// If this project has potential project reference for any of the project we are loading ancestor tree for
// we need to load this project tree
if (forEachPotentialProjectReference(
project,
potentialRefPath => forProjects!.has(potentialRefPath)
) || forEachResolvedProjectReference(
project,
(_ref, resolvedPath) => forProjects!.has(resolvedPath)
)) {
// Load children
this.ensureProjectChildren(project, seenProjects);
// load this project first
if (forEachPotentialProjectReference(project, potentialRefPath => forProjects!.has(potentialRefPath))) {
updateProjectIfDirty(project);
}
this.ensureProjectChildren(project, forProjects, seenProjects);
}
}
private ensureProjectChildren(project: ConfiguredProject, seenProjects: Set<NormalizedPath>) {
private ensureProjectChildren(project: ConfiguredProject, forProjects: ReadonlyCollection<string>, seenProjects: Set<NormalizedPath>) {
if (!tryAddToSet(seenProjects, project.canonicalConfigFilePath)) return;
// Update the project
updateProjectIfDirty(project);
// Create tree because project is uptodate we only care of resolved references
forEachResolvedProjectReferenceProject(
project,
child => this.ensureProjectChildren(child, seenProjects),
ProjectReferenceProjectLoadKind.FindCreateLoad,
`Creating project for reference of project: ${project.projectName}`
);
// If this project disables child load ignore it
if (project.getCompilerOptions().disableReferencedProjectLoad) return;
const children = project.getCurrentProgram()?.getResolvedProjectReferences();
if (!children) return;
for (const child of children) {
if (!child) continue;
const referencedProject = forEachResolvedProjectReference(child.references, ref => forProjects.has(ref.sourceFile.path) ? ref : undefined);
if (!referencedProject) continue;
// Load this project,
const configFileName = toNormalizedPath(child.sourceFile.fileName);
const childProject = project.projectService.findConfiguredProjectByProjectName(configFileName) ||
project.projectService.createAndLoadConfiguredProject(configFileName, `Creating project referenced by : ${project.projectName} as it references project ${referencedProject.sourceFile.fileName}`);
updateProjectIfDirty(childProject);
// Ensure children for this project
this.ensureProjectChildren(childProject, forProjects, seenProjects);
}
}
private cleanupAfterOpeningFile(toRetainConfigProjects: readonly ConfiguredProject[] | ConfiguredProject | undefined) {

View File

@@ -750,11 +750,8 @@ namespace ts.server {
for (const f of this.program.getSourceFiles()) {
this.detachScriptInfoIfNotRoot(f.fileName);
}
this.program.forEachResolvedProjectReference(ref => {
if (ref) {
this.detachScriptInfoFromProject(ref.sourceFile.fileName);
}
});
this.program.forEachResolvedProjectReference(ref =>
this.detachScriptInfoFromProject(ref.sourceFile.fileName));
}
// Release external files
@@ -1099,8 +1096,8 @@ namespace ts.server {
}
}
oldProgram.forEachResolvedProjectReference((resolvedProjectReference, resolvedProjectReferencePath) => {
if (resolvedProjectReference && !this.program!.getResolvedProjectReferenceByPath(resolvedProjectReferencePath)) {
oldProgram.forEachResolvedProjectReference(resolvedProjectReference => {
if (!this.program!.getResolvedProjectReferenceByPath(resolvedProjectReference.sourceFile.path)) {
this.detachScriptInfoFromProject(resolvedProjectReference.sourceFile.fileName);
}
});
@@ -2201,6 +2198,13 @@ namespace ts.server {
return program && program.getResolvedProjectReferenceToRedirect(fileName);
}
/*@internal*/
forEachResolvedProjectReference<T>(
cb: (resolvedProjectReference: ResolvedProjectReference) => T | undefined
): T | undefined {
return this.getCurrentProgram()?.forEachResolvedProjectReference(cb);
}
/*@internal*/
enablePluginsWithOptions(options: CompilerOptions, pluginConfigOverrides: ESMap<string, any> | undefined) {
const host = this.projectService.host;
@@ -2301,6 +2305,7 @@ namespace ts.server {
getDefaultChildProjectFromProjectWithReferences(info: ScriptInfo) {
return forEachResolvedProjectReferenceProject(
this,
info.path,
child => projectContainsInfoDirectly(child, info) ?
child :
undefined,
@@ -2338,6 +2343,7 @@ namespace ts.server {
return this.containsScriptInfo(info) ||
!!forEachResolvedProjectReferenceProject(
this,
info.path,
child => child.containsScriptInfo(info),
ProjectReferenceProjectLoadKind.Find
);