---
generated_at: 2026-03-01 12:00:00
metrics:
  claims_total: 67
  claims_with_evidence: 61
  claims_without_evidence: 6
confidence_derived: 0.91
---

# 根拠レポート：crates_node 単体テストケース一覧

## 本レポートについて

### 目的
本レポートは、生成された設計書・ドキュメントの信頼性を検証し、人間レビュアーが効率的にレビューできるようにすることを目的としています。

### チェック方法
以下の観点でドキュメントの内容（Claim：主張）を検証しています：

1. **根拠の有無確認**：各主張に対して、ソースコード・既存設計書・要件定義書などの根拠（Evidence）が存在するか
2. **根拠との整合性**：主張の内容が根拠と矛盾していないか
3. **網羅性**：参照すべき情報源を適切にカバーしているか

### 信頼度スコアの算出
- **confidence_derived** = 根拠あり件数 / 総主張件数
- 状態「○」：根拠あり、「△」：根拠不足または要確認

### 本レポートの使い方
1. まず「サマリー」で全体の信頼度と優先レビュー項目を確認
2. 「Claims と根拠の対応」で △ の項目を重点的にレビュー
3. 「不足情報」で補完が必要な情報源を確認

---

## 1) サマリー（まず見るところ）
- 総合信頼度（derived）：**0.91**
  - 根拠あり：61 / 67、根拠なし：6
- 優先レビュー（高）
  1. **C-34（マルチバイト文字のUTF-16位置確認）**：Scanner内部でのIndexConverter連携はコードから読み取れるが、具体的な期待値の正確性は実行確認が必要
  2. **C-36（content/file両方指定時のcontent優先）**：unwrap_or_elseのセマンティクスから推定されるが、get_candidates_with_positionsの冒頭でcontent.unwrap_or_elseが先に評価されるため仕様の明示的記載はない
  3. **C-61（3バイトUTF-8文字の変換）**：Rust標準ライブラリの仕様から推定できるが、既存テストにはこのパターンが含まれていない
  4. **C-62（2バイトUTF-8文字の変換）**：Rust標準ライブラリの仕様から推定できるが、既存テストにはこのパターンが含まれていない
  5. **C-66（混合文字列の変換）**：各文字種の個別テストから推測できるが、統合的な検証はない
  6. **C-67（リセット後の再スキャン）**：コードロジックから推測されるが明示的なテストがない

## 2) 参照した情報（Evidence一覧）
> ここに「実在するもの」だけ列挙。抽出フェーズで付けたIDをそのまま出す。

- E-01: `crates/node/src/lib.rs` - lib.rsソースコード全体（構造体定義、From実装、Scannerメソッド実装）
- E-02: `crates/node/src/utf16.rs` - utf16.rsソースコード全体（IndexConverter構造体、new/getメソッド実装）
- E-03: `crates/node/src/utf16.rs` 49-93行目 - 既存テストコード `test_index_converter`（UTF-8からUTF-16変換の期待値マッピング）
- E-04: `crates/node/Cargo.toml` - クレート依存関係（napi, napi-derive, tailwindcss-oxide）
- E-05: `crates/node/build.rs` - ビルドスクリプト（napi_build::setup()）

