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 a 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

Examples:

# 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 }

# bad (negative form)
array.reject { |x| !x.match? /regexp/ }

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

Constant Summary collapse

MSG =
'Prefer `%<replacement>s` to `%<original_method>s` with a regexp match.'
RESTRICT_ON_SEND =
i[select filter find_all reject].freeze
SELECT_METHODS =
i[select filter find_all].freeze
REGEXP_METHODS =
i[match? =~].to_set.freeze
REGEXP_METHODS_NEGATED =
i[!~].to_set.freeze

Constants included from RangeHelp

RangeHelp::BYTE_ORDER_MARK, RangeHelp::NOT_GIVEN

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, #always_autocorrect?, autocorrect_incompatible_with, badge, #begin_investigation, #callbacks_needed, callbacks_needed, #config_to_allow_offenses, #config_to_allow_offenses=, #contextual_autocorrect?, #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, #parser_engine, #ready, #relevant_file?, requires_gem, #string_literals_frozen_by_default?, support_autocorrect?, support_multiple_source?, #target_gem_version, #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



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

def_node_matcher :calls_lvar?, "{\n  (send (lvar %1) ...)\n  (send ... (lvar %1))\n  (match-with-lvasgn regexp (lvar %1))\n  (send (send (lvar %1) ...) :!)\n  (send (send ... (lvar %1)) :!)\n  (send (match-with-lvasgn regexp (lvar %1)) :!)\n  (send (begin (send (lvar %1) ...)) :!)\n  (send (begin (send ... (lvar %1))) :!)\n  (send (begin (match-with-lvasgn regexp (lvar %1))) :!)\n}\n"

#creates_hash?(node) ⇒ Object

Returns true if a node appears to return a hash



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

def_node_matcher :creates_hash?, "{\n  (call (const _ :Hash) {:new :[]} ...)\n  (block (call (const _ :Hash) :new ...) ...)\n  (call _ { :to_h :to_hash } ...)\n}\n"

#env_const?(node) ⇒ Object



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

def_node_matcher :env_const?, "(const {nil? cbase} :ENV)\n"

#on_send(node) ⇒ Object Also known as: on_csend

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



101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
# File 'lib/rubocop/cop/style/select_by_regexp.rb', line 101

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.include?('grep_v')

  regexp = find_regexp(regexp_method_send_node, block_node)

  register_offense(node, block_node, regexp, replacement)
end

#regexp_match?(node) ⇒ Object



59
60
61
62
63
64
65
66
67
68
# File 'lib/rubocop/cop/style/select_by_regexp.rb', line 59

def_node_matcher :regexp_match?, "{\n  (block call (args (arg $_)) ${(send _ %REGEXP_METHODS _) (send _ %REGEXP_METHODS_NEGATED _) match-with-lvasgn})\n  (block call (args (arg $_)) ${(send (send _ %REGEXP_METHODS _) :!) (send (begin (send _ %REGEXP_METHODS _)) :!) (send match-with-lvasgn :!) (send (begin match-with-lvasgn) :!)})\n  (numblock call $1 ${(send _ %REGEXP_METHODS _) (send _ %REGEXP_METHODS_NEGATED _) match-with-lvasgn})\n  (numblock call $1 ${(send (send _ %REGEXP_METHODS _) :!) (send (begin (send _ %REGEXP_METHODS _)) :!) (send match-with-lvasgn :!) (send (begin match-with-lvasgn) :!)})\n  (itblock call $_ ${(send _ %REGEXP_METHODS _) (send _ %REGEXP_METHODS_NEGATED _) match-with-lvasgn})\n  (itblock call $_ ${(send (send _ %REGEXP_METHODS _) :!) (send (begin (send _ %REGEXP_METHODS _)) :!) (send match-with-lvasgn :!) (send (begin match-with-lvasgn) :!)})\n}\n"