Class: RuboCop::Cop::DevDoc::Style::RepeatedBracketRead

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

Overview

Avoid reading obj[key] more than once with the same receiver and same key in a single method body.

Rationale

When the same bracket read appears in multiple places, two distinct failure modes open up:

  1. Silent typos. Bracket access returns nil for missing keys; nothing raises. Two occurrences of params[:status] keep the spelling in sync, but if one of them silently becomes params[:stutus], the line returns nil and the bug ships. A single assignment fixes the spelling in exactly one place — a typo there becomes a NameError, not a silent nil.

  2. Reader ambiguity and mutation risk. The reader has to verify every occurrence really is the same key and that nothing in between mutates the hash. Assigning once makes the value a stable named thing — and on receivers like session or shared hashes, it also avoids a real TOCTOU shape where the value can change between the guard and the use.

❌ def show @item = Item.lookup_by_slug(params) redirect_to canonical_url(@item) if @item.slug != params end

✔️
def show
slug  = params[:slug]
@item = Item.lookup_by_slug(slug)
redirect_to canonical_url(@item) if @item.slug != slug
end

The cop only counts reads. obj[k] = v is a write (:[]=) and is not compared against bracket reads of the same key.

Exception

Genuine intentional re-reads (e.g. a deliberate second check after a write that may have mutated the receiver) go through inline # rubocop:disable with a reason.

NOTE: Receiver and key are compared by source text — hash['foo'] and hash[:foo] look different to the cop even though they're the same value on a HashWithIndifferentAccess like params. That is a known false negative; the cop will miss that shape rather than over-fire.

NOTE: Scope is the enclosing def/defs body, including nested blocks. Block parameters are scoped to their block, so the same name in two sibling blocks (arr.each { |item| item[:k] }; other.each { |item| item[:k] }) is correctly treated as two different bindings, not a repeat. Reads of the same block param within one block are still flagged. (Numbered/it block params are matched textually — a rare residual false positive; inline-disable if it surfaces.)

NOTE: Multi-argument bracket calls (arr[i, len], slicing) are ignored. Only single-argument reads participate.

Examples:

# bad — same key read twice
def show
  @item = Item.lookup_by_slug(params[:slug])
  redirect_to canonical_url(@item) if @item.slug != params[:slug]
end

# bad — guard-then-use re-reads the receiver
def session_get(key)
  return nil unless session[key]
  session[key].symbolize_keys
end

# good — assign once
def session_get(key)
  payload = session[key]
  return nil unless payload
  payload.symbolize_keys
end

# good — different keys, no offense
def filters
  @search = params[:search]
  @status = params[:status]
end

Constant Summary collapse

MSG =
"Receiver `%<receiver>s[%<key>s]` already read earlier in this method — assign once and reuse.".freeze

Instance Method Summary collapse

Instance Method Details

#on_def(node) ⇒ Object Also known as: on_defs



92
93
94
# File 'lib/rubocop/cop/dev_doc/style/repeated_bracket_read.rb', line 92

def on_def(node)
  check_method(node)
end