## 3) Claims と根拠の対応（レビューの主戦場）
| Claim ID | 主張 | Evidence | 状態 |
|---|---|---|---|
| C-01 | fileフィールドが設定されている場合にFile変換される | E-01 (44-48行目: if let Some(file)分岐でreturn) | ○ |
| C-02 | contentフィールドが設定されている場合にContent変換される | E-01 (50-52行目: if let Some(contents)分岐) | ○ |
| C-03 | fileとcontent両方設定時にfileが優先される | E-01 (46-48行目: fileのチェックが先にありreturnで抜ける) | ○ |
| C-04 | fileもcontentもNoneの場合にパニックする | E-01 (54行目: unreachable!()) | ○ |
| C-05 | extensionが空文字列の場合に変換される | E-01 (45-52行目: extensionのバリデーションなし) | ○ |
| C-06 | contentが空文字列の場合に変換される | E-01 (50-52行目: Someであれば内容を問わず変換) | ○ |
| C-07 | fileが空文字列の場合に変換される | E-01 (46-48行目: Someであれば内容を問わず変換) | ○ |
| C-08 | マルチバイト文字を含むファイルパスが変換される | E-01 (46-48行目: String型の値をそのまま渡す) | ○ |
| C-09 | 大量のコンテンツが変換される | E-01 (50-52行目: String型の値をそのまま渡す、サイズ制限なし) | ○ |
| C-10 | GlobEntryからoxide::GlobEntryへ正しく変換される | E-01 (58-65行目: フィールド直接マッピング) | ○ |
| C-11 | oxide::GlobEntryからGlobEntryへ正しく変換される | E-01 (67-74行目: フィールド直接マッピング) | ○ |
| C-12 | baseが空文字列の場合にGlobEntryが変換される | E-01 (58-65行目: バリデーションなし) | ○ |
| C-13 | patternが空文字列の場合にGlobEntryが変換される | E-01 (58-65行目: バリデーションなし) | ○ |
| C-14 | base/pattern両方空文字列の場合にGlobEntryが変換される | E-01 (58-65行目: バリデーションなし) | ○ |
| C-15 | GlobEntryの双方向変換でデータが保持される | E-01 (58-74行目: 両方向のFrom実装が同じフィールドをマッピング) | ○ |
| C-16 | negated=falseのSourceEntryが変換される | E-01 (76-84行目: フィールド直接マッピング) | ○ |
| C-17 | negated=trueのSourceEntryが変換される | E-01 (76-84行目: フィールド直接マッピング) | ○ |
| C-18 | base/patternが空文字列のSourceEntryが変換される | E-01 (76-84行目: バリデーションなし) | ○ |
| C-19 | 特殊グロブ文字を含むSourceEntryが変換される | E-01 (76-84行目: String型をそのまま渡す) | ○ |
| C-20 | sourcesがSomeでScannerが初期化される | E-01 (114-121行目: Some分岐でinto_iter().map) | ○ |
| C-21 | sourcesがNoneでScannerが空ソースで初期化される | E-01 (119行目: None => vec![]) | ○ |
| C-22 | sourcesが空Vecの場合にScannerが初期化される | E-01 (117行目: Some(sources)で空Vecもマッチ) | ○ |
| C-23 | 複数のSourceEntryでScannerが初期化される | E-01 (117行目: sources.into_iter().map(Into::into).collect()) | ○ |
| C-24 | scanでスキャン結果が返却される | E-01 (124-126行目: self.scanner.scan()の委譲) | ○ |
| C-25 | ソースが空の場合にscanで空Vecが返却される | E-01 (124-126行目: 空ソースScanner経由) | ○ |
| C-26 | scan_filesでChangedContentリストからスキャン結果が返却される | E-01 (129-133行目: scan_contentへの委譲) | ○ |
| C-27 | scan_filesに空Vecを入力で空結果が返却される | E-01 (129-133行目: 空Vecの変換) | ○ |
| C-28 | 複数のChangedContentから候補が統合抽出される | E-01 (129-133行目: input.into_iter().map(Into::into).collect()) | ○ |
| C-29 | fileベースのChangedContentがscan_filesで処理される | E-01 (129-133行目), E-01 (44-56行目: From実装でFile変換) | ○ |
| C-30 | get_candidates_with_positionsでcontent指定時に位置付き候補が返却される | E-01 (136-161行目: unwrap_or_elseでcontentを取得) | ○ |
| C-31 | get_candidates_with_positionsでfile指定時にファイル読み取りで候補が返却される | E-01 (140-142行目: read_to_string) | ○ |
| C-32 | fileが存在しないパスの場合にパニックする | E-01 (141行目: expect("Failed to read file")) | ○ |
| C-33 | fileもcontentもNoneの場合にget_candidates_with_positionsでパニックする | E-01 (140-141行目: unwrap_or_elseでinput.file.unwrap()) | ○ |
| C-34 | マルチバイト文字を含むコンテンツでUTF-16位置が正しく返却される | E-01 (150-159行目: IndexConverter使用), E-02 | △ |
| C-35 | 空文字列のcontentで空ベクタが返却される | E-01 (140行目: content取得後oxide側で処理) | ○ |
| C-36 | contentとfile両方設定時にcontentが使用される | **根拠不足** (unwrap_or_elseの動作から推測、lib.rs 140行目) | △ |
| C-37 | filesゲッターでファイルパス一覧が返却される | E-01 (164-166行目: self.scanner.get_files()) | ○ |
| C-38 | スキャン前のfilesゲッター呼び出しの挙動 | E-01 (164-166行目: get_files()への委譲、内部動作はoxide依存) | ○ |
| C-39 | globsゲッターでグロブ一覧が返却される | E-01 (169-176行目: get_globs().map(Into::into)) | ○ |
| C-40 | 空ソースのScannerでglobsが空ベクタを返す | E-01 (169-176行目) | ○ |
| C-41 | normalized_sourcesゲッターでソース一覧が返却される | E-01 (179-186行目: get_normalized_sources().map(Into::into)) | ○ |
| C-42 | 空ソースのScannerでnormalized_sourcesが空ベクタを返す | E-01 (179-186行目) | ○ |
| C-43 | IndexConverterが有効な文字列で初期化される | E-02 (11-17行目: newの実装) | ○ |
| C-44 | IndexConverterが空文字列で初期化される | E-02 (11-17行目: newに空文字列も受理可能) | ○ |
| C-45 | マルチバイト文字を含む文字列でIndexConverterが初期化される | E-02 (11-17行目: &strスライスで任意UTF-8受理) | ○ |
| C-46 | ASCII文字のpos=0でUTF-8/UTF-16インデックスが一致する | E-03 (60行目: (0, 0)) | ○ |
| C-47 | ASCII文字列の各位置で正しいインデックスが返却される | E-03 (60-66行目: 0-5のマッピング) | ○ |
| C-48 | 4バイトUTF-8文字の開始位置が正しく変換される | E-03 (66行目: (6, 6)) | ○ |
| C-49 | 4バイトUTF-8文字の内部バイト位置が文字の終了位置に変換される | E-03 (68-71行目: (7,8), (8,8), (9,8), (10,8)) | ○ |
| C-50 | 2つ目の4バイトUTF-8文字の内部バイト位置が正しく変換される | E-03 (73-76行目: (11,10)-(14,10)) | ○ |
| C-51 | 4バイト文字後のASCII文字インデックスが正しい | E-03 (78行目: (15, 11)) | ○ |
| C-52 | 文字列末尾の位置が正しく変換される | E-03 (84行目: (21, 17)) | ○ |
| C-53 | 順方向のインクリメンタル呼び出しで正しい結果が返却される | E-02 (25-28行目: pos < curr_utf8の場合のリセットロジック), E-03 | ○ |
| C-54 | 逆方向呼び出しでcurr_utf8/curr_utf16がリセットされ正しい結果が返却される | E-02 (25-28行目: pos < curr_utf8でリセット) | ○ |
| C-55 | 同じposを連続呼び出しで同じ結果が返却される | E-02 (37行目: curr_utf8 >= posでbreak) | ○ |
| C-56 | pos=0の場合に0が返却される | E-03 (60行目: (0, 0)) | ○ |
| C-57 | posが文字列バイト長を超える場合に最後のUTF-16インデックスが返却される | E-03 (87行目: (100, 17)) | ○ |
| C-58 | posがちょうど文字列バイト長+1の場合の挙動 | E-03 (86行目: (22, 17)) | ○ |
| C-59 | 空文字列にpos=0を指定で0が返却される | E-02 (36-43行目: charsイテレーションが空でスキップ) | ○ |
| C-60 | 空文字列にpos>0を指定で0が返却される | E-02 (36-43行目: charsイテレーションが空、curr_utf16は0のまま) | ○ |
| C-61 | 3バイトUTF-8文字（日本語）のUTF-16インデックスが正しく返却される | **根拠なし** (Rust std::char仕様から推定) | △ |
| C-62 | 2バイトUTF-8文字のUTF-16インデックスが正しく返却される | **根拠なし** (Rust std::char仕様から推定) | △ |
| C-63 | 1文字ASCII文字列でpos=0が正しく変換される | E-02 (36-43行目: 1文字でもcharsが動作), E-03 (60行目から類推) | ○ |
| C-64 | 1文字の絵文字でpos=0が正しく変換される | E-02 (36-43行目), E-03 (66行目から類推) | ○ |
| C-65 | 1文字の絵文字でpos=4（バイト長超え）が処理される | E-02 (36-43行目: charsが1文字で終了、len_utf16()=2), E-03 (87行目の超過パターンから類推) | ○ |
| C-66 | 混合文字列（ASCII+日本語+絵文字）で正しく変換される | **根拠なし** (個別文字種テストからの推定) | △ |
| C-67 | 逆方向呼び出し後に再度順方向で正しく動作する | **根拠なし** (リセットロジックから推定、E-02 25-28行目) | △ |

