Typescript匹配类型级别上第一个大写字母



我想将字符串文字从camelCase转换为snake_case,比如:

type CamelCaseStr = "helloWorldAgain"
type _ = ToSnakeCase<CamelCaseStr> // "hello_world_again"

这是我的解决方案:

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"

我试图提取LowerStr,直到第一次遇到大写字母,因此首先提取大写str,然后提取Uncapitalize,然后再次使用ToSnakeCase。然而,结果是错误的,所以我写了一些测试代码。

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"];

这个问题似乎被归结为消除了歧视。我搜索了一下,发现这个链接很有用,于是我再试了一次。

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"

对于解决这个问题,我有没有遗漏什么关键的东西?我希望我能很好地表达我的想法,格式化很麻烦:(

游乐场连接

代码的主要问题是直接在条件子句中使用UpperLetters进行推断。因为UpperLetters只能是单个字符,所以应该推断该字符,然后检查它是否为extendsUpperLetters。这将使你看到的工会扁平化。

type UpperLetter = "a" | "b";
type Parse<S extends string> = S extends
`${infer $Start}${infer $UpperLetter}${infer $Rest}`
? $UpperLetter extends UpperLetter ? true : false
: false;

无论如何,我通过检查Capitalize<S>是否等于S,在不使用静态UpperLetter并集的情况下自己尝试了这个问题。这是:

type CamelToSnakeCase<S extends string, $Acc extends string = ""> =
//
S extends `${infer $Ch}${infer $Rest}`
? S extends Capitalize<S>
? CamelToSnakeCase<$Rest, `${$Acc}_${Lowercase<$Ch>}`>
: CamelToSnakeCase<$Rest, `${$Acc}${$Ch}`>
: $Acc;
type foo = CamelToSnakeCase<"helloAwesomeWorld">; // "hello_awesome_world"
type foo2 = CamelToSnakeCase<"">; // ""
type foo3 = CamelToSnakeCase<"bruhSOCool">; // "bruh_s_o_cool"
type SnakeToCamelCase<S extends string, $Acc extends string = ""> =
//
S extends `${infer $Ch}${infer $Rest}`
? $Ch extends "_"
? $Rest extends `${infer $Ch}${infer $Rest}`
? SnakeToCamelCase<$Rest, `${$Acc}${Capitalize<$Ch>}`>
: ""
: SnakeToCamelCase<$Rest, `${$Acc}${$Ch}`>
: $Acc;
type asdf = SnakeToCamelCase<"hello_awesome_world">; // "helloAwesomeWorld"
type asdf2 = SnakeToCamelCase<"bruh_s_o_cool">; // "bruhSOCool"
type asdf3 = SnakeToCamelCase<"">; // ""
type asdf4 = SnakeToCamelCase<"so_cool_bro">; // "soCoolBro"

TypeScript游乐场链接

编辑:不是工会修复了你的代码。事实上,我在做${infer $Ch}${infer $Rest}时推断出了下一个字母,然后通过Capitalize检查它是否是大写的。您正在执行${infer LowerStr}${UpperLetters}${infer AfterFirstBigLetter},它计算UpperLetters中每个分支的字符串值,这导致LowerStrAfterFirstBigLetter的多个类型出现问题。这里有一个例子:

type UpperLetter = "A" | "B" | "C" | "D" // ..
// This is going to go through each computed type in the extends clause and come up with multiple 
// answers in the union for after the various characters.
type bad = "ABCD" extends `${string}${UpperLetter}${infer $After}` ? $After : never; // "" | "D" | "BCD" | "CD"
// Now, let's fix the code by writing an infer character to force the extends clause to only have one possible combination.
// This can be done by replacing `UpperLetter` with `infer $Ch` because any `string` / `infer $Ch` and removing the leading
// `${string}` part.
type fixed = "ABCD" extends `${infer $Ch}${infer $After}` ? $Ch extends UpperLetter ? $After : never : never; // "BCD"

TypeScript游乐场链接

这是我的解决方案,灵感来自@sno2

我关注的是,只有一种方法可以将"目标角色"与没有Alphabet Collection的递归进行比较。

export type CamelCaseToSnakeCase<
T extends string,
Joiner extends '' | '_' = ''
> = T extends `${infer Character}${infer Rest}`
? Character extends Uppercase<Character>
? `${Joiner}${Lowercase<Character>}${CamelCaseToSnakeCase<Rest, '_'>}`
: `${Character}${CamelCaseToSnakeCase<Rest, '_'>}`
: '';

最新更新