Skip to main content

prefer-type-fest-readonly-deep

Require TypeFest ReadonlyDeep over DeepReadonly aliases.

Targeted pattern scopeโ€‹

This rule reports DeepReadonly<T> aliases and prefers ReadonlyDeep<T> for recursive immutability contracts.

What this rule reportsโ€‹

  • Type references named DeepReadonly.

Detection boundariesโ€‹

  • โœ… Reports direct DeepReadonly<T> type references.
  • โŒ Does not auto-fix when legacy helper semantics differ for containers.

Why this rule existsโ€‹

ReadonlyDeep<T> is TypeFest's canonical deep immutability utility.

Canonical naming prevents mixed deep-readonly conventions in shared contract packages.

โŒ Incorrectโ€‹

type Config = DeepReadonly<AppConfig>;

โœ… Correctโ€‹

import type { ReadonlyDeep } from "type-fest";

type Config = ReadonlyDeep<AppConfig>;

Behavior and migration notesโ€‹

  • ReadonlyDeep<T> recursively applies readonly semantics to nested structures.
  • Verify behavior for maps/sets/tuples if your prior alias had custom handling.
  • Prefer applying deep readonly at API boundaries where mutation should be prevented.

ESLint flat config exampleโ€‹

import typefest from "eslint-plugin-typefest";

export default [
{
plugins: { typefest },
rules: {
"typefest/prefer-type-fest-readonly-deep": "error",
},
},
];

When not to use itโ€‹

Disable this rule if your codebase intentionally standardizes DeepReadonly naming instead of TypeFest.

Package documentationโ€‹

TypeFest package documentation:

Source file: source/readonly-deep.d.ts

/**
* Convert `object`s, `Map`s, `Set`s, and `Array`s and all of their
* keys/elements into immutable structures recursively.
*
* This is useful when a deeply nested structure needs to be exposed as
* completely immutable, for example, an imported JSON module or when receiving
* an API response that is passed around.
*
* Please upvote [this
* issue](https://github.com/microsoft/TypeScript/issues/13923) if you want to
* have this type as a built-in in TypeScript.
*
* @category Object
* @category Array
* @category Set
* @category Map
*
* @example
* ```
* import type {ReadonlyDeep} from 'type-fest';
*
* declare const foo: {
* a: string;
* b: {c: number};
* d: Array<{e: number}>;
* };
*
* foo.a = 'bar'; // Allowed
*
* foo.b = {c: 3}; // Allowed
*
* foo.b.c = 4; // Allowed
*
* foo.d = [{e: 5}]; // Allowed
*
* foo.d.push({e: 6}); // Allowed
*
* const last = foo.d.at(-1);
* if (last) {
* last.e = 7; // Allowed
* }
*
* declare const readonlyFoo: ReadonlyDeep<typeof foo>;
*
* // @ts-expect-error
* readonlyFoo.a = 'bar';
* // Error: Cannot assign to 'a' because it is a read-only property.
*
* // @ts-expect-error
* readonlyFoo.b = {c: 3};
* // Error: Cannot assign to 'b' because it is a read-only property.
*
* // @ts-expect-error
* readonlyFoo.b.c = 4;
* // Error: Cannot assign to 'c' because it is a read-only property.
*
* // @ts-expect-error
* readonlyFoo.d = [{e: 5}];
* // Error: Cannot assign to 'd' because it is a read-only property.
*
* // @ts-expect-error
* readonlyFoo.d.push({e: 6});
* // Error: Property 'push' does not exist on type 'ReadonlyArray<{readonly e: number}>'.
*
* const readonlyLast = readonlyFoo.d.at(-1);
* if (readonlyLast) {
* // @ts-expect-error
* readonlyLast.e = 8;
* // Error: Cannot assign to 'e' because it is a read-only property.
* }
* ```
*
* Note that types containing overloaded functions are not made deeply readonly due to a [TypeScript limitation](https://github.com/microsoft/TypeScript/issues/29732).
*/

Rule catalog ID: R055

Further readingโ€‹

Adoption resourcesโ€‹