class RuboCop::Cop::Lint::FormatParameterMismatch

This lint sees if there is a mismatch between the number of expected fields for format/sprintf/#% and what is actually passed as arguments.

@example

# bad

format('A value: %s and another: %i', a_value)

@example

# good

format('A value: %s and another: %i', a_value, another)

Constants

FIELD_REGEX
KERNEL
MSG

rubular.com/r/CvpbxkcTzy

NAMED_FIELD_REGEX
NAMED_INTERPOLATION
PERCENT
PERCENT_PERCENT
SHOVEL
STRING_TYPES

Public Instance Methods

on_send(node) click to toggle source
# File lib/rubocop/cop/lint/format_parameter_mismatch.rb, line 35
def on_send(node)
  return unless offending_node?(node)

  add_offense(node, location: :selector)
end

Private Instance Methods

arguments_count(format) click to toggle source

number of arguments required for the format sequence

# File lib/rubocop/cop/lint/format_parameter_mismatch.rb, line 142
def arguments_count(format)
  format.scan('*').count + 1
end
called_on_string?(node) click to toggle source
# File lib/rubocop/cop/lint/format_parameter_mismatch.rb, line 62
def called_on_string?(node)
  receiver_node, _method, format_string, = *node
  if receiver_node.nil? || receiver_node.const_type?
    format_string && format_string.str_type?
  else
    receiver_node.str_type?
  end
end
count_format_matches(node) click to toggle source
# File lib/rubocop/cop/lint/format_parameter_mismatch.rb, line 113
def count_format_matches(node)
  [node.arguments.count - 1, expected_fields_count(node.first_argument)]
end
count_matches(node) click to toggle source
# File lib/rubocop/cop/lint/format_parameter_mismatch.rb, line 95
def count_matches(node)
  if countable_format?(node)
    count_format_matches(node)
  elsif countable_percent?(node)
    count_percent_matches(node)
  else
    [:unknown] * 2
  end
end
count_percent_matches(node) click to toggle source
# File lib/rubocop/cop/lint/format_parameter_mismatch.rb, line 117
def count_percent_matches(node)
  [node.first_argument.child_nodes.count,
   expected_fields_count(node.receiver)]
end
countable_format?(node) click to toggle source
# File lib/rubocop/cop/lint/format_parameter_mismatch.rb, line 105
def countable_format?(node)
  (sprintf?(node) || format?(node)) && !heredoc?(node)
end
countable_percent?(node) click to toggle source
# File lib/rubocop/cop/lint/format_parameter_mismatch.rb, line 109
def countable_percent?(node)
  percent?(node) && node.first_argument.array_type?
end
expected_fields_count(node) click to toggle source
# File lib/rubocop/cop/lint/format_parameter_mismatch.rb, line 130
def expected_fields_count(node)
  return :unknown unless node.str_type?
  return 1 if node.source =~ NAMED_INTERPOLATION

  node
    .source
    .scan(FIELD_REGEX)
    .reject { |x| x.first == PERCENT_PERCENT }
    .reduce(0) { |acc, elem| acc + arguments_count(elem[2]) }
end
format?(node) click to toggle source
# File lib/rubocop/cop/lint/format_parameter_mismatch.rb, line 146
def format?(node)
  format_method?(:format, node)
end
format_method?(name, node) click to toggle source
# File lib/rubocop/cop/lint/format_parameter_mismatch.rb, line 122
def format_method?(name, node)
  return false if node.const_receiver? &&
                  !node.receiver.loc.name.is?(KERNEL)
  return false unless node.method?(name)

  node.arguments.size > 1 && node.first_argument.str_type?
end
heredoc?(node) click to toggle source
# File lib/rubocop/cop/lint/format_parameter_mismatch.rb, line 91
def heredoc?(node)
  node.first_argument.source[0, 2] == SHOVEL
end
matched_arguments_count?(expected, passed) click to toggle source
# File lib/rubocop/cop/lint/format_parameter_mismatch.rb, line 54
def matched_arguments_count?(expected, passed)
  if passed < 0
    expected < passed.abs
  else
    expected != passed
  end
end
message(node) click to toggle source
# File lib/rubocop/cop/lint/format_parameter_mismatch.rb, line 167
def message(node)
  num_args_for_format, num_expected_fields = count_matches(node)

  method_name = node.method?(:%) ? 'String#%' : node.method_name

  format(MSG, num_args_for_format, method_name, num_expected_fields)
end
method_with_format_args?(node) click to toggle source
# File lib/rubocop/cop/lint/format_parameter_mismatch.rb, line 71
def method_with_format_args?(node)
  sprintf?(node) || format?(node) || percent?(node)
end
named_mode?(node) click to toggle source
# File lib/rubocop/cop/lint/format_parameter_mismatch.rb, line 75
def named_mode?(node)
  relevant_node = if sprintf?(node) || format?(node)
                    node.first_argument
                  elsif percent?(node)
                    node.receiver
                  end

  !relevant_node.source.scan(NAMED_FIELD_REGEX).empty?
end
offending_node?(node) click to toggle source
# File lib/rubocop/cop/lint/format_parameter_mismatch.rb, line 43
def offending_node?(node)
  return false unless called_on_string?(node)
  return false unless method_with_format_args?(node)
  return false if named_mode?(node) || splat_args?(node)

  num_of_format_args, num_of_expected_fields = count_matches(node)

  return false if num_of_format_args == :unknown
  matched_arguments_count?(num_of_expected_fields, num_of_format_args)
end
percent?(node) click to toggle source
# File lib/rubocop/cop/lint/format_parameter_mismatch.rb, line 154
def percent?(node)
  receiver = node.receiver

  percent = node.method?(:%) &&
            (STRING_TYPES.include?(receiver.type) ||
             node.first_argument.array_type?)

  return false if percent && STRING_TYPES.include?(receiver.type) &&
                  heredoc?(node)

  percent
end
splat_args?(node) click to toggle source
# File lib/rubocop/cop/lint/format_parameter_mismatch.rb, line 85
def splat_args?(node)
  return false if percent?(node)

  node.arguments.butfirst.any?(&:splat_type?)
end
sprintf?(node) click to toggle source
# File lib/rubocop/cop/lint/format_parameter_mismatch.rb, line 150
def sprintf?(node)
  format_method?(:sprintf, node)
end