123456789_123456789_123456789_123456789_123456789_

Module: RuboCop::Cop::HashSubset

Relationships & Source Files
Extension / Inclusion / Inheritance Descendants
Included In:
Super Chains via Extension / Inclusion / Inheritance
Class Chain:
self, Macros
Instance Chain:
self, RangeHelp
Defined in: lib/rubocop/cop/mixin/hash_subset.rb

Overview

Common functionality for Style/HashExcept and Style/HashSlice cops. It registers an offense on methods with blocks that are equivalent to Hash#except or Hash#slice.

Constant Summary

RangeHelp - Included

BYTE_ORDER_MARK, NOT_GIVEN

Instance Method Summary

Instance Method Details

#block_with_first_arg_check?(node)

[ GitHub ]

  
# File 'lib/rubocop/cop/mixin/hash_subset.rb', line 20

def_node_matcher :block_with_first_arg_check?, <<~PATTERN
  (block
    (call _ _)
    (args
      $(arg _key)
      (arg _))
    {
      $(send
        {(lvar _key) $_ _ | _ $_ (lvar _key)})
      (send
        $(send
          {(lvar _key) $_ _ | _ $_ (lvar _key)}) :!)
      })
PATTERN

#decorate_source(value) (private)

[ GitHub ]

  
# File 'lib/rubocop/cop/mixin/hash_subset.rb', line 149

def decorate_source(value)
  return ":\"#{value.source}\"" if value.dsym_type?
  return "\"#{value.source}\"" if value.dstr_type?
  return ":#{value.source}" if value.sym_type?

  "'#{value.source}'"
end

#except_key(node) (private)

[ GitHub ]

  
# File 'lib/rubocop/cop/mixin/hash_subset.rb', line 157

def except_key(node)
  key_arg = node.argument_list.first.source
  body, = extract_body_if_negated(node.body)
  lhs, _method_name, rhs = *body

  lhs.source == key_arg ? rhs : lhs
end

#except_key_source(key) (private)

[ GitHub ]

  
# File 'lib/rubocop/cop/mixin/hash_subset.rb', line 136

def except_key_source(key)
  if key.array_type?
    key = if key.percent_literal?
            key.each_value.map { |v| decorate_source(v) }
          else
            key.each_value.map(&:source)
          end
    return key.join(', ')
  end

  key.literal? ? key.source : "*#{key.source}"
end

#extract_body_if_negated(body) (private)

[ GitHub ]

  
# File 'lib/rubocop/cop/mixin/hash_subset.rb', line 128

def extract_body_if_negated(body)
  if body.method?('!')
    [body.receiver, true]
  else
    [body, false]
  end
end

#extract_offense(node) (private)

[ GitHub ]

  
# File 'lib/rubocop/cop/mixin/hash_subset.rb', line 58

def extract_offense(node)
  block = node.parent
  return unless extracts_hash_subset?(block)

  except_key = except_key(block)
  return if except_key.nil? || !safe_to_register_offense?(block, except_key)

  [offense_range(node), except_key_source(except_key)]
end

#extracts_hash_subset?(block) ⇒ Boolean (private)

[ GitHub ]

  
# File 'lib/rubocop/cop/mixin/hash_subset.rb', line 68

def extracts_hash_subset?(block)
  block_with_first_arg_check?(block) do |key_arg, send_node, method|
    return false unless supported_subset_method?(method)

    case method
    when :include?, :exclude?
      send_node.first_argument.source == key_arg.source
    when :in?
      send_node.receiver.source == key_arg.source
    else
      true
    end
  end
end

#included?(body, negated) ⇒ Boolean (private)

[ GitHub ]

  
# File 'lib/rubocop/cop/mixin/hash_subset.rb', line 106

def included?(body, negated)
  if negated
    body.method?('exclude?')
  else
    body.method?('include?') || body.method?('in?')
  end
end

#not_included?(body, negated) ⇒ Boolean (private)

[ GitHub ]

  
# File 'lib/rubocop/cop/mixin/hash_subset.rb', line 114

def not_included?(body, negated)
  included?(body, !negated)
end

#offense_range(node) (private)

[ GitHub ]

  
# File 'lib/rubocop/cop/mixin/hash_subset.rb', line 165

def offense_range(node)
  range_between(node.loc.selector.begin_pos, node.parent.loc.end.end_pos)
end

#on_csend(node)

Alias for #on_send.

[ GitHub ]

  
# File 'lib/rubocop/cop/mixin/hash_subset.rb', line 46

alias on_csend on_send

#on_send(node) Also known as: #on_csend

[ GitHub ]

  
# File 'lib/rubocop/cop/mixin/hash_subset.rb', line 35

def on_send(node)
  offense_range, key_source = extract_offense(node)

  return unless offense_range
  return unless semantically_subset_method?(node)

  preferred_method = "#{preferred_method_name}(#{key_source})"
  add_offense(offense_range, message: format(MSG, prefer: preferred_method)) do |corrector|
    corrector.replace(offense_range, preferred_method)
  end
end

#preferred_method_name (private)

Raises:

  • (NotImplementedError)
[ GitHub ]

  
# File 'lib/rubocop/cop/mixin/hash_subset.rb', line 54

def preferred_method_name
  raise NotImplementedError
end

#safe_to_register_offense?(block, except_key) ⇒ Boolean (private)

[ GitHub ]

  
# File 'lib/rubocop/cop/mixin/hash_subset.rb', line 118

def safe_to_register_offense?(block, except_key)
  body = block.body

  if body.method?('==') || body.method?('!=')
    except_key.sym_type? || except_key.str_type?
  else
    true
  end
end

#semantically_except_method?(node) ⇒ Boolean (private)

[ GitHub ]

  
# File 'lib/rubocop/cop/mixin/hash_subset.rb', line 91

def semantically_except_method?(node)
  block = node.parent
  body, negated = extract_body_if_negated(block.body)

  if node.method?('reject')
    body.method?('==') || body.method?('eql?') || included?(body, negated)
  else
    body.method?('!=') || not_included?(body, negated)
  end
end

#semantically_slice_method?(node) ⇒ Boolean (private)

[ GitHub ]

  
# File 'lib/rubocop/cop/mixin/hash_subset.rb', line 102

def semantically_slice_method?(node)
  !semantically_except_method?(node)
end

#semantically_subset_method?(node) ⇒ Boolean (private)

Raises:

  • (NotImplementedError)
[ GitHub ]

  
# File 'lib/rubocop/cop/mixin/hash_subset.rb', line 50

def semantically_subset_method?(node)
  raise NotImplementedError
end

#supported_subset_method?(method) ⇒ Boolean (private)

[ GitHub ]

  
# File 'lib/rubocop/cop/mixin/hash_subset.rb', line 83

def supported_subset_method?(method)
  if active_support_extensions_enabled?
    ACTIVE_SUPPORT_SUBSET_METHODS.include?(method)
  else
    SUBSET_METHODS.include?(method)
  end
end