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 28, 2021
1 parent 8f2f4fb commit f9037af
Show file tree
Hide file tree
Showing 17 changed files with 2,706 additions and 10 deletions.
4 changes: 3 additions & 1 deletion .cspell.json
Expand Up @@ -28,12 +28,14 @@
"/`[^`]*`/",
"/\\.\\/docs\\/rules\\/[^.]*.md/",
"/TS[^\\s]+/",
"\\(#.+?\\)"
"\\(#.+?\\)",
"/\\/\\/ @ts-.*/"
],
"words": [
"globstar",
"IIFE",
"IIFEs",
"readonlyness",
"ruleset",
"rulesets",
"typeguard",
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
210 changes: 210 additions & 0 deletions docs/rules/prefer-readonly-type-declaration.md
@@ -0,0 +1,210 @@
# 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>;
ignoreAliasPatterns: string | Array<string>;
readonlyAliasPatterns: string | Array<string>;
mutableAliasPatterns: string | Array<string>;
}
```

The default options:

```ts
{
allowLocalMutation: false,
allowMutableReturnType: true,
ignoreClass: false,
ignoreInterface: false,
ignoreCollections: false,
readonlyAliasPatterns: "^(?!I?Mutable).+$",
mutableAliasPatterns: "^I?Mutable.+$",
ignoreAliasPatterns: "^Mutable$",
}
```

### `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();
```

### `readonlyAliasPatterns` and `mutableAliasPatterns`

These options apply only to type aliases and interface declarations.

The regex pattern(s) specified here are used to test against the type's name.
If it's a match then for `readonlyAliasPatterns` the type must be deeply readonly; for `mutableAliasPatterns` the type must be **not** deeply readonly;.

These options can be set to an empty array to disable this check.

#### Common configs for these options

Require all type aliases and interfaces to be deeply readonly unless prefixed with "Mutable".\
This is the default config.

```jsonc
{
"readonlyAliasPatterns": "^(?!I?Mutable).+$",
"mutableAliasPatterns": "^I?Mutable.+$",
}
```

Require all deeply readonly type aliases and interfaces to be prefixed with "Readonly".

```jsonc
{
"readonlyAliasPatterns": "^I?Readonly.+$",
"mutableAliasPatterns": "^(?!I?Readonly).+$",
}
```

Require all type aliases and interfaces to be explicity prefixed with either "Readonly" or "Mutable".

```jsonc
{
"readonlyAliasPatterns": "^I?Readonly.+$",
"mutableAliasPatterns": "^I?Mutable.+$",
}
```

### `ignoreAliasPatterns`

Any type alias or interface declaration that matches one of these patterns will be ignored for readonlyness.

### `allowLocalMutation`

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

### `ignorePattern`

Use the given regex pattern(s) to match against the type's name (for objects this is the property's name not the object's name).

Note: If using this option to require mutable properties are marked as mutable via a naming convention (e.g. `{ "ignorePattern": "^[Mm]utable.+" }`),
type aliases and interfaces names will still need to comply with the `readonlyAliasPatterns` and `mutableAliasPatterns` options.

See the [ignorePattern](./options/ignore-pattern.md) docs for more info.
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
8 changes: 8 additions & 0 deletions src/configs/functional-lite.ts
Expand Up @@ -16,6 +16,14 @@ const config: Linter.Config = deepmerge(functional, {
},
],
},
overrides: [
{
files: ["*.ts", "*.tsx"],
rules: {
"functional/prefer-readonly-type-declaration": "warn",
},
},
],
} as Linter.Config);

export default config;
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 f9037af

Please sign in to comment.