danger 8.4.5 → 9.3.0

Sign up to get free protection for your applications and to get access to all the features.
Files changed (83) hide show
  1. checksums.yaml +4 -4
  2. data/LICENSE +1 -1
  3. data/bin/danger +1 -1
  4. data/lib/danger/ci_source/appcircle.rb +83 -0
  5. data/lib/danger/ci_source/appveyor.rb +1 -0
  6. data/lib/danger/ci_source/azure_pipelines.rb +20 -5
  7. data/lib/danger/ci_source/bamboo.rb +1 -1
  8. data/lib/danger/ci_source/bitbucket_pipelines.rb +1 -2
  9. data/lib/danger/ci_source/bitrise.rb +10 -10
  10. data/lib/danger/ci_source/buildkite.rb +1 -1
  11. data/lib/danger/ci_source/circle.rb +1 -1
  12. data/lib/danger/ci_source/circle_api.rb +2 -2
  13. data/lib/danger/ci_source/code_build.rb +20 -6
  14. data/lib/danger/ci_source/codefresh.rb +1 -1
  15. data/lib/danger/ci_source/concourse.rb +4 -5
  16. data/lib/danger/ci_source/custom_ci_with_github.rb +45 -0
  17. data/lib/danger/ci_source/dotci.rb +4 -6
  18. data/lib/danger/ci_source/github_actions.rb +6 -6
  19. data/lib/danger/ci_source/gitlab_ci.rb +4 -4
  20. data/lib/danger/ci_source/jenkins.rb +22 -23
  21. data/lib/danger/ci_source/local_git_repo.rb +7 -1
  22. data/lib/danger/ci_source/local_only_git_repo.rb +1 -0
  23. data/lib/danger/ci_source/support/find_repo_info_from_url.rb +11 -10
  24. data/lib/danger/ci_source/support/pull_request_finder.rb +15 -7
  25. data/lib/danger/ci_source/teamcity.rb +1 -1
  26. data/lib/danger/ci_source/xcode_cloud.rb +7 -7
  27. data/lib/danger/commands/init.rb +1 -1
  28. data/lib/danger/commands/local.rb +3 -3
  29. data/lib/danger/commands/local_helpers/http_cache.rb +2 -0
  30. data/lib/danger/commands/local_helpers/local_setup.rb +2 -0
  31. data/lib/danger/commands/local_helpers/pry_setup.rb +1 -0
  32. data/lib/danger/commands/plugins/plugin_json.rb +1 -3
  33. data/lib/danger/commands/plugins/plugin_lint.rb +0 -2
  34. data/lib/danger/commands/plugins/plugin_readme.rb +2 -5
  35. data/lib/danger/commands/pr.rb +2 -1
  36. data/lib/danger/commands/runner.rb +1 -1
  37. data/lib/danger/commands/staging.rb +7 -7
  38. data/lib/danger/commands/systems.rb +4 -6
  39. data/lib/danger/comment_generators/gitlab_inline.md.erb +1 -1
  40. data/lib/danger/comment_generators/vsts_inline.md.erb +17 -0
  41. data/lib/danger/core_ext/file_list.rb +2 -2
  42. data/lib/danger/danger_core/dangerfile.rb +16 -16
  43. data/lib/danger/danger_core/environment_manager.rb +2 -1
  44. data/lib/danger/danger_core/executor.rb +10 -11
  45. data/lib/danger/danger_core/message_aggregator.rb +1 -0
  46. data/lib/danger/danger_core/messages/base.rb +1 -0
  47. data/lib/danger/danger_core/messages/markdown.rb +3 -4
  48. data/lib/danger/danger_core/messages/violation.rb +1 -2
  49. data/lib/danger/danger_core/plugins/dangerfile_bitbucket_cloud_plugin.rb +1 -3
  50. data/lib/danger/danger_core/plugins/dangerfile_bitbucket_server_plugin.rb +4 -4
  51. data/lib/danger/danger_core/plugins/dangerfile_danger_plugin.rb +9 -3
  52. data/lib/danger/danger_core/plugins/dangerfile_git_plugin.rb +4 -3
  53. data/lib/danger/danger_core/plugins/dangerfile_github_plugin.rb +2 -0
  54. data/lib/danger/danger_core/plugins/dangerfile_gitlab_plugin.rb +3 -1
  55. data/lib/danger/danger_core/plugins/dangerfile_local_only_plugin.rb +4 -3
  56. data/lib/danger/danger_core/plugins/dangerfile_messaging_plugin.rb +3 -1
  57. data/lib/danger/danger_core/plugins/dangerfile_vsts_plugin.rb +3 -3
  58. data/lib/danger/danger_core/standard_error.rb +2 -3
  59. data/lib/danger/helpers/comments_helper.rb +8 -7
  60. data/lib/danger/helpers/comments_parsing_helper.rb +3 -2
  61. data/lib/danger/helpers/emoji_mapper.rb +1 -1
  62. data/lib/danger/plugin_support/plugin.rb +0 -1
  63. data/lib/danger/plugin_support/plugin_linter.rb +1 -0
  64. data/lib/danger/request_sources/bitbucket_cloud.rb +17 -14
  65. data/lib/danger/request_sources/bitbucket_cloud_api.rb +2 -4
  66. data/lib/danger/request_sources/bitbucket_server.rb +47 -10
  67. data/lib/danger/request_sources/bitbucket_server_api.rb +14 -10
  68. data/lib/danger/request_sources/code_insights_api.rb +7 -11
  69. data/lib/danger/request_sources/github/github.rb +15 -14
  70. data/lib/danger/request_sources/github/github_review.rb +3 -2
  71. data/lib/danger/request_sources/github/github_review_resolver.rb +0 -2
  72. data/lib/danger/request_sources/github/github_review_unsupported.rb +0 -2
  73. data/lib/danger/request_sources/gitlab.rb +46 -58
  74. data/lib/danger/request_sources/local_only.rb +0 -2
  75. data/lib/danger/request_sources/request_source.rb +4 -4
  76. data/lib/danger/request_sources/support/get_ignored_violation.rb +1 -1
  77. data/lib/danger/request_sources/vsts.rb +175 -14
  78. data/lib/danger/request_sources/vsts_api.rb +39 -4
  79. data/lib/danger/scm_source/git_repo.rb +2 -1
  80. data/lib/danger/version.rb +1 -1
  81. data/lib/danger.rb +1 -0
  82. metadata +42 -40
  83. data/lib/danger/ci_source/vsts.rb +0 -73