## 4) 不足情報（Unknown / Missing）
- C-34: Scanner内部でIndexConverterを使用してUTF-16変換を行うことはコードから確認できるが、特定の入力に対するScannerの候補抽出結果は内部ロジック（tailwindcss_oxide）に依存しており、具体的な期待値を断定するにはoxideクレートの動作確認が必要
  - 候補：tailwindcss_oxide の単体テスト / 結合テスト / ドキュメント
- C-36: `get_candidates_with_positions`でcontent/file両方指定時の挙動は、`input.content.unwrap_or_else(|| { std::fs::read_to_string(input.file.unwrap()) })`の動作から推測される。contentがSomeならunwrap_or_elseはcontentの値を返しファイル読み取りは行われない。ただし明示的な仕様記載はない
  - 候補：メソッドのドキュメントコメント / N-API呼び出し側の仕様
- C-61: 3バイトUTF-8文字（日本語文字 U+3000-U+9FFF）のインデックス変換はRust標準ライブラリの `char::len_utf8()` = 3、`char::len_utf16()` = 1 の仕様から推定できるが、このモジュールの既存テストにはこのパターンが含まれていない
  - 候補：Rust std::char ドキュメント / 追加テストケース
- C-62: 2バイトUTF-8文字（例: U+00E9 = e accent）の `char::len_utf8()` = 2、`char::len_utf16()` = 1 の仕様から推定できるが、既存テストにはこのパターンが含まれていない
  - 候補：Rust std::char ドキュメント / 追加テストケース
