No specific TypeScript type behaves in this manner. A feature request for regular-expression validated string types is open at microsoft/TypeScript#41160, which might provide the functionality you are looking for, but as of now, it has not been implemented.
The closest workaround involves creating a generic type that serves as a constraint on a string's literal type. To achieve this, you can enforce that T extends OnlyAlphabet<T>
only when T
represents a string literal composed entirely of alphabetic characters. This solution would require a generic helper function to infer the type argument: instead of writing const x: OnlyAphabet = "abcdef";
, you would write
const x = onlyAlphabet("abcdef");
.
To implement OnlyAlphabet<T>
, we need to utilize template literal types for character inspection in a string. An approach involving recursive conditional types is necessary to iterate over these characters effectively.
type OnlyAlphabet<T extends string, A extends string = ""> =
T extends `${infer F}${infer R}` ?
OnlyAlphabet<R, `${A}${Uppercase<F> extends Lowercase<F> ? "A" : F}`> :
A;
const onlyAlphabet = <T extends string>(
x: T extends OnlyAlphabet<T> ? T : OnlyAlphabet<T>
) => x;
OnlyAphabet
functions as a tail-recursive conditional type that checks each character within the input string. Alphabetic characters remain unchanged, while non-alphabetic characters are replaced with 'A'. For example, OnlyAlphabet<"abcdef">
remains "abcdef"
, whereas OnlyAlphabet<"abcd3f">
becomes "abcdAf"
.
This implementation assumes a character is alphabetical if and only if
Uppercase<F> extends Lowercase<F>
evaluates to false, utilizing the intrinsic
Uppercase<T>
and
Lowercase<T>
utility types. Although not perfect, this method caters to many character sets where every alphabetical character has distinct upper and lower case forms.
The onlyAlphabet
helper function enforces constraints on the generic T
, limiting it to string
. While
<T extends OnlyAlphabet<T>>(x: T)=>x
is desirable, it forms an illegal circular constraint. Instead, leveraging inference,
<T extends string>(x: T extends OnlyAlphabet<T> ? T : OnlyAlphabet<T>)=>x
is employed. When called, the compiler infers the type of
x
, subsequently assessing
T extends OnlyAlphabet<T>
. If successful, the call proceeds as
<T extends string>(x: T)=>x
; otherwise, it resembles
<T extends string>(x: OnlyAlphabet<T>)=>x
, leading to rejection.
Let's put it into practice:
let x = onlyAlphabet("someAlphabetChars"); // passes
let y = onlyAlphabet("some alphabet"); // fails!
// Error: Argument of type '"some alphabet"' is not assignable to parameter of type '"someAalphabet"'
let z = onlyAlphabet("123"); // fails!
// Error: Argument of type '"123"' is not assignable to parameter of type '"AAA"'
The compiler accepts purely alphabetic strings but rejects those containing non-alphabetic characters, specifying that the non-alpha character should be 'A' ('some alphabet' does not match 'someAalphabet').
This technique also works for character sets with distinct upper/lower cases:
let w = onlyAlphabet("κάποιοαλφάβητο"); // passes
let v = onlyAlphabet("какойтоалфавит"); // passes
However, it falls short for character sets lacking such distinctions:
let u = onlyAlphabet("いくつかのアルファベット"); // fails!
// Error: Argument of type "いくつかのアルファベット" is not assignable to parameter of type "AAAAAAAAAAAA"
Exercise caution when implementing.
Playground link to code