0
0

Delete article

Deleted articles cannot be recovered.

Draft of this article would be also deleted.

Are you sure you want to delete this article?

その色、トークンあるよ。ベタ書きの色を毎PRで数えてCIで止めるリンタを作った

0
Posted at

デザイントークンを用意したのに、急ぎのPRで #3b82f6 が直書きされる

--color-primary を定義して、チームにも「色はトークン経由で」と伝えてある。それでも色は直書きされる。締め切り前のPRで、どこかから貼り付けたコンポーネントの中で、className="text-[#3b82f6]" の中で。私自身、何度もやった。

止める手段が無いわけではない。stylelint-declaration-strict-value(週48万ダウンロード)を入れれば、色のベタ書きはCIで弾ける。でも返ってくるのは pass か fail だけだ。「今どれくらいトークン化できているのか」「このPRで負債が増えたのか」——私が見たい数字は出てこない。

だから作った。ベタ書きの色を数えて、トークン網羅率をスコアカードとバッジにして、毎PRで「増えたら落とす」リンタ、tokenlint だ。

まず、数字が出る

npx @hyuga/tokenlint を叩くと、こう出る。

$ npx @hyuga/tokenlint

  tokenlint  · color token coverage

  Coverage    28.6%   · tokenized 4 / hardcoded 10
  Hardcoded   10 colors across 3 files
  Palette     4 tokens defined

     #3b82f6            Card.jsx:3     → --color-primary (Δ0)
     rgb(107,114,128)   Card.jsx:4     → --color-primary (Δ209)
     #1a1a1a            legacy.css:3   → --color-ink (Δ0)
     hsl(217,91%,60%)   legacy.css:10  → --color-primary (Δ2)
     …

やっていることは3つだ。

  • 網羅率 = トークン経由の色 ÷(トークン経由 + ベタ書き)を出す
  • ベタ書きの色を file:line 付きで並べ、--color-primary のような近傍トークンを色距離で提案する(Δ が小さいほど「そのトークンで置き換えられる」)
  • --report を付けると、同じ内容を色チップのスコアカードHTMLで吐く(色の四角が並ぶスコアカード。GitHubのREADMEに実物を貼ってある)

設定ファイルは要らない。:root--color-* を勝手にトークン源として読むからだ。Figmaもトークンファイルも要らない。フォルダに向けて3秒で数字が出る。ここが入口になる。

でも、見えるだけのツールは続かない

スクショが映えるツールは拡散はするが、毎日は使われない。だから定着の本体は別に置いた。GitHub Action だ。

# .github/workflows/tokenlint.yml
on: [pull_request]
jobs:
  tokens:
    runs-on: ubuntu-latest
    steps:
      - uses: actions/checkout@v4
        with: { fetch-depth: 0 }
      - uses: hyuga611/tokenlint@v0
        with:
          max-new: 0   # このPRで色を新しくベタ書きしたら落とす

max-new: 0 の意味はこうだ。「既にある負債は責めない。でもこのPRで増やしたら落とす。」git の共通祖先(merge-base)と比べて、このPRで追加されたベタ書きの実数だけを見る。size-limit がバンドルのバイト増を見張るのと同じ発想を、色でやる。

これが効くのは、既存の巨大なレガシーCSSで詰まらないからだ。網羅率が今3割でも、「これ以上増やさない」なら明日から入れられる。人が意識しなくても毎PRで走る——ここが定着の本体だ。

正直に言う。色の検出そのものは、レッドオーシャンだ

ここは正直に書きたい。「色のベタ書きを検出する」だけなら、さっきの stylelint-declaration-strict-value が既にデファクトだ。近傍トークンの提案も、ds-bridge というツールが先にやっている。まっさらな空き地ではなかった。

だから tokenlint は「検出」で殴り合うのをやめた。空いていたのは見せ方だった。

  • READMEに常駐する網羅率バッジを出すツールは無かった
  • 「このPRで新規ベタ書き +N」という差分の実数を主役にするツールも無かった
  • 設定ゼロで、CSS変数と Tailwind の text-[#..]一本で両対応するものも無かった

検出は誰でも書ける。ccusage のドル、size-limit のバイトのように、盛れない数字を見せる——そこに賭けた。

公開する前に自分のツールを敵対的にレビューさせたら、launch即死バグが出てきた

ここが一番書きたいところだ。

見えるwowで拡散を狙うツールは、最初に触った人が誤検知を踏んだら終わりだ。スクショで叩かれて信用が飛ぶ。だから npm に publish する直前、自分のコードを6つの観点(スキャナ・色パース・git差分・CLI・HTMLのXSS・ReDoS)で敵対的にレビューさせ、各指摘を独立に検証した。17件、実バグが出た。

一番まずかったのはこれだ。hsl(var(--background)) を「ベタ書きの色」だと誤検知していた。

shadcn/ui や最近の Tailwind は、色を hsl(var(--foo)) の形で持つ。つまりいちばん模範的にトークン化されたコードだ。それを私のツールは「網羅率50%、ベタ書き3件」と報告していた。色関数の中の括弧を雑に閉じていたのが原因だ。もしこのまま公開して shadcn ユーザーが最初に試していたら、「こいつ何も分かってない」で即終了だったと思う。

もう一つ、地味に怖いやつがあった。閉じ括弧の無い [color:#fff を含む巨大なファイルを食わせると、正規表現が壊滅的バックトラッキングを起こして、20万文字で16秒ハングした。悪意あるPRファイル1個でCIを溶かせる。

両方とも公開前に直した。hsl(var()) は入れ子対応とトークン参照の除外で網羅率100%に戻し、ReDoS は正規表現を lookahead で書き直して16秒→5ミリ秒にした。テストは11件から24件に増やして、17件すべてを回帰テストで固定した。

教訓はひとつ。見えるツールほど、公開する前に自分を疑え。自作のテストが通ることは、正しさの証明にはならない。

まとめ

色のベタ書きは、「数えて、見せて、毎PRで増やさない」でCIに載る。

  • npx @hyuga/tokenlint で今すぐ試せる
  • GitHub Action なら uses: hyuga611/tokenlint@v0 の一行
  • 依存ゼロ・設定ゼロ・CSS変数と Tailwind 両対応

リポジトリはこちら。 https://github.com/hyuga611/tokenlint

0
0
0

Register as a new user and use Qiita more conveniently

  1. You get articles that match your needs
  2. You can efficiently read back useful information
  3. You can use dark theme
What you can do with signing up
0
0

Delete article

Deleted articles cannot be recovered.

Draft of this article would be also deleted.

Are you sure you want to delete this article?