`canonicalize` のコラプス処理が任意値(arbitrary value)に対応

tailwindlabs/tailwindcss

px-[1.2rem] py-[1.2rem] のような任意値ペアが p-[1.2rem] にコラプスされない問題が修正されました。これにより、--stream モードで発生していた非決定的な出力も解消されます。

背景

canonicalize コマンドには、px-*py-* のペアを p-* へ短縮するコラプス処理が実装されています。しかしこのコラプス処理は、名前付き値(named value)に対してのみ動作しており、任意値(arbitrary value)には一切適用されていませんでした。

この制限が #19835 の根本原因でした。--stream モードでは、同一ストリーム内で先に p-[1.2rem] が処理されると、そのショートハンドが STATIC_UTILITIES_KEY にキャッシュされる副作用が生じます。後続の行でこのキャッシュが参照されることで偶発的にコラプスが成功し、「単独で渡した場合はコラプスされないが、先行入力があるとコラプスされる」という非決定的な挙動が生まれていました。

問題の本質はストリームの状態管理ではなく、任意値のコラプスがそもそも未対応だったことにあります。--stream モードはエディタやフォーマッタとの統合用途で永続プロセスとして動作するため、この挙動は複数ファイルを処理する際に冪等性を損なうなど、実用上の深刻な問題を引き起こしていました。

技術的な変更

修正の核心は、canonicalize-candidates.tscollapseCandidates 関数内にある1行のガード条件の変更です。

変更前:

if (
  parsedCandidate.kind !== 'functional' ||
  parsedCandidate.value?.kind !== 'named' // Necessary for bare values
) {
  continue
}

変更後:

if (parsedCandidate.kind !== 'functional' || parsedCandidate.value === null) {
  continue
}

変更前のガードは parsedCandidate.value?.kind !== 'named' という条件で、named 以外のすべての値種別——任意値(arbitrary value)を含む——を continue でスキップしていました。変更後は parsedCandidate.value === null、すなわちベア値(p-4 のように値を持たないケース)のみをスキップします。任意値は value が非 null であるため、コラプス処理のループに進むようになります。

コメントにあった「Necessary for bare values」という意図はそのまま保持されており、cloneCandidateprintCandidate が既に任意値を処理できていたため、根回り(root-swapping)のロジック自体は変更不要でした。また、dynamicUtilities のイテレーション対象は designSystem.utilities.keys('functional') という固定のルートセットであり、入力量に比例しないため、パフォーマンスへの影響は無視できるとされています。

テストケースでは、以下の2パターンが追加されています:

  • px-[1.2rem] py-[1.2rem]p-[1.2rem](任意値→任意値へのコラプス)
  • px-[30.75rem] py-[30.75rem]p-123(任意値→名前付き値へのコラプス)

設計判断

最小限の変更で根本原因を修正するアプローチが取られました。

非決定的挙動の直接の原因は --stream モードのキャッシュ副作用ですが、修正はその副作用の制御ではなく、「任意値のコラプスをそもそも正しく動作させる」という根治策が選ばれています。コラプス処理が常に正しく機能するようになれば、キャッシュの有無に関わらず出力が一定になるためです。

変更は4行削除・1行追加という最小限の差分であり、既存の cloneCandidate / printCandidate の任意値対応を活用することで、ロジックの重複や新たな分岐の追加を避けています。

まとめ

本修正は、コラプス処理のガード条件を1行緩和することで、任意値に対する正しいショートハンド変換を実現しました。--stream モードの非決定的挙動はこの欠落に起因していたため、根本原因の修正によってエディタ統合やフォーマッタ用途での信頼性が向上します。

記事メタデータ

Generated by:
Claude Sonnet 4.6 for DiffDaily
LLM Trace:
a9701e66

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

品質レビュー結果

Review Status:
リトライ後承認
Review Count:
2回 (改善を経て承認)
Reviewed by:
Gemini 2.5 Pro for DiffDaily

Review Criteria:

記事構成 ✓ PASS

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

「リード文(総論)→背景・技術的な変更・設計判断(各論)→まとめ(結論)」という3部構成が明確に適用されており、各セクションの役割が果たされています。

カスタムMarkdown構文 ✓ PASS

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

ファイル名付きシンタックスハイライト(```typescript:...)およびGitHubのPR・Issueへのリンク記法([#123])が正しく使用されています。

対象読者への適合性 ✓ PASS

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

「canonicalize」「コラプス処理」「任意値」「冪等性」などの専門用語が適切に使用されており、専門知識を持つエンジニアという対象読者に適合した内容です。

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

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

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

Diff内容との照合 ✓ PASS

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

記事内で引用されている`canonicalize-candidates.ts`のコード変更は、提供されたDiff情報と完全に一致しています。

技術用語の正確性 ✓ PASS

技術用語の正確な使用

「arbitrary value(任意値)」「named value(名前付き値)」「root-swapping(根回り)」など、PRで使われている技術用語が正確に翻訳・使用されています。

説明の技術的正確性 ✓ PASS

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

ガード条件の変更がなぜ任意値のコラプスを可能にするのか、という技術的な説明が論理的かつ正確です。

事実の突合 ⚠ WARNING

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

記事の主張の大部分はPR情報で裏付けられていますが、「--stream モードはエディタやフォーマッタとの統合用途で永続プロセスとして動作する」という説明はPRに明記されておらず、技術的に自明な補足情報と判断しました。

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

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

PR番号(#19837)、Issue番号(#19835)、テストケースで用いられている具体的な値(1.2rem, 30.75remなど)がすべて正確です。

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

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

記事のタイトル「`canonicalize` のコラプス処理が任意値(arbitrary value)に対応」は、PRの主題を正確に要約しています。

外部知識の正確性 ✓ PASS

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

バージョン情報やリリース予定など、PRに記載のない外部知識の追加は見られませんでした。

時間表現の正確性 ✓ PASS

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

「〜が修正されました」「〜が発生していた」といった時間表現は、PRの内容と矛盾なく、正確です。