Завдання

Привести рядок до формату kebab-case. Наприклад:

type kebabCase = KebabCase<"FooBarBaz">; // expected "foo-bar-baz"

Розв’язок

Це завдання дуже схоже з завданням “CamelCase”. Почнемо з виводу типів. Нам потрібно дізнатись перший символ рядка та решту (хвіст).

type KebabCase<S> = S extends `${infer C}${infer T}` ? never : never;

Коли тип не відповідає шаблону “перший символ та решта (хвіст)”, це означає кінець рядка. Тому повертаємо вхідний параметр без змін.

type KebabCase<S> = S extends `${infer C}${infer T}` ? never : S;

Але, коли наш шаблон спрацював, ми повинні обробити два випадки. Коли решта рядка не має заголовного символу та навпаки, коли має. Для перевірки використовуємо вбудований тип Uncapitalize.

type KebabCase<S> = S extends `${infer C}${infer T}`
  ? T extends Uncapitalize<T>
    ? never
    : never
  : S;

Якщо решта рядка не має заголовного символу? Це означає, що в нас може бути Foo або foo. Тож, ми перетворюємо перший символ в нижній регістр, а решту рядка залишаємо без змін. Не забуваємо застосовувати цей тип рекурсивно, для обробки наступних частин рядка.

type KebabCase<S> = S extends `${infer C}${infer T}`
  ? T extends Uncapitalize<T>
    ? `${Uncapitalize<C>}${KebabCase<T>}`
    : never
  : S;

Другий випадок, коли решта рядка містить заголовний символ(наприклад, fooBar). Перетворюємо перший символ в нижній регістр, додавши дефіс, та продовжуємо рекурсивно обробляти рядок. Нам не потрібно застосовувати Uncapitalize для решти рядка, бо він буде застосований в Uncapitalize<C>.

type KebabCase<S> = S extends `${infer C}${infer T}`
  ? T extends Uncapitalize<T>
    ? `${Uncapitalize<C>}${KebabCase<T>}`
    : `${Uncapitalize<C>}-${KebabCase<T>}`
  : S;

Посилання