@@ -1,5 +1,3 @@
1
- # coding: utf-8
2
-
3
1
  # rubocop:disable Metrics/ClassLength
4
2
 
5
3
  require "octokit"
@@ -65,6 +63,7 @@ module Danger
65
63
 
66
64
  def client
67
65
  raise "No API token given, please provide one using `DANGER_GITHUB_API_TOKEN` or `DANGER_GITHUB_BEARER_TOKEN`" if !valid_access_token? && !valid_bearer_token? && !support_tokenless_auth
66
+
68
67
  @client ||= begin
69
68
  Octokit.configure do |config|
70
69
  config.connection_options[:ssl] = { verify: verify_ssl }
@@ -83,6 +82,7 @@ module Danger
83
82
 
84
83
  def review
85
84
  return @review unless @review.nil?
85
+
86
86
  begin
87
87
  @review = client.pull_request_reviews(ci_source.repo_slug, ci_source.pull_request_id)
88
88
  .map { |review_json| Danger::RequestSources::GitHubSource::Review.new(client, ci_source, review_json) }
@@ -133,10 +133,8 @@ module Danger
133
133
  end
134
134
 
135
135
  def issue_comments
136
- @comments ||= begin
137
- client.issue_comments(ci_source.repo_slug, ci_source.pull_request_id)
138
- .map { |comment| Comment.from_github(comment) }
139
- end
136
+ @comments ||= client.issue_comments(ci_source.repo_slug, ci_source.pull_request_id)
137
+ .map { |comment| Comment.from_github(comment) }
140
138
  end
141
139
 
142
140
  # Sending data to GitHub
@@ -224,7 +222,7 @@ module Danger
224
222
  context: "danger/#{danger_id}",
225
223
  target_url: details_url
226
224
  })
