require 'json' module Checkstyle class FileReviewer HEADERS = { 'Authorization' => "token #{ENV['GITHUB_TOKEN']}" } RUBOCOP_QUOTES = [ "> - Robo, excuse me, Robo. Any special message for all the kids watching at home?\n> - Stay out of trouble.", '> Four... three... two... one... I am now authorized to use physical force!', '> Thank you for your co-operation. Good night.', '> Serve the public trust. Protect the innocent. Uphold the law.', '> Your move, creep.', '> Come quietly or there will be trouble.', '> You are under arrest.', "> Dead or alive, you're coming with me!", '> Oooh, guns guns guns!', '> Excuse me, I have to go. Somewhere there is a crime happening.' ] attr_reader :file_path, :repository, :violations def initialize(repository, file_path, violations) @file_path = file_path @violations = violations @repository = repository end def review log "Reviewing file #{file_path}" violations.each { |violation| comment_violation(violation) } end private def line_for_violation(patch, violation) violating_line = violation['location']['line'] meta, *patch_lines = patch.split("\n") start = meta.match(/\+(\d+)/)[1].to_i patch_index = 0 patch_lines.each_with_index do |content, patch_position| next if content.first == '-' line_number = start + patch_index return { number: line_number, patch_position: patch_position + 1, content: content } if line_number == violating_line patch_index += 1 end {} end def comment_violation(violation) commit_sha = violating_commit_sha(violation) commit = get("#{commit_url(commit_sha)}?v=3", cache: true).data file_object = commit.files.find { |f| f.filename == file_path } line = line_for_violation(file_object.patch, violation) comment_on_line(file_object, line[:number], line[:patch_position], violation, commit) end # Gets SHA of the commit where line was last changed def violating_commit_sha(violation) line = violation['location']['line'] `git log --format="%H" -1 -L #{line},#{line}:#{file_path} | head -n 1`.strip end def commit_url(sha) "https://api.github.com/repos/#{repository}/commits/#{sha}" end def already_commented?(file, line_number, violation, commit) comments = get(commit.comments_url).data comments.any? do |comment| comment.body.scan(comment_signature(file, line_number, violation)).any? end end def comment_on_line(file, line_number, patch_position, violation, commit) if already_commented?(file, line_number, violation, commit) log "\tI already commented on this" return end body = { body: line_comment(file, line_number, violation), path: file.filename, position: patch_position } LHC.post(commit.comments_url, headers: HEADERS, body: body.to_json) log "\tfile change commented on Github" rescue LHC::Error log "\tencountered problems while trying to post comment file change on Github" end def comment_signature(file, line_number, violation) "*#{file.filename}:#{line_number} #{violation['cop_name']}*" end def get(url, options = {}) LHC.get(url, options.reverse_merge(headers: HEADERS)) end def line_comment(file, line_number, violation) return line_comment_with_quote(file, line_number, violation) if rand > 0.6 plain_line_comment(file, line_number, violation) end def line_comment_with_quote(file, line_number, violation) "#{plain_line_comment(file, line_number, violation)}\n\n#{RUBOCOP_QUOTES.sample}" end def plain_line_comment(file, line_number, violation) "**[#{violation['severity']}]** #{violation['message']}\n#{comment_signature(file, line_number, violation)}" end def log(text) puts "[CHECKSTYLE][Review file] #{text}" end end end