Important: This documentation covers Yarn 1 (Classic).
For Yarn 2+ docs and migration guide, see yarnpkg.com.

Package detail

is-immutable-type

RebeccaStevens995.8kBSD-3-Clause5.0.1TypeScript support: included

Check the immutability of TypeScript types

readonly, immutable, type, typescript

readme

is-immutable-type

npm version Release Coverage Status\ code style: prettier GitHub Discussions BSD 3 Clause license Commitizen friendly semantic-release

Test the immutability of TypeScript types.

Any donations would be much appreciated. 😄

Installation

# Install with npm
npm install is-immutable-type

# Install with yarn
yarn add is-immutable-type

# Install with pnpm
pnpm add is-immutable-type

Usage

import {
  Immutability,
  getTypeImmutability,
  isReadonlyDeep,
  isUnknown,
} from "is-immutable-type";
import { hasType } from "ts-api-utils";
import type ts from "typescript";

function example(program: ts.Program, node: ts.Node) {
  const typeNodeOrType = hasType(node)
    ? // Use the TypeNode if it's avaliable.
      node.type
    : // Otherwise, get the Type.
      program.getTypeChecker().getTypeAtLocation(node);

  const immutability = getTypeImmutability(program, typeNodeOrType);

  if (isUnknown(immutability)) {
    console.log("`immutability` is `Unknown`");
  } else if (isReadonlyDeep(immutability)) {
    console.log("`immutability` is `ReadonlyDeep` or `Immutable`");
  } else {
    console.log("`immutability` is `ReadonlyShallow` or `Mutable`");
  }
}

Tip: You can also use comparator expressions (such as > and <) to compare Immutability.\ Note: Immutability.Unknown will always return false when used in a comparator expression. This includes === - use isUnknown() if you need to test if a value is Unknown.

Immutability

Definitions

  • Immutable: Everything is deeply read-only and nothing can be modified.
  • ReadonlyDeep: The data is deeply immutable but methods are not.
  • ReadonlyShallow: The data is shallowly immutable, but at least one deep value is not.
  • Mutable: The data is shallowly mutable.
  • Unknown: We couldn't determine the immutability of the type.

Note: Calculating is used internally to mean that we are still calculating the immutability of the type. You shouldn't ever need to use this value.

Overrides

Sometimes we cannot correctly tell what a type's immutability is supposed to be just by analyzing its type makeup. One common reason for this is because methods may modify internal state and we cannot tell this just by the method's type. For this reason, we allow types to be overridden.

To override a type, pass an overrides array of all the override objects you want to use to your function call.\ An override object consists of a type, a to and optionally a from property. The type property specifies the type that will be overridden.\ The to property specifies the new immutability value that will be used.\ The from property, if given, will limit when the override is applied to when the calculated immutability is between the to value and this value (inclusively).

