class RuboCop::Cop::Performance::Sample

This cop is used to identify usages of `shuffle.first`, `shuffle.last` and `shuffle[]` and change them to use `sample` instead.

@example

# bad
[1, 2, 3].shuffle.first
[1, 2, 3].shuffle.first(2)
[1, 2, 3].shuffle.last
[1, 2, 3].shuffle[2]
[1, 2, 3].shuffle[0, 2]    # sample(2) will do the same
[1, 2, 3].shuffle[0..2]    # sample(3) will do the same
[1, 2, 3].shuffle(random: Random.new).first

# good
[1, 2, 3].shuffle
[1, 2, 3].sample
[1, 2, 3].sample(3)
[1, 2, 3].shuffle[1, 3]    # sample(3) might return a longer Array
[1, 2, 3].shuffle[1..3]    # sample(3) might return a longer Array
[1, 2, 3].shuffle[foo, bar]
[1, 2, 3].shuffle(random: Random.new)

Constants

MSG

Public Instance Methods

autocorrect(node) click to toggle source
# File lib/rubocop/cop/performance/sample.rb, line 43
def autocorrect(node)
  shuffle_node, shuffle_arg, method, method_args =
    sample_candidate?(node)

  lambda do |corrector|
    corrector.replace(source_range(shuffle_node, node),
                      correction(shuffle_arg, method, method_args))
  end
end
on_send(node) click to toggle source
# File lib/rubocop/cop/performance/sample.rb, line 33
def on_send(node)
  sample_candidate?(node) do |shuffle, shuffle_arg, method, method_args|
    return unless offensive?(method, method_args)

    range = source_range(shuffle, node)
    message = message(shuffle_arg, method, method_args, range)
    add_offense(node, location: range, message: message)
  end
end

Private Instance Methods

correction(shuffle_arg, method, method_args) click to toggle source
# File lib/rubocop/cop/performance/sample.rb, line 117
def correction(shuffle_arg, method, method_args)
  shuffle_arg = extract_source(shuffle_arg)
  sample_arg = sample_arg(method, method_args)
  args = [sample_arg, shuffle_arg].compact.join(', ')
  args.empty? ? 'sample' : "sample(#{args})"
end
extract_source(args) click to toggle source
# File lib/rubocop/cop/performance/sample.rb, line 133
def extract_source(args)
  args.empty? ? nil : args.first.source
end
message(shuffle_arg, method, method_args, range) click to toggle source
# File lib/rubocop/cop/performance/sample.rb, line 111
def message(shuffle_arg, method, method_args, range)
  format(MSG,
         correct: correction(shuffle_arg, method, method_args),
         incorrect: range.source)
end
offensive?(method, method_args) click to toggle source
# File lib/rubocop/cop/performance/sample.rb, line 55
def offensive?(method, method_args)
  case method
  when :first, :last
    true
  when :[]
    sample_size(method_args) != :unknown
  else
    false
  end
end
range_size(range_node) click to toggle source
# File lib/rubocop/cop/performance/sample.rb, line 91
def range_size(range_node)
  vals = range_node.to_a
  return :unknown unless vals.all?(&:int_type?)
  low, high = vals.map { |val| val.children[0] }
  return :unknown unless low.zero? && high >= 0

  case range_node.type
  when :erange
    (low...high).size
  when :irange
    (low..high).size
  end
end
sample_arg(method, method_args) click to toggle source
# File lib/rubocop/cop/performance/sample.rb, line 124
def sample_arg(method, method_args)
  case method
  when :first, :last
    extract_source(method_args)
  when :[]
    sample_size(method_args)
  end
end
sample_size(method_args) click to toggle source
# File lib/rubocop/cop/performance/sample.rb, line 66
def sample_size(method_args)
  case method_args.size
  when 1
    sample_size_for_one_arg(method_args.first)
  when 2
    sample_size_for_two_args(*method_args)
  end
end
sample_size_for_one_arg(arg) click to toggle source
# File lib/rubocop/cop/performance/sample.rb, line 75
def sample_size_for_one_arg(arg)
  case arg.type
  when :erange, :irange
    range_size(arg)
  when :int
    arg.to_a.first.zero? ? nil : :unknown
  else
    :unknown
  end
end
sample_size_for_two_args(first, second) click to toggle source
# File lib/rubocop/cop/performance/sample.rb, line 86
def sample_size_for_two_args(first, second)
  return :unknown unless first.int_type? && first.to_a.first.zero?
  second.int_type? ? second.to_a.first : :unknown
end
source_range(shuffle_node, node) click to toggle source
# File lib/rubocop/cop/performance/sample.rb, line 105
def source_range(shuffle_node, node)
  Parser::Source::Range.new(shuffle_node.source_range.source_buffer,
                            shuffle_node.loc.selector.begin_pos,
                            node.source_range.end_pos)
end