require 'open3' require 'shiba' require 'shiba/review/diff' require 'shiba/review/api' require 'shiba/review/comment_renderer' module Shiba # TODO: # 1. Properly handle more than a handful of review failures # 2. May make sense to edit the comment on a commit line when the code # is semi-corrected but still a problem class Reviewer MESSAGE_FILTER_THRESHOLD = 0.005 attr_reader :repo_url, :problems, :options def initialize(repo_url, problems, options) @repo_url = repo_url @problems = problems @options = options @commit_id = options.fetch("branch") do if options["submit"] raise Shiba::Error.new("Must specify a branch") end end end def comments return @comments if @comments @comments = problems.map do |path, explain| file, line_number = path.split(":") if path.empty? || line_number.nil? raise Shiba::Error.new("Bad path received: #{line_number}") end position = if path == "none:-1" nil else diff_parser.find_position(file, line_number.to_i) end explain = keep_only_dangerous_messages(explain) { body: renderer.render(explain), commit_id: @commit_id, path: file, line: line_number, position: position } end end # FIXME: Only submit 10 comments for now. The rest just vanish. # Submits commits, checking to makre sure the line doesn't already have a review. def submit report("Connecting to #{api.uri}") api.connect do previous_reviews = api.previous_comments.map { |c| c['body'] } comments[0,10].each do |c| if previous_reviews.any? { |r| r == c[:body] } report("skipped duplicate comment") next end # :line isn't part of the github api comment = c.dup.tap { |dc| dc.delete(:line) } if options[:verbose] comment[:body] += " (verbose mode ts=#{Time.now.to_i})" end res = api.comment_on_pull_request(comment) report("API success #{res.inspect}") end end report("HTTP request finished") end def repo_host @repo_host ||= api.host_and_path.first end def repo_path @repo_path ||= api.host_and_path.last end protected def report(message) if options["verbose"] $stderr.puts message end end def keep_only_dangerous_messages(explain) explain_b = explain.dup explain_b["messages"] = explain_b["messages"].select do |message| message['cost'] && message['cost'] > MESSAGE_FILTER_THRESHOLD end explain_b end def diff_parser @diff_parser ||= Review::Diff::Parser.new(diff.file) end def diff @diff ||= if options['diff'] report("Finding PR position using file: #{options['diff']}") Review::Diff::FileDiff.new(options['diff']) else report("Finding PR position using git") Review::Diff::GitDiff.new(options) end end def api @api ||= begin api_options = { "token" => options["token"], "pull_request" => options["pull_request"] } Review::API.new(repo_url, api_options) end end def renderer @renderer ||= Review::CommentRenderer.new(tags) end def tags @tags ||= YAML.load_file(Shiba::TEMPLATE_FILE) end end end