lib/danger/request_sources/vsts.rb in danger-8.4.5 vs lib/danger/request_sources/vsts.rb in danger-8.5.0

- old
+ new

@@ -41,10 +41,14 @@ def scm @scm ||= GitRepo.new end + def client + @api + end + def host @host ||= @api.host end def fetch_details @@ -74,19 +78,40 @@ def update_pull_request!(warnings: [], errors: [], messages: [], markdowns: [], danger_id: "danger", new_comment: false, remove_previous_comments: false) unless @api.supports_comments? return end - comment = generate_description(warnings: warnings, errors: errors) + regular_violations = regular_violations_group( + warnings: warnings, + errors: errors, + messages: messages, + markdowns: markdowns + ) + + inline_violations = inline_violations_group( + warnings: warnings, + errors: errors, + messages: messages, + markdowns: markdowns + ) + + rest_inline_violations = submit_inline_comments!(**{ + danger_id: danger_id, + previous_violations: {} + }.merge(inline_violations)) + + main_violations = merge_violations( + regular_violations, rest_inline_violations + ) + + comment = generate_description(warnings: main_violations[:warnings], errors: main_violations[:errors]) comment += "\n\n" - comment += generate_comment(warnings: warnings, - errors: errors, - messages: messages, - markdowns: markdowns, - previous_violations: {}, - danger_id: danger_id, - template: "vsts") + comment += generate_comment(**{ + previous_violations: {}, + danger_id: danger_id, + template: "vsts" + }.merge(main_violations)) if new_comment || remove_previous_comments post_new_comment(comment) else update_old_comment(comment, danger_id: danger_id) end @@ -103,15 +128,153 @@ comment = c[:comments].first comment_id = comment[:id] comment_content = comment[:content].nil? ? "" : comment[:content] # Skip the comment if it wasn't posted by danger next unless comment_content.include?("generated_by_#{danger_id}") + # Skip the comment if it's an inline comment + next unless c[:threadContext].nil? # Updated the danger posted comment @api.update_comment(thread_id, comment_id, new_comment) comment_updated = true end # If no comment was updated, post a new one post_new_comment(new_comment) unless comment_updated end + + def submit_inline_comments!(warnings: [], errors: [], messages: [], markdowns: [], previous_violations: [], danger_id: "danger") + # Avoid doing any fetchs if there's no inline comments + return {} if (warnings + errors + messages + markdowns).select(&:inline?).empty? + + pr_threads = @api.fetch_last_comments + danger_threads = pr_threads.select do |thread| + comment = thread[:comments].first + comment_content = comment[:content].nil? ? "" : comment[:content] + + next comment_content.include?("generated_by_#{danger_id}") + end + non_danger_threads = pr_threads - danger_threads + + warnings = submit_inline_comments_for_kind!(:warning, warnings, danger_threads, previous_violations["warning"], danger_id: danger_id) + errors = submit_inline_comments_for_kind!(:error, errors, danger_threads, previous_violations["error"], danger_id: danger_id) + messages = submit_inline_comments_for_kind!(:message, messages, danger_threads, previous_violations["message"], danger_id: danger_id) + markdowns = submit_inline_comments_for_kind!(:markdown, markdowns, danger_threads, [], danger_id: danger_id) + + # submit removes from the array all comments that are still in force + # so we strike out all remaining ones + danger_threads.each do |thread| + violation = violations_from_table(thread[:comments].first[:content]).first + if !violation.nil? && violation.sticky + body = generate_inline_comment_body("white_check_mark", violation, danger_id: danger_id, resolved: true, template: "github") + @api.update_comment(thread[:id], thread[:comments].first[:id], body) + end + end + + { + warnings: warnings, + errors: errors, + messages: messages, + markdowns: markdowns + } + end + + def messages_are_equivalent(m1, m2) + blob_regexp = %r{blob/[0-9a-z]+/} + m1.file == m2.file && m1.line == m2.line && + m1.message.sub(blob_regexp, "") == m2.message.sub(blob_regexp, "") + end + + def submit_inline_comments_for_kind!(kind, messages, danger_threads, previous_violations, danger_id: "danger") + previous_violations ||= [] + is_markdown_content = kind == :markdown + emoji = { warning: "warning", error: "no_entry_sign", message: "book" }[kind] + + messages.reject do |m| + next false unless m.file && m.line + + # Once we know we're gonna submit it, we format it + if is_markdown_content + body = generate_inline_markdown_body(m, danger_id: danger_id, template: "vsts") + else + # Hide the inline link behind a span + m.message.gsub!("\n", "<br />") + m = process_markdown(m, true) + body = generate_inline_comment_body(emoji, m, danger_id: danger_id, template: "vsts") + # A comment might be in previous_violations because only now it's part of the unified diff + # We remove from the array since it won't have a place in the table anymore + previous_violations.reject! { |v| messages_are_equivalent(v, m) } + end + + matching_threads = danger_threads.select do |comment_data| + if comment_data.key?(:threadContext) && !comment_data[:threadContext].nil? && + comment_data[:threadContext][:filePath] == m.file && + comment_data[:threadContext].key?(:rightFileStart) && + comment_data[:threadContext][:rightFileStart][:line] == m.line + # Parse it to avoid problems with strikethrough + violation = violations_from_table(comment_data[:comments].first[:content]).first + if violation + messages_are_equivalent(violation, m) + else + blob_regexp = %r{blob/[0-9a-z]+/} + comment_data[:comments].first[:content].sub(blob_regexp, "") == body.sub(blob_regexp, "") + end + else + false + end + end + + if matching_threads.empty? + @api.post_inline_comment(body, m.file, m.line) + + # Not reject because this comment has not completed + next false + else + # Remove the surviving comment so we don't strike it out + danger_threads.reject! { |c| matching_threads.include? c } + + # Update the comment to remove the strikethrough if present + thread = matching_threads.first + @api.update_comment(thread[:id], thread[:comments].first[:id], body) + end + + # Remove this element from the array + next true + end + end + + private + + def regular_violations_group(warnings: [], errors: [], messages: [], markdowns: []) + { + warnings: warnings.reject(&:inline?), + errors: errors.reject(&:inline?), + messages: messages.reject(&:inline?), + markdowns: markdowns.reject(&:inline?) + } + end + + def inline_violations_group(warnings: [], errors: [], messages: [], markdowns: []) + cmp = proc do |a, b| + next -1 unless a.file && a.line + next 1 unless b.file && b.line + + next a.line <=> b.line if a.file == b.file + next a.file <=> b.file + end + + # Sort to group inline comments by file + { + warnings: warnings.select(&:inline?).sort(&cmp), + errors: errors.select(&:inline?).sort(&cmp), + messages: messages.select(&:inline?).sort(&cmp), + markdowns: markdowns.select(&:inline?).sort(&cmp) + } + end + + def merge_violations(*violation_groups) + violation_groups.inject({}) do |accumulator, group| + accumulator.merge(group) { |_, old, fresh| old + fresh } + end + end + end end end