- C-66: ASCII、日本語、絵文字が混在する文字列での変換は、各文字種の個別テスト結果から正しく動作すると推測できるが、混合パターンの統合的な検証はない
  - 候補：混合文字列のテストケース追加
- C-67: 逆方向呼び出し後の再順方向動作は、リセットロジック（curr_utf8 = 0, curr_utf16 = 0）の後に順方向スキャンが正しく行われることをコードから推測できるが、3段階以上の呼び出しパターンの明示的なテストはない
  - 候補：既存テストの拡張

## 5) リスクフラグ（レビュー観点）
- 0: 低リスク - From トレイト実装（C-01 から C-19）は単純なフィールドマッピングであり、ソースコードから直接確認可能
- 0: 低リスク - IndexConverter コンストラクタ（C-43 から C-45）は初期値設定のみで自明
- 1: 中リスク - Scannerメソッド（C-20 から C-42）はtailwindcss_oxideクレートへの委譲が主であり、内部動作の正確性はoxideクレートに依存する
- 1: 中リスク - IndexConverter getメソッド（C-46 から C-67）のうち、既存テストでカバーされていないUTF-8文字パターン（2バイト・3バイト文字、混合文字列）の期待値は追加検証が望ましい
- 2: 高リスク - `get_candidates_with_positions`（C-30 から C-36）はファイルI/O、UTF-16変換、oxide内部ロジックが組み合わさるため、統合的な動作確認が重要

## 6) レビュアーチェックリスト（最小）
- [ ] ChangedContent::fromのfile優先仕様（C-03）がプロジェクトの要件と一致するか確認
- [ ] unreachable!()（C-04）が本当に到達不能であることをN-APIレイヤでの呼び出しパターンから確認
- [ ] get_candidates_with_positionsのexpect("Failed to read file")（C-32）がN-API経由のエラーハンドリングとして適切か確認
- [ ] get_candidates_with_positionsでcontent/file両方指定時の挙動（C-36）が意図された動作か確認
- [ ] IndexConverterの2バイト・3バイトUTF-8文字に対するテストケース（C-61, C-62）の期待値をRust標準ライブラリの仕様と照合
- [ ] 混合文字列テスト（C-66）の期待値を各文字のUTF-8/UTF-16バイト数から手動計算して検証
- [ ] Scannerの各メソッドがtailwindcss_oxideに正しく委譲されていることの結合テスト観点を確認
