sindresorhus / type-fest

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

Multi-replace type #589

Open mmkal opened 1 year ago

mmkal commented 1 year ago

Would be nice to have a type that does something this:

const escaped: Escape<'abc.def ghi', '.' | ' ', '\\'> = 'abc\.def\ ghi'

Or maybe more like

const escaped: Escape<'abc.def ghi', { '.': '\\.'; ' ': '\\ ' }> = 'abc\.def\ ghi'

Could be a thin-ish Replace wrapper, maybe?

Use case: dot-prop style properties

Upvote & Fund

Fund with Polar

mmkal commented 1 year ago

First-cut implementation:

type Escape<
  S extends string,
  Escapes extends Record<string, string>,
> = S extends `${infer Head}${EscapeableCharacters<Escapes>}${string}`
  ? Head extends `${string}${EscapeableCharacters<Escapes>}${string}`
    ? never
    : S extends `${Head}${EscapeableCharacters<Escapes>}${infer Tail}`
    ? S extends `${Head}${infer Escapeable}${Tail}`
      ? `${Head}${Escapes[Escapeable]}${Escape<Tail, Escapes>}`
      : never
    : never
  : S

type EscapeableCharacters<Escapes extends Record<string, string>> = Extract<keyof Escapes, string>

const escaped: Escape<'abc.def ghi', {
    '.': '\\.';
    ' ': '\\ '
}> = 'abc\\.def\\ ghi'

Playground - there is probably a more efficient way of doing this involving fewer extends and infers but it was tricky handling multiple escapeable characters (unlike Replace, which only replaces a single string with a single replacement).

I'd be happy to open a PR with this (+ docs + tests) if it'd be accepted.

fregante commented 3 months ago

The type you're suggesting is just a replace with multiple needles, it doesn't actually include any replacements or specify what kind of escaping you're looking for.

So I changed the title.

Technically this is already possible by nesting calls of the existing Replace type:

https://github.com/sindresorhus/type-fest/blob/main/source/replace.d.ts