require "yaml" require "json" require "tempfile" module RuboCopAutoCollrect class << self def run_all!(targets, *cop_names, commit_log_format:, fixup_commit_log_format:) plan(targets, *cop_names).each do |cop_name| run!(targets, cop_name, commit_log_format: commit_log_format) end run!(targets, *cop_names, commit_log_format: fixup_commit_log_format) end def run!(targets, *cop_names, commit_log_format:) cop_list = cop_names.join(",") unless cop_names.empty? print "\n*** auto-correct by #{cop_list || 'AllCops'}...\n\n" only_cops = cop_list ? ["--only", cop_list] : [] begin rubocop_auto_correct("--format", "autogenconf", *only_cops, *targets) rescue print "\n** try to detect errors...\n\n" rubocop_check(*only_cops, *targets) abort end return unless git_diff? git("add", ".") git("commit", "-q", "-m", format(commit_log_format, cop_list: cop_list)) print "\n** commit changes\n\n" end def plan(targets, *cop_names) print "*** calculate auto-correct plan...\n\n" only_cops = cop_names.empty? ? [] : ["--only", cop_names.join(",")] begin output = Tempfile.open("auto_correct_plan") do |tf| rubocop_auto_correct( *only_cops, "--display-cop-names", "--format", "fuubar", "--format", "json", "--out", tf.path, *targets, ) tf.read end rescue print "\n** try to detect errors...\n\n" rubocop_check(*only_cops, *targets) abort end git("restore", ".") categories = cop_names.map {|cop_name| cop_name[%r{\A[^/]*}] }.uniq corrected_cops = corrected_cops(JSON.parse(output)) corrected_cops.keys.sort_by do |cop_name| count = corrected_cops[cop_name] idx = categories.index {|category| cop_name.start_with?(category) } [idx || categories.size, -count] end end private def corrected_cops(rubocop_output) rubocop_output["files"] .map {|f| f["offenses"].select {|o| o["corrected"] } } .flatten .group_by {|o| o["cop_name"] } .transform_values(&:size) end def rubocop(*args, on_failure: :raise) command("rubocop", "--safe", *args) rescue => e warn "\n#{e}" abort if on_failure == :abort raise end def rubocop_auto_correct(*args) rubocop( "--fail-level", "E", "--safe-auto-correct", *args, ) end def rubocop_check(*args) rubocop( "--fail-level", "E", "--display-only-fail-level-offenses", *args, on_failure: :abort, ) end def git(*args) command("git", *args) rescue => e warn e abort end def git_diff? command("git", "diff", "--quiet") false rescue true end def command(*cmd) system(*cmd) raise "failed: #{cmd.join(' ')}" unless $?.success? end end end sep_idx = ARGV.index("--") || 0 targets = ARGV[(sep_idx + 1)..-1] commit_log_prefix, = ARGV[0, sep_idx] commit_log_format = "#{commit_log_prefix}auto-correct by RuboCop %s" cop_names = %w(Layout Style) RuboCopAutoCollrect.run_all!( targets, *cop_names, commit_log_format: commit_log_format, fixup_commit_log_format: "#{commit_log_format} (fix-up)", )