Skip to content

Commit d18d9bc

Browse files
committed
Add needDefaultProjectInfo to get details about the default project for file
This adds createReplay which behaves like create but (does not create project and does not short circuit if we find project early)
1 parent c48c058 commit d18d9bc

31 files changed

Lines changed: 671 additions & 336 deletions

File tree

src/server/editorServices.ts

Lines changed: 61 additions & 11 deletions
Original file line numberDiff line numberDiff line change
@@ -713,6 +713,8 @@ function isAncestorConfigFileInfo(infoOrFileNameOrConfig: OpenScriptInfoOrClosed
713713
export enum ConfiguredProjectLoadKind {
714714
FindOptimized,
715715
Find,
716+
CreateReplayOptimized,
717+
CreateReplay,
716718
CreateOptimized,
717719
Create,
718720
ReloadOptimized,
@@ -721,11 +723,13 @@ export enum ConfiguredProjectLoadKind {
721723

722724
type ConguredProjectLoadFindCreateOrReload =
723725
| ConfiguredProjectLoadKind.Find
726+
| ConfiguredProjectLoadKind.CreateReplay
724727
| ConfiguredProjectLoadKind.Create
725728
| ConfiguredProjectLoadKind.Reload;
726729

727730
type ConguredProjectLoadFindCreateOrReloadOptimized =
728731
| ConfiguredProjectLoadKind.FindOptimized
732+
| ConfiguredProjectLoadKind.CreateReplayOptimized
729733
| ConfiguredProjectLoadKind.CreateOptimized
730734
| ConfiguredProjectLoadKind.ReloadOptimized;
731735

@@ -794,8 +798,7 @@ function forEachAncestorProjectLoad<T>(
794798
configFileInfo: true,
795799
isForDefaultProject: !searchOnlyPotentialSolution,
796800
},
797-
kind === ConfiguredProjectLoadKind.Find ||
798-
kind === ConfiguredProjectLoadKind.FindOptimized,
801+
kind <= ConfiguredProjectLoadKind.CreateReplay,
799802
);
800803
if (!configFileName) return;
801804

@@ -865,21 +868,28 @@ function forEachResolvedProjectReferenceProjectLoad<T>(
865868
configFileExistenceInfo?.exists || project.resolvedChildConfigs?.has(childCanonicalConfigPath) ?
866869
configFileExistenceInfo!.config!.parsedCommandLine : undefined :
867870
project.getParsedCommandLine(childConfigName);
868-
if (childConfig && loadKind !== kind) {
871+
if (childConfig && loadKind !== kind && loadKind > ConfiguredProjectLoadKind.CreateReplayOptimized) {
869872
// If this was found using find: ensure this is uptodate if looking for creating or reloading
870873
childConfig = project.getParsedCommandLine(childConfigName);
871874
}
872875
if (!childConfig) return undefined;
873876

874877
// Find the project
875878
const childProject = project.projectService.findConfiguredProjectByProjectName(childConfigName, allowDeferredClosed);
879+
// Ignore if we couldnt find child project or config file existence info
880+
if (
881+
loadKind === ConfiguredProjectLoadKind.CreateReplayOptimized &&
882+
!configFileExistenceInfo &&
883+
!childProject
884+
) return undefined;
876885
switch (loadKind) {
877886
case ConfiguredProjectLoadKind.ReloadOptimized:
878887
if (childProject) childProject.projectService.reloadConfiguredProjectOptimized(childProject, reason, reloadedProjects!);
879888
// falls through
880889
case ConfiguredProjectLoadKind.CreateOptimized:
881890
(project.resolvedChildConfigs ??= new Set()).add(childCanonicalConfigPath);
882-
// falls through
891+
// falls through
892+
case ConfiguredProjectLoadKind.CreateReplayOptimized:
883893
case ConfiguredProjectLoadKind.FindOptimized:
884894
if (childProject || loadKind !== ConfiguredProjectLoadKind.FindOptimized) {
885895
const result = cb(
@@ -930,6 +940,12 @@ function updateProjectFoundUsingFind(
930940
// This project was found using "Find" instead of the actually specified kind of "Create" or "Reload",
931941
// We need to update or reload this existing project before calling callback
932942
switch (kind) {
943+
case ConfiguredProjectLoadKind.CreateReplayOptimized:
944+
case ConfiguredProjectLoadKind.CreateReplay:
945+
if (useConfigFileExistenceInfoForOptimizedLoading(project)) {
946+
configFileExistenceInfo = project.projectService.configFileExistenceInfoCache.get(project.canonicalConfigFilePath)!;
947+
}
948+
break;
933949
case ConfiguredProjectLoadKind.CreateOptimized:
934950
configFileExistenceInfo = configFileExistenceInfoForOptimizedLoading(project);
935951
if (configFileExistenceInfo) break;
@@ -1077,9 +1093,21 @@ function configFileExistenceInfoForOptimizedLoading(project: ConfiguredProject)
10771093
project.resolvedChildConfigs = undefined;
10781094
project.updateReferences(parsedCommandLine.projectReferences);
10791095
// Composite can determine based on files themselves, no need to load project
1080-
if (parsedCommandLine.options.composite) return configFileExistenceInfo;
10811096
// If solution, no need to load it to determine if file belongs to it
1082-
if (isSolutionConfig(parsedCommandLine)) return configFileExistenceInfo;
1097+
if (useConfigFileExistenceInfoForOptimizedLoading(project)) return configFileExistenceInfo;
1098+
}
1099+
1100+
function useConfigFileExistenceInfoForOptimizedLoading(project: ConfiguredProject) {
1101+
return !!project.parsedCommandLine &&
1102+
(!!project.parsedCommandLine.options.composite ||
1103+
// If solution, no need to load it to determine if file belongs to it
1104+
!!isSolutionConfig(project.parsedCommandLine));
1105+
}
1106+
1107+
function configFileExistenceInfoForOptimizedReplay(project: ConfiguredProject) {
1108+
return useConfigFileExistenceInfoForOptimizedLoading(project) ?
1109+
project.projectService.configFileExistenceInfoCache.get(project.canonicalConfigFilePath)! :
1110+
undefined;
10831111
}
10841112

10851113
function fileOpenReason(info: ScriptInfo) {
@@ -2603,11 +2631,22 @@ export class ProjectService {
26032631

26042632
/** @internal */
26052633
findDefaultConfiguredProject(info: ScriptInfo) {
2634+
return this.findDefaultConfiguredProjectWorker(
2635+
info,
2636+
ConfiguredProjectLoadKind.Find,
2637+
)?.defaultProject;
2638+
}
2639+
2640+
/** @internal */
2641+
findDefaultConfiguredProjectWorker(
2642+
info: ScriptInfo,
2643+
kind: ConfiguredProjectLoadKind.Find | ConfiguredProjectLoadKind.CreateReplay,
2644+
) {
26062645
return info.isScriptOpen() ?
26072646
this.tryFindDefaultConfiguredProjectForOpenScriptInfo(
26082647
info,
2609-
ConfiguredProjectLoadKind.Find,
2610-
)?.defaultProject :
2648+
kind,
2649+
) :
26112650
undefined;
26122651
}
26132652

@@ -4360,8 +4399,13 @@ export class ProjectService {
43604399
switch (kind) {
43614400
case ConfiguredProjectLoadKind.FindOptimized:
43624401
case ConfiguredProjectLoadKind.Find:
4402+
case ConfiguredProjectLoadKind.CreateReplay:
43634403
if (!project) return;
43644404
break;
4405+
case ConfiguredProjectLoadKind.CreateReplayOptimized:
4406+
if (!project) return;
4407+
configFileExistenceInfo = configFileExistenceInfoForOptimizedReplay(project);
4408+
break;
43654409
case ConfiguredProjectLoadKind.CreateOptimized:
43664410
case ConfiguredProjectLoadKind.Create:
43674411
project ??= this.createConfiguredProject(configFileName, reason!);
@@ -4414,7 +4458,7 @@ export class ProjectService {
44144458
/** Used with ConfiguredProjectLoadKind.Reload to check if this project was already reloaded */
44154459
reloadedProjects?: ConfiguredProjectToAnyReloadKind,
44164460
): DefaultConfiguredProjectResult | undefined {
4417-
const configFileName = this.getConfigFileNameForFile(info, kind === ConfiguredProjectLoadKind.Find);
4461+
const configFileName = this.getConfigFileNameForFile(info, kind <= ConfiguredProjectLoadKind.CreateReplay);
44184462
// If no config file name, no result
44194463
if (!configFileName) return;
44204464

@@ -4508,6 +4552,7 @@ export class ProjectService {
45084552
tsconfigProject: tsconfigOfDefault ?? tsconfigOfPossiblyDefault,
45094553
sentConfigDiag,
45104554
seenProjects,
4555+
seenConfigs,
45114556
};
45124557

45134558
function tryFindDefaultConfiguredProject(result: FindCreateOrLoadConfiguredProjectResult): ConfiguredProject | undefined {
@@ -4569,7 +4614,12 @@ export class ProjectService {
45694614
allowDeferredClosed,
45704615
info.fileName,
45714616
reloadedProjects,
4572-
)!;
4617+
);
4618+
if (!result) {
4619+
// Did no find existing project but thats ok, we will give information based on what we find
4620+
Debug.assert(kind === ConfiguredProjectLoadKind.CreateReplay);
4621+
return undefined;
4622+
}
45734623
seenProjects.set(result.project, optimizedKind);
45744624
if (result.sentConfigFileDiag) sentConfigDiag.add(result.project);
45754625
return isDefaultProject(result.project, tsconfigProject);
@@ -4658,7 +4708,7 @@ export class ProjectService {
46584708
): DefaultConfiguredProjectResult | undefined;
46594709
private tryFindDefaultConfiguredProjectAndLoadAncestorsForOpenScriptInfo(
46604710
info: ScriptInfo,
4661-
kind: ConguredProjectLoadFindCreateOrReload,
4711+
kind: ConfiguredProjectLoadKind.Find | ConfiguredProjectLoadKind.Create | ConfiguredProjectLoadKind.Reload,
46624712
reloadedProjects?: ConfiguredProjectToAnyReloadKind,
46634713
delayReloadedConfiguredProjects?: Set<ConfiguredProject>,
46644714
): DefaultConfiguredProjectResult | undefined {

src/server/protocol.ts

Lines changed: 20 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -498,6 +498,10 @@ export interface ProjectInfoRequestArgs extends FileRequestArgs {
498498
* Indicate if the file name list of the project is needed
499499
*/
500500
needFileNameList: boolean;
501+
/**
502+
* if true returns details about default configured project calculation
503+
*/
504+
needDefaultConfiguredProjectInfo?: boolean;
501505
}
502506

503507
/**
@@ -525,6 +529,18 @@ export interface CompilerOptionsDiagnosticsRequestArgs {
525529
projectFileName: string;
526530
}
527531

532+
/**
533+
* Details about the default project for the file if tsconfig file is found
534+
*/
535+
export interface DefaultConfiguredProjectInfo {
536+
/** List of config files looked and did not match because file was not part of root file names */
537+
notMatchedByConfig?: readonly string[];
538+
/** List of projects which were loaded but file was not part of the project or is file from referenced project */
539+
notInProject?: readonly string[];
540+
/** Configured project used as default */
541+
defaultProject?: string;
542+
}
543+
528544
/**
529545
* Response message body for "projectInfo" request
530546
*/
@@ -542,6 +558,10 @@ export interface ProjectInfo {
542558
* Indicates if the project has a active language service instance
543559
*/
544560
languageServiceDisabled?: boolean;
561+
/**
562+
* Information about default project
563+
*/
564+
configuredProjectInfo?: DefaultConfiguredProjectInfo;
545565
}
546566

547567
/**

src/server/session.ts

Lines changed: 56 additions & 9 deletions
Original file line numberDiff line numberDiff line change
@@ -143,6 +143,7 @@ import {
143143
CloseFileWatcherEvent,
144144
ConfigFileDiagEvent,
145145
ConfiguredProject,
146+
ConfiguredProjectLoadKind,
146147
convertFormatOptions,
147148
convertScriptKindName,
148149
convertUserPreferences,
@@ -2039,20 +2040,63 @@ export class Session<TMessage = string> implements EventSender {
20392040
}
20402041

20412042
private getProjectInfo(args: protocol.ProjectInfoRequestArgs): protocol.ProjectInfo {
2042-
return this.getProjectInfoWorker(args.file, args.projectFileName, args.needFileNameList, /*excludeConfigFiles*/ false);
2043+
return this.getProjectInfoWorker(
2044+
args.file,
2045+
args.projectFileName,
2046+
args.needFileNameList,
2047+
args.needDefaultConfiguredProjectInfo,
2048+
/*excludeConfigFiles*/ false,
2049+
);
20432050
}
20442051

2045-
private getProjectInfoWorker(uncheckedFileName: string, projectFileName: string | undefined, needFileNameList: boolean, excludeConfigFiles: boolean) {
2052+
private getProjectInfoWorker(
2053+
uncheckedFileName: string,
2054+
projectFileName: string | undefined,
2055+
needFileNameList: boolean,
2056+
needDefaultConfiguredProjectInfo: boolean | undefined,
2057+
excludeConfigFiles: boolean,
2058+
): Omit<protocol.ProjectInfo, "fileNames"> & { fileNames?: NormalizedPath[]; } {
20462059
const { project } = this.getFileAndProjectWorker(uncheckedFileName, projectFileName);
20472060
updateProjectIfDirty(project);
20482061
const projectInfo = {
20492062
configFileName: project.getProjectName(),
20502063
languageServiceDisabled: !project.languageServiceEnabled,
20512064
fileNames: needFileNameList ? project.getFileNames(/*excludeFilesFromExternalLibraries*/ false, excludeConfigFiles) : undefined,
2065+
configuredProjectInfo: needDefaultConfiguredProjectInfo ? this.getDefaultConfiguredProjectInfo(uncheckedFileName) : undefined,
20522066
};
20532067
return projectInfo;
20542068
}
20552069

2070+
private getDefaultConfiguredProjectInfo(uncheckedFileName: string): protocol.DefaultConfiguredProjectInfo | undefined {
2071+
const info = this.projectService.getScriptInfo(uncheckedFileName);
2072+
if (!info) return;
2073+
2074+
// Find default project for the info
2075+
const result = this.projectService.findDefaultConfiguredProjectWorker(
2076+
info,
2077+
ConfiguredProjectLoadKind.CreateReplay,
2078+
);
2079+
if (!result) return undefined;
2080+
let notMatchedByConfig: NormalizedPath[] | undefined;
2081+
let notInProject: NormalizedPath[] | undefined;
2082+
result.seenProjects.forEach((kind, project) => {
2083+
if (project !== result.defaultProject) {
2084+
if (kind !== ConfiguredProjectLoadKind.CreateReplay) {
2085+
(notMatchedByConfig ??= []).push(toNormalizedPath(project.getConfigFilePath()));
2086+
}
2087+
else {
2088+
(notInProject ??= []).push(toNormalizedPath(project.getConfigFilePath()));
2089+
}
2090+
}
2091+
});
2092+
result.seenConfigs?.forEach(config => (notMatchedByConfig ??= []).push(config));
2093+
return {
2094+
notMatchedByConfig,
2095+
notInProject,
2096+
defaultProject: result.defaultProject && toNormalizedPath(result.defaultProject.getConfigFilePath()),
2097+
};
2098+
}
2099+
20562100
private getRenameInfo(args: protocol.FileLocationRequestArgs): RenameInfo {
20572101
const { file, project } = this.getFileAndProject(args);
20582102
const position = this.getPositionInFile(args, file);
@@ -3094,16 +3138,19 @@ export class Session<TMessage = string> implements EventSender {
30943138
return;
30953139
}
30963140

3097-
const { fileNames, languageServiceDisabled } = this.getProjectInfoWorker(fileName, /*projectFileName*/ undefined, /*needFileNameList*/ true, /*excludeConfigFiles*/ true);
3098-
if (languageServiceDisabled) {
3099-
return;
3100-
}
3141+
const { fileNames, languageServiceDisabled } = this.getProjectInfoWorker(
3142+
fileName,
3143+
/*projectFileName*/ undefined,
3144+
/*needFileNameList*/ true,
3145+
/*needDefaultConfiguredProjectInfo*/ undefined,
3146+
/*excludeConfigFiles*/ true,
3147+
);
3148+
3149+
if (languageServiceDisabled) return;
31013150

31023151
// No need to analyze lib.d.ts
31033152
const fileNamesInProject = fileNames!.filter(value => !value.includes("lib.d.ts")); // TODO: GH#18217
3104-
if (fileNamesInProject.length === 0) {
3105-
return;
3106-
}
3153+
if (fileNamesInProject.length === 0) return;
31073154

31083155
// Sort the file name list to make the recently touched files come first
31093156
const highPriorityFiles: NormalizedPath[] = [];

src/testRunner/unittests/helpers/tsserver.ts

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -471,6 +471,7 @@ export function projectInfoForSession(
471471
arguments: {
472472
file: ts.isString(file) ? file : file.path,
473473
needFileNameList: false,
474+
needDefaultConfiguredProjectInfo: true,
474475
},
475476
}).response as ts.server.protocol.ProjectInfo;
476477
}

src/testRunner/unittests/tsserver/projectReferences.ts

Lines changed: 1 addition & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -21,9 +21,6 @@ import {
2121

2222
function logDefaultProjectAndDefaultConfiguredProject(session: TestSession, file: File) {
2323
const info = session.getProjectService().getScriptInfo(file.path);
24-
const defaultProject = session.getProjectService().tryGetDefaultProjectForFile(file.path as ts.server.NormalizedPath);
25-
const defaultConfiguredProject = info && session.getProjectService().findDefaultConfiguredProject(info);
26-
session.logger.info(`File: ${file.path}:\n\tgetDefaultProjectForFile:\n\t\t${defaultProject?.projectName}\n\tfindDefaultConfiguredProject:\n\t\t${defaultConfiguredProject?.projectName}`);
2724
if (info) {
2825
const projectInfo = projectInfoForSession(session, file);
2926
return session.getProjectService().findProject(projectInfo.configFileName);
@@ -1728,7 +1725,7 @@ const b: B = new B();`,
17281725
/* eslint-enable local/argument-trivia */
17291726
});
17301727

1731-
describe("when file is not part of first config tree found", () => {
1728+
describe("when file is not part of first config tree found sheetal", () => {
17321729
it("finds default project", () => {
17331730
const { session, appDemo, baseline, verifyProjectManagement } = setup();
17341731
verifyGetErrRequest({

tests/baselines/reference/api/typescript.d.ts

Lines changed: 20 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -363,6 +363,10 @@ declare namespace ts {
363363
* Indicate if the file name list of the project is needed
364364
*/
365365
needFileNameList: boolean;
366+
/**
367+
* if true returns details about default configured project calculation
368+
*/
369+
needDefaultConfiguredProjectInfo?: boolean;
366370
}
367371
/**
368372
* A request to get the project information of the current file.
@@ -386,6 +390,17 @@ declare namespace ts {
386390
*/
387391
projectFileName: string;
388392
}
393+
/**
394+
* Details about the default project for the file if tsconfig file is found
395+
*/
396+
export interface DefaultConfiguredProjectInfo {
397+
/** List of config files looked and did not match because file was not part of root file names */
398+
notMatchedByConfig?: readonly string[];
399+
/** List of projects which were loaded but file was not part of the project or is file from referenced project */
400+
notInProject?: readonly string[];
401+
/** Configured project used as default */
402+
defaultProject?: string;
403+
}
389404
/**
390405
* Response message body for "projectInfo" request
391406
*/
@@ -403,6 +418,10 @@ declare namespace ts {
403418
* Indicates if the project has a active language service instance
404419
*/
405420
languageServiceDisabled?: boolean;
421+
/**
422+
* Information about default project
423+
*/
424+
configuredProjectInfo?: DefaultConfiguredProjectInfo;
406425
}
407426
/**
408427
* Represents diagnostic info that includes location of diagnostic in two forms
@@ -3484,6 +3503,7 @@ declare namespace ts {
34843503
private setCompilerOptionsForInferredProjects;
34853504
private getProjectInfo;
34863505
private getProjectInfoWorker;
3506+
private getDefaultConfiguredProjectInfo;
34873507
private getRenameInfo;
34883508
private getProjects;
34893509
private getDefaultProject;

0 commit comments

Comments
 (0)