danger 3.1.1 → 3.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.
- checksums.yaml +4 -4
- data/lib/danger.rb +14 -0
- data/lib/danger/ci_source/jenkins.rb +1 -1
- data/lib/danger/ci_source/local_git_repo.rb +4 -0
- data/lib/danger/clients/rubygems_client.rb +14 -0
- data/lib/danger/comment_generators/bitbucket_server.md.erb +20 -0
- data/lib/danger/comment_generators/github_inline.md.erb +26 -0
- data/lib/danger/danger_core/dangerfile.rb +15 -6
- data/lib/danger/danger_core/messages/markdown.rb +28 -0
- data/lib/danger/danger_core/messages/violation.rb +35 -0
- data/lib/danger/danger_core/plugins/dangerfile_bitbucket_server_plugin.rb +182 -0
- data/lib/danger/danger_core/plugins/dangerfile_danger_plugin.rb +61 -6
- data/lib/danger/danger_core/plugins/dangerfile_messaging_plugin.rb +30 -9
- data/lib/danger/danger_core/standard_error.rb +10 -1
- data/lib/danger/helpers/comments_helper.rb +94 -33
- data/lib/danger/helpers/comments_parsing_helper.rb +52 -0
- data/lib/danger/request_source/bitbucket_server.rb +78 -0
- data/lib/danger/request_source/bitbucket_server_api.rb +70 -0
- data/lib/danger/request_source/github.rb +207 -9
- data/lib/danger/scm_source/git_repo.rb +10 -1
- data/lib/danger/version.rb +1 -1
- metadata +13 -4
- data/lib/danger/danger_core/violation.rb +0 -10
@@ -1,4 +1,5 @@
|
|
1
|
-
require "danger/danger_core/violation"
|
1
|
+
require "danger/danger_core/messages/violation"
|
2
|
+
require "danger/danger_core/messages/markdown"
|
2
3
|
require "danger/plugin_support/plugin"
|
3
4
|
|
4
5
|
module Danger
|
@@ -38,6 +39,10 @@ module Danger
|
|
38
39
|
# message << "20 | No documentation | Error \n"
|
39
40
|
# markdown message
|
40
41
|
#
|
42
|
+
# @example Adding an inline warning to a file
|
43
|
+
#
|
44
|
+
# warn("You shouldn't use puts in your Dangerfile", file: "Dangerfile", line: 10)
|
45
|
+
#
|
41
46
|
#
|
42
47
|
# @see danger/danger
|
43
48
|
# @tags core, messaging
|
@@ -65,10 +70,14 @@ module Danger
|
|
65
70
|
#
|
66
71
|
# @param [String] message
|
67
72
|
# The markdown based message to be printed below the table
|
73
|
+
# @param [String] file
|
74
|
+
# Optional. Path to the file that the message is for.
|
75
|
+
# @param [String] line
|
76
|
+
# Optional. The line in the file to present the message in.
|
68
77
|
# @return [void]
|
69
78
|
#
|
70
|
-
def markdown(message)
|
71
|
-
@markdowns << message
|
79
|
+
def markdown(message, file: nil, line: nil)
|
80
|
+
@markdowns << Markdown.new(message, file, line)
|
72
81
|
end
|
73
82
|
|
74
83
|
# @!group Core
|
@@ -79,10 +88,14 @@ module Danger
|
|
79
88
|
# @param [Boolean] sticky
|
80
89
|
# Whether the message should be kept after it was fixed,
|
81
90
|
# defaults to `true`.
|
91
|
+
# @param [String] file
|
92
|
+
# Optional. Path to the file that the message is for.
|
93
|
+
# @param [String] line
|
94
|
+
# Optional. The line in the file to present the message in.
|
82
95
|
# @return [void]
|
83
96
|
#
|
84
|
-
def message(message, sticky: true)
|
85
|
-
@messages << Violation.new(message, sticky)
|
97
|
+
def message(message, sticky: true, file: nil, line: nil)
|
98
|
+
@messages << Violation.new(message, sticky, file, line)
|
86
99
|
end
|
87
100
|
|
88
101
|
# @!group Core
|
@@ -93,11 +106,15 @@ module Danger
|
|
93
106
|
# @param [Boolean] sticky
|
94
107
|
# Whether the message should be kept after it was fixed,
|
95
108
|
# defaults to `true`.
|
109
|
+
# @param [String] file
|
110
|
+
# Optional. Path to the file that the message is for.
|
111
|
+
# @param [String] line
|
112
|
+
# Optional. The line in the file to present the message in.
|
96
113
|
# @return [void]
|
97
114
|
#
|
98
|
-
def warn(message, sticky: true)
|
115
|
+
def warn(message, sticky: true, file: nil, line: nil)
|
99
116
|
return if should_ignore_violation(message)
|
100
|
-
@warnings << Violation.new(message, sticky)
|
117
|
+
@warnings << Violation.new(message, sticky, file, line)
|
101
118
|
end
|
102
119
|
|
103
120
|
# @!group Core
|
@@ -108,11 +125,15 @@ module Danger
|
|
108
125
|
# @param [Boolean] sticky
|
109
126
|
# Whether the message should be kept after it was fixed,
|
110
127
|
# defaults to `true`.
|
128
|
+
# @param [String] file
|
129
|
+
# Optional. Path to the file that the message is for.
|
130
|
+
# @param [String] line
|
131
|
+
# Optional. The line in the file to present the message in.
|
111
132
|
# @return [void]
|
112
133
|
#
|
113
|
-
def fail(message, sticky: true)
|
134
|
+
def fail(message, sticky: true, file: nil, line: nil)
|
114
135
|
return if should_ignore_violation(message)
|
115
|
-
@errors << Violation.new(message, sticky)
|
136
|
+
@errors << Violation.new(message, sticky, file, line)
|
116
137
|
end
|
117
138
|
|
118
139
|
# @!group Reporting
|
@@ -63,10 +63,13 @@ module Danger
|
|
63
63
|
def message
|
64
64
|
@message ||= begin
|
65
65
|
trace_line, description = parse_line_number_from_description
|
66
|
+
latest_version = Danger.danger_outdated?
|
66
67
|
|
67
68
|
m = "\n[!] "
|
68
69
|
m << description
|
69
|
-
|
70
|
+
if latest_version
|
71
|
+
m << upgrade_message(latest_version)
|
72
|
+
end
|
70
73
|
m = m.red if m.respond_to?(:red)
|
71
74
|
|
72
75
|
return m unless backtrace && dsl_path && contents
|
@@ -103,5 +106,11 @@ module Danger
|
|
103
106
|
end
|
104
107
|
[trace_line, description]
|
105
108
|
end
|
109
|
+
|
110
|
+
def upgrade_message(latest_version)
|
111
|
+
". Updating the Danger gem might fix the issue. "\
|
112
|
+
"Your Danger version: #{Danger::VERSION}, "\
|
113
|
+
"latest Danger version: #{latest_version}\n"
|
114
|
+
end
|
106
115
|
end
|
107
116
|
end
|
@@ -1,27 +1,57 @@
|
|
1
1
|
require "kramdown"
|
2
|
+
require "danger/helpers/comments_parsing_helper"
|
3
|
+
|
4
|
+
# rubocop:disable Metrics/ModuleLength
|
2
5
|
|
3
6
|
module Danger
|
4
7
|
module Helpers
|
5
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
|
+
|
6
13
|
def markdown_parser(text)
|
7
14
|
Kramdown::Document.new(text, input: "GFM")
|
8
15
|
end
|
9
16
|
|
10
|
-
|
11
|
-
|
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, _)
|
28
|
+
"#{messages.file}#L#{message.line}"
|
12
29
|
end
|
13
30
|
|
14
|
-
|
15
|
-
|
16
|
-
|
31
|
+
# !@group Extension points
|
32
|
+
# Determine whether two messages are equivalent
|
33
|
+
#
|
34
|
+
# request_source implementations are invited to override this method.
|
35
|
+
# This is mostly here to enable sources to detect when inlines change only in their
|
36
|
+
# commit hash and not in content per-se. since the link is implementation dependant
|
37
|
+
# so should be the comparision.
|
38
|
+
#
|
39
|
+
# @param [Violation or Markdown] m1
|
40
|
+
# @param [Violation or Markdown] m2
|
41
|
+
#
|
42
|
+
# @return [Boolean] whether they represent the same message
|
43
|
+
def messages_are_equivalent(m1, m2)
|
44
|
+
m1 == m2
|
17
45
|
end
|
18
46
|
|
19
|
-
def process_markdown(violation)
|
20
|
-
|
47
|
+
def process_markdown(violation, hide_link = false)
|
48
|
+
message = violation.message
|
49
|
+
message = "#{markdown_link_to_message(violation, hide_link)}#{message}" if violation.file && violation.line
|
50
|
+
|
51
|
+
html = markdown_parser(message).to_html
|
21
52
|
# Remove the outer `<p>`, the -5 represents a newline + `</p>`
|
22
53
|
html = html[3...-5] if html.start_with? "<p>"
|
23
|
-
|
24
|
-
Violation.new(html, violation.sticky)
|
54
|
+
Violation.new(html, violation.sticky, violation.file, violation.line)
|
25
55
|
end
|
26
56
|
|
27
57
|
def parse_comment(comment)
|
@@ -41,50 +71,69 @@ module Danger
|
|
41
71
|
end
|
42
72
|
|
43
73
|
def table(name, emoji, violations, all_previous_violations)
|
44
|
-
content = violations.map { |v| process_markdown(v) }
|
74
|
+
content = violations.map { |v| process_markdown(v) }
|
75
|
+
|
45
76
|
kind = table_kind_from_title(name)
|
46
77
|
previous_violations = all_previous_violations[kind] || []
|
47
|
-
|
48
|
-
|
78
|
+
resolved_violations = previous_violations.reject do |pv|
|
79
|
+
content.count { |v| messages_are_equivalent(v, pv) } > 0
|
80
|
+
end
|
81
|
+
|
82
|
+
resolved_messages = resolved_violations.map(&:message).uniq
|
49
83
|
count = content.count
|
50
84
|
|
51
85
|
{
|
52
86
|
name: name,
|
53
87
|
emoji: emoji,
|
54
88
|
content: content,
|
55
|
-
resolved:
|
89
|
+
resolved: resolved_messages,
|
56
90
|
count: count
|
57
91
|
}
|
58
92
|
end
|
59
93
|
|
60
|
-
def
|
61
|
-
if title =~ /error/i
|
62
|
-
:error
|
63
|
-
elsif title =~ /warning/i
|
64
|
-
:warning
|
65
|
-
elsif title =~ /message/i
|
66
|
-
:message
|
67
|
-
end
|
68
|
-
end
|
69
|
-
|
70
|
-
def generate_comment(warnings: [], errors: [], messages: [], markdowns: [], previous_violations: {}, danger_id: "danger", template: "github")
|
94
|
+
def apply_template(tables: [], markdowns: [], danger_id: "danger", template: "github")
|
71
95
|
require "erb"
|
72
96
|
|
73
97
|
md_template = File.join(Danger.gem_path, "lib/danger/comment_generators/#{template}.md.erb")
|
74
98
|
|
75
99
|
# erb: http://www.rrn.dk/rubys-erb-templating-system
|
76
100
|
# for the extra args: http://stackoverflow.com/questions/4632879/erb-template-removing-the-trailing-line
|
77
|
-
@tables =
|
78
|
-
|
79
|
-
table("Warning", "warning", warnings, previous_violations),
|
80
|
-
table("Message", "book", messages, previous_violations)
|
81
|
-
]
|
82
|
-
@markdowns = markdowns
|
101
|
+
@tables = tables
|
102
|
+
@markdowns = markdowns.map(&:message)
|
83
103
|
@danger_id = danger_id
|
84
104
|
|
85
105
|
return ERB.new(File.read(md_template), 0, "-").result(binding)
|
86
106
|
end
|
87
107
|
|
108
|
+
def generate_comment(warnings: [], errors: [], messages: [], markdowns: [], previous_violations: {}, danger_id: "danger", template: "github")
|
109
|
+
apply_template(
|
110
|
+
tables: [
|
111
|
+
table("Error", "no_entry_sign", errors, previous_violations),
|
112
|
+
table("Warning", "warning", warnings, previous_violations),
|
113
|
+
table("Message", "book", messages, previous_violations)
|
114
|
+
],
|
115
|
+
markdowns: markdowns,
|
116
|
+
danger_id: danger_id,
|
117
|
+
template: template
|
118
|
+
)
|
119
|
+
end
|
120
|
+
|
121
|
+
def generate_inline_comment_body(emoji, message, danger_id: "danger", resolved: false, template: "github")
|
122
|
+
apply_template(
|
123
|
+
tables: [{ content: [message], resolved: resolved, emoji: emoji }],
|
124
|
+
danger_id: danger_id,
|
125
|
+
template: "#{template}_inline"
|
126
|
+
)
|
127
|
+
end
|
128
|
+
|
129
|
+
def generate_inline_markdown_body(markdown, danger_id: "danger", template: "github")
|
130
|
+
apply_template(
|
131
|
+
markdowns: [markdown],
|
132
|
+
danger_id: danger_id,
|
133
|
+
template: "#{template}_inline"
|
134
|
+
)
|
135
|
+
end
|
136
|
+
|
88
137
|
def generate_description(warnings: nil, errors: nil)
|
89
138
|
if errors.empty? && warnings.empty?
|
90
139
|
return "All green. #{random_compliment}"
|
@@ -98,9 +147,21 @@ module Danger
|
|
98
147
|
end
|
99
148
|
|
100
149
|
def random_compliment
|
101
|
-
|
102
|
-
|
103
|
-
|
150
|
+
["Well done.", "Congrats.", "Woo!",
|
151
|
+
"Yay.", "Jolly good show.", "Good on 'ya.", "Nice work."].sample
|
152
|
+
end
|
153
|
+
|
154
|
+
def character_from_emoji(emoji)
|
155
|
+
emoji.delete! ":"
|
156
|
+
if emoji == "no_entry_sign"
|
157
|
+
"🚫"
|
158
|
+
elsif emoji == "warning"
|
159
|
+
"⚠️"
|
160
|
+
elsif emoji == "book"
|
161
|
+
"📖"
|
162
|
+
elsif emoji == "white_check_mark"
|
163
|
+
"✅"
|
164
|
+
end
|
104
165
|
end
|
105
166
|
|
106
167
|
private
|
@@ -0,0 +1,52 @@
|
|
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, nil, nil)
|
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
|
+
next unless table =~ %r{<th width="100%"(.*?)</th>}im
|
31
|
+
title = Regexp.last_match(1)
|
32
|
+
kind = table_kind_from_title(title)
|
33
|
+
next unless kind
|
34
|
+
|
35
|
+
violations[kind] = violations_from_table(table)
|
36
|
+
end
|
37
|
+
|
38
|
+
violations.reject { |_, v| v.empty? }
|
39
|
+
end
|
40
|
+
|
41
|
+
def table_kind_from_title(title)
|
42
|
+
if title =~ /error/i
|
43
|
+
:error
|
44
|
+
elsif title =~ /warning/i
|
45
|
+
:warning
|
46
|
+
elsif title =~ /message/i
|
47
|
+
:message
|
48
|
+
end
|
49
|
+
end
|
50
|
+
end
|
51
|
+
end
|
52
|
+
end
|
@@ -0,0 +1,78 @@
|
|
1
|
+
# coding: utf-8
|
2
|
+
require "danger/helpers/comments_helper"
|
3
|
+
|
4
|
+
module Danger
|
5
|
+
module RequestSources
|
6
|
+
class BitbucketServer < RequestSource
|
7
|
+
include Danger::Helpers::CommentsHelper
|
8
|
+
attr_accessor :pr_json
|
9
|
+
|
10
|
+
def initialize(ci_source, environment)
|
11
|
+
self.ci_source = ci_source
|
12
|
+
self.environment = environment
|
13
|
+
|
14
|
+
project, slug = ci_source.repo_slug.split("/")
|
15
|
+
@api = BitbucketServerAPI.new(project, slug, ci_source.pull_request_id, environment)
|
16
|
+
end
|
17
|
+
|
18
|
+
def validates_as_ci?
|
19
|
+
# TODO: ???
|
20
|
+
true
|
21
|
+
end
|
22
|
+
|
23
|
+
def validates_as_api_source?
|
24
|
+
@api.credentials_given?
|
25
|
+
end
|
26
|
+
|
27
|
+
def scm
|
28
|
+
@scm ||= GitRepo.new
|
29
|
+
end
|
30
|
+
|
31
|
+
def host
|
32
|
+
@host ||= @api.host
|
33
|
+
end
|
34
|
+
|
35
|
+
def fetch_details
|
36
|
+
self.pr_json = @api.fetch_pr_json
|
37
|
+
end
|
38
|
+
|
39
|
+
def setup_danger_branches
|
40
|
+
base_commit = self.pr_json[:toRef][:latestCommit]
|
41
|
+
head_commit = self.pr_json[:fromRef][:latestCommit]
|
42
|
+
|
43
|
+
# Next, we want to ensure that we have a version of the current branch at a known location
|
44
|
+
self.scm.exec "branch #{EnvironmentManager.danger_base_branch} #{base_commit}"
|
45
|
+
|
46
|
+
# OK, so we want to ensure that we have a known head branch, this will always represent
|
47
|
+
# the head of the PR ( e.g. the most recent commit that will be merged. )
|
48
|
+
self.scm.exec "branch #{EnvironmentManager.danger_head_branch} #{head_commit}"
|
49
|
+
end
|
50
|
+
|
51
|
+
def organisation
|
52
|
+
nil
|
53
|
+
end
|
54
|
+
|
55
|
+
def update_pull_request!(warnings: [], errors: [], messages: [], markdowns: [], danger_id: "danger")
|
56
|
+
delete_old_comments(danger_id: danger_id)
|
57
|
+
|
58
|
+
comment = generate_description(warnings: warnings, errors: errors)
|
59
|
+
comment += "\n\n"
|
60
|
+
comment += generate_comment(warnings: warnings,
|
61
|
+
errors: errors,
|
62
|
+
messages: messages,
|
63
|
+
markdowns: markdowns,
|
64
|
+
previous_violations: {},
|
65
|
+
danger_id: danger_id,
|
66
|
+
template: "bitbucket_server")
|
67
|
+
|
68
|
+
@api.post_comment(comment)
|
69
|
+
end
|
70
|
+
|
71
|
+
def delete_old_comments(danger_id: "danger")
|
72
|
+
@api.fetch_last_comments.each do |c|
|
73
|
+
@api.delete_comment(c[:id], c[:version]) if c[:text] =~ /generated_by_#{danger_id}/
|
74
|
+
end
|
75
|
+
end
|
76
|
+
end
|
77
|
+
end
|
78
|
+
end
|
@@ -0,0 +1,70 @@
|
|
1
|
+
# coding: utf-8
|
2
|
+
require "danger/helpers/comments_helper"
|
3
|
+
|
4
|
+
module Danger
|
5
|
+
module RequestSources
|
6
|
+
class BitbucketServerAPI
|
7
|
+
attr_accessor :host, :pr_api_endpoint
|
8
|
+
|
9
|
+
def initialize(project, slug, pull_request_id, environment)
|
10
|
+
@username = environment["DANGER_BITBUCKETSERVER_USERNAME"]
|
11
|
+
@password = environment["DANGER_BITBUCKETSERVER_PASSWORD"]
|
12
|
+
self.host = environment["DANGER_BITBUCKETSERVER_HOST"]
|
13
|
+
self.pr_api_endpoint = "https://#{host}/rest/api/1.0/projects/#{project}/repos/#{slug}/pull-requests/#{pull_request_id}"
|
14
|
+
end
|
15
|
+
|
16
|
+
def credentials_given?
|
17
|
+
@username && !@username.empty? && @password && !@password.empty?
|
18
|
+
end
|
19
|
+
|
20
|
+
def fetch_pr_json
|
21
|
+
uri = URI(pr_api_endpoint)
|
22
|
+
fetch_json(uri)
|
23
|
+
end
|
24
|
+
|
25
|
+
def fetch_last_comments
|
26
|
+
uri = URI("#{pr_api_endpoint}/activities?limit=1000")
|
27
|
+
fetch_json(uri)[:values].select { |v| v[:action] == "COMMENTED" }.map { |v| v[:comment] }
|
28
|
+
end
|
29
|
+
|
30
|
+
def delete_comment(id, version)
|
31
|
+
uri = URI("#{pr_api_endpoint}/comments/#{id}?version=#{version}")
|
32
|
+
delete(uri)
|
33
|
+
end
|
34
|
+
|
35
|
+
def post_comment(text)
|
36
|
+
uri = URI("#{pr_api_endpoint}/comments")
|
37
|
+
body = { text: text }.to_json
|
38
|
+
post(uri, body)
|
39
|
+
end
|
40
|
+
|
41
|
+
private
|
42
|
+
|
43
|
+
def fetch_json(uri)
|
44
|
+
req = Net::HTTP::Get.new(uri.request_uri, { "Content-Type" => "application/json" })
|
45
|
+
req.basic_auth @username, @password
|
46
|
+
res = Net::HTTP.start(uri.hostname, uri.port, use_ssl: true) do |http|
|
47
|
+
http.request(req)
|
48
|
+
end
|
49
|
+
JSON.parse(res.body, symbolize_names: true)
|
50
|
+
end
|
51
|
+
|
52
|
+
def post(uri, body)
|
53
|
+
req = Net::HTTP::Post.new(uri.request_uri, { "Content-Type" => "application/json" })
|
54
|
+
req.basic_auth @username, @password
|
55
|
+
req.body = body
|
56
|
+
Net::HTTP.start(uri.hostname, uri.port, use_ssl: true) do |http|
|
57
|
+
http.request(req)
|
58
|
+
end
|
59
|
+
end
|
60
|
+
|
61
|
+
def delete(uri)
|
62
|
+
req = Net::HTTP::Delete.new(uri.request_uri, { "Content-Type" => "application/json" })
|
63
|
+
req.basic_auth @username, @password
|
64
|
+
Net::HTTP.start(uri.hostname, uri.port, use_ssl: true) do |http|
|
65
|
+
http.request(req)
|
66
|
+
end
|
67
|
+
end
|
68
|
+
end
|
69
|
+
end
|
70
|
+
end
|