227
- rescue
225
+ rescue StandardError
228
226
  # This usually means the user has no commit access to this repo
229
227
  # That's always the case for open source projects where you can only
230
228
  # use a read-only GitHub account
@@ -248,6 +246,7 @@ module Danger
248
246
  issue_comments.each do |comment|
249
247
  next unless comment.generated_by_danger?(danger_id)
250
248
  next if comment.id == except
249
+
251
250
  client.delete_comment(ci_source.repo_slug, comment.id)
252
251
  end
253
252
  end
@@ -456,9 +455,9 @@ module Danger
456
455
 
457
456
  # @return [String] The organisation name, is nil if it can't be detected
458
457
  def organisation
459
- matched = self.issue_json["repository_url"].match(%r{repos\/(.*)\/})
458
+ matched = self.issue_json["repository_url"].match(%r{repos/(.*)/})
460
459
  return matched[1] if matched && matched[1]
461
- rescue
460
+ rescue StandardError
462
461
  nil
463
462
  end
464
463
 
@@ -473,17 +472,18 @@ module Danger
473
472
  end
474
473
 
475
474
  # @return [String] A URL to the specific file, ready to be downloaded
476
- def file_url(organisation: nil, repository: nil, branch: nil, path: nil)
475
+ def file_url(organisation: nil, repository: nil, ref: nil, branch: nil, path: nil)
477
476
  organisation ||= self.organisation
477
+ ref ||= branch
478
478
 
479
479
  begin
480
- # Retrieve the download URL (default branch on nil param)
481
- contents = client.contents("#{organisation}/#{repository}", path: path, ref: branch)
480
+ # Retrieve the download URL (default ref on nil param)
481
+ contents = client.contents("#{organisation}/#{repository}", path: path, ref: ref)
482
482
  @download_url = contents["download_url"]
483
483
  rescue Octokit::ClientError
484
484
  # Fallback to github.com
485
- branch ||= "master"
486
- @download_url = "https://raw.githubusercontent.com/#{organisation}/#{repository}/#{branch}/#{path}"
485
+ ref ||= "master"
486
+ @download_url = "https://raw.githubusercontent.com/#{organisation}/#{repository}/#{ref}/#{path}"
487
487
  end
488
488
  end
489
489
 
@@ -512,6 +512,7 @@ module Danger
512
512
  next 1 unless b.file && b.line
513
513
 
514
514
  next a.line <=> b.line if a.file == b.file
515
+
515
516
  next a.file <=> b.file
516
517
  end
517
518
 
@@ -1,5 +1,3 @@
1
- # coding: utf-8
2
-
3
1
  require "octokit"
4
2
  require "danger/ci_source/ci_source"
5
3
  require "danger/request_sources/github/github_review_resolver"
@@ -35,16 +33,19 @@ module Danger
35
33
 
36
34
  def id
37
35
  return nil unless self.review_json
36
+
38
37
  self.review_json["id"]
39
38
  end
40
39
 
41
40
  def body
42
41
  return "" unless self.review_json
42
+
43
43
  self.review_json["body"]
44
44
  end
45
45
 
46
46
  def status
47
47
  return STATUS_PENDING if self.review_json.nil?
48
+
48
49
  return self.review_json["state"]
49
50
  end
50
51
 
@@ -1,5 +1,3 @@
1
- # coding: utf-8
2
-
3
1
  require "danger/request_sources/github/github_review"
4
2
 
5
3
  module Danger
@@ -1,5 +1,3 @@
1
- # coding: utf-8
2
-
3
1
  module Danger
4
2
  module RequestSources
5
3
  module GitHubSource
@@ -1,4 +1,3 @@
1
- # coding: utf-8
2
1
  require "uri"
3
2
  require "danger/helpers/comments_helper"
4
3
  require "danger/helpers/comment"
@@ -72,20 +71,18 @@ module Danger
72
71
  def mr_comments
73
72
  # @raw_comments contains what we got back from the server.
74
73
  # @comments contains Comment objects (that have less information)
