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.

In addition it checks whether different formats are used in the same format string. Do not mix numbered, unnumbered, and named formats in the same format string.

@example

# bad

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

@example

# good

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

@example

# bad

format('Unnumbered format: %s and numbered: %2$s', a_value, another)

@example

# good

format('Numbered format: %1$s and numbered %2$s', a_value, another)

Constants

KERNEL
MSG

rubular.com/r/CvpbxkcTzy

MSG_INVALID
RESTRICT_ON_SEND
SHOVEL
STRING_TYPES

Public Instance Methods

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

  if invalid_format_string?(node)
    add_offense(node.loc.selector, message: MSG_INVALID)
    return
  end

  return unless offending_node?(node)

  add_offense(node.loc.selector, message: message(node))
end

Private Instance Methods

count_format_matches(node) click to toggle source
# File lib/rubocop/cop/lint/format_parameter_mismatch.rb, line 133
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 115
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 137
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 125
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 129
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 149
def expected_fields_count(node)
  return :unknown unless node.str_type?

  format_string = RuboCop::Cop::Utils::FormatString.new(node.source)
  return 1 if format_string.named_interpolation?

  max_digit_dollar_num = format_string.max_digit_dollar_num
  return max_digit_dollar_num if max_digit_dollar_num&.nonzero?

  format_string
    .format_sequences
    .reject(&:percent?)
    .reduce(0) { |acc, seq| acc + seq.arity }
end
format?(node) click to toggle source
# File lib/rubocop/cop/lint/format_parameter_mismatch.rb, line 164
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 142
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
format_string?(node) click to toggle source
# File lib/rubocop/cop/lint/format_parameter_mismatch.rb, line 64
def format_string?(node)
  called_on_string?(node) && method_with_format_args?(node)
end
heredoc?(node) click to toggle source
# File lib/rubocop/cop/lint/format_parameter_mismatch.rb, line 111
def heredoc?(node)
  node.first_argument.source[0, 2] == SHOVEL
end
invalid_format_string?(node) click to toggle source
# File lib/rubocop/cop/lint/format_parameter_mismatch.rb, line 68
def invalid_format_string?(node)
  string = if sprintf?(node) || format?(node)
             node.first_argument.source
           else
             node.receiver.source
           end
  !RuboCop::Cop::Utils::FormatString.new(string).valid?
end
matched_arguments_count?(expected, passed) click to toggle source
# File lib/rubocop/cop/lint/format_parameter_mismatch.rb, line 87
def matched_arguments_count?(expected, passed)
  if passed.negative?
    expected < passed.abs
  else
    expected != passed
  end
end
message(node) click to toggle source
# File lib/rubocop/cop/lint/format_parameter_mismatch.rb, line 183
def message(node)
  num_args_for_format, num_expected_fields = count_matches(node)

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

  format(MSG, arg_num: num_args_for_format, method: method_name,
              field_num: num_expected_fields)
end
method_with_format_args?(node) click to toggle source
# File lib/rubocop/cop/lint/format_parameter_mismatch.rb, line 101
def method_with_format_args?(node)
  sprintf?(node) || format?(node) || percent?(node)
end
offending_node?(node) click to toggle source
# File lib/rubocop/cop/lint/format_parameter_mismatch.rb, line 77
def offending_node?(node)
  return false if 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 172
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 105
def splat_args?(node)
  return false if percent?(node)

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