Class: RuboCop::Cop::DevDoc::Style::StringSymbolComparison

Inherits:
Base
  • Object
show all
Defined in:
lib/rubocop/cop/dev_doc/style/string_symbol_comparison.rb

Overview

Flag direct == / != comparisons between a known-string source and a symbol literal. Such comparisons are silently always false ('draft' == :draft is false in Ruby), so they signal that the caller forgot to convert at the boundary.

Rationale

Strings and symbols are not equal in Ruby, and string-typed values arrive from boundaries the developer doesn't control: HTTP params, request headers, ENV. Comparing one of these directly against a symbol literal is a guaranteed bug. Allow-list into a symbol at the boundary instead — see best_practices/backend/en/01a_defensive_programming.md item 7.

❌
if params[:status] == :draft   # always false

✔ Allow-list into a symbol at the boundary, then compare
@filter_status = glib_allowlist_symbol_param(params[:status], STATUSES, default: :draft)
if @filter_status == :draft

A bare &.to_sym conversion would also fix the always-false bug, but it lets ANY client value into the symbol domain — the sibling DevDoc/Style/AvoidSymbolizingBoundaryInput flags exactly that. The two cops are a complementary pair: this one polices comparing without converting; that one polices converting without validating.

Sources detected

  • params[…]
  • request.headers[…]
  • ENV[…]

Limitations

The cop only catches the direct comparison form. Once the value flows into a local or instance variable, type information is lost and Rubocop cannot trace it back to the original string source. Those cases are caught by review.

Examples:

# bad
params[:status] == :draft
:production == ENV['MODE']
request.headers['X-Mode'] != :debug

# good
glib_allowlist_symbol_param(params[:status], STATUSES, default: :draft) == :draft
ENV['MODE'] == 'production'

Constant Summary collapse

MSG =
'Comparing string-typed `%<source>s` to a symbol literal is always false. ' \
'Allow-list it into a symbol at the boundary ' \
'(`glib_allowlist_symbol_param(value, ALLOWED, default:)`) or compare against ' \
'a string instead (see backend/01a_defensive_programming.md item 7).'.freeze
RESTRICT_ON_SEND =
%i[== !=].freeze

Instance Method Summary collapse

Instance Method Details

#on_send(node) ⇒ Object



71
72
73
74
75
76
77
78
79
80
# File 'lib/rubocop/cop/dev_doc/style/string_symbol_comparison.rb', line 71

def on_send(node)
  lhs = node.receiver
  rhs = node.first_argument
  return unless lhs && rhs

  source_node = pick_string_source(lhs, rhs)
  return unless source_node

  add_offense(node, message: format(MSG, source: source_node.source))
end