class RuboCop::Cop::Style::FormatStringToken

Use a consistent style for named format string tokens.

@example

EnforcedStyle: annotated

# bad

format('%{greeting}', greeting: 'Hello')
format('%s', 'Hello')

# good

format('%<greeting>s', greeting: 'Hello')

@example

EnforcedStyle: template

# bad

format('%<greeting>s', greeting: 'Hello')
format('%s', 'Hello')

# good

format('%{greeting}', greeting: 'Hello')

Constants

FIELD_CHARACTERS
STYLE_PATTERNS
TOKEN_PATTERN

Public Instance Methods

on_str(node) click to toggle source
# File lib/rubocop/cop/style/format_string_token.rb, line 44
def on_str(node)
  return if node.each_ancestor(:xstr).any?

  tokens(node) do |detected_style, token_range|
    if detected_style == style
      correct_style_detected
    else
      style_detected(detected_style)
      add_offense(node, location: token_range,
                        message: message(detected_style))
    end
  end
end

Private Instance Methods

match_token(source_range) click to toggle source
# File lib/rubocop/cop/style/format_string_token.rb, line 101
def match_token(source_range)
  supported_styles.each do |style_name|
    pattern = STYLE_PATTERNS.fetch(style_name)
    match = source_range.source.match(pattern)
    next unless match

    return [style_name, match.begin(:token), match.end(:token)]
  end

  nil
end
message(detected_style) click to toggle source
# File lib/rubocop/cop/style/format_string_token.rb, line 60
def message(detected_style)
  "Prefer #{message_text(style)} over #{message_text(detected_style)}."
end
message_text(style) click to toggle source

rubocop:disable FormatStringToken

# File lib/rubocop/cop/style/format_string_token.rb, line 65
def message_text(style)
  case style
  when :annotated then 'annotated tokens (like `%<foo>s`)'
  when :template  then 'template tokens (like `%{foo}`)'
  end
end
slice_source(source_range, new_begin, new_end) click to toggle source
# File lib/rubocop/cop/style/format_string_token.rb, line 131
def slice_source(source_range, new_begin, new_end)
  Parser::Source::Range.new(
    source_range.source_buffer,
    new_begin,
    new_end
  )
end
split_token(source_range, match_begin, match_end) click to toggle source
# File lib/rubocop/cop/style/format_string_token.rb, line 113
def split_token(source_range, match_begin, match_end)
  token =
    slice_source(
      source_range,
      source_range.begin_pos + match_begin,
      source_range.begin_pos + match_end
    )

  remainder =
    slice_source(
      source_range,
      source_range.begin_pos + match_end,
      source_range.end_pos
    )

  [token, remainder]
end
str_contents(source_map) click to toggle source
# File lib/rubocop/cop/style/format_string_token.rb, line 79
def str_contents(source_map)
  if source_map.is_a?(Parser::Source::Map::Heredoc)
    source_map.heredoc_body
  elsif source_map.begin
    slice_source(
      source_map.expression,
      source_map.expression.begin_pos + 1,
      source_map.expression.end_pos - 1
    )
  else
    source_map.expression
  end
end
token_ranges(contents) { |detected_style, token| ... } click to toggle source
# File lib/rubocop/cop/style/format_string_token.rb, line 93
def token_ranges(contents)
  while (offending_match = match_token(contents))
    detected_style, *range = *offending_match
    token, contents = split_token(contents, *range)
    yield(detected_style, token)
  end
end
tokens(str_node, &block) click to toggle source

rubocop:enable FormatStringToken

# File lib/rubocop/cop/style/format_string_token.rb, line 73
def tokens(str_node, &block)
  return if str_node.source == '__FILE__'

  token_ranges(str_contents(str_node.loc), &block)
end