Skip to content

Commit a499360

Browse files
committed
Add needDefaultProjectInfo to get details about the default project for file
1 parent 7fce70a commit a499360

File tree

30 files changed

+690
-340
lines changed

30 files changed

+690
-340
lines changed

src/server/editorServices.ts

Lines changed: 93 additions & 14 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

@@ -753,6 +757,18 @@ export interface FindCreateOrLoadConfiguredProjectResult {
753757
reason: string | undefined;
754758
}
755759

760+
/** @internal */
761+
export interface DefaultConfiguredProjectInfo {
762+
/** List of config files looked and did not match because file was not part of root file names */
763+
notMatchedByConfig?: Set<NormalizedPath>;
764+
/** List of projects which were loaded but file was not part of the project */
765+
notInProject?: Set<ConfiguredProject>;
766+
/** List of projects where file was present in project but its a file from referenced project */
767+
inProjectWithReference?: Set<ConfiguredProject>;
768+
/** Configured project used as default */
769+
defaultProject?: ConfiguredProject;
770+
}
771+
756772
/**
757773
* Goes through each tsconfig from project till project root of open script info and finds, creates or reloads project per kind
758774
*/
@@ -794,8 +810,7 @@ function forEachAncestorProjectLoad<T>(
794810
configFileInfo: true,
795811
isForDefaultProject: !searchOnlyPotentialSolution,
796812
},
797-
kind === ConfiguredProjectLoadKind.Find ||
798-
kind === ConfiguredProjectLoadKind.FindOptimized,
813+
kind <= ConfiguredProjectLoadKind.CreateReplay,
799814
);
800815
if (!configFileName) return;
801816

