Documenting TypeScript
Before TypeScript added type information to JavaScript, the JSDoc format, based on JavaDoc, was used to add machine-readable information about function arguments, return values, and potential Errors. Sophisticated developer tools like IDEs would read this information to enhance developer experience.
TypeScript introduced type declaration into the language syntax, allowing the compiler to operate on this knowledge more strictly than relying on string parsing of comments. However, many tools, including the TypeScript compiler, can still read JSDoc strings and incorporate information from those comments with whatever else it can derive from the language syntax. Both can coexist harmlessly.
But where should things be documented? Lately, I’ve been working on clarifying my opinions and intuitions on this and writing down the rules I typically subconsciously follow.
Document function behavior in human language.
It’s important to document the behavior of functions in clear, human-readable language. This helps other developers understand the purpose and expected usage of the function. This documentation should cover things like:
- What the function does
- What parameters does it expect?
- What it returns
- Are there any side effects or edge cases?
- Assumptions or preconditions
This high-level documentation is best placed near the function declaration, often as a JSDoc comment block. The goal is to provide sufficient context for someone to use the function without needing to read the implementation details.
Document types with syntax.
Use TypeScript syntax for types instead of relying solely on JSDoc. This allows the compiler to validate and optimize the code. JSDoc can provide additional context, but the core type information should be in the language syntax. This enhances tooling support and ensures the type information is closely linked with the implementation.
I like to combine the two approaches:
function formatDate(
date: Date,
/** The time zone to format the Date using. Defaults to local time zone. */
tz?: string,
/** A timezone to override the Date's original. */
sourceTz?: string,
) {
return; // ...
}
This lets TypeScript’s compiler/typechecker do its best while providing human-readable information for developer tooling like IDEs to helpfully present to the user.
Document any Error
throws with JSDoc.
TypeScript’s type system lacks a way of also declaring the types of Error
s a function may throw
. While TypeScript handles typing function inputs and outputs, there’s no built-in way to declare that a function may throw an Error instead of returning the declared type. Some libraries like Effect add this as part of their Effect
generic type, which I enjoy.
If you’re not using such a library, document this behavior in your JSDoc strings with the @throws
tag.