Skip to content

Commit

Permalink
Load the ancestor projects to find default project for the file
Browse files Browse the repository at this point in the history
  • Loading branch information
sheetalkamat committed May 8, 2024
1 parent 8bb01ab commit 5f46017
Show file tree
Hide file tree
Showing 170 changed files with 8,747 additions and 4,713 deletions.
6 changes: 5 additions & 1 deletion src/harness/projectServiceStateLogger.ts
Original file line number Diff line number Diff line change
Expand Up @@ -104,7 +104,11 @@ export function patchServiceForStateBaseline(service: ProjectService) {
function baselineProjects(currentMappers: Set<DocumentPositionMapper>) {
const autoImportProviderProjects = [] as AutoImportProviderProject[];
const auxiliaryProjects = [] as AuxiliaryProject[];
const orphanConfiguredProjects = service.getOrphanConfiguredProjects(/*toRetainConfiguredProjects*/ undefined);
const orphanConfiguredProjects = service.getOrphanConfiguredProjects(
/*toRetainConfiguredProjects*/ undefined,
/*openFilesWithRetainedConfiguredProject*/ undefined,
/*externalProjectsRetainingConfiguredProjects*/ undefined,
);
const noOpenRef = (project: Project) => isConfiguredProject(project) && (project.isClosed() || orphanConfiguredProjects.has(project));
return baselineState(
[service.externalProjects, service.configuredProjects, service.inferredProjects, autoImportProviderProjects, auxiliaryProjects],
Expand Down
1,086 changes: 684 additions & 402 deletions src/server/editorServices.ts

Large diffs are not rendered by default.

85 changes: 2 additions & 83 deletions src/server/project.ts
Original file line number Diff line number Diff line change
Expand Up @@ -141,15 +141,12 @@ import {
emptyArray,
Errors,
FileStats,
forEachResolvedProjectReferenceProject,
LogLevel,
ModuleImportResult,
Msg,
NormalizedPath,
PackageJsonWatcher,
projectContainsInfoDirectly,
ProjectOptions,
ProjectReferenceProjectLoadKind,
ProjectService,
ScriptInfo,
ServerHost,
Expand Down Expand Up @@ -2211,7 +2208,7 @@ export abstract class Project implements LanguageServiceHost, ModuleResolutionHo
private isDefaultProjectForOpenFiles(): boolean {
return !!forEachEntry(
this.projectService.openFiles,
(_, fileName) => this.projectService.tryGetDefaultProjectForFile(toNormalizedPath(fileName)) === this,
(_projectRootPath, path) => this.projectService.tryGetDefaultProjectForFile(this.projectService.getScriptInfoForPath(path)!) === this,
);
}

Expand Down Expand Up @@ -2751,9 +2748,6 @@ export class ConfiguredProject extends Project {
/** @internal */
canConfigFileJsonReportNoInputFiles = false;

/** Ref count to the project when opened from external project */
private externalProjectRefCount = 0;

private projectReferences: readonly ProjectReference[] | undefined;

/**
Expand Down Expand Up @@ -2861,8 +2855,7 @@ export class ConfiguredProject extends Project {
case ProgramUpdateLevel.Full:
this.openFileWatchTriggered.clear();
const reason = Debug.checkDefined(this.pendingUpdateReason);
this.pendingUpdateReason = undefined;
this.projectService.reloadConfiguredProject(this, reason, /*clearSemanticCache*/ false);
this.projectService.reloadConfiguredProject(this, reason);
result = true;
break;
default:
Expand Down Expand Up @@ -2986,91 +2979,17 @@ export class ConfiguredProject extends Project {
super.markAsDirty();
}

/** @internal */
addExternalProjectReference() {
this.externalProjectRefCount++;
}

/** @internal */
deleteExternalProjectReference() {
this.externalProjectRefCount--;
}

/** @internal */
isSolution() {
return this.getRootFilesMap().size === 0 &&
!this.canConfigFileJsonReportNoInputFiles;
}

/**
* Find the configured project from the project references in project which contains the info directly
*
* @internal
*/
getDefaultChildProjectFromProjectWithReferences(info: ScriptInfo) {
return forEachResolvedProjectReferenceProject(
this,
info.path,
child =>
projectContainsInfoDirectly(child, info) ?
child :
undefined,
ProjectReferenceProjectLoadKind.Find,
);
}

/**
* Returns true if the project is needed by any of the open script info/external project
*
* @internal
*/
hasOpenRef() {
if (!!this.externalProjectRefCount) {
return true;
}

// Closed project doesnt have any reference
if (this.isClosed()) {
return false;
}

const configFileExistenceInfo = this.projectService.configFileExistenceInfoCache.get(this.canonicalConfigFilePath)!;
if (this.deferredClose) return !!configFileExistenceInfo.openFilesImpactedByConfigFile?.size;
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 !!configFileExistenceInfo.openFilesImpactedByConfigFile && forEachEntry(
configFileExistenceInfo.openFilesImpactedByConfigFile,
(_value, infoPath) => {
const info = this.projectService.getScriptInfoForPath(infoPath)!;
return this.containsScriptInfo(info) ||
!!forEachResolvedProjectReferenceProject(
this,
info.path,
child => child.containsScriptInfo(info),
ProjectReferenceProjectLoadKind.Find,
);
},
) || false;
}

/** @internal */
override isOrphan(): boolean {
return !!this.deferredClose;
}

/** @internal */
hasExternalProjectRef() {
return !!this.externalProjectRefCount;
}

getEffectiveTypeRoots() {
return getEffectiveTypeRoots(this.getCompilationSettings(), this) || [];
}
Expand Down
38 changes: 10 additions & 28 deletions src/server/scriptInfo.ts
Original file line number Diff line number Diff line change
Expand Up @@ -39,7 +39,6 @@ import {
AbsolutePositionAndLineText,
ConfiguredProject,
Errors,
ExternalProject,
InferredProject,
isBackgroundProject,
isConfiguredProject,
Expand Down Expand Up @@ -580,18 +579,16 @@ export class ScriptInfo {
case 0:
return Errors.ThrowNoProject();
case 1:
return ensurePrimaryProjectKind(
!isProjectDeferredClose(this.containingProjects[0]) ?
this.containingProjects[0] : undefined,
);
return isProjectDeferredClose(this.containingProjects[0]) || isBackgroundProject(this.containingProjects[0]) ?
Errors.ThrowNoProject() :
this.containingProjects[0];
default:
// If this file belongs to multiple projects, below is the order in which default project is used
// - first external project
// - for open script info, its default configured project during opening is default if info is part of it
// - first configured project of which script info is not a source of project reference redirect
// - first configured project
// - first external project
// - first inferred project
let firstExternalProject: ExternalProject | undefined;
let firstConfiguredProject: ConfiguredProject | undefined;
let firstInferredProject: InferredProject | undefined;
let firstNonSourceOfProjectReferenceRedirect: ConfiguredProject | undefined;
Expand All @@ -614,20 +611,17 @@ export class ScriptInfo {
}
if (!firstConfiguredProject) firstConfiguredProject = project;
}
else if (!firstExternalProject && isExternalProject(project)) {
firstExternalProject = project;
else if (isExternalProject(project)) {
return project;
}
else if (!firstInferredProject && isInferredProject(project)) {
firstInferredProject = project;
}
}
return ensurePrimaryProjectKind(
defaultConfiguredProject ||
firstNonSourceOfProjectReferenceRedirect ||
firstConfiguredProject ||
firstExternalProject ||
firstInferredProject,
);
return (defaultConfiguredProject ||
firstNonSourceOfProjectReferenceRedirect ||
firstConfiguredProject ||
firstInferredProject) ?? Errors.ThrowNoProject();
}
}

Expand Down Expand Up @@ -742,18 +736,6 @@ export class ScriptInfo {
}
}

/**
* Throws an error if `project` is an AutoImportProvider or AuxiliaryProject,
* which are used in the background by other Projects and should never be
* reported as the default project for a ScriptInfo.
*/
function ensurePrimaryProjectKind(project: Project | undefined) {
if (!project || isBackgroundProject(project)) {
return Errors.ThrowNoProject();
}
return project;
}

function failIfInvalidPosition(position: number) {
Debug.assert(typeof position === "number", `Expected position ${position} to be a number.`);
Debug.assert(position >= 0, `Expected position to be non-negative.`);
Expand Down
4 changes: 2 additions & 2 deletions src/server/session.ts
Original file line number Diff line number Diff line change
Expand Up @@ -3190,7 +3190,7 @@ export class Session<TMessage = string> implements EventSender {
return this.requiredResponse(response);
},
[protocol.CommandTypes.OpenExternalProject]: (request: protocol.OpenExternalProjectRequest) => {
this.projectService.openExternalProject(request.arguments, /*print*/ true);
this.projectService.openExternalProject(request.arguments, /*cleanupAfter*/ true);
// TODO: GH#20447 report errors
return this.requiredResponse(/*response*/ true);
},
Expand All @@ -3200,7 +3200,7 @@ export class Session<TMessage = string> implements EventSender {
return this.requiredResponse(/*response*/ true);
},
[protocol.CommandTypes.CloseExternalProject]: (request: protocol.CloseExternalProjectRequest) => {
this.projectService.closeExternalProject(request.arguments.projectFileName, /*print*/ true);
this.projectService.closeExternalProject(request.arguments.projectFileName, /*cleanupAfter*/ true);
// TODO: GH#20447 report errors
return this.requiredResponse(/*response*/ true);
},
Expand Down
2 changes: 1 addition & 1 deletion src/testRunner/unittests/tsserver/projects.ts
Original file line number Diff line number Diff line change
Expand Up @@ -177,7 +177,7 @@ describe("unittests:: tsserver:: projects::", () => {
};
session.host.baselineHost("Before request");
session.logger.info(`request:${ts.server.stringifyIndented(request)}`);
session.getProjectService().openExternalProject(request.arguments, /*print*/ true);
session.getProjectService().openExternalProject(request.arguments, /*cleanupAfter*/ true);
session.host.baselineHost("After request");
baselineTsserverLogs("projects", "external project including config file", session);
});
Expand Down
34 changes: 11 additions & 23 deletions tests/baselines/reference/api/typescript.d.ts
Original file line number Diff line number Diff line change
Expand Up @@ -2872,8 +2872,6 @@ declare namespace ts {
*/
class ConfiguredProject extends Project {
readonly canonicalConfigFilePath: NormalizedPath;
/** Ref count to the project when opened from external project */
private externalProjectRefCount;
private projectReferences;
/**
* If the project has reload from disk pending, it reloads (and then updates graph as part of that) instead of just updating the graph
Expand Down Expand Up @@ -3117,6 +3115,8 @@ declare namespace ts {
* Open files: with value being project root path, and key being Path of the file that is open
*/
readonly openFiles: Map<Path, NormalizedPath | undefined>;
private readonly configFileInfoForOpenFiles;
private openFilesRootOfInferredProject;
/**
* Map of open files that are opened without complete path but have projectRoot as current directory
*/
Expand All @@ -3135,6 +3135,7 @@ declare namespace ts {
private safelist;
private readonly legacySafelist;
private pendingProjectUpdates;
private pendingOpenFileProjectUpdates?;
readonly currentDirectory: NormalizedPath;
readonly toCanonicalFileName: (f: string) => string;
readonly host: ServerHost;
Expand Down Expand Up @@ -3185,13 +3186,6 @@ declare namespace ts {
private delayUpdateSourceInfoProjects;
private delayUpdateProjectsOfScriptInfoPath;
private handleDeletedFile;
/**
* This function goes through all the openFiles and tries to file the config file for them.
* If the config file is found and it refers to existing project, it schedules the reload it for reload
* If there is no existing project it just opens the configured project for the config file
* shouldReloadProjectFor provides a way to filter out files to reload configured project for
*/
private delayReloadConfiguredProjectsForFile;
private removeProject;
private assignOrphanScriptInfosToInferredProject;
/**
Expand All @@ -3201,10 +3195,7 @@ declare namespace ts {
private closeOpenFile;
private deleteScriptInfo;
private configFileExists;
/**
* Returns true if the configFileExistenceInfo is needed/impacted by open files that are root of inferred project
*/
private configFileExistenceImpactsRootOfInferredProject;
private addConfigFileNameForOpenFile;
/**
* This is called on file close, so that we stop watching the config file for this script info
*/
Expand All @@ -3218,6 +3209,8 @@ declare namespace ts {
* the newly opened file.
*/
private forEachConfigFileLocation;
private getConfigFileNameForFileFromCache;
private setConfigFileNameForFileInCache;
/**
* This function tries to search for a tsconfig.json for the given file.
* This is different from the method the compiler uses because
Expand Down Expand Up @@ -3269,12 +3262,6 @@ declare namespace ts {
* This does not reload contents of open files from disk. But we could do that if needed
*/
reloadProjects(): void;
/**
* This function goes through all the openFiles and tries to file the config file for them.
* If the config file is found and it refers to existing project, it reloads it either immediately
* If there is no existing project it just opens the configured project for the config file
*/
private reloadConfiguredProjectForFiles;
/**
* Remove the root of inferred project if script info is part of another project
*/
Expand All @@ -3295,12 +3282,14 @@ declare namespace ts {
openClientFile(fileName: string, fileContent?: string, scriptKind?: ScriptKind, projectRootPath?: string): OpenConfiguredProjectResult;
private findExternalProjectContainingOpenScriptInfo;
private getOrCreateOpenScriptInfo;
private tryFindDefaultConfiguredProjectForOpenScriptInfo;
private tryFindDefaultConfiguredProjectAndLoadAncestorsForOpenScriptInfo;
private assignProjectToOpenedScriptInfo;
private createAncestorProjects;
private forEachAncestorProject;
private ensureProjectChildren;
private cleanupAfterOpeningFile;
private cleanupConfiguredProjects;
private cleanupProjectsAndScriptInfos;
openClientFileWithNormalizedPath(fileName: NormalizedPath, fileContent?: string, scriptKind?: ScriptKind, hasMixedContent?: boolean, projectRootPath?: NormalizedPath): OpenConfiguredProjectResult;
private removeOrphanConfiguredProjects;
private removeOrphanScriptInfos;
private telemetryOnOpenFile;
/**
Expand All @@ -3309,7 +3298,6 @@ declare namespace ts {
*/
closeClientFile(uncheckedFileName: string): void;
private collectChanges;
private closeConfiguredProjectReferencedFromExternalProject;
closeExternalProject(uncheckedFileName: string): void;
openExternalProjects(projects: protocol.ExternalProject[]): void;
/** Makes a filename safe to insert in a RegExp */
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -110,12 +110,11 @@ Info seq [hh:mm:ss:mss] Scheduled: *ensureProjectForOpenFiles*
Info seq [hh:mm:ss:mss] Elapsed:: *ms FileWatcher:: Triggered with /a/b/projects/project/tsconfig.json 0:: WatchInfo: /a/b/projects/project/tsconfig.json 2000 undefined WatchType: Config file for the inferred project root
Info seq [hh:mm:ss:mss] FileWatcher:: Triggered with /a/b/projects/project/tsconfig.json 0:: WatchInfo: /a/b/projects/project/tsconfig.json 2000 undefined WatchType: Config file for the inferred project root
Info seq [hh:mm:ss:mss] getConfigFileNameForFile:: File: /a/b/projects/project/src/index.ts ProjectRootPath: /a/b/projects/proj:: Result: /a/b/projects/project/tsconfig.json
Info seq [hh:mm:ss:mss] Scheduled: /a/b/projects/project/tsconfig.json, Cancelled earlier one
Info seq [hh:mm:ss:mss] Scheduled: *ensureProjectForOpenFiles*, Cancelled earlier one
Info seq [hh:mm:ss:mss] Elapsed:: *ms FileWatcher:: Triggered with /a/b/projects/project/tsconfig.json 0:: WatchInfo: /a/b/projects/project/tsconfig.json 2000 undefined WatchType: Config file for the inferred project root
Before running Timeout callback:: count: 2
3: /a/b/projects/project/tsconfig.json
4: *ensureProjectForOpenFiles*
1: /a/b/projects/project/tsconfig.json
3: *ensureProjectForOpenFiles*
//// [/a/b/projects/project/tsconfig.json]
{}
Expand Down Expand Up @@ -145,8 +144,8 @@ FsWatches::
{}
Timeout callback:: count: 2
3: /a/b/projects/project/tsconfig.json *new*
4: *ensureProjectForOpenFiles* *new*
1: /a/b/projects/project/tsconfig.json *new*
3: *ensureProjectForOpenFiles* *new*
Projects::
/a/b/projects/project/tsconfig.json (Configured) *new*
Expand Down Expand Up @@ -363,11 +362,11 @@ Info seq [hh:mm:ss:mss] DirectoryWatcher:: Triggered with /a/b/projects/project
Info seq [hh:mm:ss:mss] Project: /a/b/projects/project/tsconfig.json Detected file add/remove of non supported extension: /a/b/projects/project/tsconfig.json
Info seq [hh:mm:ss:mss] Elapsed:: *ms DirectoryWatcher:: Triggered with /a/b/projects/project/tsconfig.json :: WatchInfo: /a/b/projects/project 1 undefined Config: /a/b/projects/project/tsconfig.json WatchType: Wild card directory
Before running Timeout callback:: count: 1
5: *ensureProjectForOpenFiles*
4: *ensureProjectForOpenFiles*
//// [/a/b/projects/project/tsconfig.json] deleted
Timeout callback:: count: 1
5: *ensureProjectForOpenFiles* *new*
4: *ensureProjectForOpenFiles* *new*
Projects::
/a/b/projects/project/tsconfig.json (Configured) *changed*
Expand Down

0 comments on commit 5f46017

Please sign in to comment.