Use emit builder to emit only changed files.

This commit is contained in:
Sheetal Nandi 2018-12-19 17:08:36 -08:00
parent 47f51060e9
commit f1949bbae8
3 changed files with 78 additions and 17 deletions

View File

@ -54,6 +54,18 @@ namespace ts {
* compilerOptions for the program
*/
compilerOptions: CompilerOptions;
/**
* Files pending to be emitted
*/
affectedFilesPendingEmit: ReadonlyArray<Path> | undefined;
/**
* Current index to retrieve pending affected file
*/
affectedFilesPendingEmitIndex: number | undefined;
/**
* Already seen affected files
*/
seenEmittedFiles: Map<true> | undefined;
}
function hasSameKeys<T, U>(map1: ReadonlyMap<T> | undefined, map2: ReadonlyMap<U> | undefined): boolean {
@ -89,6 +101,10 @@ namespace ts {
// Copy old state's changed files set
copyEntries(oldState!.changedFilesSet, state.changedFilesSet);
if (!compilerOptions.outFile && !compilerOptions.out && oldState!.affectedFilesPendingEmit) {
state.affectedFilesPendingEmit = oldState!.affectedFilesPendingEmit;
state.affectedFilesPendingEmitIndex = oldState!.affectedFilesPendingEmitIndex;
}
}
// Update changed files and copy semantic diagnostics if we can
@ -203,6 +219,27 @@ namespace ts {
}
}
/**
* Returns next file to be emitted from files that retrieved semantic diagnostics but did not emit yet
*/
function getNextAffectedFilePendingEmit(state: BuilderProgramState): SourceFile | undefined {
const { affectedFilesPendingEmit } = state;
if (affectedFilesPendingEmit) {
const seenEmittedFiles = state.seenEmittedFiles || (state.seenEmittedFiles = createMap());
for (let affectedFilesIndex = state.affectedFilesPendingEmitIndex!; affectedFilesIndex < affectedFilesPendingEmit.length; affectedFilesIndex++) {
const affectedFile = Debug.assertDefined(state.program).getSourceFileByPath(affectedFilesPendingEmit[affectedFilesIndex]);
if (affectedFile && !seenEmittedFiles.has(affectedFile.path)) {
// emit this file
state.affectedFilesPendingEmitIndex = affectedFilesIndex;
return affectedFile;
}
}
state.affectedFilesPendingEmit = undefined;
state.affectedFilesPendingEmitIndex = undefined;
}
return undefined;
}
/**
* Remove the semantic diagnostics cached from old state for affected File and the files that are referencing modules that export entities from affected file
*/
@ -312,21 +349,26 @@ namespace ts {
* This is called after completing operation on the next affected file.
* The operations here are postponed to ensure that cancellation during the iteration is handled correctly
*/
function doneWithAffectedFile(state: BuilderProgramState, affected: SourceFile | Program) {
function doneWithAffectedFile(state: BuilderProgramState, affected: SourceFile | Program, isPendingEmit?: boolean) {
if (affected === state.program) {
state.changedFilesSet.clear();
}
else {
state.seenAffectedFiles!.set((affected as SourceFile).path, true);
state.affectedFilesIndex!++;
if (isPendingEmit) {
state.affectedFilesPendingEmitIndex!++;
}
else {
state.affectedFilesIndex!++;
}
}
}
/**
* Returns the result with affected file
*/
function toAffectedFileResult<T>(state: BuilderProgramState, result: T, affected: SourceFile | Program): AffectedFileResult<T> {
doneWithAffectedFile(state, affected);
function toAffectedFileResult<T>(state: BuilderProgramState, result: T, affected: SourceFile | Program, isPendingEmit?: boolean): AffectedFileResult<T> {
doneWithAffectedFile(state, affected, isPendingEmit);
return { result, affected };
}
@ -442,10 +484,20 @@ namespace ts {
* in that order would be used to write the files
*/
function emitNextAffectedFile(writeFile?: WriteFileCallback, cancellationToken?: CancellationToken, emitOnlyDtsFiles?: boolean, customTransformers?: CustomTransformers): AffectedFileResult<EmitResult> {
const affected = getNextAffectedFile(state, cancellationToken, computeHash);
let affected = getNextAffectedFile(state, cancellationToken, computeHash);
let isPendingEmitFile = false;
if (!affected) {
affected = getNextAffectedFilePendingEmit(state);
// Done
return undefined;
if (!affected) {
return undefined;
}
isPendingEmitFile = true;
}
// Mark seen emitted files if there are pending files to be emitted
if (state.affectedFilesPendingEmit && state.program !== affected) {
(state.seenEmittedFiles || (state.seenEmittedFiles = createMap())).set((affected as SourceFile).path, true);
}
return toAffectedFileResult(
@ -453,7 +505,8 @@ namespace ts {
// When whole program is affected, do emit only once (eg when --out or --outFile is specified)
// Otherwise just affected file
Debug.assertDefined(state.program).emit(affected === state.program ? undefined : affected as SourceFile, writeFile || host.writeFile, cancellationToken, emitOnlyDtsFiles, customTransformers),
affected
affected,
isPendingEmitFile
);
}
@ -552,12 +605,22 @@ namespace ts {
return getSemanticDiagnosticsOfFile(state, sourceFile, cancellationToken);
}
if (kind === BuilderProgramKind.SemanticDiagnosticsBuilderProgram) {
// When semantic builder asks for diagnostics of the whole program,
// ensure that all the affected files are handled
let affected: SourceFile | Program | undefined;
while (affected = getNextAffectedFile(state, cancellationToken, computeHash)) {
doneWithAffectedFile(state, affected);
// When semantic builder asks for diagnostics of the whole program,
// ensure that all the affected files are handled
let affected: SourceFile | Program | undefined;
let affectedFilesPendingEmit: Path[] | undefined;
while (affected = getNextAffectedFile(state, cancellationToken, computeHash)) {
if (affected !== state.program && kind === BuilderProgramKind.EmitAndSemanticDiagnosticsBuilderProgram) {
(affectedFilesPendingEmit || (affectedFilesPendingEmit = [])).push((affected as SourceFile).path);
}
doneWithAffectedFile(state, affected);
}
// In case of emit builder, cache the files to be emitted
if (affectedFilesPendingEmit) {
state.affectedFilesPendingEmit = concatenate(state.affectedFilesPendingEmit, affectedFilesPendingEmit);
if (state.affectedFilesPendingEmitIndex === undefined) {
state.affectedFilesPendingEmitIndex = 0;
}
}

View File

@ -389,9 +389,8 @@ namespace ts {
return host;
}
// TODO: we should use emit and semantic diagnostics builder but that needs to handle errors little differently so handle it later
export function createSolutionBuilderWithWatchHost<T extends BuilderProgram = SemanticDiagnosticsBuilderProgram>(system = sys, createProgram?: CreateProgram<T>, reportDiagnostic?: DiagnosticReporter, reportSolutionBuilderStatus?: DiagnosticReporter, reportWatchStatus?: WatchStatusReporter) {
const host = createSolutionBuilderHostBase(system, createProgram || createSemanticDiagnosticsBuilderProgram as any as CreateProgram<T>, reportDiagnostic, reportSolutionBuilderStatus) as SolutionBuilderWithWatchHost<T>;
const host = createSolutionBuilderHostBase(system, createProgram || createEmitAndSemanticDiagnosticsBuilderProgram as any as CreateProgram<T>, reportDiagnostic, reportSolutionBuilderStatus) as SolutionBuilderWithWatchHost<T>;
const watchHost = createWatchHost(system, reportWatchStatus);
copyProperities(host, watchHost);
return host;

View File

@ -204,9 +204,8 @@ namespace ts {
reportWatchModeWithoutSysSupport();
}
// TODO: change this to host if watch => watchHost otherwiue without watch
const buildHost = buildOptions.watch ?
createSolutionBuilderWithWatchHost(sys, createSemanticDiagnosticsBuilderProgram, reportDiagnostic, createBuilderStatusReporter(sys, shouldBePretty()), createWatchStatusReporter()) :
createSolutionBuilderWithWatchHost(sys, createEmitAndSemanticDiagnosticsBuilderProgram, reportDiagnostic, createBuilderStatusReporter(sys, shouldBePretty()), createWatchStatusReporter()) :
createSolutionBuilderHost(sys, createAbstractBuilder, reportDiagnostic, createBuilderStatusReporter(sys, shouldBePretty()), createReportErrorSummary(buildOptions));
updateCreateProgram(buildHost);
buildHost.afterProgramEmitAndDiagnostics = (program: BuilderProgram) => reportStatistics(program.getProgram());