アップグレードツールがインライン `style` 属性のCSSプロパティを誤って変換する問題を修正

tailwindlabs/tailwindcss

@tailwindcss/upgrade のアップグレードツールが、インライン style 属性内のCSSプロパティ名をTailwindユーティリティクラスとして誤認識して書き換えてしまうバグが修正されました。style="flex-grow: 1"style="grow: 1" に変換されるような誤動作が防がれます。

背景

アップグレードツールは、テンプレートファイル内の文字列を走査してTailwindユーティリティ候補を検出し、v4の新しいクラス名に変換します。しかし、flex-growflex-shrink といったCSSプロパティ名は、それ自体がTailwindユーティリティクラス名と同じ文字列であるため、インライン style 属性の値として出現した場合でも誤ってマイグレーション対象として検出されていました。

isSafeMigration 関数は、ある候補文字列が安全に変換できるかを判定する役割を担っています。この関数はすでに v-showx-if などのディレクティブ属性値、Next.jsのImage placeholder 属性など、複数の「誤検知」パターンを除外する仕組みを持っていましたが、インライン style 属性の値はその対象に含まれていませんでした。

技術的な変更

is-safe-migration.tsinlineStyleAttributeValueRanges を参照するガード処理が追加され、候補の位置がインライン 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-showx-if などのガードと同じ「早期リターン」の設計思想に沿った実装です。

まとめ

この修正により、アップグレードツールがCSSプロパティ名をTailwindユーティリティクラスと誤認識してインライン style 属性を破壊するという実害のあるバグが解消されました。範囲キャッシュと早期リターンを組み合わせた実装は、既存の isSafeMigration における誤検知防止の設計パターンを踏襲しており、今後同様の誤検知が発見された際にも同じ構造でガードを追加できる拡張性を持っています。

記事メタデータ

Generated by:
Claude Sonnet 4.6 for DiffDaily
LLM Trace:
5cd28576

この記事は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のPRリンク記法([#19918](URL))の両方が、ガイドライン通りに正しく使用されています。

対象読者への適合性 ✓ PASS

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

「アップグレードツール」「ユーティリティクラス」「isSafeMigration」といった用語を前提としており、専門知識を持つエンジニアを対象読者として適切に設定できています。

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

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

各セクション・各パラグラフが「総論→各論」の構造で書かれており、すべてのパラグラフがトピックセンテンスで始まるなど、パラグラフ・ライティングの原則が見事に実践されています。非常に読みやすい構成です。

Diff内容との照合 ✓ PASS

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

記事で引用されているコードスニペットは、提供されたDiffの内容を正確に反映しています。`is-safe-migration.ts`のロジック追加部分と、`is-safe-migration.test.ts`のテストケース追加部分の引用はどちらも正確です。

技術用語の正確性 ✓ PASS

技術用語の正確な使用

「インライン `style` 属性」「CSSプロパティ」「ユーティリティ候補」など、PRの文脈に沿った技術用語が正確に使用されています。

説明の技術的正確性 ✓ PASS

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

「`inlineStyleAttributeValueRanges` を参照して候補が `style` 属性内か判定する」という説明は、Diffで追加されたコードロジックを技術的に正しく解説しています。

事実の突合 ✓ PASS

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

記事内のすべての主張は、PRのタイトル、説明、およびDiff内のコードによって裏付けられています。「設計判断」セクションはPR Descriptionにはない情報ですが、コードから論理的に導き出せる設計意図を解説しており、ハルシネーションには該当しません。

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

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

PR番号 `#19918` が正確に記載・リンクされています。

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

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

記事のタイトルは、PRのタイトル「[@tailwindcss/upgrade] Don’t migrate inline style properties」の内容を、より具体的かつ分かりやすく日本語で表現しており、完全に一致しています。

外部知識の正確性 ✓ PASS

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

記事の内容は提供されたPR情報に限定されており、リリース日程やサポート状況といったPR外の外部知識を持ち込んでいません。

時間表現の正確性 ✓ PASS

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

PRがバグ修正であることに対し、記事が「修正されました」「解消されました」と過去形で記述しており、時間表現は正確です。