Skip to content

Commit 1c7319b

Browse files
committed
Add option --lazyConfiguredProjectsFromExternalProject to enable lazy load of configured projects referenced by external project
Fixes #26696
1 parent 1b5de9d commit 1c7319b

File tree

5 files changed

+65
-31
lines changed

5 files changed

+65
-31
lines changed

src/server/editorServices.ts

Lines changed: 14 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -323,6 +323,7 @@ namespace ts.server {
323323
cancellationToken: HostCancellationToken;
324324
useSingleInferredProject: boolean;
325325
useInferredProjectPerProjectRoot: boolean;
326+
lazyConfiguredProjectsFromExternalProject?: boolean;
326327
typingsInstaller: ITypingsInstaller;
327328
eventHandler?: ProjectServiceEventHandler;
328329
suppressDiagnosticEvents?: boolean;
@@ -440,6 +441,7 @@ namespace ts.server {
440441
public readonly cancellationToken: HostCancellationToken;
441442
public readonly useSingleInferredProject: boolean;
442443
public readonly useInferredProjectPerProjectRoot: boolean;
444+
private readonly lazyConfiguredProjectsFromExternalProject?: boolean;
443445
public readonly typingsInstaller: ITypingsInstaller;
444446
private readonly globalCacheLocationDirectoryPath: Path | undefined;
445447
public readonly throttleWaitMilliseconds?: number;
@@ -465,6 +467,7 @@ namespace ts.server {
465467
this.cancellationToken = opts.cancellationToken;
466468
this.useSingleInferredProject = opts.useSingleInferredProject;
467469
this.useInferredProjectPerProjectRoot = opts.useInferredProjectPerProjectRoot;
470+
this.lazyConfiguredProjectsFromExternalProject = opts.lazyConfiguredProjectsFromExternalProject;
468471
this.typingsInstaller = opts.typingsInstaller || nullTypingsInstaller;
469472
this.throttleWaitMilliseconds = opts.throttleWaitMilliseconds;
470473
this.eventHandler = opts.eventHandler;
@@ -1561,6 +1564,13 @@ namespace ts.server {
15611564
return project;
15621565
}
15631566

1567+
/* @internal */
1568+
private createLoadAndUpdateConfiguredProject(configFileName: NormalizedPath) {
1569+
const project = this.createAndLoadConfiguredProject(configFileName);
1570+
project.updateGraph();
1571+
return project;
1572+
}
1573+
15641574
/**
15651575
* Read the config file of the project, and update the project root file names.
15661576
*/
@@ -2192,8 +2202,7 @@ namespace ts.server {
21922202
if (configFileName) {
21932203
project = this.findConfiguredProjectByProjectName(configFileName);
21942204
if (!project) {
2195-
project = this.createAndLoadConfiguredProject(configFileName);
2196-
project.updateGraph();
2205+
project = this.createLoadAndUpdateConfiguredProject(configFileName);
21972206
// Send the event only if the project got created as part of this open request and info is part of the project
21982207
if (info.isOrphan()) {
21992208
// Since the file isnt part of configured project, do not send config file info
@@ -2633,7 +2642,9 @@ namespace ts.server {
26332642
let project = this.findConfiguredProjectByProjectName(tsconfigFile);
26342643
if (!project) {
26352644
// errors are stored in the project, do not need to update the graph
2636-
project = this.createConfiguredProjectWithDelayLoad(tsconfigFile);
2645+
project = this.lazyConfiguredProjectsFromExternalProject ?
2646+
this.createConfiguredProjectWithDelayLoad(tsconfigFile) :
2647+
this.createLoadAndUpdateConfiguredProject(tsconfigFile);
26372648
}
26382649
if (project && !contains(exisingConfigFiles, tsconfigFile)) {
26392650
// keep project alive even if no documents are opened - its lifetime is bound to the lifetime of containing external project

src/server/session.ts

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -465,6 +465,7 @@ namespace ts.server {
465465
cancellationToken: ServerCancellationToken;
466466
useSingleInferredProject: boolean;
467467
useInferredProjectPerProjectRoot: boolean;
468+
lazyConfiguredProjectsFromExternalProject?: boolean;
468469
typingsInstaller: ITypingsInstaller;
469470
byteLength: (buf: string, encoding?: string) => number;
470471
hrtime: (start?: number[]) => number[];
@@ -536,6 +537,7 @@ namespace ts.server {
536537
cancellationToken: this.cancellationToken,
537538
useSingleInferredProject: opts.useSingleInferredProject,
538539
useInferredProjectPerProjectRoot: opts.useInferredProjectPerProjectRoot,
540+
lazyConfiguredProjectsFromExternalProject: opts.lazyConfiguredProjectsFromExternalProject,
539541
typingsInstaller: this.typingsInstaller,
540542
throttleWaitMilliseconds,
541543
eventHandler: this.eventHandler,

src/testRunner/unittests/tsserverProjectSystem.ts

Lines changed: 44 additions & 28 deletions
Original file line numberDiff line numberDiff line change
@@ -642,37 +642,53 @@ namespace ts.projectSystem {
642642
checkWatchedDirectories(host, [combinePaths(getDirectoryPath(appFile.path), nodeModulesAtTypes)], /*recursive*/ true);
643643
});
644644

645-
it("can handle tsconfig file name with difference casing", () => {
646-
const f1 = {
647-
path: "/a/b/app.ts",
648-
content: "let x = 1"
649-
};
650-
const config = {
651-
path: "/a/b/tsconfig.json",
652-
content: JSON.stringify({
653-
include: []
654-
})
655-
};
645+
describe("can handle tsconfig file name with difference casing", () => {
646+
function verifyConfigFileCasing(lazyConfiguredProjectsFromExternalProject: boolean) {
647+
const f1 = {
648+
path: "/a/b/app.ts",
649+
content: "let x = 1"
650+
};
651+
const config = {
652+
path: "/a/b/tsconfig.json",
653+
content: JSON.stringify({
654+
include: []
655+
})
656+
};
656657

657-
const host = createServerHost([f1, config], { useCaseSensitiveFileNames: false });
658-
const service = createProjectService(host);
659-
const upperCaseConfigFilePath = combinePaths(getDirectoryPath(config.path).toUpperCase(), getBaseFileName(config.path));
660-
service.openExternalProject(<protocol.ExternalProject>{
661-
projectFileName: "/a/b/project.csproj",
662-
rootFiles: toExternalFiles([f1.path, upperCaseConfigFilePath]),
663-
options: {}
664-
});
665-
service.checkNumberOfProjects({ configuredProjects: 1 });
666-
const project = service.configuredProjects.get(config.path)!;
667-
assert.equal(project.pendingReload, ConfigFileProgramReloadLevel.Full); // External project referenced configured project pending to be reloaded
668-
checkProjectActualFiles(project, emptyArray);
658+
const host = createServerHost([f1, config], { useCaseSensitiveFileNames: false });
659+
const service = createProjectService(host, /*parameters*/ undefined, { lazyConfiguredProjectsFromExternalProject });
660+
const upperCaseConfigFilePath = combinePaths(getDirectoryPath(config.path).toUpperCase(), getBaseFileName(config.path));
661+
service.openExternalProject(<protocol.ExternalProject>{
662+
projectFileName: "/a/b/project.csproj",
663+
rootFiles: toExternalFiles([f1.path, upperCaseConfigFilePath]),
664+
options: {}
665+
});
666+
service.checkNumberOfProjects({ configuredProjects: 1 });
667+
const project = service.configuredProjects.get(config.path)!;
668+
if (lazyConfiguredProjectsFromExternalProject) {
669+
assert.equal(project.pendingReload, ConfigFileProgramReloadLevel.Full); // External project referenced configured project pending to be reloaded
670+
checkProjectActualFiles(project, emptyArray);
671+
}
672+
else {
673+
assert.equal(project.pendingReload, ConfigFileProgramReloadLevel.None); // External project referenced configured project pending to be reloaded
674+
checkProjectActualFiles(project, [upperCaseConfigFilePath]);
675+
}
669676

670-
service.openClientFile(f1.path);
671-
service.checkNumberOfProjects({ configuredProjects: 1, inferredProjects: 1 });
677+
service.openClientFile(f1.path);
678+
service.checkNumberOfProjects({ configuredProjects: 1, inferredProjects: 1 });
679+
680+
assert.equal(project.pendingReload, ConfigFileProgramReloadLevel.None); // External project referenced configured project is updated
681+
checkProjectActualFiles(project, [upperCaseConfigFilePath]);
682+
checkProjectActualFiles(service.inferredProjects[0], [f1.path]);
683+
}
672684

673-
assert.equal(project.pendingReload, ConfigFileProgramReloadLevel.None); // External project referenced configured project is updated
674-
checkProjectActualFiles(project, [upperCaseConfigFilePath]);
675-
checkProjectActualFiles(service.inferredProjects[0], [f1.path]);
685+
it("when lazyConfiguredProjectsFromExternalProject not set", () => {
686+
verifyConfigFileCasing(/*verifyConfigFileCasing*/ false);
687+
});
688+
689+
it("when lazyConfiguredProjectsFromExternalProject is set", () => {
690+
verifyConfigFileCasing(/*verifyConfigFileCasing*/ true);
691+
});
676692
});
677693

678694
it("create configured project without file list", () => {

src/tsserver/server.ts

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -499,6 +499,7 @@ namespace ts.server {
499499
cancellationToken,
500500
useSingleInferredProject,
501501
useInferredProjectPerProjectRoot,
502+
lazyConfiguredProjectsFromExternalProject,
502503
typingsInstaller: typingsInstaller || nullTypingsInstaller,
503504
byteLength: Buffer.byteLength,
504505
hrtime: process.hrtime,
@@ -937,6 +938,7 @@ namespace ts.server {
937938

938939
const useSingleInferredProject = hasArgument("--useSingleInferredProject");
939940
const useInferredProjectPerProjectRoot = hasArgument("--useInferredProjectPerProjectRoot");
941+
const lazyConfiguredProjectsFromExternalProject = hasArgument("--lazyConfiguredProjectsFromExternalProject");
940942
const disableAutomaticTypingAcquisition = hasArgument("--disableAutomaticTypingAcquisition");
941943
const suppressDiagnosticEvents = hasArgument("--suppressDiagnosticEvents");
942944
const syntaxOnly = hasArgument("--syntaxOnly");

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

Lines changed: 3 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -8325,6 +8325,7 @@ declare namespace ts.server {
83258325
cancellationToken: HostCancellationToken;
83268326
useSingleInferredProject: boolean;
83278327
useInferredProjectPerProjectRoot: boolean;
8328+
lazyConfiguredProjectsFromExternalProject?: boolean;
83288329
typingsInstaller: ITypingsInstaller;
83298330
eventHandler?: ProjectServiceEventHandler;
83308331
suppressDiagnosticEvents?: boolean;
@@ -8397,6 +8398,7 @@ declare namespace ts.server {
83978398
readonly cancellationToken: HostCancellationToken;
83988399
readonly useSingleInferredProject: boolean;
83998400
readonly useInferredProjectPerProjectRoot: boolean;
8401+
private readonly lazyConfiguredProjectsFromExternalProject?;
84008402
readonly typingsInstaller: ITypingsInstaller;
84018403
private readonly globalCacheLocationDirectoryPath;
84028404
readonly throttleWaitMilliseconds?: number;
@@ -8598,6 +8600,7 @@ declare namespace ts.server {
85988600
cancellationToken: ServerCancellationToken;
85998601
useSingleInferredProject: boolean;
86008602
useInferredProjectPerProjectRoot: boolean;
8603+
lazyConfiguredProjectsFromExternalProject?: boolean;
86018604
typingsInstaller: ITypingsInstaller;
86028605
byteLength: (buf: string, encoding?: string) => number;
86038606
hrtime: (start?: number[]) => number[];

0 commit comments

Comments
 (0)