# 機能設計書 52-cljfmt設定サポート

## 概要

本ドキュメントは、clojure-lspにおけるcljfmt設定サポート機能の設計を記述する。この機能は`.cljfmt.edn`からの設定読み込みと`:style/indent`メタデータのサポートを提供し、プロジェクト固有のコードフォーマットルールをカスタマイズ可能にする。

### 本機能の処理概要

**業務上の目的・背景**：
Clojureプロジェクトでは、チームやプロジェクトごとに異なるコーディングスタイルが採用されることがある。特にインデントルールは、マクロの種類や関数の性質によって異なる場合が多い。cljfmt設定サポート機能は、プロジェクト固有の設定ファイル（`.cljfmt.edn`）からルールを読み込み、さらにソースコード内の`:style/indent`メタデータからも設定を抽出することで、プロジェクトに最適化されたフォーマットを実現する。

**機能の利用シーン**：
1. プロジェクト固有のマクロに対するカスタムインデントルールを定義する場面
2. 既存のcljfmtワークフローをclojure-lspと統合する場面
3. ライブラリ作者がマクロの推奨インデントを`:style/indent`で指定する場面
4. 組織全体で統一されたフォーマットルールを適用する場面

**主要な処理内容**：
1. プロジェクトルートから`.cljfmt.edn`ファイルを検索・読み込み
2. clojure-lsp設定の`:cljfmt`セクションから設定を取得
3. プロジェクト内の`:style/indent`メタデータを解析して設定を抽出
4. これらの設定をマージしてcljfmtライブラリに渡す
5. 設定をTTL付きでメモライズしてパフォーマンスを最適化

**関連システム・外部連携**：
- cljfmtライブラリ：デフォルト設定の提供と整形処理
- clj-kondo：`:style/indent`メタデータを含む解析データの提供
- クラスパス設定：`classpath-config-paths`による外部設定の読み込み

**権限による制御**：
特に権限による制御はない。設定ファイルの読み取り権限が必要。

## 関連画面

| 画面No | 画面名 | 関連種別 | 関連する操作・処理 |
|--------|--------|----------|------------------|
| N/A | 設定ファイル | 入力 | .cljfmt.ednの読み込み |
| N/A | エディタ | 出力 | フォーマット結果の適用 |

## 機能種別

設定管理 / データ変換処理

## 入力仕様

### 入力パラメータ

| パラメータ名 | 型 | 必須 | 説明 | バリデーション |
|-------------|-----|-----|------|---------------|
| :cljfmt-config-path | string | No | cljfmt設定ファイルのパス（デフォルト: .cljfmt.edn） | 有効なファイルパス |
| :cljfmt | map | No | インラインcljfmt設定 | cljfmt設定形式 |
| :cljfmt.indents | map | No | カスタムインデントルール | シンボル -> ルール形式 |

### 入力データソース

1. `.cljfmt.edn`ファイル（プロジェクトルート相対または絶対パス）
2. `.lsp/config.edn`の`:cljfmt`セクション
3. LSPクライアントの初期化オプション
4. ソースコード内の`:style/indent`メタデータ

## 出力仕様

### 出力データ

| 項目名 | 型 | 説明 |
|--------|-----|------|
| :indents | map | シンボルとインデントルールのマッピング |
| :extra-indents | map | 追加のインデントルール |
| :alias-map | map | 名前空間エイリアスマッピング |
| その他cljfmt設定 | any | cljfmtライブラリの各種設定 |

### 出力先

cljfmtライブラリへの設定オブジェクト（フォーマット処理で使用）

## 処理フロー

### 処理シーケンス

```
1. 設定解決要求
   └─ cljfmt-config関数が呼び出される
2. TTLキャッシュ確認
   └─ 3秒以内の同一設定ならキャッシュを返却
3. デフォルト設定取得
   └─ cljfmt.config/default-configを取得
4. :style/indent抽出
   └─ プロジェクトのvar-definitionsから:style/indentメタデータを抽出
5. :style/indent変換
   └─ CIDERスタイル仕様をcljfmtルールに変換
6. ユーザー設定読み込み
   └─ resolve-user-cljfmt-configで設定ファイルを読み込み
7. 設定マージ
   └─ デフォルト < :style/indent < ユーザー設定の順でマージ
8. alias-map整理
   └─ 空の場合はnilに設定（cljfmtバグ回避）
9. キャッシュ更新
   └─ TTL付きでメモライズ
```

### フローチャート

```mermaid
flowchart TD
    A[cljfmt-config呼び出し] --> B{TTLキャッシュ有効?}
    B -->|Yes| C[キャッシュを返却]
    B -->|No| D[cljfmt.config/default-config取得]
    D --> E[extract-style-indent-metadata]
    E --> F[:style/indent -> cljfmt変換]
    F --> G[resolve-user-cljfmt-config]
    G --> H{設定ファイル存在?}
    H -->|No| I[LSP設定のみ使用]
    H -->|Yes| J[ファイル読み込み]
    J --> K{.clj拡張子?}
    K -->|Yes| L[read-string使用]
    K -->|No| M[edn/read-string使用]
    L --> N[設定マージ]
    M --> N
    I --> N
    N --> O[merge-configs]
    O --> P[alias-map整理]
    P --> Q[キャッシュ更新]
    Q --> R[設定を返却]
    C --> S[終了]
    R --> S
```

