Skip to content

Instantly share code, notes, and snippets.

@CurtisFenner
Last active May 3, 2021 04:31
Show Gist options
  • Save CurtisFenner/cbdc20a61a0fbfaa2aa095b9c4def737 to your computer and use it in GitHub Desktop.
Save CurtisFenner/cbdc20a61a0fbfaa2aa095b9c4def737 to your computer and use it in GitHub Desktop.
// Splits `T` at the first occurrence of `S`.
type SplitBy<T extends string, S extends string> =
T extends `${infer A}${S}${infer B}`
? { before: A, split: S, after: B }
: never;
type SplitInfo = { before: string, split: string, after: string };
type UseShorter<A extends SplitInfo, B extends SplitInfo> =
A["before"] extends `${B["before"]}${infer R}` ? B : A;
// Given a tuple of `SplitInfo`, returns the one which has a `before` that is a
// prefix of all others.
type UseShortest<A extends SplitInfo[]> =
A extends [infer Only]
? Only & SplitInfo
: A extends [infer First, ...infer Rest]
? UseShorter<First & SplitInfo, UseShortest<Rest extends SplitInfo[] ? Rest : never>>
: never;
// Performs `SplitBy` on each splitter in `R`, and returns the shortest `before`
// split using `UseShortest`.
type ShortestSplit<T extends string, R extends [... string[]]> = UseShortest<{ [K in keyof R]: SplitBy<T, R[K] extends string ? R[K] : never> }>;
// Leaves `before` alone.
// Wraps each `split` in parentheses.
// Recursively invokes `Escaping` on the `after`.
type ExpandEscaping<T extends SplitInfo, Splitters extends [...string[]]> =
`${T["before"]}(${T["split"]})${Escaping<T["after"], Splitters>}`;
// Wrap each occurrence of any of the `Splitters` within `T` in parentheses.
type Escaping<T extends string, Splitters extends [...string[]]> =
T extends `${infer _1}${Splitters[number]}${infer _2}`
? ExpandEscaping<ShortestSplit<T, Splitters>, Splitters>
: T;
// type z = "alpha(,)beta(@)gamma(,)delta(@)banana"
type z = Escaping<"alpha,beta@gamma,delta@banana", [",", "@"]>;
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment