Class: RuboCop::Cop::DevDoc::Style::MinimizeVariableScope

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

Overview

Assign a variable inside the ‘if` condition that guards it, so the variable’s scope is the branch that actually uses it.

## Rationale When a local is assigned and then immediately gated by a truthiness check, hoisting the assignment to its own line widens its scope to the whole method and separates the binding from the guard. Folding the assignment into the condition (with parentheses) keeps the variable local to the branch that uses it and reads as one thought.


token = params[:token]
if token
  authenticate(token)
end

✔️
if (token = params[:token])
  authenticate(token)
end

Parentheses around the assignment silence Ruby’s “assignment in condition” warning and signal the assignment is intentional.

## When it fires Only when the assigned variable is used only inside the guarding ‘if` — its condition plus the true branch — and is read at least once in that branch. If the variable is read in the `else` branch or after the block, folding wouldn’t narrow its scope, so the cop leaves it.

## Exception When the assigned expression is long, inlining it into the condition hurts readability more than the scope-narrowing helps. Keep the two-line form and inline-‘disable` with a reason.

NOTE: Conservative by design — it skips reassigned variables, ‘op_asgn` (`+=`, `||=`), compound/comparison conditions, and scopes containing a nested `def` or a block that rebinds the same name. Those are left un-flagged rather than risk a wrong rewrite.

Examples:

# bad
user = .users.find_by(id: params[:id])
if user
  redirect_to user
end

# good
if (user = .users.find_by(id: params[:id]))
  redirect_to user
end

Constant Summary collapse

MSG =
"Assign `%<name>s` inside the `if` condition " \
"(`if (%<name>s = ...)`) so its scope is the branch that uses it.".freeze
TRUTHY_PREDICATES =

Truthiness-shaped predicates we fold. Comparisons (‘x == 1`) are deliberately excluded — folding those reads as assignment-in-condition.

%i[present? any? presence].freeze

Instance Method Summary collapse

Instance Method Details

#on_lvasgn(node) ⇒ Object



64
65
66
67
68
69
70
71
72
73
74
# File 'lib/rubocop/cop/dev_doc/style/minimize_variable_scope.rb', line 64

def on_lvasgn(node)
  name, value = *node
  return unless value

  if_node = guarding_if(node)
  return unless if_node
  return unless truthiness_check?(if_node.condition, name)
  return unless confined_to_if?(node, if_node, name)

  add_offense(node.loc.name, message: format(MSG, name: name))
end