class RuboCop::Cop::Style::FormatStringToken

format(‘%s’, ‘Hello’)
# good
format(‘%{greeting}’, ‘Hello’)
format(‘%<greeting>s’, greeting: ‘Hello’)
# bad
@example EnforcedStyle: unannotated
format(‘%{greeting}’, greeting: ‘Hello’)
# good
format(‘%s’, ‘Hello’)
format(‘%<greeting>s’, greeting: ‘Hello’)
# bad
@example EnforcedStyle: template
format(‘%<greeting>s’, greeting: ‘Hello’)
# good
format(‘%s’, ‘Hello’)
format(‘%{greeting}’, greeting: ‘Hello’)
# bad
@example EnforcedStyle: annotated (default)
to encoded URLs or Date/Time formatting strings.
The reason is that unannotated format is very similar
`printf`, ‘sprintf`, `format`, `%`.
which are passed as arguments to those methods:
`unannotated` style cop only works for strings
Note:
Use a consistent style for named format string tokens.

def message(detected_style)

def message(detected_style)
  "Prefer #{message_text(style)} over #{message_text(detected_style)}."
end

def message_text(style)

rubocop:disable Style/FormatStringToken
def message_text(style)
  case style
  when :annotated then 'annotated tokens (like `%<foo>s`)'
  when :template then 'template tokens (like `%{foo}`)'
  when :unannotated then 'unannotated tokens (like `%s`)'
  end
end

def on_str(node)

def on_str(node)
  return if node.each_ancestor(:xstr, :regexp).any?
  tokens(node) do |detected_style, token_range|
    if detected_style == style ||
       unannotated_format?(node, detected_style)
      correct_style_detected
    else
      style_detected(detected_style)
      add_offense(node, location: token_range,
                        message: message(detected_style))
    end
  end
end

def str_contents(source_map)

def str_contents(source_map)
  if source_map.is_a?(Parser::Source::Map::Heredoc)
    source_map.heredoc_body
  elsif source_map.begin
    source_map.expression.adjust(begin_pos: +1, end_pos: -1)
  else
    source_map.expression
  end
end

def token_ranges(contents)

def token_ranges(contents)
  format_string = RuboCop::Cop::Utils::FormatString.new(contents.source)
  format_string.format_sequences.each do |seq|
    next if seq.percent?
    detected_style = seq.style
    token = contents.begin.adjust(
      begin_pos: seq.begin_pos,
      end_pos:   seq.end_pos
    )
    yield(detected_style, token)
  end
end

def tokens(str_node, &block)

def tokens(str_node, &block)
  return if str_node.source == '__FILE__'
  token_ranges(str_contents(str_node.loc), &block)
end

def unannotated_format?(node, detected_style)

def unannotated_format?(node, detected_style)
  detected_style == :unannotated &&
    !format_string_in_typical_context?(node)
end