Handle declaration emit errors in tsbuild mode by backing up builder state

This helps us revert to state where we pretend as if emit is not done (since we do not do emit if there are errors)
This commit is contained in:
Sheetal Nandi
2018-12-26 12:07:59 -08:00
parent b360ff770a
commit 69abc12494
3 changed files with 89 additions and 13 deletions

View File

@@ -154,6 +154,38 @@ namespace ts {
return state;
}
/**
* Releases program and other related not needed properties
*/
function releaseCache(state: BuilderProgramState) {
BuilderState.releaseCache(state);
state.program = undefined;
}
/**
* Creates a clone of the state
*/
function cloneBuilderProgramState(state: Readonly<BuilderProgramState>): BuilderProgramState {
const newState = BuilderState.clone(state) as BuilderProgramState;
newState.semanticDiagnosticsPerFile = cloneMapOrUndefined(state.semanticDiagnosticsPerFile);
newState.changedFilesSet = cloneMap(state.changedFilesSet);
newState.affectedFiles = state.affectedFiles;
newState.affectedFilesIndex = state.affectedFilesIndex;
newState.currentChangedFilePath = state.currentChangedFilePath;
newState.currentAffectedFilesSignatures = cloneMapOrUndefined(state.currentAffectedFilesSignatures);
newState.currentAffectedFilesExportedModulesMap = cloneMapOrUndefined(state.currentAffectedFilesExportedModulesMap);
newState.seenAffectedFiles = cloneMapOrUndefined(state.seenAffectedFiles);
newState.cleanedDiagnosticsOfLibFiles = state.cleanedDiagnosticsOfLibFiles;
newState.semanticDiagnosticsFromOldState = cloneMapOrUndefined(state.semanticDiagnosticsFromOldState);
newState.program = state.program;
newState.compilerOptions = state.compilerOptions;
newState.affectedFilesPendingEmit = state.affectedFilesPendingEmit;
newState.affectedFilesPendingEmitIndex = state.affectedFilesPendingEmitIndex;
newState.seenEmittedFiles = cloneMapOrUndefined(state.seenEmittedFiles);
newState.programEmitComplete = state.programEmitComplete;
return newState;
}
/**
* Verifies that source file is ok to be used in calls that arent handled by next
*/
@@ -458,7 +490,8 @@ namespace ts {
* Computing hash to for signature verification
*/
const computeHash = host.createHash || generateDjb2Hash;
const state = createBuilderProgramState(newProgram, getCanonicalFileName, oldState);
let state = createBuilderProgramState(newProgram, getCanonicalFileName, oldState);
let backupState: BuilderProgramState | undefined;
// To ensure that we arent storing any references to old program or new program without state
newProgram = undefined!; // TODO: GH#18217
@@ -467,9 +500,21 @@ namespace ts {
const result = createRedirectedBuilderProgram(state, configFileParsingDiagnostics);
result.getState = () => state;
result.backupCurrentState = () => {
Debug.assert(backupState === undefined);
backupState = cloneBuilderProgramState(state);
};
result.useBackupState = () => {
state = Debug.assertDefined(backupState);
backupState = undefined;
};
result.getAllDependencies = sourceFile => BuilderState.getAllDependencies(state, Debug.assertDefined(state.program), sourceFile);
result.getSemanticDiagnostics = getSemanticDiagnostics;
result.emit = emit;
result.releaseProgram = () => {
releaseCache(state);
backupState = undefined;
};
if (kind === BuilderProgramKind.SemanticDiagnosticsBuilderProgram) {
(result as SemanticDiagnosticsBuilderProgram).getSemanticDiagnosticsOfNextAffectedFile = getSemanticDiagnosticsOfNextAffectedFile;
@@ -650,6 +695,8 @@ namespace ts {
export function createRedirectedBuilderProgram(state: { program: Program | undefined; compilerOptions: CompilerOptions; }, configFileParsingDiagnostics: ReadonlyArray<Diagnostic>): BuilderProgram {
return {
getState: notImplemented,
backupCurrentState: noop,
useBackupState: noop,
getProgram: () => Debug.assertDefined(state.program),
getProgramOrUndefined: () => state.program,
releaseProgram: () => state.program = undefined,
@@ -694,6 +741,10 @@ namespace ts {
export interface BuilderProgram {
/*@internal*/
getState(): BuilderProgramState;
/*@internal*/
backupCurrentState(): void;
/*@internal*/
useBackupState(): void;
/**
* Returns current program
*/

View File

@@ -50,11 +50,15 @@ namespace ts {
/**
* Cache of all files excluding default library file for the current program
*/
allFilesExcludingDefaultLibraryFile: ReadonlyArray<SourceFile> | undefined;
allFilesExcludingDefaultLibraryFile?: ReadonlyArray<SourceFile>;
/**
* Cache of all the file names
*/
allFileNames: ReadonlyArray<string> | undefined;
allFileNames?: ReadonlyArray<string>;
}
export function cloneMapOrUndefined<T>(map: ReadonlyMap<T> | undefined) {
return map ? cloneMap(map) : undefined;
}
}
@@ -230,9 +234,32 @@ namespace ts.BuilderState {
fileInfos,
referencedMap,
exportedModulesMap,
hasCalledUpdateShapeSignature,
allFilesExcludingDefaultLibraryFile: undefined,
allFileNames: undefined
hasCalledUpdateShapeSignature
};
}
/**
* Releases needed properties
*/
export function releaseCache(state: BuilderState) {
state.allFilesExcludingDefaultLibraryFile = undefined;
state.allFileNames = undefined;
}
/**
* Creates a clone of the state
*/
export function clone(state: Readonly<BuilderState>): BuilderState {
const fileInfos = createMap<FileInfo>();
state.fileInfos.forEach((value, key) => {
fileInfos.set(key, { ...value });
});
// Dont need to backup allFiles info since its cache anyway
return {
fileInfos,
referencedMap: cloneMapOrUndefined(state.referencedMap),
exportedModulesMap: cloneMapOrUndefined(state.exportedModulesMap),
hasCalledUpdateShapeSignature: cloneMap(state.hasCalledUpdateShapeSignature),
};
}

View File

@@ -382,8 +382,6 @@ namespace ts {
host.reportDiagnostic = reportDiagnostic || createDiagnosticReporter(system);
host.reportSolutionBuilderStatus = reportSolutionBuilderStatus || createBuilderStatusReporter(system);
return host;
// TODO after program create
}
export function createSolutionBuilderHost<T extends BuilderProgram = BuilderProgram>(system = sys, createProgram?: CreateProgram<T>, reportDiagnostic?: DiagnosticReporter, reportSolutionBuilderStatus?: DiagnosticReporter, reportErrorSummary?: ReportEmitErrorSummary) {
@@ -499,9 +497,7 @@ namespace ts {
clearMap(allWatchedWildcardDirectories, wildCardWatches => clearMap(wildCardWatches, closeFileWatcherOf));
clearMap(allWatchedInputFiles, inputFileWatches => clearMap(inputFileWatches, closeFileWatcher));
clearMap(allWatchedConfigFiles, closeFileWatcher);
if (!options.watch) {
builderPrograms.clear();
}
builderPrograms.clear();
updateGetSourceFile();
}
@@ -576,7 +572,7 @@ namespace ts {
hostWithWatch,
resolved,
() => {
invalidateProjectAndScheduleBuilds(resolved, ConfigFileProgramReloadLevel.Full);
invalidateProjectAndScheduleBuilds(resolved, ConfigFileProgramReloadLevel.Full);
},
PollingInterval.High,
WatchType.ConfigFile,
@@ -1132,15 +1128,17 @@ namespace ts {
return buildErrors(semanticDiagnostics, BuildResultFlags.TypeErrors, "Semantic");
}
// Before emitting lets backup state, so we can revert it back if there are declaration errors to handle emit and declaration errors correctly
program.backupCurrentState();
let newestDeclarationFileContentChangedTime = minimumDate;
let anyDtsChanged = false;
let declDiagnostics: Diagnostic[] | undefined;
const reportDeclarationDiagnostics = (d: Diagnostic) => (declDiagnostics || (declDiagnostics = [])).push(d);
const outputFiles: OutputFile[] = [];
// TODO:: handle declaration diagnostics in incremental build.
emitFilesAndReportErrors(program, reportDeclarationDiagnostics, writeFileName, /*reportSummary*/ undefined, (name, text, writeByteOrderMark) => outputFiles.push({ name, text, writeByteOrderMark }));
// Don't emit .d.ts if there are decl file errors
if (declDiagnostics) {
program.useBackupState();
return buildErrors(declDiagnostics, BuildResultFlags.DeclarationEmitErrors, "Declaration file");
}