Class: RuboCop::Cop::Style::SelectByRegexp

Inherits:
Base
  • Object
show all
Extended by:
AutoCorrector
Includes:
RangeHelp
Defined in:
lib/rubocop/cop/style/select_by_regexp.rb

Overview

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 https://bugs.ruby-lang.org/issues/17030

Examples:

# bad (select 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)

Cop Safety Information:

  • 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.

Constant Summary collapse

MSG =
'Prefer `%<replacement>s` to `%<original_method>s` with a regexp match.'
RESTRICT_ON_SEND =
%i[select find_all reject].freeze
REPLACEMENTS =
{ select: 'grep', find_all: 'grep', reject: 'grep_v' }.freeze
OPPOSITE_REPLACEMENTS =
{ select: 'grep_v', find_all: 'grep_v', reject: 'grep' }.freeze
REGEXP_METHODS =
%i[match? =~ !~].to_set.freeze

Instance Attribute Summary

Attributes inherited from Base

#config, #processed_source

Instance Method Summary collapse

Methods included from AutoCorrector

support_autocorrect?

Methods inherited from Base

#active_support_extensions_enabled?, #add_global_offense, #add_offense, autocorrect_incompatible_with, badge, #begin_investigation, callbacks_needed, #callbacks_needed, #config_to_allow_offenses, #config_to_allow_offenses=, #cop_config, cop_name, #cop_name, department, documentation_url, exclude_from_registry, #excluded_file?, #external_dependency_checksum, inherited, #initialize, #inspect, joining_forces, lint?, match?, #message, #offenses, #on_investigation_end, #on_new_investigation, #on_other_file, #parse, #ready, #relevant_file?, support_autocorrect?, support_multiple_source?, #target_rails_version, #target_ruby_version

Methods included from ExcludeLimit

#exclude_limit

Methods included from AutocorrectLogic

#autocorrect?, #autocorrect_enabled?, #autocorrect_requested?, #autocorrect_with_disable_uncorrectable?, #correctable?, #disable_uncorrectable?, #safe_autocorrect?

Methods included from IgnoredNode

#ignore_node, #ignored_node?, #part_of_ignored_node?

Methods included from Util

silence_warnings

Constructor Details

This class inherits a constructor from RuboCop::Cop::Base

Instance Method Details

#calls_lvar?(node, name) ⇒ Object



79
80
81
82
83
84
85
# File 'lib/rubocop/cop/style/select_by_regexp.rb', line 79

def_node_matcher :calls_lvar?, <<~PATTERN
  {
    (send (lvar %1) ...)
    (send ... (lvar %1))
    (match-with-lvasgn regexp (lvar %1))
  }
PATTERN

#creates_hash?(node) ⇒ Object

Returns true if a node appears to return a hash



65
66
67
68
69
70
71
# File 'lib/rubocop/cop/style/select_by_regexp.rb', line 65

def_node_matcher :creates_hash?, <<~PATTERN
  {
    (send (const _ :Hash) {:new :[]} ...)
    (block (send (const _ :Hash) :new ...) ...)
    (send _ { :to_h :to_hash } ...)
  }
PATTERN

#env_const?(node) ⇒ Object



74
75
76
# File 'lib/rubocop/cop/style/select_by_regexp.rb', line 74

def_node_matcher :env_const?, <<~PATTERN
  (const {nil? cbase} :ENV)
PATTERN

#on_send(node) ⇒ Object

rubocop:disable Metrics/CyclomaticComplexity, Metrics/PerceivedComplexity



88
89
90
91
92
93
94
95
96
97
98
99
100
101
# File 'lib/rubocop/cop/style/select_by_regexp.rb', line 88

def on_send(node)
  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

#regexp_match?(node) ⇒ Object



56
57
58
59
60
61
# File 'lib/rubocop/cop/style/select_by_regexp.rb', line 56

def_node_matcher :regexp_match?, <<~PATTERN
  {
    (block send (args (arg $_)) ${(send _ %REGEXP_METHODS _) match-with-lvasgn})
    (numblock send $1 ${(send _ %REGEXP_METHODS _) match-with-lvasgn})
  }
PATTERN