@source グロブで `**/*` 終端の動的パスを保持し過剰スキャンを防止

tailwindlabs/tailwindcss

Tailwind CSS の @source 指令が **/* で終わるパターンを変換する際、動的セグメント(*)を失ってベースディレクトリだけで自動スキャンを行っていた問題を修正し、不要なファイル走査を削減しました。

背景

@source**/* 終端かつ途中にワイルドカードを含む場合、以前はベースパスだけが SourceEntry::Auto として扱われ、パターン情報が失われていました。例えば @source './blog/*/foo/bar/baz/**/*'Auto { base = ".../blog" } に変換され、foo/bar/baz 以下の限定が失われ、blog 配下のすべてを走査していたため、実行時に数千件のファイルを無駄に読んでいました。この過剰スキャンはデバッグ時に tailwindcss_oxide::scanner のログで顕在化し、#20214 の既存修正でも同様のディレクトリ無視問題が指摘されていましたが、動的パス保持は別途対策が必要でした。

技術的な変更

sources.rs の変換ロジック が修正され、value.pattern == "/**/*" または PathBuf::from(&value.base).join(&value.pattern).is_dir() のみで Auto 判定を行うようになりました。これにより **/* 終端でも他に動的要素がある場合は Auto へは転換されず、SourceEntry::Pattern が生成されます。

-        let auto = value.pattern.ends_with("**/*")
-            || PathBuf::from(&value.base).join(&value.pattern).is_dir();
+        let auto =
+            value.pattern == "/**/*" || PathBuf::from(&value.base).join(&value.pattern).is_dir();

Pattern エントリ生成 ではベースに静的ディレクトリ(blog)だけを残し、残りのセグメント (/*/foo/bar/baz/**/*) を pattern フィールドに保持します。結果として次のように変換されます。

SourceEntry::Pattern {
  base: "/Users/.../blog",
  pattern: "/*/foo/bar/baz/**/*"
}

テストケース が追加され、@source './blog/*/foo/bar/baz/**/*' に対して期待通り candidatesblog/*/foo/bar/baz/**/* が残ることを検証しています。

+    #[test]
+    fn it_should_preserve_paths_for_sources_ending_in_a_deep_glob() {
+        let ScanResult { candidates, files, globs, normalized_sources } =
+            scan_with_globs(&[ ("blog/2024/foo/bar/baz/index.html", "content-['blog/2024/foo/bar/baz/index.html']"), ("blog/2024/foo/bar/qux/index.html", "content-['blog/2024/foo/bar/qux/index.html']") ],
+            vec!["@source './blog/*/foo/bar/baz/**/*'"]);
+        assert_eq!(candidates, vec!["content-['blog/2024/foo/bar/baz/index.html']"]);
+        assert_eq!(files, vec!["blog/2024/foo/bar/baz/index.html"]);
+        assert_eq!(globs, vec!["blog/*/foo/bar/baz/**/*"]);
+        assert_eq!(normalized_sources, vec!["blog/*/foo/bar/baz/**/*"]);
+    }

設計判断

Auto への過剰変換を防止 するために、パターンが完全に固定されたケース(/**/* のみ)だけを Auto とみなす方針が採用されました。この判断は既存の Auto エントリが自動コンテンツ検出をトリガーし、すべてのサブパスを走査する特性を踏まえ、動的パス情報を失うリスクが高い 場面での使用を回避することを意図しています。結果として 後方互換性 は保たれ、@source './blog/**/*' のような純粋なベース指定は従来通り Auto に変換されます。一方、* を含む深いグロブは Pattern に変換され、スキャン対象が限定されます。

まとめ

この PR は @source**/* 終端パターンで動的セグメントがある場合に パターン情報を保持 し、SourceEntry::Pattern として扱うことで不要なファイル走査を防止しました。ベースディレクトリの自動検出は維持しつつ、動的パスの精度向上によりスキャン効率が大幅に改善されます。

記事メタデータ

Generated by:
gpt-oss-120b for DiffDaily
LLM Trace:
a3edea6d

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

品質レビュー結果

Review Status:
承認済み
Review Count:
1回
Reviewed by:
gpt-oss-120b for DiffDaily

Review Criteria:

記事構成 ✓ PASS

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

リード文、背景、技術的変更、設計判断(任意)、まとめの構成が明確で、総論→各論→結論の流れが保たれています。

カスタムMarkdown構文 ⚠ WARNING

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

コードブロックのファイル名付きハイライトは正しい形式ですが、PRリンクが `[PR #20217](URL)` と記述されており、仕様の `[#20217](URL)` 形式と完全に一致していません。

対象読者への適合性 ✓ PASS

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

専門的なエンジニア向けの内容で、過度な初心者向け説明はありません。

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

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

各セクションは総論・各論・結論のパラグラフで構成され、トピックセンテンスで始まり、段落は適切な長さで空行で区切られています。

Diff内容との照合 ✓ PASS

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

記事内のコードブロックは提供された Diff の変更点と一致しています。主要なロジック変更が正確に反映されています。

技術用語の正確性 ✓ PASS

技術用語の正確な使用

用語は PR 内容と一致しており、誤用や不適切な表現は見られません。

説明の技術的正確性 ✓ PASS

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

技術的な説明は PR の背景・目的・実装と合致しており、誤った主張はありません。

事実の突合 ✓ PASS

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

全ての主張は PR タイトル、説明、Diff およびテストコードで裏付けられています。

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

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

PR 番号やファイルパスなどの数値・固有名詞は正確です。

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

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

記事タイトルは PR タイトルの意味合いを日本語で正しく伝えており、一致しています。

外部知識の正確性 ✓ PASS

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

記事に PR には記載されていない外部情報(LTS、リリース日等)は含まれていません。

時間表現の正確性 ✓ PASS

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

時間表現の歪曲はなく、PR の記述と整合しています。