# 通知設計書 18-循環依存エラー（@utility）

## 概要

本ドキュメントは、Tailwind CSSの`@apply`処理において、`@utility`定義間で循環依存が検出された場合にエラーメッセージを出力する通知の設計を記述する。

### 本通知の処理概要

`@apply`ディレクティブの代入処理におけるトポロジカルソートのフェーズで、`@utility`定義が相互に`@apply`で参照し合う循環依存を検出し、具体的なユーティリティ名を含むエラーメッセージをスローする処理である。

**業務上の目的・背景**：`@utility`ディレクティブにより定義されたカスタムユーティリティが、`@apply`を通じて相互に参照し合うと、無限再帰が発生しCSSの生成が不可能になる。この循環依存を検出し、どのユーティリティ間で循環が発生しているかを明確に通知することで、開発者が設計を見直し、循環を解消できるようにする。

**通知の送信タイミング**：`substituteAtApply`関数内のトポロジカルソート処理において、`visit`関数が再帰的にノードを訪問する際に、処理中（WIP: Work In Progress）のノードが再度訪問された場合に検出される。さらに、そのノードと次のノードの両方が`@utility`ディレクティブであることを確認したうえでスローされる。

**通知の受信者**：CSSを記述・ビルドしている開発者。例外は上位のエラーハンドリングでキャッチされ、コンソールに表示される。

**通知内容の概要**：`@apply`で指定されたユーティリティクラス名を含む循環依存のエラーメッセージが出力される。

**期待されるアクション**：開発者は循環依存を引き起こしている`@utility`定義の一方から`@apply`を削除するか、直接CSSプロパティに書き換えることで循環を解消する。

## 通知種別

@applyエラー（Error例外 / throw）

## 送信仕様

### 基本情報

| 項目 | 内容 |
|-----|------|
| 送信方式 | 同期（throw による例外スロー） |
| 優先度 | 高 |
| リトライ | なし |

### 送信先決定ロジック

例外のスローにより上位の処理層にエラーが伝播する。最終的な出力先は上位のエラーハンドラに依存する。

## 通知テンプレート

### メール通知の場合

該当なし（例外スローのみ）

### 本文テンプレート

```
You cannot `@apply` the `{candidate}` utility here because it creates a circular dependency.
```

ここで`{candidate}`は`@apply`に指定されたユーティリティクラス名であり、循環の原因となっているユーティリティを特定する。

### 添付ファイル

該当なし

## テンプレート変数

| 変数名 | 説明 | データ取得元 | 必須 |
|--------|------|-------------|-----|
| candidate | 循環依存を引き起こしているユーティリティクラス名 | @applyのparamsをsplitしたもの | Yes |
| next.params | 循環先の@utilityのパラメータ | トポロジカルソートのパスから取得 | Yes |

## 送信トリガー・条件

### トリガー一覧

| トリガー種別 | トリガーイベント | 送信条件 | 説明 |
|------------|----------------|---------|------|
| コンパイル処理 | トポロジカルソートのvisit関数 | wip.has(node) が true かつ node と next の両方が @utility の @at-rule | apply.ts 92-117行目 |

### 送信抑止条件

| 条件 | 説明 |
|-----|------|
| node または next が @utility でない場合 | 汎用的な循環依存エラー（No.19）にフォールスルーする |
| candidateAstNode.kind === 'arbitrary' の場合 | 任意値ユーティリティはスキップ（110行目） |
| candidateAstNode.root !== next.params の場合 | 循環の原因でないユーティリティはスキップ（114行目） |

## 処理フロー

### 送信フロー

```mermaid
flowchart TD
    A[visit関数呼び出し] --> B{wip.has node?}
    B -->|No| C[通常のDFS処理]
    B -->|Yes| D{node と next が @utility?}
    D -->|No| E[汎用循環依存エラーへフォールスルー]
    D -->|Yes| F[@utility内の@applyノードを走査]
    F --> G[candidateをparse]
    G --> H{candidate.root === next.params?}
    H -->|No| I[次のcandidateへ]
    H -->|Yes| J["throw Error: 循環依存エラー"]
```

## データベース参照・更新仕様

### 参照テーブル一覧

該当なし（データベースは使用しない）

### 更新テーブル一覧

該当なし（データベースは使用しない）

## エラー処理

### エラーケース一覧

| エラー種別 | 発生条件 | 対処方法 |
|----------|---------|---------|
| @utility間循環依存 | 2つの@utilityが相互に@applyで参照 | 一方の@applyを直接CSSプロパティに書き換え |

### リトライ仕様

| 項目 | 内容 |
|-----|------|
| リトライ回数 | 0 |
| リトライ間隔 | N/A |
| リトライ対象エラー | N/A（例外がスローされ処理は中断） |

## 配信設定

### レート制限

| 項目 | 内容 |
|-----|------|
| 1分あたり上限 | なし |
| 1日あたり上限 | なし |

### 配信時間帯

