Support loading of child project if found project is not pure solution has some of its own files and project references (#39613)

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

* Handle indirect references

* Support loading of child project if found project is not pure solution has some of its own files and project references
Fixes #38605

* Fix grammar
This commit is contained in:
Sheetal Nandi 2020-07-21 15:02:10 -07:00 committed by GitHub
parent e92afacc44
commit 34adaaf6aa
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23
3 changed files with 239 additions and 26 deletions

View File

@ -1775,11 +1775,9 @@ namespace ts.server {
const project = configFileName &&
this.findConfiguredProjectByProjectName(configFileName);
return project?.isSolution() ?
project.getDefaultChildProjectFromSolution(info) :
project && projectContainsInfoDirectly(project, info) ?
project :
undefined;
return project && projectContainsInfoDirectly(project, info) ?
project :
project?.getDefaultChildProjectFromProjectWithReferences(info);
}
/**
@ -2826,8 +2824,8 @@ namespace ts.server {
else {
// reload from the disk
this.reloadConfiguredProject(project, reason);
// If this is solution, reload the project till the reloaded project contains the script info directly
if (!project.containsScriptInfo(info) && project.isSolution()) {
// If this project does not contain this file directly, reload the project till the reloaded project contains the script info directly
if (!projectContainsInfoDirectly(project, info)) {
const referencedProject = forEachResolvedProjectReferenceProject(
project,
child => {
@ -2936,14 +2934,18 @@ namespace ts.server {
this.createAndLoadConfiguredProject(configFileName, `Creating project for original file: ${originalFileInfo.fileName}${location !== originalLocation ? " for location: " + location.fileName : ""}`);
updateProjectIfDirty(configuredProject);
if (configuredProject.isSolution()) {
const projectContainsOriginalInfo = (project: ConfiguredProject) => {
const info = this.getScriptInfo(fileName);
return info && projectContainsInfoDirectly(project, info);
};
if (configuredProject.isSolution() || !projectContainsOriginalInfo(configuredProject)) {
// Find the project that is referenced from this solution that contains the script info directly
configuredProject = forEachResolvedProjectReferenceProject(
configuredProject,
child => {
updateProjectIfDirty(child);
const info = this.getScriptInfo(fileName);
return info && projectContainsInfoDirectly(child, info) ? child : undefined;
return projectContainsOriginalInfo(child) ? child : undefined;
},
configuredProject.getCompilerOptions().disableReferencedProjectLoad ? ProjectReferenceProjectLoadKind.Find : ProjectReferenceProjectLoadKind.FindCreateLoad,
`Creating project referenced in solution ${configuredProject.projectName} to find possible configured project for original file: ${originalFileInfo.fileName}${location !== originalLocation ? " for location: " + location.fileName : ""}`
@ -3026,7 +3028,7 @@ namespace ts.server {
// If this configured project doesnt contain script info but
// it is solution with project references, try those project references
if (project.isSolution()) {
if (!projectContainsInfoDirectly(project, info)) {
forEachResolvedProjectReferenceProject(
project,
child => {
@ -3151,10 +3153,10 @@ namespace ts.server {
if (forEachPotentialProjectReference(
project,
potentialRefPath => forProjects!.has(potentialRefPath)
) || (project.isSolution() && forEachResolvedProjectReference(
) || forEachResolvedProjectReference(
project,
(_ref, resolvedPath) => forProjects!.has(resolvedPath)
))) {
)) {
// Load children
this.ensureProjectChildren(project, seenProjects);
}

View File

@ -2225,9 +2225,8 @@ namespace ts.server {
}
/* @internal */
/** Find the configured project from the project references in this solution which contains the info directly */
getDefaultChildProjectFromSolution(info: ScriptInfo) {
Debug.assert(this.isSolution());
/** Find the configured project from the project references in project which contains the info directly */
getDefaultChildProjectFromProjectWithReferences(info: ScriptInfo) {
return forEachResolvedProjectReferenceProject(
this,
child => projectContainsInfoDirectly(child, info) ?
@ -2257,8 +2256,6 @@ namespace ts.server {
return !!configFileExistenceInfo.openFilesImpactedByConfigFile.size;
}
const isSolution = this.isSolution();
// 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
@ -2266,13 +2263,12 @@ namespace ts.server {
configFileExistenceInfo.openFilesImpactedByConfigFile,
(_value, infoPath) => {
const info = this.projectService.getScriptInfoForPath(infoPath)!;
return isSolution ?
return this.containsScriptInfo(info) ||
!!forEachResolvedProjectReferenceProject(
this,
child => child.containsScriptInfo(info),
ProjectReferenceProjectLoadKind.Find
) :
this.containsScriptInfo(info);
);
}
) || false;
}

View File

@ -1827,11 +1827,13 @@ bar();
describe("when default project is solution project", () => {
interface Setup {
solutionOptions?: CompilerOptions;
solutionFiles?: string[];
configRefs: string[];
additionalFiles: readonly File[];
expectedOpenEvents: protocol.Event[];
}
interface VerifySolutionScenario extends Setup {
solutionProject?: readonly string[];
additionalProjects: readonly { projectName: string, files: readonly string[] }[];
expectedReloadEvents: protocol.Event[];
expectedReferences: protocol.ReferencesResponseBody;
@ -1876,12 +1878,13 @@ export { foo };
const fileResolvingToMainDts: File = {
path: `${tscWatch.projectRoot}/indirect3/main.ts`,
content: `import { foo } from 'main';
foo;`
foo;
export function bar() {}`
};
const tsconfigSrcPath = `${tscWatch.projectRoot}/tsconfig-src.json`;
const tsconfigPath = `${tscWatch.projectRoot}/tsconfig.json`;
const dummyFilePath = "/dummy/dummy.ts";
function setup({ solutionOptions, configRefs, additionalFiles, expectedOpenEvents }: Setup) {
function setup({ solutionFiles, solutionOptions, configRefs, additionalFiles, expectedOpenEvents }: Setup) {
const tsconfigSrc: File = {
path: tsconfigSrcPath,
content: JSON.stringify({
@ -1898,7 +1901,7 @@ foo;`
content: JSON.stringify({
... (solutionOptions ? { compilerOptions: solutionOptions } : {}),
references: configRefs.map(path => ({ path })),
files: []
files: solutionFiles || []
})
};
const dummyFile: File = {
@ -1921,7 +1924,7 @@ foo;`
function verifySolutionScenario(input: VerifySolutionScenario) {
const { session, service, host, tsconfigSrc, tsconfig } = setup(input);
const {
additionalProjects, expectedReloadEvents,
solutionProject, additionalProjects, expectedReloadEvents,
expectedReferences, expectedReferencesFromDtsProject
} = input;
verifyProjects(/*includeConfigured*/ true, /*includeDummy*/ false);
@ -1981,7 +1984,7 @@ foo;`
checkNumberOfProjects(service, { configuredProjects, inferredProjects });
if (includeConfigured) {
checkProjectActualFiles(service.configuredProjects.get(tsconfigSrc.path)!, [tsconfigSrc.path, main.path, helper.path, libFile.path]);
checkProjectActualFiles(service.configuredProjects.get(tsconfig.path)!, [tsconfig.path]);
checkProjectActualFiles(service.configuredProjects.get(tsconfig.path)!, solutionProject || [tsconfig.path]);
additionalProjects.forEach(({ projectName, files }) =>
checkProjectActualFiles(service.configuredProjects.get(projectName)!, files));
}
@ -2320,6 +2323,218 @@ foo;`
]
});
});
describe("when solution is project that contains its own files", () => {
it("when the project found is not solution but references open file through project reference", () => {
const ownMain: File = {
path: `${tscWatch.projectRoot}/own/main.ts`,
content: fileResolvingToMainDts.content
};
const { refs, ...rest } = expectedReferencesResponse();
verifySolutionScenario({
solutionFiles: [`./own/main.ts`],
solutionOptions: {
outDir: "./target/",
baseUrl: "./src/"
},
solutionProject: [tsconfigPath, ownMain.path, main.path, libFile.path, helper.path],
configRefs: ["./tsconfig-src.json"],
additionalFiles: [ownMain],
additionalProjects: emptyArray,
expectedOpenEvents: [
...expectedSolutionLoadAndTelemetry(),
...expectedProjectReferenceLoadAndTelemetry(tsconfigSrcPath),
configFileDiagEvent(main.path, tsconfigSrcPath, [])
],
expectedReloadEvents: [
...expectedReloadEvent(tsconfigPath),
...expectedReloadEvent(tsconfigSrcPath),
],
expectedReferences: {
refs: [
...refs,
...expectedIndirectRefs(ownMain),
],
...rest
},
expectedReferencesFromDtsProject: {
...rest,
refs: [
...expectedIndirectRefs(fileResolvingToMainDts),
...refs,
...expectedIndirectRefs(ownMain),
],
symbolDisplayString: "(alias) const foo: 1\nimport foo",
},
});
});
it("when project is indirectly referenced by solution", () => {
const ownMain: File = {
path: `${tscWatch.projectRoot}/own/main.ts`,
content: `import { bar } from 'main';
bar;`
};
const { tsconfigIndirect, indirect } = getIndirectProject("1");
const { tsconfigIndirect: tsconfigIndirect2, indirect: indirect2 } = getIndirectProject("2");
const { refs, ...rest } = expectedReferencesResponse();
verifySolutionScenario({
solutionFiles: [`./own/main.ts`],
solutionOptions: {
outDir: "./target/",
baseUrl: "./indirect1/"
},
solutionProject: [tsconfigPath, indirect.path, ownMain.path, main.path, libFile.path, helper.path],
configRefs: ["./tsconfig-indirect1.json", "./tsconfig-indirect2.json"],
additionalFiles: [tsconfigIndirect, indirect, tsconfigIndirect2, indirect2, ownMain],
additionalProjects: [{
projectName: tsconfigIndirect.path,
files: [tsconfigIndirect.path, main.path, helper.path, indirect.path, libFile.path]
}],
expectedOpenEvents: [
...expectedSolutionLoadAndTelemetry(),
...expectedProjectReferenceLoadAndTelemetry(tsconfigIndirect.path),
...expectedProjectReferenceLoadAndTelemetry(tsconfigSrcPath),
configFileDiagEvent(main.path, tsconfigSrcPath, [])
],
expectedReloadEvents: [
...expectedReloadEvent(tsconfigPath),
...expectedReloadEvent(tsconfigIndirect.path),
...expectedReloadEvent(tsconfigSrcPath),
],
expectedReferences: {
refs: [
...refs,
...expectedIndirectRefs(indirect),
...expectedIndirectRefs(indirect2),
],
...rest
},
expectedReferencesFromDtsProject: {
...rest,
refs: [
...expectedIndirectRefs(fileResolvingToMainDts),
...refs,
...expectedIndirectRefs(indirect2),
...expectedIndirectRefs(indirect),
],
symbolDisplayString: "(alias) const foo: 1\nimport foo",
}
});
});
it("disables looking into the child project if disableReferencedProjectLoad is set", () => {
const ownMain: File = {
path: `${tscWatch.projectRoot}/own/main.ts`,
content: fileResolvingToMainDts.content
};
const expectedProjectsOnOpen: VerifyProjects = {
configuredProjects: [
{ projectName: tsconfigPath, files: [tsconfigPath, ownMain.path, main.path, libFile.path, helper.path] },
],
inferredProjects: emptyArray
};
verifyDisableReferencedProjectLoad({
solutionFiles: [`./own/main.ts`],
solutionOptions: {
outDir: "./target/",
baseUrl: "./src/",
disableReferencedProjectLoad: true
},
configRefs: ["./tsconfig-src.json"],
additionalFiles: [ownMain],
expectedOpenEvents: [
...expectedSolutionLoadAndTelemetry(),
configFileDiagEvent(main.path, tsconfigPath, [])
],
expectedDefaultProject: service => service.configuredProjects.get(tsconfigPath)!,
expectedDefaultConfiguredProject: returnUndefined,
expectedProjectsOnOpen,
expectedReloadEvents: expectedReloadEvent(tsconfigPath)
});
});
it("disables looking into the child project if disableReferencedProjectLoad is set in indirect project", () => {
const ownMain: File = {
path: `${tscWatch.projectRoot}/own/main.ts`,
content: `import { bar } from 'main';
bar;`
};
const { tsconfigIndirect, indirect } = getIndirectProject("1", { disableReferencedProjectLoad: true });
const expectedProjectsOnOpen: VerifyProjects = {
configuredProjects: [
{ projectName: tsconfigPath, files: [tsconfigPath, indirect.path, ownMain.path, main.path, libFile.path, helper.path] },
{ projectName: tsconfigIndirect.path, files: [tsconfigIndirect.path, main.path, helper.path, indirect.path, libFile.path] },
],
inferredProjects: emptyArray
};
verifyDisableReferencedProjectLoad({
solutionFiles: [`./own/main.ts`],
solutionOptions: {
outDir: "./target/",
baseUrl: "./indirect1/",
},
configRefs: ["./tsconfig-indirect1.json"],
additionalFiles: [tsconfigIndirect, indirect, ownMain],
expectedOpenEvents: [
...expectedSolutionLoadAndTelemetry(),
...expectedProjectReferenceLoadAndTelemetry(tsconfigIndirect.path),
configFileDiagEvent(main.path, tsconfigPath, [])
],
expectedDefaultProject: service => service.configuredProjects.get(tsconfigPath)!,
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 ownMain: File = {
path: `${tscWatch.projectRoot}/own/main.ts`,
content: `import { bar } from 'main';
bar;`
};
const { tsconfigIndirect, indirect } = getIndirectProject("1", { disableReferencedProjectLoad: true });
const { tsconfigIndirect: tsconfigIndirect2, indirect: indirect2 } = getIndirectProject("2");
const expectedProjectsOnOpen: VerifyProjects = {
configuredProjects: [
{ projectName: tsconfigPath, files: [tsconfigPath, indirect.path, ownMain.path, main.path, libFile.path, helper.path] },
{ 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({
solutionFiles: [`./own/main.ts`],
solutionOptions: {
outDir: "./target/",
baseUrl: "./indirect1/",
},
configRefs: ["./tsconfig-indirect1.json", "./tsconfig-indirect2.json"],
additionalFiles: [tsconfigIndirect, indirect, tsconfigIndirect2, indirect2, ownMain],
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", () => {