diff --git a/types/ramda/index.d.ts b/types/ramda/index.d.ts index 9849ee48b1..e3eda424ec 100644 --- a/types/ramda/index.d.ts +++ b/types/ramda/index.d.ts @@ -20,6 +20,7 @@ // Keagan McClelland // Tomas Szabo // Bonggyun Lee +// Marcin Biernat // Definitions: https://github.com/DefinitelyTyped/DefinitelyTyped // TypeScript Version: 2.8 @@ -82,9 +83,28 @@ declare namespace R { (obj: Dictionary): Dictionary; } - type Evolver = - | ((x: T) => T) - | { [K in keyof T]?: Evolver }; + type Evolve, E extends Evolver> = { + [P in keyof O]: P extends keyof E ? EvolveValue : O[P]; + }; + + type EvolveValue = + E extends (value: V) => any ? ReturnType : + E extends Evolver ? EvolveNestedValue : + never; + + type EvolveNestedValue = + V extends object ? (V extends Evolvable ? Evolve : never) : never; + + interface Evolver { + [key: string]: ((value: any) => any) | Evolver; + } + + // Represents all objects evolvable with Evolver E + type Evolvable = { + [P in keyof E]?: E[P] extends (value: infer V) => any ? V : + E[P] extends Evolver ? Evolvable : + never + }; // @see https://gist.github.com/donnut/fd56232da58d25ceecf1, comment by @albrow interface CurriedTypeGuard2 { @@ -604,8 +624,8 @@ declare namespace R { /** * Creates a new object by evolving a shallow copy of object, according to the transformation functions. */ - evolve(transformations: Evolver, obj: V): V; - evolve(transformations: Evolver): (obj: W) => W; + evolve>(transformations: E, obj: V): Evolve; + evolve(transformations: E): >(obj: V) => Evolve; /* * A function that always returns false. Any passed in parameters are ignored. diff --git a/types/ramda/ramda-tests.ts b/types/ramda/ramda-tests.ts index 8cbbf9ceac..ff19276316 100644 --- a/types/ramda/ramda-tests.ts +++ b/types/ramda/ramda-tests.ts @@ -1307,19 +1307,55 @@ type Pair = KeyValuePair; }; () => { - const a1 = R.evolve({elapsed: R.add(1), remaining: R.add(-1)}, {name: "Tomato", elapsed: 100, remaining: 1400}); - const a2 = R.evolve({elapsed: R.add(1), remaining: R.add(-1)})({name: "Tomato", elapsed: 100, remaining: 1400}); -}; + // No type transformation -() => { - // const tomato = {firstName: 'Tomato ', data: {elapsed: 100, remaining: 1400}, id:123}; - // const transformations = { - // firstName: R.trim, - // lastName: R.trim, // Will not get invoked. - // data: {elapsed: R.add(1), remaining: R.add(-1)} - // }; - // const a = R.evolve(transformations, tomato); // => {firstName: 'Tomato', data: {elapsed: 101, remaining: 1399}, id:123} - // const b = R.evolve(transformations)(tomato); // => {firstName: 'Tomato', data: {elapsed: 101, remaining: 1399}, id:123} + const a1 = R.evolve({ elapsed: R.add(1), remaining: R.add(-1) }, { name: "Tomato", elapsed: 100, remaining: 1400 }); + + const a1Test: { elapsed: number, remaining: number, name: string } = a1; + + const a2 = R.evolve({ elapsed: R.add(1), remaining: R.add(-1) })({ name: "Tomato", elapsed: 100, remaining: 1400 }); + + const a2Test: { elapsed: number, remaining: number, name: string } = a2; + + // Object doesn't have all evolver keys + + const a3 = R.evolve({ age: R.add(1), name: R.trim }, { name: "Potato", elapsed: 100 }); + + const a3Test: { name: string, elapsed: number } = a3; + + // Flat transformation + + const ex0 = R.evolve({ a: parseInt }, { a: '10', b: 1 }); + + const ex0Test: { a: number, b: number } = ex0; + + // Nested transformation: + + const ex1 = R.evolve( + { a: { b: R.toString, d: { e: R.toString } } }, + { a: { b: 1, c: null, d: { e: 2 } } }, + ); + + const ex1Test: { a: { b: string, c: null, d: { e: string } } } = ex1; + + // Mapping a nested object with a single function + + const ex2 = R.evolve( + { a: (obj: { foo: string }) => ({ bar: 1, baz: 2 }) }, + { a: { foo: 'a', skipped: 3 }, b: null }, + ); + + const ex2Test: { a: { bar: number, baz: number }, b: null } = ex2; + + // Nested curried: + + const ex3 = R.evolve( + { a: { b: R.toString, d: { e: R.toString } } }, + )( + { a: { b: 1, c: null, d: { e: 2 } } } + ); + + const ex3Test: { a: { b: string, c: null, d: { e: string } } } = ex3; }; () => {