class RuboCop::Cop::Lint::UnmodifiedReduceAccumulator

end
bar(x)
x = foo(acc, el)
enum.reduce do |acc, el|
# ignored as the return value cannot be determined
keys.reduce(self) { |result, key| result }
# good, recursive
end
value
break result if something?
values.reduce(nil) do |result, value|
# good, at least one branch returns the accumulator<br><br>end<br>acc<br>acc = true
%w(a b c).reduce({}) do |acc, letter|
# good, returns the accumulator instead of the index
end
el
el << acc
values.reduce do |acc, el|
# good, element is returned but modified using the accumulator
end
acc + el * 2
(1..4).reduce(0) do |acc, el|
# good<br><br>end<br>acc = true
%w(a b c).reduce({}) do |acc, letter|
# bad, may raise a NoMethodError after the first iteration
end
el * 2
(1..4).reduce(0) do |acc, el|
# bad
@example
relate to via static analysis).
the expression (since we will not be able to tell what other variables
returns in ‘reduce` blocks where the element is the only variable in
NOTE: For the purpose of reducing false positives, this cop only flags
this may change the type of object being retained.
Also catches instances where an index of the accumulator is returned, as
could be rewritten as such without a loop.
block will just return a transformation of the last element value, and
If the accumulator is not included in the return value, then the entire
long as at least one return value includes the accumulator.
explicitly) does not include the accumulator. A block is considered valid as
Looks for `reduce` or `inject` blocks where the value returned (implicitly or

def acceptable_return?(return_val, element_name)

otherwise do not have enough information to prevent false positives.
Otherwise, it is only unacceptable if it contains the iterated element, since we
If it is an expression containing the accumulator, it is acceptable
Determine if a return value is acceptable for the purposes of this cop
def acceptable_return?(return_val, element_name)
  vars = expression_values(return_val).uniq
  return true if vars.none? || (vars - [element_name]).any?
  false
end

def allowed_type?(parent_node)

Exclude `begin` nodes inside a `dstr` from being collected by `return_values`
def allowed_type?(parent_node)
  !parent_node.dstr_type?
end

def block_arg_name(node, index)

def block_arg_name(node, index)
  node.argument_list[index].name
end

def check_return_values(block_node)

def check_return_values(block_node)
  return_values = return_values(block_node.body)
  accumulator_name = block_arg_name(block_node, 0)
  element_name = block_arg_name(block_node, 1)
  message_opts = { method: block_node.method_name, accum: accumulator_name }
  if (node = returned_accumulator_index(return_values, accumulator_name, element_name))
    add_offense(node, message: format(MSG_INDEX, message_opts))
  elsif potential_offense?(return_values, block_node.body, element_name, accumulator_name)
    return_values.each do |return_val|
      unless acceptable_return?(return_val, element_name)
        add_offense(return_val, message: format(MSG, message_opts))
      end
    end
  end
end

def on_block(node)

def on_block(node)
  return unless node.body
  return unless reduce_with_block?(node)
  return unless node.argument_list.length >= 2
  check_return_values(node)
end

def potential_offense?(return_values, block_body, element_name, accumulator_name)

def potential_offense?(return_values, block_body, element_name, accumulator_name)
  !(element_modified?(block_body, element_name) ||
    returns_accumulator_anywhere?(return_values, accumulator_name))
end

def return_values(block_body_node)

the last line of a multiline block, or the only line of the block
Return values in a block are either the value given to next,
def return_values(block_body_node)
  nodes = [block_body_node.begin_type? ? block_body_node.child_nodes.last : block_body_node]
  block_body_node.each_descendant(:next, :break) do |n|
    # Ignore `next`/`break` inside an inner block
    next if n.each_ancestor(:any_block).first != block_body_node.parent
    next unless n.first_argument
    nodes << n.first_argument
  end
  nodes
end

def returned_accumulator_index(return_values, accumulator_name, element_name)

due to type mismatches
This is always an offense, in order to try to catch potential exceptions
is the element.
Look for an index of the accumulator being returned, except where the index
def returned_accumulator_index(return_values, accumulator_name, element_name)
  return_values.detect do |val|
    next unless accumulator_index?(val, accumulator_name)
    next true if val.method?(:[]=)
    val.arguments.none? { |arg| lvar_used?(arg, element_name) }
  end
end

def returns_accumulator_anywhere?(return_values, accumulator_name)

the accumulator has a chance to change each iteration
If the accumulator is used in any return value, the node is acceptable since
def returns_accumulator_anywhere?(return_values, accumulator_name)
  return_values.any? { |node| lvar_used?(node, accumulator_name) }
end