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 Jan 29, 2024
1 parent 8d2b5f4 commit dbd6717
Show file tree
Hide file tree
Showing 75 changed files with 1,103 additions and 1,325 deletions.
849 changes: 521 additions & 328 deletions src/server/editorServices.ts

Large diffs are not rendered by default.

84 changes: 2 additions & 82 deletions src/server/project.ts
Original file line number Diff line number Diff line change
Expand Up @@ -139,15 +139,12 @@ import {
emptyArray,
Errors,
FileStats,
forEachResolvedProjectReferenceProject,
LogLevel,
ModuleImportResult,
Msg,
NormalizedPath,
PackageJsonWatcher,
projectContainsInfoDirectly,
ProjectOptions,
ProjectReferenceProjectLoadKind,
ProjectService,
ScriptInfo,
ServerHost,
Expand Down Expand Up @@ -2185,7 +2182,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 @@ -2681,9 +2678,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 @@ -2778,8 +2772,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, isInitialLoad, /*clearSemanticCache*/ false);
this.projectService.reloadConfiguredProject(this, reason, isInitialLoad);
result = true;
break;
default:
Expand Down Expand Up @@ -2881,85 +2874,12 @@ export class ConfiguredProject extends Project {
super.close();
}

/** @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.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 */
hasExternalProjectRef() {
return !!this.externalProjectRefCount;
}

