Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
7 changes: 7 additions & 0 deletions src/compiler/commandLineParser.ts
Original file line number Diff line number Diff line change
Expand Up @@ -882,6 +882,13 @@ namespace ts {
category: Diagnostics.Advanced_Options,
description: Diagnostics.Disable_solution_searching_for_this_project
},
{
name: "disableReferencedProjectLoad",
type: "boolean",
isTSConfigOnly: true,
category: Diagnostics.Advanced_Options,
description: Diagnostics.Disable_loading_referenced_projects
},
{
name: "noImplicitUseStrict",
type: "boolean",
Expand Down
4 changes: 4 additions & 0 deletions src/compiler/diagnosticMessages.json
Original file line number Diff line number Diff line change
Expand Up @@ -4469,6 +4469,10 @@
"category": "Error",
"code": 6234
},
"Disable loading referenced projects.": {
"category": "Message",
"code": 6235
},

"Projects to reference": {
"category": "Message",
Expand Down
1 change: 1 addition & 0 deletions src/compiler/types.ts
Original file line number Diff line number Diff line change
Expand Up @@ -5671,6 +5671,7 @@ namespace ts {
disableSizeLimit?: boolean;
disableSourceOfProjectReferenceRedirect?: boolean;
disableSolutionSearching?: boolean;
disableReferencedProjectLoad?: boolean;
downlevelIteration?: boolean;
emitBOM?: boolean;
emitDecoratorMetadata?: boolean;
Expand Down
124 changes: 82 additions & 42 deletions src/server/editorServices.ts
Original file line number Diff line number Diff line change
Expand Up @@ -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*/
Expand Down Expand Up @@ -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
Expand Down Expand Up @@ -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
);
}
}
}
}
Expand Down Expand Up @@ -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;
Expand Down Expand Up @@ -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 => {
Expand All @@ -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)) {
Expand All @@ -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`
);
}

// 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 {
// Create ancestor configured project
this.createAncestorProjects(info, defaultConfigProject || project);
// Since the file isnt part of configured project, do not send config file info
configFileName = undefined;
}

// Create ancestor configured project
this.createAncestorProjects(info, project);
}
}

Expand Down
13 changes: 10 additions & 3 deletions src/server/project.ts
Original file line number Diff line number Diff line change
Expand Up @@ -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;
}

Expand Down
4 changes: 2 additions & 2 deletions src/testRunner/unittests/tsserver/helpers.ts
Original file line number Diff line number Diff line change
Expand Up @@ -449,11 +449,11 @@ namespace ts.projectSystem {
}

export function checkProjectActualFiles(project: server.Project, expectedFiles: readonly string[]) {
checkArray(`${server.ProjectKind[project.projectKind]} project, actual files`, project.getFileNames(), expectedFiles);
checkArray(`${server.ProjectKind[project.projectKind]} project: ${project.getProjectName()}:: actual files`, project.getFileNames(), expectedFiles);
}

export function checkProjectRootFiles(project: server.Project, expectedFiles: readonly string[]) {
checkArray(`${server.ProjectKind[project.projectKind]} project, rootFileNames`, project.getRootFiles(), expectedFiles);
checkArray(`${server.ProjectKind[project.projectKind]} project: ${project.getProjectName()}::, rootFileNames`, project.getRootFiles(), expectedFiles);
}

export function mapCombinedPathsInAncestor(dir: string, path2: string, mapAncestor: (ancestor: string) => boolean) {
Expand Down
Loading