class RuboCop::Cop::Performance::CaseWhenSplat
end
baz
when 5
bar
when 1, 2, 3, 4
case foo
end
bar
when *condition
foobar
when baz
case foo
# good
end
baz
when 5
bar
when *[1, 2, 3, 4]
case foo
end
foobar
when baz
bar
when *condition
case foo
# bad
@example
and run slightly slower.
moving the splat condition to the end will use more memory,
hitting a condition in the splat expansion, it is possible that
processed by the ‘case` condition is normalized in a manner that favors
This is not a guaranteed performance improvement. If the data being
the expansion.
reduce the number of times that memory has to be allocated for
splat expansions at the end of the list of `when` branches we will
the order of the `when` branches does not matter. By placing any
fall through inside of `case` `when`, like some other languages do,
that the `case` `when` statement is run. Since Ruby does not support
Ruby has to allocate memory for the splat expansion every time
of the list of `when` branches.
Place `when` conditions that use splat at the end
def autocorrect(node)
def autocorrect(node) *conditions, _body = *node new_condition = conditions.each_with_object([]) do |condition, correction| variable, = *condition if variable.respond_to?(:array_type?) && variable.array_type? correction << expand_percent_array(variable) next end correction << condition.source end new_condition = new_condition.join(', ') lambda do |corrector| if needs_reorder?(conditions) reorder_condition(corrector, node, new_condition) else inline_fix_branch(corrector, node, conditions, new_condition) end end end
def error_condition?(condition)
def error_condition?(condition) variable, = *condition (condition.splat_type? && variable.array_type?) || !condition.splat_type? end
def expand_percent_array(array)
def expand_percent_array(array) array_start = array.loc.begin.source elements = *array elements = elements.map(&:source) if array_start.start_with?(PERCENT_W) "'#{elements.join("', '")}'" elsif array_start.start_with?(PERCENT_CAPITAL_W) %("#{elements.join('", "')}") elsif array_start.start_with?(PERCENT_I) ":#{elements.join(', :')}" elsif array_start.start_with?(PERCENT_CAPITAL_I) %(:"#{elements.join('", :"')}") else elements.join(', ') end end
def inline_fix_branch(corrector, node, conditions, new_condition)
def inline_fix_branch(corrector, node, conditions, new_condition) range = Parser::Source::Range.new(node.loc.expression.source_buffer, conditions[0].loc.expression.begin_pos, conditions[-1].loc.expression.end_pos) corrector.replace(range, new_condition) end
def needs_reorder?(conditions)
def needs_reorder?(conditions) conditions.any? do |condition| variable, = *condition condition.splat_type? && !(variable && variable.array_type?) end end
def new_branch_without_then(node, body, new_condition)
def new_branch_without_then(node, body, new_condition) "\n#{' ' * node.loc.column}when #{new_condition}\n" \ "#{' ' * body.loc.column}#{node.children.last.source}" end
def new_condition_with_then(node, new_condition)
def new_condition_with_then(node, new_condition) "\n#{' ' * node.loc.column}when " \ "#{new_condition} then #{node.children.last.source}" end
def on_case(node)
def on_case(node) _case_branch, *when_branches, _else_branch = *node when_conditions = when_branches.each_with_object([]) do |branch, conditions| *condition, _ = *branch condition.each { |c| conditions << c } end splat_offenses(when_conditions).reverse_each do |condition| range = condition.parent.loc.keyword.join(condition.source_range) variable, = *condition message = variable.array_type? ? ARRAY_MSG : MSG add_offense(condition.parent, range, message) end end
def reorder_condition(corrector, node, new_condition)
def reorder_condition(corrector, node, new_condition) *_conditions, body = *node parent = node.parent _case_branch, *when_branches, _else_branch = *parent current_index = when_branches.index { |branch| branch == node } next_branch = when_branches[current_index + 1] range = Parser::Source::Range.new(parent, node.source_range.begin_pos, next_branch.source_range.begin_pos) corrector.remove(range) correction = if same_line?(node, body) new_condition_with_then(node, new_condition) else new_branch_without_then(node, body, new_condition) end corrector.insert_after(when_branches.last.source_range, correction) end
def same_line?(node, other)
def same_line?(node, other) node.loc.first_line == other.loc.first_line end
def splat_offenses(when_conditions)
def splat_offenses(when_conditions) found_non_splat = false when_conditions.reverse.each_with_object([]) do |condition, result| found_non_splat ||= error_condition?(condition) next unless condition.splat_type? result << condition if found_non_splat end end