Skip to content

Disallow the use of enums #417

@mcmire

Description

@mcmire

We have had multiple discussions in the past on disallowing the use of enums. We should follow up on this.

Why enums are useful

Enums help in the case when you have a collection of possible values and you want to change one of them without needing to update all of the references.

The popular example is this. Say you need to define a set of acceptable HTTP methods. You can use an enum to declare this:

enum HttpMethod {
  Get = 'GET',
  Post = 'POST',
}

And say you have a bunch of places in your code that reference one of these values, e.g. HttpMethod.Post.

But now say that you want to change the value, e.g. HttpMethod.Post is 'post' instead of 'POST'. If you were using literal strings, you'd have to go and change all of them, but now you could merely change it in the enum, and everything would "just work".

Enums also help when using a switch statement to handle possibilities as TypeScript can enforce an exhaustive check. TypeScript also uses exhaustive checks with type unions, so it isn't specific to enums.

Why enums are not useful

Enums have a surprisingly large number of features that are bloat at best and dangerous at worst.

At the same time, there are a number of known annoyances and pitfalls with using enums:

Besides this, enums violate one of the fundamental rules of TypeScript, which is that TypeScript should only ever be a layer on top of TypeScript, and that it should be possible to remove that layer without affecting the underlying code. Everything in TypeScript works this way — except enums (this is even noted at the very top of the section on enums in the TypeScript handbook).

Looking ahead

As of 22.18, Node can run TypeScript code using type erasure, obviating the need to run TypeScript through a transform step (Babel, SWC, esbuild, or whatever the "fast TypeScript runner du jour" is). If we used this, it could theoretically save a bunch of time in CI and remove some tooling bloat. We could also turn on the --erasableSyntaxOnly option introduced in TypeScript 5.8 to enforce this.

To make use of this, we would need to stop using enums.

How to stop using enums

What's the alternative to enums? This guide outlines a few ways, but essentially we would have two options:

  • Use a literal object + as const + type. This might look something like:
    const HTTP_METHODS = {
      Get: 'GET',
      Post: 'POST',
    } as const;
    type HttpMethod = (typeof HTTP_METHODS)[keyof typeof HTTP_METHODS];
  • Use a literal array + as const + a type union:
    const HTTP_METHODS = ['GET', 'POST'] as const;
    type HttpMethod = (typeof HTTP_METHODS)[number];

We could also disallow enums via a lint rule:

{
  "rules": {
    "no-restricted-syntax": [
      "error",
      {
        "selector": "TSEnumDeclaration",
        "message": "Don't declare enums"
      }
    ]
  }
}

Caveats

  • If we switch away from enums we may not be able to enforce member names as we do now.

Metadata

Metadata

Assignees

No one assigned

    Labels

    No labels
    No labels

    Type

    No type

    Projects

    No projects

    Milestone

    No milestone

    Relationships

    None yet

    Development

    No branches or pull requests

    Issue actions