sindresorhus / type-fest

A collection of essential TypeScript types
Creative Commons Zero v1.0 Universal
14.03k stars 532 forks source link

Cannot copy Date type via CamelCasedPropertiesDeep #326

Open David245M opened 2 years ago

David245M commented 2 years ago

I'm currently using CamelCasedPropertiesDeep type and I found that it's not copy Date type, instead it creates copy of it. I've got this error

Argument of type '{ toString: () => string; toDateString
string; toLocaleString: { (): string; (locales?: string | string[] | undefined, options?: DateTimeFormatOptions | undefineON: (key?: any) => string; }' is not assignable to parameter of type 'number | Date'.

So I tried it with another standard build-in object types and it works with none of them.

interface ExampleType {
    bool: Boolean
    sym: Symbol
    error: Error
    num: Number
    bigInt: BigInt
    math: Math
    date: Date
    str: String
    regExp: RegExp
    array: Int8Array
    map: Map<any,any>
    weakMap: WeakMap<any, any>
    weakSet: WeakSet<any>
    json: JSON
    promise: Promise<any>
    generator: Generator
    dateTimeFormat: Intl.DateTimeFormat
}

type CamelizedExample = CamelCasedPropertiesDeep<ExampleType>

Here you can try to camelize keys of an object I've present above and notice this issue.

Upvote & Fund

Fund with Polar

voxpelli commented 2 years ago

I think easiest solution here would be to add a new exclude parameter like CamelCasedPropertiesDeep<ExampleType, String | Date>

Some of these should be getting built in support though, like Promise, Map and maybe Date.

David245M commented 2 years ago

@voxpelli I'm thinking about add check like

 : Value extends Map<infer Key, infer Val>
 ? Map<CamelCasedPropertiesDeep<Key>, Val>
 : Value extends StandartBuildInTypes
 ? Value
 : {
    ...
 }

This will copy type instead of iterating it and creating the new ones. StandartBuildInTypes is just the union of standart types excluding Object and iterative types. What do you think? I've searching for the way to distinguish types like Date and RegExp from Object for the whole day, but I haven't found any good way except I've describe above.

voxpelli commented 2 years ago

A problem with that way is how TypeScript defines object: That its pure duck typing: As long as it looks like a duck, quacks like a duck, it is a duck.

See eg this playground.

A Value extends Boolean matches not only Boolean, but also the NonFake2 and FakeBoolean in that example:

interface NonFake {
    test: 'test'
}

interface NonFake2 {
    test: 'test'
    valueOf: () => boolean
}

interface FakeBoolean {
    valueOf: () => boolean
}

In other words: Any object that implements valueOf: () => boolean.

Because of that we can't really add these basic ones in my opinion, but we can add the more complex ones which are less likely to cause false positives.

David245M commented 2 years ago

I think easiest solution here would be to add a new exclude parameter like CamelCasedPropertiesDeep<ExampleType, String | Date>

Did you mean create second optional type parameter that will be exceptional to deep copy?

voxpelli commented 2 years ago

I think easiest solution here would be to add a new exclude parameter like CamelCasedPropertiesDeep<ExampleType, String | Date>

Did you mean create second optional type parameter that will be exceptional to deep copy?

Yes, because then one adds it when one only adds it when one knows it won't create any false matches.

David245M commented 2 years ago

@voxpelli https://github.com/sindresorhus/type-fest/pull/327

sindresorhus commented 2 years ago

It should use the same solution as done here: https://github.com/sdotson/type-fest/blob/da1a717790396afb0fab16dfc61f6d3df5d0b6fb/source/delimiter-cased-properties-deep.d.ts#L48