bbatsov/rubocop

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

Summary

Maintainability
A
50 mins
Test Coverage
# frozen_string_literal: true

module RuboCop
  module Cop
    module Style
      # Looks for uses of `\_.each_with_object({}) {...}`,
      # `\_.map {...}.to_h`, and `Hash[\_.map {...}]` that are actually just
      # transforming the keys of a hash, and tries to use a simpler & faster
      # call to `transform_keys` instead.
      # It should only be enabled on Ruby version 2.5 or newer.
      # (`transform_keys` was added in Ruby 2.5.)
      #
      # @safety
      #   This cop is unsafe, as it can produce false positives if we are
      #   transforming an enumerable of key-value-like pairs that isn't actually
      #   a hash, e.g.: `[[k1, v1], [k2, v2], ...]`
      #
      # @example
      #   # bad
      #   {a: 1, b: 2}.each_with_object({}) { |(k, v), h| h[foo(k)] = v }
      #   Hash[{a: 1, b: 2}.collect { |k, v| [foo(k), v] }]
      #   {a: 1, b: 2}.map { |k, v| [k.to_s, v] }.to_h
      #   {a: 1, b: 2}.to_h { |k, v| [k.to_s, v] }
      #
      #   # good
      #   {a: 1, b: 2}.transform_keys { |k| foo(k) }
      #   {a: 1, b: 2}.transform_keys { |k| k.to_s }
      class HashTransformKeys < Base
        include HashTransformMethod
        extend AutoCorrector
        extend TargetRubyVersion

        minimum_target_ruby_version 2.5

        # @!method on_bad_each_with_object(node)
        def_node_matcher :on_bad_each_with_object, <<~PATTERN
          (block
            (call !#array_receiver? :each_with_object (hash))
            (args
              (mlhs
                (arg $_)
                (arg _val))
              (arg _memo))
            (call (lvar _memo) :[]= $!`_memo $(lvar _val)))
        PATTERN

        # @!method on_bad_hash_brackets_map(node)
        def_node_matcher :on_bad_hash_brackets_map, <<~PATTERN
          (send
            (const _ :Hash)
            :[]
            (block
              (call !#array_receiver? {:map :collect})
              (args
                (arg $_)
                (arg _val))
              (array $_ $(lvar _val))))
        PATTERN

        # @!method on_bad_map_to_h(node)
        def_node_matcher :on_bad_map_to_h, <<~PATTERN
          (call
            (block
              (call !#array_receiver? {:map :collect})
              (args
                (arg $_)
                (arg _val))
              (array $_ $(lvar _val)))
            :to_h)
        PATTERN

        # @!method on_bad_to_h(node)
        def_node_matcher :on_bad_to_h, <<~PATTERN
          (block
            (call !#array_receiver? :to_h)
            (args
              (arg $_)
              (arg _val))
            (array $_ $(lvar _val)))
        PATTERN

        private

        def extract_captures(match)
          key_argname, key_body_expr, val_body_expr = *match
          Captures.new(key_argname, key_body_expr, val_body_expr)
        end

        def new_method_name
          'transform_keys'
        end
      end
    end
  end
end