Skip to content

Commit 97fcea1

Browse files
committed
Api to get next invalidated project
1 parent 8c489bf commit 97fcea1

File tree

5 files changed

+164
-71
lines changed

5 files changed

+164
-71
lines changed

src/compiler/tsbuild.ts

Lines changed: 67 additions & 50 deletions
Original file line numberDiff line numberDiff line change
@@ -264,15 +264,10 @@ namespace ts {
264264
export interface SolutionBuilderWithWatchHost<T extends BuilderProgram> extends SolutionBuilderHostBase<T>, WatchHost {
265265
}
266266

267-
export interface SolutionBuilderResult<T> {
268-
project: ResolvedConfigFileName;
269-
result: T;
270-
}
271-
272-
export interface SolutionBuilder {
267+
export interface SolutionBuilder<T extends BuilderProgram> {
273268
build(project?: string, cancellationToken?: CancellationToken): ExitStatus;
274269
clean(project?: string): ExitStatus;
275-
buildNextProject(cancellationToken?: CancellationToken): SolutionBuilderResult<ExitStatus> | undefined;
270+
getNextInvalidatedProject(cancellationToken?: CancellationToken): InvalidatedProject<T> | undefined;
276271

277272
// Currently used for testing but can be made public if needed:
278273
/*@internal*/ getBuildOrder(): ReadonlyArray<ResolvedConfigFileName>;
@@ -325,11 +320,11 @@ namespace ts {
325320
return result;
326321
}
327322

328-
export function createSolutionBuilder<T extends BuilderProgram>(host: SolutionBuilderHost<T>, rootNames: ReadonlyArray<string>, defaultOptions: BuildOptions): SolutionBuilder {
323+
export function createSolutionBuilder<T extends BuilderProgram>(host: SolutionBuilderHost<T>, rootNames: ReadonlyArray<string>, defaultOptions: BuildOptions): SolutionBuilder<T> {
329324
return createSolutionBuilderWorker(/*watch*/ false, host, rootNames, defaultOptions);
330325
}
331326

332-
export function createSolutionBuilderWithWatch<T extends BuilderProgram>(host: SolutionBuilderWithWatchHost<T>, rootNames: ReadonlyArray<string>, defaultOptions: BuildOptions): SolutionBuilder {
327+
export function createSolutionBuilderWithWatch<T extends BuilderProgram>(host: SolutionBuilderWithWatchHost<T>, rootNames: ReadonlyArray<string>, defaultOptions: BuildOptions): SolutionBuilder<T> {
333328
return createSolutionBuilderWorker(/*watch*/ true, host, rootNames, defaultOptions);
334329
}
335330

@@ -380,6 +375,7 @@ namespace ts {
380375
allProjectBuildPending: boolean;
381376
needsSummary: boolean;
382377
watchAllProjectsPending: boolean;
378+
currentInvalidatedProject: InvalidatedProject<T> | undefined;
383379

384380
// Watch state
385381
readonly watch: boolean;
@@ -452,6 +448,7 @@ namespace ts {
452448
allProjectBuildPending: true,
453449
needsSummary: true,
454450
watchAllProjectsPending: watch,
451+
currentInvalidatedProject: undefined,
455452

456453
// Watch state
457454
watch,
@@ -654,36 +651,38 @@ namespace ts {
654651
}
655652
}
656653

657-
const enum InvalidatedProjectKind {
654+
export enum InvalidatedProjectKind {
658655
Build,
659656
UpdateBundle,
660657
UpdateOutputFileStamps
661658
}
662659

663-
interface InvalidatedProjectBase {
660+
export interface InvalidatedProjectBase {
664661
readonly kind: InvalidatedProjectKind;
665662
readonly project: ResolvedConfigFileName;
666-
readonly projectPath: ResolvedConfigFilePath;
663+
/*@internal*/ readonly projectPath: ResolvedConfigFilePath;
664+
/*@internal*/ readonly buildOrder: readonly ResolvedConfigFileName[];
667665
/**
668666
* To dispose this project and ensure that all the necessary actions are taken and state is updated accordingly
669667
*/
670-
done(cancellationToken?: CancellationToken): void;
668+
done(cancellationToken?: CancellationToken): ExitStatus;
669+
getCompilerOptions(): CompilerOptions;
670+
getCurrentDirectory(): string;
671671
}
672672

673-
interface UpdateOutputFileStampsProject extends InvalidatedProjectBase {
673+
export interface UpdateOutputFileStampsProject extends InvalidatedProjectBase {
674674
readonly kind: InvalidatedProjectKind.UpdateOutputFileStamps;
675675
updateOutputFileStatmps(): void;
676676
}
677677

678-
interface BuildInvalidedProject<T extends BuilderProgram = BuilderProgram> extends InvalidatedProjectBase {
678+
export interface BuildInvalidedProject<T extends BuilderProgram> extends InvalidatedProjectBase {
679679
readonly kind: InvalidatedProjectKind.Build;
680680
/*
681681
* Emitting with this builder program without the api provided for this project
682682
* can result in build system going into invalid state as files written reflect the state of the project
683683
*/
684684
getBuilderProgram(): T | undefined;
685685
getProgram(): Program | undefined;
686-
getCompilerOptions(): CompilerOptions;
687686
getSourceFile(fileName: string): SourceFile | undefined;
688687
getSourceFiles(): ReadonlyArray<SourceFile>;
689688
getOptionsDiagnostics(cancellationToken?: CancellationToken): ReadonlyArray<Diagnostic>;
@@ -704,22 +703,41 @@ namespace ts {
704703
emit(targetSourceFile?: SourceFile, writeFile?: WriteFileCallback, cancellationToken?: CancellationToken, emitOnlyDtsFiles?: boolean, customTransformers?: CustomTransformers): EmitResult | undefined;
705704
// TODO(shkamat):: investigate later if we can emit even when there are declaration diagnostics
706705
// emitNextAffectedFile(writeFile?: WriteFileCallback, cancellationToken?: CancellationToken, customTransformers?: CustomTransformers): AffectedFileResult<EmitResult>;
707-
getCurrentDirectory(): string;
708706
}
709707

710-
interface UpdateBundleProject<T extends BuilderProgram = BuilderProgram> extends InvalidatedProjectBase {
708+
export interface UpdateBundleProject<T extends BuilderProgram> extends InvalidatedProjectBase {
711709
readonly kind: InvalidatedProjectKind.UpdateBundle;
712710
emit(writeFile?: WriteFileCallback, customTransformers?: CustomTransformers): EmitResult | BuildInvalidedProject<T> | undefined;
713711
}
714712

715-
type InvalidatedProject<T extends BuilderProgram = BuilderProgram> = UpdateOutputFileStampsProject | BuildInvalidedProject<T> | UpdateBundleProject<T>;
713+
export type InvalidatedProject<T extends BuilderProgram> = UpdateOutputFileStampsProject | BuildInvalidedProject<T> | UpdateBundleProject<T>;
716714

717-
function createUpdateOutputFileStampsProject(state: SolutionBuilderState, project: ResolvedConfigFileName, projectPath: ResolvedConfigFilePath, config: ParsedCommandLine): UpdateOutputFileStampsProject {
715+
function doneInvalidatedProject(
716+
state: SolutionBuilderState,
717+
projectPath: ResolvedConfigFilePath
718+
) {
719+
state.projectPendingBuild.delete(projectPath);
720+
state.currentInvalidatedProject = undefined;
721+
return state.diagnostics.has(projectPath) ?
722+
ExitStatus.DiagnosticsPresent_OutputsSkipped :
723+
ExitStatus.Success;
724+
}
725+
726+
function createUpdateOutputFileStampsProject(
727+
state: SolutionBuilderState,
728+
project: ResolvedConfigFileName,
729+
projectPath: ResolvedConfigFilePath,
730+
config: ParsedCommandLine,
731+
buildOrder: readonly ResolvedConfigFileName[]
732+
): UpdateOutputFileStampsProject {
718733
let updateOutputFileStampsPending = true;
719734
return {
720735
kind: InvalidatedProjectKind.UpdateOutputFileStamps,
721736
project,
722737
projectPath,
738+
buildOrder,
739+
getCompilerOptions: () => config.options,
740+
getCurrentDirectory: () => state.currentDirectory,
723741
updateOutputFileStatmps: () => {
724742
updateOutputTimestamps(state, config, projectPath);
725743
updateOutputFileStampsPending = false;
@@ -728,7 +746,7 @@ namespace ts {
728746
if (updateOutputFileStampsPending) {
729747
updateOutputTimestamps(state, config, projectPath);
730748
}
731-
state.projectPendingBuild.delete(projectPath);
749+
return doneInvalidatedProject(state, projectPath);
732750
}
733751
};
734752
}
@@ -763,12 +781,14 @@ namespace ts {
763781
kind,
764782
project,
765783
projectPath,
784+
buildOrder,
785+
getCompilerOptions: () => config.options,
786+
getCurrentDirectory: () => state.currentDirectory,
766787
getBuilderProgram: () => withProgramOrUndefined(identity),
767788
getProgram: () =>
768789
withProgramOrUndefined(
769790
program => program.getProgramOrUndefined()
770791
),
771-
getCompilerOptions: () => config.options,
772792
getSourceFile: fileName =>
773793
withProgramOrUndefined(
774794
program => program.getSourceFile(fileName)
@@ -817,26 +837,27 @@ namespace ts {
817837
if (step !== Step.Emit) return undefined;
818838
return emit(writeFile, cancellationToken, customTransformers);
819839
},
820-
getCurrentDirectory: () => state.currentDirectory,
821-
done: cancellationToken => {
822-
executeSteps(Step.Done, cancellationToken);
823-
state.projectPendingBuild.delete(projectPath);
824-
}
840+
done
825841
} :
826842
{
827843
kind,
828844
project,
829845
projectPath,
846+
buildOrder,
847+
getCompilerOptions: () => config.options,
848+
getCurrentDirectory: () => state.currentDirectory,
830849
emit: (writeFile: WriteFileCallback | undefined, customTransformers: CustomTransformers | undefined) => {
831850
if (step !== Step.EmitBundle) return invalidatedProjectOfBundle;
832851
return emitBundle(writeFile, customTransformers);
833852
},
834-
done: cancellationToken => {
835-
executeSteps(Step.Done, cancellationToken);
836-
state.projectPendingBuild.delete(projectPath);
837-
}
853+
done,
838854
};
839855

856+
function done(cancellationToken?: CancellationToken) {
857+
executeSteps(Step.Done, cancellationToken);
858+
return doneInvalidatedProject(state, projectPath);
859+
}
860+
840861
function withProgramOrUndefined<U>(action: (program: T) => U | undefined): U | undefined {
841862
executeSteps(Step.CreateProgram);
842863
return program && action(program);
@@ -1152,6 +1173,12 @@ namespace ts {
11521173

11531174
function getNextInvalidatedProject<T extends BuilderProgram>(state: SolutionBuilderState<T>, buildOrder: readonly ResolvedConfigFileName[]): InvalidatedProject<T> | undefined {
11541175
if (!state.projectPendingBuild.size) return undefined;
1176+
if (state.currentInvalidatedProject) {
1177+
// Only if same buildOrder the currentInvalidated project can be sent again
1178+
return arrayIsEqualTo(state.currentInvalidatedProject.buildOrder, buildOrder) ?
1179+
state.currentInvalidatedProject :
1180+
undefined;
1181+
}
11551182

11561183
const { options, projectPendingBuild } = state;
11571184
for (let projectIndex = 0; projectIndex < buildOrder.length; projectIndex++) {
@@ -1200,7 +1227,8 @@ namespace ts {
12001227
state,
12011228
project,
12021229
projectPath,
1203-
config
1230+
config,
1231+
buildOrder
12041232
);
12051233
}
12061234
}
@@ -1635,20 +1663,6 @@ namespace ts {
16351663
}
16361664
}
16371665

1638-
function buildNextProject(state: SolutionBuilderState, cancellationToken?: CancellationToken): SolutionBuilderResult<ExitStatus> | undefined {
1639-
setupInitialBuild(state, cancellationToken);
1640-
const invalidatedProject = getNextInvalidatedProject(state, getBuildOrder(state));
1641-
if (!invalidatedProject) return undefined;
1642-
1643-
invalidatedProject.done(cancellationToken);
1644-
return {
1645-
project: invalidatedProject.project,
1646-
result: state.diagnostics.has(invalidatedProject.projectPath) ?
1647-
ExitStatus.DiagnosticsPresent_OutputsSkipped :
1648-
ExitStatus.Success
1649-
};
1650-
}
1651-
16521666
function build(state: SolutionBuilderState, project?: string, cancellationToken?: CancellationToken): ExitStatus {
16531667
const buildOrder = getBuildOrderFor(state, project);
16541668
if (!buildOrder) return ExitStatus.InvalidProject_OutputsSkipped;
@@ -1883,14 +1897,17 @@ namespace ts {
18831897
* A SolutionBuilder has an immutable set of rootNames that are the "entry point" projects, but
18841898
* can dynamically add/remove other projects based on changes on the rootNames' references
18851899
*/
1886-
function createSolutionBuilderWorker<T extends BuilderProgram>(watch: false, host: SolutionBuilderHost<T>, rootNames: ReadonlyArray<string>, defaultOptions: BuildOptions): SolutionBuilder;
1887-
function createSolutionBuilderWorker<T extends BuilderProgram>(watch: true, host: SolutionBuilderWithWatchHost<T>, rootNames: ReadonlyArray<string>, defaultOptions: BuildOptions): SolutionBuilder;
1888-
function createSolutionBuilderWorker<T extends BuilderProgram>(watch: boolean, hostOrHostWithWatch: SolutionBuilderHost<T> | SolutionBuilderWithWatchHost<T>, rootNames: ReadonlyArray<string>, options: BuildOptions): SolutionBuilder {
1900+
function createSolutionBuilderWorker<T extends BuilderProgram>(watch: false, host: SolutionBuilderHost<T>, rootNames: ReadonlyArray<string>, defaultOptions: BuildOptions): SolutionBuilder<T>;
1901+
function createSolutionBuilderWorker<T extends BuilderProgram>(watch: true, host: SolutionBuilderWithWatchHost<T>, rootNames: ReadonlyArray<string>, defaultOptions: BuildOptions): SolutionBuilder<T>;
1902+
function createSolutionBuilderWorker<T extends BuilderProgram>(watch: boolean, hostOrHostWithWatch: SolutionBuilderHost<T> | SolutionBuilderWithWatchHost<T>, rootNames: ReadonlyArray<string>, options: BuildOptions): SolutionBuilder<T> {
18891903
const state = createSolutionBuilderState(watch, hostOrHostWithWatch, rootNames, options);
18901904
return {
18911905
build: (project, cancellationToken) => build(state, project, cancellationToken),
18921906
clean: project => clean(state, project),
1893-
buildNextProject: cancellationToken => buildNextProject(state, cancellationToken),
1907+
getNextInvalidatedProject: cancellationToken => {
1908+
setupInitialBuild(state, cancellationToken);
1909+
return getNextInvalidatedProject(state, getBuildOrder(state));
1910+
},
18941911
getBuildOrder: () => getBuildOrder(state),
18951912
getUpToDateStatusOfProject: project => {
18961913
const configFileName = resolveProjectName(state, project);

src/testRunner/unittests/tsbuild/sample.ts

Lines changed: 9 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -350,7 +350,12 @@ namespace ts {
350350
assert.equal(result, ExitStatus.InvalidProject_OutputsSkipped);
351351
});
352352

353-
it("building using buildNextProject", () => {
353+
it("building using getNextInvalidatedProject", () => {
354+
interface SolutionBuilderResult<T> {
355+
project: ResolvedConfigFileName;
356+
result: T;
357+
}
358+
354359
const fs = projFs.shadow();
355360
const host = new fakes.SolutionBuilderHost(fs);
356361
const builder = createSolutionBuilder(host, ["/src/tests"], {});
@@ -376,8 +381,9 @@ namespace ts {
376381
presentOutputs: readonly string[],
377382
absentOutputs: readonly string[]
378383
) {
379-
const result = builder.buildNextProject();
380-
assert.deepEqual(result, expected);
384+
const project = builder.getNextInvalidatedProject();
385+
const result = project && project.done();
386+
assert.deepEqual(project && { project: project.project, result }, expected);
381387
verifyOutputsPresent(fs, presentOutputs);
382388
verifyOutputsAbsent(fs, absentOutputs);
383389
}

src/testRunner/unittests/tsbuildWatchMode.ts

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -676,7 +676,7 @@ let x: string = 10;`);
676676
}
677677

678678
function verifyScenario(
679-
edit: (host: TsBuildWatchSystem, solutionBuilder: SolutionBuilder) => void,
679+
edit: (host: TsBuildWatchSystem, solutionBuilder: SolutionBuilder<EmitAndSemanticDiagnosticsBuilderProgram>) => void,
680680
expectedFilesAfterEdit: ReadonlyArray<string>
681681
) {
682682
it("with tsc-watch", () => {
@@ -899,7 +899,7 @@ export function gfoo() {
899899
}
900900

901901
function verifyScenario(
902-
edit: (host: TsBuildWatchSystem, solutionBuilder: SolutionBuilder) => void,
902+
edit: (host: TsBuildWatchSystem, solutionBuilder: SolutionBuilder<EmitAndSemanticDiagnosticsBuilderProgram>) => void,
903903
expectedEditErrors: ReadonlyArray<string>,
904904
expectedProgramFiles: ReadonlyArray<string>,
905905
expectedWatchedFiles: ReadonlyArray<string>,

0 commit comments

Comments
 (0)