Завдання

Вирахуйте довжину рядкового літерала. Наприклад:

type length = LengthOfString<"Hello, World">; // expected to be 12

Розв’язок

Спочатку я спробував найпростіше рішення – звернутись до властивості length через індексні типи. Сподівався, що TypeScript достатньо розумний, щоб повернути значення:

type LengthOfString<S extends string> = S["length"];

На жаль, ні. Таким чином ми отримаємо number, але не числовий літерал. Отже, потрібно придумати інше рішення.

А що, якщо ми виведемо перший символ та решту символів рекурсивно, доти, доки не залишиться символів в рядку? В такому випадку, ми отримаємо лічильник, який працює на рекурсії. Почнемо з типу, який виводить перший символ та решту рядка (хвіст):

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

В тип-параметрі C отримуємо перший символ та у T отримуємо хвіст. Викликаючи тип LengthOfString рекурсивно з рештою рядка, ми зупинимось у випадку, коли символів більше не залишиться.

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

Проблема в тому, що ми не знаємо, де зберігати наш лічильник. Очевидно, ми можемо додати інший тип параметр до LengthOfString, який буде акумулювати значення, але TypeScript не надає інструментів для керування числами в системі типів.

Замість чисел створимо кортеж і будемо додавати по одному символу на кожній ітерації.

type LengthOfString<
  S extends string,
  A extends string[],
> = S extends `${infer C}${infer T}` ? LengthOfString<T, [C, ...A]> : never;

Перетворюємо рядковий літерал в кортеж символів цього рядкового літерала. Як тільки в рядковому літералі не залишається символів – повертаємо довжину кортежу.

type LengthOfString<
  S extends string,
  A extends string[],
> = S extends `${infer C}${infer T}`
  ? LengthOfString<T, [C, ...A]>
  : A["length"];

Додавши новий тип параметр ми зламали тести. Тому, що ми передаємо два типи параметри замість одного. Виправляємо це, додавши до другого параметру значення за замовчуванням – порожній кортеж.

type LengthOfString<
  S extends string,
  A extends string[] = [],
> = S extends `${infer C}${infer T}`
  ? LengthOfString<T, [C, ...A]>
  : A["length"];

Посилання