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