danger 8.4.2 → 9.2.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (84) hide show
  1. checksums.yaml +4 -4
  2. data/LICENSE +1 -1
  3. data/README.md +1 -1
  4. data/bin/danger +1 -1
  5. data/lib/danger/ci_source/appcircle.rb +83 -0
  6. data/lib/danger/ci_source/appveyor.rb +1 -0
  7. data/lib/danger/ci_source/azure_pipelines.rb +20 -5
  8. data/lib/danger/ci_source/bamboo.rb +1 -1
  9. data/lib/danger/ci_source/bitbucket_pipelines.rb +1 -2
  10. data/lib/danger/ci_source/bitrise.rb +10 -10
  11. data/lib/danger/ci_source/buildkite.rb +1 -1
  12. data/lib/danger/ci_source/circle.rb +1 -1
  13. data/lib/danger/ci_source/circle_api.rb +2 -2
  14. data/lib/danger/ci_source/code_build.rb +20 -6
  15. data/lib/danger/ci_source/codefresh.rb +7 -13
  16. data/lib/danger/ci_source/concourse.rb +4 -5
  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 +13 -19
  20. data/lib/danger/ci_source/jenkins.rb +22 -23
  21. data/lib/danger/ci_source/local_git_repo.rb +36 -38
  22. data/lib/danger/ci_source/local_only_git_repo.rb +5 -8
  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 +47 -43
  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 +1 -1
  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 +19 -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 +3 -1
  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/array_subclass.rb +2 -2
  60. data/lib/danger/helpers/comments_helper.rb +8 -7
  61. data/lib/danger/helpers/comments_parsing_helper.rb +3 -2
  62. data/lib/danger/helpers/emoji_mapper.rb +1 -1
  63. data/lib/danger/plugin_support/plugin.rb +0 -1
  64. data/lib/danger/plugin_support/plugin_linter.rb +1 -0
  65. data/lib/danger/request_sources/bitbucket_cloud.rb +3 -6
  66. data/lib/danger/request_sources/bitbucket_cloud_api.rb +5 -7
  67. data/lib/danger/request_sources/bitbucket_server.rb +47 -11
  68. data/lib/danger/request_sources/bitbucket_server_api.rb +17 -14
  69. data/lib/danger/request_sources/code_insights_api.rb +9 -14
  70. data/lib/danger/request_sources/github/github.rb +30 -38
  71. data/lib/danger/request_sources/github/github_review.rb +3 -2
  72. data/lib/danger/request_sources/github/github_review_resolver.rb +0 -2
  73. data/lib/danger/request_sources/github/github_review_unsupported.rb +0 -2
  74. data/lib/danger/request_sources/gitlab.rb +53 -75
  75. data/lib/danger/request_sources/local_only.rb +1 -4
  76. data/lib/danger/request_sources/request_source.rb +20 -8
  77. data/lib/danger/request_sources/support/get_ignored_violation.rb +1 -1
  78. data/lib/danger/request_sources/vsts.rb +175 -15
  79. data/lib/danger/request_sources/vsts_api.rb +41 -7
  80. data/lib/danger/scm_source/git_repo.rb +2 -1
  81. data/lib/danger/version.rb +1 -1
  82. data/lib/danger.rb +1 -0
  83. metadata +39 -37
  84. data/lib/danger/ci_source/vsts.rb +0 -73
@@ -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"
@@ -8,7 +7,7 @@ module Danger
8
7
  module RequestSources
9
8
  class GitLab < RequestSource
10
9
  include Danger::Helpers::CommentsHelper
11
- attr_accessor :mr_json, :commits_json, :dismiss_out_of_range_messages
10
+ attr_accessor :mr_json, :commits_json, :dismiss_out_of_range_messages, :endpoint, :host
12
11
 
13
12
  FIRST_GITLAB_GEM_WITH_VERSION_CHECK = Gem::Version.new("4.6.0")
14
13
  FIRST_VERSION_WITH_INLINE_COMMENTS = Gem::Version.new("10.8.0")
@@ -23,20 +22,19 @@ module Danger
23
22
 
