Challenge

Drop a specified char from a string. For example:

type Butterfly = DropChar<" b u t t e r f l y ! ", " ">; // 'butterfly!'

Solution

To solve the challenge, we need to know about template literal types. You can read more about them in the TypeScript Handbook.

When using template literal types, we can infer the needed parts from the string and check if the part is what we expect. Let’s start with the simplest case - infer the left part and the right part of the string. The delimiter between them is the needed char itself.

type DropChar<S, C> = S extends `${infer L}${C}${infer R}` ? never : never;

With such a notation, we are getting a compilation error “Type ‘C’ is not assignable to type ‘string | number | bigint | boolean | null | undefined’.“. Adding a generic constraint will fix it.

type DropChar<S, C extends string> = S extends `${infer L}${C}${infer R}`
  ? never
  : never;

Now, we can see clearly that we have all the parts and the character from C between them. Since we need to remove the C, we return the left and right parts without it.

type DropChar<S, C extends string> = S extends `${infer L}${C}${infer R}`
  ? `${L}${R}`
  : never;

That way, we dropped one character from the string. To drop others, we need to continue dropping them by calling the type recursively.

type DropChar<S, C extends string> = S extends `${infer L}${C}${infer R}`
  ? DropChar<`${L}${R}`, C>
  : never;

We covered all the cases, except the case when there are no patterns we look for. If so, we just passing through the incoming string itself.

type DropChar<S, C extends string> = S extends `${infer L}${C}${infer R}`
  ? DropChar<`${L}${R}`, C>
  : S;

References