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 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
- REGEXP_METHODS
- REPLACEMENTS
- RESTRICT_ON_SEND
Public Instance Methods
on_send(node)
click to toggle source
# File lib/rubocop/cop/style/select_by_regexp.rb, line 86 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) regexp = find_regexp(regexp_method_send_node, block_node) register_offense(node, block_node, regexp) end
Private Instance Methods
extract_send_node(block_node)
click to toggle source
# File lib/rubocop/cop/style/select_by_regexp.rb, line 118 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 127 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.arguments.first.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 138 def match_predicate_without_receiver?(node) node.send_type? && node.method?(:match?) && node.receiver.nil? end
receiver_allowed?(node)
click to toggle source
# File lib/rubocop/cop/style/select_by_regexp.rb, line 99 def receiver_allowed?(node) return false unless node node.hash_type? || creates_hash?(node) || env_const?(node) end
register_offense(node, block_node, regexp)
click to toggle source
# File lib/rubocop/cop/style/select_by_regexp.rb, line 105 def register_offense(node, block_node, regexp) replacement = REPLACEMENTS[node.method_name.to_sym] 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