rubocop-hq/rubocop

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

Summary

Maintainability
A
45 mins
Test Coverage
A
100%
# frozen_string_literal: true

module RuboCop
  module Cop
    module Style
      # Checks for presence or absence of braces around hash literal as a last
      # array item depending on configuration.
      #
      # NOTE: This cop will ignore arrays where all items are hashes, regardless of
      # EnforcedStyle.
      #
      # @example EnforcedStyle: braces (default)
      #   # bad
      #   [1, 2, one: 1, two: 2]
      #
      #   # good
      #   [1, 2, { one: 1, two: 2 }]
      #
      #   # good
      #   [{ one: 1 }, { two: 2 }]
      #
      # @example EnforcedStyle: no_braces
      #   # bad
      #   [1, 2, { one: 1, two: 2 }]
      #
      #   # good
      #   [1, 2, one: 1, two: 2]
      #
      #   # good
      #   [{ one: 1 }, { two: 2 }]
      class HashAsLastArrayItem < Base
        include RangeHelp
        include ConfigurableEnforcedStyle
        extend AutoCorrector

        def on_hash(node)
          return if node.children.first&.kwsplat_type?
          return unless (array = containing_array(node))
          return unless last_array_item?(array, node) && explicit_array?(array)

          if braces_style?
            check_braces(node)
          else
            check_no_braces(node)
          end
        end

        private

        def containing_array(hash_node)
          parent = hash_node.parent
          parent if parent&.array_type?
        end

        def last_array_item?(array, node)
          return false if array.child_nodes.all?(&:hash_type?)

          array.children.last.equal?(node)
        end

        def explicit_array?(array)
          # an implicit array cannot have an "unbraced" hash
          array.square_brackets?
        end

        def check_braces(node)
          return if node.braces?

          add_offense(node, message: 'Wrap hash in `{` and `}`.') do |corrector|
            corrector.wrap(node, '{', '}')
          end
        end

        def check_no_braces(node)
          return unless node.braces?
          return if node.children.empty? # Empty hash cannot be "unbraced"

          add_offense(node, message: 'Omit the braces around the hash.') do |corrector|
            remove_last_element_trailing_comma(corrector, node.parent)
            corrector.remove(node.loc.begin)
            corrector.remove(node.loc.end)
          end
        end

        def braces_style?
          style == :braces
        end

        def remove_last_element_trailing_comma(corrector, node)
          range = range_with_surrounding_space(
            node.children.last.source_range,
            side: :right
          ).end.resize(1)

          corrector.remove(range) if range.source == ','
        end
      end
    end
  end
end