コンテンツにスキップ

Plugin boilerplate reduction — phased plan

Status: Plan, 2026-06-02. 20260601-plugin-mechanism-pre-1.0-review.md §1のボイラープレートの所見に対する実装ロードマップ。非規範的です。公開サーフェスへの追加(新しいRigor::SourcePlugin::Baseヘルパー)は、着地するたびにADR-2ADR-37に記録され、spec/rigor/public_api_drift_spec.rbにピン留めされます。

ボイラープレートが存在する理由を取り除く。単にDRYにするのではない。最大の単一重複 — 約25個のプラグインにある手作りのAST走査器 — は、diagnostics_for_fileが生のrootを渡し、作者にそれを自分で走査せよと指示するという理由だけで存在します。ADR-37のNodeRuleはエンジンに走査を所有させるので、それらの走査器は理由ごと消えます。したがって、巧妙な共有walkヘルパーを手で作り込むのは無駄な作業になります。

そこで作業は単一のテストで分けられます。その重複はADR-37を生き延びるか?

  • 生き延びる → 今すぐ抽出する(後悔なし)。これらは即座に報われ、インターフェース分離リファクタリングに触れられません。
  • ADR-37に吸収される → ヘルパーを手で作り込まない。リファクタリングに取り除かせる。

インベントリの分割(レビュー §1.1のカウント)

Section titled “インベントリの分割(レビュー §1.1のカウント)”
DuplicationCountResolutionSurvives ADR-37?
Literal Symbol/String extraction20 + 4 in coreRigor::Source::Literals✅ extract now
Diagnostic construction (start_column + 1)23Diagnostic.from_node / Base#diagnostic✅ extract now
levenshtein / did-you-mean4Base#suggest (DidYouMean::SpellChecker)✅ extract now
config.fetch + DEFAULT_*17config_schema default: slot✅ extract now
Inflector copiesseveralPlugin::Inflector✅ extract now
Discoverer skeleton + NameKeyedIndex + load-error rescue + cache idiom4 + 6 + 10ClassDiscoverer / NameKeyedIndex base (FactProvider side, orthogonal to NodeRule)✅ mid
AST walker25NodeRule (engine-owned walk)❌ do not hand-build
dispatch-loop duplication / fan-out2ADR-37 slice 2 (indexed registry)❌ absorbed

カウントが最も多い2項目(走査器25、ディスパッチ2)は意図的に手でパッチしません — ADR-37がそれらを取り除きます。

それぞれが、ADR-37を生き延びる重複を畳み込み、かつコア自身のコピーを脱重複するので、プラグイン境界の両側で報われます。

  • 0a Rigor::Source::Literalssymbol_or_string(node)symbol(node)symbol_arguments(call)symbol_arg(call, index)。4つのコアのコピー(sig_gen/observation_collectorsig_gen/generatoranalysis/dependency_source_inference/return_type_heuristicinference/synthetic_method_scanner)をまず純粋なリファクタリングとして畳み込み、その後プラグイン向けに公開します。
  • 0b Diagnostic.from_node(node, path:, message:, severity:, rule:)Plugin::Base#diagnostic(node, …) — 要となるstart_column + 1の慣習を内部化します。analysis/check_rules.rbのインラインパターン(15箇所以上)を畳み込み、プラグインにビルダーを与えます。
  • 0c Plugin::Base#suggest(name, candidates)DidYouMean::SpellCheckerのラッパー。手作りのlevenshteinコピーを退役させます。
  • 0d config_schema default:スロットManifestスキーマの変更。Base#configが宣言されたデフォルトをマージするので、DEFAULT_*定数イディオムが退役します。
  • 0e Rigor::Plugin::Inflector — 1つの語形変化モジュール。routes(×2)、activerecord、actionmailer/actionpackのunderscoreコピーを退役させます。 ADR-39による再構成: singularizepluralizeのコピーは偽陽性に敏感(FP-sensitive)です(ルートヘルパー/モデル名解決に供給されるため)。そのため近似を統一するのではなく、このモジュールは許可リスト(allow-list)+rescueハーネスを通して実際のActiveSupport::Inflectorを呼び出し、config/initializers/inflections.rbを静的に解析してプロジェクト独自のルールを取り込みます。純粋なunderscoreのケース変換は、ADR-39の作業から独立して着地できる安全な部分集合です。

Phase 1 — ブリッジ(最小投資)

Section titled “Phase 1 — ブリッジ(最小投資)”

既存のRigor::Source::NodeWalkerをプラグインサーフェスに公開+文書化します。require1つ+文書化のみ — プラグインがNodeRuleへ移行するにつれて自然に退役するので、ここにそれ以上の投資はしません。

Phase 2 — 発見の基底(FactProvider側、NodeRuleと直交)

Section titled “Phase 2 — 発見の基底(FactProvider側、NodeRuleと直交)”

ClassDiscoverer基底+NameKeyedIndexdiscover_ruby_filesruby_files_underread_safelyrescue三つ組を畳み込む)+標準のglob_descriptorキャッシュイディオム。4つのRails発見プラグイン(activejob/actioncable/activestorage/actionmailer)を移行し、ほぼ同一のAST走査+インデックスコードのファイル約4個を削除します。

NodeRuleの着地が、25個の走査器、23箇所のインラインDiagnostic構築(今やBase#diagnostic経由)、そして複製されたディスパッチループを取り除きます。ProtocolContractChecker基底(hanami/webのそっくりな重複)をここに追加します。

  • 各抽出は純粋なリファクタリングであり、挙動を保存します。安全網は、ゴールデンマスターとして使う既存のrun_plugin統合スペックと、すべてのスライスでグリーンのままのmake verify(テスト並列+リント+セルフチェック)です。
  • PRごとに1つのプラグインファミリーを移行します。各PRは「重複N → 1、テストは不変」を実証します。
  • レビュー §1.1の重複カウントを前後で追跡し、進捗を定量的にします。
  • 公開サーフェスへの追加はspec/rigor/public_api_drift_spec.rbのスナップショットを更新し、ADR-2/ADR-37に記録されます。

Phase 0a(Rigor::Source::Literals。最高のROI: 4つのコアのコピーと20個のプラグインを脱重複し、ADR-37を無傷で生き延び、リグレッションリスク最小の純粋なリファクタリングです。

© 2026 TypedDuck. Licensed under CC BY-SA 4.0.