CommitLintでの絵文字 VS16対策メモ

Technical

VS16 (Variation Selector-16) commitlint 対策メモ

OSSプロジェクトでは、コミットメッセージを書くときfeat:などのプレフィックスがついていることが多い。視認性をよくするために絵文字をつけることもよく行われる。gitmoj など。

そしてコミットメッセージのフォーマットを検証するのに、commitlintがあり、よくある使い方としては HuskyのPre-Commit フックと併用する。

ここで問題になるのが、typeに絵文字をつけると、同じ絵文字のはずなのにエラーが起きたり通ったりする。

原因: VS16 の有無

絵文字には text presentation (モノクロ記号) と emoji presentation (カラー絵文字) の 2 モードがある。一部の絵文字はコードポイント自体に絵文字表示指定がないため、後ろに U+FE0F (VS16) を付けないと text presentation になる。

VS16 が必要な絵文字 (定義側に VS16 を付ける):

  • 🛠️ = U+1F6E0 + U+FE0F
  • ♻️ = U+267B + U+FE0F

VS16 付き入力 (🛠️ fix) と VS16 なし入力 (🛠 fix) は文字列としては別物。String.startsWith()=== で false になる。

結果、Lintで「絵文字は同じに見えるのに通らない」という謎現象が起きる。

解決策:

commitlint公式サイトのサンプルを参考にした。

@commitlint/config-conventionaltype-enum=== ベースで動くため、emoji + VS16 を含む値を直接渡すと弾かれるので、カスタムルールを作って、VS16 をトリムしたうえで絵文字を比較するようにする。

// commitlint.config.js
 
// 🎉 init,✨ feat,🛠️ fix,♻️ refactor,🚀 perf,
// 🧪 test,💄 style,📝 docs,🧹 chore,🚧 wip
const emojiTypes = [
  '\u{1F389} init',
  '\u{2728} feat',
  '\u{1F6E0}\u{FE0F} fix',
  '\u{267B}\u{FE0F} refactor',
  '\u{1F680} perf',
  '\u{1F9EA} test',
  '\u{1F484} style',
  '\u{1F4DD} docs',
  '\u{1F9F9} chore',
  '\u{1F6A7} wip',
];
 
const plainTypes = emojiTypes.map((t) => t.split(' ')[1]);
// Emojiなしも許容する
const allAllowedTypes = [...emojiTypes, ...plainTypes];
 
export default {
  extends: ['@commitlint/config-conventional'],
  parserPreset: {
    parserOpts: {
      headerPattern: /^([^(:]+)(?:\(([^)]+)\))?!?: (.*)$/,
      headerCorrespondence: ['type', 'scope', 'subject'],
    },
  },
  plugins: [
    {
      rules: {
        'type-emoji-vs16-enum': (parsed, _when, expectedValues) => {
          const { type } = parsed;
          if (!type) return [false, 'type is required'];
 
          const normalizedType = type.replace(/\uFE0F/g, '').trim();
          const normalizedExpected = expectedValues.map((v) =>
            v.replace(/\uFE0F/g, '')
          );
 
          return [normalizedExpected.includes(normalizedType), 'invalid type'];
        },
      },
    },
  ],
  rules: {
    'type-empty': [2, 'never'],
    // 標準ルールは無効化
    'type-enum': [0],
    // Emojiつきで判定する
    'type-emoji-vs16-enum': [2, 'always', allAllowedTypes],
  },
};

VSCode コードスニペット

上記コードではあいまいさをなくすためエスケープ文字列で記述しているが、実際に作成するコミットメッセージは、絵文字をそのまま用いてよい。

すぐに組み合わせを忘れるのでエディタを開くようにして、gitmessageにコピペ用文字列を用意しておく。

VSCodeのスニペットも一応作った。

{
  "Commit Message": {
    "prefix": "commitmsg",
    "body": [
      "${1|🎉 init,✨ feat,🛠️ fix,♻️ refactor,🚀 perf,🧪 test,💄 style,📝 docs,🧹 chore,🚧 wip|}(${2:scope}): ${3:subject}",
    ],
    "description": "Gitmoji + Conventional Commit message",
  },
}

リスト形式でtypeを選択することができる。

VSCode画面のスクリーンショット

関連