Class: RuboCop::Cop::DevDoc::Style::RedundantGuardAfterBang

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

Overview

Flag a nil/presence guard on a variable that was just assigned from a bang method. Bang methods raise on failure instead of returning nil/false, so the guard can never fire — it is dead code that hides the real contract and invites style churn (e.g. Rails/Presence "fixing" the guard's shape instead of questioning its existence).

Rationale

By convention a ! method signals failure by raising (create!, save!, find_by!, custom do_thing!). Guarding its result reads as if nil were possible, which misleads reviewers about the failure mode and preserves defensive noise through refactors. The fix is to delete the guard, not to restyle it.

Ruby-core mutator bangs (gsub!, uniq!, compact!, ...) are the exception — they return nil when nothing changed, so guarding them is legitimate. Those are excluded via the default AllowedMethods; add any app-specific nil-returning bang there too (or better, rename it, since a nil-returning ! method is itself misleading).

Caveat on present?/blank?: for a bang that returns a possibly-empty collection or string, a present? check is emptiness logic rather than a nil-guard, and is NOT dead — the cop cannot distinguish this statically, so such methods also belong in AllowedMethods. Plain truthiness (if x), nil?, and &. guards carry no such ambiguity: [] and "" are truthy, so those forms only ever test for nil.

❌ dead guard — create! raises rather than return nil
snapshot = create_snapshot!(**attrs)
if snapshot.present?
snapshot.update_columns(version: version)
end

✔️ no guard — the bang contract is the guard
snapshot = create_snapshot!(**attrs)
snapshot.update_columns(version: version)

✔️ core mutator bang — nil means "no change", guard is real logic
cleaned = name.strip!
apply(cleaned) if cleaned

Examples:

# bad
record = Order.create!(params)
record&.notify_owner

# good
record = Order.create!(params)
record.notify_owner

Constant Summary collapse

MSG =
'`%<method>s` raises on failure instead of returning nil, ' \
'so this guard on `%<var>s` is dead; remove it.'.freeze

Instance Method Summary collapse

Instance Method Details

#on_lvasgn(node) ⇒ Object Also known as: on_ivasgn



84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
# File 'lib/rubocop/cop/dev_doc/style/redundant_guard_after_bang.rb', line 84

def on_lvasgn(node)
  # `a ||= x` wraps a value-less lvasgn in an or_asgn; nothing to check.
  return unless node.expression

  method_name = bang_call(node.expression)
  return unless bang_method?(method_name)
  return if allowed_method?(method_name)

  following = next_sibling(node)
  return unless following

  guard = guard_node(following, node.name)
  return unless guard

  add_offense(guard, message: format(MSG, method: method_name, var: node.name))
end