agiledigital / readonly-types

A collection of readonly TypeScript types inspired by the built-in ReadonlyArray, ReadonlyMap, etc.
MIT License
15 stars 2 forks source link

Provide a truly immutable version of ReadonlyArray #518

Closed danielnixon closed 1 year ago

danielnixon commented 1 year ago

ReadonlyArray is mutable for the same reason we provide ImmutableSet and ImmutableMap - method syntax.

Here's an example - this compiles:

const foo: ReadonlyArray<string> = ["asdf"] as const;

// eslint-disable-next-line functional/no-expression-statements, functional/immutable-data, functional/functional-parameters
foo.every = () => false;
// eslint-disable-next-line functional/no-expression-statements, functional/immutable-data, functional/functional-parameters
foo.at = () => undefined;

Unfortunately, due to what I assume is a special case rule (because arrays get special treatment by the compiler), Readonly does not fix the problem for ReadonlyArray the way it does for ReadonlySet and ReadonlyMap and any other type.

This still compiles:

// eslint-disable-next-line functional/type-declaration-immutability
export type ImmutableArray<T> = Readonly<ReadonlyArray<T>>;

const foo: ImmutableArray<string> = ["asdf"] as const;

// eslint-disable-next-line functional/no-expression-statements, functional/immutable-data, functional/functional-parameters
foo.every = () => false;
// eslint-disable-next-line functional/no-expression-statements, functional/immutable-data, functional/functional-parameters
foo.at = () => undefined;

If we want an ImmutableArray (i.e. one that isn't flagged by the type-declaration-immutability eslint rule), we will have to copy all of the ReadonlyArray methods from lib.es5.d.ts (and elsewhere probably) and replace them with readonly properties by hand.

danielnixon commented 1 year ago

In the meantime, let's configure this override: https://github.com/eslint-functional/eslint-plugin-functional/blob/main/docs/rules/settings/immutability.md#example-of-configuring-immutability-overrides

danielnixon commented 1 year ago

If we did go down the path of creating a copy of of the ReadonlyArray type by hand, that would be the time to solve this too https://github.com/agiledigital/readonly-types/issues/7 .

danielnixon commented 1 year ago

Other builtins/primitives are similarly "ignored" by Readonly. For example, these all compile:

type Foo = Readonly<number>;

const a: Foo = 1;

// eslint-disable-next-line functional/no-expression-statements, functional/immutable-data, functional/functional-parameters
a.toString = () => "";
type Foo = Readonly<bigint>;

const a: Foo = BigInt(1);

// eslint-disable-next-line functional/no-expression-statements, functional/immutable-data, functional/functional-parameters
a.toString = () => "";
danielnixon commented 1 year ago

Oh, the answer was right in front of my eyes, courtesy of is-immutable-type.

image

type ImmutableShallow<T extends {}> = {
  readonly [P in keyof T & {}]: T[P];
};