danger-additional-logging 0.0.1

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 (127) hide show
  1. checksums.yaml +7 -0
  2. data/LICENSE +22 -0
  3. data/README.md +93 -0
  4. data/bin/danger +5 -0
  5. data/lib/assets/DangerfileTemplate +13 -0
  6. data/lib/danger/ci_source/appcenter.rb +55 -0
  7. data/lib/danger/ci_source/appcircle.rb +83 -0
  8. data/lib/danger/ci_source/appveyor.rb +64 -0
  9. data/lib/danger/ci_source/azure_pipelines.rb +61 -0
  10. data/lib/danger/ci_source/bamboo.rb +41 -0
  11. data/lib/danger/ci_source/bitbucket_pipelines.rb +37 -0
  12. data/lib/danger/ci_source/bitrise.rb +78 -0
  13. data/lib/danger/ci_source/buddybuild.rb +62 -0
  14. data/lib/danger/ci_source/buildkite.rb +51 -0
  15. data/lib/danger/ci_source/ci_source.rb +37 -0
  16. data/lib/danger/ci_source/circle.rb +94 -0
  17. data/lib/danger/ci_source/circle_api.rb +51 -0
  18. data/lib/danger/ci_source/cirrus.rb +31 -0
  19. data/lib/danger/ci_source/code_build.rb +71 -0
  20. data/lib/danger/ci_source/codefresh.rb +47 -0
  21. data/lib/danger/ci_source/codemagic.rb +58 -0
  22. data/lib/danger/ci_source/codeship.rb +44 -0
  23. data/lib/danger/ci_source/concourse.rb +60 -0
  24. data/lib/danger/ci_source/custom_ci_with_github.rb +49 -0
  25. data/lib/danger/ci_source/dotci.rb +50 -0
  26. data/lib/danger/ci_source/drone.rb +71 -0
  27. data/lib/danger/ci_source/github_actions.rb +44 -0
  28. data/lib/danger/ci_source/gitlab_ci.rb +89 -0
  29. data/lib/danger/ci_source/jenkins.rb +148 -0
  30. data/lib/danger/ci_source/local_git_repo.rb +117 -0
  31. data/lib/danger/ci_source/local_only_git_repo.rb +44 -0
  32. data/lib/danger/ci_source/screwdriver.rb +48 -0
  33. data/lib/danger/ci_source/semaphore.rb +37 -0
  34. data/lib/danger/ci_source/support/commits.rb +19 -0
  35. data/lib/danger/ci_source/support/find_repo_info_from_logs.rb +35 -0
  36. data/lib/danger/ci_source/support/find_repo_info_from_url.rb +43 -0
  37. data/lib/danger/ci_source/support/local_pull_request.rb +14 -0
  38. data/lib/danger/ci_source/support/no_pull_request.rb +7 -0
  39. data/lib/danger/ci_source/support/no_repo_info.rb +5 -0
  40. data/lib/danger/ci_source/support/pull_request_finder.rb +190 -0
  41. data/lib/danger/ci_source/support/remote_pull_request.rb +15 -0
  42. data/lib/danger/ci_source/support/repo_info.rb +10 -0
  43. data/lib/danger/ci_source/surf.rb +37 -0
  44. data/lib/danger/ci_source/teamcity.rb +163 -0
  45. data/lib/danger/ci_source/travis.rb +51 -0
  46. data/lib/danger/ci_source/xcode_cloud.rb +38 -0
  47. data/lib/danger/ci_source/xcode_server.rb +48 -0
  48. data/lib/danger/clients/rubygems_client.rb +14 -0
  49. data/lib/danger/commands/dangerfile/gem.rb +43 -0
  50. data/lib/danger/commands/dangerfile/init.rb +30 -0
  51. data/lib/danger/commands/dry_run.rb +54 -0
  52. data/lib/danger/commands/init.rb +297 -0
  53. data/lib/danger/commands/init_helpers/interviewer.rb +92 -0
  54. data/lib/danger/commands/local.rb +83 -0
  55. data/lib/danger/commands/local_helpers/http_cache.rb +38 -0
  56. data/lib/danger/commands/local_helpers/local_setup.rb +48 -0
  57. data/lib/danger/commands/local_helpers/pry_setup.rb +32 -0
  58. data/lib/danger/commands/plugins/plugin_json.rb +44 -0
  59. data/lib/danger/commands/plugins/plugin_lint.rb +52 -0
  60. data/lib/danger/commands/plugins/plugin_readme.rb +42 -0
  61. data/lib/danger/commands/pr.rb +93 -0
  62. data/lib/danger/commands/runner.rb +94 -0
  63. data/lib/danger/commands/staging.rb +53 -0
  64. data/lib/danger/commands/systems.rb +41 -0
  65. data/lib/danger/comment_generators/bitbucket_server.md.erb +20 -0
  66. data/lib/danger/comment_generators/bitbucket_server_inline.md.erb +15 -0
  67. data/lib/danger/comment_generators/bitbucket_server_message_group.md.erb +12 -0
  68. data/lib/danger/comment_generators/github.md.erb +55 -0
  69. data/lib/danger/comment_generators/github_inline.md.erb +26 -0
  70. data/lib/danger/comment_generators/gitlab.md.erb +40 -0
  71. data/lib/danger/comment_generators/gitlab_inline.md.erb +21 -0
  72. data/lib/danger/comment_generators/vsts.md.erb +20 -0
  73. data/lib/danger/comment_generators/vsts_inline.md.erb +17 -0
  74. data/lib/danger/core_ext/file_list.rb +18 -0
  75. data/lib/danger/core_ext/string.rb +20 -0
  76. data/lib/danger/danger_core/dangerfile.rb +348 -0
  77. data/lib/danger/danger_core/dangerfile_dsl.rb +29 -0
  78. data/lib/danger/danger_core/dangerfile_generator.rb +11 -0
  79. data/lib/danger/danger_core/environment_manager.rb +126 -0
  80. data/lib/danger/danger_core/executor.rb +91 -0
  81. data/lib/danger/danger_core/message_aggregator.rb +50 -0
  82. data/lib/danger/danger_core/message_group.rb +68 -0
  83. data/lib/danger/danger_core/messages/base.rb +57 -0
  84. data/lib/danger/danger_core/messages/markdown.rb +41 -0
  85. data/lib/danger/danger_core/messages/violation.rb +53 -0
  86. data/lib/danger/danger_core/plugins/dangerfile_bitbucket_cloud_plugin.rb +142 -0
  87. data/lib/danger/danger_core/plugins/dangerfile_bitbucket_server_plugin.rb +211 -0
  88. data/lib/danger/danger_core/plugins/dangerfile_danger_plugin.rb +274 -0
  89. data/lib/danger/danger_core/plugins/dangerfile_git_plugin.rb +159 -0
  90. data/lib/danger/danger_core/plugins/dangerfile_github_plugin.rb +264 -0
  91. data/lib/danger/danger_core/plugins/dangerfile_gitlab_plugin.rb +275 -0
  92. data/lib/danger/danger_core/plugins/dangerfile_local_only_plugin.rb +43 -0
  93. data/lib/danger/danger_core/plugins/dangerfile_messaging_plugin.rb +220 -0
  94. data/lib/danger/danger_core/plugins/dangerfile_vsts_plugin.rb +191 -0
  95. data/lib/danger/danger_core/standard_error.rb +142 -0
  96. data/lib/danger/helpers/array_subclass.rb +61 -0
  97. data/lib/danger/helpers/comment.rb +32 -0
  98. data/lib/danger/helpers/comments_helper.rb +179 -0
  99. data/lib/danger/helpers/comments_parsing_helper.rb +71 -0
  100. data/lib/danger/helpers/emoji_mapper.rb +41 -0
  101. data/lib/danger/helpers/find_max_num_violations.rb +31 -0
  102. data/lib/danger/helpers/message_groups_array_helper.rb +31 -0
  103. data/lib/danger/plugin_support/gems_resolver.rb +77 -0
  104. data/lib/danger/plugin_support/plugin.rb +52 -0
  105. data/lib/danger/plugin_support/plugin_file_resolver.rb +30 -0
  106. data/lib/danger/plugin_support/plugin_linter.rb +162 -0
  107. data/lib/danger/plugin_support/plugin_parser.rb +199 -0
  108. data/lib/danger/plugin_support/templates/readme_table.html.erb +26 -0
  109. data/lib/danger/request_sources/bitbucket_cloud.rb +169 -0
  110. data/lib/danger/request_sources/bitbucket_cloud_api.rb +181 -0
  111. data/lib/danger/request_sources/bitbucket_server.rb +210 -0
  112. data/lib/danger/request_sources/bitbucket_server_api.rb +129 -0
  113. data/lib/danger/request_sources/code_insights_api.rb +142 -0
  114. data/lib/danger/request_sources/github/github.rb +535 -0
  115. data/lib/danger/request_sources/github/github_review.rb +127 -0
  116. data/lib/danger/request_sources/github/github_review_resolver.rb +17 -0
  117. data/lib/danger/request_sources/github/github_review_unsupported.rb +23 -0
  118. data/lib/danger/request_sources/gitlab.rb +557 -0
  119. data/lib/danger/request_sources/local_only.rb +50 -0
  120. data/lib/danger/request_sources/request_source.rb +97 -0
  121. data/lib/danger/request_sources/support/get_ignored_violation.rb +17 -0
  122. data/lib/danger/request_sources/vsts.rb +278 -0
  123. data/lib/danger/request_sources/vsts_api.rb +172 -0
  124. data/lib/danger/scm_source/git_repo.rb +198 -0
  125. data/lib/danger/version.rb +4 -0
  126. data/lib/danger.rb +45 -0
  127. metadata +351 -0
