Module: RspecSprint::Fix
- Defined in:
- lib/rspec_sprint/fix.rb
Overview
‘fix –dry-run let-it-be` の orchestrator。suite は CLI 側で 1 回走らせ済みで、その Result(rspec JSON + FactoryProf JSON のパス)を受け取る。FactoryProf はhard precondition(設計): 無ければ診断不能としてヒントを返す。
Class Method Summary collapse
-
.apply(result, dir: ".", limit: 10, all: false) ⇒ Object
let/let! 候補を verify-and-revert で適用する。 limit: 処理するファイル上限(ROI 降順)。all: true なら全ファイルを処理。.
- .dry_run(result, dir: ".") ⇒ Object
- .git_not_repo_hint ⇒ Object
- .git_repo?(dir) ⇒ Boolean
- .let_it_be_recipe_loaded?(dir) ⇒ Boolean
- .parse_verify_duration(out_path) ⇒ Object
- .recipe_not_loaded_hint(dir) ⇒ Object
- .relative(path, dir) ⇒ Object
- .rspec_runner(dir) ⇒ Object
- .spec_files(dir) ⇒ Object
Class Method Details
.apply(result, dir: ".", limit: 10, all: false) ⇒ Object
let/let! 候補を verify-and-revert で適用する。limit: 処理するファイル上限(ROI 降順)。all: true なら全ファイルを処理。
49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 70 71 72 73 74 75 76 77 78 79 80 81 82 83 84 85 86 |
# File 'lib/rspec_sprint/fix.rb', line 49 def apply(result, dir: ".", limit: 10, all: false) return Doctor.factory_prof_missing_hint unless result.factory_prof? return git_not_repo_hint unless git_repo?(dir) return recipe_not_loaded_hint(dir) unless let_it_be_recipe_loaded?(dir) snapshot = Normalizer.new( rspec_json: result.rspec_json_path, factory_prof_json: result.factory_prof_path ).call factory_time = snapshot.factories.each_with_object({}) do |f, h| h[f.name.to_s] = f.top_level_time.to_f end files_with_candidates = {} spec_files(dir).each do |abs_path| rel_path = relative(abs_path, dir) source = File.read(abs_path) cands = Fixers::LetItBe::Detector.scan_all(source, file_path: rel_path) files_with_candidates[abs_path] = cands unless cands.empty? end return "変換可能な候補が見つかりませんでした(let/let! の単一 create 本体が対象)。" \ " まず `rspec-sprint fix let-it-be --dry-run` で確認してください。" if files_with_candidates.empty? sorted = files_with_candidates.sort_by do |_path, cands| -cands.map { |c| factory_time[c.factory_name.to_s].to_f }.sum end target_files = all ? sorted : sorted.first(limit) verifier = Fixers::LetItBe::Verifier.new(runner: rspec_runner(dir), dir: dir) file_results = target_files.map do |abs_path, cands| verifier.verify_file(abs_path, cands) end Fixers::LetItBe::ApplyFormatter.format(file_results) end |
.dry_run(result, dir: ".") ⇒ Object
23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 |
# File 'lib/rspec_sprint/fix.rb', line 23 def dry_run(result, dir: ".") return Doctor.factory_prof_missing_hint unless result.factory_prof? snapshot = Normalizer.new( rspec_json: result.rspec_json_path, factory_prof_json: result.factory_prof_path ).call candidates = [] let_bang_count = 0 spec_files(dir).each do |path| source = File.read(path) candidates.concat(Fixers::LetItBe::Detector.scan(source, file_path: relative(path, dir))) let_bang_count += Fixers::LetItBe::Detector.count_let_bang_create(source) end report = Fixers::LetItBe::Report.build( candidates: candidates, factories: snapshot.factories, let_bang_count: let_bang_count ) Fixers::LetItBe::Formatter.format(report) end |
.git_not_repo_hint ⇒ Object
102 103 104 |
# File 'lib/rspec_sprint/fix.rb', line 102 def git_not_repo_hint "エラー: git リポジトリが見つかりません。`fix` 適用は git が必要です(復元保証のため)。" end |
.git_repo?(dir) ⇒ Boolean
97 98 99 100 |
# File 'lib/rspec_sprint/fix.rb', line 97 def git_repo?(dir) _, status = Open3.capture2e("git", "-C", dir, "rev-parse", "--git-dir") status.success? end |
.let_it_be_recipe_loaded?(dir) ⇒ Boolean
106 107 108 109 110 111 112 113 114 115 |
# File 'lib/rspec_sprint/fix.rb', line 106 def let_it_be_recipe_loaded?(dir) helper_files = Dir.glob(File.join(dir, "spec/{spec_helper,rails_helper}.rb")) helper_files.any? do |f| content = File.read(f) content.include?("test_prof/recipes/rspec/let_it_be") || content.include?("RSpecLetItBe") end rescue StandardError false end |
.parse_verify_duration(out_path) ⇒ Object
145 146 147 148 149 150 151 152 |
# File 'lib/rspec_sprint/fix.rb', line 145 def parse_verify_duration(out_path) return 0.0 unless File.exist?(out_path) && !File.zero?(out_path) data = JSON.parse(File.read(out_path)) data.dig("summary", "duration")&.to_f || 0.0 rescue JSON::ParserError 0.0 end |
.recipe_not_loaded_hint(dir) ⇒ Object
117 118 119 120 121 122 123 124 125 126 127 |
# File 'lib/rspec_sprint/fix.rb', line 117 def recipe_not_loaded_hint(dir) <<~MSG エラー: `let_it_be` recipe がロードされていません。 spec/spec_helper.rb または spec/rails_helper.rb に以下を追加してください: require "test_prof/recipes/rspec/let_it_be" 追加後、もう一度 `rspec-sprint fix let-it-be` を実行してください。 MSG end |
.relative(path, dir) ⇒ Object
92 93 94 95 |
# File 'lib/rspec_sprint/fix.rb', line 92 def relative(path, dir) base = File.(dir) File.(path).delete_prefix("#{base}/") end |
.rspec_runner(dir) ⇒ Object
129 130 131 132 133 134 135 136 137 138 139 140 141 142 143 |
# File 'lib/rspec_sprint/fix.rb', line 129 def rspec_runner(dir) out_dir = File.join(dir, "tmp", "rspec_sprint", "verify") FileUtils.mkdir_p(out_dir) ->(path) { out_path = File.join(out_dir, "#{Digest::MD5.hexdigest(path)[0..7]}_verify.json") _, status = Open3.capture2e( "bundle", "exec", "rspec", path, "--format", "json", "--out", out_path, chdir: dir ) green = status.exitstatus&.zero? || false duration = parse_verify_duration(out_path) {green: green, duration: duration} } end |
.spec_files(dir) ⇒ Object
88 89 90 |
# File 'lib/rspec_sprint/fix.rb', line 88 def spec_files(dir) Dir.glob(File.join(dir, "spec/**/*_spec.rb")).sort end |