Class: RuboCop::Cop::Style::ProcCaseWhen
- Inherits:
-
Base
- Object
- Base
- RuboCop::Cop::Style::ProcCaseWhen
- Defined in:
- lib/rubocop/cop/style/proc_case_when.rb
Overview
Checks for case statements whose when conditions are only proc/lambda
literals and value literals (with at least one proc).
Such a case is just a harder-to-read if/elsif tree with a needless
performance cost: a when clause matches using pattern === subject,
and for a proc Proc#=== is an alias for Proc#call. Every inline proc
literal therefore allocates a brand new Proc object each time the
case is evaluated (on a hot path, once per branch per call) and adds
Proc#call indirection, where an if/elsif allocates nothing. For a
value literal (number, string, symbol, nil, true, false) === is
==, so the whole statement is exactly equivalent to if/elsif using
Proc#call and ==. Use if/elsif instead.
Constants assigned a proc literal or a value literal in the same file
are resolved and treated as such. Constants defined in other files cannot
be resolved: a bare when SOME_CONST is indistinguishable from matching
against a class, so those are left alone to avoid flagging idiomatic
case obj when SomeClass.
Cases that use class, range, or regexp patterns are also left alone:
those rely on === in ways that read far worse as if conditions
(is_a?, cover?, match?), which is exactly what case is for.
Constant Summary collapse
- MSG =
"Avoid a `case`/`when` where every `when` is a proc or value " \ "literal: each proc literal allocates a new `Proc` every time the " \ "`case` is evaluated and adds `Proc#call` overhead, and the whole " \ "thing is just a harder-to-read `if`/`elsif` tree. Use `if`/`elsif` " \ "instead."
Instance Method Summary collapse
- #on_case(node) ⇒ Object
- #on_new_investigation ⇒ Object
-
#proc_literal?(node) ⇒ Object
Matches
->(x) {},lambda {},proc {}andProc.new {}, including their numbered-parameter (_1) block variants.
Instance Method Details
#on_case(node) ⇒ Object
84 85 86 87 88 89 90 91 92 93 94 95 96 97 98 99 |
# File 'lib/rubocop/cop/style/proc_case_when.rb', line 84 def on_case(node) # `case` without a subject evaluates each `when` for truthiness rather # than with `===`, so procs there are not used as matchers. return unless node.condition kinds = node.when_branches.flat_map(&:conditions).map { |condition| kind_of(condition) } # Require at least one proc; a case of only value literals is a fine # dispatch and out of scope here. return unless kinds.include?(:proc) # Bail out if any condition is something other than a proc or a value # literal (e.g. a class, range, regexp, or an unresolvable constant), # where `case` reads better than the equivalent `if`. return unless kinds.all? { |kind| kind == :proc || kind == :value } add_offense(node) end |
#on_new_investigation ⇒ Object
79 80 81 82 |
# File 'lib/rubocop/cop/style/proc_case_when.rb', line 79 def on_new_investigation super @constant_kinds = collect_constant_kinds end |
#proc_literal?(node) ⇒ Object
Matches ->(x) {}, lambda {}, proc {} and Proc.new {}, including
their numbered-parameter (_1) block variants.
72 73 74 75 76 77 |
# File 'lib/rubocop/cop/style/proc_case_when.rb', line 72 def_node_matcher :proc_literal?, <<~PATTERN { ({block numblock} (send nil? {:lambda :proc}) ...) ({block numblock} (send (const {nil? cbase} :Proc) :new) ...) } PATTERN |