24
23
  def initialize(ci_source, environment)
25
24
  self.ci_source = ci_source
26
- self.environment = environment
27
25
  self.dismiss_out_of_range_messages = false
28
-
29
- @token = @environment["DANGER_GITLAB_API_TOKEN"]
26
+ @endpoint = environment["DANGER_GITLAB_API_BASE_URL"] || environment.fetch("CI_API_V4_URL", "https://gitlab.com/api/v4")
27
+ @host = environment.fetch("DANGER_GITLAB_HOST", URI.parse(endpoint).host) || "gitlab.com"
28
+ @token = environment["DANGER_GITLAB_API_TOKEN"]
30
29
  end
31
30
 
32
31
  def client
33
- token = @environment["DANGER_GITLAB_API_TOKEN"]
34
- raise "No API token given, please provide one using `DANGER_GITLAB_API_TOKEN`" unless token
32
+ raise "No API token given, please provide one using `DANGER_GITLAB_API_TOKEN`" unless @token
35
33
 
36
34
  # The require happens inline so that it won't cause exceptions when just using the `danger` gem.
37
35
  require "gitlab"
38
36
 
39
- @client ||= Gitlab.client(endpoint: endpoint, private_token: token)
37
+ @client ||= Gitlab.client(endpoint: endpoint, private_token: @token)
40
38
  rescue LoadError => e
41
39
  if e.path == "gitlab"
42
40
  puts "The GitLab gem was not installed, you will need to change your Gem from `danger` to `danger-gitlab`.".red
@@ -48,7 +46,7 @@ module Danger
48
46
  end
49
47
 
50
48
  def validates_as_ci?
51
- includes_port = self.host.include? ":"
49
+ includes_port = host.include? ":"
52
50
  raise "Port number included in `DANGER_GITLAB_HOST`, this will fail with GitLab CI Runners" if includes_port
53
51
 
54
52
  # We don't call super because in some cases the Git remote doesn't match the GitLab instance host.
@@ -66,14 +64,6 @@ module Danger
66
64
  @scm ||= GitRepo.new
67
65
  end
68
66
 
69
- def endpoint
70
- @endpoint ||= @environment["DANGER_GITLAB_API_BASE_URL"] || @environment["CI_API_V4_URL"] || "https://gitlab.com/api/v4"
71
- end
72
-
73
- def host
74
- @host ||= @environment["DANGER_GITLAB_HOST"] || URI.parse(endpoint).host || "gitlab.com"
75
- end
76
-
77
67
  def base_commit
78
68
  @base_commit ||= self.mr_json.diff_refs.base_sha
79
69
  end
@@ -81,20 +71,18 @@ module Danger
81
71
  def mr_comments
82
72
  # @raw_comments contains what we got back from the server.
83
73
  # @comments contains Comment objects (that have less information)
84
- @comments ||= begin
85
- if supports_inline_comments
86
- @raw_comments = mr_discussions
87
- .auto_paginate
88
- .flat_map { |discussion| discussion.notes.map { |note| note.to_h.merge({"discussion_id" => discussion.id}) } }
89
- @raw_comments
90
- .map { |comment| Comment.from_gitlab(comment) }
91
- else
92
- @raw_comments = client.merge_request_comments(ci_source.repo_slug, ci_source.pull_request_id, per_page: 100)
93
- .auto_paginate
94
- @raw_comments
95
- .map { |comment| Comment.from_gitlab(comment) }
96
- end
97
- 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
98
86
  end
99
87
 
100
88
  def mr_discussions
@@ -105,10 +93,10 @@ module Danger
105
93
  @mr_diff ||= begin
106
94
  diffs = mr_changes.changes.map do |change|
107
95
  diff = change["diff"]
108
- if diff.start_with?('--- a/')
96
+ if diff.start_with?("--- a/")
109
97
  diff
110
98
  else
111
- "--- a/#{change["old_path"]}\n+++ b/#{change["new_path"]}\n#{diff}"
99
+ "--- a/#{change['old_path']}\n+++ b/#{change['new_path']}\n#{diff}"
112
100
  end
