Canonicalization時の空白文字処理を修正:セレクタの意味保持と可読性向上
Tailwind CSSのCanonicalization処理において、任意値内の空白文字(_)が不適切に除去される不具合を修正し、あわせてCSS関数の可読性を改善するための括弧挿入ロジックが追加されました。
背景
Canonicalizationとは、Tailwind CSSがクラス名を正規化して一意な表現に変換する処理です。この処理では、任意値(Arbitrary Value)内のアンダースコア _ をスペースとして扱い、不要な空白を除去します。しかし、この仕組みが意図しない副作用を引き起こしていました。
tailwindcss-intellisense#1544 では、suggestCanonicalClasses 機能が不正なクラス名を提案する問題が報告されています。具体的には、[&:has(~_*_*:checked)]:text-green-500 がCanonicalization後に [&:has(~**:checked)]:text-green-500 へ変換されており、CSSセレクタとして全く異なる意味になっていました。この変換では、後続兄弟結合子(~)直後の _*_* が「意味のない空白+ユニバーサルセレクタ×2」として解釈されず、** という無効な表記に化けていました。
さらに同Issueのコメントでは、w-[calc(100%_-_--spacing(60))] が w-[calc(100%---spacing(60))] に変換される問題も指摘されていました。構文的には解析可能ですが、- が減算演算子なのかカスタムプロパティ名の一部なのか、人間には判別しにくい表記です。
技術的な変更
candidate.ts 内の printArbitraryValueCache の処理が拡張され、空白除去の判断ロジックと括弧挿入ロジックが追加されました。
変更の核心は、symbols セットの導入です。従来は +、-、*、/ の4つの数学演算子のみを対象に空白除去の判定を行っていましたが、今回の修正でセレクタ関連の記号(~、>)も同じ集合で管理するようになりました。
変更前:
if (
node.kind === 'word' &&
// Operators
(node.value === '+' || node.value === '-' || node.value === '*' || node.value === '/')
) {
変更後:
let symbols = new Set([
// Selectors
'~', // Subsequent sibling combinator
'>', // Child combinator
// Math operators
'+', // or next sibling combinator
'-',
'*', // or universal selector
'/',
])
walk(ast, (node, ctx) => {
if (node.kind === 'word' && symbols.has(node.value)) {
これにより、~_*_*:checked のようなセレクタ内の空白が「記号の両隣に存在する有意な空白」として正しく認識され、除去されなくなります。
可読性改善のための括弧挿入ロジックは、より細かい条件分岐で制御されています。テストケースから確認できる挙動の原則は以下の通りです:
-
w-[calc(100%_-_--spacing(60))]→w-[calc(100%-(--spacing(60)))]:演算子の右辺にカスタムプロパティが来る場合は(…)で包む -
shadow-[inset_0px_1px_--theme(--color-white/15%)]→ そのまま保持:スペース区切りのリスト内では括弧不要 -
m-[min(100%,--spacing(6))]→ そのまま保持:,の後では可読性上の問題がないため括弧不要 -
m-[calc(--spacing(12.34)*2)]→ そのまま保持:最初の引数で他の記号と競合しない場合は括弧不要
canonicalize-candidates.test.ts には24行のテストケースが追加され、空白保持と括弧挿入の両方の挙動が検証されています。
設計判断
記号の意味が文脈依存である点を明示的に管理する設計 が採用されています。* はユニバーサルセレクタにも乗算演算子にもなり、+ は隣接兄弟結合子にも加算演算子にもなります。これらを単一の symbols セットに集約したことで、「前後にスペースを持つ記号は有意」という統一的なルールが適用されます。
括弧の挿入については、常に追加するのではなく「可読性が実際に低下するケース」に限定する保守的なアプローチが取られています。, や最初の引数位置など、文脈から意図が明確な場合は括弧を挿入しないことで、出力の簡潔さを維持しています。
まとめ
本PRは、Canonicalizationが「空白を除去する処理」から「空白の意味を文脈に応じて正しく解釈する処理」へと進化した変更です。CSSセレクタと数学関数が混在する任意値という複雑な文脈を扱うため、機械的な空白除去ではなく、前後の記号との関係性に基づいた判断ロジックを導入することで、正確性と可読性を両立しています。