`calc()` を含むシャドウ値のカラー解析バグを修正

tailwindlabs/tailwindcss

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関数やカスタム関数が追加される際にも、拡張ポイントとして機能します。

記事メタデータ

Generated by:
Claude Sonnet 4.6 for DiffDaily
LLM Trace:
1169036e

この記事はAIによって自動生成されています。内容の正確性については、必ずソースコードやPRを確認してください。

品質レビュー結果

Review Status:
承認済み
Review Count:
1回
Reviewed by:
Gemini 2.5 Pro for DiffDaily

Review Criteria:

記事構成 ✓ PASS

Title, Context, Technical Detailの存在と明確さ

リード文(総論)→セクション群(各論)→まとめ(結論)の3部構成が明確です。背景、技術的な変更、設計判断といった必須・任意要素がすべて含まれており、理想的な構成です。

カスタムMarkdown構文 ✓ PASS

シンタックスハイライト・GitHubリンク記法の正確性

ファイル名付きシンタックスハイライト(```typescript:path/to/file.ts)やGitHubへのリンク記法([#123](URL))が正しく使用されています。

対象読者への適合性 ✓ PASS

エンジニア向けの適切な技術レベルと表現

`ValueParser`やASTといった専門用語を前提として話が進んでおり、専門知識を持つエンジニアという対象読者に適合した内容です。

パラグラフ・ライティング ✓ PASS

トピックセンテンス・1段落1トピック・段落長

各セクションが総論→各論の構成で、各段落もトピックセンテンスで始まるなど、パラグラフ・ライティングの原則が守られており、非常に読みやすいです。

Diff内容との照合 ✓ PASS

コードブロックとDiff内容の一致

記事内で引用されているコードブロック(`LENGTH_FUNCTIONS`, `isNamedColor`, 修正前後のCSS)は、提供されたDiffの内容と正確に一致しています。

技術用語の正確性 ✓ PASS

技術用語の正確な使用

`ValueParser`, AST, `デカルト積テスト` (`cartesian`) といった技術用語が、PRやDiffの文脈に沿って正確に使用されています。

説明の技術的正確性 ✓ PASS

技術的主張の正確性と論理性

正規表現ベースの解析の限界と、ASTベースの解析へ移行したことによるメリット(型別の解析が可能になった点)についての説明が、技術的に正確かつ論理的です。

事実の突合 ✓ PASS

PR情報による主張の裏付け(ハルシネーション検出)

記事内のすべての主張(`calc()`の誤認識、`ValueParser`への移行、テストの強化など)は、PRのDescriptionやDiff内のコードで完全に裏付けられており、ハルシネーションは見られません。

数値・固有名詞の確認 ✓ PASS

PR番号・コミットID・バージョン等の正確性

PR番号(#20080)、Issue番号(#20065)、ファイルパスなどの数値・固有名詞はすべて正確に記載されています。

タイトル・説明との一致 ✓ PASS

記事タイトル・説明とPR内容の一致

記事のタイトル「`calc()` を含むシャドウ値のカラー解析バグを修正」は、PRの主題(シャドウ値の解析改善)を、最も重要な修正点に焦点を当てて的確に表現しています。

外部知識の正確性 ✓ PASS

PRに記載のない外部知識(LTS、サポート状況など)の不使用

記事の内容はすべて提供されたPR情報とDiffに基づいており、バージョン情報やリリース予定といった外部知識の追記(捏造)はありません。

時間表現の正確性 ✓ PASS

時間表現がPR情報と一致しているか

「〜を修正しました」「〜が確認されました」といった時間表現は、完了した変更を報告するPRの内容と一致しており、適切です。