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

@@ -530,6 +530,51 @@ namespace ts {
return resolutions;
}
/* @internal */
export function forEachResolvedProjectReference<T>(
resolvedProjectReferences: readonly (ResolvedProjectReference | undefined)[] | undefined,
cb: (resolvedProjectReference: ResolvedProjectReference, parent: ResolvedProjectReference | undefined) => T | undefined
): T | undefined {
return forEachProjectReference(/*projectReferences*/ undefined, resolvedProjectReferences, (resolvedRef, parent) => resolvedRef && cb(resolvedRef, parent));
}
function forEachProjectReference<T>(
projectReferences: readonly ProjectReference[] | undefined,
resolvedProjectReferences: readonly (ResolvedProjectReference | undefined)[] | undefined,
cbResolvedRef: (resolvedRef: ResolvedProjectReference | undefined, parent: ResolvedProjectReference | undefined, index: number) => T | undefined,
cbRef?: (projectReferences: readonly ProjectReference[] | undefined, parent: ResolvedProjectReference | undefined) => T | undefined
): T | undefined {
let seenResolvedRefs: Set<Path> | undefined;
return worker(projectReferences, resolvedProjectReferences, /*parent*/ undefined);
function worker(
projectReferences: readonly ProjectReference[] | undefined,
resolvedProjectReferences: readonly (ResolvedProjectReference | undefined)[] | undefined,
parent: ResolvedProjectReference | undefined,
): T | undefined {
// Visit project references first
if (cbRef) {
const result = cbRef(projectReferences, parent);
if (result) { return result; }
}
return forEach(resolvedProjectReferences, (resolvedRef, index) => {
if (resolvedRef && seenResolvedRefs?.has(resolvedRef.sourceFile.path)) {
// ignore recursives
return undefined;
}
const result = cbResolvedRef(resolvedRef, parent, index);
if (result || !resolvedRef) return result;
(seenResolvedRefs ||= new Set()).add(resolvedRef.sourceFile.path);
return worker(resolvedRef.commandLine.projectReferences, resolvedRef.references, resolvedRef);
});
}
}
/* @internal */
export const inferredTypesContainingFile = "__inferred type names__.ts";
@@ -914,8 +959,8 @@ namespace ts {
host.onReleaseOldSourceFile(oldSourceFile, oldProgram.getCompilerOptions(), !!getSourceFileByPath(oldSourceFile.path));
}
}
oldProgram.forEachResolvedProjectReference((resolvedProjectReference, resolvedProjectReferencePath) => {
if (resolvedProjectReference && !getResolvedProjectReferenceByPath(resolvedProjectReferencePath)) {
oldProgram.forEachResolvedProjectReference(resolvedProjectReference => {
if (!getResolvedProjectReferenceByPath(resolvedProjectReference.sourceFile.path)) {
host.onReleaseOldSourceFile!(resolvedProjectReference.sourceFile, oldProgram!.getCompilerOptions(), /*hasSourceFileByPath*/ false);
}
});
@@ -1038,7 +1083,6 @@ namespace ts {
if (!source) return undefined;
// Output of .d.ts file so return resolved ref that matches the out file name
return forEachResolvedProjectReference(resolvedRef => {
if (!resolvedRef) return undefined;
const out = outFile(resolvedRef.commandLine.options);
if (!out) return undefined;
return toPath(out) === filePath ? resolvedRef : undefined;
@@ -1251,7 +1295,7 @@ namespace ts {
return !forEachProjectReference(
oldProgram!.getProjectReferences(),
oldProgram!.getResolvedProjectReferences(),
(oldResolvedRef, index, parent) => {
(oldResolvedRef, parent, index) => {
const newRef = (parent ? parent.commandLine.projectReferences : projectReferences)![index];
const newResolvedRef = parseProjectReferenceConfigFile(newRef);
if (oldResolvedRef) {
@@ -2115,9 +2159,7 @@ namespace ts {
if (!options.configFile) { return emptyArray; }
let diagnostics = programDiagnostics.getDiagnostics(options.configFile.fileName);
forEachResolvedProjectReference(resolvedRef => {
if (resolvedRef) {
diagnostics = concatenate(diagnostics, programDiagnostics.getDiagnostics(resolvedRef.sourceFile.fileName));
}
diagnostics = concatenate(diagnostics, programDiagnostics.getDiagnostics(resolvedRef.sourceFile.fileName));
});
return diagnostics;
}
@@ -2597,12 +2639,11 @@ namespace ts {
function getResolvedProjectReferenceToRedirect(fileName: string) {
if (mapFromFileToProjectReferenceRedirects === undefined) {
mapFromFileToProjectReferenceRedirects = new Map();
forEachResolvedProjectReference((referencedProject, referenceProjectPath) => {
forEachResolvedProjectReference(referencedProject => {
// not input file from the referenced project, ignore
if (referencedProject &&
toPath(options.configFilePath!) !== referenceProjectPath) {
if (toPath(options.configFilePath!) !== referencedProject.sourceFile.path) {
referencedProject.commandLine.fileNames.forEach(f =>
mapFromFileToProjectReferenceRedirects!.set(toPath(f), referenceProjectPath));
mapFromFileToProjectReferenceRedirects!.set(toPath(f), referencedProject.sourceFile.path));
}
});
}
@@ -2612,13 +2653,9 @@ namespace ts {
}
function forEachResolvedProjectReference<T>(
cb: (resolvedProjectReference: ResolvedProjectReference | undefined, resolvedProjectReferencePath: Path) => T | undefined
cb: (resolvedProjectReference: ResolvedProjectReference) => T | undefined
): T | undefined {
return forEachProjectReference(projectReferences, resolvedProjectReferences, (resolvedRef, index, parent) => {
const ref = (parent ? parent.commandLine.projectReferences : projectReferences)![index];
const resolvedRefPath = toPath(resolveProjectReferencePath(ref));
return cb(resolvedRef, resolvedRefPath);
});
return ts.forEachResolvedProjectReference(resolvedProjectReferences, cb);
}
function getSourceOfProjectReferenceRedirect(file: string) {
@@ -2626,21 +2663,19 @@ namespace ts {
if (mapFromToProjectReferenceRedirectSource === undefined) {
mapFromToProjectReferenceRedirectSource = new Map();
forEachResolvedProjectReference(resolvedRef => {
if (resolvedRef) {
const out = outFile(resolvedRef.commandLine.options);
if (out) {
// Dont know which source file it means so return true?
const outputDts = changeExtension(out, Extension.Dts);
mapFromToProjectReferenceRedirectSource!.set(toPath(outputDts), true);
}
else {
forEach(resolvedRef.commandLine.fileNames, fileName => {
if (!fileExtensionIs(fileName, Extension.Dts) && !fileExtensionIs(fileName, Extension.Json)) {
const outputDts = getOutputDeclarationFileName(fileName, resolvedRef.commandLine, host.useCaseSensitiveFileNames());
mapFromToProjectReferenceRedirectSource!.set(toPath(outputDts), fileName);
}
});
}
const out = outFile(resolvedRef.commandLine.options);
if (out) {
// Dont know which source file it means so return true?
const outputDts = changeExtension(out, Extension.Dts);
mapFromToProjectReferenceRedirectSource!.set(toPath(outputDts), true);
}
else {
forEach(resolvedRef.commandLine.fileNames, fileName => {
if (!fileExtensionIs(fileName, Extension.Dts) && !fileExtensionIs(fileName, Extension.Json)) {
const outputDts = getOutputDeclarationFileName(fileName, resolvedRef.commandLine, host.useCaseSensitiveFileNames());
mapFromToProjectReferenceRedirectSource!.set(toPath(outputDts), fileName);
}
});
}
});
}
@@ -2651,49 +2686,6 @@ namespace ts {
return useSourceOfProjectReferenceRedirect && !!getResolvedProjectReferenceToRedirect(fileName);
}
function forEachProjectReference<T>(
projectReferences: readonly ProjectReference[] | undefined,
resolvedProjectReferences: readonly (ResolvedProjectReference | undefined)[] | undefined,
cbResolvedRef: (resolvedRef: ResolvedProjectReference | undefined, index: number, parent: ResolvedProjectReference | undefined) => T | undefined,
cbRef?: (projectReferences: readonly ProjectReference[] | undefined, parent: ResolvedProjectReference | undefined) => T | undefined
): T | undefined {
let seenResolvedRefs: ResolvedProjectReference[] | undefined;
return worker(projectReferences, resolvedProjectReferences, /*parent*/ undefined, cbResolvedRef, cbRef);
function worker(
projectReferences: readonly ProjectReference[] | undefined,
resolvedProjectReferences: readonly (ResolvedProjectReference | undefined)[] | undefined,
parent: ResolvedProjectReference | undefined,
cbResolvedRef: (resolvedRef: ResolvedProjectReference | undefined, index: number, parent: ResolvedProjectReference | undefined) => T | undefined,
cbRef?: (projectReferences: readonly ProjectReference[] | undefined, parent: ResolvedProjectReference | undefined) => T | undefined,
): T | undefined {
// Visit project references first
if (cbRef) {
const result = cbRef(projectReferences, parent);
if (result) { return result; }
}
return forEach(resolvedProjectReferences, (resolvedRef, index) => {
if (contains(seenResolvedRefs, resolvedRef)) {
// ignore recursives
return undefined;
}
const result = cbResolvedRef(resolvedRef, index, parent);
if (result) {
return result;
}
if (!resolvedRef) return undefined;
(seenResolvedRefs || (seenResolvedRefs = [])).push(resolvedRef);
return worker(resolvedRef.commandLine.projectReferences, resolvedRef.references, resolvedRef, cbResolvedRef, cbRef);
});
}
}
function getResolvedProjectReferenceByPath(projectReferencePath: Path): ResolvedProjectReference | undefined {
if (!projectReferenceRedirects) {
return undefined;
@@ -3348,7 +3340,7 @@ namespace ts {
function verifyProjectReferences() {
const buildInfoPath = !options.suppressOutputPathCheck ? getTsBuildInfoEmitOutputFilePath(options) : undefined;
forEachProjectReference(projectReferences, resolvedProjectReferences, (resolvedRef, index, parent) => {
forEachProjectReference(projectReferences, resolvedProjectReferences, (resolvedRef, parent, index) => {
const ref = (parent ? parent.commandLine.projectReferences : projectReferences)![index];
const parentFile = parent && parent.sourceFile as JsonSourceFile;
if (!resolvedRef) {
@@ -3546,7 +3538,7 @@ namespace ts {
toPath(fileName: string): Path;
getResolvedProjectReferences(): readonly (ResolvedProjectReference | undefined)[] | undefined;
getSourceOfProjectReferenceRedirect(fileName: string): SourceOfProjectReferenceRedirect | undefined;
forEachResolvedProjectReference<T>(cb: (resolvedProjectReference: ResolvedProjectReference | undefined, resolvedProjectReferencePath: Path) => T | undefined): T | undefined;
forEachResolvedProjectReference<T>(cb: (resolvedProjectReference: ResolvedProjectReference) => T | undefined): T | undefined;
}
function updateHostForUseSourceOfProjectReferenceRedirect(host: HostForUseSourceOfProjectReferenceRedirect) {
@@ -3576,7 +3568,6 @@ namespace ts {
if (!setOfDeclarationDirectories) {
setOfDeclarationDirectories = new Set();
host.forEachResolvedProjectReference(ref => {
if (!ref) return;
const out = outFile(ref.commandLine.options);
if (out) {
setOfDeclarationDirectories!.add(getDirectoryPath(host.toPath(out)));

View File

@@ -3800,7 +3800,7 @@ namespace ts {
getResolvedProjectReferences(): readonly (ResolvedProjectReference | undefined)[] | undefined;
/*@internal*/ getProjectReferenceRedirect(fileName: string): string | undefined;
/*@internal*/ getResolvedProjectReferenceToRedirect(fileName: string): ResolvedProjectReference | undefined;
/*@internal*/ forEachResolvedProjectReference<T>(cb: (resolvedProjectReference: ResolvedProjectReference | undefined, resolvedProjectReferencePath: Path) => T | undefined): T | undefined;
/*@internal*/ forEachResolvedProjectReference<T>(cb: (resolvedProjectReference: ResolvedProjectReference) => T | undefined): T | undefined;
/*@internal*/ getResolvedProjectReferenceByPath(projectReferencePath: Path): ResolvedProjectReference | undefined;
/*@internal*/ isSourceOfProjectReferenceRedirect(fileName: string): boolean;
/*@internal*/ getProgramBuildInfo?(): ProgramBuildInfo | undefined;
@@ -6333,7 +6333,7 @@ namespace ts {
/*@internal*/
export interface ResolvedProjectReferenceCallbacks {
getSourceOfProjectReferenceRedirect(fileName: string): SourceOfProjectReferenceRedirect | undefined;
forEachResolvedProjectReference<T>(cb: (resolvedProjectReference: ResolvedProjectReference | undefined, resolvedProjectReferencePath: Path) => T | undefined): T | undefined;
forEachResolvedProjectReference<T>(cb: (resolvedProjectReference: ResolvedProjectReference) => T | undefined): T | undefined;
}
/* @internal */

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
);

View File

@@ -2189,19 +2189,14 @@ export function bar() {}`
verifySolutionScenario({
configRefs: ["./tsconfig-indirect1.json", "./tsconfig-indirect2.json"],
additionalFiles: [tsconfigIndirect, indirect, tsconfigIndirect2, indirect2],
additionalProjects: [{
projectName: tsconfigIndirect.path,
files: [tsconfigIndirect.path, main.path, helper.path, indirect.path, libFile.path]
}],
additionalProjects: emptyArray,
expectedOpenEvents: [
...expectedSolutionLoadAndTelemetry(),
...expectedProjectReferenceLoadAndTelemetry(tsconfigIndirect.path),
...expectedProjectReferenceLoadAndTelemetry(tsconfigSrcPath),
configFileDiagEvent(main.path, tsconfigSrcPath, [])
],
expectedReloadEvents: [
...expectedReloadEvent(tsconfigPath),
...expectedReloadEvent(tsconfigIndirect.path),
...expectedReloadEvent(tsconfigSrcPath),
],
expectedReferences: {
@@ -2217,8 +2212,8 @@ export function bar() {}`
refs: [
...expectedIndirectRefs(fileResolvingToMainDts),
...refs,
...expectedIndirectRefs(indirect2),
...expectedIndirectRefs(indirect),
...expectedIndirectRefs(indirect2),
],
symbolDisplayString: "(alias) const foo: 1\nimport foo",
}
@@ -2296,8 +2291,6 @@ export function bar() {}`
const expectedProjectsOnOpen: VerifyProjects = {
configuredProjects: [
{ projectName: tsconfigPath, files: [tsconfigPath] },
{ projectName: tsconfigIndirect.path, files: [tsconfigIndirect.path, main.path, helper.path, indirect.path, libFile.path] },
{ projectName: tsconfigIndirect2.path, files: [tsconfigIndirect2.path, main.path, helper.path, indirect2.path, libFile.path] },
{ projectName: tsconfigSrcPath, files: [tsconfigSrcPath, main.path, helper.path, libFile.path] },
],
inferredProjects: emptyArray
@@ -2307,8 +2300,6 @@ export function bar() {}`
additionalFiles: [tsconfigIndirect, indirect, tsconfigIndirect2, indirect2],
expectedOpenEvents: [
...expectedSolutionLoadAndTelemetry(),
...expectedProjectReferenceLoadAndTelemetry(tsconfigIndirect.path),
...expectedProjectReferenceLoadAndTelemetry(tsconfigIndirect2.path),
...expectedProjectReferenceLoadAndTelemetry(tsconfigSrcPath),
configFileDiagEvent(main.path, tsconfigSrcPath, [])
],
@@ -2317,9 +2308,7 @@ export function bar() {}`
expectedProjectsOnOpen,
expectedReloadEvents: [
...expectedReloadEvent(tsconfigPath),
...expectedReloadEvent(tsconfigIndirect.path),
...expectedReloadEvent(tsconfigSrcPath),
...expectedReloadEvent(tsconfigIndirect2.path),
]
});
});
@@ -2387,19 +2376,14 @@ bar;`
solutionProject: [tsconfigPath, indirect.path, ownMain.path, main.path, libFile.path, helper.path],
configRefs: ["./tsconfig-indirect1.json", "./tsconfig-indirect2.json"],
additionalFiles: [tsconfigIndirect, indirect, tsconfigIndirect2, indirect2, ownMain],
additionalProjects: [{
projectName: tsconfigIndirect.path,
files: [tsconfigIndirect.path, main.path, helper.path, indirect.path, libFile.path]
}],
additionalProjects: emptyArray,
expectedOpenEvents: [
...expectedSolutionLoadAndTelemetry(),
...expectedProjectReferenceLoadAndTelemetry(tsconfigIndirect.path),
...expectedProjectReferenceLoadAndTelemetry(tsconfigSrcPath),
configFileDiagEvent(main.path, tsconfigSrcPath, [])
],
expectedReloadEvents: [
...expectedReloadEvent(tsconfigPath),
...expectedReloadEvent(tsconfigIndirect.path),
...expectedReloadEvent(tsconfigSrcPath),
],
expectedReferences: {
@@ -2415,8 +2399,8 @@ bar;`
refs: [
...expectedIndirectRefs(fileResolvingToMainDts),
...refs,
...expectedIndirectRefs(indirect2),
...expectedIndirectRefs(indirect),
...expectedIndirectRefs(indirect2),
],
symbolDisplayString: "(alias) const foo: 1\nimport foo",
}
@@ -2502,8 +2486,6 @@ bar;`
const expectedProjectsOnOpen: VerifyProjects = {
configuredProjects: [
{ projectName: tsconfigPath, files: [tsconfigPath, indirect.path, ownMain.path, main.path, libFile.path, helper.path] },
{ projectName: tsconfigIndirect.path, files: [tsconfigIndirect.path, main.path, helper.path, indirect.path, libFile.path] },
{ projectName: tsconfigIndirect2.path, files: [tsconfigIndirect2.path, main.path, helper.path, indirect2.path, libFile.path] },
{ projectName: tsconfigSrcPath, files: [tsconfigSrcPath, main.path, helper.path, libFile.path] },
],
inferredProjects: emptyArray
@@ -2518,8 +2500,6 @@ bar;`
additionalFiles: [tsconfigIndirect, indirect, tsconfigIndirect2, indirect2, ownMain],
expectedOpenEvents: [
...expectedSolutionLoadAndTelemetry(),
...expectedProjectReferenceLoadAndTelemetry(tsconfigIndirect.path),
...expectedProjectReferenceLoadAndTelemetry(tsconfigIndirect2.path),
...expectedProjectReferenceLoadAndTelemetry(tsconfigSrcPath),
configFileDiagEvent(main.path, tsconfigSrcPath, [])
],
@@ -2528,9 +2508,7 @@ bar;`
expectedProjectsOnOpen,
expectedReloadEvents: [
...expectedReloadEvent(tsconfigPath),
...expectedReloadEvent(tsconfigIndirect.path),
...expectedReloadEvent(tsconfigSrcPath),
...expectedReloadEvent(tsconfigIndirect2.path),
]
});
});
@@ -2645,5 +2623,75 @@ bar;`
verifyAutoImport(/*built*/ true, /*disableSourceOfProjectReferenceRedirect*/ true);
});
});
it("when files from two projects are open and one project references", () => {
function getPackageAndFile(packageName: string, references?: string[], optionsToExtend?: CompilerOptions): [file: File, config: File] {
const file: File = {
path: `${tscWatch.projectRoot}/${packageName}/src/file1.ts`,
content: `export const ${packageName}Const = 10;`
};
const config: File = {
path: `${tscWatch.projectRoot}/${packageName}/tsconfig.json`,
content: JSON.stringify({
compilerOptions: { composite: true, ...optionsToExtend || {} },
references: references?.map(path => ({ path: `../${path}` }))
})
};
return [file, config];
}
const [mainFile, mainConfig] = getPackageAndFile("main", ["core", "indirect", "noCoreRef1", "indirectDisabledChildLoad1", "indirectDisabledChildLoad2", "refToCoreRef3", "indirectNoCoreRef"]);
const [coreFile, coreConfig] = getPackageAndFile("core");
const [noCoreRef1File, noCoreRef1Config] = getPackageAndFile("noCoreRef1");
const [indirectFile, indirectConfig] = getPackageAndFile("indirect", ["coreRef1"]);
const [coreRef1File, coreRef1Config] = getPackageAndFile("coreRef1", ["core"]);
const [indirectDisabledChildLoad1File, indirectDisabledChildLoad1Config] = getPackageAndFile("indirectDisabledChildLoad1", ["coreRef2"], { disableReferencedProjectLoad: true });
const [coreRef2File, coreRef2Config] = getPackageAndFile("coreRef2", ["core"]);
const [indirectDisabledChildLoad2File, indirectDisabledChildLoad2Config] = getPackageAndFile("indirectDisabledChildLoad2", ["coreRef3"], { disableReferencedProjectLoad: true });
const [coreRef3File, coreRef3Config] = getPackageAndFile("coreRef3", ["core"]);
const [refToCoreRef3File, refToCoreRef3Config] = getPackageAndFile("refToCoreRef3", ["coreRef3"]);
const [indirectNoCoreRefFile, indirectNoCoreRefConfig] = getPackageAndFile("indirectNoCoreRef", ["noCoreRef2"]);
const [noCoreRef2File, noCoreRef2Config] = getPackageAndFile("noCoreRef2");
const host = createServerHost([
libFile, mainFile, mainConfig, coreFile, coreConfig, noCoreRef1File, noCoreRef1Config,
indirectFile, indirectConfig, coreRef1File, coreRef1Config,
indirectDisabledChildLoad1File, indirectDisabledChildLoad1Config, coreRef2File, coreRef2Config,
indirectDisabledChildLoad2File, indirectDisabledChildLoad2Config, coreRef3File, coreRef3Config,
refToCoreRef3File, refToCoreRef3Config,
indirectNoCoreRefFile, indirectNoCoreRefConfig, noCoreRef2File, noCoreRef2Config
], { useCaseSensitiveFileNames: true });
const session = createSession(host);
const service = session.getProjectService();
openFilesForSession([mainFile, coreFile], session);
verifyProject(mainConfig);
verifyProject(coreConfig);
// Find all refs in coreFile
session.executeCommandSeq<protocol.ReferencesRequest>({
command: protocol.CommandTypes.References,
arguments: protocolFileLocationFromSubstring(coreFile, `coreConst`)
});
verifyProject(mainConfig);
verifyProject(coreConfig);
verifyNoProject(noCoreRef1Config); // Should not be loaded
verifyProject(indirectConfig);
verifyProject(coreRef1Config);
verifyProject(indirectDisabledChildLoad1Config);
verifyNoProject(coreRef2Config); // Should not be loaded
verifyProject(indirectDisabledChildLoad2Config);
verifyProject(coreRef3Config);
verifyProject(refToCoreRef3Config);
verifyNoProject(indirectNoCoreRefConfig); // Should not be loaded
verifyNoProject(noCoreRef2Config); // Should not be loaded
function verifyProject(config: File) {
assert.isDefined(service.configuredProjects.get(config.path), `Expected to find ${config.path}`);
}
function verifyNoProject(config: File) {
assert.isUndefined(service.configuredProjects.get(config.path), `Expected to not find ${config.path}`);
}
});
});
}