113
101
  end
114
102
  diffs.join("\n")
@@ -116,24 +104,18 @@ module Danger
116
104
  end
117
105
 
118
106
  def mr_changed_paths
119
- @mr_changed_paths ||= begin
120
- mr_changes
121
- .changes.map { |change| change["new_path"] }
122
- end
107
+ @mr_changed_paths ||= mr_changes
108
+ .changes.map { |change| change["new_path"] }
123
109
 
124
110
  @mr_changed_paths
125
111
  end
126
112
 
127
113
  def mr_changes
128
- @mr_changes ||= begin
129
- client.merge_request_changes(ci_source.repo_slug, ci_source.pull_request_id)
130
- end
114
+ @mr_changes ||= client.merge_request_changes(ci_source.repo_slug, ci_source.pull_request_id)
131
115
  end
132
116
 
133
117
  def mr_closes_issues
134
- @mr_closes_issues ||= begin
135
- client.merge_request_closes_issues(ci_source.repo_slug, ci_source.pull_request_id)
136
- end
118
+ @mr_closes_issues ||= client.merge_request_closes_issues(ci_source.repo_slug, ci_source.pull_request_id)
137
119
  end
138
120
 
139
121
  def setup_danger_branches
@@ -163,16 +145,14 @@ module Danger
163
145
  end
164
146
 
165
147
  def supports_inline_comments
166
- @supports_inline_comments ||= begin
167
- # If we can't check GitLab's version, we assume we don't support inline comments
168
- if Gem.loaded_specs["gitlab"].version < FIRST_GITLAB_GEM_WITH_VERSION_CHECK
169
- false
170
- else
171
- 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)
172
153
 
173
- current_version >= FIRST_VERSION_WITH_INLINE_COMMENTS
174
- end
175
- end
154
+ current_version >= FIRST_VERSION_WITH_INLINE_COMMENTS
155
+ end
176
156
  end
177
157
 
178
158
  def update_pull_request!(warnings: [], errors: [], messages: [], markdowns: [], danger_id: "danger", new_comment: false, remove_previous_comments: false)
@@ -262,11 +242,11 @@ module Danger
262
242
  delete_old_comments!(danger_id: danger_id)
263
243
  else
264
244
  body = generate_comment(warnings: warnings,
265
- errors: errors,
245
+ errors: errors,
266
246
  messages: messages,
267
- markdowns: markdowns,
268
- previous_violations: previous_violations,
269
- danger_id: danger_id,
247
+ markdowns: markdowns,
248
+ previous_violations: previous_violations,
249
+ danger_id: danger_id,
270
250
  template: "gitlab")
271
251
 
272
252
  if editable_comments.empty? or should_create_new_comment
@@ -287,11 +267,10 @@ module Danger
287
267
 
288
268
  def delete_old_comments!(except: nil, danger_id: "danger")
289
269
  @raw_comments.each do |raw_comment|
290
-
291
270
  comment = Comment.from_gitlab(raw_comment)
292
271
  next unless comment.generated_by_danger?(danger_id)
293
272
  next if comment.id == except
294
- next unless raw_comment.is_a?(Hash) && raw_comment["position"].nil?
273
+ next unless raw_comment.kind_of?(Hash) && raw_comment["position"].nil?
295
274
 
296
275
  begin
297
276
  client.delete_merge_request_comment(
@@ -299,7 +278,7 @@ module Danger
299
278
  ci_source.pull_request_id,
300
279
  comment.id
301
280
  )
302
- rescue
281
+ rescue StandardError
303
282
  end
304
283
  end
305
284
  end
@@ -307,7 +286,7 @@ module Danger
307
286
  def markdown_link_to_message(message, _)
308
287
  "#{message.file}#L#{message.line}: "
309
288
  end
310
-
289
+
311
290
  # @return [String] The organisation name, is nil if it can't be detected
312
291
  def organisation
313
292
  nil # TODO: Implement this
@@ -324,13 +303,12 @@ module Danger
324
303
  end
325
304
 
