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.
@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).
@category Object
@category Array
@category Set
@category Map
*/
Rule catalog ID: R055