75
- @comments ||= begin
76
- if supports_inline_comments
77
- @raw_comments = mr_discussions
78
- .auto_paginate
79
- .flat_map { |discussion| discussion.notes.map { |note| note.to_h.merge({"discussion_id" => discussion.id}) } }
80
- @raw_comments
81
- .map { |comment| Comment.from_gitlab(comment) }
82
- else
83
- @raw_comments = client.merge_request_comments(ci_source.repo_slug, ci_source.pull_request_id, per_page: 100)
84
- .auto_paginate
85
- @raw_comments
86
- .map { |comment| Comment.from_gitlab(comment) }
87
- end
88
- end
74
+ @comments ||= if supports_inline_comments
75
+ @raw_comments = mr_discussions
76
+ .auto_paginate
77
+ .flat_map { |discussion| discussion.notes.map { |note| note.to_h.merge({ "discussion_id" => discussion.id }) } }
78
+ @raw_comments
79
+ .map { |comment| Comment.from_gitlab(comment) }
80
+ else
81
+ @raw_comments = client.merge_request_comments(ci_source.repo_slug, ci_source.pull_request_id, per_page: 100)
82
+ .auto_paginate
83
+ @raw_comments
84
+ .map { |comment| Comment.from_gitlab(comment) }
85
+ end
89
86
  end
90
87
 
91
88
  def mr_discussions
@@ -96,10 +93,10 @@ module Danger
96
93
  @mr_diff ||= begin
97
94
  diffs = mr_changes.changes.map do |change|
98
95
  diff = change["diff"]
99
- if diff.start_with?('--- a/')
96
+ if diff.start_with?("--- a/")
100
97
  diff
101
98
  else
102
- "--- a/#{change["old_path"]}\n+++ b/#{change["new_path"]}\n#{diff}"
99
+ "--- a/#{change['old_path']}\n+++ b/#{change['new_path']}\n#{diff}"
103
100
  end
104
101
  end
105
102
  diffs.join("\n")
@@ -107,24 +104,18 @@ module Danger
107
104
  end
108
105
 
109
106
  def mr_changed_paths
110
- @mr_changed_paths ||= begin
111
- mr_changes
112
- .changes.map { |change| change["new_path"] }
113
- end
107
+ @mr_changed_paths ||= mr_changes
108
+ .changes.map { |change| change["new_path"] }
114
109
 
115
110
  @mr_changed_paths
116
111
  end
117
112
 
118
113
  def mr_changes
119
- @mr_changes ||= begin
120
- client.merge_request_changes(ci_source.repo_slug, ci_source.pull_request_id)
121
- end
114
+ @mr_changes ||= client.merge_request_changes(ci_source.repo_slug, ci_source.pull_request_id)
122
115
  end
123
116
 
124
117
  def mr_closes_issues
125
- @mr_closes_issues ||= begin
126
- client.merge_request_closes_issues(ci_source.repo_slug, ci_source.pull_request_id)
127
- end
118
+ @mr_closes_issues ||= client.merge_request_closes_issues(ci_source.repo_slug, ci_source.pull_request_id)
128
119
  end
129
120
 
130
121
  def setup_danger_branches
@@ -154,16 +145,14 @@ module Danger
154
145
  end
155
146
 
156
147
  def supports_inline_comments
157
- @supports_inline_comments ||= begin
158
- # If we can't check GitLab's version, we assume we don't support inline comments
159
- if Gem.loaded_specs["gitlab"].version < FIRST_GITLAB_GEM_WITH_VERSION_CHECK
160
- false
161
- else
162
- current_version = Gem::Version.new(client.version.version)
148
+ # If we can't check GitLab's version, we assume we don't support inline comments
149
+ @supports_inline_comments ||= if Gem.loaded_specs["gitlab"].version < FIRST_GITLAB_GEM_WITH_VERSION_CHECK
150
+ false
151
+ else
152
+ current_version = Gem::Version.new(client.version.version)
163
153
 
164
- current_version >= FIRST_VERSION_WITH_INLINE_COMMENTS
165
- end
166
- end
154
+ current_version >= FIRST_VERSION_WITH_INLINE_COMMENTS
155
+ end
167
156
  end
168
157
 
169
158
  def update_pull_request!(warnings: [], errors: [], messages: [], markdowns: [], danger_id: "danger", new_comment: false, remove_previous_comments: false)
