diff --git a/test/assoc.test.ts b/test/assoc.test.ts new file mode 100644 index 0000000..324b1cc --- /dev/null +++ b/test/assoc.test.ts @@ -0,0 +1,147 @@ +import { expectType, expectError } from 'tsd'; +import { __, assoc } from '../es'; + +type Obj = { + str: string; + num: number; +}; + +const obj: Obj = { str: 'foo', num: 1 }; + +// +// assoc(key) +// + +// assoc(key)(__, obj)(val) +expectType(assoc('str')(__, obj)('bar')); +// fails for wrong value type +expectError(assoc('str')(__, obj)(2)); +// fails if key not unknown +expectError(assoc('what')(__, obj)('bar')); +// Record works as expected +expectType>(assoc('what')(__, {} as Record)(2)); + +// assoc(key)(val, obj) +expectType(assoc('str')('bar', obj)); +// fails for wrong value type +expectError(assoc('str')(2, obj)); +// fails if key not unknown +expectError(assoc('what')('bar', obj)); +// Record works as expected +expectType>(assoc('what')(2, {} as Record)); + +// assoc(key)(val)(obj) +expectType(assoc('str')('bar')(obj)); +// fails for wrong value type +expectError(assoc('str')(2)(obj)); +// fails if key not unknown +expectError(assoc('what')('foo')(obj)); +// Record works as expected +expectType>(assoc('what')(2)({} as Record)); + + +// +// assoc(__, val) +// + +// assoc(__, val)(key)(obj) +expectType(assoc(__, 'bar')('str')(obj)); +// fails for wrong value type +expectError(assoc(__, 2)('str')(obj)); +// fails if key not unknown +expectError(assoc(__, 'bar')('what')(obj)); +// Record works as expected +expectType>(assoc(__, 2)('what')({} as Record)); + +// assoc(__, val)(__, key)(obj) +expectType(assoc(__, 'bar')(__, obj)('str')); +// fails for wrong value type +expectError(assoc(__, 2)(__, obj)('str')); +// fails if key not unknown +expectError(assoc(__, 'bar')(__, obj)('what')); +// Record works as expected +expectType>(assoc(__, 2)(__, {} as Record)('str')); + +// assoc(__, val)(key, obj) +expectType(assoc(__, 'bar')('str', obj)); +// fails for wrong value type +expectError(assoc(__, 2)('str', obj)); +// fails if key not unknown +expectError(assoc(__, 'bar')('what', obj)); +// Record works as expected +expectType>(assoc(__, 2)('str', {} as Record)); + +// +// assoc(key, val) +// + +// assoc(key, val)(obj) +expectType(assoc('str', 'bar')(obj)); +// fails for wrong value type +expectError(assoc('str', 2)(obj)); +// fails if key not unknown +expectError(assoc('what', 'bar')(obj)); +// Record works as expected +expectType>(assoc('str', 2)({} as Record)); + +// +// assoc__, __, obj) +// + +// assoc(__, __, obj)(key)(val) +expectType(assoc(__, __, obj)('str')('bar')); +// fails for wrong value type +expectError(assoc(__, __, obj)('str')(2)); +// fails if key not unknown +expectError(assoc(__, __, obj)('what')('bar')); +// Record works as expected +expectType>(assoc(__, __, {} as Record)('str')(2)); + +// assoc(__, __, obj)(__, val)(key) +expectType(assoc(__, __, obj)(__, 'bar')('str')); +// fails for wrong value type +expectError(assoc(__, __, obj)(__, 2)('str')); +// fails if key not unknown +expectError(assoc(__, __, obj)(__, 'bar')('what')); +// Record works as expected +expectType>(assoc(__, __, {} as Record)(__, 2)('str')); + +// assoc(__, __, obj)(key, val) +expectType(assoc(__, __, obj)('str', 'bar')); +// fails for wrong value type +expectError(assoc(__, __, obj)('str', 2)); +// fails if key not unknown +expectError(assoc(__, __, obj)('what', 'bar')); +// Record works as expected +expectType>(assoc(__, __, {} as Record)('str', 2)); + +// +// rest +// + +// assoc(__, val, obj)(prop) +expectType(assoc(__,'bar', obj)('str')); +// fails for wrong value type +expectError(assoc(__, 2, obj)('str')); +// fails if key not unknown +expectError(assoc(__, 'bar', obj)('what')); +// Record works as expected +expectType>(assoc(__, 2, {} as Record)('str')); + +// assoc(key, __, obj)(__, val) +expectType(assoc('str', __, obj)('bar')); +// fails for wrong value type +expectError(assoc('str', __, obj)(2)); +// fails if key not unknown +expectError(assoc('what', __, obj)('bar')); +// Record works as expected +expectType>(assoc('str', __, {} as Record)(2)); + +// assoc(key, val, obj) +expectType(assoc('str', 'bar', obj)); +// fails for wrong value type +expectError(assoc('str', 2, obj)); +// fails if key not unknown +expectError(assoc('what', 'bar', obj)); +// Record works as expected +expectType>(assoc('str', 2, {} as Record)); diff --git a/test/dissoc.test.ts b/test/dissoc.test.ts new file mode 100644 index 0000000..639d1ed --- /dev/null +++ b/test/dissoc.test.ts @@ -0,0 +1,65 @@ +import { expectType, expectError } from 'tsd'; +import { __, dissoc } from '../es'; + +// `dissoc` does a `delete obj.key` under the hood, so the behavior for `dissoc` should make that +type Obj = { + str: string; + num: number; + opt?: boolean; + orUndefined: boolean | undefined; + orNull: boolean | null; +}; + +const obj: Obj = { str: 'foo', num: 1, orUndefined: true, orNull: true }; + +// must mark failed operations with ts-expect-error, otherwise `npm run test` fails +// @ts-expect-error +delete obj.str; +// @ts-expect-error +delete obj.num; +delete obj.opt; +delete obj.orUndefined; +// @ts-expect-error +delete obj.orNull; + +// only `opt` and `orUndefined` are allowed operations +// `dissoc` should match that behavior + +expectError(dissoc('str', obj)); +expectError(dissoc('num', obj)); +expectType(dissoc('opt', obj)); +expectType(dissoc('orUndefined', obj)); +expectError(dissoc('orNull', obj)); + +expectError(dissoc('str')(obj)); +expectError(dissoc('num')(obj)); +expectType(dissoc('opt')(obj)); +expectType(dissoc('orUndefined')(obj)); +expectError(dissoc('orNull')(obj)); + +expectError(dissoc(__, obj)('str')); +expectError(dissoc(__, obj)('num')); +expectType(dissoc(__, obj)('opt')); +expectType(dissoc(__, obj)('orUndefined')); +expectError(dissoc(__, obj)('orNull')); + +// Record is allowed +const rec: Record = { foo: 1, bar: 2 }; + +// delete operation is ok for all keys +delete rec.foo; +delete rec.bar; +delete rec.unknownKey; + +// and so are for `dissoc` +dissoc('foo', rec); +dissoc('bar', rec); +dissoc('unknownKey', rec); + +dissoc('foo')(rec); +dissoc('bar')(rec); +dissoc('unknownKey')(rec); + +dissoc(__, rec)('foo'); +dissoc(__, rec)('bar'); +dissoc(__, rec)('unknownKey'); diff --git a/test/modify.test.ts b/test/modify.test.ts new file mode 100644 index 0000000..dbf37d0 --- /dev/null +++ b/test/modify.test.ts @@ -0,0 +1,34 @@ +import { expectError, expectType } from 'tsd'; + +import { modify, toUpper, add, identity, pipe, map } from '../es'; + +type Obj = { + foo: string; + bar: number; +}; + +expectType(modify('foo', toUpper, {} as Obj)); +expectType(modify('bar', add(1), {} as Obj)); +expectType(modify('foo', toUpper)({} as Obj)); +expectType(modify('bar', add(1))({} as Obj)); +expectType(modify('foo')(toUpper)({} as Obj)); +expectType(modify('bar')(add(1))({} as Obj)); +expectType(modify('foo')(toUpper, {} as Obj)); +expectType(modify('bar')(add(1), {} as Obj)); + +// fails when function has wrong argument type +expectError(modify('foo', add(1), {} as Obj)); +expectError(modify('bar', toUpper, {} as Obj)); + +// fails when key does not exist on Obj +expectError(modify('unknownKey', toUpper, {} as Obj)); + +// works with generic fn +expectType(modify('foo', identity, {} as Obj)); + +// pipe and map sanity checks +const f = pipe( + map(modify('foo', toUpper)) +); + +expectType(f([] as Obj[])); diff --git a/types/assoc.d.ts b/types/assoc.d.ts index 1814c87..853f670 100644 --- a/types/assoc.d.ts +++ b/types/assoc.d.ts @@ -1,73 +1,49 @@ import { Placeholder } from './util/tools'; -// assoc(__, val, obj)(prop), this tests if prop is keyof obj and if val is typeof obj[prop] for best return type -export function assoc(__: Placeholder, val: T, obj: U): (prop: K) => K extends keyof U ? T extends U[K] ? U : Record & Omit : U & Record; -// assoc(prop, __, obj)(val), when K is keyof obj, tests if val is typeof obj[prop] for best return type -export function assoc(prop: K, __: Placeholder, obj: U): (val: T) => T extends U[K] ? U : Record & Omit; -// assoc(prop, __, obj)(val), when prop is not keyof obj -export function assoc(prop: K, __: Placeholder, obj: U): (val: T) => U & Record; -// assoc(prop, val, obj) when prop is keyof obj and val is same type -export function assoc(prop: K, val: U[K], obj: U): U; -// assoc(prop, val, obj) when prop is keyof obj and val is not same type -export function assoc(prop: K, val: T, obj: U): Record & Omit; -// assoc(prop, val, obj) when prop is not keyof obj -export function assoc(prop: K, val: T, obj: U): U & Record; +// assoc(prop) +export function assoc(prop: K): { + // assoc(prop)(val)(obj) + (val: T): >(obj: U) => U; + + // assoc(prop)(__, obj)(val) + >(__: Placeholder, obj: U): (val: T) => U; + + // assoc(prop)(val, obj) when obj has key prop, tests if val is typeof obj[prop] for best return type + , T>(val: T, obj: U): U; +}; // assoc(__, val) export function assoc(__: Placeholder, val: T) : { - // assoc(__, val)(__, obj) - (__2: Placeholder, obj: U): { - // assoc(__, val)(__, obj)(prop), prop is keyof obj, tests if val is typeof obj[prop] for best return type - (prop: K): U[K] extends T ? U : Record & Omit; - // assoc(__, val)(__, obj)(prop), prop is not keyof obj - (prop: K): U & Record; - }; - // assoc(__, val)(prop, obj), when obj has key prop, tests if val is typeof obj[prop] for best return type - (prop: K, obj: U): U[K] extends T ? U : Record & Omit; - // assoc(__, val)(prop, obj), when obj does not have key prop - (prop: K, obj: U): U & Record; + // assoc(__, val)(prop)(obj) + (prop: K extends Placeholder ? never : K): >(obj: U) => U; - // assoc(__, val)(prop) - (prop: K): { - // assoc(__, val)(prop)(obj) when obj has key prop, tests if val is typeof obj[prop] for best return type - >(obj: U): U[K] extends T ? U : Record & Omit; - // assoc(__, val)(prop)(obj) when obj does not have key prop - (obj: U): U & Record; - } -}; + // assoc(__, val)(__, obj)(prop) + (__2: Placeholder, obj: U): (prop: T extends U[K] ? K : never) => U; -// assoc(prop, val) -export function assoc(prop: K, val: T) : { - // assoc(prop, val)(obj), when obj has key prop, tests if val is typeof obj[prop] for best return type - >(obj: U): U[K] extends T ? U : Record & Omit; - // assoc(prop, val)(obj), when obj does not have key prop - (obj: U): U & Record; + // assoc(__, val)(prop, obj) + , K extends keyof U>(prop: K, obj: U): U; }; -// assoc(prop) -export function assoc(prop: K): { - // assoc(prop)(__, obj) when prop is keyof obj - >(__: Placeholder, obj: U): { - // assoc(prop)(__, obj)(val) if val is typeof obj[prop] - (val: T): U; - // assoc(prop)(__, obj)(val) if val is not typeof obj[prop] - (val: T): Record & Omit; - } - // assoc(prop)(__, obj) when prop is not keyof obj - (__: Placeholder, obj: U): (val: T) => U & Record; +// assoc(prop, val)(obj) +export function assoc(prop: K extends Placeholder ? never : K, val: T): >(obj: U) => U; - // assoc(prop)(val, obj) when obj has key prop, tests if val is typeof obj[prop] for best return type - >(val: T, obj: U): U[K] extends T ? U : Record & Omit; - // assoc(prop)(val, obj) when obj does not have a key prop - (val: T, obj: U): U & Record; +// assoc (__, __, obj) +export function assoc(__: Placeholder, __2: Placeholder, obj: U): { + // assoc(__, __, obj)(prop)(val) + (prop: K extends Placeholder ? never : K): (val: T) => U; + + // assoc(__, __, obj)(__, val)(prop) + (__: Placeholder, val: T): (prop: T extends U[K] ? K : never) => U; - // assoc(prop)(val) - (val: T): { - // assoc(prop)(val)(obj) when obj has key prop and val is typeof obj[prop] - >(obj: U): U; - // assoc(prop)(val)(obj) when obj has key prop and val is not typeof obj[prop] - >(obj: U): Record & Omit; - // assoc(prop)(val)(obj) when obj does not have key prop - (obj: U): U & Record; - } + // assoc(__, __, obj)(prop, val) + (prop: K, val: T): U; }; + +// assoc(__, val, obj)(prop) +export function assoc(__: Placeholder, val: T, obj: U): (prop: T extends U[K] ? K : never) => U; + +// assoc(prop, __, obj)(val) +export function assoc(prop: K, __: Placeholder, obj: U): (val: T) => U; + +// assoc(prop, val, obj) +export function assoc(prop: K, val: T, obj: U): U; diff --git a/types/dissoc.d.ts b/types/dissoc.d.ts index 08533a4..3d1d680 100644 --- a/types/dissoc.d.ts +++ b/types/dissoc.d.ts @@ -1,2 +1,6 @@ -export function dissoc(prop: K): (obj: T) => Omit; -export function dissoc(prop: K, obj: T): Omit; +import { Placeholder } from './util/tools'; + +// `string extends keyof U` is true only for `Record`, where the keys are not known, something that still needs to be supported +export function dissoc(prop: K extends Placeholder ? never : K): (obj: string extends keyof U ? U : undefined extends U[K] ? U : never) => U; +export function dissoc(__: Placeholder, obj: U): (prop: string extends keyof U ? K : undefined extends U[K] ? K : never) => U; +export function dissoc(prop: string extends keyof U ? K : undefined extends U[K] ? K : never, obj: U): U; diff --git a/types/modify.d.ts b/types/modify.d.ts index fe9a229..28c0737 100644 --- a/types/modify.d.ts +++ b/types/modify.d.ts @@ -1,10 +1,13 @@ -export function modify( - prop: K, - fn: (a: A) => P, -): >(target: T) => Omit & Record; +// modify(prop) +export function modify(prop: K): { + // modify(prop)(fn)(obj) + (fn: (value: T) => T): >(object: U) => U; + // modify(prop)(fn), obj) + >(fn: (value: T) => T, object: U): U; +}; -export function modify( - prop: K, - fn: (a: T[K]) => P, - obj: T, -): Omit & Record; +// modify(prop, fn)(obj) +export function modify(prop: K, fn: (value: T) => T): >(object: U) => U; + +// modify(prop, fn, obj) +export function modify(prop: K, fn: (value: U[K]) => U[K], object: U): U; diff --git a/types/util/tools.d.ts b/types/util/tools.d.ts index c80c6d2..9d70ad3 100644 --- a/types/util/tools.d.ts +++ b/types/util/tools.d.ts @@ -501,6 +501,11 @@ export type WidenLiterals = ? number : T; +/** + * Alias for the common result type for the `assoc` function + */ +export type AssocResults = U extends Record ? T extends U[K] ? U : Omit & Record : Omit & Record; + /** * Extract the types from an array * Works with Tuples, eg `ElementOf` => `'p1' | 'p2'`