326
305
  # @return [String] A URL to the specific file, ready to be downloaded
327
- def file_url(organisation: nil, repository: nil, branch: nil, path: nil)
328
- branch ||= 'master'
329
- token = @environment["DANGER_GITLAB_API_TOKEN"]
306
+ def file_url(organisation: nil, repository: nil, ref: nil, branch: nil, path: nil)
307
+ ref ||= (branch || "master")
330
308
  # According to GitLab Repositories API docs path and id(slug) should be encoded.
331
309
  path = URI.encode_www_form_component(path)
332
310
  repository = URI.encode_www_form_component(repository)
333
- "#{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}"
334
312
  end
335
313
 
336
314
  def regular_violations_group(warnings: [], errors: [], messages: [], markdowns: [])
@@ -348,6 +326,7 @@ module Danger
348
326
  next 1 unless b.file && b.line
349
327
 
350
328
  next a.line <=> b.line if a.file == b.file
329
+
351
330
  next a.file <=> b.file
352
331
  end
353
332
 
@@ -369,7 +348,7 @@ module Danger
369
348
  def submit_inline_comments!(warnings: [], errors: [], messages: [], markdowns: [], previous_violations: [], danger_id: "danger")
370
349
  comments = mr_discussions
371
350
  .auto_paginate
372
- .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 }) } }
373
352
  .select { |comment| Comment.from_gitlab(comment).inline? }
374
353
 
375
354
  danger_comments = comments.select { |comment| Comment.from_gitlab(comment).generated_by_danger?(danger_id) }
@@ -411,7 +390,7 @@ module Danger
411
390
  }
412
391
  end
413
392
 
414
- 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")
415
394
  previous_violations ||= []
416
395
  is_markdown_content = kind == :markdown
417
396
  emoji = { warning: "warning", error: "no_entry_sign", message: "book" }[kind]