@@ -253,11 +242,11 @@ module Danger
253
242
  delete_old_comments!(danger_id: danger_id)
254
243
  else
255
244
  body = generate_comment(warnings: warnings,
256
- errors: errors,
245
+ errors: errors,
257
246
  messages: messages,
258
- markdowns: markdowns,
259
- previous_violations: previous_violations,
260
- danger_id: danger_id,
247
+ markdowns: markdowns,
248
+ previous_violations: previous_violations,
249
+ danger_id: danger_id,
261
250
  template: "gitlab")
262
251
 
263
252
  if editable_comments.empty? or should_create_new_comment
@@ -278,11 +267,10 @@ module Danger
278
267
 
279
268
  def delete_old_comments!(except: nil, danger_id: "danger")
280
269
  @raw_comments.each do |raw_comment|
281
-
282
270
  comment = Comment.from_gitlab(raw_comment)
283
271
  next unless comment.generated_by_danger?(danger_id)
284
272
  next if comment.id == except
285
- next unless raw_comment.is_a?(Hash) && raw_comment["position"].nil?
273
+ next unless raw_comment.kind_of?(Hash) && raw_comment["position"].nil?
286
274
 
287
275
  begin
288
276
  client.delete_merge_request_comment(
@@ -290,7 +278,7 @@ module Danger
290
278
  ci_source.pull_request_id,
291
279
  comment.id
292
280
  )
293
- rescue
281
+ rescue StandardError
294
282
  end
295
283
  end
296
284
  end
@@ -298,7 +286,7 @@ module Danger
298
286
  def markdown_link_to_message(message, _)
299
287
  "#{message.file}#L#{message.line}: "
300
288
  end
301
-
289
+
302
290
  # @return [String] The organisation name, is nil if it can't be detected
303
291
  def organisation
304
292
  nil # TODO: Implement this
@@ -315,12 +303,12 @@ module Danger
315
303
  end
316
304
 
317
305
  # @return [String] A URL to the specific file, ready to be downloaded
318
- def file_url(organisation: nil, repository: nil, branch: nil, path: nil)
319
- branch ||= 'master'
306
+ def file_url(organisation: nil, repository: nil, ref: nil, branch: nil, path: nil)
307
+ ref ||= (branch || "master")
320
308
  # According to GitLab Repositories API docs path and id(slug) should be encoded.
321
309
  path = URI.encode_www_form_component(path)
322
310
  repository = URI.encode_www_form_component(repository)
323
- "#{endpoint}/projects/#{repository}/repository/files/#{path}/raw?ref=#{branch}&private_token=#{@token}"
311
+ "#{endpoint}/projects/#{repository}/repository/files/#{path}/raw?ref=#{ref}&private_token=#{@token}"
324
312
  end
325
313
 
326
314
  def regular_violations_group(warnings: [], errors: [], messages: [], markdowns: [])
@@ -338,6 +326,7 @@ module Danger
338
326
  next 1 unless b.file && b.line
339
327
 
340
328
  next a.line <=> b.line if a.file == b.file
329
+
341
330
  next a.file <=> b.file
342
331
  end
343
332
 
@@ -359,7 +348,7 @@ module Danger
359
348
  def submit_inline_comments!(warnings: [], errors: [], messages: [], markdowns: [], previous_violations: [], danger_id: "danger")
360
349
  comments = mr_discussions
361
350
  .auto_paginate
362
- .flat_map { |discussion| discussion.notes.map { |note| note.to_h.merge({"discussion_id" => discussion.id}) } }
351
+ .flat_map { |discussion| discussion.notes.map { |note| note.to_h.merge({ "discussion_id" => discussion.id }) } }
363
352
  .select { |comment| Comment.from_gitlab(comment).inline? }
364
353
 
365
354
  danger_comments = comments.select { |comment| Comment.from_gitlab(comment).generated_by_danger?(danger_id) }
@@ -401,7 +390,7 @@ module Danger
401
390
  }
402
391
  end
403
392
 
404
- def submit_inline_comments_for_kind!(kind, messages, diff_lines, danger_comments, previous_violations, danger_id: "danger")
393
+ def submit_inline_comments_for_kind!(kind, messages, _diff_lines, danger_comments, previous_violations, danger_id: "danger")
405
394
  previous_violations ||= []
