class RuboCop::Cop::Lint::SymbolConversion

Checks for uses of literal strings converted to a symbol where a literal symbol could be used instead.

There are two possible styles for this cop. ‘strict` (default) will register an offense for any incorrect usage. `consistent` additionally requires hashes to use the same style for every symbol key (ie. if any symbol key needs to be quoted it requires all keys to be quoted).

@example

# bad
'string'.to_sym
:symbol.to_sym
'underscored_string'.to_sym
:'underscored_symbol'
'hyphenated-string'.to_sym

# good
:string
:symbol
:underscored_string
:underscored_symbol
:'hyphenated-string'

@example EnforcedStyle: strict (default)

# bad
{
  'a': 1,
  "b": 2,
  'c-d': 3
}

# good (don't quote keys that don't require quoting)
{
  a: 1,
  b: 2,
  'c-d': 3
}

@example EnforcedStyle: consistent

# bad
{
  a: 1,
  'b-c': 2
}

# good (quote all keys if any need quoting)
{
  'a': 1,
  'b-c': 2
}

# good (no quoting required)
{
  a: 1,
  b: 2
}

Constants

MSG
MSG_CONSISTENCY
RESTRICT_ON_SEND

Public Instance Methods

on_hash(node) click to toggle source
# File lib/rubocop/cop/lint/symbol_conversion.rb, line 100
def on_hash(node)
  # For `EnforcedStyle: strict`, hash keys are evaluated in `on_sym`
  return unless style == :consistent

  keys = node.keys.select(&:sym_type?)

  if keys.any? { |key| requires_quotes?(key) }
    correct_inconsistent_hash_keys(keys)
  else
    # If there are no symbol keys requiring quoting,
    # treat the hash like `EnforcedStyle: strict`.
    keys.each { |key| correct_hash_key(key) }
  end
end
on_send(node) click to toggle source
# File lib/rubocop/cop/lint/symbol_conversion.rb, line 76
def on_send(node)
  return unless node.receiver
  return unless node.receiver.str_type? || node.receiver.sym_type?

  register_offense(node, correction: node.receiver.value.to_sym.inspect)
end
on_sym(node) click to toggle source
# File lib/rubocop/cop/lint/symbol_conversion.rb, line 83
def on_sym(node)
  return if ignored_node?(node) || properly_quoted?(node.source, node.value.inspect)

  # `alias` arguments are symbols but since a symbol that requires
  # being quoted is not a valid method identifier, it can be ignored
  return if in_alias?(node)

  # The `%I[]` and `%i[]` macros are parsed as normal arrays of symbols
  # so they need to be ignored.
  return if in_percent_literal_array?(node)

  # Symbol hash keys have a different format and need to be handled separately
  return correct_hash_key(node) if hash_key?(node)

  register_offense(node, correction: node.value.inspect)
end

Private Instance Methods

correct_hash_key(node) click to toggle source
# File lib/rubocop/cop/lint/symbol_conversion.rb, line 142
def correct_hash_key(node)
  # Although some operators can be converted to symbols normally
  # (ie. `:==`), these are not accepted as hash keys and will
  # raise a syntax error (eg. `{ ==: ... }`). Therefore, if the
  # symbol does not start with an alphanumeric or underscore, it
  # will be ignored.
  return unless node.value.to_s.match?(/\A[a-z0-9_]/i)

  correction = node.value.inspect
  correction = correction.delete_prefix(':') if node.parent.colon?
  return if properly_quoted?(node.source, correction)

  register_offense(
    node,
    correction: correction,
    message: format(MSG, correction: node.parent.colon? ? "#{correction}:" : correction)
  )
end
correct_inconsistent_hash_keys(keys) click to toggle source
# File lib/rubocop/cop/lint/symbol_conversion.rb, line 161
def correct_inconsistent_hash_keys(keys)
  keys.each do |key|
    ignore_node(key)

    next if requires_quotes?(key)
    next if properly_quoted?(key.source, %("#{key.value}"))

    correction = %("#{key.value}")
    register_offense(
      key,
      correction: correction,
      message: format(MSG_CONSISTENCY, correction: "#{correction}:")
    )
  end
end
in_alias?(node) click to toggle source
# File lib/rubocop/cop/lint/symbol_conversion.rb, line 134
def in_alias?(node)
  node.parent&.alias_type?
end
in_percent_literal_array?(node) click to toggle source
# File lib/rubocop/cop/lint/symbol_conversion.rb, line 138
def in_percent_literal_array?(node)
  node.parent&.array_type? && node.parent&.percent_literal?
end
properly_quoted?(source, value) click to toggle source
# File lib/rubocop/cop/lint/symbol_conversion.rb, line 121
def properly_quoted?(source, value)
  return true if style == :strict && (!source.match?(/['"]/) || value.end_with?('='))

  source == value ||
    # `Symbol#inspect` uses double quotes, but allow single-quoted
    # symbols to work as well.
    source.tr("'", '"') == value
end
register_offense(node, correction:, message: format(MSG, correction: correction)) click to toggle source
# File lib/rubocop/cop/lint/symbol_conversion.rb, line 117
def register_offense(node, correction:, message: format(MSG, correction: correction))
  add_offense(node, message: message) { |corrector| corrector.replace(node, correction) }
end
requires_quotes?(sym_node) click to toggle source
# File lib/rubocop/cop/lint/symbol_conversion.rb, line 130
def requires_quotes?(sym_node)
  sym_node.value.inspect.match?(/^:".*?"|=$/)
end