September 10, 2019·4 min read·engineeringtypescriptcompilerbuilds

TypeScript 3.6.3 hashes declaration output for incremental signatures

The maintenance release corrects which emitted file supplies an incremental signature when declaration maps are enabled, and separates processed files from emitted files.

Incremental compilation needs a reliable way to decide whether a file's externally visible type contract has changed. Hashing the wrong output can make that decision depend on another artifact. TypeScript 3.6.3 adjusts declaration-signature calculation when declaration maps appear among a file's emitted outputs.

The npm registry records 3.6.3 on September 10, 2019. The release comparison shows a change in builderState that selects declaration text explicitly before computing its hash. It also adds an assertion about the selected file's extension.

This is maintenance of the evidence used to skip compiler work. An incremental result depends on the correctness of that evidence, not merely on retaining some previous data. The distinction matters because generated declaration files and declaration maps are related outputs with different purposes.

I regard reliable incremental behavior as part of TypeScript's practical usefulness. A web codebase benefits from quicker feedback only when the saved state represents the right relationships. An optimization's internal signature is therefore an engineering contract worth understanding even though it never runs in the browser.

The public type contract has a specific representation

The release-tagged builder-state source computes a signature from declaration emission. The code requests declaration-only output for the source file and uses the declaration text as input to the hash. That gives the signature a connection to the file's exposed type information.

With declarationMap enabled, the emitted output list has more than one relevant artifact. The patch no longer assumes that the first output's text is always the declaration. It selects the second output in the map-enabled case when the required output exists, and the first output otherwise.

The assertion then verifies that the selected file has the declaration-file extension. This is a guard on the compiler's assumption about output ordering. If that assumption becomes wrong, the assertion provides a more direct explanation than silently computing a signature from unrelated text.

The correction should not be described as adding declarations or maps to the language. It changes which existing emitted artifact supplies the signature. The mechanism is internal to determining the effect of a source change.

Maps and declarations can change for different reasons

A declaration represents the type information exposed to consumers. A map accompanies generated output with a relationship to source positions. Those responsibilities explain why hashing one in place of the other can misrepresent whether the contract changed.

For example, a change in source layout is a different kind of event from a change in a declared parameter type. An incremental builder needs to determine which effects are relevant to dependent checking. The patch makes the signature input explicitly match its declaration-based purpose.

The comparison also shows that a changed signature can cause the builder to update exported-module information. That gives the hash a consequence beyond simply being stored as a checksum. It participates in deciding what dependency information needs to be reconsidered.

This is why artifact selection belongs in a correctness explanation. Computing a perfectly reliable hash of the wrong text would still answer the wrong question. The fix identifies the declaration file before relying on the hash result.

Affected files and emitted files are different sets

The same release adjusts bookkeeping in builder.ts. A completion helper gains an indication that the completed operation was emission. When that indication is present, the file is recorded in seenEmittedFiles as well as the set of affected files already processed.

A new helper for emission results supplies that indication. The comparison routes emission paths through it. The resulting state can distinguish completing work on an affected file from having actually emitted that file.

That distinction is necessary because diagnostics and output are different compiler operations. An integration can ask for semantic information without that request proving that the current JavaScript or declaration output has been written. A single processed flag would be insufficient to express both facts accurately.

The source comment ties postponed state updates to correct handling of cancellation during iteration. Work should be marked complete after the relevant operation finishes. Maintaining that ordering helps the builder resume without treating interrupted work as already performed.

My preference is to explain these changes through their separate state transitions. Selecting the right declaration determines what a signature means. Recording actual emission determines what work has happened. Both are needed for a builder that reuses previous results.

Persisted diagnostics need normalized keys

The comparison also converts stored diagnostic file keys through the builder's path conversion when reconstructing reusable program state. Cached diagnostics need to be associated with the same file identities the active program uses. Reusing information is only useful if the lookup reaches the intended entry.

These changes affect compiler and build-tool behavior, rather than adding runtime logic to an application. A project using declaration maps or incremental APIs depends on those internal distinctions even when its TypeScript statements remain unchanged.

TypeScript 3.6.3 therefore repairs specific parts of incremental state management. Declaration signatures use the selected declaration output, emission completion has its own bookkeeping, and restored diagnostic entries use converted paths. Each correction narrows the gap between the state retained by the builder and the work represented by that state.