Re: B-150計画フィードバック: 最終版計画(ステップ付きワイルドカードテスト・OR説明文改善反映)
B-150 Cron Parser仕様整合・バリデーション改善 -- 最終版計画
変更履歴
- 初版: 19caca12dd9
- 本版: レビューフィードバック(19caca44f7d / 19caca3e375)の2点を反映
フィードバック反映内容
- [重要] 修正3にステップ付きワイルドカードのOR判定テストを追加:
0 0 */2 * 1のようにrawが*/2の場合にOR判定が正しく動作することを確認するテストケースを追加した。 - [軽微] 修正3のOR条件日本語説明文をより自然な表現に変更: 「1日 または 月曜 0時0分 に実行」ではなく、「毎月1日、または毎週月曜の 0時0分 に実行」のように、DOM/DOWそれぞれに適切な頻度修飾語(毎月/毎週)を付与し、時刻の前に「の」を挿入する方針に変更した。
概要
Cron Parserツール(logic.ts)に存在する4つの仕様不整合・バリデーション問題を修正する。全修正はlogic.ts 1ファイルに集中し、インターフェース変更は不要。テスト追加はlogic.test.ts 1ファイルで完結する。
前提: 対象Cron方言
UNIX/Vixie cron(5フィールド標準)に準拠する。
- 5フィールド: 分 時 日 月 曜日
- 曜日: 0-7(0と7が日曜日)
- DOM/DOW: 両方が非
*の場合はOR判定 - meta.tsの既存FAQで「標準的な5フィールド形式のみ対応」と記載済み
修正計画(4件)
修正1: 不正トークン「1a」受理問題 (#23)
対象: logic.ts parseFieldValues() 96-99行目
根本原因: JavaScriptの parseInt("1a", 10) が 1 を返す仕様により、末尾に非数値文字を含むトークンがバリデーションをすり抜ける。
修正内容: parseInt 呼び出し前に /^\d+$/ 正規表現チェックを追加する。
影響範囲: parseFieldValues 関数の単値パース部分のみ。レンジパターンやステップパターンは正規表現で数字部分を抽出しているため影響なし。
テストケース (logic.test.ts に追加):
parseCron("1a * * * *")->valid: falseparseCron("1.5 * * * *")->valid: falseparseCron("1e2 * * * *")->valid: falseparseCron("+1 * * * *")->valid: false
修正2: 午後13時表示問題 (#24)
対象: logic.ts buildFullDescription() 262-265行目
根本原因: 午前/午後 プレフィックスを付けているが、時刻値を12時間形式に変換していない。
修正内容: 24時間表記に統一し、午前/午後プレフィックスを削除する。
選択理由:
- Cronはプログラマー向けツールであり、24時間表記が自然
- 既存の
describeCronField("hour")が24時間表記を使用しており一貫性がある - 12時間表記への変換は12時/0時の境界でエッジケースが多い
修正後の表示例: 0 13 * * * -> 「毎日 13時0分 に実行」
テストケース:
parseCron("0 13 * * *").description-> 「午後13時」を含まないことparseCron("0 13 * * *").description-> 「13時0分」を含むことparseCron("0 0 * * *").description-> 「0時0分」を含むことparseCron("30 23 * * *").description-> 「23時30分」を含むことparseCron("0 9 * * *").description-> 「9時0分」を含むこと
修正3: DOM/DOW AND判定問題 (#3) [フィードバック反映]
対象: logic.ts getNextExecutions() 416-421行目、buildFullDescription() 237-245行目
根本原因: UNIX/Vixie cronの標準仕様では、DOMとDOWがともに非*の場合はOR判定だが、現実装はAND判定になっている。
修正内容:
(a) getNextExecutions のOR判定ロジック
getNextExecutions の日付マッチング条件で、DOMとDOWの raw フィールドが両方とも "*" でない場合にORロジックを適用する。
判定ロジック:
parsed.dayOfMonth.raw !== "*"かつparsed.dayOfWeek.raw !== "*"の場合: domMatch OR dowMatch- それ以外: domMatch AND dowMatch(従来通り)
重要: */2 のようなステップ付きワイルドカードは raw が "*/2" であり "*" とは不一致となるため、正しくOR判定の対象になる。これはVixie cron仕様(DOMまたはDOWが「制限されている」場合にOR判定)に合致する。ビルダーはこの点を認識した上で実装すること。
(b) buildFullDescription のOR説明文 [フィードバック反映: 日本語表現改善]
DOMとDOWが両方非*の場合の説明テキストを、OR条件を明示するように修正する。
日本語表現の方針:
- DOMには「毎月」を付与する(例: 「毎月1日」「毎月15日」)
- DOWには「毎週」を付与する(例: 「毎週月曜」「毎週金曜」)
- DOM/DOWを「、または」で接続する
- 時刻の前に「の」を挿入する
表現例:
0 0 1 * 1-> 「毎月1日、または毎週月曜の 0時0分 に実行」0 0 15 * 5-> 「毎月15日、または毎週金曜の 0時0分 に実行」0 0 */2 * 1-> 「毎月2日ごと、または毎週月曜の 0時0分 に実行」0 0 1,15 * 1-> 「毎月1日と15日、または毎週月曜の 0時0分 に実行」
実装方針: buildFullDescription のDOM/DOW部分(L237-245)で、両方が非*の場合に通常のpush処理ではなく、「毎月{DOM.description}、または毎週{DOW.description}の」という複合文字列を1つのpartsエントリとして生成する。時刻部分は既存のロジックで後続のpartsエントリとして追加されるが、DOM/DOWのOR文と時刻の間に「の」が入るよう調整する。
注意: monthが非*の場合(例: 0 0 1 6 5)は「毎月」ではなく月名が既にpartsに入っているため、「毎月」の付与は月フィールドが*の場合のみ適用する。
テストケース [フィードバック反映: ステップ付きワイルドカードテスト追加]:
getNextExecutions("0 0 1 * 1", 10, from)-> 結果に「月の1日」と「月曜日」の両方が含まれる(OR条件)getNextExecutions("0 0 15 * 5", 10, from)-> 結果に「月の15日」と「金曜日」の両方が含まれるgetNextExecutions("0 0 1 * *", 5, from)-> 従来通りAND(DOWが*なので)getNextExecutions("0 0 * * 1", 5, from)-> 従来通りAND(DOMが*なので)- [追加]
getNextExecutions("0 0 */2 * 1", 10, from)-> ステップ付きワイルドカード*/2でOR判定が正しく適用されることを検証。結果に偶数日(2日,4日,...)と月曜日の両方が含まれること。rawが"*/2"であり"*"ではないため、OR対象になることの確認。 parseCron("0 0 1 * 1").description-> 「毎月1日、または毎週月曜」を含むこと- [追加]
parseCron("0 0 */2 * 1").description-> 「または」を含むこと(ステップ付きワイルドカードでもOR説明が生成されること)
修正4: 1年以上先の取りこぼし問題 (#22)
対象: logic.ts getNextExecutions() 404行目
根本原因: 探索ウィンドウが最大366日に固定されており、年次実行Cronで count > 1 を要求すると取りこぼしが発生する。
修正内容: MAX_ITERATIONS を count に比例させて拡大する。
MAX_ITERATIONS = count * 366 * 24 * 60
パフォーマンス考慮:
- UIからの呼び出しは count=5 固定なので、最大5年分の探索(約263万イテレーション)
- 頻繁に実行されるCronは早期にcountに到達してループを抜けるため問題なし
テストケース:
getNextExecutions("0 0 1 1 *", 3, from)-> 3件返ることを検証(年次実行で3年分)getNextExecutions("0 0 29 2 *", 2, from)-> 2件返ることを検証(うるう年のみ、約8年分の探索)
実施手順
- logic.ts の修正: 4つの修正を以下の順番で適用する
- 修正1(parseFieldValues の正規表現チェック)
- 修正2(24時間表記への統一)
- 修正4(MAX_ITERATIONS の拡大)
- 修正3(DOM/DOW OR判定 + 説明文のOR反映)-- 説明文の変更を伴うため最後に実施
- logic.test.ts のテスト追加: 各修正に対応するテストケースを追加する(合計約18件)
- 全テスト実行:
npx vitest run src/tools/cron-parser/__tests__/logic.test.tsで全テストが通ることを確認 - ビルド確認:
npm run buildでビルドエラーがないことを確認 - レビュー依頼: reviewerに修正内容のレビューを依頼
完了条件
- 修正1:
parseCron("1a * * * *")等がvalid: falseを返す - 修正2:
parseCron("0 13 * * *").descriptionに「午後13時」が含まれず「13時0分」が含まれる - 修正3:
getNextExecutions("0 0 1 * 1", 10)がOR条件の結果を返す - 修正3:
getNextExecutions("0 0 */2 * 1", 10)がステップ付きワイルドカードでOR判定が正しく適用される - 修正3: 説明文が「毎月{DOM}、または毎週{DOW}の {時刻} に実行」の形式になる
- 修正4:
getNextExecutions("0 0 1 1 *", 3)が3件返る - 既存48テストが全て通る
- 新規テストが全て通る(約18テスト追加見込み)
-
npm run buildが成功する - meta.ts の updatedAt を更新する
変更ファイル一覧
| ファイル | 変更内容 |
|---|---|
| src/tools/cron-parser/logic.ts | 4つの修正(parseFieldValues, buildFullDescription, getNextExecutions) |
| src/tools/cron-parser/tests/logic.test.ts | テストケース追加(約18件) |
| src/tools/cron-parser/meta.ts | updatedAt を更新 |
リスク評価
- 全修正がlogic.ts 1ファイルに集中しており、影響範囲が限定的
- インターフェース変更なし、Component.tsx の変更なし
- 既存48テストで回帰を検出可能
- 低リスク・小規模な修正