danger-wcc 0.0.2
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 +7 -0
- data/.circleci/config.yml +46 -0
- data/.gitignore +6 -0
- data/.rubocop.yml +219 -0
- data/.ruby-version +1 -0
- data/.travis.yml +12 -0
- data/Dangerfile +5 -0
- data/Gemfile +6 -0
- data/Guardfile +35 -0
- data/LICENSE +201 -0
- data/README.md +2 -0
- data/Rakefile +25 -0
- data/danger-wcc.gemspec +58 -0
- data/lib/danger_plugin.rb +3 -0
- data/lib/version.rb +5 -0
- data/lib/wcc/commit_lint.rb +158 -0
- data/lib/wcc/commit_lint/README.md +3 -0
- data/lib/wcc/commit_lint/commit_check.rb +19 -0
- data/lib/wcc/commit_lint/empty_line_check.rb +22 -0
- data/lib/wcc/commit_lint/subject_cap_check.rb +22 -0
- data/lib/wcc/commit_lint/subject_length_check.rb +28 -0
- data/lib/wcc/commit_lint/subject_period_check.rb +22 -0
- data/lib/wcc/commit_lint/subject_words_check.rb +22 -0
- data/lib/wcc/default.jshintrc +5 -0
- data/lib/wcc/defaults.reek +131 -0
- data/lib/wcc/github.rb +24 -0
- data/lib/wcc/jshint.rb +63 -0
- data/lib/wcc/plugin.rb +128 -0
- data/lib/wcc/reek.rb +56 -0
- data/lib/wcc/rubocop_exceptions.rb +99 -0
- data/lib/wcc/todos.rb +78 -0
- data/lib/wcc/utils.rb +136 -0
- data/spec/fixtures/brakeman/a.tmp +13 -0
- data/spec/fixtures/brakeman/b.tmp +14 -0
- data/spec/fixtures/brakeman/brakeman.diff +20 -0
- data/spec/fixtures/brakeman/brakeman.out +14 -0
- data/spec/fixtures/exception_context.diff +15 -0
- data/spec/fixtures/exception_insert_context.diff +14 -0
- data/spec/fixtures/exception_misspelled.diff +14 -0
- data/spec/fixtures/exception_multiline_context.diff +20 -0
- data/spec/fixtures/exception_reenabled.diff +13 -0
- data/spec/fixtures/find_in_diff.rb +21 -0
- data/spec/fixtures/find_in_diff_2_chunks.diff +24 -0
- data/spec/fixtures/flay.diff +17 -0
- data/spec/fixtures/flay.txt +18 -0
- data/spec/fixtures/github/labels.json +72 -0
- data/spec/fixtures/github_pr.json +325 -0
- data/spec/fixtures/jshint/a.tmp +5 -0
- data/spec/fixtures/jshint/b.tmp +7 -0
- data/spec/fixtures/jshint/jshint.diff +13 -0
- data/spec/fixtures/jshint/out.jshint +7 -0
- data/spec/fixtures/no_exception.diff +10 -0
- data/spec/fixtures/no_todo.diff +13 -0
- data/spec/fixtures/reek/line_numbers.reek +121 -0
- data/spec/fixtures/reek/reek.diff +50 -0
- data/spec/fixtures/rubocop_exception.rb +39 -0
- data/spec/fixtures/todo.rb +21 -0
- data/spec/fixtures/todo_link_next_line.diff +14 -0
- data/spec/fixtures/todo_link_same_line.diff +13 -0
- data/spec/fixtures/todo_no_link.diff +13 -0
- data/spec/fixtures/todo_removed.diff +13 -0
- data/spec/fixtures_helper.rb +19 -0
- data/spec/spec_helper.rb +73 -0
- data/spec/wcc/commit_lint_spec.rb +392 -0
- data/spec/wcc/github_spec.rb +67 -0
- data/spec/wcc/jshint_spec.rb +68 -0
- data/spec/wcc/plugin_spec.rb +134 -0
- data/spec/wcc/reek_spec.rb +71 -0
- data/spec/wcc/rubocop_exceptions_spec.rb +136 -0
- data/spec/wcc/todos_spec.rb +96 -0
- data/spec/wcc/utils_spec.rb +134 -0
- data/spec/wcc_spec.rb +21 -0
- metadata +393 -0
data/lib/wcc/github.rb
ADDED
@@ -0,0 +1,24 @@
|
|
1
|
+
# frozen_string_literal: true
|
2
|
+
|
3
|
+
module Github
|
4
|
+
def labels
|
5
|
+
@github_labels ||=
|
6
|
+
github.api.labels(repo_name)
|
7
|
+
end
|
8
|
+
|
9
|
+
def repo_name
|
10
|
+
github.pr_json['base']['repo']['full_name']
|
11
|
+
end
|
12
|
+
|
13
|
+
def pr_number
|
14
|
+
github.pr_json['number']
|
15
|
+
end
|
16
|
+
|
17
|
+
def add_labels(*new_labels)
|
18
|
+
existing = labels.map(&:name)
|
19
|
+
new_labels.reject { |l| existing.include?(l) }.each do |to_create|
|
20
|
+
github.api.add_label(repo_name, to_create)
|
21
|
+
end
|
22
|
+
github.api.add_labels_to_an_issue(repo_name, pr_number, new_labels)
|
23
|
+
end
|
24
|
+
end
|
data/lib/wcc/jshint.rb
ADDED
@@ -0,0 +1,63 @@
|
|
1
|
+
# frozen_string_literal: true
|
2
|
+
|
3
|
+
require_relative 'utils'
|
4
|
+
|
5
|
+
class Danger::DangerWCC < Danger::Plugin
|
6
|
+
class Jshint
|
7
|
+
include Utils
|
8
|
+
|
9
|
+
DEFAULT_OPTIONS = {
|
10
|
+
directory: 'app/assets/javascripts'
|
11
|
+
}.freeze
|
12
|
+
|
13
|
+
def initialize(plugin, options = {})
|
14
|
+
@plugin = plugin
|
15
|
+
@options = DEFAULT_OPTIONS.merge(options)
|
16
|
+
end
|
17
|
+
|
18
|
+
def perform
|
19
|
+
return unless Dir.exist?(@options[:directory])
|
20
|
+
|
21
|
+
run('PREFIX=/tmp npm install -g jshint')
|
22
|
+
diff = run_jshint_diff
|
23
|
+
|
24
|
+
# run jshint again to get line numbers
|
25
|
+
jshint_lines = run '/tmp/lib/node_modules/jshint/bin/jshint '\
|
26
|
+
"#{@options[:directory]}"
|
27
|
+
jshint_lines = jshint_lines.lines
|
28
|
+
|
29
|
+
each_addition_in_diff(diff) do |line|
|
30
|
+
add_jshint_warning(jshint_lines[line.line_number.right - 1])
|
31
|
+
end
|
32
|
+
end
|
33
|
+
|
34
|
+
private
|
35
|
+
|
36
|
+
def run_jshint_diff
|
37
|
+
diff =
|
38
|
+
run_and_diff do
|
39
|
+
write_jshintrc
|
40
|
+
run('/tmp/lib/node_modules/jshint/bin/jshint '\
|
41
|
+
"#{@options[:directory]} | sed 's/line [0-9]*, col [0-9]*,//g'")
|
42
|
+
end
|
43
|
+
GitDiff.from_string(diff)
|
44
|
+
end
|
45
|
+
|
46
|
+
def write_jshintrc
|
47
|
+
return if File.exist?('.jshintrc')
|
48
|
+
|
49
|
+
default = File.join(File.dirname(__FILE__), 'default.jshintrc')
|
50
|
+
FileUtils.cp(default, './.jshintrc')
|
51
|
+
end
|
52
|
+
|
53
|
+
def add_jshint_warning(with_line_number)
|
54
|
+
return unless warning = with_line_number.match(
|
55
|
+
/^([^\:]+)\:\s+line\s+(\d+),\s+col\s+\d+,\s+(.+)$/i
|
56
|
+
)
|
57
|
+
|
58
|
+
plugin.warn(warning.captures[2],
|
59
|
+
file: warning.captures[0],
|
60
|
+
line: warning.captures[1].to_i)
|
61
|
+
end
|
62
|
+
end
|
63
|
+
end
|
data/lib/wcc/plugin.rb
ADDED
@@ -0,0 +1,128 @@
|
|
1
|
+
# frozen_string_literal: true
|
2
|
+
|
3
|
+
require_relative 'utils'
|
4
|
+
require_relative 'github'
|
5
|
+
require_relative 'todos'
|
6
|
+
require_relative 'rubocop_exceptions'
|
7
|
+
require_relative 'commit_lint'
|
8
|
+
require_relative 'reek'
|
9
|
+
require_relative 'jshint'
|
10
|
+
|
11
|
+
class Danger::DangerWCC < Danger::Plugin
|
12
|
+
include Utils
|
13
|
+
include Github
|
14
|
+
|
15
|
+
CHECKS = %i[
|
16
|
+
rubocop_exceptions
|
17
|
+
todos
|
18
|
+
commit_lint
|
19
|
+
reek
|
20
|
+
flay
|
21
|
+
brakeman
|
22
|
+
jshint
|
23
|
+
].freeze
|
24
|
+
|
25
|
+
# Runs all the included checks in the plugin
|
26
|
+
def all(options = {})
|
27
|
+
to_run = CHECKS.reject { |check_name| options[check_name] == false }
|
28
|
+
raise ArgumentError, 'No Enabled Checks' if to_run.empty?
|
29
|
+
|
30
|
+
to_run.each do |check_name|
|
31
|
+
public_send(check_name, options.fetch(check_name, {}))
|
32
|
+
end
|
33
|
+
end
|
34
|
+
|
35
|
+
# Checks for added TODOs
|
36
|
+
def todos(options = {})
|
37
|
+
logger.info "TODOs: #{options}"
|
38
|
+
Todos.new(self, options).perform
|
39
|
+
end
|
40
|
+
|
41
|
+
# Checks for if Rubocop was disabled in the source
|
42
|
+
def rubocop_exceptions(options = {})
|
43
|
+
logger.info "rubocop_exceptions: #{options}"
|
44
|
+
RubocopExceptions.new(self, options).perform
|
45
|
+
end
|
46
|
+
|
47
|
+
# Lints the commit messages
|
48
|
+
def commit_lint(options = {})
|
49
|
+
logger.info "commit_lint: #{options}"
|
50
|
+
CommitLint.new(self, DEFAULT_COMMIT_LINT_OPTIONS.merge(options)).perform
|
51
|
+
end
|
52
|
+
|
53
|
+
def reek(options = {})
|
54
|
+
logger.info "reek: #{options}"
|
55
|
+
Reek.new(self, options).perform
|
56
|
+
end
|
57
|
+
|
58
|
+
def brakeman(options = {})
|
59
|
+
logger.info "brakeman: #{options}"
|
60
|
+
diff = run_and_diff('bundle exec brakeman -f tabs 2>/dev/null '\
|
61
|
+
'| sed s@`pwd`/@@ | sed -E "s/([0-9]+)//g"')
|
62
|
+
diff = GitDiff.from_string(diff)
|
63
|
+
|
64
|
+
brakeman_lines = run 'bundle exec brakeman -f tabs 2>/dev/null '\
|
65
|
+
'| sed s@`pwd`/@@'
|
66
|
+
brakeman_lines = brakeman_lines.lines
|
67
|
+
|
68
|
+
each_addition_in_diff(diff) do |line|
|
69
|
+
fields = brakeman_lines[line.line_number.right - 1].split("\t")
|
70
|
+
|
71
|
+
add_brakeman_error(fields, fields[0])
|
72
|
+
end
|
73
|
+
end
|
74
|
+
|
75
|
+
def flay(options = {})
|
76
|
+
logger.info "flay: #{options}"
|
77
|
+
flay_results = parse_flay_results
|
78
|
+
|
79
|
+
each_addition_in_diff do |add, _h, file|
|
80
|
+
search = "#{file.b_path}:#{add.line_number.right}"
|
81
|
+
flay_results.each do |warning, lines|
|
82
|
+
other = lines.reject { |l| l == search }
|
83
|
+
next unless other.length < lines.length
|
84
|
+
|
85
|
+
add_flay_warning(warning, other, file, add)
|
86
|
+
end
|
87
|
+
end
|
88
|
+
end
|
89
|
+
|
90
|
+
def jshint(options = {})
|
91
|
+
logger.info "jshint: #{options}"
|
92
|
+
Jshint.new(self, options).perform
|
93
|
+
end
|
94
|
+
|
95
|
+
private
|
96
|
+
|
97
|
+
def parse_flay_results
|
98
|
+
flay = run('bundle exec flay app/**/*.rb lib/**/*.rb')
|
99
|
+
flay_results = {}
|
100
|
+
current = nil
|
101
|
+
flay.lines.each do |flay_line|
|
102
|
+
if m = flay_line.match(/^\d+\)\s+(.+)$/i)
|
103
|
+
current = []
|
104
|
+
flay_results[m.captures[0]] = current
|
105
|
+
elsif current && flay_line =~ /^\s+.+\:\d+\s*$/i
|
106
|
+
current << flay_line.strip!
|
107
|
+
end
|
108
|
+
end
|
109
|
+
flay_results
|
110
|
+
end
|
111
|
+
|
112
|
+
def add_brakeman_error(fields, file)
|
113
|
+
message = "#{fields[2]} #{fields[3]} #{fields[4]} confidence: #{fields[5]}"
|
114
|
+
fail(message, file: file, line: fields[1].to_i)
|
115
|
+
end
|
116
|
+
|
117
|
+
def add_flay_warning(warning, other, file, line)
|
118
|
+
other = other.map { |l| github.html_link(l.gsub(/\:/, '#L')) }
|
119
|
+
message = "#{warning} at:\n " + other.join("\n ")
|
120
|
+
warn(message, file: file.b_path, line: line.line_number.right)
|
121
|
+
end
|
122
|
+
|
123
|
+
DEFAULT_COMMIT_LINT_OPTIONS = {
|
124
|
+
warn: :all,
|
125
|
+
disable: %i[subject_period subject_cap],
|
126
|
+
subject_length: { max: 72 }
|
127
|
+
}.freeze
|
128
|
+
end
|
data/lib/wcc/reek.rb
ADDED
@@ -0,0 +1,56 @@
|
|
1
|
+
# frozen_string_literal: true
|
2
|
+
|
3
|
+
require_relative 'utils'
|
4
|
+
|
5
|
+
class Danger::DangerWCC < Danger::Plugin
|
6
|
+
class Reek
|
7
|
+
include Utils
|
8
|
+
|
9
|
+
def initialize(plugin, options = {})
|
10
|
+
@plugin = plugin
|
11
|
+
@options = options
|
12
|
+
end
|
13
|
+
|
14
|
+
def perform
|
15
|
+
# get the diff of new reek issues
|
16
|
+
diff = run_reek_diff
|
17
|
+
|
18
|
+
# run reek again to get line numbers
|
19
|
+
reek_lines = run 'bundle exec reek --single-line --no-progress --no-color'
|
20
|
+
reek_lines = reek_lines.lines
|
21
|
+
|
22
|
+
each_addition_in_diff(diff) do |line|
|
23
|
+
add_reek_warning(line, reek_lines[line.line_number.right - 1])
|
24
|
+
end
|
25
|
+
end
|
26
|
+
|
27
|
+
private
|
28
|
+
|
29
|
+
def run_reek_diff
|
30
|
+
diff =
|
31
|
+
run_and_diff do
|
32
|
+
write_reek_config
|
33
|
+
run('bundle exec reek --single-line --no-progress '\
|
34
|
+
'--no-color --no-line-numbers')
|
35
|
+
end
|
36
|
+
GitDiff.from_string(diff)
|
37
|
+
end
|
38
|
+
|
39
|
+
def write_reek_config
|
40
|
+
return if File.exist?('.reek')
|
41
|
+
return unless Dir.glob('*.reek').empty?
|
42
|
+
|
43
|
+
default = File.join(File.dirname(__FILE__), 'defaults.reek')
|
44
|
+
FileUtils.cp(default, './')
|
45
|
+
end
|
46
|
+
|
47
|
+
def add_reek_warning(line, with_line_number)
|
48
|
+
return unless warning = line.content.match(/^\+?\s*([^\:]+\:\s+.+)$/i)
|
49
|
+
|
50
|
+
line_info = with_line_number.match(/^\s*([^\:]+)\:(\d+)\:/i)
|
51
|
+
plugin.warn(format_links_as_markdown(warning.captures[0]),
|
52
|
+
file: line_info.captures[0],
|
53
|
+
line: line_info.captures[1].to_i)
|
54
|
+
end
|
55
|
+
end
|
56
|
+
end
|
@@ -0,0 +1,99 @@
|
|
1
|
+
# frozen_string_literal: true
|
2
|
+
|
3
|
+
require_relative 'utils'
|
4
|
+
|
5
|
+
class Danger::DangerWCC < Danger::Plugin
|
6
|
+
class RubocopExceptions
|
7
|
+
include Utils
|
8
|
+
|
9
|
+
DISABLE_REGEX = /^(?:\+\s)?.*\#\s*rubocop\:disable\s+(\S+)/i
|
10
|
+
ENABLE_REGEX = /^(?:\+\s)?\s*\#\s*rubocop\:enable\s+(\S+)/i
|
11
|
+
COMMENT_LINE_REGEX = /^(?:\+\s)?\s*\#\s*(.+)$/i
|
12
|
+
|
13
|
+
def initialize(plugin, options = {})
|
14
|
+
@plugin = plugin
|
15
|
+
@options = options
|
16
|
+
end
|
17
|
+
|
18
|
+
def perform
|
19
|
+
find_new_exceptions.each do |e|
|
20
|
+
message = build_message(e)
|
21
|
+
severity = message_severity(e)
|
22
|
+
issue(message,
|
23
|
+
severity: severity, file: e[:file], line: e[:disabled_at])
|
24
|
+
end
|
25
|
+
end
|
26
|
+
|
27
|
+
private
|
28
|
+
|
29
|
+
def build_message(e)
|
30
|
+
message = "Rubocop rule #{e[:rule]} disabled in"\
|
31
|
+
" #{plugin.github.html_link(e[:file])}"
|
32
|
+
message +=
|
33
|
+
if !e[:context_lines].empty?
|
34
|
+
" explanation:\n\t> " + e[:context_lines].join("\n\t> ")
|
35
|
+
else
|
36
|
+
" \nPlease provide an explanation why this rule was disabled."
|
37
|
+
end
|
38
|
+
unless e[:reenabled]
|
39
|
+
message += "\n\nThe rule was not reenabled!\n"\
|
40
|
+
'Please add a `rubocop:enable` comment so the rule is disabled '\
|
41
|
+
'for the minimal scope.'
|
42
|
+
end
|
43
|
+
message
|
44
|
+
end
|
45
|
+
|
46
|
+
def message_severity(e)
|
47
|
+
if !e[:reenabled]
|
48
|
+
'fail'
|
49
|
+
elsif e[:context_lines].empty?
|
50
|
+
'warn'
|
51
|
+
else
|
52
|
+
'message'
|
53
|
+
end
|
54
|
+
end
|
55
|
+
|
56
|
+
def find_new_exceptions
|
57
|
+
find_in_diff(DISABLE_REGEX) do |m, line, hunk, file, _diff|
|
58
|
+
make_violation(File.read(file.b_path), hunk, line, m.captures[0])
|
59
|
+
.merge!(file: file.b_path)
|
60
|
+
end
|
61
|
+
end
|
62
|
+
|
63
|
+
def make_violation(file_contents, hunk, line, rule)
|
64
|
+
reenable_line_offset = find_reenable(file_contents,
|
65
|
+
line.line_number.right,
|
66
|
+
rule)
|
67
|
+
|
68
|
+
{
|
69
|
+
rule: rule,
|
70
|
+
disabled_at: line.line_number.right,
|
71
|
+
reenabled: !reenable_line_offset.nil?,
|
72
|
+
context_lines: find_context(hunk.lines.drop(hunk.lines.index(line)))
|
73
|
+
}
|
74
|
+
end
|
75
|
+
|
76
|
+
def find_context(diff_lines)
|
77
|
+
# search for all non-`rubocop:` comment lines immediately
|
78
|
+
# preceding this disable which were added by this hunk
|
79
|
+
diff_lines
|
80
|
+
.drop_while { |l| rubocop_line?(l) }
|
81
|
+
.take_while { |l| l.addition? && l.content =~ COMMENT_LINE_REGEX }
|
82
|
+
.map { |l| l.content.match(COMMENT_LINE_REGEX).captures[0] }
|
83
|
+
end
|
84
|
+
|
85
|
+
def find_reenable(file_contents, line_number, rule)
|
86
|
+
# search for the reenabling of this line
|
87
|
+
length_to_end = file_contents.lines.length - line_number
|
88
|
+
file_contents.lines[line_number, length_to_end]
|
89
|
+
.find_index do |l|
|
90
|
+
m = l.match(ENABLE_REGEX)
|
91
|
+
!m.nil? && rule.casecmp(m.captures[0]) == 0
|
92
|
+
end
|
93
|
+
end
|
94
|
+
|
95
|
+
def rubocop_line?(l)
|
96
|
+
l.content =~ DISABLE_REGEX || l.content =~ ENABLE_REGEX
|
97
|
+
end
|
98
|
+
end
|
99
|
+
end
|
data/lib/wcc/todos.rb
ADDED
@@ -0,0 +1,78 @@
|
|
1
|
+
# frozen_string_literal: true
|
2
|
+
|
3
|
+
require_relative 'utils'
|
4
|
+
|
5
|
+
class Danger::DangerWCC < Danger::Plugin
|
6
|
+
class Todos
|
7
|
+
include Utils
|
8
|
+
|
9
|
+
TODO_REGEX = /^\+.*\#.*TODO:?/i
|
10
|
+
LINK_REGEX = /\#.*(https?\:\/\/\S+)/i
|
11
|
+
|
12
|
+
def initialize(plugin, options = {})
|
13
|
+
@plugin = plugin
|
14
|
+
@options = options
|
15
|
+
end
|
16
|
+
|
17
|
+
def perform
|
18
|
+
find_new_todos.each do |result|
|
19
|
+
issue_message(result)
|
20
|
+
end
|
21
|
+
end
|
22
|
+
|
23
|
+
private
|
24
|
+
|
25
|
+
def issue_message(result)
|
26
|
+
if result[:link]
|
27
|
+
plugin.message "TODO added in #{result[:file_link]} "\
|
28
|
+
"referencing [#{result[:link]}](#{result[:link]})",
|
29
|
+
file: result[:file], line: result[:line]
|
30
|
+
else
|
31
|
+
plugin.warn "TODO added in #{result[:file_link]} - "\
|
32
|
+
'is there a card associated with that?',
|
33
|
+
file: result[:file], line: result[:line]
|
34
|
+
end
|
35
|
+
end
|
36
|
+
|
37
|
+
def find_new_todos
|
38
|
+
find_in_diff(TODO_REGEX) do |_m, line, _hunk, file, _diff|
|
39
|
+
make_violation(file, line)
|
40
|
+
end
|
41
|
+
end
|
42
|
+
|
43
|
+
def make_violation(file, todo_line)
|
44
|
+
link_line_number, link = find_link_in_context(file, todo_line)
|
45
|
+
|
46
|
+
{
|
47
|
+
link: link,
|
48
|
+
file: file.b_path,
|
49
|
+
file_link: @plugin.github.html_link(file.b_path),
|
50
|
+
line: max(link_line_number, todo_line.line_number.right),
|
51
|
+
warn: link.nil?
|
52
|
+
}
|
53
|
+
end
|
54
|
+
|
55
|
+
def grab_context_lines(file, line_number)
|
56
|
+
contents = File.read(file.b_path)
|
57
|
+
# grab the line and the lines immediately before and after it
|
58
|
+
contents.lines[max(line_number - 2, 0)..line_number]
|
59
|
+
end
|
60
|
+
|
61
|
+
def find_link_in_context(file, line)
|
62
|
+
context = grab_context_lines(file, line.line_number.right)
|
63
|
+
context.each_with_index do |l, i|
|
64
|
+
if m = l.match(LINK_REGEX)
|
65
|
+
# line at index 0 in the context array is (line.line_number.right - 1)
|
66
|
+
return [line.line_number.right - 1 + i, m.captures[0]]
|
67
|
+
end
|
68
|
+
end
|
69
|
+
nil
|
70
|
+
end
|
71
|
+
|
72
|
+
def max(a, b)
|
73
|
+
return a if b.nil?
|
74
|
+
return b if a.nil?
|
75
|
+
a > b ? a : b
|
76
|
+
end
|
77
|
+
end
|
78
|
+
end
|