@@ -865,21 +880,28 @@ function forEachResolvedProjectReferenceProjectLoad<T>(
865880
configFileExistenceInfo?.exists || project.resolvedChildConfigs?.has(childCanonicalConfigPath) ?
866881
configFileExistenceInfo!.config!.parsedCommandLine : undefined :
867882
project.getParsedCommandLine(childConfigName);
868-
if (childConfig && loadKind !== kind) {
883+
if (childConfig && loadKind !== kind && loadKind > ConfiguredProjectLoadKind.CreateReplayOptimized) {
869884
// If this was found using find: ensure this is uptodate if looking for creating or reloading
870885
childConfig = project.getParsedCommandLine(childConfigName);
871886
}
872887
if (!childConfig) return undefined;
873888

874889
// Find the project
875890
const childProject = project.projectService.findConfiguredProjectByProjectName(childConfigName, allowDeferredClosed);
891+
// Ignore if we couldnt find child project or config file existence info
892+
if (
893+
loadKind === ConfiguredProjectLoadKind.CreateReplayOptimized &&
894+
!configFileExistenceInfo &&
895+
!childProject
896+
) return undefined;
876897
switch (loadKind) {
877898
case ConfiguredProjectLoadKind.ReloadOptimized:
878899
if (childProject) childProject.projectService.reloadConfiguredProjectOptimized(childProject, reason, reloadedProjects!);
879900
// falls through
880901
case ConfiguredProjectLoadKind.CreateOptimized:
881902
(project.resolvedChildConfigs ??= new Set()).add(childCanonicalConfigPath);
882-
// falls through
903+
// falls through
904+
case ConfiguredProjectLoadKind.CreateReplayOptimized:
883905
case ConfiguredProjectLoadKind.FindOptimized:
884906
if (childProject || loadKind !== ConfiguredProjectLoadKind.FindOptimized) {
885907
const result = cb(
@@ -930,6 +952,12 @@ function updateProjectFoundUsingFind(
930952
// This project was found using "Find" instead of the actually specified kind of "Create" or "Reload",
931953
// We need to update or reload this existing project before calling callback
932954
switch (kind) {
955+
case ConfiguredProjectLoadKind.CreateReplayOptimized:
956+
case ConfiguredProjectLoadKind.CreateReplay:
957+
if (useConfigFileExistenceInfoForOptimizedLoading(project)) {
958+
configFileExistenceInfo = project.projectService.configFileExistenceInfoCache.get(project.canonicalConfigFilePath)!;
959+
}
960+
break;
933961
case ConfiguredProjectLoadKind.CreateOptimized:
934962
configFileExistenceInfo = configFileExistenceInfoForOptimizedLoading(project);
935963
if (configFileExistenceInfo) break;
@@ -1077,9 +1105,21 @@ function configFileExistenceInfoForOptimizedLoading(project: ConfiguredProject)
10771105
project.resolvedChildConfigs = undefined;
10781106
project.updateReferences(parsedCommandLine.projectReferences);
10791107
// Composite can determine based on files themselves, no need to load project
1080-
if (parsedCommandLine.options.composite) return configFileExistenceInfo;
10811108
// If solution, no need to load it to determine if file belongs to it
1082-
if (isSolutionConfig(parsedCommandLine)) return configFileExistenceInfo;
1109+
if (useConfigFileExistenceInfoForOptimizedLoading(project)) return configFileExistenceInfo;
1110+
}
1111+
1112+
function useConfigFileExistenceInfoForOptimizedLoading(project: ConfiguredProject) {
1113+
return !!project.parsedCommandLine &&
1114+
(!!project.parsedCommandLine.options.composite ||
1115+
// If solution, no need to load it to determine if file belongs to it
1116+
!!isSolutionConfig(project.parsedCommandLine));
1117+
}
1118+
1119+
function configFileExistenceInfoForOptimizedReplay(project: ConfiguredProject) {
1120+
return useConfigFileExistenceInfoForOptimizedLoading(project) ?
1121+
project.projectService.configFileExistenceInfoCache.get(project.canonicalConfigFilePath)! :
1122+
undefined;
10831123
}
10841124

10851125
function fileOpenReason(info: ScriptInfo) {
@@ -2603,11 +2643,26 @@ export class ProjectService {
26032643

26042644
/** @internal */
26052645
findDefaultConfiguredProject(info: ScriptInfo) {
2646+
return this.findDefaultConfiguredProjectWorker(
2647+
info,
2648+
ConfiguredProjectLoadKind.Find,
2649+
)?.defaultProject;
2650+
}
2651+
2652+
/** @internal */
2653+
findDefaultConfiguredProjectWorker(
2654+
info: ScriptInfo,
2655+
kind: ConfiguredProjectLoadKind.Find | ConfiguredProjectLoadKind.CreateReplay,
2656+
replayResult?: DefaultConfiguredProjectInfo,
2657+
) {
26062658
return info.isScriptOpen() ?
26072659
this.tryFindDefaultConfiguredProjectForOpenScriptInfo(
26082660
info,
2609-
ConfiguredProjectLoadKind.Find,
2610-
)?.defaultProject :
2661+
kind,
2662+
/*allowDeferredClosed*/ undefined,
2663+
/*reloadedProjects*/ undefined,
2664+
replayResult,
2665+
) :
26112666
undefined;
26122667
}
26132668

@@ -4360,7 +4415,12 @@ export class ProjectService {
43604415
switch (kind) {
43614416
case ConfiguredProjectLoadKind.FindOptimized:
43624417
case ConfiguredProjectLoadKind.Find:
4418+
case ConfiguredProjectLoadKind.CreateReplay:
4419+
if (!project) return;
4420+
break;
4421+
case ConfiguredProjectLoadKind.CreateReplayOptimized:
43634422
if (!project) return;
4423+
configFileExistenceInfo = configFileExistenceInfoForOptimizedReplay(project);
43644424
break;
43654425
case ConfiguredProjectLoadKind.CreateOptimized:
43664426
case ConfiguredProjectLoadKind.Create:
@@ -4413,8 +4473,10 @@ export class ProjectService {
44134473
allowDeferredClosed?: boolean,
44144474
/** Used with ConfiguredProjectLoadKind.Reload to check if this project was already reloaded */
44154475
reloadedProjects?: ConfiguredProjectToAnyReloadKind,
4476+
/** Used with ConfiguredProjectLoadKind.CreateReplay to store replay result */
4477+
replayResult?: DefaultConfiguredProjectInfo,
44164478
): DefaultConfiguredProjectResult | undefined {
4417-
const configFileName = this.getConfigFileNameForFile(info, kind === ConfiguredProjectLoadKind.Find);
4479+
const configFileName = this.getConfigFileNameForFile(info, kind <= ConfiguredProjectLoadKind.CreateReplay);
44184480
// If no config file name, no result
44194481
if (!configFileName) return;
44204482

@@ -4437,6 +4499,7 @@ export class ProjectService {
44374499
project => `Creating project referenced in solution ${project.projectName} to find possible configured project for ${info.fileName} to open`,
44384500
allowDeferredClosed,
44394501
reloadedProjects,
4502+
replayResult,
44404503
);
44414504
}
44424505

@@ -4491,6 +4554,8 @@ export class ProjectService {
44914554
allowDeferredClosed?: boolean,
44924555
/** Used with ConfiguredProjectLoadKind.Reload to check if this project was already reloaded */
44934556
reloadedProjects?: ConfiguredProjectToAnyReloadKind,
4557+
/** Used with ConfiguredProjectLoadKind.CreateReplay to store replay result */
4558+
replayResult?: DefaultConfiguredProjectInfo,
44944559
) {
44954560
const infoIsOpenScriptInfo = isOpenScriptInfo(info);
44964561
const optimizedKind = toConfiguredProjectLoadOptimized(kind);
@@ -4503,14 +4568,18 @@ export class ProjectService {
45034568
let tsconfigOfPossiblyDefault: ConfiguredProject | undefined;
45044569
// See if this is the project or is it one of the references or find ancestor projects
45054570
tryFindDefaultConfiguredProject(initialConfigResult);
4571+
const result = defaultProject ?? possiblyDefault;
4572+
if (replayResult) replayResult.defaultProject = result;
45064573
return {
4507-
defaultProject: defaultProject ?? possiblyDefault,
4574+
defaultProject: result,
45084575
tsconfigProject: tsconfigOfDefault ?? tsconfigOfPossiblyDefault,
45094576
sentConfigDiag,
45104577
seenProjects,
45114578
};
45124579

4513-
function tryFindDefaultConfiguredProject(result: FindCreateOrLoadConfiguredProjectResult): ConfiguredProject | undefined {
4580+
function tryFindDefaultConfiguredProject(
4581+
result: FindCreateOrLoadConfiguredProjectResult,
4582+
): ConfiguredProject | undefined {
45144583
return isDefaultProjectOptimized(result, result.project) ??
45154584
tryFindDefaultConfiguredProjectFromReferences(result.project) ??
45164585
tryFindDefaultConfiguredProjectFromAncestor(result.project);
@@ -4542,7 +4611,8 @@ export class ProjectService {
45424611
info,
45434612
)
45444613
) {
4545-
if (tsconfigProject.languageServiceEnabled) {
4614+
if (replayResult) (replayResult.notMatchedByConfig ??= new Set()).add(childConfigName);
4615+
else if (tsconfigProject.languageServiceEnabled) {
45464616
// Ensure we are watching the parsedCommandLine
45474617
tsconfigProject.projectService.watchWildcards(
45484618
childConfigName,
@@ -4569,7 +4639,12 @@ export class ProjectService {
45694639
allowDeferredClosed,
45704640
info.fileName,
45714641
reloadedProjects,
4572-
)!;
4642+
);
4643+
if (!result) {
4644+
// Did no find existing project but thats ok, we will give information based on what we find
4645+
Debug.assert(replayResult);
4646+
return undefined;
4647+
}
45734648
seenProjects.set(result.project, optimizedKind);
45744649
if (result.sentConfigFileDiag) sentConfigDiag.add(result.project);
45754650
return isDefaultProject(result.project, tsconfigProject);
@@ -4589,6 +4664,10 @@ export class ProjectService {
45894664
tsconfigOfDefault = tsconfigProject;
45904665
return defaultProject = project;
45914666
}
4667+
if (replayResult) {
4668+
if (!projectWithInfo) (replayResult.notInProject ??= new Set()).add(project);
4669+
else (replayResult.inProjectWithReference ??= new Set()).add(project);
4670+
}
45924671
// If this project uses the script info, if default project is not found, use this project as possible default
45934672
if (!possiblyDefault && infoIsOpenScriptInfo && projectWithInfo) {
45944673
tsconfigOfPossiblyDefault = tsconfigProject;
@@ -4658,7 +4737,7 @@ export class ProjectService {
46584737
): DefaultConfiguredProjectResult | undefined;
46594738
private tryFindDefaultConfiguredProjectAndLoadAncestorsForOpenScriptInfo(
46604739
info: ScriptInfo,
4661-
kind: ConguredProjectLoadFindCreateOrReload,
4740+
kind: ConfiguredProjectLoadKind.Find | ConfiguredProjectLoadKind.Create | ConfiguredProjectLoadKind.Reload,
46624741
reloadedProjects?: ConfiguredProjectToAnyReloadKind,
46634742
delayReloadedConfiguredProjects?: Set<ConfiguredProject>,
46644743
): DefaultConfiguredProjectResult | undefined {

src/server/protocol.ts

Lines changed: 22 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,20 @@ export interface CompilerOptionsDiagnosticsRequestArgs {
525529
projectFileName: string;
526530
}
527531

532+
/**
533+
* Details about the default project for the file
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 */
539+
notInProject?: readonly string[];
540+
/** List of projects where file was present in project but its a file from referenced project */
541+
inProjectWithReference?: readonly string[];
542+
/** Configured project used as default */
543+
defaultProject?: string;
544+
}
545+
528546
/**
529547
* Response message body for "projectInfo" request
530548
*/
@@ -542,6 +560,10 @@ export interface ProjectInfo {
542560
* Indicates if the project has a active language service instance
543561
*/
544562
languageServiceDisabled?: boolean;
563+
/**
564+
* Information about default project
565+
*/
566+
configuredProjectInfo?: DefaultConfiguredProjectInfo;
545567
}
546568

547569
/**

src/server/session.ts

Lines changed: 47 additions & 9 deletions
Original file line numberDiff line numberDiff line change
@@ -143,11 +143,13 @@ import {
143143
CloseFileWatcherEvent,
144144
ConfigFileDiagEvent,
145145
ConfiguredProject,
146+
ConfiguredProjectLoadKind,
146147
convertFormatOptions,
147148
convertScriptKindName,
148149
convertUserPreferences,
149150
CreateDirectoryWatcherEvent,
150151
CreateFileWatcherEvent,
152+
DefaultConfiguredProjectInfo,
151153
EmitResult,
152154
emptyArray,
153155
Errors,
@@ -2039,20 +2041,53 @@ export class Session<TMessage = string> implements EventSender {
20392041
}
20402042

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

2045-
private getProjectInfoWorker(uncheckedFileName: string, projectFileName: string | undefined, needFileNameList: boolean, excludeConfigFiles: boolean) {
2053+
private getProjectInfoWorker(
2054+
uncheckedFileName: string,
2055+
projectFileName: string | undefined,
2056+
needFileNameList: boolean,
2057+
needDefaultConfiguredProjectInfo: boolean | undefined,
2058+
excludeConfigFiles: boolean,
2059+
): Omit<protocol.ProjectInfo, "fileNames"> & { fileNames?: NormalizedPath[]; } {
20462060
const { project } = this.getFileAndProjectWorker(uncheckedFileName, projectFileName);
20472061
updateProjectIfDirty(project);
20482062
const projectInfo = {
20492063
configFileName: project.getProjectName(),
20502064
languageServiceDisabled: !project.languageServiceEnabled,
20512065
fileNames: needFileNameList ? project.getFileNames(/*excludeFilesFromExternalLibraries*/ false, excludeConfigFiles) : undefined,
2066+
configuredProjectInfo: needDefaultConfiguredProjectInfo ? this.getDefaultConfiguredProjectInfo(uncheckedFileName) : undefined,
20522067
};
20532068
return projectInfo;
20542069
}
20552070

2071+
private getDefaultConfiguredProjectInfo(uncheckedFileName: string): protocol.DefaultConfiguredProjectInfo | undefined {
2072+
const info = this.projectService.getScriptInfo(uncheckedFileName);
2073+
if (!info) return;
2074+
2075+
// Find default project for the info
2076+
const replayResult: DefaultConfiguredProjectInfo = {};
2077+
this.projectService.findDefaultConfiguredProjectWorker(
2078+
info,
2079+
ConfiguredProjectLoadKind.CreateReplay,
2080+
replayResult,
2081+
);
2082+
const configName = (project: ConfiguredProject) => toNormalizedPath(project.getConfigFilePath());
2083+
return {
2084+
notMatchedByConfig: replayResult.notMatchedByConfig && arrayFrom(replayResult.notMatchedByConfig),
2085+
notInProject: replayResult.notInProject && arrayFrom(mapIterator(replayResult.notInProject, configName)),
2086+
inProjectWithReference: replayResult.inProjectWithReference && arrayFrom(mapIterator(replayResult.inProjectWithReference, configName)),
2087+
defaultProject: replayResult.defaultProject && configName(replayResult.defaultProject),
2088+
};
2089+
}
2090+
20562091
private getRenameInfo(args: protocol.FileLocationRequestArgs): RenameInfo {
20572092
const { file, project } = this.getFileAndProject(args);
20582093
const position = this.getPositionInFile(args, file);
@@ -3094,16 +3129,19 @@ export class Session<TMessage = string> implements EventSender {
30943129
return;
30953130
}
30963131

3097-
const { fileNames, languageServiceDisabled } = this.getProjectInfoWorker(fileName, /*projectFileName*/ undefined, /*needFileNameList*/ true, /*excludeConfigFiles*/ true);
3098-
if (languageServiceDisabled) {
3099-
return;
3100-
}
3132+
const { fileNames, languageServiceDisabled } = this.getProjectInfoWorker(
3133+
fileName,
3134+
/*projectFileName*/ undefined,
3135+
/*needFileNameList*/ true,
3136+
/*needDefaultConfiguredProjectInfo*/ undefined,
3137+
/*excludeConfigFiles*/ true,
3138+
);
3139+
3140+
if (languageServiceDisabled) return;
31013141

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

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

0 commit comments

Comments
 (0)