class Danger::DangerRubocop

Run Ruby files through Rubocop. Results are passed out as a table in markdown.

@example Specifying custom config file.

rubocop.lint

@example Lint specific files in a folder, when they change

public_files = (modified_files + added_files).select { |path| path.include?("/public/") }
rubocop.lint public_files

@see Moya/Aeryn @tags ruby, rubocop, linter

Public Instance Methods

lint(config = nil) click to toggle source

Runs Ruby files through Rubocop. Generates a `markdown` list of warnings.

@param [String] files

A globbed string which should return the files that you want to
run through, defaults to nil. If nil, modified and added files
from the diff will be used.

@return [void]

# File lib/danger_plugin.rb, line 29
def lint(config = nil)
  config = config.is_a?(Hash) ? config : { files: config }
  files = config[:files]
  force_exclusion = config[:force_exclusion] || false
  config_path = config[:config]
  report_danger = config[:report_danger] || false
  only_report_new_offenses = config[:only_report_new_offenses] || false
  inline_comment = config[:inline_comment] || false
  fail_on_inline_comment = config[:fail_on_inline_comment] || false
  include_cop_names = config[:include_cop_names] || false
  rubocop_cmd = config[:rubocop_cmd] || 'rubocop'

  files_to_lint = fetch_files_to_lint(files)
  files_to_report = rubocop(files_to_lint, force_exclusion, only_report_new_offenses, cmd: rubocop_cmd, config_path: config_path)

  return if files_to_report.empty?
  return report_failures files_to_report if report_danger

  if inline_comment
    add_violation_for_each_line(files_to_report, fail_on_inline_comment, include_cop_names: include_cop_names)
  else
    markdown offenses_message(files_to_report, include_cop_names: include_cop_names)
  end
end

Private Instance Methods

add_violation_for_each_line(offending_files, fail_on_inline_comment, include_cop_names: false) click to toggle source
# File lib/danger_plugin.rb, line 127
def add_violation_for_each_line(offending_files, fail_on_inline_comment, include_cop_names: false)
  offending_files.flat_map do |file|
    file['offenses'].map do |offense|
      offense_message = offense['message']
      offense_message = offense['cop_name'] + ': ' + offense_message if include_cop_names
      arguments = [
        offense_message,
        {
          file: file['path'],
          line: offense['location']['line']
        }
      ]
      if fail_on_inline_comment
        fail(*arguments)
      else
        warn(*arguments)
      end
    end
  end
end
added_lines(path) click to toggle source
# File lib/danger_plugin.rb, line 81
def added_lines(path)
  diff_for_file = git.diff_for_file(path)
  return [] if diff_for_file.nil?

  diff_for_file
     .patch
     .split("\n@@")
     .tap(&:shift)
     .flat_map do |chunk|
       first_line, *diff = chunk.split("\n")
       # Get start from diff.
       lineno = first_line.match(/\+(\d+),?(\d?)/).captures.first.to_i
       diff.each_with_object([]) do |current_line, added_lines|
         added_lines << lineno if current_line.start_with?('+')
         lineno += 1 unless current_line.start_with?('-')
         added_lines
       end
     end
end
fetch_files_to_lint(files = nil) click to toggle source
# File lib/danger_plugin.rb, line 148
def fetch_files_to_lint(files = nil)
  to_lint = if files.nil?
    # when files are renamed, git.modified_files contains the old name not the new one, so we need to do the convertion
    renaming_map = (git.renamed_files || []).map { |e| [e[:before], e[:after]] }.to_h
    (git.modified_files.map { |f| renaming_map[f] || f }) + git.added_files
  else
    Dir.glob(files)
  end
  Shellwords.join(to_lint)
end
filter_out_offenses(files) click to toggle source
# File lib/danger_plugin.rb, line 72
def filter_out_offenses(files)
  files.each do |file|
    added_lines = added_lines(file['path']).to_set
    file['offenses'].select! do |offense|
      added_lines.include?(offense['location']['line'])
    end
  end
end
offenses_message(offending_files, include_cop_names: false) click to toggle source
# File lib/danger_plugin.rb, line 101
def offenses_message(offending_files, include_cop_names: false)
  require 'terminal-table'

  message = "### Rubocop violations\n\n"
  table = Terminal::Table.new(
    headings: %w(File Line Reason),
    style: { border_i: '|' },
    rows: offending_files.flat_map do |file|
      file['offenses'].map do |offense|
        offense_message = offense['message']
        offense_message = offense['cop_name'] + ': ' + offense_message if include_cop_names
        [file['path'], offense['location']['line'], offense_message]
      end
    end
  ).to_s
  message + table.split("\n")[1..-2].join("\n")
end
report_failures(offending_files) click to toggle source
# File lib/danger_plugin.rb, line 119
def report_failures(offending_files)
  offending_files.each do |file|
    file['offenses'].each do |offense|
      fail "#{file['path']} | #{offense['location']['line']} | #{offense['message']}"
    end
  end
end
rubocop(files_to_lint, force_exclusion, only_report_new_offenses, cmd: 'rubocop', config_path: nil) click to toggle source
# File lib/danger_plugin.rb, line 56
def rubocop(files_to_lint, force_exclusion, only_report_new_offenses, cmd: 'rubocop', config_path: nil)
  base_command = [cmd, '-f', 'json', '--only-recognized-file-types']
  base_command.concat(['--force-exclusion']) if force_exclusion
  base_command.concat(['--config', config_path.shellescape]) unless config_path.nil?

  rubocop_output = `#{'bundle exec ' if File.exist?('Gemfile')}#{base_command.join(' ')} #{files_to_lint}`

  return [] if rubocop_output.empty?

  files = JSON.parse(rubocop_output)['files']

  filter_out_offenses(files) if only_report_new_offenses

  files.select { |f| f['offenses'].any? }
end