mirror of
https://github.com/microsoft/TypeScript.git
synced 2026-02-05 16:38:05 -06:00
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:
parent
e92afacc44
commit
34adaaf6aa
@ -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);
|
||||
}
|
||||
|
||||
@ -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;
|
||||
}
|
||||
|
||||
@ -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", () => {
|
||||
|
||||
Loading…
x
Reference in New Issue
Block a user