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.

· 3 min read

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;
}