Skip to content

Commit 69abc12

Browse files
committed
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)
1 parent b360ff7 commit 69abc12

File tree

3 files changed

+89
-13
lines changed

3 files changed

+89
-13
lines changed

src/compiler/builder.ts

Lines changed: 52 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -154,6 +154,38 @@ namespace ts {
154154
return state;
155155
}
156156

157+
/**
158+
* Releases program and other related not needed properties
159+
*/
160+
function releaseCache(state: BuilderProgramState) {
161+
BuilderState.releaseCache(state);
162+
state.program = undefined;
163+
}
164+
165+
/**
166+
* Creates a clone of the state
167+
*/
168+
function cloneBuilderProgramState(state: Readonly<BuilderProgramState>): BuilderProgramState {
169+
const newState = BuilderState.clone(state) as BuilderProgramState;
170+
newState.semanticDiagnosticsPerFile = cloneMapOrUndefined(state.semanticDiagnosticsPerFile);
171+
newState.changedFilesSet = cloneMap(state.changedFilesSet);
172+
newState.affectedFiles = state.affectedFiles;
173+
newState.affectedFilesIndex = state.affectedFilesIndex;
174+
newState.currentChangedFilePath = state.currentChangedFilePath;
175+
newState.currentAffectedFilesSignatures = cloneMapOrUndefined(state.currentAffectedFilesSignatures);
176+
newState.currentAffectedFilesExportedModulesMap = cloneMapOrUndefined(state.currentAffectedFilesExportedModulesMap);
177+
newState.seenAffectedFiles = cloneMapOrUndefined(state.seenAffectedFiles);
178+
newState.cleanedDiagnosticsOfLibFiles = state.cleanedDiagnosticsOfLibFiles;
179+
newState.semanticDiagnosticsFromOldState = cloneMapOrUndefined(state.semanticDiagnosticsFromOldState);
180+
newState.program = state.program;
181+
newState.compilerOptions = state.compilerOptions;
182+
newState.affectedFilesPendingEmit = state.affectedFilesPendingEmit;
183+
newState.affectedFilesPendingEmitIndex = state.affectedFilesPendingEmitIndex;
184+
newState.seenEmittedFiles = cloneMapOrUndefined(state.seenEmittedFiles);
185+
newState.programEmitComplete = state.programEmitComplete;
186+
return newState;
187+
}
188+
157189
/**
158190
* Verifies that source file is ok to be used in calls that arent handled by next
159191
*/
@@ -458,7 +490,8 @@ namespace ts {
458490
* Computing hash to for signature verification
459491
*/
460492
const computeHash = host.createHash || generateDjb2Hash;
461-
const state = createBuilderProgramState(newProgram, getCanonicalFileName, oldState);
493+
let state = createBuilderProgramState(newProgram, getCanonicalFileName, oldState);
494+
let backupState: BuilderProgramState | undefined;
462495

463496
// To ensure that we arent storing any references to old program or new program without state
464497
newProgram = undefined!; // TODO: GH#18217
@@ -467,9 +500,21 @@ namespace ts {
467500

468501
const result = createRedirectedBuilderProgram(state, configFileParsingDiagnostics);
469502
result.getState = () => state;
503+
result.backupCurrentState = () => {
504+
Debug.assert(backupState === undefined);
505+
backupState = cloneBuilderProgramState(state);
506+
};
507+
result.useBackupState = () => {
508+
state = Debug.assertDefined(backupState);
509+
backupState = undefined;
510+
};
470511
result.getAllDependencies = sourceFile => BuilderState.getAllDependencies(state, Debug.assertDefined(state.program), sourceFile);
471512
result.getSemanticDiagnostics = getSemanticDiagnostics;
472513
result.emit = emit;
514+
result.releaseProgram = () => {
515+
releaseCache(state);
516+
backupState = undefined;
517+
};
473518

474519
if (kind === BuilderProgramKind.SemanticDiagnosticsBuilderProgram) {
475520
(result as SemanticDiagnosticsBuilderProgram).getSemanticDiagnosticsOfNextAffectedFile = getSemanticDiagnosticsOfNextAffectedFile;
@@ -650,6 +695,8 @@ namespace ts {
650695
export function createRedirectedBuilderProgram(state: { program: Program | undefined; compilerOptions: CompilerOptions; }, configFileParsingDiagnostics: ReadonlyArray<Diagnostic>): BuilderProgram {
651696
return {
652697
getState: notImplemented,
698+
backupCurrentState: noop,
699+
useBackupState: noop,
653700
getProgram: () => Debug.assertDefined(state.program),
654701
getProgramOrUndefined: () => state.program,
655702
releaseProgram: () => state.program = undefined,
@@ -694,6 +741,10 @@ namespace ts {
694741
export interface BuilderProgram {
695742
/*@internal*/
696743
getState(): BuilderProgramState;
744+
/*@internal*/
745+
backupCurrentState(): void;
746+
/*@internal*/
747+
useBackupState(): void;
697748
/**
698749
* Returns current program
699750
*/

src/compiler/builderState.ts

Lines changed: 32 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -50,11 +50,15 @@ namespace ts {
5050
/**
5151
* Cache of all files excluding default library file for the current program
5252
*/
53-
allFilesExcludingDefaultLibraryFile: ReadonlyArray<SourceFile> | undefined;
53+
allFilesExcludingDefaultLibraryFile?: ReadonlyArray<SourceFile>;
5454
/**
5555
* Cache of all the file names
5656
*/
57-
allFileNames: ReadonlyArray<string> | undefined;
57+
allFileNames?: ReadonlyArray<string>;
58+
}
59+
60+
export function cloneMapOrUndefined<T>(map: ReadonlyMap<T> | undefined) {
61+
return map ? cloneMap(map) : undefined;
5862
}
5963
}
6064

@@ -230,9 +234,32 @@ namespace ts.BuilderState {
230234
fileInfos,
231235
referencedMap,
232236
exportedModulesMap,
233-
hasCalledUpdateShapeSignature,
234-
allFilesExcludingDefaultLibraryFile: undefined,
235-
allFileNames: undefined
237+
hasCalledUpdateShapeSignature
238+
};
239+
}
240+
241+
/**
242+
* Releases needed properties
243+
*/
244+
export function releaseCache(state: BuilderState) {
245+
state.allFilesExcludingDefaultLibraryFile = undefined;
246+
state.allFileNames = undefined;
247+
}
248+
249+
/**
250+
* Creates a clone of the state
251+
*/
252+
export function clone(state: Readonly<BuilderState>): BuilderState {
253+
const fileInfos = createMap<FileInfo>();
254+
state.fileInfos.forEach((value, key) => {
255+
fileInfos.set(key, { ...value });
256+
});
257+
// Dont need to backup allFiles info since its cache anyway
258+
return {
259+
fileInfos,
260+
referencedMap: cloneMapOrUndefined(state.referencedMap),
261+
exportedModulesMap: cloneMapOrUndefined(state.exportedModulesMap),
262+
hasCalledUpdateShapeSignature: cloneMap(state.hasCalledUpdateShapeSignature),
236263
};
237264
}
238265

src/compiler/tsbuild.ts

Lines changed: 5 additions & 7 deletions
Original file line numberDiff line numberDiff line change
@@ -382,8 +382,6 @@ namespace ts {
382382
host.reportDiagnostic = reportDiagnostic || createDiagnosticReporter(system);
383383
host.reportSolutionBuilderStatus = reportSolutionBuilderStatus || createBuilderStatusReporter(system);
384384
return host;
385-
386-
// TODO after program create
387385
}
388386

389387
export function createSolutionBuilderHost<T extends BuilderProgram = BuilderProgram>(system = sys, createProgram?: CreateProgram<T>, reportDiagnostic?: DiagnosticReporter, reportSolutionBuilderStatus?: DiagnosticReporter, reportErrorSummary?: ReportEmitErrorSummary) {
@@ -499,9 +497,7 @@ namespace ts {
499497
clearMap(allWatchedWildcardDirectories, wildCardWatches => clearMap(wildCardWatches, closeFileWatcherOf));
500498
clearMap(allWatchedInputFiles, inputFileWatches => clearMap(inputFileWatches, closeFileWatcher));
501499
clearMap(allWatchedConfigFiles, closeFileWatcher);
502-
if (!options.watch) {
503-
builderPrograms.clear();
504-
}
500+
builderPrograms.clear();
505501
updateGetSourceFile();
506502
}
507503

@@ -576,7 +572,7 @@ namespace ts {
576572
hostWithWatch,
577573
resolved,
578574
() => {
579-
invalidateProjectAndScheduleBuilds(resolved, ConfigFileProgramReloadLevel.Full);
575+
invalidateProjectAndScheduleBuilds(resolved, ConfigFileProgramReloadLevel.Full);
580576
},
581577
PollingInterval.High,
582578
WatchType.ConfigFile,
@@ -1132,15 +1128,17 @@ namespace ts {
11321128
return buildErrors(semanticDiagnostics, BuildResultFlags.TypeErrors, "Semantic");
11331129
}
11341130

1131+
// Before emitting lets backup state, so we can revert it back if there are declaration errors to handle emit and declaration errors correctly
1132+
program.backupCurrentState();
11351133
let newestDeclarationFileContentChangedTime = minimumDate;
11361134
let anyDtsChanged = false;
11371135
let declDiagnostics: Diagnostic[] | undefined;
11381136
const reportDeclarationDiagnostics = (d: Diagnostic) => (declDiagnostics || (declDiagnostics = [])).push(d);
11391137
const outputFiles: OutputFile[] = [];
1140-
// TODO:: handle declaration diagnostics in incremental build.
11411138
emitFilesAndReportErrors(program, reportDeclarationDiagnostics, writeFileName, /*reportSummary*/ undefined, (name, text, writeByteOrderMark) => outputFiles.push({ name, text, writeByteOrderMark }));
11421139
// Don't emit .d.ts if there are decl file errors
11431140
if (declDiagnostics) {
1141+
program.useBackupState();
11441142
return buildErrors(declDiagnostics, BuildResultFlags.DeclarationEmitErrors, "Declaration file");
11451143
}
11461144

0 commit comments

Comments
 (0)