Add disableReferencedProjectLoad to stop loading child projects to allow users to disable loading large solutions (#39593)

* Use disableReferencedProjectLoad to stop loading child projects to allow users to disable loading large solutions
Fixes #39144

* Handle indirect references

* PR feedback
This commit is contained in:
Sheetal Nandi
2020-07-21 14:24:15 -07:00
committed by GitHub
parent 5484687384
commit e92afacc44
10 changed files with 302 additions and 66 deletions

View File

@@ -449,11 +449,11 @@ namespace ts.projectSystem {
}
export function checkProjectActualFiles(project: server.Project, expectedFiles: readonly string[]) {
checkArray(`${server.ProjectKind[project.projectKind]} project, actual files`, project.getFileNames(), expectedFiles);
checkArray(`${server.ProjectKind[project.projectKind]} project: ${project.getProjectName()}:: actual files`, project.getFileNames(), expectedFiles);
}
export function checkProjectRootFiles(project: server.Project, expectedFiles: readonly string[]) {
checkArray(`${server.ProjectKind[project.projectKind]} project, rootFileNames`, project.getRootFiles(), expectedFiles);
checkArray(`${server.ProjectKind[project.projectKind]} project: ${project.getProjectName()}::, rootFileNames`, project.getRootFiles(), expectedFiles);
}
export function mapCombinedPathsInAncestor(dir: string, path2: string, mapAncestor: (ancestor: string) => boolean) {

View File

@@ -1825,11 +1825,14 @@ bar();
});
describe("when default project is solution project", () => {
interface VerifySolutionScenario {
interface Setup {
solutionOptions?: CompilerOptions;
configRefs: string[];
additionalFiles: readonly File[];
additionalProjects: readonly { projectName: string, files: readonly string[] }[];
expectedOpenEvents: protocol.Event[];
}
interface VerifySolutionScenario extends Setup {
additionalProjects: readonly { projectName: string, files: readonly string[] }[];
expectedReloadEvents: protocol.Event[];
expectedReferences: protocol.ReferencesResponseBody;
expectedReferencesFromDtsProject: protocol.ReferencesResponseBody;
@@ -1877,11 +1880,8 @@ foo;`
};
const tsconfigSrcPath = `${tscWatch.projectRoot}/tsconfig-src.json`;
const tsconfigPath = `${tscWatch.projectRoot}/tsconfig.json`;
function verifySolutionScenario({
configRefs, additionalFiles, additionalProjects,
expectedOpenEvents, expectedReloadEvents,
expectedReferences, expectedReferencesFromDtsProject
}: VerifySolutionScenario) {
const dummyFilePath = "/dummy/dummy.ts";
function setup({ solutionOptions, configRefs, additionalFiles, expectedOpenEvents }: Setup) {
const tsconfigSrc: File = {
path: tsconfigSrcPath,
content: JSON.stringify({
@@ -1896,12 +1896,13 @@ foo;`
const tsconfig: File = {
path: tsconfigPath,
content: JSON.stringify({
... (solutionOptions ? { compilerOptions: solutionOptions } : {}),
references: configRefs.map(path => ({ path })),
files: []
})
};
const dummyFile: File = {
path: "/dummy/dummy.ts",
path: dummyFilePath,
content: "let a = 10;"
};
const host = createServerHost([
@@ -1913,8 +1914,17 @@ foo;`
const session = createSession(host, { canUseEvents: true });
const service = session.getProjectService();
service.openClientFile(main.path);
verifyProjects(/*includeConfigured*/ true, /*includeDummy*/ false);
checkEvents(session, expectedOpenEvents);
return { session, service, host, tsconfigSrc, tsconfig };
}
function verifySolutionScenario(input: VerifySolutionScenario) {
const { session, service, host, tsconfigSrc, tsconfig } = setup(input);
const {
additionalProjects, expectedReloadEvents,
expectedReferences, expectedReferencesFromDtsProject
} = input;
verifyProjects(/*includeConfigured*/ true, /*includeDummy*/ false);
const info = service.getScriptInfoForPath(main.path as Path)!;
const project = service.configuredProjects.get(tsconfigSrc.path)!;
assert.equal(info.getDefaultProject(), project);
@@ -1924,17 +1934,17 @@ foo;`
verifyGetErrRequestNoErrors({ session, host, files: [main] });
// Verify collection of script infos
service.openClientFile(dummyFile.path);
service.openClientFile(dummyFilePath);
verifyProjects(/*includeConfigured*/ true, /*includeDummy*/ true);
service.closeClientFile(main.path);
service.closeClientFile(dummyFile.path);
service.openClientFile(dummyFile.path);
service.closeClientFile(dummyFilePath);
service.openClientFile(dummyFilePath);
verifyProjects(/*includeConfigured*/ false, /*includeDummy*/ true);
service.openClientFile(main.path);
service.closeClientFile(dummyFile.path);
service.openClientFile(dummyFile.path);
service.closeClientFile(dummyFilePath);
service.openClientFile(dummyFilePath);
verifyProjects(/*includeConfigured*/ true, /*includeDummy*/ true);
// Verify Reload projects
@@ -1951,7 +1961,7 @@ foo;`
assert.deepEqual(response, expectedReferences);
service.closeClientFile(main.path);
service.closeClientFile(dummyFile.path);
service.closeClientFile(dummyFilePath);
// Verify when declaration map references the file
service.openClientFile(fileResolvingToMainDts.path);
@@ -1976,7 +1986,7 @@ foo;`
checkProjectActualFiles(service.configuredProjects.get(projectName)!, files));
}
if (includeDummy) {
checkProjectActualFiles(service.inferredProjects[0], [dummyFile.path, libFile.path]);
checkProjectActualFiles(service.inferredProjects[0], [dummyFilePath, libFile.path]);
}
}
}
@@ -2061,14 +2071,15 @@ foo;`
];
}
function getIndirectProject(postfix: string) {
function getIndirectProject(postfix: string, optionsToExtend?: CompilerOptions) {
const tsconfigIndirect: File = {
path: `${tscWatch.projectRoot}/tsconfig-indirect${postfix}.json`,
content: JSON.stringify({
compilerOptions: {
composite: true,
outDir: "./target/",
baseUrl: "./src/"
baseUrl: "./src/",
...optionsToExtend
},
files: [`./indirect${postfix}/main.ts`],
references: [{ path: "./tsconfig-src.json" }]
@@ -2081,6 +2092,66 @@ foo;`
return { tsconfigIndirect, indirect };
}
interface VerifyProjects {
configuredProjects: readonly { projectName: string, files: readonly string[] }[];
inferredProjects: readonly (readonly string[])[];
}
interface VerifyDisableReferencedProjectLoad extends Setup {
expectedProjectsOnOpen: VerifyProjects;
expectedProjectsOnDummyOpen?: VerifyProjects;
expectedProjectsOnReload?: VerifyProjects;
expectedDefaultProject: (service: server.ProjectService) => server.Project;
expectedDefaultConfiguredProject: (service: server.ProjectService) => server.ConfiguredProject | undefined;
expectedReloadEvents: protocol.Event[];
}
function verifyDisableReferencedProjectLoad(input: VerifyDisableReferencedProjectLoad) {
const { session, service } = setup(input);
const { expectedProjectsOnOpen, expectedDefaultProject, expectedDefaultConfiguredProject, expectedReloadEvents } = input;
const expectedProjectsOnOnlyDummy: VerifyProjects = {
configuredProjects: emptyArray,
inferredProjects: [
[dummyFilePath, libFile.path],
]
};
const expectedProjectsOnDummyOpen = input.expectedProjectsOnDummyOpen || {
configuredProjects: expectedProjectsOnOpen.configuredProjects,
inferredProjects: expectedProjectsOnOnlyDummy.inferredProjects,
};
const expectedProjectsOnReload = input.expectedProjectsOnReload || expectedProjectsOnDummyOpen;
verifyProjects(expectedProjectsOnOpen);
const info = service.getScriptInfoForPath(main.path as Path)!;
assert.equal(info.getDefaultProject(), expectedDefaultProject(service));
assert.equal(service.findDefaultConfiguredProject(info), expectedDefaultConfiguredProject(service));
// Verify collection of script infos
service.openClientFile(dummyFilePath);
verifyProjects(expectedProjectsOnDummyOpen);
service.closeClientFile(main.path);
service.closeClientFile(dummyFilePath);
service.openClientFile(dummyFilePath);
verifyProjects(expectedProjectsOnOnlyDummy);
service.openClientFile(main.path);
// Verify Reload projects
session.clearMessages();
service.reloadProjects();
checkEvents(session, expectedReloadEvents);
verifyProjects(expectedProjectsOnReload);
function verifyProjects(expected: VerifyProjects) {
checkNumberOfProjects(service, { configuredProjects: expected.configuredProjects.length, inferredProjects: expected.inferredProjects.length });
expected.configuredProjects.forEach(({ projectName, files }) =>
checkProjectActualFiles(service.configuredProjects.get(projectName)!, files));
expected.inferredProjects.forEach((files, index) =>
checkProjectActualFiles(service.inferredProjects[index], files));
}
}
it("when project is directly referenced by solution", () => {
const expectedReferences = expectedReferencesResponse();
verifySolutionScenario({
@@ -2150,6 +2221,105 @@ foo;`
}
});
});
it("disables looking into the child project if disableReferencedProjectLoad is set", () => {
const expectedProjectsOnOpen: VerifyProjects = {
configuredProjects: [
{ projectName: tsconfigPath, files: [tsconfigPath] },
],
inferredProjects: [
[main.path, libFile.path],
]
};
verifyDisableReferencedProjectLoad({
solutionOptions: { disableReferencedProjectLoad: true },
configRefs: ["./tsconfig-src.json"],
additionalFiles: emptyArray,
expectedOpenEvents: [
...expectedSolutionLoadAndTelemetry(),
configFileDiagEvent(main.path, tsconfigPath, [])
],
expectedDefaultProject: service => service.inferredProjects[0],
expectedDefaultConfiguredProject: returnUndefined,
expectedProjectsOnOpen,
expectedProjectsOnDummyOpen: {
configuredProjects: emptyArray,
inferredProjects: [
...expectedProjectsOnOpen.inferredProjects,
[dummyFilePath, libFile.path],
]
},
expectedProjectsOnReload: {
configuredProjects: expectedProjectsOnOpen.configuredProjects,
inferredProjects: [
[dummyFilePath, libFile.path],
...expectedProjectsOnOpen.inferredProjects,
]
},
expectedReloadEvents: expectedReloadEvent(tsconfigPath)
});
});
it("disables looking into the child project if disableReferencedProjectLoad is set in indirect project", () => {
const { tsconfigIndirect, indirect } = getIndirectProject("1", { disableReferencedProjectLoad: true });
const expectedProjectsOnOpen: VerifyProjects = {
configuredProjects: [
{ projectName: tsconfigPath, files: [tsconfigPath] },
{ projectName: tsconfigIndirect.path, files: [tsconfigIndirect.path, main.path, helper.path, indirect.path, libFile.path] },
],
inferredProjects: emptyArray
};
verifyDisableReferencedProjectLoad({
configRefs: ["./tsconfig-indirect1.json"],
additionalFiles: [tsconfigIndirect, indirect],
expectedOpenEvents: [
...expectedSolutionLoadAndTelemetry(),
...expectedProjectReferenceLoadAndTelemetry(tsconfigIndirect.path),
configFileDiagEvent(main.path, tsconfigIndirect.path, [])
],
expectedDefaultProject: service => service.configuredProjects.get(tsconfigIndirect.path)!,
expectedDefaultConfiguredProject: returnUndefined,
expectedProjectsOnOpen,
expectedReloadEvents: [
...expectedReloadEvent(tsconfigPath),
...expectedReloadEvent(tsconfigIndirect.path),
]
});
});
it("disables looking into the child project if disableReferencedProjectLoad is set in first indirect project but not in another one", () => {
const { tsconfigIndirect, indirect } = getIndirectProject("1", { disableReferencedProjectLoad: true });
const { tsconfigIndirect: tsconfigIndirect2, indirect: indirect2 } = getIndirectProject("2");
const expectedProjectsOnOpen: VerifyProjects = {
configuredProjects: [
{ projectName: tsconfigPath, files: [tsconfigPath] },
{ projectName: tsconfigIndirect.path, files: [tsconfigIndirect.path, main.path, helper.path, indirect.path, libFile.path] },
{ projectName: tsconfigIndirect2.path, files: [tsconfigIndirect2.path, main.path, helper.path, indirect2.path, libFile.path] },
{ projectName: tsconfigSrcPath, files: [tsconfigSrcPath, main.path, helper.path, libFile.path] },
],
inferredProjects: emptyArray
};
verifyDisableReferencedProjectLoad({
configRefs: ["./tsconfig-indirect1.json", "./tsconfig-indirect2.json"],
additionalFiles: [tsconfigIndirect, indirect, tsconfigIndirect2, indirect2],
expectedOpenEvents: [
...expectedSolutionLoadAndTelemetry(),
...expectedProjectReferenceLoadAndTelemetry(tsconfigIndirect.path),
...expectedProjectReferenceLoadAndTelemetry(tsconfigIndirect2.path),
...expectedProjectReferenceLoadAndTelemetry(tsconfigSrcPath),
configFileDiagEvent(main.path, tsconfigSrcPath, [])
],
expectedDefaultProject: service => service.configuredProjects.get(tsconfigSrcPath)!,
expectedDefaultConfiguredProject: service => service.configuredProjects.get(tsconfigSrcPath)!,
expectedProjectsOnOpen,
expectedReloadEvents: [
...expectedReloadEvent(tsconfigPath),
...expectedReloadEvent(tsconfigIndirect.path),
...expectedReloadEvent(tsconfigSrcPath),
...expectedReloadEvent(tsconfigIndirect2.path),
]
});
});
});
describe("auto import with referenced project", () => {