class RuboCop::Cop::Style::Sample

Identifies 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
[2, 1, 3].shuffle.at(0)
[2, 1, 3].shuffle.slice(0)
[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
RESTRICT_ON_SEND

Public Instance Methods

on_send(node) click to toggle source
# File lib/rubocop/cop/style/sample.rb, line 41
def on_send(node)
  sample_candidate?(node) do |shuffle_node, shuffle_arg, method, method_args|
    return unless offensive?(method, method_args)

    range = source_range(shuffle_node, node)
    message = message(shuffle_arg, method, method_args, range)

    add_offense(range, message: message) do |corrector|
      corrector.replace(
        source_range(shuffle_node, node), correction(shuffle_arg, method, method_args)
      )
    end
  end
end

Private Instance Methods

correction(shuffle_arg, method, method_args) click to toggle source
# File lib/rubocop/cop/style/sample.rb, line 123
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/style/sample.rb, line 139
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/style/sample.rb, line 117
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/style/sample.rb, line 58
def offensive?(method, method_args)
  case method
  when :first, :last
    true
  when :[], :at, :slice
    sample_size(method_args) != :unknown
  else
    false
  end
end
range_size(range_node) click to toggle source

rubocop:disable Metrics/CyclomaticComplexity, Metrics/PerceivedComplexity

# File lib/rubocop/cop/style/sample.rb, line 95
def range_size(range_node)
  vals = range_node.to_a
  return :unknown unless vals.all? { |val| val.nil? || val.int_type? }

  low, high = vals.map { |val| val.nil? ? 0 : 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/style/sample.rb, line 130
def sample_arg(method, method_args)
  case method
  when :first, :last
    extract_source(method_args)
  when :[], :slice
    sample_size(method_args)
  end
end
sample_size(method_args) click to toggle source
# File lib/rubocop/cop/style/sample.rb, line 69
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/style/sample.rb, line 78
def sample_size_for_one_arg(arg)
  if arg.range_type?
    range_size(arg)
  elsif arg.int_type?
    [0, -1].include?(arg.to_a.first) ? nil : :unknown
  else
    :unknown
  end
end
sample_size_for_two_args(first, second) click to toggle source
# File lib/rubocop/cop/style/sample.rb, line 88
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

rubocop:enable Metrics/CyclomaticComplexity, Metrics/PerceivedComplexity

# File lib/rubocop/cop/style/sample.rb, line 111
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