@@ -0,0 +1,191 @@
1
+ require "danger/plugin_support/plugin"
2
+
3
+ module Danger
4
+ # Handles interacting with VSTS inside a Dangerfile. Provides a few functions which wrap `pr_json` and also
5
+ # through a few standard functions to simplify your code.
6
+ #
7
+ # @example Warn when a PR is classed as work in progress
8
+ #
9
+ # warn "PR is classed as Work in Progress" if vsts.pr_title.include? "[WIP]"
10
+ #
11
+ # @example Declare a PR to be simple to avoid specific Danger rules
12
+ #
13
+ # declared_trivial = (vsts.pr_title + vsts.pr_body).include?("#trivial")
14
+ #
15
+ # @example Ensure there is a summary for a PR
16
+ #
17
+ # failure "Please provide a summary in the Pull Request description" if vsts.pr_body.length < 5
18
+ #
19
+ # @example Only accept PRs to the develop branch
20
+ #
21
+ # failure "Please re-submit this PR to develop, we may have already fixed your issue." if vsts.branch_for_base != "develop"
22
+ #
23
+ # @example Highlight when a celebrity makes a pull request
24
+ #
25
+ # message "Welcome, Danger." if vsts.pr_author == "dangermcshane"
26
+ #
27
+ # @example Ensure that all PRs have an assignee
28
+ #
29
+ # warn "This PR does not have any assignees yet." unless vsts.pr_json["reviewers"].length == 0
30
+ #
31
+ # @example Send a message with links to a collection of specific files
32
+ #
33
+ # if git.modified_files.include? "config/*.js"
34
+ # config_files = git.modified_files.select { |path| path.include? "config/" }
35
+ # message "This PR changes #{ vsts.markdown_link(config_files) }"
36
+ # end
37
+ #
38
+ # @example Highlight with a clickable link if a Package.json is changed
39
+ #
40
+ # warn "#{vsts.markdown_link("Package.json")} was edited." if git.modified_files.include? "Package.json"
41
+ #
42
+ # @example Note an issue with a particular line on a file using the #L[num] syntax, e.g. `#L23`
43
+ #
44
+ # linter_json = `my_linter lint "file"`
45
+ # results = JSON.parse linter_json
46
+ # unless results.empty?
47
+ # file, line, warning = result.first
48
+ # warn "#{vsts.markdown_link("#{file}#L#{line}")} has linter issue: #{warning}."
49
+ # end
50
+ #
51
+ #
52
+ # @see danger/danger
53
+ # @tags core, vsts
54
+ #
55
+ class DangerfileVSTSPlugin < Plugin
56
+ # So that this init can fail.
57
+ def self.new(dangerfile)
58
+ return nil if dangerfile.env.request_source.class != Danger::RequestSources::VSTS
59
+
60
+ super
61
+ end
62
+
63
+ # The instance name used in the Dangerfile
64
+ # @return [String]
65
+ #
66
+ def self.instance_name
67
+ "vsts"
68
+ end
69
+
70
+ def initialize(dangerfile)
71
+ super(dangerfile)
72
+ @source = dangerfile.env.request_source
73
+ end
74
+
75
+ # @!group VSTS Misc
76
+ # The hash that represents the PR's JSON. For an example of what this looks like
77
+ # see the [Danger Fixture'd one](https://raw.githubusercontent.com/danger/danger/master/spec/fixtures/vsts_api/pr_response.json).
78
+ # @return [Hash]
79
+ def pr_json
80
+ @source.pr_json
81
+ end
82
+
83
+ # @!group PR Metadata
84
+ # The title of the Pull Request.
85
+ # @return [String]
86
+ #
87
+ def pr_title
88
+ @source.pr_json[:title].to_s
89
+ end
90
+
91
+ # @!group PR Metadata
92
+ # The body text of the Pull Request.
93
+ # @return [String]
94
+ #
95
+ def pr_description
96
+ @source.pr_json[:description].to_s
97
+ end
98
+ alias pr_body pr_description
99
+
100
+ # @!group PR Metadata
101
+ # The username of the author of the Pull Request.
102
+ # @return [String]
103
+ #
104
+ def pr_author
105
+ @source.pr_json[:createdBy][:displayName].to_s
106
+ end
107
+
108
+ # @!group PR Commit Metadata
109
+ # The branch to which the PR is going to be merged into.
110
+ # @return [String]
111
+ #
112
+ def branch_for_base
113
+ branch_name(:targetRefName)
114
+ end
115
+
116
+ # @!group PR Commit Metadata
117
+ # A href that represents the current PR
118
+ # @return [String]
119
+ #
120
+ def pr_link
121
+ repo_path = @source.pr_json[:repository][:remoteUrl].to_s
122
+ pull_request_id = @source.pr_json[:pullRequestId].to_s
123
+
124
+ "#{repo_path}/pullRequest/#{pull_request_id}"
125
+ end
126
+
127
+ # @!group PR Commit Metadata
128
+ # The branch to which the PR is going to be merged from.
129
+ # @return [String]
130
+ #
131
+ def branch_for_head
132
+ branch_name(:sourceRefName)
133
+ end
134
+
135
+ # @!group PR Commit Metadata
136
+ # The base commit to which the PR is going to be merged as a parent.
137
+ # @return [String]
138
+ #
139
+ def base_commit
140
+ @source.pr_json[:lastMergeTargetCommit][:commitId].to_s
141
+ end
142
+
143
+ # @!group PR Commit Metadata
144
+ # The head commit to which the PR is requesting to be merged from.
145
+ # @return [String]
146
+ #
147
+ def head_commit
148
+ @source.pr_json[:lastMergeSourceCommit][:commitId].to_s
149
+ end
150
+
151
+ # @!group VSTS Misc
152
+ # Returns a list of Markdown links for a file, or files in the head repository.
153
+ # It returns a string of multiple links if passed an array.
154
+ # @param [String or Array<String>] paths
155
+ # A list of strings to convert to Markdown links
156
+ # @param [Bool] full_path
157
+ # Shows the full path as the link's text, defaults to `true`.
158
+ #
159
+ # @return [String]
160
+ #
161
+ def markdown_link(paths, full_path: true)
162
+ paths = [paths] unless paths.kind_of?(Array)
163
+ commit = head_commit
164
+ repo = pr_json[:repository][:remoteUrl].to_s
165
+
166
+ paths = paths.map do |path|
167
+ path, line = path.split("#L")
168
+ url_path = path.start_with?("/") ? path : "/#{path}"
169
+ text = full_path ? path : File.basename(path)
170
+ url_path.gsub!(" ", "%20")
171
+ line_ref = line ? "&line=#{line}" : ""
172
+ create_markdown_link("#{repo}/commit/#{commit}?path=#{url_path}&_a=contents#{line_ref}", text)
173
+ end
174
+
175
+ return paths.first if paths.count < 2
176
+
177
+ paths.first(paths.count - 1).join(", ") + " & " + paths.last
178
+ end
179
+
180
+ private
181
+
182
+ def create_markdown_link(href, text)
183
+ "[#{text}](#{href})"
184
+ end
185
+
186
+ def branch_name(key)
187
+ repo_matches = @source.pr_json[key].to_s.match(%r{refs/heads/(.*)})
188
+ repo_matches[1] unless repo_matches.nil?
189
+ end
190
+ end
191
+ end
@@ -0,0 +1,142 @@
1
+ require "claide"
2
+ require "claide/informative_error"
3
+
4
+ module Danger
5
+ # From below here - this entire file was taken verbatim for CocoaPods-Core.
6
+
7
+ #-------------------------------------------------------------------------#
8
+
9
+ # Wraps an exception raised by a DSL file in order to show to the user the
10
+ # contents of the line that raised the exception.
11
+ #
12
+ class DSLError < StandardError
13
+ # @return [String] the description that should be presented to the user.
14
+ #
15
+ attr_reader :description
16
+
17
+ # @return [String] the path of the dsl file that raised the exception.
18
+ #
19
+ attr_reader :dsl_path
20
+
21
+ # @return [Exception] the backtrace of the exception raised by the
22
+ # evaluation of the dsl file.
23
+ #
24
+ attr_reader :backtrace
25
+
26
+ # @param [Exception] backtrace @see backtrace
27
+ # @param [String] dsl_path @see dsl_path
28
+ #
29
+ def initialize(description, dsl_path, backtrace, contents = nil)
30
+ @description = description
31
+ @dsl_path = dsl_path
32
+ @backtrace = backtrace
33
+ @contents = contents
34
+ end
35
+
36
+ # @return [String] the contents of the DSL that cause the exception to
37
+ # be raised.
38
+ #
39
+ def contents
40
+ @contents ||= dsl_path && File.exist?(dsl_path) && File.read(dsl_path)
41
+ end
42
+
43
+ # The message of the exception reports the content of podspec for the
44
+ # line that generated the original exception.
45
+ #
46
+ # @example Output
47
+ #
48
+ # Invalid podspec at `RestKit.podspec` - undefined method
49
+ # `exclude_header_search_paths=' for #<Pod::Specification for
50
+ # `RestKit/Network (0.9.3)`>
51
+ #
52
+ # from spec-repos/master/RestKit/0.9.3/RestKit.podspec:36
53
+ # -------------------------------------------
54
+ # # because it would break: #import <CoreData/CoreData.h>
55
+ # > ns.exclude_header_search_paths = 'Code/RestKit.h'
56
+ # end
57
+ # -------------------------------------------
58
+ #
59
+ # @return [String] the message of the exception.
60
+ #
61
+ def message
62
+ @message ||= begin
63
+ description, stacktrace = parse.values_at(:description, :stacktrace)
64
+
65
+ msg = description
66
+ msg = msg.red if msg.respond_to?(:red)
67
+ msg << stacktrace if stacktrace
68
+ msg
69
+ end
70
+ end
71
+
72
+ def to_markdown
73
+ @markdown ||= begin
74
+ description, stacktrace = parse.values_at(:description, :stacktrace)
75
+
76
+ # Highlight failed method in markdown
77
+ description = description.tr("'", "`")
78
+
79
+ # Escape markdown brackets
80
+ description = description.gsub(/<|>/) { |bracket| "\\#{bracket}" }
81
+
82
+ md = "## Danger has errored"
83
+ md << "#{description}\n"
84
+ md << "```#{stacktrace}```" if stacktrace
85
+
86
+ Markdown.new(md, nil, nil)
87
+ end
88
+ end
89
+
90
+ private
91
+
92
+ def parse
93
+ result = {}
94
+
95
+ trace_line, description = parse_line_number_from_description
96
+ latest_version = Danger.danger_outdated?
97
+
98
+ result[:description] = "\n[!] #{description}"
99
+ result[:description] << upgrade_message(latest_version) if latest_version
100
+
101
+ return result unless backtrace && dsl_path && contents
102
+
103
+ trace_line = backtrace.find { |l| l.include?(dsl_path.to_s) } || trace_line
104
+ return result unless trace_line
105
+
106
+ line_numer = trace_line.split(":")[1].to_i - 1
107
+ return result unless line_numer
108
+
109
+ lines = contents.lines
110
+ indent = " # "
111
+ indicator = indent.tr("#", ">")
112
+ first_line = line_numer.zero?
113
+ last_line = (line_numer == (lines.count - 1))
114
+
115
+ result[:stacktrace] = "\n"
116
+ result[:stacktrace] << "#{indent}from #{trace_line.gsub(/:in.*$/, '')}\n"
117
+ result[:stacktrace] << "#{indent}-------------------------------------------\n"
118
+ result[:stacktrace] << "#{indent}#{lines[line_numer - 1]}" unless first_line
119
+ result[:stacktrace] << "#{indicator}#{lines[line_numer]}"
120
+ result[:stacktrace] << "#{indent}#{lines[line_numer + 1]}" unless last_line
121
+ result[:stacktrace] << "\n" unless result[:stacktrace].end_with?("\n")
122
+ result[:stacktrace] << "#{indent}-------------------------------------------\n"
123
+
124
+ result
125
+ end
126
+
127
+ def parse_line_number_from_description
128
+ description = self.description
129
+ if dsl_path && description =~ /((#{Regexp.quote File.expand_path(dsl_path)}|#{Regexp.quote dsl_path.to_s}):\d+)/
130
+ trace_line = Regexp.last_match[1]
131
+ description = description.sub(/#{Regexp.quote trace_line}:\s*/, "")
132
+ end
133
+ [trace_line, description]
134
+ end
135
+
136
+ def upgrade_message(latest_version)
137
+ ". Updating the Danger gem might fix the issue. "\
138
+ "Your Danger version: #{Danger::VERSION}, "\
139
+ "latest Danger version: #{latest_version}\n"
140
+ end
141
+ end
142
+ end
@@ -0,0 +1,61 @@
1
+ module Danger
2
+ module Helpers
3
+ module ArraySubclass
4
+ include Comparable
5
+
6
+ def initialize(array)
7
+ @__array__ = array
8
+ end
9
+
10
+ def kind_of?(compare_class)
11
+ return true if compare_class == self.class
12
+
13
+ dummy.kind_of?(compare_class)
14
+ end
15
+
16
+ def method_missing(name, *args, &block)
17
+ super unless __array__.respond_to?(name)
18
+
19
+ respond_to_method(name, *args, &block)
20
+ end
21
+
22
+ def respond_to_missing?(name, include_all)
23
+ __array__.respond_to?(name, include_all) || super
24
+ end
25
+
26
+ def to_a
27
+ __array__
28
+ end
29
+
30
+ def to_ary
31
+ __array__
32
+ end
33
+
34
+ def <=>(other)
35
+ return unless other.kind_of?(self.class)
36
+
37
+ __array__ <=> other.instance_variable_get(:@__array__)
38
+ end
39
+
40
+ private
41
+
42
+ attr_accessor :__array__
43
+
44
+ def dummy
45
+ Class.new(Array).new
46
+ end
47
+
48
+ def respond_to_method(name, *args, &block)
49
+ result = __array__.send(name, *args, &block)
50
+ return result unless result.kind_of?(Array)
51
+
52
+ if name =~ /!/
53
+ __array__ = result
54
+ self
55
+ else
56
+ self.class.new(result)
57
+ end
58
+ end
59
+ end
60
+ end
61
+ end
@@ -0,0 +1,32 @@
1
+ module Danger
2
+ class Comment
3
+ attr_reader :id, :body
4
+
5
+ def initialize(id, body, inline = nil)
6
+ @id = id
7
+ @body = body
8
+ @inline = inline
9
+ end
10
+
11
+ def self.from_github(comment)
12
+ self.new(comment["id"], comment["body"])
13
+ end
14
+
15
+ def self.from_gitlab(comment)
16
+ if comment.respond_to?(:id) && comment.respond_to?(:body)
17
+ type = comment.respond_to?(:type) ? comment.type : nil
18
+ self.new(comment.id, comment.body, type == "DiffNote")
19
+ else
20
+ self.new(comment["id"], comment["body"], comment["type"] == "DiffNote")
21
+ end
22
+ end
23
+
24
+ def generated_by_danger?(danger_id)
25
+ body.include?("\"generated_by_#{danger_id}\"")
26
+ end
27
+
28
+ def inline?
29
+ @inline.nil? ? body.include?("") : @inline
30
+ end
31
+ end
32
+ end
@@ -0,0 +1,179 @@
1
+ require "kramdown"
2
+ require "danger/helpers/comments_parsing_helper"
3
+ require "danger/helpers/emoji_mapper"
4
+ require "danger/helpers/find_max_num_violations"
5
+
6
+ module Danger
7
+ module Helpers
8
+ module CommentsHelper
9
+ # This might be a bit weird, but table_kind_from_title is a shared dependency for
10
+ # parsing and generating. And rubocop was adamant about file size so...
11
+ include Danger::Helpers::CommentsParsingHelper
12
+
13
+ def markdown_parser(text)
14
+ Kramdown::Document.new(text, input: "GFM", smart_quotes: %w(apos apos quot quot))
15
+ end
16
+
17
+ # !@group Extension points
18
+ # Produces a markdown link to the file the message points to
19
+ #
20
+ # request_source implementations are invited to override this method with their
21
+ # vendor specific link.
22
+ #
23
+ # @param [Violation or Markdown] message
24
+ # @param [Bool] Should hide any generated link created
25
+ #
26
+ # @return [String] The Markdown compatible link
27
+ def markdown_link_to_message(message, hide_link)
28
+ return "" if hide_link
29
+
30
+ "#{message.file}#L#{message.line}"
31
+ end
32
+
33
+ # !@group Extension points
34
+ # Determine whether two messages are equivalent
35
+ #
36
+ # request_source implementations are invited to override this method.
37
+ # This is mostly here to enable sources to detect when inlines change only in their
38
+ # commit hash and not in content per-se. since the link is implementation dependant
39
+ # so should be the comparison.
40
+ #
41
+ # @param [Violation or Markdown] m1
42
+ # @param [Violation or Markdown] m2
43
+ #
44
+ # @return [Boolean] whether they represent the same message
45
+ def messages_are_equivalent(m1, m2)
46
+ m1 == m2
47
+ end
48
+
49
+ def process_markdown(violation, hide_link = false)
50
+ message = violation.message
51
+ message = "#{markdown_link_to_message(violation, hide_link)}#{message}" if violation.file && violation.line
52
+
53
+ html = markdown_parser(message).to_html
54
+ # Remove the outer `<p>` and `</p>`.
55
+ html = html.strip.sub(%r{\A<p>(.*)</p>\z}m, '\1')
56
+ Violation.new(html, violation.sticky, violation.file, violation.line)
57
+ end
58
+
59
+ def table(name, emoji, violations, all_previous_violations, template: "github")
60
+ content = violations
61
+ content = content.map { |v| process_markdown(v) } unless ["bitbucket_server", "vsts"].include?(template)
62
+
63
+ kind = table_kind_from_title(name)
64
+ previous_violations = all_previous_violations[kind] || []
65
+ resolved_violations = previous_violations.reject do |pv|
66
+ content.count { |v| messages_are_equivalent(v, pv) } > 0
67
+ end
68
+
69
+ resolved_messages = resolved_violations.map(&:message).uniq
70
+ count = content.count
71
+
72
+ {
73
+ name: name,
74
+ emoji: emoji,
75
+ content: content,
76
+ resolved: resolved_messages,
77
+ count: count
78
+ }
79
+ end
80
+
81
+ def apply_template(tables: [], markdowns: [], danger_id: "danger", template: "github", request_source: template)
82
+ require "erb"
83
+
84
+ md_template = File.join(Danger.gem_path, "lib/danger/comment_generators/#{template}.md.erb")
85
+
86
+ # erb: http://www.rrn.dk/rubys-erb-templating-system
87
+ # for the extra args: http://stackoverflow.com/questions/4632879/erb-template-removing-the-trailing-line
88
+ @tables = tables
89
+ @markdowns = markdowns.map(&:message)
90
+ @danger_id = danger_id
91
+ @emoji_mapper = EmojiMapper.new(request_source.sub("_inline", ""))
92
+
93
+ return ERB.new(File.read(md_template), trim_mode: "-").result(binding)
94
+ end
95
+
96
+ def generate_comment(warnings: [], errors: [], messages: [], markdowns: [], previous_violations: {}, danger_id: "danger", template: "github")
97
+ apply_template(
98
+ tables: [
99
+ table("Error", "no_entry_sign", errors, previous_violations, template: template),
100
+ table("Warning", "warning", warnings, previous_violations, template: template),
101
+ table("Message", "book", messages, previous_violations, template: template)
102
+ ],
103
+ markdowns: markdowns,
104
+ danger_id: danger_id,
105
+ template: template
106
+ )
107
+ end
108
+
109
+ # resolved is essentially reserved for future use - eventually we might
110
+ # have some nice generic resolved-thing going :)
111
+ def generate_message_group_comment(message_group:,
112
+ danger_id: "danger",
113
+ resolved: [],
114
+ template: "github")
115
+ # cheating a bit - I don't want to alter the apply_template API
116
+ # so just sneak around behind its back setting some instance variables
117
+ # to get them to show up in the template
118
+ @message_group = message_group
119
+ @resolved = resolved
120
+ request_source_name = template.sub("_message_group", "")
121
+
122
+ apply_template(danger_id: danger_id,
123
+ markdowns: message_group.markdowns,
124
+ template: template,
125
+ request_source: request_source_name)
126
+ .sub(/\A\n*/, "")
127
+ end
128
+
129
+ def generate_inline_comment_body(emoji,
130
+ message,
131
+ danger_id: "danger",
132
+ resolved: false,
133
+ template: "github")
134
+ apply_template(
135
+ tables: [{ content: [message], resolved: resolved, emoji: emoji }],
136
+ danger_id: danger_id,
137
+ template: "#{template}_inline"
138
+ )
139
+ end
140
+
141
+ def generate_inline_markdown_body(markdown, danger_id: "danger", template: "github")
142
+ apply_template(
143
+ markdowns: [markdown],
144
+ danger_id: danger_id,
145
+ template: "#{template}_inline"
146
+ )
147
+ end
148
+
149
+ def generate_description(warnings: nil, errors: nil, template: "github")
150
+ emoji_mapper = EmojiMapper.new(template)
151
+ if (errors.nil? || errors.empty?) && (warnings.nil? || warnings.empty?)
152
+ return ENV["DANGER_SUCCESS_MESSAGE"] || "All green. #{random_compliment}"
153
+ else
154
+ message = "#{emoji_mapper.map('warning')} "
155
+ message += "#{'Error'.danger_pluralize(errors.count)}. " unless errors.empty?
156
+ message += "#{'Warning'.danger_pluralize(warnings.count)}. " unless warnings.empty?
157
+ message += "Don't worry, everything is fixable."
158
+ return message
159
+ end
160
+ end
161
+
162
+ def random_compliment
163
+ ["Well done.", "Congrats.", "Woo!",
164
+ "Yay.", "Jolly good show.", "Good on 'ya.", "Nice work."].sample
165
+ end
166
+
167
+ private
168
+
169
+ def pluralize(string, count)
170
+ string.danger_pluralize(count)
171
+ end
172
+
173
+ def truncate(string)
174
+ max_message_length = 30
175
+ string.danger_truncate(max_message_length)
176
+ end
177
+ end
178
+ end
179
+ end
@@ -0,0 +1,71 @@
1
+ module Danger
2
+ module Helpers
3
+ module CommentsParsingHelper
4
+ # !@group Extension points
5
+ # Produces a message-like from a row in a comment table
6
+ #
7
+ # @param [String] row
8
+ # The content of the row in the table
9
+ #
10
+ # @return [Violation or Markdown] the extracted message
11
+ def parse_message_from_row(row)
12
+ Violation.new(row, true)
13
+ end
14
+
15
+ def parse_tables_from_comment(comment)
16
+ comment.split("</table>")
17
+ end
18
+
19
+ def violations_from_table(table)
20
+ row_regex = %r{<td data-sticky="true">(?:<del>)?(.*?)(?:</del>)?\s*</td>}im
21
+ table.scan(row_regex).flatten.map do |row|
22
+ parse_message_from_row(row.strip)
23
+ end
24
+ end
25
+
26
+ def parse_comment(comment)
27
+ tables = parse_tables_from_comment(comment)
28
+ violations = {}
29
+ tables.each do |table|
30
+ match = danger_table?(table)
31
+ next unless match
32
+
33
+ title = match[1]
34
+ kind = table_kind_from_title(title)
35
+ next unless kind
36
+
37
+ violations[kind] = violations_from_table(table)
38
+ end
39
+
40
+ violations.reject { |_, v| v.empty? }
41
+ end
42
+
43
+ def table_kind_from_title(title)
44
+ if title =~ /error/i
45
+ :error
46
+ elsif title =~ /warning/i
47
+ :warning
48
+ elsif title =~ /message/i
49
+ :message
50
+ end
51
+ end
52
+
53
+ private
54
+
55
+ GITHUB_OLD_REGEX = %r{<th width="100%"(.*?)</th>}im.freeze
56
+ NEW_REGEX = %r{<th.*data-danger-table="true"(.*?)</th>}im.freeze
57
+
58
+ def danger_table?(table)
59
+ # The old GitHub specific method relied on
60
+ # the width of a `th` element to find the table
61
+ # title and determine if it was a danger table.
62
+ # The new method uses a more robust data-danger-table
63
+ # tag instead.
64
+ match = GITHUB_OLD_REGEX.match(table)
65
+ return match if match
66
+
67
+ return NEW_REGEX.match(table)
68
+ end
69
+ end
70
+ end
71
+ end