The type is specified with a TypeSpecifier.\ This can either be a string that will match against the type's name or a regex pattern that will match against the type's name and any type arguments.\ Additionally, you can specify where the type needs to come from for it to be overridden.\ To do this, use an object, with either a name or pattern value; and a from property. This from property specifies where the type needs to come from, either lib (TypeScript's lib), package (a node_modules package), or file (a local file).

Example 1

Always treat TypeScript's ReadonlyArrays as Immutable.

[
  {
    type: { from: "lib", name: "ReadonlyArray" },
    to: Immutability.Immutable,
  },
];

Example 2

Treat TypeScript's ReadonlyArrays as Immutable instead of ReadonlyDeep. But if the instance type was calculated as ReadonlyShallow, it will stay as such.

[
  {
    type: { from: "lib", name: "ReadonlyArray" },
    to: Immutability.Immutable,
    from: Immutability.ReadonlyDeep,
  },
];

Default Overrides

By default, the following TypeScript lib types are overridden to be Mutable:

  • Map
  • Set
  • Date
  • URL
  • URLSearchParams

If you know of any other TypeScript lib types that need to be overridden, please open an issue.

Note: When providing custom overrides, the default ones will not be used. Be sure to include the default overrides in your custom overrides if you don't want to lose them. You can obtain them with getDefaultOverrides().

Another Use for Overrides

Currently, due to limitations in TypeScript, it is impossible to write a utility type that will transform any given type to an immutable version of it in all cases. (See this issue)

One popular implementation of such a utility type is type-fest's ReadonlyDeep type. If you want this library to treat types wrapped in ReadonlyDeep as immutable regardless, you can provide an override stating as such.

[
  {
    type: {
      from: "package",
      package: "type-fest",
      pattern: /^ReadonlyDeep<.+>$/u,
    },
    to: Immutability.Immutable,
  },
];

Caching

By default, we use a global cache to speed up the calculation of multiple types' immutability. This prevents us from needing to calculate the immutability of the same types over and over again.

However, this cache assumes you are always using the same type checker. If you need to use multiple (such as in a testing environment), this can lead to issues. To prevent this, you can provide a custom cache (by passing a WeakMap) to be used or have a temporary cache be used (by passing false).

Making ReadonlyDeep types Immutable

Many types that you may expect to be immutable (including those defined internally by TypeScript itself) are not written with immutable methods and thus are not reported as immutable by this library. Luckily it is quite easy to make such type immutable. Just simply wrap them in Readonly.

Example

These types are ReadonlyDeep:

type Foo = ReadonlySet<string>;
type Bar = ReadonlyMap<string, number>;

While these types are Immutable:

type Foo = Readonly<ReadonlySet<string>>;
type Bar = Readonly<ReadonlyMap<string, number>>;

However, it should be noted that this does not work for arrays. TypeScript will treat Readonly<Array<T>> exactly the same as ReadonlyArray<T> and as a consequence Readonly<ReadonlyArray<T>> is also treated the same.

In order to get around this, we need to slightly tweak the Readonly definition like so:

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

Now the following will correctly be marked as Immutable.

type Foo = ImmutableShallow<readonly string[]>;
type Bar = ImmutableShallow<ReadonlyArray<string>>;

Note: ImmutableShallow<string[]> will also be marked as immutable but the type will still have methods such as push and pop. Be sure to pass a readonly array to ImmutableShallow to prevent this.

changelog

Changelog

All notable changes to this project will be documented in this file. Dates are displayed in UTC.

5.0.1 (2025-01-01)

5.0.0 (2024-08-03)

Bug Fixes

BREAKING CHANGES

  • drop support for typescript-eslint v7 in favor typescript-eslint v8.

4.0.0 (2024-04-22)

Features

BREAKING CHANGES

  • TypeMatchesPatternSpecifier's signature has changed
  • TypeMatchesPatternSpecifier's signature has changed

3.1.0 (2024-04-15)

Features

  • allow for overriding how we check if a type matches a specifier (b3bb9f3)
  • allow for overriding how we check if a type matches a specifier (#406) (9480a37)

3.0.0 (2024-04-15)

Performance Improvements

  • simplify how type are converted to string (2b545e4)
  • simplify how type are converted to string (#405) (39d5d21)

BREAKING CHANGES

  • types aren't processed as much and thus not all override patterns are still supported
  • types aren't processed as much and thus not all override patterns are still supported

2.1.1 (2024-04-14)

Bug Fixes

  • use safe way of getting an identifier's text (18ef5e0)

2.1.0 (2024-04-14)

Features

  • file specifiers can now use globs to match the file path (d4e60a2)
  • file specifiers can now use globs to match the file path (#404) (cf68cc0)

2.0.6 (2024-04-06)

Bug Fixes

2.0.5 (2024-04-04)

Bug Fixes

  • improve comparison of file paths (ce76598)

2.0.4 (2024-03-22)

Bug Fixes

  • regression in getting a type's name (3864987)

2.0.3 (2024-03-22)

Bug Fixes

  • infinite loop in typeToString (4f87951)
  • remove another infinate loop (c36259c)

2.0.2 (2024-03-19)

Performance Improvements

2.0.1 (2023-07-15)

Bug Fixes

  • type name not matching specifier when it needs to be evaluated (c42bb73)

2.0.0 (2023-07-15)

BREAKING CHANGES

  • compatibility with typescript-eslint v6 is now required

1.2.9 (2023-03-20)

Bug Fixes

1.2.8 (2023-03-14)

Bug Fixes

  • @typescript-eslint/type-utils is no longer a peer dependency (#103) (c1793cc)

1.2.7 (2023-03-14)

Bug Fixes

  • typeArgumentsToString: use evaluated type if its name isn't available (#101) (988f34a)
  • use a recursion identity to test if a type has already been looked at (#100) (fb2aaa5)

1.2.6 (2023-03-14)

Bug Fixes

  • remove @typescript-eslint/utils peerdep (#99) (cbf0b7a)

1.2.5 (2023-03-11)

Bug Fixes

  • support for recursive types in the form type Foo = ReadonlyArray<Foo> (#88) (a2582a7)

1.2.4 (2023-02-02)

Bug Fixes

  • improve calculation of immutability involving intersections (2d86f3d), closes #40
  • increase the list of default overrides (7dacc68), closes #41

1.2.3 (2022-11-11)

Bug Fixes

  • support for circular index signatures (8e13105)

1.2.2 (2022-10-20)

Bug Fixes

  • detection of readonly modifier (18070a8)

1.2.1 (2022-10-19)

Bug Fixes

  • remove reliance on tsutils as it no longer seems to be maintained (c759e91)

1.2.0 (2022-10-13)

Features

  • allow passing a TypeNode instead of a Type (46160e8)

1.1.0 (2022-10-12)

Features

  • add option maxImmutability to getTypeImmutability (00449da)

1.0.2 (2022-10-04)

Bug Fixes

  • allow for overrides to test against the evaluated type (21f8fa8)

1.0.1 (2022-10-04)

Bug Fixes

  • arguments as string sometimes being wrapped in double brackets <> (32e69fb)

1.0.0 (2022-09-24)

0.0.7 (2022-09-15)

Bug Fixes

  • improve ImmutablenessOverrides XOR typings (175a0fc)
  • simplify early escape of override (f28ac12)