Using the builtin Uncapitalize<S>
, you can naively transform a string such as FooBar
to fooBar
:
const fooBar: Uncapitalize<'FooBar'> = 'fooBar';
However, this method falls short when you use class names following the HTML5 convention, such as XMLHttpRequest
:
const xmlHttpRequest: Uncapitalize<'XMLHttpRequest'> = 'xMLHttpRequest';
Specifically, I'm looking for a type that can:
PascalCase
that contains mutliple conjoined UPPER_CASE
letters that form an acronym, into a strictCamelCase
string type that lowercase
s the acronyms,For example:
XMLHttpRequest
-> xmlHttpRequest
HTMLAnchorElement
-> htmlAnchorElement
FOOBaAAr
-> foobaaAr
Since I'll be using this to validate code, not transform user input, I don't really care if it's ugly like that last example, because that becomes the consuming developer's problem to fix their naming (:
I know I can use the new template literal types in TypeScript, specifically, using their inference features. However, their examples only show one-sided inference, like ${infer Foo}bar
, & using multiple inference types make seem to make the last one greedy, like ${infer FirstChar}${infer Rest}bar
.
You can use recursion on the type level to get around the greediness problem.
type StrictCase<S extends string> =
S extends `${
infer P1 extends Uppercase<string>
}${
infer P2 extends Uppercase<string>
}${
infer P3
}`
? `${Lowercase<P1>}${StrictCase<`${P2}${P3}`>}`
: S extends `${infer P1}${infer P2}`
? P2 extends ''
? Lowercase<P1>
: `${P1}${StrictCase<P2>}`
: S;
type StrictPascalCase<S extends string> = Capitalize<StrictCase<S>>
type StrictCamelCase<S extends string> = Uncapitalize<StrictCase<S>>;
In the StrictCase<S>
type:
FOOBar
StrictCase<S>
. -> fStrictCamelCaseImpl<'OOBar'>
r
r
Bar
StrictCase<S>
. -> BStrictCamelCaseImpl<'ar'>
ar
ar
Then, after that is done, to normalise into either camelCase
or PascalCase
, it's just a matter of Capitalize<S>
or Uncapitalize<S>
, which transforms the first letter either into UPPER_CASE
or lowercase
.