Skip to content

Commit 57ff8e0

Browse files
feat!(prefer-readonly-type-declaration): creation of new rule.
BREAKING CHANGE: rule "prefer-readonly-type" is now deprecated in favor of "prefer-readonly-type-declaration" and @typescript-eslint/prefer-readonly-parameter-types".
1 parent e7e5d5b commit 57ff8e0

File tree

13 files changed

+2656
-2
lines changed

13 files changed

+2656
-2
lines changed
Lines changed: 207 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,207 @@
1+
# Prefer readonly types over mutable types (prefer-readonly-type-declaration)
2+
3+
This rule enforces use of the readonly modifier and readonly types.
4+
5+
## Rule Details
6+
7+
This rule enforces use of `readonly T[]` (`ReadonlyArray<T>`) over `T[]` (`Array<T>`).
8+
9+
The readonly modifier must appear on property signatures in interfaces, property declarations in classes, and index signatures.
10+
11+
Examples of **incorrect** code for this rule:
12+
13+
```ts
14+
/* eslint functional/prefer-readonly-type-declaration: "error" */
15+
16+
interface Point {
17+
x: number;
18+
y: number;
19+
}
20+
const point: Point = { x: 23, y: 44 };
21+
point.x = 99; // This is perfectly valid.
22+
```
23+
24+
Examples of **correct** code for this rule:
25+
26+
```ts
27+
/* eslint functional/prefer-readonly-type-declaration: "error" */
28+
29+
interface Point {
30+
readonly x: number;
31+
readonly y: number;
32+
}
33+
const point: Point = { x: 23, y: 44 };
34+
point.x = 99; // <- No object mutation allowed.
35+
```
36+
37+
```ts
38+
/* eslint functional/prefer-readonly-type-declaration: "error" */
39+
40+
interface Point {
41+
readonly x: number;
42+
readonly y: number;
43+
}
44+
const point: Point = { x: 23, y: 44 };
45+
const transformedPoint = { ...point, x: 99 };
46+
```
47+
48+
### Benefits of using the `readonly` modifier
49+
50+
A variable declared as `const` can not be reassigned, however what's in the variable can be mutated.
51+
This is why the `readonly` modifier exists. It prevents you from assigning a value to the result of a member expression.
52+
This is just as effective as using `Object.freeze()` to prevent mutations. However the `readonly` modifier has **no run-time cost**, and is enforced at **compile time**.
53+
54+
The `readonly` modifier also works on indexers:
55+
56+
```ts
57+
const foo: { readonly [key: string]: number } = { a: 1, b: 2 };
58+
foo["a"] = 3; // Error: Index signature only permits reading
59+
```
60+
61+
### Benefits of using `readonly T[]`
62+
63+
Even if an array is declared with `const` it is still possible to mutate the contents of the array.
64+
65+
```ts
66+
interface Point {
67+
readonly x: number;
68+
readonly y: number;
69+
}
70+
const points: Array<Point> = [{ x: 23, y: 44 }];
71+
points.push({ x: 1, y: 2 }); // This is perfectly valid.
72+
```
73+
74+
Using the `ReadonlyArray<T>` type or `readonly T[]` will stop this mutation:
75+
76+
```ts
77+
interface Point {
78+
readonly x: number;
79+
readonly y: number;
80+
}
81+
82+
const points: ReadonlyArray<Point> = [{ x: 23, y: 44 }];
83+
// const points: readonly Point[] = [{ x: 23, y: 44 }]; // This is the alternative syntax for the line above
84+
85+
points.push({ x: 1, y: 2 }); // Unresolved method push()
86+
```
87+
88+
## Options
89+
90+
This rule accepts an options object of the following type:
91+
92+
```ts
93+
{
94+
allowLocalMutation: boolean;
95+
allowMutableReturnType: boolean;
96+
checkImplicit: boolean;
97+
ignoreClass: boolean | "fieldsOnly";
98+
ignoreInterface: boolean;
99+
ignoreCollections: boolean;
100+
ignorePattern?: string | Array<string>;
101+
}
102+
```
103+
104+
The default options:
105+
106+
```ts
107+
{
108+
allowLocalMutation: false,
109+
allowMutableReturnType: true,
110+
checkImplicit: false,
111+
ignoreClass: false,
112+
ignoreInterface: false,
113+
ignoreCollections: false,
114+
}
115+
```
116+
117+
### `checkImplicit`
118+
119+
By default, this function only checks explicit types. Enabling this option will make the rule also check implicit types.
120+
121+
Note: Checking implicit types is more expensive (slow).
122+
123+
### `allowMutableReturnType`
124+
125+
Doesn't check the return type of functions.
126+
127+
### `ignoreClass`
128+
129+
A boolean to specify if checking for `readonly` should apply to classes. `false` by default.
130+
131+
Examples of **incorrect** code for the `{ "ignoreClass": false }` option:
132+
133+
```ts
134+
/* eslint functional/readonly: ["error", { "ignoreClass": false }] */
135+
136+
class {
137+
myprop: string;
138+
}
139+
```
140+
141+
Examples of **correct** code for the `{ "ignoreClass": true }` option:
142+
143+
```ts
144+
/* eslint functional/readonly: ["error", { "ignoreClass": true }] */
145+
146+
class {
147+
myprop: string;
148+
}
149+
```
150+
151+
### `ignoreInterface`
152+
153+
A boolean to specify if checking for `readonly` should apply to interfaces. `false` by default.
154+
155+
Examples of **incorrect** code for the `{ "ignoreInterface": false }` option:
156+
157+
```ts
158+
/* eslint functional/readonly: ["error", { "ignoreInterface": false }] */
159+
160+
interface {
161+
myprop: string;
162+
}
163+
```
164+
165+
Examples of **correct** code for the `{ "ignoreInterface": true }` option:
166+
167+
```ts
168+
/* eslint functional/readonly: ["error", { "ignoreInterface": true }] */
169+
170+
interface {
171+
myprop: string;
172+
}
173+
```
174+
175+
### `ignoreCollections`
176+
177+
A boolean to specify if checking for `readonly` should apply to mutable collections (Array, Tuple, Set, and Map). Helpful for migrating from tslint-immutable to this plugin. `false` by default.
178+
179+
Examples of **incorrect** code for the `{ "ignoreCollections": false }` option:
180+
181+
```ts
182+
/* eslint functional/readonly: ["error", { "ignoreCollections": false }] */
183+
184+
const foo: number[] = [];
185+
const bar: [string, string] = ["foo", "bar"];
186+
const baz: Set<string, string> = new Set();
187+
const qux: Map<string, string> = new Map();
188+
```
189+
190+
Examples of **correct** code for the `{ "ignoreCollections": true }` option:
191+
192+
```ts
193+
/* eslint functional/readonly: ["error", { "ignoreCollections": true }] */
194+
195+
const foo: number[] = [];
196+
const bar: [string, string] = ["foo", "bar"];
197+
const baz: Set<string, string> = new Set();
198+
const qux: Map<string, string> = new Map();
199+
```
200+
201+
### `allowLocalMutation`
202+
203+
See the [allowLocalMutation](./options/allow-local-mutation.md) docs.
204+
205+
### `ignorePattern`
206+
207+
See the [ignorePattern](./options/ignore-pattern.md) docs.

