Skip to content

Commit

Permalink
docs: document new rules
Browse files Browse the repository at this point in the history
  • Loading branch information
RebeccaStevens committed Sep 19, 2022
1 parent cfa93dc commit 3b4dc49
Show file tree
Hide file tree
Showing 5 changed files with 387 additions and 6 deletions.
1 change: 1 addition & 0 deletions .eslintrc.json
Original file line number Diff line number Diff line change
Expand Up @@ -126,6 +126,7 @@
"functional/no-expression-statement": "off",
"functional/no-let": "off",
"functional/no-loop-statement": "off",
"functional/no-mixed-type": "off",
"functional/no-return-void": "off",
"functional/no-this-expression": "off",
"functional/no-throw-statement": "off",
Expand Down
14 changes: 8 additions & 6 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -186,12 +186,14 @@ 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-immutable-parameter-types`](./docs/rules/prefer-immutable-parameter-types.md) | Require function parameters to be typed as certain immutability | :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: |
| [`type-declaration-immutability`](./docs/rules/type-declaration-immutability.md) | Enforce the immutability of types based on patterns | :heavy_check_mark: | :heavy_check_mark: | :heavy_check_mark: | | :thought_balloon: |

### No Object-Orientation Rules

Expand Down
195 changes: 195 additions & 0 deletions docs/rules/prefer-immutable-parameter-types.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,195 @@
# Prefer immutable parameter types over mutable ones (prefer-immutable-parameter-types)

Although other rules can be used to ensure parameter are not mutated, it is
best to explicitly declare that the parameters are immutable.

It is also worth noting that as immutable types are not assignable to mutable
ones, users will not be able to pass something like a readonly array to a
functional that wants a mutable array; even if the function does not actually
mutate said array.

## Rule Details

This rule differs from the
[@typescript-eslint/prefer-readonly-parameter-types](https://github.com/typescript-eslint/typescript-eslint/blob/main/packages/eslint-plugin/docs/rules/prefer-readonly-parameter-types.md)
rule by the fact that it uses the
[is-immutable-type](https://www.npmjs.com/package/is-immutable-type) library to
calculated immutability. This library allows for more powerful and customizable
immutability enforcements to be made.

This rule is designed to replace the aforementioned rule.

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

<!-- eslint-disable functional/prefer-immutable-parameter-types -->

```ts
/* eslint functional/prefer-immutable-parameter-types: "error" */

function array1(arg: string[]) {} // array is not readonly
function array2(arg: ReadonlyArray<string[]>) {} // array element is not readonly
function array3(arg: [string, number]) {} // tuple is not readonly
function array4(arg: readonly [string[], number]) {} // tuple element is not readonly
// the above examples work the same if you use ReadonlyArray<T> instead

function object1(arg: { prop: string }) {} // property is not readonly
function object2(arg: { readonly prop: string; prop2: string }) {} // not all properties are readonly
function object3(arg: { readonly prop: { prop2: string } }) {} // nested property is not readonly
// the above examples work the same if you use Readonly<T> instead

interface CustomArrayType extends ReadonlyArray<string> {
prop: string; // note: this property is mutable
}
function custom1(arg: CustomArrayType) {}

interface CustomFunction {
(): void;
prop: string; // note: this property is mutable
}
function custom2(arg: CustomFunction) {}

function union(arg: string[] | ReadonlyArray<number[]>) {} // not all types are readonly

// rule also checks function types
interface Foo1 {
(arg: string[]): void;
}
interface Foo2 {
new (arg: string[]): void;
}
const x = { foo(arg: string[]): void; };
function foo(arg: string[]);
type Foo3 = (arg: string[]) => void;
interface Foo4 {
foo(arg: string[]): void;
}
```

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

<!-- eslint-disable functional/prefer-immutable-parameter-types -->

```ts
/* eslint functional/prefer-immutable-parameter-types: "error" */

function array1(arg: ReadonlyArray<string>) {}
function array2(arg: ReadonlyArray<ReadonlyArray<string>>) {}
function array3(arg: readonly [string, number]) {}
function array4(arg: readonly [ReadonlyArray<string>, number]) {}
// the above examples work the same if you use ReadonlyArray<T> instead

function object1(arg: { readonly prop: string }) {}
function object2(arg: { readonly prop: string; readonly prop2: string }) {}
function object3(arg: { readonly prop: { readonly prop2: string } }) {}
// the above examples work the same if you use Readonly<T> instead

