class RuboCop::Cop::Lint::OutOfRangeRegexpRef

Looks for references of Regexp captures that are out of range and thus always returns nil.

@safety

This cop is unsafe because it is naive in how it determines what
references are available based on the last encountered regexp, but
it cannot handle some cases, such as conditional regexp matches, which
leads to false positives, such as:

[source,ruby]
----
foo ? /(c)(b)/ =~ str : /(b)/ =~ str
do_something if $2
# $2 is defined for the first condition but not the second, however
# the cop will mark this as an offense.
----

This might be a good indication of code that should be refactored,
however.

@example

/(foo)bar/ =~ 'foobar'

# bad - always returns nil

puts $2 # => nil

# good

puts $1 # => foo

Constants

MSG
REGEXP_ARGUMENT_METHODS
REGEXP_CAPTURE_METHODS
REGEXP_RECEIVER_METHODS
RESTRICT_ON_SEND

Public Instance Methods

after_send(node) click to toggle source
# File lib/rubocop/cop/lint/out_of_range_regexp_ref.rb, line 55
def after_send(node)
  @valid_ref = nil

  if regexp_first_argument?(node)
    check_regexp(node.first_argument)
  elsif regexp_receiver?(node)
    check_regexp(node.receiver)
  end
end
on_in_pattern(node) click to toggle source
# File lib/rubocop/cop/lint/out_of_range_regexp_ref.rb, line 71
def on_in_pattern(node)
  regexp_patterns = regexp_patterns(node)

  @valid_ref = regexp_patterns.filter_map { |pattern| check_regexp(pattern) }.max
end
on_match_with_lvasgn(node) click to toggle source
# File lib/rubocop/cop/lint/out_of_range_regexp_ref.rb, line 51
def on_match_with_lvasgn(node)
  check_regexp(node.children.first)
end
on_new_investigation() click to toggle source
# File lib/rubocop/cop/lint/out_of_range_regexp_ref.rb, line 47
def on_new_investigation
  @valid_ref = 0
end
on_nth_ref(node) click to toggle source
# File lib/rubocop/cop/lint/out_of_range_regexp_ref.rb, line 77
def on_nth_ref(node)
  backref, = *node
  return if @valid_ref.nil? || backref <= @valid_ref

  message = format(
    MSG,
    backref: backref,
    count: @valid_ref.zero? ? 'no' : @valid_ref,
    group: @valid_ref == 1 ? 'group' : 'groups'
  )

  add_offense(node, message: message)
end
on_when(node) click to toggle source
# File lib/rubocop/cop/lint/out_of_range_regexp_ref.rb, line 65
def on_when(node)
  regexp_conditions = node.conditions.select(&:regexp_type?)

  @valid_ref = regexp_conditions.filter_map { |condition| check_regexp(condition) }.max
end

Private Instance Methods

check_regexp(node) click to toggle source
# File lib/rubocop/cop/lint/out_of_range_regexp_ref.rb, line 102
def check_regexp(node)
  return if node.interpolation?

  named_capture = node.each_capture(named: true).count
  @valid_ref = if named_capture.positive?
                 named_capture
               else
                 node.each_capture(named: false).count
               end
end
nth_ref_receiver?(send_node) click to toggle source
# File lib/rubocop/cop/lint/out_of_range_regexp_ref.rb, line 122
def nth_ref_receiver?(send_node)
  send_node.receiver&.nth_ref_type?
end
regexp_first_argument?(send_node) click to toggle source
# File lib/rubocop/cop/lint/out_of_range_regexp_ref.rb, line 113
def regexp_first_argument?(send_node)
  send_node.first_argument&.regexp_type? \
    && REGEXP_ARGUMENT_METHODS.include?(send_node.method_name)
end
regexp_patterns(in_node) click to toggle source
# File lib/rubocop/cop/lint/out_of_range_regexp_ref.rb, line 93
def regexp_patterns(in_node)
  pattern = in_node.pattern
  if pattern.regexp_type?
    [pattern]
  else
    pattern.each_descendant(:regexp).to_a
  end
end
regexp_receiver?(send_node) click to toggle source
# File lib/rubocop/cop/lint/out_of_range_regexp_ref.rb, line 118
def regexp_receiver?(send_node)
  send_node.receiver&.regexp_type?
end