lib/rubocop/cop/style/symbol_array.rb



# frozen_string_literal: true

module RuboCop
  module Cop
    module Style
      # This cop can check for array literals made up of symbols that are not
      # using the %i() syntax.
      #
      # Alternatively, it checks for symbol arrays using the %i() syntax on
      # projects which do not want to use that syntax, perhaps because they
      # support a version of Ruby lower than 2.0.
      class SymbolArray < Cop
        include ConfigurableEnforcedStyle
        include ArraySyntax

        PERCENT_MSG = 'Use `%i` or `%I` for an array of symbols.'.freeze
        ARRAY_MSG = 'Use `[]` for an array of symbols.'.freeze

        def on_array(node)
          if bracketed_array_of?(:sym, node)
            check_bracketed_array(node)
          elsif percent_array?(node)
            check_percent_array(node)
          end
        end

        def validate_config
          return unless style == :percent && target_ruby_version < 2.0

          raise ValidationError, 'The default `percent` style for the ' \
                                '`Style/SymbolArray` cop is only compatible' \
                                ' with Ruby 2.0 and up, but the target Ruby' \
                                " version for your project is 1.9.\nPlease " \
                                'either disable this cop, configure it to ' \
                                'use `array` style, or adjust the ' \
                                '`TargetRubyVersion` parameter in your ' \
                                'configuration.'
        end

        private

        def percent_array?(node)
          node.loc.begin && node.loc.begin.source =~ /\A%[iI]/
        end

        def check_bracketed_array(node)
          return if comments_in_array?(node)
          return if symbols_contain_spaces?(node)

          style_detected(:brackets)
          add_offense(node, :expression, PERCENT_MSG) if style == :percent
        end

        def check_percent_array(node)
          style_detected(:percent)
          add_offense(node, :expression, ARRAY_MSG) if style == :brackets
        end

        def comments_in_array?(node)
          comments = processed_source.comments
          array_range = node.source_range.to_a

          comments.any? do |comment|
            !(comment.loc.expression.to_a & array_range).empty?
          end
        end

        def symbols_contain_spaces?(node)
          node.children.any? do |sym|
            content, = *sym
            content =~ / /
          end
        end

        def autocorrect(node)
          syms = node.children.map { |c| c.children[0].to_s }
          corrected = if style == :percent
                        percent_replacement(syms)
                      else
                        bracket_replacement(syms)
                      end

          lambda do |corrector|
            corrector.replace(node.source_range, corrected)
          end
        end

        def percent_replacement(syms)
          escape = syms.any? { |s| needs_escaping?(s) }
          syms = syms.map { |s| escape_string(s) } if escape
          syms = syms.map { |s| s.gsub(/\)/, '\\)') }
          if escape
            "%I(#{syms.join(' ')})"
          else
            "%i(#{syms.join(' ')})"
          end
        end

        def bracket_replacement(syms)
          syms = syms.map { |s| to_symbol_literal(s) }
          "[#{syms.join(', ')}]"
        end
      end
    end
  end
end