interface CustomArrayType extends ReadonlyArray<string> {
readonly prop: string;
}
function custom1(arg: Readonly<CustomArrayType>) {}
// interfaces that extend the array types are not considered arrays, and thus must be made readonly.

interface CustomFunction {
(): void;
readonly prop: string;
}
function custom2(arg: CustomFunction) {}

function union(arg: ReadonlyArray<string> | ReadonlyArray<number[]>) {}

function primitive1(arg: string) {}
function primitive2(arg: number) {}
function primitive3(arg: boolean) {}
function primitive4(arg: unknown) {}
function primitive5(arg: null) {}
function primitive6(arg: undefined) {}
function primitive7(arg: any) {}
function primitive8(arg: never) {}
function primitive9(arg: string | number | undefined) {}

function fnSig(arg: () => void) {}

enum Foo { a, b }
function enum1(arg: Foo) {}

function symb1(arg: symbol) {}
const customSymbol = Symbol('a');
function symb2(arg: typeof customSymbol) {}

// function types
interface Foo1 {
(arg: ReadonlyArray<string>): void;
}
interface Foo2 {
new (arg: ReadonlyArray<string>): void;
}
const x = { foo(arg: ReadonlyArray<string>): void; };
function foo(arg: ReadonlyArray<string>);
type Foo3 = (arg: ReadonlyArray<string>) => void;
interface Foo4 {
foo(arg: ReadonlyArray<string>): void;
}
```

## Settings

This rule can leverage shared settings to configure immutability settings.

See the [immutability](./settings/immutability.md) docs.

## Options

This rule accepts an options object of the following type:

```ts
type Options = {
enforcement: "ReadonlyShallow" | "ReadonlyDeep" | "Immutable";
}
```
The default options:
```ts
const defaults = {
enforcement: "ReadonlyDeep",
}
```

### `enforcement`

The level of immutability that should be enforced.

**incorrect**:

<!-- eslint-disable functional/prefer-immutable-parameter-types -->

```ts
/* eslint functional/prefer-immutable-parameter-types: ["error", { "enforcement": "Immutable" }] */

function array(arg: ReadonlyArray<string>) {} // ReadonlyArray is not immutable
function set(arg: ReadonlySet<string>) {} // ReadonlySet is not immutable
function map(arg: ReadonlyMap<string>) {} // ReadonlyMap is not immutable
```

**correct**:

<!-- eslint-disable functional/prefer-immutable-parameter-types -->

```ts
/* eslint functional/prefer-immutable-parameter-types: ["error", { "enforcement": "Immutable" }] */

function set(arg: Readonly<ReadonlySet<string>>) {}
function map(arg: Readonly<ReadonlyMap<string>>) {}
function object(arg: Readonly<{ prop: string }>) {}
```

<!-- eslint-disable functional/prefer-immutable-parameter-types -->

```ts
/* eslint functional/prefer-immutable-parameter-types: ["error", { "enforcement": "ReadonlyShallow" }] */

function array(arg: ReadonlyArray<{ foo: string; }>) {}
function set(arg: ReadonlySet<{ foo: string; }>) {}
function map(arg: ReadonlyMap<{ foo: string; }>) {}
function object(arg: Readonly<{ prop: { foo: string; }; }>) {}
```
41 changes: 41 additions & 0 deletions docs/rules/settings/immutability.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,41 @@
# Using the `immutability` setting

We are using the
[is-immutable-type](https://www.npmjs.com/package/is-immutable-type) library to
determine the immutability of types. This library can be configure for all rules
at once using a shared setting.

## Overrides

For details see [the overrides
section](https://github.com/RebeccaStevens/is-immutable-type#overrides) of
[is-immutable-type](https://www.npmjs.com/package/is-immutable-type).

### Example of configuring immutability overrides

In this example, we are configuring
[is-immutable-type](https://www.npmjs.com/package/is-immutable-type) to treat
any readonly array (regardless of the syntax used) as immutable in the case
where it was found to be deeply readonly. If it was only found to be shallowly
readonly, then no override will be applied.

```jsonc
// .eslintrc.json
{
// ...
"settings": {
"immutability": {
"overrides": [
{
"name": "ReadonlyArray",
"to": "Immutable",
"from": "ReadonlyDeep"
}
]
}
},
"rules": {
// ...
}
}
```
Loading

0 comments on commit 3b4dc49

Please sign in to comment.