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))