## ビジネスルール

### 業務ルール

| ルールNo | ルール名 | 内容 | 適用条件 |
|---------|---------|------|---------|
| BR-52-01 | 設定優先順位 | デフォルト < :style/indent < ユーザー設定 | 常時 |
| BR-52-02 | indentsマージ | :indentsキーは上書きではなくマージ | 複数設定源存在時 |
| BR-52-03 | パス解決 | 相対パスはプロジェクトルートからの相対 | cljfmt-config-path |
| BR-52-04 | 絶対パス優先 | /で始まるパスは絶対パスとして扱う | cljfmt-config-path |
| BR-52-05 | TTLキャッシュ | 設定は3秒間キャッシュされる | 常時 |
| BR-52-06 | 正規表現サポート | :indentsキーで#"pattern"形式の正規表現をサポート | indents定義時 |

### 計算ロジック

**:style/indent -> cljfmt変換ロジック**:

CIDERの:style/indent仕様をcljfmtの:indentsルールに変換:

| :style/indent | cljfmt変換 | 説明 |
|--------------|------------|------|
| `0`, `1`, `2` | `[[:block N]]` | N個の特殊引数 |
| `:defn` | `[[:inner 0]]` | defn風インデント |
| `:form` | nil | 通常フォーム |
| `[N ...]` | 複合ルール | ネスト仕様 |

## データベース操作仕様

### 操作別データベース影響一覧

| 操作 | 対象テーブル | 操作種別 | 概要 |
|-----|-------------|---------|------|
| 解析情報読み取り | db.analysis | SELECT | var-definitionsから:style/indent取得 |

### テーブル別操作詳細

#### db.analysis

| 操作 | 項目（カラム名） | 更新値・取得条件 | 備考 |
|-----|-----------------|-----------------|------|
| SELECT | var-definitions.meta | 全var定義 | :style/indentメタデータを持つもの |

## エラー処理

### エラーケース一覧

| エラーコード | エラー種別 | 発生条件 | 対処方法 |
|------------|----------|---------|---------|
| - | ファイル読み取りエラー | 設定ファイルが読み取れない | エラーログ出力、デフォルト設定使用 |
| - | EDN解析エラー | 設定ファイルの構文エラー | エラーログ出力、デフォルト設定使用 |
| - | 不正な:style/indent | 変換不能な仕様 | nilを返却（無視） |

### リトライ仕様

リトライは行わない。エラー時はデフォルト設定またはエラー前の設定を使用。

## トランザクション仕様

読み取り専用のため、トランザクション管理は不要。

## パフォーマンス要件

- TTLキャッシュ: 3秒
- 設定解決: 通常10ms未満

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

- `.clj`形式の設定ファイル読み込み時は`*read-eval*`を`false`に設定してコード実行を防止
- 正規表現パターンはユーザー入力なのでReDoS攻撃に注意が必要

## 備考

- cljfmtには:alias-mapが提供されると名前空間のエイリアスが無視されるバグがあるため、空の場合はnilに設定
- :classpath-config-pathsにより、依存ライブラリからも設定を読み込み可能

---

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

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

### 推奨読解順序

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

cljfmt設定のデータ構造を理解する。

| 順序 | ファイル | パス | 読解ポイント |
|-----|---------|------|-------------|
| 1-1 | format.clj | `lib/src/clojure_lsp/feature/format.clj` | cljfmt設定マップの構造（:indents, :extra-indents, :alias-map） |

**読解のコツ**: cljfmtの:indentsは`{シンボル [ルール...]}`形式。ルールは`[:block N]`や`[:inner depth index?]`など。

#### Step 2: 設定ファイル読み込みを理解する

| 順序 | ファイル | パス | 読解ポイント |
|-----|---------|------|-------------|
| 2-1 | format.clj | `lib/src/clojure_lsp/feature/format.clj` | **21-33行目**: `resolve-user-cljfmt-config`関数 |
| 2-2 | settings.clj | `lib/src/clojure_lsp/settings.clj` | **62行目**: `:cljfmt-config-path`のデフォルト値 |

**主要処理フロー**:
1. **22行目**: プロジェクトルートを取得
2. **23行目**: `:cljfmt-config-path`設定を取得（デフォルト: ".cljfmt.edn"）
3. **24-26行目**: パスが絶対パスか相対パスかを判定
4. **27-28行目**: LSP設定の`:cljfmt`とファイル設定をディープマージ
5. **29-33行目**: ファイル拡張子に応じた読み込み方法を選択

#### Step 3: :style/indent変換を理解する

