Class: RuboCop::Cop::DevDoc::Style::CaseElseDecision

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

Overview

Every case must carry an else that DECIDES what a non-matching value means — this cop supersedes Style/MissingElse (EnforcedStyle: case), whose "Missing else statement" message teaches the wrong reflex: add an else, any else.

Rationale

A case without else makes "exhaustive dispatch" and "deliberate partial match" indistinguishable — an unanticipated value silently evaluates to nil. The fix is not a bare else; it is a decision. Three legitimate outcomes:

  1. Fall-through genuinely happens in normal operation — add else with a comment stating WHY it is correct (requires Style/EmptyElse to be disabled).
  2. A non-match is a bug and continuing is unsafeelse raise (fail fast; unexpected enum/mode values must not proceed).
  3. A non-match is a bug but users can safely continue — report to the error tracker, then degrade gracefully (return the neutral value). Prefer this over raising in user-facing render paths.

Never pick 1 by reflex: if you cannot write down why the else legitimately happens, it belongs in bucket 2 or 3. Also weigh WHERE the case runs: raising on user-supplied input that executes before authorization turns garbage requests into 500s, and error-tracker reports on pre-auth, scanner-reachable paths are noise, not signal.

Interaction with Style/MissingElse

Same detection surface (case statements and case/in pattern matches; if is exempt) — running both double-flags every offense. Disable Style/MissingElse when enabling this cop:

Style/MissingElse:
Enabled: false

Examples:

# bad — a value nobody anticipated silently becomes nil
case status
when :active then process
when :archived then skip
end

# good — closed set: fail fast
case status
when :active then process
when :archived then skip
else
  raise "Unexpected status: #{status}"
end

# good — bug, but users can continue: report and degrade
case status
when :active then process
when :archived then skip
else
  ErrorTracker.error("Unexpected status: #{status}")
  nil
end

# good — legitimate fall-through, reason stated
case action_name.to_sym
when :new, :create then load_import
else
  # No resource setup needed for the remaining actions.
end

Constant Summary collapse

MSG =
'`case` has no `else` — decide what a non-match means: ' \
'`raise` if continuing is unsafe (closed set); report to the ' \
'error tracker and degrade gracefully if users can continue; ' \
'or `else` + a comment stating why fall-through is ' \
'legitimate. Never add a bare `else` by reflex.'.freeze

Instance Method Summary collapse

Instance Method Details

#on_case(node) ⇒ Object



76
77
78
# File 'lib/rubocop/cop/dev_doc/style/case_else_decision.rb', line 76

def on_case(node)
  add_offense(node.loc.keyword) unless node.else?
end

#on_case_match(node) ⇒ Object



80
81
82
# File 'lib/rubocop/cop/dev_doc/style/case_else_decision.rb', line 80

def on_case_match(node)
  add_offense(node.loc.keyword) unless node.else?
end