制限なし（CSSコンパイル時に随時発生）

## セキュリティ考慮事項

ユーティリティクラス名のみが出力されるため、機密情報の漏洩リスクはない。

## 備考

- このエラーは`@utility`ディレクティブ同士の循環依存に特化した詳細なエラーメッセージである
- `@utility`以外のノード間の循環依存はNo.19の汎用循環依存エラーとして処理される
- 循環検出のアルゴリズムはDFS（深さ優先探索）ベースのトポロジカルソートで実装されている
- `wip`セットが「処理中」のノードを追跡し、再訪問により循環を検出する（84行目、92行目）
- `path`配列で循環のチェーンを追跡し、循環の「次のノード」を特定する（94行目）
- `designSystem.parseCandidate`を使用してcandidateの種類（static/functional/arbitrary）を判定する（107行目）

---

## コードリーディングガイド

本通知を理解するために参照すべきファイルと、推奨する読み解き順序を以下に示す。

### 推奨読解順序

#### Step 1: データ構造を理解する

トポロジカルソートに使用されるデータ構造を理解する。

| 順序 | ファイル | パス | 読解ポイント |
|-----|---------|------|-------------|
| 1-1 | apply.ts | `packages/tailwindcss/src/apply.ts` | 19-26行目: parents, dependencies, definitionsの3つの追跡セット |
| 1-2 | apply.ts | `packages/tailwindcss/src/apply.ts` | 82-84行目: seen, sorted, wipの3つの状態セット |

**読解のコツ**: `DefaultMap`は存在しないキーにアクセスした際にデフォルト値を生成するユーティリティ。`dependencies`はノードが依存するユーティリティ名のセット、`definitions`はユーティリティ名からノードへのマッピング。

#### Step 2: エントリーポイントを理解する

visit関数がトポロジカルソートの核心。

| 順序 | ファイル | パス | 読解ポイント |
|-----|---------|------|-------------|
| 2-1 | apply.ts | `packages/tailwindcss/src/apply.ts` | 86行目: visit関数の定義 |
| 2-2 | apply.ts | `packages/tailwindcss/src/apply.ts` | 92行目: 循環依存の検出条件（wip.has(node)） |

**主要処理フロー**:
1. **86行目**: `visit(node, path)`関数の定義
2. **87-89行目**: `seen`チェック（既に訪問済みなら早期リターン）
3. **92行目**: `wip.has(node)` - 処理中のノードの再訪問=循環依存
4. **94行目**: `path`から循環の「次のノード」を特定
5. **96-101行目**: node と next が両方とも`@utility`であるか確認
6. **102-126行目**: @utility内の@applyノードを走査し、循環の原因を特定
7. **114-117行目**: `next.params`と`candidateAstNode.root`の一致で循環を確認しthrow Error

### プログラム呼び出し階層図

```
substituteAtApply(ast, designSystem) [apply.ts:10]
    |
    +-- walk([root], ...) [29行目] -> @utility定義と依存関係の収集
    |
    +-- visit(node, path) [86行目] -> トポロジカルソート
            |
            +-- [wip.has(node)] 循環検出 [92行目]
            |       |
            |       +-- [node & next が @utility] [96-101行目]
            |               |
            |               +-- walk(node.nodes) [102行目]
            |               |       |
            |               |       +-- designSystem.parseCandidate(candidate) [107行目]
            |               |       +-- [root一致] throw Error [115-117行目]
            |               |
            |               +-- [フォールスルー] 汎用エラー [131-133行目]
            |
            +-- [通常] dependencies走査 [138-144行目]
            +-- visit(dependency, path) [再帰呼び出し]
```

### データフロー図

```
[入力]                         [処理]                           [出力]

CSS AST               --->  依存関係収集
(@utility A が               |
 @apply B を使い、            v
 @utility B が          トポロジカルソート (DFS)
 @apply A を使う)            |
                        visit(A) -> wip.add(A)
                             |
                        visit(B) -> wip.add(B)
                             |
                        visit(A) -> wip.has(A) = true
                             |
                        循環検出!
                             |
                             v
                        throw Error    --->  "You cannot `@apply` the
                                              `{candidate}` utility here
                                              because it creates a
                                              circular dependency."
```

### 関連ファイル一覧

| ファイル | パス | 種別 | 役割 |
|---------|------|------|------|
| apply.ts | `packages/tailwindcss/src/apply.ts` | ソース | @apply代入処理・トポロジカルソート・循環依存検出 |
| ast.ts | `packages/tailwindcss/src/ast.ts` | ソース | AST型定義、toCss関数 |
| walk.ts | `packages/tailwindcss/src/walk.ts` | ソース | AST走査ユーティリティ |
| design-system.ts | `packages/tailwindcss/src/design-system.ts` | ソース | DesignSystem定義（parseCandidate関数） |
| default-map.ts | `packages/tailwindcss/src/utils/default-map.ts` | ソース | DefaultMapユーティリティ |
