アップグレードツールがインライン `style` 属性のCSSプロパティを誤って変換する問題を修正
@tailwindcss/upgrade のアップグレードツールが、インライン style 属性内のCSSプロパティ名をTailwindユーティリティクラスとして誤認識して書き換えてしまうバグが修正されました。style="flex-grow: 1" が style="grow: 1" に変換されるような誤動作が防がれます。
背景
アップグレードツールは、テンプレートファイル内の文字列を走査してTailwindユーティリティ候補を検出し、v4の新しいクラス名に変換します。しかし、flex-grow や flex-shrink といったCSSプロパティ名は、それ自体がTailwindユーティリティクラス名と同じ文字列であるため、インライン style 属性の値として出現した場合でも誤ってマイグレーション対象として検出されていました。
isSafeMigration 関数は、ある候補文字列が安全に変換できるかを判定する役割を担っています。この関数はすでに v-show や x-if などのディレクティブ属性値、Next.jsのImage placeholder 属性など、複数の「誤検知」パターンを除外する仕組みを持っていましたが、インライン style 属性の値はその対象に含まれていませんでした。
技術的な変更
is-safe-migration.ts に inlineStyleAttributeValueRanges を参照するガード処理が追加され、候補の位置がインライン style 属性の値の範囲内にある場合は false を返して変換をスキップするようになりました。
変更の核心は以下の箇所です:
// Inline `style="..."` attributes can contain CSS property names that look
// like valid utility candidates, such as `flex-grow`.
{
let ranges = inlineStyleAttributeValueRanges.get(location.contents)
for (let i = 0; i < ranges.length; i += 2) {
let start = ranges[i]
let end = ranges[i + 1]
if (location.start >= start && location.end <= end) {
return false
}
}
}
inlineStyleAttributeValueRanges はファイル内容をキーとしてキャッシュされた範囲情報(開始・終了インデックスのフラットな配列)を返します。候補の位置 [location.start, location.end] がいずれかの style 属性値の範囲内に収まっている場合、安全でないと判定して変換を抑制します。
また、この変更に伴い currentLineBeforeCandidate および currentLineAfterCandidate の計算処理が関数の後半から前半(ガード処理群の直前)へ移動しています。これは処理の早期リターンとコードの論理的な整理を兼ねた移動です。
回帰テストとして、以下のケースが is-safe-migration.test.ts に追加されました:
[`<div style="flex-grow: 1"></div>\n`, 'flex-grow'],
[`<div style='flex-shrink: 0'></div>\n`, 'flex-shrink'],
[`<div style=" flex-shrink: 0"></div>\n`, 'flex-shrink'],
[`<div style="\nflex-shrink: 0\n"></div>\n`, 'flex-shrink'],
ダブルクォート・シングルクォートのどちらの属性記法にも対応しており、値の前後に空白や改行が含まれるケースも網羅されています。
設計判断
範囲情報をキャッシュする inlineStyleAttributeValueRanges Map を介してガード判定を行う設計が採用されました。
インライン style 属性の検出をその場でパースする代わりに、事前に全ファイル内の style 属性値の範囲を列挙してキャッシュしておき、判定時には範囲内包チェックのみを行う構造です。これにより isSafeMigration が候補ごとに何度も呼び出される状況でも、属性のパース処理が重複することなく効率よく判定できます。
ガード処理を parseCandidate の呼び出しより前に配置している点も重要です。構文解析という重い処理を行う前にインライン style 属性内であると判明した時点で即座に false を返すことで、不要な処理を回避しています。既存の v-show や x-if などのガードと同じ「早期リターン」の設計思想に沿った実装です。
まとめ
この修正により、アップグレードツールがCSSプロパティ名をTailwindユーティリティクラスと誤認識してインライン style 属性を破壊するという実害のあるバグが解消されました。範囲キャッシュと早期リターンを組み合わせた実装は、既存の isSafeMigration における誤検知防止の設計パターンを踏襲しており、今後同様の誤検知が発見された際にも同じ構造でガードを追加できる拡張性を持っています。