# frozen_string_literal: true module RuboCop # This class wraps the `Parser::Source::Comment` object that represents a # special `rubocop:disable` and `rubocop:enable` comment and exposes what # cops it contains. class DirectiveComment # @api private REDUNDANT_DIRECTIVE_COP_DEPARTMENT = 'Lint' # @api private REDUNDANT_DIRECTIVE_COP = "#{REDUNDANT_DIRECTIVE_COP_DEPARTMENT}/RedundantCopDisableDirective" # @api private COP_NAME_PATTERN = '([A-Z]\w+/)*(?:[A-Z]\w+)' # @api private COP_NAMES_PATTERN = "(?:#{COP_NAME_PATTERN} , )*#{COP_NAME_PATTERN}" # @api private COPS_PATTERN = "(all|#{COP_NAMES_PATTERN})" # @api private DIRECTIVE_COMMENT_REGEXP = Regexp.new( "# rubocop : ((?:disable|enable|todo))\\b #{COPS_PATTERN}" .gsub(' ', '\s*') ) def self.before_comment(line) line.split(DIRECTIVE_COMMENT_REGEXP).first end attr_reader :comment, :cop_registry, :mode, :cops def initialize(comment, cop_registry = Cop::Registry.global) @comment = comment @cop_registry = cop_registry @mode, @cops = match_captures end # Checks if this directive relates to single line def single_line? !comment.text.start_with?(DIRECTIVE_COMMENT_REGEXP) end # Checks if this directive contains all the given cop names def match?(cop_names) parsed_cop_names.uniq.sort == cop_names.uniq.sort end def range match = comment.text.match(DIRECTIVE_COMMENT_REGEXP) begin_pos = comment.source_range.begin_pos Parser::Source::Range.new( comment.source_range.source_buffer, begin_pos + match.begin(0), begin_pos + match.end(0) ) end # Returns match captures to directive comment pattern def match_captures @match_captures ||= comment.text.match(DIRECTIVE_COMMENT_REGEXP)&.captures end # Checks if this directive disables cops def disabled? %w[disable todo].include?(mode) end # Checks if this directive enables cops def enabled? mode == 'enable' end # Checks if this directive enables all cops def enabled_all? !disabled? && all_cops? end # Checks if this directive disables all cops def disabled_all? disabled? && all_cops? end # Checks if all cops specified in this directive def all_cops? cops == 'all' end # Returns array of specified in this directive cop names def cop_names @cop_names ||= all_cops? ? all_cop_names : parsed_cop_names end # Returns array of specified in this directive department names # when all department disabled def department_names splitted_cops_string.select { |cop| department?(cop) } end # Checks if directive departments include cop def in_directive_department?(cop) department_names.any? { |department| cop.start_with?(department) } end # Checks if cop department has already used in directive comment def overridden_by_department?(cop) in_directive_department?(cop) && splitted_cops_string.include?(cop) end def directive_count splitted_cops_string.count end # Returns line number for directive def line_number comment.source_range.line end private def splitted_cops_string (cops || '').split(/,\s*/) end def parsed_cop_names splitted_cops_string.map do |name| department?(name) ? cop_names_for_department(name) : name end.flatten end def department?(name) cop_registry.department?(name) end def all_cop_names exclude_redundant_directive_cop(cop_registry.names) end def cop_names_for_department(department) names = cop_registry.names_for_department(department) has_redundant_directive_cop = department == REDUNDANT_DIRECTIVE_COP_DEPARTMENT has_redundant_directive_cop ? exclude_redundant_directive_cop(names) : names end def exclude_redundant_directive_cop(cops) cops - [REDUNDANT_DIRECTIVE_COP] end end end