# frozen_string_literal: true module RuboCop module Cop module Lint # This cop checks for loops that will have at most one iteration. # # A loop that can never reach the second iteration is a possible error in the code. # In rare cases where only one iteration (or at most one iteration) is intended behavior, # the code should be refactored to use `if` conditionals. # # @example # # bad # while node # do_something(node) # node = node.parent # break # end # # # good # while node # do_something(node) # node = node.parent # end # # # bad # def verify_list(head) # item = head # begin # if verify(item) # return true # else # return false # end # end while(item) # end # # # good # def verify_list(head) # item = head # begin # if verify(item) # item = item.next # else # return false # end # end while(item) # # true # end # # # bad # def find_something(items) # items.each do |item| # if something?(item) # return item # else # raise NotFoundError # end # end # end # # # good # def find_something(items) # items.each do |item| # if something?(item) # return item # end # end # raise NotFoundError # end # class UnreachableLoop < Base MSG = 'This loop will have at most one iteration.' def on_while(node) check(node) end alias on_until on_while alias on_while_post on_while alias on_until_post on_while alias on_for on_while def on_block(node) check(node) if loop_method?(node) end private def loop_method?(node) return false unless node.block_type? send_node = node.send_node send_node.enumerable_method? || send_node.enumerator_method? || send_node.method?(:loop) end def check(node) statements = statements(node) break_statement = statements.find { |statement| break_statement?(statement) } return unless break_statement add_offense(node) unless preceded_by_continue_statement?(break_statement) end def statements(node) body = node.body if body.nil? [] elsif body.begin_type? body.children else [body] end end def_node_matcher :break_command?, <<~PATTERN { return break (send {nil? (const {nil? cbase} :Kernel)} {:raise :fail :throw :exit :exit! :abort} ...) } PATTERN def break_statement?(node) return true if break_command?(node) case node.type when :begin, :kwbegin statements = *node break_statement = statements.find { |statement| break_statement?(statement) } break_statement && !preceded_by_continue_statement?(break_statement) when :if check_if(node) when :case check_case(node) else false end end def check_if(node) if_branch = node.if_branch else_branch = node.else_branch if_branch && else_branch && break_statement?(if_branch) && break_statement?(else_branch) end def check_case(node) else_branch = node.else_branch return false unless else_branch return false unless break_statement?(else_branch) node.when_branches.all? do |branch| branch.body && break_statement?(branch.body) end end def preceded_by_continue_statement?(break_statement) break_statement.left_siblings.any? do |sibling| next if sibling.loop_keyword? || loop_method?(sibling) sibling.each_descendant(:next, :redo).any? end end end end end end