A private member can only be assigned inside its class. If nothing there assigns it after the constructor, it never changes. readonly says so, and then TypeScript rejects an assignment that someone adds later by mistake.
The rule checks private fields and #private fields, including static ones, and private constructor parameters. Assignments, ++, --, delete, and destructuring into the member count as changes. Changing what's inside the value, such as this.entries.set(...), doesn't.
--fix adds readonly.
Configure
Off by default. This rule needs types, so turning it on makes bun lint type check your code first.
[lint.rules]
"@typescript-eslint/prefer-readonly" = "error" # or "warn"
Every option, set to its default:
[lint.rules]
"@typescript-eslint/prefer-readonly" = ["error", {
onlyInlineLambdas = false,
}]
Examples
Reported:
class Cache {
private entries = new Map<string, string>();
#limit = 100;
private name: string;
constructor(name: string, private ttl: number) {
this.name = name;
}
set(key: string, value: string) {
this.entries.set(key, value);
}
}
Not reported:
class Cache {
private readonly entries = new Map<string, string>();
readonly #limit = 100;
private hits = 0;
size = 0;
constructor(private readonly ttl: number) {}
get(key: string) {
this.hits++;
return this.entries.get(key);
}
}
Options
| Option | Default | What it does |
|---|---|---|
onlyInlineLambdas | false | Skip members that are declared with a value other than an arrow function. |
With onlyInlineLambdas: true, this isn't reported:
class Button {
private label = "Save";
}
An arrow function is still reported, and so is a member that's declared without a value:
class Button {
private id: string;
private onClick = () => {
console.log("clicked");
};
constructor(id: string) {
this.id = id;
}
}