rubocop-hq/rubocop

View on GitHub
lib/rubocop/cop/style/word_array.rb

Summary

Maintainability
A
1 hr
Test Coverage
A
100%
# frozen_string_literal: true

module RuboCop
  module Cop
    module Style
      # Checks for array literals made up of word-like
      # strings, that are not using the %w() syntax.
      #
      # Alternatively, it can check for uses of the %w() syntax, in projects
      # which do not want to include that syntax.
      #
      # NOTE: When using the `percent` style, %w() arrays containing a space
      # will be registered as offenses.
      #
      # Configuration option: MinSize
      # If set, arrays with fewer elements than this value will not trigger the
      # cop. For example, a `MinSize` of `3` will not enforce a style on an
      # array of 2 or fewer elements.
      #
      # @example EnforcedStyle: percent (default)
      #   # good
      #   %w[foo bar baz]
      #
      #   # bad
      #   ['foo', 'bar', 'baz']
      #
      #   # bad (contains spaces)
      #   %w[foo\ bar baz\ quux]
      #
      #   # bad
      #   [
      #     ['one', 'One'],
      #     ['two', 'Two']
      #   ]
      #
      #   # good
      #   [
      #     %w[one One],
      #     %w[two Two]
      #   ]
      #
      #   # good (2d array containing spaces)
      #   [
      #     ['one', 'One'],
      #     ['two', 'Two'],
      #     ['forty two', 'Forty Two']
      #   ]
      #
      # @example EnforcedStyle: brackets
      #   # good
      #   ['foo', 'bar', 'baz']
      #
      #   # bad
      #   %w[foo bar baz]
      #
      #   # good (contains spaces)
      #   ['foo bar', 'baz quux']
      #
      #   # good
      #   [
      #     ['one', 'One'],
      #     ['two', 'Two']
      #   ]
      #
      #   # bad
      #   [
      #     %w[one One],
      #     %w[two Two]
      #   ]
      #
      class WordArray < Base
        include ArrayMinSize
        include ArraySyntax
        include ConfigurableEnforcedStyle
        include PercentArray
        extend AutoCorrector

        PERCENT_MSG = 'Use `%w` or `%W` for an array of words.'
        ARRAY_MSG = 'Use %<prefer>s for an array of words.'

        class << self
          attr_accessor :largest_brackets
        end

        def on_new_investigation
          super

          # Prevent O(n2) checks (checking the entire matrix once for each child array) by caching
          @matrix_of_complex_content_cache = Hash.new do |cache, node|
            cache[node] = matrix_of_complex_content?(node)
          end
        end

        def on_array(node)
          if bracketed_array_of?(:str, node)
            return if complex_content?(node.values)
            return if within_matrix_of_complex_content?(node)

            check_bracketed_array(node, 'w')
          elsif node.percent_literal?(:string)
            check_percent_array(node)
          end
        end

        private

        def within_matrix_of_complex_content?(node)
          return false unless (parent = node.parent)

          parent.array_type? && @matrix_of_complex_content_cache[parent]
        end

        def matrix_of_complex_content?(array)
          array.values.all?(&:array_type?) &&
            array.values.any? { |subarray| complex_content?(subarray.values) }
        end

        def complex_content?(strings, complex_regex: word_regex)
          strings.any? do |s|
            next unless s.str_content

            string = s.str_content.dup.force_encoding(::Encoding::UTF_8)
            !string.valid_encoding? ||
              (complex_regex && !complex_regex.match?(string)) ||
              string.include?(' ')
          end
        end

        def invalid_percent_array_contents?(node)
          # Disallow %w() arrays that contain invalid encoding or spaces
          complex_content?(node.values, complex_regex: false)
        end

        def word_regex
          Regexp.new(cop_config['WordRegex'])
        end

        def build_bracketed_array(node)
          return '[]' if node.children.empty?

          words = node.children.map do |word|
            if word.dstr_type?
              string_literal = to_string_literal(word.source)

              trim_string_interpolation_escape_character(string_literal)
            else
              to_string_literal(word.children[0])
            end
          end
          build_bracketed_array_with_appropriate_whitespace(elements: words, node: node)
        end
      end
    end
  end
end