Skip to content

Commit

Permalink
feat!(prefer-readonly-type-declaration): creation of new rule.
Browse files Browse the repository at this point in the history
BREAKING CHANGE: rule "prefer-readonly-type" is now deprecated in favor of
"prefer-readonly-type-declaration" and @typescript-eslint/prefer-readonly-parameter-types".
  • Loading branch information
RebeccaStevens committed Aug 16, 2021
1 parent 9bcc8d6 commit 01079c7
Show file tree
Hide file tree
Showing 16 changed files with 2,698 additions and 10 deletions.
3 changes: 2 additions & 1 deletion .cspell.json
Expand Up @@ -28,7 +28,8 @@
"/`[^`]*`/",
"/\\.\\/docs\\/rules\\/[^.]*.md/",
"/TS[^\\s]+/",
"\\(#.+?\\)"
"\\(#.+?\\)",
"\/\/ @ts-.*"
],
"words": [
"globstar",
Expand Down
12 changes: 6 additions & 6 deletions README.md
Expand Up @@ -192,12 +192,12 @@ The [below section](#supported-rules) gives details on which rules are enabled b

:see_no_evil: = `no-mutations` Ruleset.

| 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: |
| -------------------------------------------------------------- | -------------------------------------------------------------------------- | :---------------------------------------------: | :--------------------------------------: | :----------------------------------------------: | :------: | :---------------: |
| [`immutable-data`](./docs/rules/immutable-data.md) | Disallow mutating objects and arrays | :heavy_check_mark: | :heavy_check_mark: | :heavy_check_mark: | | :blue_heart: |
| [`no-let`](./docs/rules/no-let.md) | Disallow mutable variables | :heavy_check_mark: | :heavy_check_mark: | :heavy_check_mark: | | |
| [`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: |
| [`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: |
| 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: |
| -------------------------------------------------------------------------------------- | -------------------------------------------------------------------------- | :---------------------------------------------: | :--------------------------------------: | :----------------------------------------------: | :------: | :---------------: |
| [`immutable-data`](./docs/rules/immutable-data.md) | Disallow mutating objects and arrays | :heavy_check_mark: | :heavy_check_mark: | :heavy_check_mark: | | :blue_heart: |
| [`no-let`](./docs/rules/no-let.md) | Disallow mutable variables | :heavy_check_mark: | :heavy_check_mark: | :heavy_check_mark: | | |
| [`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: |
| [`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: |

### No Object-Orientation Rules

Expand Down
236 changes: 236 additions & 0 deletions docs/rules/prefer-readonly-type-declaration.md
@@ -0,0 +1,236 @@
# Prefer readonly types over mutable types (prefer-readonly-type-declaration)

This rule enforces use of readonly type declarations.

## Rule Details

This rule checks that declared types are deeply readonly (unless declared to not be).

It can also be used to enforce a naming convention for readonly vs mutable type aliases and interfaces.

Examples of **incorrect** code for this rule:

```ts
/* eslint functional/prefer-readonly-type-declaration: "error" */

type Point = {
x: number;
y: number;
};

const point: Point = { x: 23, y: 44 };
point.x = 99;
```

Examples of **correct** code for this rule:

```ts
/* eslint functional/prefer-readonly-type-declaration: "error" */

type Point = {
readonly x: number;
readonly y: number;
};
const point1: Point = { x: 23, y: 44 };
const transformedPoint1 = { ...point, x: 99 };

type MutablePoint = {
x: number;
y: number;
};
const point2: MutablePoint = { x: 23, y: 44 };
point2.x = 99;
```

## Options

This rule accepts an options object of the following type:

```ts
{
allowLocalMutation: boolean;
allowMutableReturnType: boolean;
ignoreClass: boolean | "fieldsOnly";
ignoreInterface: boolean;
ignoreCollections: boolean;
ignorePattern?: string | Array<string>;
aliases: {
mustBeReadonly: {
pattern: ReadonlyArray<string> | string;
requireOthersToBeMutable: boolean;
};
mustBeMutable: {
pattern: ReadonlyArray<string> | string;
requireOthersToBeReadonly: boolean;
};
blacklist: ReadonlyArray<string> | string;
};
}
```

The default options:

```ts
{
allowLocalMutation: false,
allowMutableReturnType: true,
ignoreClass: false,
ignoreInterface: false,
ignoreCollections: false,
aliases: {
blacklist: "^Mutable$",
mustBeReadonly: {
pattern: "^(I?)Readonly",
requireOthersToBeMutable: false,
},
mustBeMutable: {
pattern: "^(I?)Mutable",
requireOthersToBeReadonly: true,
},
},
}
```

### `allowMutableReturnType`

If set, return types of functions will not be checked.

### `ignoreClass`

If set, classes will not be checked.

Examples of **incorrect** code for the `{ "ignoreClass": false }` option:

```ts
/* eslint functional/readonly: ["error", { "ignoreClass": false }] */

class {
myprop: string;
}
```

Examples of **correct** code for the `{ "ignoreClass": true }` option:

```ts
/* eslint functional/readonly: ["error", { "ignoreClass": true }] */

class {
myprop: string;
}
```

### `ignoreInterface`

If set, interfaces will not be checked.

Examples of **incorrect** code for the `{ "ignoreInterface": false }` option:

```ts
/* eslint functional/readonly: ["error", { "ignoreInterface": false }] */

interface {
myprop: string;
}
```

Examples of **correct** code for the `{ "ignoreInterface": true }` option:

```ts
/* eslint functional/readonly: ["error", { "ignoreInterface": true }] */

interface {
myprop: string;
}
```

### `ignoreCollections`

If set, collections (Array, Tuple, Set, and Map) will not be required to be readonly when used outside of type aliases and interfaces.

Examples of **incorrect** code for the `{ "ignoreCollections": false }` option:

```ts
/* eslint functional/readonly: ["error", { "ignoreCollections": false }] */

const foo: number[] = [];
const bar: [string, string] = ["foo", "bar"];
const baz: Set<string, string> = new Set();
const qux: Map<string, string> = new Map();
```

Examples of **correct** code for the `{ "ignoreCollections": true }` option:

```ts
/* eslint functional/readonly: ["error", { "ignoreCollections": true }] */

const foo: number[] = [];
const bar: [string, string] = ["foo", "bar"];
const baz: Set<string, string> = new Set();
const qux: Map<string, string> = new Map();
```

### `aliases`

These options apply only to type aliases and interface declarations.

#### `aliases.mustBeReadonly`

##### `aliases.mustBeReadonly.pattern`

The regex pattern(s) used to test against the type's name. If it's a match the type must be deeply readonly.

Set to an empty array to disable this check.

##### `aliases.mustBeReadonly.requireOthersToBeMutable`

If set, all other types that don't match the pattern(s) must **not** be deeply readonly.

#### `aliases.mustBeMutable`

##### `aliases.mustBeMutable.pattern`

The regex pattern(s) used to test against the type's name. If it's a match the type must **not** be deeply readonly.

Set to an empty array to disable this check.

##### `aliases.mustBeMutable.requireOthersToBeReadonly`

If set, all other types that don't match the pattern(s) must be deeply readonly.

#### `aliases.blacklist`

Any type names that match this regex pattern(s) will be ignored by this rule.

#### `aliases` Examples

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`.

```ts
/* eslint functional/prefer-readonly-type-declaration: ["error", { "aliases": { "mustBeReadonly": { "requireOthersToBeMutable": true }, "mustBeMutable": { "requireOthersToBeReadonly": false } } }] */

type Point = {
x: number;
y: number;
};
type ReadonlyPoint = Readonly<Point>;
```

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.

```ts
/* eslint functional/prefer-readonly-type-declaration: ["error", { "aliases": { "mustBeReadonly": { "requireOthersToBeMutable": true }, "mustBeMutable": { "requireOthersToBeReadonly": true } } }] */

type MutablePoint = {
x: number;
y: number;
};
type ReadonlyPoint = Readonly<MutablePoint>;
```

### `allowLocalMutation`

See the [allowLocalMutation](./options/allow-local-mutation.md) docs.

### `ignorePattern`

See the [ignorePattern](./options/ignore-pattern.md) docs.
4 changes: 3 additions & 1 deletion docs/rules/prefer-readonly-type.md
@@ -1,6 +1,8 @@
# Prefer readonly types over mutable types (prefer-readonly-type)

This rule enforces use of the readonly modifier and readonly types.
## :warning: This rule is deprecated

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)

## Rule Details

Expand Down
2 changes: 1 addition & 1 deletion src/configs/all.ts
Expand Up @@ -21,7 +21,7 @@ const config: Linter.Config = {
rules: {
"functional/no-method-signature": "error",
"functional/no-mixed-type": "error",
"functional/prefer-readonly-type": "error",
"functional/prefer-readonly-type-declaration": "error",
"functional/prefer-tacit": ["error", { assumeTypes: false }],
"functional/no-return-void": "error",
},
Expand Down
2 changes: 1 addition & 1 deletion src/configs/no-mutations.ts
Expand Up @@ -10,7 +10,7 @@ const config: Linter.Config = {
files: ["*.ts", "*.tsx"],
rules: {
"functional/no-method-signature": "warn",
"functional/prefer-readonly-type": "error",
"functional/prefer-readonly-type-declaration": "error",
},
},
],
Expand Down
2 changes: 2 additions & 0 deletions src/rules/index.ts
Expand Up @@ -13,6 +13,7 @@ import * as noThisExpression from "./no-this-expression";
import * as noThrowStatement from "./no-throw-statement";
import * as noTryStatement from "./no-try-statement";
import * as preferReadonlyTypes from "./prefer-readonly-type";
import * as preferReadonlyTypesDeclaration from "./prefer-readonly-type-declaration";
import * as preferTacit from "./prefer-tacit";

/**
Expand All @@ -34,5 +35,6 @@ export const rules = {
[noThrowStatement.name]: noThrowStatement.rule,
[noTryStatement.name]: noTryStatement.rule,
[preferReadonlyTypes.name]: preferReadonlyTypes.rule,
[preferReadonlyTypesDeclaration.name]: preferReadonlyTypesDeclaration.rule,
[preferTacit.name]: preferTacit.rule,
};

0 comments on commit 01079c7

Please sign in to comment.