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.
Files changed (73) hide show
  1. checksums.yaml +7 -0
  2. data/.circleci/config.yml +46 -0
  3. data/.gitignore +6 -0
  4. data/.rubocop.yml +219 -0
  5. data/.ruby-version +1 -0
  6. data/.travis.yml +12 -0
  7. data/Dangerfile +5 -0
  8. data/Gemfile +6 -0
  9. data/Guardfile +35 -0
  10. data/LICENSE +201 -0
  11. data/README.md +2 -0
  12. data/Rakefile +25 -0
  13. data/danger-wcc.gemspec +58 -0
  14. data/lib/danger_plugin.rb +3 -0
  15. data/lib/version.rb +5 -0
  16. data/lib/wcc/commit_lint.rb +158 -0
  17. data/lib/wcc/commit_lint/README.md +3 -0
  18. data/lib/wcc/commit_lint/commit_check.rb +19 -0
  19. data/lib/wcc/commit_lint/empty_line_check.rb +22 -0
  20. data/lib/wcc/commit_lint/subject_cap_check.rb +22 -0
  21. data/lib/wcc/commit_lint/subject_length_check.rb +28 -0
  22. data/lib/wcc/commit_lint/subject_period_check.rb +22 -0
  23. data/lib/wcc/commit_lint/subject_words_check.rb +22 -0
  24. data/lib/wcc/default.jshintrc +5 -0
  25. data/lib/wcc/defaults.reek +131 -0
  26. data/lib/wcc/github.rb +24 -0
  27. data/lib/wcc/jshint.rb +63 -0
  28. data/lib/wcc/plugin.rb +128 -0
  29. data/lib/wcc/reek.rb +56 -0
  30. data/lib/wcc/rubocop_exceptions.rb +99 -0
  31. data/lib/wcc/todos.rb +78 -0
  32. data/lib/wcc/utils.rb +136 -0
  33. data/spec/fixtures/brakeman/a.tmp +13 -0
  34. data/spec/fixtures/brakeman/b.tmp +14 -0
  35. data/spec/fixtures/brakeman/brakeman.diff +20 -0
  36. data/spec/fixtures/brakeman/brakeman.out +14 -0
  37. data/spec/fixtures/exception_context.diff +15 -0
  38. data/spec/fixtures/exception_insert_context.diff +14 -0
  39. data/spec/fixtures/exception_misspelled.diff +14 -0
  40. data/spec/fixtures/exception_multiline_context.diff +20 -0
  41. data/spec/fixtures/exception_reenabled.diff +13 -0
  42. data/spec/fixtures/find_in_diff.rb +21 -0
  43. data/spec/fixtures/find_in_diff_2_chunks.diff +24 -0
  44. data/spec/fixtures/flay.diff +17 -0
  45. data/spec/fixtures/flay.txt +18 -0
  46. data/spec/fixtures/github/labels.json +72 -0
  47. data/spec/fixtures/github_pr.json +325 -0
  48. data/spec/fixtures/jshint/a.tmp +5 -0
  49. data/spec/fixtures/jshint/b.tmp +7 -0
  50. data/spec/fixtures/jshint/jshint.diff +13 -0
  51. data/spec/fixtures/jshint/out.jshint +7 -0
  52. data/spec/fixtures/no_exception.diff +10 -0
  53. data/spec/fixtures/no_todo.diff +13 -0
  54. data/spec/fixtures/reek/line_numbers.reek +121 -0
  55. data/spec/fixtures/reek/reek.diff +50 -0
  56. data/spec/fixtures/rubocop_exception.rb +39 -0
  57. data/spec/fixtures/todo.rb +21 -0
  58. data/spec/fixtures/todo_link_next_line.diff +14 -0
  59. data/spec/fixtures/todo_link_same_line.diff +13 -0
  60. data/spec/fixtures/todo_no_link.diff +13 -0
  61. data/spec/fixtures/todo_removed.diff +13 -0
  62. data/spec/fixtures_helper.rb +19 -0
  63. data/spec/spec_helper.rb +73 -0
  64. data/spec/wcc/commit_lint_spec.rb +392 -0
  65. data/spec/wcc/github_spec.rb +67 -0
  66. data/spec/wcc/jshint_spec.rb +68 -0
  67. data/spec/wcc/plugin_spec.rb +134 -0
  68. data/spec/wcc/reek_spec.rb +71 -0
  69. data/spec/wcc/rubocop_exceptions_spec.rb +136 -0
  70. data/spec/wcc/todos_spec.rb +96 -0
  71. data/spec/wcc/utils_spec.rb +134 -0
  72. data/spec/wcc_spec.rb +21 -0
  73. metadata +393 -0
@@ -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
@@ -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
@@ -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
@@ -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
@@ -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