lib/rubocop/cop/style/regexp_literal.rb



# encoding: utf-8

module RuboCop
  module Cop
    module Style
      # This cop checks for regexp literals and reports offenses based
      # on how many escaped slashes there are in the regexp and on the
      # value of the configuration parameter MaxSlashes.
      class RegexpLiteral < Cop
        class << self
          attr_accessor :slash_count
        end

        def on_regexp(node)
          string_parts = node.children.select { |child| child.type == :str }
          total_string = string_parts.map { |s| s.loc.expression.source }.join
          slashes = total_string.count('/')
          delimiter_start = node.loc.begin.source[0]

          if delimiter_start == '/'
            if slashes > max_slashes
              add_offense(node, :expression, error_message(''))
            end
          elsif slashes <= max_slashes
            add_offense(node, :expression, error_message('only '))
          end

          configure_max(delimiter_start, slashes) if @options[:auto_gen_config]
        end

        private

        def max_slashes
          m = cop_config['MaxSlashes']
          unless m.is_a?(Fixnum) && m >= 0
            fail "Illegal value for MaxSlashes: #{m}"
          end
          m
        end

        def configure_max(delimiter_start, value)
          self.class.slash_count ||= {
            '/' => Set.new([0]),
            '%' => Set.new([100_000])
          }

          self.class.slash_count[delimiter_start].add(value)

          # To avoid reports, MaxSlashes must be set equal to the highest
          # number of slashes used within //, and also one less than the
          # highest number of slashes used within %r{}. If no value can satisfy
          # both requirements, just disable.
          max = self.class.slash_count['/'].max
          min = self.class.slash_count['%'].min

          self.config_to_allow_offenses = calculate_config(max, min)
        end

        def calculate_config(max, min)
          if max > max_slashes
            if max < min
              { 'MaxSlashes' => max }
            else
              { 'Enabled' => false }
            end
          elsif min < max_slashes + 1
            if max < min
              { 'MaxSlashes' => min - 1 }
            else
              { 'Enabled' => false }
            end
          end
        end

        def error_message(word)
          format('Use %%r %sfor regular expressions matching more ' \
                 "than %d '/' character%s.",
                 word,
                 max_slashes,
                 max_slashes == 1 ? '' : 's')
        end
      end
    end
  end
end