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"); }