Завдання

Реалізуйте TrimRight<T>, який приймає рядок та повертає новий рядок із видаленими пробілами в кінці. Наприклад:

type Trimmed = TrimRight<"   Hello World    ">; // expected to be '   Hello World'

Розв’язок

Це завдання насправді таке саме, як Trim та Trim Left. Але цього разу нам потрібно видалити пробіли праворуч.

Ми починаємо, як зазвичай, з порожнього типу, який нам потрібно реалізувати:

type TrimRight<S extends string> = any;

Тепер, як перевірити, чи закінчується рядок пробілом? Для цього ми можемо використати умовний тип, додавши пробіл до вхідного рядка:

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

Зверніть увагу на частину infer T. Якщо рядок справді закінчується пробілом, нам потрібно взяти його частину без пробілу. Отже, ми виводимо частину рядка без нього в параметр типу T.

Маючи частину рядка без пробілів праворуч, ми можемо повернути її:

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

Однак у такому випадку це вирішить проблему лише для одного пробілу. А як щодо випадків, коли їх більше? Щоб покрити це, нам потрібно продовжувати їх позбуватися, доки їх не залишиться. Це легко зробити через рекурсивний виклик того самого типу:

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

Тепер наш тип буде рекурсивно видаляти пробіли один за одним, поки їх не залишиться. У такому випадку наш умовний тип перейде до помилкової гілки. Оскільки на цьому кроці ми не маємо пробілу, ми можемо повернути вхідний рядок без будь-яких змін:

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

Я думав, що це все. Але ми бачимо в тестових випадках, що є деякі помилки. Причина в тому, що ми не обробляємо символи табуляції та нового рядка. Отже, давайте перемістимо їх в окремий тип під назвою Whitespace, де ми створимо список символів:

type Whitespace = " " | "\n" | "\t";

А тепер просто замінимо наш символ на тип:

type TrimRight<S extends string> = S extends `${infer T}${Whitespace}`
  ? TrimRight<T>
  : S;

Повне рішення з обома типами:

type Whitespace = " " | "\n" | "\t";
type TrimRight<S extends string> = S extends `${infer T}${Whitespace}`
  ? TrimRight<T>
  : S;

Посилання