class RuboCop::Cop::Style::RedundantFormat


‘foo bar’
# good
sprintf(‘%s %s’, ‘foo’, ‘bar’)
format(‘%s %s’, ‘foo’, ‘bar’)
# bad
MESSAGE
# good
sprintf(MESSAGE)
format(MESSAGE)
# bad
’the quick brown fox jumps over the lazy dog.‘
# good
sprintf(’the quick brown fox jumps over the lazy dog.‘)
format(’the quick brown fox jumps over the lazy dog.‘)
# bad
@example
—-
’template’.frozen? # => true
format(‘template’).frozen? # => false
# frozen_string_literal: true
—-
[source,ruby]

this autocorrection is inherently unsafe.
Additionally, since the necessity of ‘dup` cannot be determined automatically,
Consider using `’string’.dup` instead of ‘format(’string’)‘.
`’string’‘, `FrozenError` may occur when calling a destructive method like `String#<<`.
`format` and `sprintf` are never frozen. If `format(’string’)‘ is autocorrected to
This cop’s autocorrection is unsafe because string object returned by
@safety
`%f` format specifiers.
inlined into a string easily. This applies to the ‘%s`, `%d`, `%i`, `%u`, and
Also looks for `format` calls where the arguments are literals that can be
as it can be replaced by the string or constant itself.
Calling `format` with only a single string or constant argument is redundant,
Checks for calls to `Kernel#format` or `Kernel#sprintf` that are redundant.

def all_fields_literal?(string, arguments)

def all_fields_literal?(string, arguments)
  count = 0
  sequences = RuboCop::Cop::Utils::FormatString.new(string).format_sequences
  return false unless sequences.any?
  sequences.each do |sequence|
    next if sequence.percent?
    hash = arguments.detect(&:hash_type?)
    next unless (argument = find_argument(sequence, arguments, hash))
    next unless matching_argument?(sequence, argument)
    count += 1
  end
  sequences.size == count
end

def argument_value(argument)

def argument_value(argument)
  argument = argument.children.first if argument.begin_type?
  if argument.dsym_type?
    dsym_value(argument)
  elsif argument.hash_type?
    hash_value(argument)
  elsif rational_number?(argument)
    rational_value(argument)
  elsif complex_number?(argument)
    complex_value(argument)
  elsif argument.respond_to?(:value)
    argument.value
  else
    argument.source
  end
end

def argument_values(arguments)

def argument_values(arguments)
  arguments.map { |argument| argument_value(argument) }
end

def complex_value(complex_node)

def complex_value(complex_node)
  Complex(complex_node.source)
end

def detect_unnecessary_fields(node)

def detect_unnecessary_fields(node)
  return unless node.first_argument&.str_type?
  string = node.first_argument.value
  arguments = node.arguments[1..]
  return unless string && arguments.any?
  return if splatted_arguments?(node)
  register_all_fields_literal(node, string, arguments)
end

def dsym_value(dsym_node)

def dsym_value(dsym_node)
  dsym_node.children.first.source
end

def find_argument(sequence, arguments, hash)

def find_argument(sequence, arguments, hash)
  if hash && (sequence.annotated? || sequence.template?)
    find_hash_value_node(hash, sequence.name.to_sym).first
  elsif sequence.arg_number
    arguments[sequence.arg_number.to_i - 1]
  else
    # If the specifier contains `*`, the following arguments will be used
    # to specify the width and can be ignored.
    (sequence.arity - 1).times { arguments.shift }
    arguments.shift
  end
end

def float?(argument)

def float?(argument)
  numeric?(argument) && Float(argument_value(argument), exception: false)
end

def hash_value(hash_node)

def hash_value(hash_node)
  hash_node.each_pair.with_object({}) do |pair, hash|
    hash[pair.key.value] = argument_value(pair.value)
  end
end

def integer?(argument)

def integer?(argument)
  numeric?(argument) && Integer(argument_value(argument), exception: false)
end

def matching_argument?(sequence, argument)

def matching_argument?(sequence, argument)
  # Template specifiers don't give a type, any acceptable literal type is ok.
  return argument.type?(*ACCEPTABLE_LITERAL_TYPES) if sequence.template?
  # An argument matches a specifier if it can be easily converted
  # to that type.
  case sequence.type
  when 's'
    argument.type?(*ACCEPTABLE_LITERAL_TYPES)
  when 'd', 'i', 'u'
    integer?(argument)
  when 'f'
    float?(argument)
  else
    false
  end
end

def message(node, prefer)

def message(node, prefer)
  format(MSG, prefer: prefer, method_name: node.method_name)
end

def numeric?(argument)

def numeric?(argument)
  argument.type?(:numeric, :str) ||
    rational_number?(argument) ||
    complex_number?(argument)
end

def on_send(node)

def on_send(node)
  format_without_additional_args?(node) do |value|
    replacement = value.source
    add_offense(node, message: message(node, replacement)) do |corrector|
      corrector.replace(node, replacement)
    end
    return
  end
  detect_unnecessary_fields(node)
end

def quote(string, node)

quotes if possible.
Add correct quotes to the formatted string, preferring retaining the existing
def quote(string, node)
  str_node = node.first_argument
  start_delimiter = str_node.loc.begin.source
  end_delimiter = str_node.loc.end.source
  # If there is any interpolation, the delimiters need to be changed potentially
  if node.each_descendant(:dstr, :dsym).any?
    case start_delimiter
    when "'"
      start_delimiter = end_delimiter = '"'
    when /\A%q(.)/
      start_delimiter = "%Q#{Regexp.last_match[1]}"
    end
  end
  "#{start_delimiter}#{string}#{end_delimiter}"
end

def rational_value(rational_node)

def rational_value(rational_node)
  rational_node.source.to_r
end

def register_all_fields_literal(node, string, arguments)

def register_all_fields_literal(node, string, arguments)
  return unless all_fields_literal?(string, arguments.dup)
  format_arguments = argument_values(arguments)
  begin
    formatted_string = format(string, *format_arguments)
  rescue ArgumentError
    return
  end
  replacement = quote(formatted_string, node)
  add_offense(node, message: message(node, replacement)) do |corrector|
    corrector.replace(node, replacement)
  end
end