406
395
  is_markdown_content = kind == :markdown
407
396
  emoji = { warning: "warning", error: "no_entry_sign", message: "book" }[kind]
@@ -440,7 +429,7 @@ module Danger
440
429
  params = {
441
430
  body: body,
442
431
  position: {
443
- position_type: 'text',
432
+ position_type: "text",
444
433
  new_path: m.file,
445
434
  new_line: m.line,
446
435
  old_path: old_position[:path],
@@ -550,20 +539,19 @@ module Danger
550
539
  line_number = 0
551
540
  diff.each_line do |line|
552
541
  if line.match range_header_regexp
553
- line = line.split('+').last
554
- line = line.split(' ').first
555
- range_string = line.split(',')
542
+ line = line.split("+").last
543
+ line = line.split(" ").first
544
+ range_string = line.split(",")
556
545
  line_number = range_string[0].to_i - 1
557
- elsif line.start_with?('+')
546
+ elsif line.start_with?("+")
558
547
  addition_lines.push(line_number)
559
- elsif line.start_with?('-')
560
- line_number=line_number-1
548
+ elsif line.start_with?("-")
549
+ line_number -= 1
561
550
  end
562
- line_number=line_number+1
551
+ line_number += 1
563
552
  end
564
553
  addition_lines
565
554
  end
566
-
567
555
  end
568
556
  end
569
557
  end
@@ -1,5 +1,3 @@
1
- # coding: utf-8
2
-
3
1
  require "danger/helpers/comments_helper"
4
2
  require "danger/helpers/comment"
5
3
 
@@ -85,12 +85,12 @@ module Danger
85
85
  raise "Subclass and overwrite organisation"
86
86
  end
87
87
 
88
- def file_url(_organisation: nil, _repository: nil, _branch: "master", _path: nil)
88
+ def file_url(_organisation: nil, _repository: nil, _ref: nil, _branch: nil, _path: nil)
89
89
  raise "Subclass and overwrite file_url"
90
90
  end
91
-
92
- def update_build_status(status)
93
- raise "Subclass and overwrite update_build_status"
91
+
92
+ def update_build_status(_status)
93
+ raise "Subclass and overwrite update_build_status"
94
94
  end
95
95
  end
96
96
  end
@@ -1,5 +1,5 @@
1
1
  class GetIgnoredViolation
2
- IGNORE_REGEXP = />*\s*danger\s*:\s*ignore\s*"(?<error>[^"]*)"/i
2
+ IGNORE_REGEXP = />*\s*danger\s*:\s*ignore\s*"(?<error>[^"]*)"/i.freeze
3
3
 
4
4
  def initialize(body)
5
5
  @body = body
@@ -1,5 +1,3 @@
1
- # coding: utf-8
2
-
3
1
  require "danger/helpers/comments_helper"
4
2
  require "danger/request_sources/vsts_api"
5
3
 
@@ -25,14 +23,13 @@ module Danger
25
23
  def initialize(ci_source, environment)
26
24
  self.ci_source = ci_source
27
25
 
28
- @is_vsts_git = environment["BUILD_REPOSITORY_PROVIDER"] == "TfsGit"
26
+ @is_vsts_ci = environment.key? "DANGER_VSTS_HOST"
29
27
 
30
- project, slug = ci_source.repo_slug.split("/")
31
- @api = VSTSAPI.new(project, slug, ci_source.pull_request_id, environment)
28
+ @api = VSTSAPI.new(ci_source.repo_slug, ci_source.pull_request_id, environment)
32
29
  end
33
30
 
34
31
  def validates_as_ci?
35
- @is_vsts_git
32
+ @is_vsts_ci
36
33
  end
37
34
 
38
35
  def validates_as_api_source?
@@ -43,6 +40,10 @@ module Danger
43
40
  @scm ||= GitRepo.new
44
41
  end
45
42
 
43
+ def client
44
+ @api
45
+ end
46
+
46
47
  def host
47
48
  @host ||= @api.host
48
49
  end
@@ -76,15 +77,36 @@ module Danger
76
77
  return
77
78
  end
78
79
 
