From 69abc124944ddd7fa9b8c64384be5538757fc536 Mon Sep 17 00:00:00 2001 From: Sheetal Nandi Date: Wed, 26 Dec 2018 12:07:59 -0800 Subject: [PATCH] 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) --- src/compiler/builder.ts | 53 +++++++++++++++++++++++++++++++++++- src/compiler/builderState.ts | 37 +++++++++++++++++++++---- src/compiler/tsbuild.ts | 12 ++++---- 3 files changed, 89 insertions(+), 13 deletions(-) diff --git a/src/compiler/builder.ts b/src/compiler/builder.ts index ef017e2ae97..a4c9dd0cc84 100644 --- a/src/compiler/builder.ts +++ b/src/compiler/builder.ts @@ -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 { + 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): 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 */ diff --git a/src/compiler/builderState.ts b/src/compiler/builderState.ts index 0462beada9e..552f46c378d 100644 --- a/src/compiler/builderState.ts +++ b/src/compiler/builderState.ts @@ -50,11 +50,15 @@ namespace ts { /** * Cache of all files excluding default library file for the current program */ - allFilesExcludingDefaultLibraryFile: ReadonlyArray | undefined; + allFilesExcludingDefaultLibraryFile?: ReadonlyArray; /** * Cache of all the file names */ - allFileNames: ReadonlyArray | undefined; + allFileNames?: ReadonlyArray; + } + + export function cloneMapOrUndefined(map: ReadonlyMap | 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 { + const fileInfos = createMap(); + 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), }; } diff --git a/src/compiler/tsbuild.ts b/src/compiler/tsbuild.ts index ae5ddfcccf3..fa84b4bd9bb 100644 --- a/src/compiler/tsbuild.ts +++ b/src/compiler/tsbuild.ts @@ -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(system = sys, createProgram?: CreateProgram, 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"); }