Class: RuboCop::Cop::DevDoc::Style::RedundantGuardAfterBang
- Inherits:
-
Base
- Object
- Base
- RuboCop::Cop::DevDoc::Style::RedundantGuardAfterBang
- 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
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
- #on_lvasgn(node) ⇒ Object (also: #on_ivasgn)
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 |