Deprecate Functions and Symbols with JSDoc in TypeScript
Adding JSDoc annotations to communicate that a symbol is deprecated and provides a type-safe link to alternatives.
When dealing with large codebases and quickly iterating to deliver value to users, it is not straightforward to deal with the technical debt indirectly generated.
Adding these functionalities sometimes leads to creating slightly different variants of the same business logic. Aligning all the different variants is usually initially out of scope when an experimental and iterative approach is taken.
However, you have some simple tools available for your teammates or yourself when it is clear that some of these functions, constants, or even types need to go.
Let’s take a concrete example: You have a collection of
type predicates
but you suddenly need to write a lot of validation and you decide to adopt zod
to
ease the pain of writing schemas.
export function isString(value: unknown): value is string {
return typeof value === 'string';
}
// `isString` will be struck through
if (isString(input)) {
// ...
}
At this point, the predicate is used all across the codebase, and removing it would be a large effort that might outweigh the benefit of removing it.
Therefore, it is important to clearly define that this function should not be used anymore and
possibly be removed if the code is changed. You can use @deprecated
to communicate this intent and it will be processed and used by your IDE intellisense. You
can try it out in the
following playground.
/**
* @deprecated Zod schemas should be used instead.
*/
export function isString(value: unknown): value is string {
return typeof value === 'string';
}
As a note, simply marking the function as deprecated won’t tell much either why it is deprecated
or what to use instead. By putting a message after the @deprecated
JSDoc annotation, the information
will also be available when hovering function call and it should help your teammates.
This could be improved further by adding a @link
annotation with what alternative should
be used instead. Providing a TypeScript symbol enables you to click a link to jump to the location where the symbol
is defined.
// eslint-disable-next-line @typescript-eslint/no-unused-vars
type z = typeof import('zod').z;
/**
* @deprecated Zod schemas with {@link z} should be used instead.
*/
export function isString(value: unknown): value is string {
return typeof value === 'string';
}
At the time of writing, it is sadly necessary to define the symbol outside of the JSDoc block which requires
an ESLint exception if its not used anywhere else on the file. It would be much cleaner to use the import
syntax directly inside the @link
annotation but this is currently not supported and tracked by microsoft/TypeScript#43950.
This is however a small price to pay knowing that it provides a type-safe way to reference symbols in your JSDoc comments. In other words, each link will be kept in sync if a refactor that renames or moves these symbols take place. This is a big benefit considering that it is very easy to have comments falling out of sync!
Using @deprecated
can be used on almost all symbols that TypeScript supports except for unions since
JSDoc cannot be used for these cases.
/** @deprecated */
const variableOrConstant = 'variable-or-constant';
/** @deprecated */
type Type = 'type';
type RecordType = {
version: 1;
/** @deprecated */
field: string;
};
/** @deprecated */
function overload(value: number): void;
function overload(value: string): void;
function overload(value: number | string): void {
return;
}