Skip to content

Commit 4290ee1

Browse files
author
Rebecca Stevens
committed
feat(prefer-readonly-types): Merge rules readonly-array and readonly-keyword
1 parent 6fb4983 commit 4290ee1

File tree

9 files changed

+1342
-1334
lines changed

9 files changed

+1342
-1334
lines changed

README.md

Lines changed: 6 additions & 7 deletions
Original file line numberDiff line numberDiff line change
@@ -99,13 +99,12 @@ In addition to immutable rules this project also contains a few rules for enforc
9999

100100
### Immutability rules
101101

102-
| Name | Description | <span title="Recommended">:see_no_evil:</span> | <span title="Functional Lite">:hear_no_evil:</span> | <span title="Functional">:speak_no_evil:</span> | :wrench: | :blue_heart: |
103-
| ------------------------------------------------------------ | -------------------------------------------------------------------------- | :--------------------------------------------: | :-------------------------------------------------: | :---------------------------------------------: | :------: | :---------------: |
104-
| [`readonly-keyword`](./docs/rules/readonly-keyword.md) | Enforce readonly modifiers are used where possible | :heavy_check_mark: | :heavy_check_mark: | :heavy_check_mark: | :wrench: | :thought_balloon: |
105-
| [`readonly-array`](./docs/rules/readonly-array.md) | Enforce readonly array over mutable arrays | :heavy_check_mark: | :heavy_check_mark: | :heavy_check_mark: | :wrench: | :thought_balloon: |
106-
| [`no-let`](./docs/rules/no-let.md) | Disallow mutable variables | :heavy_check_mark: | :heavy_check_mark: | :heavy_check_mark: | | |
107-
| [`immutable-data`](./docs/rules/immutable-data.md) | Disallow mutating objects and arrays | :heavy_check_mark: | :heavy_check_mark: | :heavy_check_mark: | | :blue_heart: |
108-
| [`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: |
102+
| Name | Description | <span title="Recommended">:see_no_evil:</span> | <span title="Functional Lite">:hear_no_evil:</span> | <span title="Functional">:speak_no_evil:</span> | :wrench: | :blue_heart: |
103+
| ---------------------------------------------------------------- | -------------------------------------------------------------------------- | :--------------------------------------------: | :-------------------------------------------------: | :---------------------------------------------: | :------: | :---------------: |
104+
| [`prefer-readonly-types`](./docs/rules/prefer-readonly-types.md) | Use readonly types and readonly modifiers where possible | :heavy_check_mark: | :heavy_check_mark: | :heavy_check_mark: | :wrench: | :thought_balloon: |
105+
| [`no-let`](./docs/rules/no-let.md) | Disallow mutable variables | :heavy_check_mark: | :heavy_check_mark: | :heavy_check_mark: | | |
106+
| [`immutable-data`](./docs/rules/immutable-data.md) | Disallow mutating objects and arrays | :heavy_check_mark: | :heavy_check_mark: | :heavy_check_mark: | | :blue_heart: |
107+
| [`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: |
109108

110109
### Functional style rules
111110

docs/rules/prefer-readonly-types.md

Lines changed: 173 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,173 @@
1+
# Prefer readonly types over mutable types (prefer-readonly-types)
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+
### Benefits of using the `readonly` modifier
12+
13+
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`.
14+
15+
```typescript
16+
interface Point {
17+
x: number;
18+
y: number;
19+
}
20+
const point: Point = { x: 23, y: 44 };
21+
point.x = 99; // This is legal
22+
```
23+
24+
This is why the `readonly` modifier exists. It prevents you from assigning a value to the result of a member expression.
25+
26+
```typescript
27+
interface Point {
28+
readonly x: number;
29+
readonly y: number;
30+
}
31+
const point: Point = { x: 23, y: 44 };
32+
point.x = 99; // <- No object mutation allowed.
33+
```
34+
35+
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](https://github.com/Microsoft/TypeScript/wiki/What's-new-in-TypeScript#object-spread-and-rest) that was added in typescript 2.1:
36+
37+
```typescript
38+
interface Point {
39+
readonly x: number;
40+
readonly y: number;
41+
}
42+
const point: Point = { x: 23, y: 44 };
43+
const transformedPoint = { ...point, x: 99 };
44+
```
45+
46+
Note that you can also use object spread when destructuring to [delete keys](http://stackoverflow.com/questions/35342355/remove-data-from-nested-objects-without-mutating/35676025#35676025) in an object:
47+
48+
```typescript
49+
let { [action.id]: deletedItem, ...rest } = state;
50+
```
51+
52+
The `readonly` modifier also works on indexers:
53+
54+
```typescript
55+
const foo: { readonly [key: string]: number } = { a: 1, b: 2 };
56+
foo["a"] = 3; // Error: Index signature only permits reading
57+
```
58+
59+
### Benefits of using `readonly T[]`
60+
61+
Even if an array is declared with `const` it is still possible to mutate the contents of the array.
62+
63+
```typescript
64+
interface Point {
65+
readonly x: number;
66+
readonly y: number;
67+
}
68+
const points: Array<Point> = [{ x: 23, y: 44 }];
69+
points.push({ x: 1, y: 2 }); // This is legal
70+
```
71+
72+
Using the `ReadonlyArray<T>` type or `readonly T[]` will stop this mutation:
73+
74+
```typescript
75+
interface Point {
76+
readonly x: number;
77+
readonly y: number;
78+
}
79+
80+
const points: ReadonlyArray<Point> = [{ x: 23, y: 44 }];
81+
// const points: readonly Point[] = [{ x: 23, y: 44 }]; // This is the alternative syntax for the line above
82+
83+
points.push({ x: 1, y: 2 }); // Unresolved method push()
84+
```
85+
86+
## Options
87+
88+
The rule accepts an options object with the following properties:
89+
90+
```typescript
91+
type Options = {
92+
readonly checkImplicit: boolean
93+
readonly ignoreClass?: boolean;
94+
readonly ignoreInterface?: boolean;
95+
readonly ignoreLocal?: boolean;
96+
readonly ignorePattern?: string | Array<string>;
97+
readonly ignoreReturnType?: boolean;
98+
};
99+
100+
const defaults = {
101+
checkImplicit: false,
102+
ignoreClass: false,
103+
ignoreInterface: false,
104+
ignoreLocal: false,
105+
ignoreReturnType: false
106+
};
107+
```
108+
109+
### `checkImplicit`
110+
111+
By default, this function only checks explicit types. Enabling this option will make the rule also check implicit types.
112+
113+
Note: Checking implicit types is more expensive (slow).
114+
115+
### `ignoreReturnType`
116+
117+
Doesn't check the return type of functions.
118+
119+
### `ignoreClass`
120+
121+
A boolean to specify if checking for `readonly` should apply to classes. `false` by default.
122+
123+
Examples of **incorrect** code for the `{ "ignoreClass": false }` option:
124+
125+
```ts
126+
/*eslint ts-immutable/readonly: ["error", { "ignoreClass": false }]*/
127+
128+
class {
129+
myprop: string;
130+
}
131+
```
132+
133+
Examples of **correct** code for the `{ "ignoreClass": true }` option:
134+
135+
```ts
136+
/*eslint ts-immutable/readonly: ["error", { "ignoreClass": true }]*/
137+
138+
class {
139+
myprop: string;
140+
}
141+
```
142+
143+
### `ignoreInterface`
144+
145+
A boolean to specify if checking for `readonly` should apply to interfaces. `false` by default.
146+
147+
Examples of **incorrect** code for the `{ "ignoreInterface": false }` option:
148+
149+
```ts
150+
/*eslint ts-immutable/readonly: ["error", { "ignoreInterface": false }]*/
151+
152+
interface {
153+
myprop: string;
154+
}
155+
```
156+
157+
Examples of **correct** code for the `{ "ignoreInterface": true }` option:
158+
159+
```ts
160+
/*eslint ts-immutable/readonly: ["error", { "ignoreInterface": true }]*/
161+
162+
interface {
163+
myprop: string;
164+
}
165+
```
166+
167+
### `ignoreLocal`
168+
169+
See the [ignoreLocal](./options/ignore-local.md) docs.
170+
171+
### `ignorePattern`
172+
173+
See the [ignorePattern](./options/ignore-pattern.md) docs.

docs/rules/readonly-array.md

Lines changed: 0 additions & 69 deletions
This file was deleted.

src/rules/index.ts

Lines changed: 4 additions & 9 deletions
Original file line numberDiff line numberDiff line change
@@ -37,13 +37,9 @@ import { name as noThisRuleName, rule as noThisRule } from "./no-this";
3737
import { name as noThrowRuleName, rule as noThrowRule } from "./no-throw";
3838
import { name as noTryRuleName, rule as noTryRule } from "./no-try";
3939
import {
40-
name as readonlyArrayRuleName,
41-
rule as readonlyArrayRule
42-
} from "./readonly-array";
43-
import {
44-
name as readonlyKeywordRuleName,
45-
rule as readonlyKeywordRule
46-
} from "./readonly-keyword";
40+
name as preferReadonlyTypesRuleName,
41+
rule as preferReadonlyTypesRule
42+
} from "./prefer-readonly-types";
4743

4844
/**
4945
* All of the custom rules.
@@ -63,6 +59,5 @@ export const rules = {
6359
[noThisRuleName]: noThisRule,
6460
[noThrowRuleName]: noThrowRule,
6561
[noTryRuleName]: noTryRule,
66-
[readonlyArrayRuleName]: readonlyArrayRule,
67-
[readonlyKeywordRuleName]: readonlyKeywordRule
62+
[preferReadonlyTypesRuleName]: preferReadonlyTypesRule
6863
};

0 commit comments

Comments
 (0)