# frozen_string_literal: truemoduleRuboCopmoduleCopmoduleStyle# Checks for identical expressions at the beginning or end of# each branch of a conditional expression. Such expressions should normally# be placed outside the conditional expression - before or after it.## NOTE: The cop is poorly named and some people might think that it actually# checks for duplicated conditional branches. The name will probably be changed# in a future major RuboCop release.## @safety# Autocorrection is unsafe because changing the order of method invocations# may change the behavior of the code. For example:## [source,ruby]# ----# if method_that_modifies_global_state # 1# method_that_relies_on_global_state # 2# foo # 3# else# method_that_relies_on_global_state # 2# bar # 3# end# ----## In this example, `method_that_relies_on_global_state` will be moved before# `method_that_modifies_global_state`, which changes the behavior of the program.## @example# # bad# if condition# do_x# do_z# else# do_y# do_z# end## # good# if condition# do_x# else# do_y# end# do_z## # bad# if condition# do_z# do_x# else# do_z# do_y# end## # good# do_z# if condition# do_x# else# do_y# end## # bad# case foo# when 1# do_x# when 2# do_x# else# do_x# end## # good# case foo# when 1# do_x# do_y# when 2# # nothing# else# do_x# do_z# end## # bad# case foo# in 1# do_x# in 2# do_x# else# do_x# end## # good# case foo# in 1# do_x# do_y# in 2# # nothing# else# do_x# do_z# endclassIdenticalConditionalBranches<BaseincludeRangeHelpextendAutoCorrectorMSG='Move `%<source>s` out of the conditional.'defon_if(node)returnifnode.elsif?branches=expand_elses(node.else_branch).unshift(node.if_branch)check_branches(node,branches)enddefon_case(node)returnunlessnode.else?&&node.else_branchbranches=node.when_branches.map(&:body).push(node.else_branch)check_branches(node,branches)enddefon_case_match(node)returnunlessnode.else?&&node.else_branchbranches=node.in_pattern_branches.map(&:body).push(node.else_branch)check_branches(node,branches)endprivate# rubocop:disable Metrics/AbcSize, Metrics/CyclomaticComplexity, Metrics/PerceivedComplexitydefcheck_branches(node,branches)# return if any branch is empty. An empty branch can be an `if`# without an `else` or a branch that contains only comments.returnifbranches.any?(&:nil?)tails=branches.map{|branch|tail(branch)}check_expressions(node,tails,:after_condition)ifduplicated_expressions?(node,tails)returniflast_child_of_parent?(node)&&branches.any?{|branch|single_child_branch?(branch)}heads=branches.map{|branch|head(branch)}returnunlessduplicated_expressions?(node,heads)condition_variable=assignable_condition_value(node)head=heads.firstifhead.respond_to?(:assignment?)&&head.assignment?# The `send` node is used instead of the `indexasgn` node, so `name` cannot be used.# https://github.com/rubocop/rubocop-ast/blob/v1.29.0/lib/rubocop/ast/node/indexasgn_node.rb## FIXME: It would be better to update `RuboCop::AST::OpAsgnNode` or its subclasses to# handle `self.foo ||= value` as a solution, instead of using `head.node_parts[0].to_s`.assigned_value=head.send_type??head.receiver.source:head.node_parts[0].to_sreturnifcondition_variable==assigned_valueendcheck_expressions(node,heads,:before_condition)end# rubocop:enable Metrics/AbcSize, Metrics/CyclomaticComplexity, Metrics/PerceivedComplexitydefduplicated_expressions?(node,expressions)unique_expressions=expressions.uniqreturnfalseunlessexpressions.size>=1&&unique_expressions.one?unique_expression=unique_expressions.firstreturntrueunlessunique_expression&.assignment?lhs=unique_expression.child_nodes.firstnode.condition.child_nodes.none?{|n|n.source==lhs.sourceifn.variable?}enddefassignable_condition_value(node)ifnode.condition.call_type?(receiver=node.condition.receiver)?receiver.source:node.condition.sourceelsifnode.condition.variable?node.condition.sourceendend# rubocop:disable Metrics/AbcSize, Metrics/CyclomaticComplexity, Metrics/MethodLength, Metrics/PerceivedComplexitydefcheck_expressions(node,expressions,insert_position)returnifexpressions.any?(&:nil?)inserted_expression=falseexpressions.eachdo|expression|add_offense(expression)do|corrector|nextifnode.if_type?&&(node.ternary?||node.then?)range=range_by_whole_lines(expression.source_range,include_final_newline: true)corrector.remove(range)nextifinserted_expressionifnode.parent&.assignment?correct_assignment(corrector,node,expression,insert_position)elsecorrect_no_assignment(corrector,node,expression,insert_position)endinserted_expression=trueendendend# rubocop:enable Metrics/AbcSize, Metrics/CyclomaticComplexity, Metrics/MethodLength, Metrics/PerceivedComplexitydefcorrect_assignment(corrector,node,expression,insert_position)ifinsert_position==:after_conditionassignment=node.parent.source_range.with(end_pos: node.source_range.begin_pos)corrector.remove(assignment)corrector.insert_after(node,"\n#{assignment.source}#{expression.source}")elsecorrector.insert_before(node.parent,"#{expression.source}\n")endenddefcorrect_no_assignment(corrector,node,expression,insert_position)ifinsert_position==:after_conditioncorrector.insert_after(node,"\n#{expression.source}")elsecorrector.insert_before(node,"#{expression.source}\n")endenddeflast_child_of_parent?(node)returntrueunless(parent=node.parent)parent.child_nodes.last==nodeenddefsingle_child_branch?(branch_node)!branch_node.begin_type?||branch_node.children.size==1enddefmessage(node)format(MSG,source: node.source)end# `elsif` branches show up in the if node as nested `else` branches. We# need to recursively iterate over all `else` branches.defexpand_elses(branch)ifbranch.nil?[nil]elsifbranch.if_type?_condition,elsif_branch,else_branch=*branchexpand_elses(else_branch).unshift(elsif_branch)else[branch]endenddeftail(node)node.begin_type??node.children.last:nodeenddefhead(node)node.begin_type??node.children.first:nodeendendendendend