src/configs/all.ts

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -21,7 +21,7 @@ const config: Linter.Config = {
2121
rules: {
2222
"functional/no-method-signature": "error",
2323
"functional/no-mixed-type": "error",
24-
"functional/prefer-readonly-type": "error",
24+
"functional/prefer-readonly-type-declaration": "error",
2525
"functional/prefer-tacit": ["error", { assumeTypes: false }],
2626
"functional/no-return-void": "error",
2727
},

src/configs/no-mutations.ts

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -10,7 +10,7 @@ const config: Linter.Config = {
1010
files: ["*.ts", "*.tsx"],
1111
rules: {
1212
"functional/no-method-signature": "warn",
13-
"functional/prefer-readonly-type": "error",
13+
"functional/prefer-readonly-type-declaration": "error",
1414
},
1515
},
1616
],

src/rules/index.ts

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -13,6 +13,7 @@ import * as noThisExpression from "./no-this-expression";
1313
import * as noThrowStatement from "./no-throw-statement";
1414
import * as noTryStatement from "./no-try-statement";
1515
import * as preferReadonlyTypes from "./prefer-readonly-type";
16+
import * as preferReadonlyTypesDeclaration from "./prefer-readonly-type-declaration";
1617
import * as preferTacit from "./prefer-tacit";
1718

1819
/**
@@ -34,5 +35,6 @@ export const rules = {
3435
[noThrowStatement.name]: noThrowStatement.rule,
3536
[noTryStatement.name]: noTryStatement.rule,
3637
[preferReadonlyTypes.name]: preferReadonlyTypes.rule,
38+
[preferReadonlyTypesDeclaration.name]: preferReadonlyTypesDeclaration.rule,
3739
[preferTacit.name]: preferTacit.rule,
3840
};

0 commit comments

Comments
 (0)