class RuboCop::Cop::Style::SelectByRegexp

Looks for places where an subset of an Enumerable (array, range, set, etc.; see note below) is calculated based on a ‘Regexp` match, and suggests `grep` or `grep_v` instead.

NOTE: Hashes do not behave as you may expect with ‘grep`, which means that `hash.grep` is not equivalent to `hash.select`. Although RuboCop is limited by static analysis, this cop attempts to avoid registering an offense when the receiver is a hash (hash literal, `Hash.new`, `Hash#[]`, or `to_h`/`to_hash`).

NOTE: ‘grep` and `grep_v` were optimized when used without a block in Ruby 3.0, but may be slower in previous versions. See bugs.ruby-lang.org/issues/17030

@safety

Autocorrection is marked as unsafe because `MatchData` will
not be created by `grep`, but may have previously been relied
upon after the `match?` or `=~` call.

Additionally, the cop cannot guarantee that the receiver of
`select` or `reject` is actually an array by static analysis,
so the correction may not be actually equivalent.

@example

# bad (select, filter, or find_all)
array.select { |x| x.match? /regexp/ }
array.select { |x| /regexp/.match?(x) }
array.select { |x| x =~ /regexp/ }
array.select { |x| /regexp/ =~ x }

# bad (reject)
array.reject { |x| x.match? /regexp/ }
array.reject { |x| /regexp/.match?(x) }
array.reject { |x| x =~ /regexp/ }
array.reject { |x| /regexp/ =~ x }

# good
array.grep(regexp)
array.grep_v(regexp)

Constants

MSG
OPPOSITE_REPLACEMENTS
REGEXP_METHODS
REPLACEMENTS
RESTRICT_ON_SEND

Public Instance Methods

on_csend(node)

rubocop:enable Metrics/AbcSize, Metrics/CyclomaticComplexity, Metrics/PerceivedComplexity

Alias for: on_send
on_send(node) click to toggle source

rubocop:disable Metrics/AbcSize, Metrics/CyclomaticComplexity, Metrics/PerceivedComplexity

# File lib/rubocop/cop/style/select_by_regexp.rb, line 90
def on_send(node)
  return if target_ruby_version < 2.6 && node.method?(:filter)
  return unless (block_node = node.block_node)
  return if block_node.body&.begin_type?
  return if receiver_allowed?(block_node.receiver)
  return unless (regexp_method_send_node = extract_send_node(block_node))
  return if match_predicate_without_receiver?(regexp_method_send_node)

  replacement = replacement(regexp_method_send_node, node)
  return if target_ruby_version <= 2.2 && replacement == 'grep_v'

  regexp = find_regexp(regexp_method_send_node, block_node)

  register_offense(node, block_node, regexp, replacement)
end
Also aliased as: on_csend

Private Instance Methods

extract_send_node(block_node) click to toggle source
# File lib/rubocop/cop/style/select_by_regexp.rb, line 136
def extract_send_node(block_node)
  return unless (block_arg_name, regexp_method_send_node = regexp_match?(block_node))

  block_arg_name = :"_#{block_arg_name}" if block_node.numblock_type?
  return unless calls_lvar?(regexp_method_send_node, block_arg_name)

  regexp_method_send_node
end
find_regexp(node, block) click to toggle source
# File lib/rubocop/cop/style/select_by_regexp.rb, line 149
def find_regexp(node, block)
  return node.child_nodes.first if node.match_with_lvasgn_type?

  if node.receiver.lvar_type? &&
     (block.numblock_type? || node.receiver.source == block.first_argument.source)
    node.first_argument
  elsif node.first_argument.lvar_type?
    node.receiver
  end
end
match_predicate_without_receiver?(node) click to toggle source
# File lib/rubocop/cop/style/select_by_regexp.rb, line 160
def match_predicate_without_receiver?(node)
  node.send_type? && node.method?(:match?) && node.receiver.nil?
end
opposite?(regexp_method_send_node) click to toggle source
# File lib/rubocop/cop/style/select_by_regexp.rb, line 145
def opposite?(regexp_method_send_node)
  regexp_method_send_node.send_type? && regexp_method_send_node.method?(:!~)
end
receiver_allowed?(node) click to toggle source
# File lib/rubocop/cop/style/select_by_regexp.rb, line 110
def receiver_allowed?(node)
  return false unless node

  node.hash_type? || creates_hash?(node) || env_const?(node)
end
register_offense(node, block_node, regexp, replacement) click to toggle source
# File lib/rubocop/cop/style/select_by_regexp.rb, line 124
def register_offense(node, block_node, regexp, replacement)
  message = format(MSG, replacement: replacement, original_method: node.method_name)

  add_offense(block_node, message: message) do |corrector|
    # Only correct if it can be determined what the regexp is
    if regexp
      range = range_between(node.loc.selector.begin_pos, block_node.loc.end.end_pos)
      corrector.replace(range, "#{replacement}(#{regexp.source})")
    end
  end
end
replacement(regexp_method_send_node, node) click to toggle source
# File lib/rubocop/cop/style/select_by_regexp.rb, line 116
def replacement(regexp_method_send_node, node)
  opposite = opposite?(regexp_method_send_node)

  method_name = node.method_name

  opposite ? OPPOSITE_REPLACEMENTS[method_name] : REPLACEMENTS[method_name]
end