Literal returntype of Array.prototype.join in TypeScript
Creates a literal type when joining strings in TypeScript if the input array is a tuple. This was mainly motivated by my curiosity about TypeScripts’s template literal types.
// Sample input
type Items0 = [];
type Items1 = ['a'];
type Items2 = ['a', 'b'];
type Items3 = ['a', 'b', 'c'];
type Items4 = ['a', 'b', 'c', 'd'];
type ItemsN = string[];
// Usage
type ClassKey0 = Join<Items0, ' '>; // ""
type ClassKey1 = Join<Items1, ' '>; // "a"
type ClassKey2 = Join<Items2, ' '>; // "a b"
type ClassKey3 = Join<Items3, ' '>; // "a b c"
type ClassKey4 = Join<Items4, ' '>; // "a b c d"
type ClassKey5 = Join<ItemsN, ' '>; // string
type Hyphened2 = Join<Items3, '-'>; // "a-b-c"
type Joined3 = Join<Items4>; // "abcd"
// Actual type
export type Join<Items, Delimiter extends string = ''> = Items extends [
any,
...any[]
]
? `${Items[0]}${HasTail<Items> extends true
? `${Delimiter}${Join<Tail<Items>, Delimiter>}`
: ``}`
: string[] extends Items
? string
: ``;
// Helper type
type Tail<Items extends string[]> = Items extends [string, ...infer Tail]
? Tail
: never;
type Tail0 = Tail<Items0>; // never
type Tail1 = Tail<Items1>; // []
type Tail2 = Tail<Items2>; // ["b"]
type Tail3 = Tail<Items3>; // ["c"]
type Tail4 = Tail<Items4>; // ["d"]
// Helper type
type HasTail<Items extends string[]> = Items extends [any, any, ...any[]]
? true
: false;
type HasTail0 = HasTail<Items0>; // false
type HasTail1 = HasTail<Items1>; // false
type HasTail2 = HasTail<Items2>; // true
type HasTail3 = HasTail<Items3>; // true
type HasTail4 = HasTail<Items4>; // true
Tested with typescript@4.1.3
.