`calc()` を含むシャドウ値のカラー解析バグを修正
drop-shadow-* カラーユーティリティと calc() を含むカスタムシャドウ値を組み合わせると、calc() が誤ってカラー部分として認識されCSSが壊れる問題を修正しました。ValueParser を活用した型別の解析ロジックへの刷新により、長さ値と色値の判別精度が大幅に向上しています。
背景
Tailwind CSS の drop-shadow-[color] ユーティリティは、シャドウ値のカラー部分を var(--tw-shadow-color, <元の値>) に置換することでカラースワップを実現しています。この置換を行うためには、シャドウ値の文字列を解析してどの部分がカラーかを特定する必要があります。
問題が発生したのは #20065 で報告されたケースです。--drop-shadow-broken: 0 0 calc(1 * var(--spacing)) black; のようなカスタムシャドウ値に対して drop-shadow-[color] ユーティリティを適用すると、calc(1 * var(--spacing)) がカラーと誤判定され、本来の black が無視されて生成CSSが壊れる事象が確認されました。
従来の実装は正規表現 /^-?(\d+|\.\d+)(.*?)$/ で長さ値を検出し、残りをカラー候補として扱うシンプルな文字列処理でした。しかし calc() のような関数型の値は先頭が数字で始まらないため、この正規表現にマッチせず、カラーとして誤認されてしまいました。
技術的な変更
replace-shadow-colors.ts の解析ロジックが、正規表現ベースの文字列処理から ValueParser を使ったAST(抽象構文木)ベースの処理に刷新されました。これにより、関数(function node)と単語(word node)を型として区別した上で、それぞれに適したチェックを適用できるようになっています。
新たに定義された判別ルールは以下の通りです:
-
長さ値として扱う関数 (
LENGTH_FUNCTIONS):calc,clamp,max,min,--spacing -
色値として扱う関数 (
COLOR_FUNCTIONS):color,color-mix,hsl,hsla,rgb,rgba,lab,lch,oklab,oklch,hwb,light-dark,--alphaなど
const LENGTH_FUNCTIONS = new Set(['calc', 'clamp', 'max', 'min', '--spacing'])
const COLOR_FUNCTIONS = new Set([
'color',
'color-mix',
'contrast-color',
'device-cmyk',
'hsl',
'hsla',
'hwb',
'lab',
'lch',
'light-dark',
'oklab',
'oklch',
'rgb',
'rgba',
'--alpha',
])
is-color.ts には新たに isNamedColor 関数が追加され、NAMED_COLORS セットを使った名前付き色(black, red など)の判定が独立した関数として利用できるようになりました。
export function isNamedColor(value: string): boolean {
return NAMED_COLORS.has(value.toLowerCase())
}
また、replaceAst というヘルパー関数が内部に追加され、ASTノードをCSS文字列に戻してから置換処理を行い、再度ASTに変換するパイプラインが整備されています。テストコードでは、x, y, blur, spread, color の各位置に対して calc(), --spacing(), 数値, 名前付き色, hex色, CSS関数, --alpha() 等をすべて組み合わせた デカルト積テスト (cartesian) が追加され、どの位置にどの型の値が来ても正しくカラーが検出されることが網羅的に検証されています。
この変更により、修正前後の挙動は以下のように変わります:
修正前:
.drop-shadow-calc {
/* calc() がカラーと誤認識され、本来の black が末尾に残ってしまう */
--tw-drop-shadow-size: drop-shadow(0 0 var(--tw-drop-shadow-color, calc(1 * var(--spacing))) black);
}
修正後:
.drop-shadow-calc {
/* calc() は長さ値として正しく処理され、black がカラーとして置換される */
--tw-drop-shadow-size: drop-shadow(0 0 calc(1 * var(--spacing)) var(--tw-drop-shadow-color, black));
}
設計判断
ValueParser によるAST解析への移行 が採用された点が、この修正の核心的な設計判断です。
正規表現による文字列処理では、calc(...) のように括弧を含む関数値を「関数」として認識できず、内部の文字列パターンのみで判断するしかありませんでした。ValueParser を介することで、トークンの種類(関数ノード・単語ノード)が明示的に区別され、関数名に基づく「確実に長さ値」「確実に色値」というホワイトリスト判定が可能になっています。
また、--spacing() や --alpha() のようなTailwind CSS独自のカスタム関数を LENGTH_FUNCTIONS / COLOR_FUNCTIONS に明示的に追加している点も注目です。PR本文でも言及されているように、これらの関数は実行時には別の値に展開されますが、Tailwind CSS自身がその意味論を把握しているため、解析時に確実な型として扱えます。var(--unknown) のような型不明の変数については保守的な扱いを維持しつつ、既知の関数については積極的に型を付ける設計となっています。
まとめ
本修正は、文字列パターンマッチングという限界に達していたアドホックな解析を、ASTベースの型aware解析に置き換えることで根本的に解決した変更です。長さ値・色値のホワイトリストを明示的に管理する構造は、将来的に新しいCSS関数やカスタム関数が追加される際にも、拡張ポイントとして機能します。