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'
With template literals, you can avoid the concatenation operator — and improve the readability of your code — by using placeholders of the form ${expression} to perform substitutions for embedded expressions: const a = 5; const b = 10; console.
Template literal types build on string literal types, and have the ability to expand into many strings via unions. They have the same syntax as template literal strings in JavaScript, but are used in type positions.
popular templaters like mustache/underscore/handlebars are dozens to hundreds of times slower than concatenation is or template literals will be. Theoretical speaking (unless the JS is compiled), template literals would be slower since the 'string' needs to be parsed regardless of placeholder existence.
Template literals provide an easy way to interpolate variables and expressions into strings. The method is called string interpolation.
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.
Playground link to code
If you love us? You can donate to us via Paypal or buy me a coffee so we can maintain and grow! Thank you!
Donate Us With