Reputation: 4594
Is it possible to generate permutation of combinations of string literal with template literal in TypeScript?
type MetaKey = 'meta';
type CtrlKey = 'ctrl';
type ShiftKey = 'shift';
type AltKey = 'alt';
type ModiferKeyCombinations = ???
where ModiferKeyCombinations
expected to be:
type ModiferKeyCombinations =
| 'meta'
| 'ctrl'
| 'shift'
| 'alt'
| 'meta ctrl'
| 'meta shift'
| 'meta alt'
| 'ctrl meta'
| 'ctrl shift'
| 'ctrl alt'
| 'shift meta'
| 'shift ctrl'
| 'shift alt'
| 'alt meta'
| 'alt ctrl'
| 'alt shift'
| 'meta ctrl shift'
| 'meta ctrl alt'
| 'meta shift ctrl'
| 'meta shift alt'
| 'meta alt ctrl'
| 'meta alt shift'
| 'ctrl meta shift'
| 'ctrl meta alt'
| 'ctrl shift meta'
| 'ctrl shift alt'
| 'ctrl alt meta'
| 'ctrl alt shift'
| 'shift meta ctrl'
| 'shift meta alt'
| 'shift ctrl meta'
| 'shift ctrl alt'
| 'shift alt meta'
| 'shift alt ctrl'
| 'alt meta ctrl'
| 'alt meta shift'
| 'alt ctrl meta'
| 'alt ctrl shift'
| 'alt shift meta'
| 'alt shift ctrl'
| 'meta ctrl shift alt'
| 'meta ctrl alt shift'
| 'meta shift ctrl alt'
| 'meta shift alt ctrl'
| 'meta alt ctrl shift'
| 'meta alt shift ctrl'
| 'ctrl meta shift alt'
| 'ctrl meta alt shift'
| 'ctrl shift meta alt'
| 'ctrl shift alt meta'
| 'ctrl alt meta shift'
| 'ctrl alt shift meta'
| 'shift meta ctrl alt'
| 'shift meta alt ctrl'
| 'shift ctrl meta alt'
| 'shift ctrl alt meta'
| 'shift alt meta ctrl'
| 'shift alt ctrl meta'
| 'alt meta ctrl shift'
| 'alt meta shift ctrl'
| 'alt ctrl meta shift'
| 'alt ctrl shift meta'
| 'alt shift meta ctrl'
| 'alt shift ctrl meta'
Upvotes: 10
Views: 1303
Reputation: 329943
You can make the compiler calculate such permutations, although since the number of permutations grows exponentially with the number of elements, you should be careful using it. Here's how I'd proceed:
type Permutations<T extends string, U extends string = T> =
T extends any ? (T | `${T} ${Permutations<Exclude<U, T>>}`) : never;
and then the type you want is to pass Permutations
the union of the strings you want to permute:
type ModiferKeyCombinations = Permutations<MetaKey | CtrlKey | ShiftKey | AltKey>;
You can verify they are the same type by declaring a var
multiple times with both that type and the manually created one from your question, and seeing that the compiler is happy with it:
var x: ModiferKeyCombinations;
var x: ManualModiferKeyCombinations; // no compiler error
The way Permutations<T>
works: first, I have to give it the full union twice; once as the T
parameter, and once as the U
parameter. That's because we need to pull apart this union into its pieces while also maintaining it so we can remove one element with the Exclude
utility type. The idea is to take each piece T
of the full union U
, and then return that piece alone as well as concatenating Permutations<Exclude<U, T>>
onto the end using template literal string types
If you call Permutations<T>
when T
is never
(meaning zero strings), you get never
out.
If you call Permutations<T>
when T
is one string like "oneString"
, then you use Permutations<never>
as part of the answer: "oneString" | `oneString ${never}`
... the latter of which becomes just never
itself according to the rules for template literal strings. So just "oneString"
.
If you call Permutations<T>
when T
is a union of two strings, like "a" | "b"
, then you use Permutations<"a">
and Permutations<"b">
as part of the answer: "a" | `a ${Permutations<"b">}` | "b" | `b ${Permutations<"a">}`
, which becomes "a" | "a b" | "b" | "b a"
.
...and so forth; I'll stop there.
Upvotes: 16