@@ -450,7 +429,7 @@ module Danger
450
429
  params = {
451
430
  body: body,
452
431
  position: {
453
- position_type: 'text',
432
+ position_type: "text",
454
433
  new_path: m.file,
455
434
  new_line: m.line,
456
435
  old_path: old_position[:path],
@@ -560,20 +539,19 @@ module Danger
560
539
  line_number = 0
561
540
  diff.each_line do |line|
562
541
  if line.match range_header_regexp
563
- line = line.split('+').last
564
- line = line.split(' ').first
565
- range_string = line.split(',')
542
+ line = line.split("+").last
543
+ line = line.split(" ").first
544
+ range_string = line.split(",")
566
545
  line_number = range_string[0].to_i - 1
567
- elsif line.start_with?('+')
546
+ elsif line.start_with?("+")
568
547
  addition_lines.push(line_number)
569
- elsif line.start_with?('-')
570
- line_number=line_number-1
548
+ elsif line.start_with?("-")
549
+ line_number -= 1
571
550
  end
572
- line_number=line_number+1
551
+ line_number += 1
573
552
  end
574
553
  addition_lines
575
554
  end
576
-
577
555
  end
578
556
  end
579
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
 
@@ -13,9 +11,8 @@ module Danger
13
11
  ["DANGER_LOCAL_ONLY"]
14
12
  end
15
13
 
16
- def initialize(ci_source, environment)
14
+ def initialize(ci_source, _environment)
17
15
  self.ci_source = ci_source
18
- self.environment = environment
19
16
  end
20
17
 
21
18
  def validates_as_ci?
@@ -1,9 +1,11 @@
1
+ # frozen_string_literal: true
2
+
1
3
  module Danger
2
4
  module RequestSources
3
5
  class RequestSource
4
- DANGER_REPO_NAME = "danger".freeze
6
+ DANGER_REPO_NAME = "danger"
5
7
 
6
- attr_accessor :ci_source, :environment, :scm, :host, :ignored_violations
8
+ attr_accessor :ci_source, :scm, :host, :ignored_violations
7
9
 
8
10
  def self.env_vars
9
11
  raise "Subclass and overwrite self.env_vars"
@@ -23,12 +25,12 @@ module Danger
23
25
  end
24
26
 
25
27
  def self.source_name
26
- to_s.sub("Danger::RequestSources::".freeze, "".freeze)
28
+ to_s.sub("Danger::RequestSources::", "")
27
29
  end
28
30
 
29
31
  def self.available_source_names_and_envs
30
32
  available_request_sources.map do |klass|
31
- " - #{klass.source_name}: #{klass.env_vars.join(', '.freeze).yellow}"
33
+ " - #{klass.source_name}: #{klass.env_vars.join(', ').yellow}"
32
34
  end
33
35
  end
34
36
 
@@ -36,6 +38,16 @@ module Danger
36
38
  raise "Subclass and overwrite initialize"
37
39
  end
38
40
 
41
+ def inspect
42
+ inspected = super
43
+
44
+ inspected.gsub!(@token, "********") if @token
45
+ inspected.gsub!(@access_token, "********") if @access_token
46
+ inspected.gsub!(@bearer_token, "********") if @bearer_token
47
+
48
+ inspected
49
+ end
50
+
39
51
  # @return [Boolean] whether scm.origins is a valid git repository or not
40
52
  def validates_as_ci?
41
53
  !!self.scm.origins.match(%r{#{Regexp.escape self.host}(:|/)(.+/.+?)(?:\.git)?$})
@@ -73,12 +85,12 @@ module Danger
73
85
  raise "Subclass and overwrite organisation"
74
86
  end
75
87
 
76
- 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)
77
89
  raise "Subclass and overwrite file_url"
78
90
  end
79
-
80
- def update_build_status(status)
81
- raise "Subclass and overwrite update_build_status"
91
+
92
+ def update_build_status(_status)
93
+ raise "Subclass and overwrite update_build_status"
82
94
  end
83
95
  end
84
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
 
@@ -24,16 +22,14 @@ module Danger
24
22
 
25
23
  def initialize(ci_source, environment)
26
24
  self.ci_source = ci_source
27
- self.environment = environment
28
25
 
29
- @is_vsts_git = environment["BUILD_REPOSITORY_PROVIDER"] == "TfsGit"
26
+ @is_vsts_ci = environment.key? "DANGER_VSTS_HOST"
30
27
 
31
- project, slug = ci_source.repo_slug.split("/")
32
- @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)
33
29
  end
34
30
 
35
31
  def validates_as_ci?
36
- @is_vsts_git
32
+ @is_vsts_ci
37
33
  end
38
34
 
39
35
  def validates_as_api_source?
@@ -44,6 +40,10 @@ module Danger
44
40
  @scm ||= GitRepo.new
45
41
  end
46
42
 
43
+ def client
44
+ @api
45
+ end
46
+
47
47
  def host
48
48
  @host ||= @api.host
49
49
  end
@@ -77,15 +77,36 @@ module Danger
77
77
  return
78
78
  end
79
79
 
80
- 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])
81
104
  comment += "\n\n"
82
- comment += generate_comment(warnings: warnings,
83
- errors: errors,
84
- messages: messages,
85
- markdowns: markdowns,
86
- previous_violations: {},
87
- danger_id: danger_id,
88
- template: "vsts")
105
+ comment += generate_comment(**{
106
+ previous_violations: {},
107
+ danger_id: danger_id,
108
+ template: "vsts"
109
+ }.merge(main_violations))
89
110
  if new_comment || remove_previous_comments
90
111
  post_new_comment(comment)
91
112
  else
@@ -106,6 +127,9 @@ module Danger
106
127
  comment_content = comment[:content].nil? ? "" : comment[:content]
107
128
  # Skip the comment if it wasn't posted by danger
108
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
+
109
133
  # Updated the danger posted comment
110
134
  @api.update_comment(thread_id, comment_id, new_comment)
111
135
  comment_updated = true
@@ -113,6 +137,142 @@ module Danger
113
137
  # If no comment was updated, post a new one
114
138
  post_new_comment(new_comment) unless comment_updated
115
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
116
276
  end
117
277
  end
118
278
  end