| 順序 | ファイル | パス | 読解ポイント |
|-----|---------|------|-------------|
| 3-1 | format.clj | `lib/src/clojure_lsp/feature/format.clj` | **35-44行目**: `arg-spec->cljfmt-arg`関数 |
| 3-2 | format.clj | `lib/src/clojure_lsp/feature/format.clj` | **46-81行目**: `style-indent->cljfmt-spec`関数 |
| 3-3 | format.clj | `lib/src/clojure_lsp/feature/format.clj` | **83-94行目**: `extract-style-indent-metadata`関数 |

**主要処理フロー**:
- **35-44行目**: 引数仕様をcljfmtの`[:inner depth index]`形式に変換
- **46-81行目**: CIDER形式の:style/indentをcljfmtルールに変換
  - 数値 -> `[:block N]`
  - `:defn` -> `[:inner 0]`
  - `:form` -> nil
  - ベクター -> 複合ルール
- **83-94行目**: db.analysisからvar-definitionsを走査し:style/indentを抽出

#### Step 4: 設定マージを理解する

| 順序 | ファイル | パス | 読解ポイント |
|-----|---------|------|-------------|
| 4-1 | format.clj | `lib/src/clojure_lsp/feature/format.clj` | **96-103行目**: `merge-configs`関数 |
| 4-2 | format.clj | `lib/src/clojure_lsp/feature/format.clj` | **105-117行目**: `resolve-cljfmt-config`と`cljfmt-config` |

**主要処理フロー**:
- **96-103行目**: 2つ以上の設定をマージ。:indentsと:extra-indentsは個別にマージ
- **105-112行目**: デフォルト設定、:style/indent設定、ユーザー設定を順にマージ
- **112行目**: alias-mapが空の場合はnilに設定（cljfmtバグ回避）
- **116-117行目**: 3秒のTTLでメモライズ

#### Step 5: クラスパス設定を理解する

| 順序 | ファイル | パス | 読解ポイント |
|-----|---------|------|-------------|
| 5-1 | config.clj | `lib/src/clojure_lsp/config.clj` | **87-101行目**: `jar-file->config`関数 |
| 5-2 | config.clj | `lib/src/clojure_lsp/config.clj` | **140-168行目**: `resolve-from-classpath-config-paths` |

**主要処理フロー**:
- **87-101行目**: JARファイル内の`clojure-lsp.exports/*/config.edn`を読み込み
- **140-168行目**: クラスパス上の設定を再帰的に解決してマージ

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

```
フォーマット処理
    │
    └─ cljfmt-config (format.clj:116-117)
           │
           └─ resolve-cljfmt-config (format.clj:105-112)
                  │
                  ├─ cljfmt.config/default-config
                  │
                  ├─ extract-style-indent-metadata (format.clj:83-94)
                  │      │
                  │      └─ style-indent->cljfmt-spec (format.clj:46-81)
                  │             └─ arg-spec->cljfmt-arg (format.clj:35-44)
                  │
                  └─ resolve-user-cljfmt-config (format.clj:21-33)
                         │
                         ├─ settings/get (settings.clj)
                         │      └─ :cljfmt-config-path
                         │      └─ :cljfmt
                         │
                         └─ edn/read-string または read-string
```

### データフロー図

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

.cljfmt.edn ─────────────────────►┐
                                  │
.lsp/config.edn                   │  resolve-user-cljfmt-config
  └─ :cljfmt ───────────────────►├─────────────────────────────────►┐
                                  │                                  │
:cljfmt-config-path ─────────────►┘                                  │
                                                                     │
                                                                     │  merge-configs
cljfmt.config/default-config ───────────────────────────────────────►├────────────────►  統合cljfmt設定
                                                                     │                    ├─ :indents
db.analysis                                                          │                    ├─ :extra-indents
  └─ var-definitions                                                 │                    └─ :alias-map
       └─ :meta                   extract-style-indent-metadata      │
            └─ :style/indent ───────────────────────────────────────►┘
                                       │
                                       └─ style-indent->cljfmt-spec
```

### 関連ファイル一覧

| ファイル | パス | 種別 | 役割 |
|---------|------|------|------|
| format.clj | `lib/src/clojure_lsp/feature/format.clj` | ソース | cljfmt設定解決の主要実装 |
| config.clj | `lib/src/clojure_lsp/config.clj` | ソース | プロジェクト設定の読み込み |
| settings.clj | `lib/src/clojure_lsp/settings.clj` | ソース | LSP設定管理 |
| queries.clj | `lib/src/clojure_lsp/queries.clj` | ソース | 解析データのクエリ |
| format_test.clj | `lib/test/clojure_lsp/feature/format_test.clj` | テスト | :style/indent変換のテスト |
| settings_test.clj | `lib/test/clojure_lsp/settings_test.clj` | テスト | 設定処理のテスト |
| config_test.clj | `lib/test/clojure_lsp/config_test.clj` | テスト | 設定読み込みのテスト |
