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` 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` keep the spelling in sync, but if one of them silently becomes `params`, 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[:slug])
  redirect_to canonical_url(@item) if @item.slug != params[:slug]
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 = 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` and `hash` 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 }; other.each { |item| item }`) 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