diff --git a/src/compiler/tsbuild.ts b/src/compiler/tsbuild.ts index 43b1e24a573..8a43dd446f2 100644 --- a/src/compiler/tsbuild.ts +++ b/src/compiler/tsbuild.ts @@ -275,6 +275,8 @@ namespace ts { export interface SolutionBuilder { build(project?: string, cancellationToken?: CancellationToken): ExitStatus; clean(project?: string): ExitStatus; + buildReferences(project: string, cancellationToken?: CancellationToken): ExitStatus; + cleanReferences(project?: string): ExitStatus; getNextInvalidatedProject(cancellationToken?: CancellationToken): InvalidatedProject | undefined; // Currently used for testing but can be made public if needed: @@ -565,7 +567,7 @@ namespace ts { (state.buildOrder = createBuildOrder(state, state.rootNames.map(f => resolveProjectName(state, f)))); } - function getBuildOrderFor(state: SolutionBuilderState, project: string | undefined) { + function getBuildOrderFor(state: SolutionBuilderState, project: string | undefined, onlyReferences: boolean | undefined) { const resolvedProject = project && resolveProjectName(state, project); if (resolvedProject) { const projectPath = toResolvedConfigFilePath(state, resolvedProject); @@ -575,7 +577,10 @@ namespace ts { ); if (projectIndex === -1) return undefined; } - return resolvedProject ? createBuildOrder(state, [resolvedProject]) : getBuildOrder(state); + const buildOrder = resolvedProject ? createBuildOrder(state, [resolvedProject]) : getBuildOrder(state); + Debug.assert(!onlyReferences || resolvedProject !== undefined); + Debug.assert(!onlyReferences || buildOrder[buildOrder.length - 1] === resolvedProject); + return onlyReferences ? buildOrder.slice(0, buildOrder.length - 1) : buildOrder; } function enableCache(state: SolutionBuilderState) { @@ -653,7 +658,6 @@ namespace ts { if (state.options.watch) { reportWatchStatus(state, Diagnostics.Starting_compilation_in_watch_mode); } enableCache(state); const buildOrder = getBuildOrder(state); - reportBuildQueue(state, buildOrder); buildOrder.forEach(configFileName => state.projectPendingBuild.set( toResolvedConfigFilePath(state, configFileName), @@ -1186,7 +1190,11 @@ namespace ts { !isIncrementalCompilation(config.options); } - function getNextInvalidatedProject(state: SolutionBuilderState, buildOrder: readonly ResolvedConfigFileName[]): InvalidatedProject | undefined { + function getNextInvalidatedProject( + state: SolutionBuilderState, + buildOrder: readonly ResolvedConfigFileName[], + reportQueue: boolean + ): InvalidatedProject | undefined { if (!state.projectPendingBuild.size) return undefined; if (state.currentInvalidatedProject) { // Only if same buildOrder the currentInvalidated project can be sent again @@ -1202,6 +1210,11 @@ namespace ts { const reloadLevel = state.projectPendingBuild.get(projectPath); if (reloadLevel === undefined) continue; + if (reportQueue) { + reportQueue = false; + reportBuildQueue(state, buildOrder); + } + const config = parseConfigFile(state, project, projectPath); if (!config) { reportParseConfigFileDiagnostic(state, projectPath); @@ -1678,17 +1691,19 @@ namespace ts { } } - function build(state: SolutionBuilderState, project?: string, cancellationToken?: CancellationToken): ExitStatus { - const buildOrder = getBuildOrderFor(state, project); + function build(state: SolutionBuilderState, project?: string, cancellationToken?: CancellationToken, onlyReferences?: boolean): ExitStatus { + const buildOrder = getBuildOrderFor(state, project, onlyReferences); if (!buildOrder) return ExitStatus.InvalidProject_OutputsSkipped; setupInitialBuild(state, cancellationToken); + let reportQueue = true; let successfulProjects = 0; let errorProjects = 0; while (true) { - const invalidatedProject = getNextInvalidatedProject(state, buildOrder); + const invalidatedProject = getNextInvalidatedProject(state, buildOrder, reportQueue); if (!invalidatedProject) break; + reportQueue = false; invalidatedProject.done(cancellationToken); if (state.diagnostics.has(invalidatedProject.projectPath)) { errorProjects++; @@ -1709,8 +1724,8 @@ namespace ts { ExitStatus.Success; } - function clean(state: SolutionBuilderState, project?: string) { - const buildOrder = getBuildOrderFor(state, project); + function clean(state: SolutionBuilderState, project?: string, onlyReferences?: boolean) { + const buildOrder = getBuildOrderFor(state, project, onlyReferences); if (!buildOrder) return ExitStatus.InvalidProject_OutputsSkipped; const { options, host } = state; @@ -1783,7 +1798,7 @@ namespace ts { state.projectErrorsReported.clear(); reportWatchStatus(state, Diagnostics.File_change_detected_Starting_incremental_compilation); } - const invalidatedProject = getNextInvalidatedProject(state, getBuildOrder(state)); + const invalidatedProject = getNextInvalidatedProject(state, getBuildOrder(state), /*reportQueue*/ false); if (invalidatedProject) { invalidatedProject.done(); if (state.projectPendingBuild.size) { @@ -1923,9 +1938,11 @@ namespace ts { return { build: (project, cancellationToken) => build(state, project, cancellationToken), clean: project => clean(state, project), + buildReferences: (project, cancellationToken) => build(state, project, cancellationToken, /*onlyReferences*/ true), + cleanReferences: project => clean(state, project, /*onlyReferences*/ true), getNextInvalidatedProject: cancellationToken => { setupInitialBuild(state, cancellationToken); - return getNextInvalidatedProject(state, getBuildOrder(state)); + return getNextInvalidatedProject(state, getBuildOrder(state), /*reportQueue*/ false); }, getBuildOrder: () => getBuildOrder(state), getUpToDateStatusOfProject: project => { diff --git a/src/testRunner/unittests/tsbuild/sample.ts b/src/testRunner/unittests/tsbuild/sample.ts index 25ad761c8f1..df2fb62db25 100644 --- a/src/testRunner/unittests/tsbuild/sample.ts +++ b/src/testRunner/unittests/tsbuild/sample.ts @@ -417,6 +417,22 @@ namespace ts { verifyOutputsAbsent(fs, absentOutputs); } }); + + it("building using buildReferencedProject", () => { + const fs = projFs.shadow(); + const host = new fakes.SolutionBuilderHost(fs); + const builder = createSolutionBuilder(host, ["/src/tests"], { verbose: true }); + builder.buildReferences("/src/tests"); + host.assertDiagnosticMessages( + getExpectedDiagnosticForProjectsInBuild("src/core/tsconfig.json", "src/logic/tsconfig.json"), + [Diagnostics.Project_0_is_out_of_date_because_output_file_1_does_not_exist, "src/core/tsconfig.json", "src/core/anotherModule.js"], + [Diagnostics.Building_project_0, "/src/core/tsconfig.json"], + [Diagnostics.Project_0_is_out_of_date_because_output_file_1_does_not_exist, "src/logic/tsconfig.json", "src/logic/index.js"], + [Diagnostics.Building_project_0, "/src/logic/tsconfig.json"], + ); + verifyOutputsPresent(fs, [...coreOutputs, ...logicOutputs]); + verifyOutputsAbsent(fs, testsOutputs); + }); }); describe("downstream-blocked compilations", () => { diff --git a/tests/baselines/reference/api/tsserverlibrary.d.ts b/tests/baselines/reference/api/tsserverlibrary.d.ts index 05f8195b992..8e78922e636 100644 --- a/tests/baselines/reference/api/tsserverlibrary.d.ts +++ b/tests/baselines/reference/api/tsserverlibrary.d.ts @@ -4610,6 +4610,8 @@ declare namespace ts { interface SolutionBuilder { build(project?: string, cancellationToken?: CancellationToken): ExitStatus; clean(project?: string): ExitStatus; + buildReferences(project: string, cancellationToken?: CancellationToken): ExitStatus; + cleanReferences(project?: string): ExitStatus; getNextInvalidatedProject(cancellationToken?: CancellationToken): InvalidatedProject | undefined; } /** diff --git a/tests/baselines/reference/api/typescript.d.ts b/tests/baselines/reference/api/typescript.d.ts index 86a37c9f498..3d0df0fabec 100644 --- a/tests/baselines/reference/api/typescript.d.ts +++ b/tests/baselines/reference/api/typescript.d.ts @@ -4610,6 +4610,8 @@ declare namespace ts { interface SolutionBuilder { build(project?: string, cancellationToken?: CancellationToken): ExitStatus; clean(project?: string): ExitStatus; + buildReferences(project: string, cancellationToken?: CancellationToken): ExitStatus; + cleanReferences(project?: string): ExitStatus; getNextInvalidatedProject(cancellationToken?: CancellationToken): InvalidatedProject | undefined; } /**