# frozen_string_literal: true require_relative 'utils' class Danger::DangerWCC < Danger::Plugin class RubocopExceptions include Utils DISABLE_REGEX = /^(?:\+\s)?.*\#\s*rubocop\:disable\s+(\S+)/i.freeze ENABLE_REGEX = /^(?:\+\s)?\s*\#\s*rubocop\:enable\s+(\S+)/i.freeze COMMENT_LINE_REGEX = /^(?:\+\s)?\s*\#\s*(.+)$/i.freeze def initialize(plugin, options = {}) @plugin = plugin @options = options end def perform find_new_exceptions.each do |e| message = build_message(e) severity = message_severity(e) issue(message, severity: severity, file: e[:file], line: e[:disabled_at]) end end private def build_message(e) message = "Rubocop rule #{e[:rule]} disabled in"\ " #{plugin.github.html_link(e[:file])}" message += if !e[:context_lines].empty? " explanation:\n\t> " + e[:context_lines].join("\n\t> ") else " \nPlease provide an explanation why this rule was disabled." end unless e[:inline] || e[:reenabled] message += "\n\nThe rule was not reenabled!\n"\ 'Please add a `rubocop:enable` comment so the rule is disabled '\ 'for the minimal scope.' end message end def message_severity(e) if !e[:inline] && !e[:reenabled] 'fail' elsif e[:context_lines].empty? 'warn' else 'message' end end def find_new_exceptions find_in_diff(DISABLE_REGEX) do |m, line, hunk, file, _diff| make_violation(File.read(file.b_path), hunk, line, m.captures[0]) .merge!(file: file.b_path) end end # rubocop:disable Metrics/AbcSize def make_violation(file_contents, hunk, line, rule) is_inline_comment = !/^\s*\+?\s*\#/.match(line.content) reenable_line_offset = find_reenable(file_contents, line.line_number.right, rule) { rule: rule, disabled_at: line.line_number.right, inline: is_inline_comment, reenabled: !reenable_line_offset.nil?, context_lines: find_context(hunk.lines.drop(hunk.lines.index(line))) } end # rubocop:enable Metrics/AbcSize def find_context(diff_lines) # search for all non-`rubocop:` comment lines immediately # preceding this disable which were added by this hunk diff_lines .drop_while { |l| rubocop_line?(l) } .take_while { |l| l.addition? && l.content =~ COMMENT_LINE_REGEX } .map { |l| l.content.match(COMMENT_LINE_REGEX).captures[0] } end def find_reenable(file_contents, line_number, rule) # search for the reenabling of this line length_to_end = file_contents.lines.length - line_number file_contents.lines[line_number, length_to_end] .find_index do |l| m = l.match(ENABLE_REGEX) !m.nil? && rule.casecmp(m.captures[0]) == 0 end end def rubocop_line?(l) l.content =~ DISABLE_REGEX || l.content =~ ENABLE_REGEX end end end