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