I want to transform a string literal from camelCase to snake_case, like:
type CamelCaseStr = "helloWorldAgain"
type _ = ToSnakeCase<CamelCaseStr> // "hello_world_again"
Here is my Solution:
type str = "helloWorldAgain";
type UpperCollection = "ABCDEFGHIJKLMNOPQRSTUVWXYZ";
type Chars<T extends string> = T extends `${infer Char}${infer Rest}` ? Char | Chars<Rest> : never;
type UpperLetters = Chars<UpperCollection>;
type ToSnakeCase<T extends string> =
T extends `${infer LowerStr}${UpperLetters}${infer AfterFirstBigLetter}` ?
T extends `${LowerStr}${infer WithFirstBigLetter}` ?
`${LowerStr}_${ToSnakeCase<Uncapitalize<WithFirstBigLetter>>}` :
LowerStr :
T;
type _ = ToSnakeCase<str>;
// type _ = "helloWorld_again" | "helloWorld_world_again" | "hello_again" | "hello_world_again"
I attempted to extract a LowerStr until firstly encountered an uppercased letter, therefore extract first capitalized str, then Uncapitalize it and use ToSnakeCase again. However, the result is wrong, so I wrote some test code.
type TestToSnakeCase<T extends string> =
T extends `${infer LowerStr}${UpperLetters}${infer AfterFirstBigLetter}` ?
T extends `${LowerStr}${infer WithFirstBigLetter}` ?
[LowerStr, WithFirstBigLetter] :
never
: T;
type __ = TestToSnakeCase<str>;
// type __ = ["helloWorld" | "hello", "Again" | "WorldAgain"];
The problem seems to be reduced down to shut discrimination. I searched and found this link useful, with which I tried again.
type ShutDiscrimination<T extends string> =
[T] extends [`${infer LowerStr}${UpperLetters}${infer AfterFirstBigLetter}`] ?
[T] extends [`${LowerStr}${infer WithFirstBigLetter}`] ?
`${LowerStr}_${ShutDiscrimination<Uncapitalize<WithFirstBigLetter>>}` :
LowerStr :
T;
type ___ = ShutDiscrimination<str>;
// type ___ = "helloWorld_again" | "helloWorld_worldAgain" | "hello_again" | "hello_worldAgain"
Is there anything critical I missed to solve this problem? I hope I expressed my idea well, formatting is quite cumbersome:)