Issue
Is it possible to make type-annotation like this?
name: OnlyAlfabetChars
where
name = "someAlfabetChars"
is allowed but
name = "some alpfabet"
or
name = "123"
is not allowed.
Solution
There is no specific type in TypeScript that acts this way. There is an open feature request at microsoft/TypeScript#41160 for regular-expression validated string types that would probably give you this, but for now this is not implemented.
The closest you can get to this is to write a generic type that acts as a constraint on a string literal type, so that T extends OnlyAlphabet<T>
if and only if T
is a string literal type composed only of alphabetic characters. And that means you would need a generic helper function to infer the generic type argument, so instead of const x: OnlyAphabet = "abcdef";
you'd have to write const x = onlyAlphabet("abcdef")
.
So, how can we implement OnlyAlphabet<T>
? We need to use template literal types to inspect the characters that make up a string, and in order to iterate over these characters we have to write it as a recursive conditional type. Here's one approach:
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;
So OnlyAphabet
is a tail-recursive conditional type which checks each character in the input string. If it's an alphabetical character it leaves it alone; otherwise it replaces it with a capital A. So OnlyAlphabet<"abcdef">
is just "abcdef"
, but OnlyAlphabet<"abcd3f">
is "abcdAf"
.
Note that I take the shortcut that a character is alphabetical if and only if Uppercase<F> extends Lowercase<F>
is false, using the intrinsic Uppercase<T>
utility type and the Lowercase<T>
utility type. This isn't perfect, but it works for a lot of character sets for which every alphabetical character has a distinct upper and lower case version, while non-alphabetical characters do not. If you have some other idea of what makes a character "alphabetical" and it can be implemented in TypeScript's type system, feel free to change it.
The onlyAlphabet
helper function is generic in T
that's constrained to string
. I would love to write <T extends OnlyAlphabet<T>>(x: T)=>x
, but that's an illegally circular constraint. Instead I use a quirk of inference and write <T extends string>(x: T extends OnlyAlphabet<T> ? T : OnlyAlphabet<T>)=>x
. When you call it, the compiler will infer T
to be the type of the x
input, and then it will check T extends OnlyAlphabet<T>
. If it succeeds, then the call succeeds because it amounts to <T extends string>(x: T)=>x
which will work. If it fails, then the call fails, because it amounts to <T extends string>(x: OnlyAlphabet<T>) => x
which will not match.
Let's test it out:
let x = onlyAlphabet("someAlphabetChars"); // okay
let y = onlyAlphabet("some alphabet"); // error!
// Argument of type '"some alphabet"' is not assignable to parameter of type '"someAalphabet"'
let z = onlyAlphabet("123"); // error!
// Argument of type '"123"' is not assignable to parameter of type '"AAA"'
Looks good. The compiler accepts a purely alphabetic string, but rejects ones with non-alphabetic characters and complains that the non-alpha character is not the letter A (so "some alphabet"
is not "someAalphabet"
and thus rejected).
In case it matters, this also works for other character sets that have distinct upper/lower case:
let w = onlyAlphabet("κάποιοαλφάβητο"); // okay
let v = onlyAlphabet("какойтоалфавит"); // okay
But fails for character sets with no such distinction:
let u = onlyAlphabet("いくつかのアルファベット"); // error!
// Argument of type '"いくつかのアルファベット"' is not assignable to parameter of type '"AAAAAAAAAAAA"'
So be careful.
Answered By - jcalz
0 comments:
Post a Comment
Note: Only a member of this blog may post a comment.