getEffectiveTypeRoots() {
return getEffectiveTypeRoots(this.getCompilationSettings(), this) || [];
}
Expand Down
33 changes: 8 additions & 25 deletions src/server/scriptInfo.ts
Original file line number Diff line number Diff line change
Expand Up @@ -38,7 +38,6 @@ import {
AbsolutePositionAndLineText,
ConfiguredProject,
Errors,
ExternalProject,
InferredProject,
isBackgroundProject,
isConfiguredProject,
Expand Down Expand Up @@ -566,15 +565,14 @@ export class ScriptInfo {
case 0:
return Errors.ThrowNoProject();
case 1:
return ensurePrimaryProjectKind(this.containingProjects[0]);
return 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 @@ -596,20 +594,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 @@ -724,18 +719,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
7 changes: 6 additions & 1 deletion src/testRunner/unittests/helpers/tsserver.ts
Original file line number Diff line number Diff line change
Expand Up @@ -495,5 +495,10 @@ export function logInferredProjectsOrphanStatus(session: TestSession) {
}

export function logConfiguredProjectsHasOpenRefStatus(session: TestSession) {
session.getProjectService().configuredProjects.forEach(configuredProject => session.logger.log(`Configured project: ${configuredProject.projectName} hasOpenRef:: ${configuredProject.hasOpenRef()} isClosed: ${configuredProject.isClosed()}`));
const toRemoveConfiguredProjects = session.getProjectService().getOrphanConfiguredProjects(
/*toRetainConfiguredProjects*/ undefined,
/*openFilesWithRetainedConfiguredProject*/ undefined,
/*externalProjectsRetainingConfiguredProjects*/ undefined,
);
session.getProjectService().configuredProjects.forEach(configuredProject => session.logger.log(`Configured project: ${configuredProject.projectName} hasOpenRef:: ${!toRemoveConfiguredProjects.has(configuredProject)} isClosed: ${configuredProject.isClosed()}`));
}
2 changes: 1 addition & 1 deletion src/testRunner/unittests/tsserver/projects.ts
Original file line number Diff line number Diff line change
Expand Up @@ -185,7 +185,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
24 changes: 8 additions & 16 deletions tests/baselines/reference/api/typescript.d.ts
Original file line number Diff line number Diff line change
Expand Up @@ -3476,8 +3476,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 @@ -3704,7 +3702,7 @@ declare namespace ts {
/**
* maps external project file name to list of config files that were the part of this project
*/
private readonly externalProjectToConfiguredProjectMap;
private readonly externalProjectToConfigFilesMap;
/**
* external projects (configuration and list of root files is not controlled by tsserver)
*/
Expand All @@ -3720,7 +3718,7 @@ declare namespace ts {
/**
* Open files: with value being project root path, and key being Path of the file that is open
*/
readonly openFiles: Map<string, NormalizedPath | undefined>;
readonly openFiles: Map<Path, NormalizedPath | undefined>;
/**
* Map of open files that are opened without complete path but have projectRoot as current directory
*/
Expand Down Expand Up @@ -3815,6 +3813,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 @@ -3868,14 +3868,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
* or schedules it for reload depending on delayReload option
* If there is no existing project it just opens the configured project for the config file
* reloadForInfo provides a way to filter out files to reload configured project for
*/
private reloadConfiguredProjectForFiles;
/**
* Remove the root of inferred project if script info is part of another project
*/
Expand All @@ -3896,12 +3888,13 @@ declare namespace ts {
openClientFile(fileName: string, fileContent?: string, scriptKind?: ScriptKind, projectRootPath?: string): OpenConfiguredProjectResult;
private findExternalProjectContainingOpenScriptInfo;
private getOrCreateOpenScriptInfo;
private tryFindDefaultConfiguredProjectForOpenScriptInfo;
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 @@ -3910,7 +3903,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 @@ -517,19 +517,11 @@ Info seq [hh:mm:ss:mss] event:
}
}
Info seq [hh:mm:ss:mss] Finding references to /packages/b/index.ts position 13 in project /tsconfig.json
Info seq [hh:mm:ss:mss] Search path: /packages/b
Info seq [hh:mm:ss:mss] For info: /packages/b/index.ts :: Config file name: /packages/b/tsconfig.json
Info seq [hh:mm:ss:mss] Search path: /packages/b
Info seq [hh:mm:ss:mss] For info: /packages/b/index.ts :: Config file name: /packages/b/tsconfig.json
Info seq [hh:mm:ss:mss] Search path: /packages/a
Info seq [hh:mm:ss:mss] For info: /packages/a/index.ts :: Config file name: /packages/a/tsconfig.json
Info seq [hh:mm:ss:mss] Search path: /packages/a
Info seq [hh:mm:ss:mss] For info: /packages/a/index.ts :: Config file name: /packages/a/tsconfig.json
Info seq [hh:mm:ss:mss] Finding references to /packages/b/index.ts position 13 in project /packages/a/tsconfig.json
Info seq [hh:mm:ss:mss] Search path: /packages/b
Info seq [hh:mm:ss:mss] For info: /packages/b/index.ts :: Config file name: /packages/b/tsconfig.json
Info seq [hh:mm:ss:mss] Search path: /packages/b
Info seq [hh:mm:ss:mss] For info: /packages/b/index.ts :: Config file name: /packages/b/tsconfig.json
Info seq [hh:mm:ss:mss] response:
{
"response": {
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -857,10 +857,6 @@ Info seq [hh:mm:ss:mss] request:
Info seq [hh:mm:ss:mss] Finding references to /a/a.ts position 16 in project /a/tsconfig.json
Info seq [hh:mm:ss:mss] FileWatcher:: Added:: WatchInfo: /a/bin/a.d.ts.map 500 undefined WatchType: Closed Script info
Info seq [hh:mm:ss:mss] Finding references to /a/bin/a.d.ts position 24 in project /dev/null/inferredProject1*
Info seq [hh:mm:ss:mss] Search path: /a
Info seq [hh:mm:ss:mss] For info: /a/a.ts :: Config file name: /a/tsconfig.json
Info seq [hh:mm:ss:mss] Search path: /a
Info seq [hh:mm:ss:mss] For info: /a/a.ts :: Config file name: /a/tsconfig.json
Info seq [hh:mm:ss:mss] response:
{
"response": {
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -855,8 +855,6 @@ Info seq [hh:mm:ss:mss] request:
"type": "request"
}
Info seq [hh:mm:ss:mss] FileWatcher:: Added:: WatchInfo: /a/bin/a.d.ts.map 500 undefined WatchType: Closed Script info
Info seq [hh:mm:ss:mss] Search path: /a
Info seq [hh:mm:ss:mss] For info: /a/a.ts :: Config file name: /a/tsconfig.json
Info seq [hh:mm:ss:mss] response:
{
"response": {
Expand Down
Loading

0 comments on commit dbd6717

Please sign in to comment.