79
- comment = generate_description(warnings: warnings, errors: errors)
80
+ regular_violations = regular_violations_group(
81
+ warnings: warnings,
82
+ errors: errors,
83
+ messages: messages,
84
+ markdowns: markdowns
85
+ )
86
+
87
+ inline_violations = inline_violations_group(
88
+ warnings: warnings,
89
+ errors: errors,
90
+ messages: messages,
91
+ markdowns: markdowns
92
+ )
93
+
94
+ rest_inline_violations = submit_inline_comments!(**{
95
+ danger_id: danger_id,
96
+ previous_violations: {}
97
+ }.merge(inline_violations))
98
+
99
+ main_violations = merge_violations(
100
+ regular_violations, rest_inline_violations
101
+ )
102
+
103
+ comment = generate_description(warnings: main_violations[:warnings], errors: main_violations[:errors])
80
104
  comment += "\n\n"
81
- comment += generate_comment(warnings: warnings,
82
- errors: errors,
83
- messages: messages,
84
- markdowns: markdowns,
85
- previous_violations: {},
86
- danger_id: danger_id,
87
- template: "vsts")
105
+ comment += generate_comment(**{
106
+ previous_violations: {},
107
+ danger_id: danger_id,
108
+ template: "vsts"
109
+ }.merge(main_violations))
88
110
  if new_comment || remove_previous_comments
89
111
  post_new_comment(comment)
90
112
  else
@@ -105,6 +127,9 @@ module Danger
105
127
  comment_content = comment[:content].nil? ? "" : comment[:content]
106
128
  # Skip the comment if it wasn't posted by danger
107
129
  next unless comment_content.include?("generated_by_#{danger_id}")
130
+ # Skip the comment if it's an inline comment
131
+ next unless c[:threadContext].nil?
132
+
108
133
  # Updated the danger posted comment
109
134
  @api.update_comment(thread_id, comment_id, new_comment)
110
135
  comment_updated = true
@@ -112,6 +137,142 @@ module Danger
112
137
  # If no comment was updated, post a new one
113
138
  post_new_comment(new_comment) unless comment_updated
114
139
  end
