gcanti / io-ts

Runtime type system for IO decoding/encoding
https://gcanti.github.io/io-ts/
MIT License
6.68k stars 331 forks source link

make Type, Props etc immutable #676

Open adrian-gierakowski opened 1 year ago

adrian-gierakowski commented 1 year ago

šŸš€ Feature request

The goal of this request is to be able to use io-ts in projects where @typescript-eslint/prefer-readonly-parameter-types is enabled (without having to disable the rule each time various io-ts types are used as function parameters).

Current Behavior

I get a linter error when declaring functions parameter of certain io-ts types

Desired Behavior

No linter errors

Suggested Solution

Currently Props and other cases where [key: string]: T is used are mutable. These should be replaced by readonly versions.

Additionally, class methods on Type (and possibly others) are not immutable (see: this issue). A workaround would be the define methods as readonly properties. For example:

  readonly asDecoder = function asDecoder(this: Type<A, O, I>): Decoder<I, A> {
    return this
  }

instead of:

https://github.com/gcanti/io-ts/blob/4edbeb35f2c808156b31bb11aaf6a0cb783e8963/src/index.ts#L196-L198

Who does this impact? Who is this for?

All io-ts users who wish to enable @typescript-eslint/prefer-readonly-parameter-types

Describe alternatives you've considered

Additional context

Your environment

Software Version(s)
io-ts ^2.2.19
fp-ts ^2.5.0
TypeScript 4.7, 4.8
ragnese commented 1 year ago

I'm not affiliated with this project, so my opinion doesn't matter.

I don't see any harm in marking a bunch of these fields as readonly, of course (why would someone need to modify, e.g., a props value?)...

But, I really feel like readonly on object properties is such a broken feature of TypeScript that it's not worth even using it at all except maybe as a library author simply to communicate intent to consumers of your public API. Here's why readonly is basically useless:

type Foo = {
    i: number
}

type ReadonlyFoo {
    readonly i: number
}

function useFoo(foo: ReadonlyFoo) {
    /* The next line would give a compile error--darn.
     * foo.i = 3
     */
    // But these lines won't!
    const mutableFoo: Foo = foo
    mutableFoo.i = 3 // mutates the input argument!
}

Add to that the pain of working with some APIs that use readonly and some that don't, and it can get pretty tedious for very little "safety".

Not that readonly arrays actually work mostly okay because the TypeScript compiler has lots of custom analysis for them, and knows what standard library methods are mutating, etc. For some reason, they just haven't implemented readonly on objects very well, IMO.

Basically, my point is that I think that ESLint lint is not worth using, anyway.

adrian-gierakowski commented 1 year ago

Yes, Iā€™m ware of these issue and kind of gave up on this mainly due to issues which arise when interfacing with 3rd party libs