Skip to content

Commit 907ea41

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 57f17bd commit 907ea41

File tree

16 files changed

+2698
-10
lines changed

16 files changed

+2698
-10
lines changed

.cspell.json

Lines changed: 2 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -28,7 +28,8 @@
2828
"/`[^`]*`/",
2929
"/\\.\\/docs\\/rules\\/[^.]*.md/",
3030
"/TS[^\\s]+/",
31-
"\\(#.+?\\)"
31+
"\\(#.+?\\)",
32+
"\/\/ @ts-.*"
3233
],
3334
"words": [
3435
"globstar",

README.md

Lines changed: 6 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -192,12 +192,12 @@ The [below section](#supported-rules) gives details on which rules are enabled b
192192

193193
:see_no_evil: = `no-mutations` Ruleset.
194194

195-
| Name | Description | <span title="No Mutations">:see_no_evil:</span> | <span title="Lite">:hear_no_evil:</span> | <span title="Recommended">:speak_no_evil:</span> | :wrench: | :blue_heart: |
196-
| -------------------------------------------------------------- | -------------------------------------------------------------------------- | :---------------------------------------------: | :--------------------------------------: | :----------------------------------------------: | :------: | :---------------: |
197-
| [`immutable-data`](./docs/rules/immutable-data.md) | Disallow mutating objects and arrays | :heavy_check_mark: | :heavy_check_mark: | :heavy_check_mark: | | :blue_heart: |
198-
| [`no-let`](./docs/rules/no-let.md) | Disallow mutable variables | :heavy_check_mark: | :heavy_check_mark: | :heavy_check_mark: | | |
199-
| [`no-method-signature`](./docs/rules/no-method-signature.md) | Enforce property signatures with readonly modifiers over method signatures | :heavy_check_mark: | :heavy_check_mark: | :heavy_check_mark: | | :thought_balloon: |
200-
| [`prefer-readonly-type`](./docs/rules/prefer-readonly-type.md) | Use readonly types and readonly modifiers where possible | :heavy_check_mark: | :heavy_check_mark: | :heavy_check_mark: | :wrench: | :thought_balloon: |
195+
| Name | Description | <span title="No Mutations">:see_no_evil:</span> | <span title="Lite">:hear_no_evil:</span> | <span title="Recommended">:speak_no_evil:</span> | :wrench: | :blue_heart: |
196+
| -------------------------------------------------------------------------------------- | -------------------------------------------------------------------------- | :---------------------------------------------: | :--------------------------------------: | :----------------------------------------------: | :------: | :---------------: |
197+
| [`immutable-data`](./docs/rules/immutable-data.md) | Disallow mutating objects and arrays | :heavy_check_mark: | :heavy_check_mark: | :heavy_check_mark: | | :blue_heart: |
198+
| [`no-let`](./docs/rules/no-let.md) | Disallow mutable variables | :heavy_check_mark: | :heavy_check_mark: | :heavy_check_mark: | | |
199+
| [`no-method-signature`](./docs/rules/no-method-signature.md) | Enforce property signatures with readonly modifiers over method signatures | :heavy_check_mark: | :heavy_check_mark: | :heavy_check_mark: | | :thought_balloon: |
200+
| [`prefer-readonly-type-declaration`](./docs/rules/prefer-readonly-type-declaration.md) | Encore use of readonly types where possible | :heavy_check_mark: | :heavy_check_mark: | :heavy_check_mark: | :wrench: | :thought_balloon: |
201201

202202
### No Object-Orientation Rules
203203

Lines changed: 236 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,236 @@
1+
# Prefer readonly types over mutable types (prefer-readonly-type-declaration)
2+
3+
This rule enforces use of readonly type declarations.
4+
5+
## Rule Details
6+
7+
This rule checks that declared types are deeply readonly (unless declared to not be).
8+
9+
It can also be used to enforce a naming convention for readonly vs mutable type aliases and interfaces.
10+
11+
Examples of **incorrect** code for this rule:
12+
13+
```ts
14+
/* eslint functional/prefer-readonly-type-declaration: "error" */
15+
16+
type Point = {
17+
x: number;
18+
y: number;
19+
};
20+
21+
const point: Point = { x: 23, y: 44 };
22+
point.x = 99;
23+
```
24+
25+
Examples of **correct** code for this rule:
26+
27+
```ts
28+
/* eslint functional/prefer-readonly-type-declaration: "error" */
29+
30+
type Point = {
31+
readonly x: number;
32+
readonly y: number;
33+
};
34+
const point1: Point = { x: 23, y: 44 };
35+
const transformedPoint1 = { ...point, x: 99 };
36+
37+
type MutablePoint = {
38+
x: number;
39+
y: number;
40+
};
41+
const point2: MutablePoint = { x: 23, y: 44 };
42+
point2.x = 99;
43+
```
44+
45+
## Options
46+
47+
This rule accepts an options object of the following type:
48+
49+
```ts
50+
{
51+
allowLocalMutation: boolean;
52+
allowMutableReturnType: boolean;
53+
ignoreClass: boolean | "fieldsOnly";
54+
ignoreInterface: boolean;
55+
ignoreCollections: boolean;
56+
ignorePattern?: string | Array<string>;
57+
aliases: {
58+
mustBeReadonly: {
59+
pattern: ReadonlyArray<string> | string;
60+
requireOthersToBeMutable: boolean;
61+
};
62+
mustBeMutable: {
63+
pattern: ReadonlyArray<string> | string;
64+
requireOthersToBeReadonly: boolean;
65+
};
66+
blacklist: ReadonlyArray<string> | string;
67+
};
68+
}
69+
```
70+
71+
The default options:
72+
73+
```ts
74+
{
75+
allowLocalMutation: false,
76+
allowMutableReturnType: true,
77+
ignoreClass: false,
78+
ignoreInterface: false,
79+
ignoreCollections: false,
80+
aliases: {
81+
blacklist: "^Mutable$",
82+
mustBeReadonly: {
83+
pattern: "^(I?)Readonly",
84+
requireOthersToBeMutable: false,
85+
},
86+
mustBeMutable: {
87+
pattern: "^(I?)Mutable",
88+
requireOthersToBeReadonly: true,
89+
},
90+
},
91+
}
92+
```
93+
94+
### `allowMutableReturnType`
95+
96+
If set, return types of functions will not be checked.
97+
98+
### `ignoreClass`
99+
100+
If set, classes will not be checked.
101+
102+
Examples of **incorrect** code for the `{ "ignoreClass": false }` option:
103+
104+
```ts
105+
/* eslint functional/readonly: ["error", { "ignoreClass": false }] */
106+
107+
class {
108+
myprop: string;
109+
}
110+
```
111+
112+
Examples of **correct** code for the `{ "ignoreClass": true }` option:
113+
114+
```ts
115+
/* eslint functional/readonly: ["error", { "ignoreClass": true }] */
116+
117+
class {
118+
myprop: string;
119+
}
120+
```
121+
122+
### `ignoreInterface`
123+
124+
If set, interfaces will not be checked.
125+
126+
Examples of **incorrect** code for the `{ "ignoreInterface": false }` option:
127+
128+
```ts
129+
/* eslint functional/readonly: ["error", { "ignoreInterface": false }] */
130+
131+
interface {
132+
myprop: string;
133+
}
134+
```
135+
136+
Examples of **correct** code for the `{ "ignoreInterface": true }` option:
137+
138+
```ts
139+
/* eslint functional/readonly: ["error", { "ignoreInterface": true }] */
140+
141+
interface {
142+
myprop: string;
143+
}
144+
```
145+
146+
### `ignoreCollections`
147+
148+
If set, collections (Array, Tuple, Set, and Map) will not be required to be readonly when used outside of type aliases and interfaces.
149+
150+
Examples of **incorrect** code for the `{ "ignoreCollections": false }` option:
151+
152+
```ts
153+
/* eslint functional/readonly: ["error", { "ignoreCollections": false }] */
154+
155+
const foo: number[] = [];
156+
const bar: [string, string] = ["foo", "bar"];
157+
const baz: Set<string, string> = new Set();
158+
const qux: Map<string, string> = new Map();
159+
```
160+
161+
Examples of **correct** code for the `{ "ignoreCollections": true }` option:
162+
163+
```ts
164+
/* eslint functional/readonly: ["error", { "ignoreCollections": true }] */
165+
166+
const foo: number[] = [];
167+
const bar: [string, string] = ["foo", "bar"];
168+
const baz: Set<string, string> = new Set();
169+
const qux: Map<string, string> = new Map();
170+
```
171+
172+
### `aliases`
173+
174+
These options apply only to type aliases and interface declarations.
175+
176+
#### `aliases.mustBeReadonly`
177+
178+
##### `aliases.mustBeReadonly.pattern`
179+
180+
The regex pattern(s) used to test against the type's name. If it's a match the type must be deeply readonly.
181+
182+
Set to an empty array to disable this check.
183+
184+
##### `aliases.mustBeReadonly.requireOthersToBeMutable`
185+
186+
If set, all other types that don't match the pattern(s) must **not** be deeply readonly.
187+
188+
#### `aliases.mustBeMutable`
189+
190+
##### `aliases.mustBeMutable.pattern`
191+
192+
The regex pattern(s) used to test against the type's name. If it's a match the type must **not** be deeply readonly.
193+
194+
Set to an empty array to disable this check.
195+
196+
##### `aliases.mustBeMutable.requireOthersToBeReadonly`
197+
198+
If set, all other types that don't match the pattern(s) must be deeply readonly.
199+
200+
#### `aliases.blacklist`
201+
202+
Any type names that match this regex pattern(s) will be ignored by this rule.
203+
204+
#### `aliases` Examples
205+
206+
By toggling the default settings of `aliases.mustBeReadonly.requireOthersToBeMutable` and `aliases.mustBeMutable.requireOthersToBeReadonly`, you can make it so that types are mutable by default and immutable versions need to be prefixed. This more closely matches how TypeScript itself implements types like `Set` and `ReadonlySet`.
207+
208+
```ts
209+
/* eslint functional/prefer-readonly-type-declaration: ["error", { "aliases": { "mustBeReadonly": { "requireOthersToBeMutable": true }, "mustBeMutable": { "requireOthersToBeReadonly": false } } }] */
210+
211+
type Point = {
212+
x: number;
213+
y: number;
214+
};
215+
type ReadonlyPoint = Readonly<Point>;
216+
```
217+
218+
Alternatively, if both `aliases.mustBeReadonly.requireOthersToBeMutable` and `aliases.mustBeMutable.requireOthersToBeReadonly` are set, you can make it so that types explicitly need to be marked as either readonly or mutable.
219+
220+
```ts
221+
/* eslint functional/prefer-readonly-type-declaration: ["error", { "aliases": { "mustBeReadonly": { "requireOthersToBeMutable": true }, "mustBeMutable": { "requireOthersToBeReadonly": true } } }] */
222+
223+
type MutablePoint = {
224+
x: number;
225+
y: number;
226+
};
227+
type ReadonlyPoint = Readonly<MutablePoint>;
228+
```
229+
230+
### `allowLocalMutation`
231+
232+
See the [allowLocalMutation](./options/allow-local-mutation.md) docs.
233+
234+
### `ignorePattern`
235+
236+
See the [ignorePattern](./options/ignore-pattern.md) docs.

docs/rules/prefer-readonly-type.md

Lines changed: 3 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,6 +1,8 @@
11
# Prefer readonly types over mutable types (prefer-readonly-type)
22

3-
This rule enforces use of the readonly modifier and readonly types.
3+
## :warning: This rule is deprecated
4+
5+
This rule has been replaced by [prefer-readonly-type-declaration](./prefer-readonly-type-declaration.md) and [@typescript-eslint/prefer-readonly-parameter-types](https://github.com/typescript-eslint/typescript-eslint/blob/master/packages/eslint-plugin/docs/rules/prefer-readonly-parameter-types.md)
46

57
## Rule Details
68

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)