diff --git a/src/compiler/tsbuild.ts b/src/compiler/tsbuild.ts index 673f712689e..9e625041287 100644 --- a/src/compiler/tsbuild.ts +++ b/src/compiler/tsbuild.ts @@ -260,9 +260,15 @@ namespace ts { export interface SolutionBuilderWithWatchHost extends SolutionBuilderHostBase, WatchHost { } + export interface SolutionBuilderResult { + project: ResolvedConfigFileName; + result: T; + } + export interface SolutionBuilder { build(project?: string, cancellationToken?: CancellationToken): ExitStatus; clean(project?: string): ExitStatus; + buildNextProject(cancellationToken?: CancellationToken): SolutionBuilderResult | undefined; // Currently used for testing but can be made public if needed: /*@internal*/ getBuildOrder(): ReadonlyArray; @@ -401,6 +407,7 @@ namespace ts { return { build, clean, + buildNextProject, getBuildOrder, getUpToDateStatusOfProject, invalidateProject, @@ -1437,10 +1444,7 @@ namespace ts { return resolvedProject ? createBuildOrder([resolvedProject]) : getBuildOrder(); } - function build(project?: string, cancellationToken?: CancellationToken): ExitStatus { - const buildOrder = getBuildOrderFor(project); - if (!buildOrder) return ExitStatus.InvalidProject_OutputsSkipped; - + function setupInitialBuild(cancellationToken: CancellationToken | undefined) { // Set initial build if not already built if (allProjectBuildPending) { allProjectBuildPending = false; @@ -1455,6 +1459,27 @@ namespace ts { cancellationToken.throwIfCancellationRequested(); } } + } + + function buildNextProject(cancellationToken?: CancellationToken): SolutionBuilderResult | undefined { + setupInitialBuild(cancellationToken); + const invalidatedProject = getNextInvalidatedProject(getBuildOrder()); + if (!invalidatedProject) return undefined; + + buildInvalidatedProject(invalidatedProject, cancellationToken); + return { + project: invalidatedProject.project, + result: diagnostics.has(invalidatedProject.projectPath) ? + ExitStatus.DiagnosticsPresent_OutputsSkipped : + ExitStatus.Success + }; + } + + function build(project?: string, cancellationToken?: CancellationToken): ExitStatus { + const buildOrder = getBuildOrderFor(project); + if (!buildOrder) return ExitStatus.InvalidProject_OutputsSkipped; + + setupInitialBuild(cancellationToken); let successfulProjects = 0; let errorProjects = 0; diff --git a/src/testRunner/unittests/tsbuild/sample.ts b/src/testRunner/unittests/tsbuild/sample.ts index 65530e59705..7f9f2260d6d 100644 --- a/src/testRunner/unittests/tsbuild/sample.ts +++ b/src/testRunner/unittests/tsbuild/sample.ts @@ -2,9 +2,10 @@ namespace ts { describe("unittests:: tsbuild:: on 'sample1' project", () => { let projFs: vfs.FileSystem; const { time, tick } = getTime(); - const allExpectedOutputs = ["/src/tests/index.js", - "/src/core/index.js", "/src/core/index.d.ts", "/src/core/index.d.ts.map", - "/src/logic/index.js", "/src/logic/index.js.map", "/src/logic/index.d.ts"]; + const testsOutputs = ["/src/tests/index.js"]; + const logicOutputs = ["/src/logic/index.js", "/src/logic/index.js.map", "/src/logic/index.d.ts"]; + const coreOutputs = ["/src/core/index.js", "/src/core/index.d.ts", "/src/core/index.d.ts.map"]; + const allExpectedOutputs = [...testsOutputs, ...logicOutputs, ...coreOutputs]; before(() => { projFs = loadProjectFromDisk("tests/projects/sample1", time); @@ -140,8 +141,8 @@ namespace ts { builder.build(); const result = builder.clean("/src/logic"); host.assertDiagnosticMessages(/*empty*/); - verifyOutputsPresent(fs, [allExpectedOutputs[0]]); - verifyOutputsAbsent(fs, allExpectedOutputs.slice(1)); + verifyOutputsPresent(fs, testsOutputs); + verifyOutputsAbsent(fs, [...logicOutputs, ...coreOutputs]); assert.equal(result, ExitStatus.Success); }); @@ -334,8 +335,8 @@ namespace ts { const builder = createSolutionBuilder(host, ["/src/tests"], {}); const result = builder.build("/src/logic"); host.assertDiagnosticMessages(/*empty*/); - verifyOutputsAbsent(fs, [allExpectedOutputs[0]]); - verifyOutputsPresent(fs, allExpectedOutputs.slice(1)); + verifyOutputsAbsent(fs, testsOutputs); + verifyOutputsPresent(fs, [...logicOutputs, ...coreOutputs]); assert.equal(result, ExitStatus.Success); }); @@ -348,6 +349,39 @@ namespace ts { verifyOutputsAbsent(fs, allExpectedOutputs); assert.equal(result, ExitStatus.InvalidProject_OutputsSkipped); }); + + it("building using buildNextProject", () => { + const fs = projFs.shadow(); + const host = new fakes.SolutionBuilderHost(fs); + const builder = createSolutionBuilder(host, ["/src/tests"], {}); + verifyBuildNextResult({ + project: "/src/core/tsconfig.json" as ResolvedConfigFileName, + result: ExitStatus.Success + }, coreOutputs, [...logicOutputs, ...testsOutputs]); + + verifyBuildNextResult({ + project: "/src/logic/tsconfig.json" as ResolvedConfigFileName, + result: ExitStatus.Success + }, [...coreOutputs, ...logicOutputs], testsOutputs); + + verifyBuildNextResult({ + project: "/src/tests/tsconfig.json" as ResolvedConfigFileName, + result: ExitStatus.Success + }, allExpectedOutputs, emptyArray); + + verifyBuildNextResult(/*expected*/ undefined, allExpectedOutputs, emptyArray); + + function verifyBuildNextResult( + expected: SolutionBuilderResult | undefined, + presentOutputs: readonly string[], + absentOutputs: readonly string[] + ) { + const result = builder.buildNextProject(); + assert.deepEqual(result, expected); + verifyOutputsPresent(fs, presentOutputs); + verifyOutputsAbsent(fs, absentOutputs); + } + }); }); describe("downstream-blocked compilations", () => { diff --git a/tests/baselines/reference/api/tsserverlibrary.d.ts b/tests/baselines/reference/api/tsserverlibrary.d.ts index 7b45703dcfd..f13d0ab3f51 100644 --- a/tests/baselines/reference/api/tsserverlibrary.d.ts +++ b/tests/baselines/reference/api/tsserverlibrary.d.ts @@ -4594,9 +4594,14 @@ declare namespace ts { } interface SolutionBuilderWithWatchHost extends SolutionBuilderHostBase, WatchHost { } + interface SolutionBuilderResult { + project: ResolvedConfigFileName; + result: T; + } interface SolutionBuilder { build(project?: string, cancellationToken?: CancellationToken): ExitStatus; clean(project?: string): ExitStatus; + buildNextProject(cancellationToken?: CancellationToken): SolutionBuilderResult | undefined; } /** * Create a function that reports watch status by writing to the system and handles the formating of the diagnostic diff --git a/tests/baselines/reference/api/typescript.d.ts b/tests/baselines/reference/api/typescript.d.ts index acbe1b1ac0d..e5e5046794a 100644 --- a/tests/baselines/reference/api/typescript.d.ts +++ b/tests/baselines/reference/api/typescript.d.ts @@ -4594,9 +4594,14 @@ declare namespace ts { } interface SolutionBuilderWithWatchHost extends SolutionBuilderHostBase, WatchHost { } + interface SolutionBuilderResult { + project: ResolvedConfigFileName; + result: T; + } interface SolutionBuilder { build(project?: string, cancellationToken?: CancellationToken): ExitStatus; clean(project?: string): ExitStatus; + buildNextProject(cancellationToken?: CancellationToken): SolutionBuilderResult | undefined; } /** * Create a function that reports watch status by writing to the system and handles the formating of the diagnostic