class RuboCop::Cop::Lint::FormatParameterMismatch

format(‘Numbered format: %1$s and numbered %2$s’, a_value, another)
# good
@example
format(‘Unnumbered format: %s and numbered: %2$s’, a_value, another)
# bad
@example
format(‘A value: %s and another: %i’, a_value, another)
# good
@example
format(‘A value: %s and another: %i’, a_value)
# bad
@example
the same format string.
format string. Do not mix numbered, unnumbered, and named formats in
In addition it checks whether different formats are used in the same
passed as arguments.
expected fields for format/sprintf/#% and what is actually
This lint sees if there is a mismatch between the number of

def count_format_matches(node)

def count_format_matches(node)
  [node.arguments.count - 1, expected_fields_count(node.first_argument)]
end

def count_matches(node)

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

def count_percent_matches(node)

def count_percent_matches(node)
  [node.first_argument.child_nodes.count,
   expected_fields_count(node.receiver)]
end

def countable_format?(node)

def countable_format?(node)
  (sprintf?(node) || format?(node)) && !heredoc?(node)
end

def countable_percent?(node)

def countable_percent?(node)
  percent?(node) && node.first_argument.array_type?
end

def expected_fields_count(node)

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

def format?(node)

def format?(node)
  format_method?(:format, node)
end

def format_method?(name, node)

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

def format_string?(node)

def format_string?(node)
  called_on_string?(node) && method_with_format_args?(node)
end

def heredoc?(node)

def heredoc?(node)
  node.first_argument.source[0, 2] == SHOVEL
end

def invalid_format_string?(node)

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

def matched_arguments_count?(expected, passed)

def matched_arguments_count?(expected, passed)
  if passed.negative?
    expected < passed.abs
  else
    expected != passed
  end
end

def message(node)

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

def method_with_format_args?(node)

def method_with_format_args?(node)
  sprintf?(node) || format?(node) || percent?(node)
end

def offending_node?(node)

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

def on_send(node)

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

def percent?(node)

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

def splat_args?(node)

def splat_args?(node)
  return false if percent?(node)
  node.arguments.drop(1).any?(&:splat_type?)
end

def sprintf?(node)

def sprintf?(node)
  format_method?(:sprintf, node)
end