This rule enforces use of the readonly modifier and readonly types.
This rule enforces use of readonly T[]
(ReadonlyArray<T>
) over T[]
(Array<T>
).
The readonly modifier must appear on property signatures in interfaces, property declarations in classes, and index signatures.
You might think that using const
would eliminate mutation from your TypeScript code. Wrong. Turns out that there's a pretty big loophole in const
.
interface Point {
x: number;
y: number;
}
const point: Point = { x: 23, y: 44 };
point.x = 99; // This is legal
This is why the readonly
modifier exists. It prevents you from assigning a value to the result of a member expression.
interface Point {
readonly x: number;
readonly y: number;
}
const point: Point = { x: 23, y: 44 };
point.x = 99; // <- No object mutation allowed.
This is just as effective as using Object.freeze() to prevent mutations in your Redux reducers. However the readonly
modifier has no run-time cost, and is enforced at compile time. A good alternative to object mutation is to use the ES2016 object spread syntax that was added in typescript 2.1:
interface Point {
readonly x: number;
readonly y: number;
}
const point: Point = { x: 23, y: 44 };
const transformedPoint = { ...point, x: 99 };
Note that you can also use object spread when destructuring to delete keys in an object:
let { [action.id]: deletedItem, ...rest } = state;
The readonly
modifier also works on indexers:
const foo: { readonly [key: string]: number } = { a: 1, b: 2 };
foo["a"] = 3; // Error: Index signature only permits reading
Even if an array is declared with const
it is still possible to mutate the contents of the array.
interface Point {
readonly x: number;
readonly y: number;
}
const points: Array<Point> = [{ x: 23, y: 44 }];
points.push({ x: 1, y: 2 }); // This is legal
Using the ReadonlyArray<T>
type or readonly T[]
will stop this mutation:
interface Point {
readonly x: number;
readonly y: number;
}
const points: ReadonlyArray<Point> = [{ x: 23, y: 44 }];
// const points: readonly Point[] = [{ x: 23, y: 44 }]; // This is the alternative syntax for the line above
points.push({ x: 1, y: 2 }); // Unresolved method push()
The rule accepts an options object with the following properties:
type Options = {
readonly checkImplicit: boolean
readonly ignoreClass?: boolean;
readonly ignoreInterface?: boolean;
readonly ignoreLocal?: boolean;
readonly ignorePattern?: string | Array<string>;
readonly ignoreReturnType?: boolean;
};
const defaults = {
checkImplicit: false,
ignoreClass: false,
ignoreInterface: false,
ignoreLocal: false,
ignoreReturnType: false
};
By default, this function only checks explicit types. Enabling this option will make the rule also check implicit types.
Note: Checking implicit types is more expensive (slow).
Doesn't check the return type of functions.
A boolean to specify if checking for readonly
should apply to classes. false
by default.
Examples of incorrect code for the { "ignoreClass": false }
option:
/*eslint ts-immutable/readonly: ["error", { "ignoreClass": false }]*/
class {
myprop: string;
}
Examples of correct code for the { "ignoreClass": true }
option:
/*eslint ts-immutable/readonly: ["error", { "ignoreClass": true }]*/
class {
myprop: string;
}
A boolean to specify if checking for readonly
should apply to interfaces. false
by default.
Examples of incorrect code for the { "ignoreInterface": false }
option:
/*eslint ts-immutable/readonly: ["error", { "ignoreInterface": false }]*/
interface {
myprop: string;
}
Examples of correct code for the { "ignoreInterface": true }
option:
/*eslint ts-immutable/readonly: ["error", { "ignoreInterface": true }]*/
interface {
myprop: string;
}
See the ignoreLocal docs.
See the ignorePattern docs.