140
+
141
+ def submit_inline_comments!(warnings: [], errors: [], messages: [], markdowns: [], previous_violations: [], danger_id: "danger")
142
+ # Avoid doing any fetchs if there's no inline comments
143
+ return {} if (warnings + errors + messages + markdowns).select(&:inline?).empty?
144
+
145
+ pr_threads = @api.fetch_last_comments
146
+ danger_threads = pr_threads.select do |thread|
147
+ comment = thread[:comments].first
148
+ comment_content = comment[:content].nil? ? "" : comment[:content]
149
+
150
+ next comment_content.include?("generated_by_#{danger_id}")
151
+ end
152
+ non_danger_threads = pr_threads - danger_threads
153
+
154
+ warnings = submit_inline_comments_for_kind!(:warning, warnings, danger_threads, previous_violations["warning"], danger_id: danger_id)
155
+ errors = submit_inline_comments_for_kind!(:error, errors, danger_threads, previous_violations["error"], danger_id: danger_id)
156
+ messages = submit_inline_comments_for_kind!(:message, messages, danger_threads, previous_violations["message"], danger_id: danger_id)
157
+ markdowns = submit_inline_comments_for_kind!(:markdown, markdowns, danger_threads, [], danger_id: danger_id)
158
+
159
+ # submit removes from the array all comments that are still in force
160
+ # so we strike out all remaining ones
161
+ danger_threads.each do |thread|
162
+ violation = violations_from_table(thread[:comments].first[:content]).first
163
+ if !violation.nil? && violation.sticky
164
+ body = generate_inline_comment_body("white_check_mark", violation, danger_id: danger_id, resolved: true, template: "github")
165
+ @api.update_comment(thread[:id], thread[:comments].first[:id], body)
166
+ end
167
+ end
168
+
169
+ {
170
+ warnings: warnings,
171
+ errors: errors,
172
+ messages: messages,
173
+ markdowns: markdowns
174
+ }
175
+ end
176
+
177
+ def messages_are_equivalent(m1, m2)
178
+ blob_regexp = %r{blob/[0-9a-z]+/}
179
+ m1.file == m2.file && m1.line == m2.line &&
180
+ m1.message.sub(blob_regexp, "") == m2.message.sub(blob_regexp, "")
181
+ end
182
+
183
+ def submit_inline_comments_for_kind!(kind, messages, danger_threads, previous_violations, danger_id: "danger")
184
+ previous_violations ||= []
185
+ is_markdown_content = kind == :markdown
186
+ emoji = { warning: "warning", error: "no_entry_sign", message: "book" }[kind]
187
+
188
+ messages.reject do |m|
189
+ next false unless m.file && m.line
190
+
191
+ # Once we know we're gonna submit it, we format it
192
+ if is_markdown_content
193
+ body = generate_inline_markdown_body(m, danger_id: danger_id, template: "vsts")
194
+ else
195
+ # Hide the inline link behind a span
196
+ m.message = m.message.gsub("\n", "<br />")
197
+ m = process_markdown(m, true)
198
+ body = generate_inline_comment_body(emoji, m, danger_id: danger_id, template: "vsts")
199
+ # A comment might be in previous_violations because only now it's part of the unified diff
200
+ # We remove from the array since it won't have a place in the table anymore
201
+ previous_violations.reject! { |v| messages_are_equivalent(v, m) }
202
+ end
203
+
204
+ matching_threads = danger_threads.select do |comment_data|
205
+ if comment_data.key?(:threadContext) && !comment_data[:threadContext].nil? &&
206
+ comment_data[:threadContext][:filePath] == m.file &&
207
+ comment_data[:threadContext].key?(:rightFileStart) &&
208
+ comment_data[:threadContext][:rightFileStart][:line] == m.line
209
+ # Parse it to avoid problems with strikethrough
210
+ violation = violations_from_table(comment_data[:comments].first[:content]).first
211
+ if violation
212
+ messages_are_equivalent(violation, m)
213
+ else
214
+ blob_regexp = %r{blob/[0-9a-z]+/}
215
+ comment_data[:comments].first[:content].sub(blob_regexp, "") == body.sub(blob_regexp, "")
216
+ end
217
+ else
218
+ false
219
+ end
220
+ end
221
+
222
+ if matching_threads.empty?
223
+ @api.post_inline_comment(body, m.file, m.line)
224
+
225
+ # Not reject because this comment has not completed
226
+ next false
227
+ else
228
+ # Remove the surviving comment so we don't strike it out
229
+ danger_threads.reject! { |c| matching_threads.include? c }
230
+
231
+ # Update the comment to remove the strikethrough if present
232
+ thread = matching_threads.first
233
+ @api.update_comment(thread[:id], thread[:comments].first[:id], body)
234
+ end
235
+
236
+ # Remove this element from the array
237
+ next true
238
+ end
239
+ end
240
+
241
+ private
242
+
243
+ def regular_violations_group(warnings: [], errors: [], messages: [], markdowns: [])
244
+ {
245
+ warnings: warnings.reject(&:inline?),
246
+ errors: errors.reject(&:inline?),
247
+ messages: messages.reject(&:inline?),
248
+ markdowns: markdowns.reject(&:inline?)
249
+ }
250
+ end
251
+
252
+ def inline_violations_group(warnings: [], errors: [], messages: [], markdowns: [])
253
+ cmp = proc do |a, b|
254
+ next -1 unless a.file && a.line
255
+ next 1 unless b.file && b.line
256
+
257
+ next a.line <=> b.line if a.file == b.file
258
+
259
+ next a.file <=> b.file
260
+ end
261
+
262
+ # Sort to group inline comments by file
263
+ {
264
+ warnings: warnings.select(&:inline?).sort(&cmp),
265
+ errors: errors.select(&:inline?).sort(&cmp),
266
+ messages: messages.select(&:inline?).sort(&cmp),
267
+ markdowns: markdowns.select(&:inline?).sort(&cmp)
268
+ }
269
+ end
270
+
271
+ def merge_violations(*violation_groups)
272
+ violation_groups.inject({}) do |accumulator, group|
273
+ accumulator.merge(group) { |_, old, fresh| old + fresh }
274
+ end
275
+ end
115
276
  end
116
277
  end
117
278
  end