TypeScript 4.0.5 preserves pending output across diagnostic-only builds
The 4.0.5 patch repairs noEmit and noEmitOnError handling in the semantic diagnostics builder. Build information must distinguish checked files from files whose output has been written.
Checking a file and writing its output are different accomplishments. An incremental builder that records the first as if it had completed the second can give a later build the wrong account of remaining work. TypeScript 4.0.5, published October 26, includes corrections to this bookkeeping in the semantic diagnostics builder.
The changes since 4.0.3 include a backport for noEmit and noEmitOnError handling. The patch records pending output in build information even when the selected builder primarily tracks diagnostics. Its tests compare output after reusing that information through different builder implementations.
I care about incremental correctness because TypeScript often shares a development workflow with another tool responsible for application output. A fast checker is useful only if its saved state remains honest about what happened. Reusing analysis should not make unwritten output appear complete.
The builder has more than one responsibility
The builder repair distinguishes a semantic diagnostics builder from an emit-and-semantic-diagnostics builder. Tracking output work is part of the latter's purpose. A semantic diagnostics builder can still encounter settings that affect emission and can write build information used later.
Before the change, the call handling no-emit options sat inside a condition for the emit-oriented builder. The patch moves that handling outside the condition. The semantic builder therefore passes through the same relevant option handling instead of bypassing it solely because of its builder kind.
This is a practical compiler API concern. A custom watch host can choose a builder that suits its diagnostic workflow. The selected implementation still has to honour the compiler options and preserve usable state for subsequent work. Builder selection cannot silently change the meaning of noEmit.
The repair also updates how affected files become pending output. That recording now considers noEmit and noEmitOnError in addition to the emit-oriented builder. The source comment explains that build information can be written and reused between builders.
Pending output must survive a check that writes no program files
A file can have current diagnostics while still needing JavaScript or declaration output. That is the distinction the patch preserves. Saving enough state to skip repeated analysis is useful, but it must not erase evidence that another operation has yet to write the files.
The test added with the patch uses a composite configuration. It runs a watch setup with emission disabled, then reuses the resulting state through builders that perform emission. The test compares the generated JavaScript, declaration files and build information against another path through the builder APIs.
An illustrative configuration identifies the two independent choices. It requests composite build information while preventing ordinary program output during that check.
{
"compilerOptions": {
"composite": true,
"noEmit": true
}
}
This is not a complete deployment workflow. It identifies the combination covered by the repair. A subsequent operation that emits output must receive an accurate record of affected files rather than assume that the earlier diagnostic pass produced all artifacts.
Error-dependent emission needs its own state handling
noEmitOnError adds another condition. A pass may need to save build information after detecting errors while leaving affected files pending for later output. The patch has explicit backup and restoration logic around that case for the non-emit builder.
The source comments distinguish paths with errors from successful paths. They also state that callers of a builder that does not track emitted files remain responsible for writing output where appropriate. The repair therefore should not be described as making every custom builder automatically manage all files on disk.
The important guarantee is narrower. The builder must preserve the information required by the option-handling path and by later reuse. A diagnostics-oriented operation cannot safely discard output obligations merely because it completed its own analysis.
I prefer examining those transitions to treating an incremental build as one undifferentiated success flag. A useful test asks what another builder does with the saved state after the first pass. That checks the relationship between operations, where this kind of defect can remain hidden during a single invocation.
The tests exercise reuse and affected-file checking
The added tests also verify that a changed file receives cached semantic diagnostics without unnecessarily retrieving diagnostics for an unchanged peer. This keeps the maintenance change connected to the purpose of an incremental builder. Correct emission state should not require abandoning affected-file checking altogether.
Other cases compare outputs before and after source changes and exercise reuse between the semantic-only and emit-oriented builders. The comparison includes the build-information file, so correctness is judged through what the next operation can read as well as the immediate output.
For integrations using these compiler APIs, TypeScript 4.0.5 supplies a correction at the shared bookkeeping layer. A completed diagnostic pass can coexist with pending output. The saved state now represents that distinction for the covered no-emit and error-dependent emission paths.