2

I am trying to create a helper type which can convert a function of type

type Func1 = (id: string) => void;

to

type Func2 = (id: string, conf: { extraArg: boolean }) => void;

I found this answer which does work, however it names the first argument as head and the following one would be named id if I were to apply my example which is very misleading.

I cranked up this code a bit for my purposes

type Cons<T extends readonly any[], H> =
        ((head: H, ...tail: T) => void) extends ((...cons: infer R) => void) ? R : never;

type Push<T extends readonly any[], V>
        = T extends any ? Cons<T, void> extends infer U ?
            { [K in keyof U]: K extends keyof T ? T[K] : V } : never : never;

export type FunctionWithExtraArgument<F extends (...args: any[]) => any, Arg, R> = (...args: Push<Parameters<F>, Arg>) => R;

However I still couldn't get a complete solution which resolves the naming problem.

Rinta
  • 71
  • 5

1 Answers1

4

No need to use code from above answer now. At the time of writing this answer there was not variadic tuple types.

Now you can achieve it with help of only one util:

type Func1 = (id: string) => void;


type Func2 = (id: string, conf: { extraArg: boolean }) => void;

type AddArgument<
  Fn extends (...args: any[]) => any,
  NextArg
  > =
  Fn extends (...arg: [...infer PrevArg]) => infer Return
  ? (...args: [...PrevArg, NextArg]) => Return
  : never;

// type Result = (args_0: string, args_1: {
//     extraArg: boolean;
// }) => void
type Result = AddArgument<Func1, { extraArg: boolean }>

Playground

If you want to you will always operate on one argument function, you can use this code:

type Func1 = (id: string) => void;


type Func2 = (id: string, conf: { extraArg: boolean }) => void;

type AddArgument<
  Fn extends (arg: any) => any,
  Conf
  > =
  Fn extends (id: infer Id) => infer Return
  ? (id: Id, conf: Conf) => Return
  : never;

// type Result = (id: string, conf: {
//     extraArg: boolean;
// }) => void
type Result = AddArgument<Func1, { extraArg: boolean }>

if I would have to "hardcode" every variable preceding the "conf" as I use 2-3 variables in a couple of cases

You can expect second generic argument as a tuple:

type Func1 = (id: string) => void;


type Func2 = (id: string, conf: { extraArg: boolean }) => void;

type AddArgument<
  Fn extends (...args: any[]) => any,
  NextArg extends any[]
  > =
  Fn extends (...arg: [...infer PrevArg]) => infer Return
  ? (...args: [...PrevArg, ...NextArg]) => Return
  : never;

// type Result = (args_0: string, args_1: {
//     extraArg: boolean;
// }) => void
type Result = AddArgument<Func1, [{ extraArg: boolean }, { anotherArg: 42 }]>
  • Thanks for the answer! I tried doing it with the above mentioned variadic tuple types. However using this feature still gives me the same problem - even though the variables are not misnamed anymore (which is good), it tampers with their names making them unrecognisable. – Rinta Oct 11 '21 at 16:38
  • The second example seems more suitable for my case but I wonder if I would have to "hardcode" every variable preceding the "conf" as I use 2-3 variables in a couple of cases – Rinta Oct 11 '21 at 16:40
  • @Rinta I made an update. If you are confused by the name of argument - you should not worry to much, it does not affect the type itself. – captain-yossarian from Ukraine Oct 11 '21 at 16:47
  • It seems like variadic tuple types is not the ideal way to go in my case. I want to be able to see the names of the variables I should pass to the function, especially if there's a variable amount of them. While the types you suggested do in fact work, it still isn't enough unfortunately. There may be no way of doing what I am aiming for as of now without just extending the second variant you provided, which is hardcoding the names of the arguments – Rinta Oct 12 '21 at 10:42