lib/rubocop/cop/style/infinite_loop.rb in rubocop-0.60.0 vs lib/rubocop/cop/style/infinite_loop.rb in rubocop-0.61.0

- old
+ new

@@ -18,20 +18,25 @@ class InfiniteLoop < Cop LEADING_SPACE = /\A(\s*)/.freeze MSG = 'Use `Kernel#loop` for infinite loops.'.freeze - def on_while(node) - return unless node.condition.truthy_literal? + def join_force?(force_class) + force_class == VariableForce + end - add_offense(node, location: :keyword) + def after_leaving_scope(scope, _variable_table) + @variables ||= [] + @variables.concat(scope.variables.values) end - def on_until(node) - return unless node.condition.falsey_literal? + def on_while(node) + while_or_until(node) if node.condition.truthy_literal? + end - add_offense(node, location: :keyword) + def on_until(node) + while_or_until(node) if node.condition.falsey_literal? end alias on_while_post on_while alias on_until_post on_until @@ -44,9 +49,40 @@ replace_source(non_modifier_range(node), 'loop do') end end private + + def while_or_until(node) + range = node.source_range + # Not every `while true` and `until false` can be turned into a + # `loop do` without further modification. The reason is that a + # variable that's introduced inside a while/until loop is in scope + # outside of that loop too, but a variable that's assigned for the + # first time inside a block can not be accessed after the block. In + # those more complicated cases we don't report an offense. + return if @variables.any? do |var| + assigned_inside_loop?(var, range) && + !assigned_before_loop?(var, range) && + referenced_after_loop?(var, range) + end + + add_offense(node, location: :keyword) + end + + def assigned_inside_loop?(var, range) + var.assignments.any? { |a| range.contains?(a.node.source_range) } + end + + def assigned_before_loop?(var, range) + b = range.begin_pos + var.assignments.any? { |a| a.node.source_range.end_pos < b } + end + + def referenced_after_loop?(var, range) + e = range.end_pos + var.references.any? { |r| r.node.source_range.begin_pos > e } + end def replace_begin_end_with_modifier(node) lambda do |corrector| corrector.replace(node.body.loc.begin, 'loop do') corrector.remove(node.body.loc.end.end.join(node.source_range.end))