# encoding: utf-8 module Rubocop # This class parses the special `rubocop:disable` comments in a source # and provides a way to check if each cop is enabled at arbitrary line. class CommentConfig COMMENT_DIRECTIVE_REGEXP = Regexp.new( '\A# rubocop : ((?:dis|en)able)\b ((?:\w+,? )+)'.gsub(' ', '\s*') ) attr_reader :processed_source def initialize(processed_source) @processed_source = processed_source end def cop_enabled_at_line?(cop, line_number) cop = cop.cop_name if cop.respond_to?(:cop_name) disabled_line_ranges = cop_disabled_line_ranges[cop] disabled_line_ranges.none? { |range| range.include?(line_number) } end private def cop_disabled_line_ranges @cop_disabled_line_ranges ||= analyze end def analyze disabled_line_ranges = Hash.new { |hash, key| hash[key] = [] } disablement_start_line_numbers = {} each_mentioned_cop do |cop_name, disabled, line, single_line| if single_line next unless disabled disabled_line_ranges[cop_name] << (line..line) else if disabled disablement_start_line_numbers[cop_name] = line else start_line = disablement_start_line_numbers.delete(cop_name) next unless start_line disabled_line_ranges[cop_name] << (start_line..line) end end end disablement_start_line_numbers.each do |cop_name, start_line| disabled_line_ranges[cop_name] << (start_line..Float::INFINITY) end disabled_line_ranges end def each_mentioned_cop all_cop_names = nil # For performance improvement processed_source.comments.each do |comment| match = comment.text.match(COMMENT_DIRECTIVE_REGEXP) next unless match switch, cops_string = match.captures if cops_string == 'all' all_cop_names ||= Cop::Cop.all.map(&:cop_name) cop_names = all_cop_names else cop_names = cops_string.split(/,\s*/) end disabled = (switch == 'disable') comment_line_number = comment.loc.expression.line single_line = !comment_only_line?(comment_line_number) cop_names.each do |cop_name| yield cop_name, disabled, comment_line_number, single_line end end end def comment_only_line?(line_number) non_comment_token_line_numbers.none? do |non_comment_line_number| non_comment_line_number == line_number end end def non_comment_token_line_numbers @non_comment_token_line_numbers ||= begin non_comment_tokens = processed_source.tokens.reject do |token| token.type == :tCOMMENT end non_comment_tokens.map { |token| token.pos.line }.uniq end end end end