🕌

正確なCamelCaseのTemplate Literal Type定義を目指した

2021/09/05に公開

概要

CamelCase<S>を TypeScriptのtemplate literal typesで実装するのは、ネット上にいくつかサンプルコードであがっているのを見たが、見つけたものはすべて不完全だった。types-festに含まれているCamelCase<S>もやはりおかしい。
とはいえ正確なCamelizationの定義というものがあるのかどうかもよく知らない。なので、npmで公開されているcamelcase の動作に近づけたCamelCase<S>の型定義を作成してみた。

ポイントは以下

  • 複数区切り文字('-', '_', '.')に対応していること。
  • 同一文字列内に上記が複合して出てきても正常に変換されること(CSSのBEMのように)。
  • 数値の後の文字がCapitalizeされること (e.g. transform_3d => transform3D)。

npmのcamelcaseの実装は上記が満たされている。

型定義

複数種類のdelimitarがあると、HeadTailでそれぞれunion distributionが起こる(それぞれの組み合わせになる)のが困ったのだが、最後に元の文字列と一致するかどうかで絞り込んで減らすことにした。

type Digits = 0 | 1 | 2 | 3 | 4 | 5 | 6 | 7 | 8 | 9;
type DigitsChar = `${Digits}`;
type Delim = '-' | '_' | '.';
type DelimDigits = Delim | DigitsChar;

export type PascalCase<
  S extends string
> = S extends `${infer Head}${DelimDigits}${infer Tail}`
  ? {
      [H in Head]: {
        [T in Tail]: {
          [D in DelimDigits]: S extends `${H}${D}${T}`
            ? D extends DigitsChar
              ? `${PascalCase<H>}${D}${PascalCase<T>}`
              : `${PascalCase<H>}${PascalCase<T>}`
            : never;
        };
      };
    }[Head][Tail][DelimDigits]
  : Capitalize<S>;

export type CamelCase<S extends string> = Uncapitalize<PascalCase<S>>;

テスト

type T1 = CamelCase<'foo-bar'>; // => 'fooBar'
type T2 = CamelCase<'foo-bar_baz'>; // => 'fooBarBaz'
type T3 = CamelCase<'foo-bar__baz.qux'>; // => 'fooBarBazQux'
type T4 = CamelCase<'article--3columns'>; // => 'article3Columns'
type T5 = CamelCase<'is_b2b_transaction'>; // => 'isB2BTransaction'
type T6 = CamelCase<string>; // => string

Discussion