class RuboCop::Cop::Style::ParallelAssignment
c = 3
b = 2
a = 1
a, b = b, a
a, b = foo()
one, two = *foo
# good
a, b, c = [1, 2, 3]
a, b, c = 1, 2, 3
# bad
@example
being assigned matched the number of assigning variables.
This will only complain when the number of variables
Checks for simple usages of parallel assignment.
def add_self_to_getters(right_elements)
This makes the sorting algorithm work for expressions such as
Converts (send nil :something) nodes to (send (:self) :something).
def add_self_to_getters(right_elements) right_elements.map do |e| implicit_self_getter?(e) { |var| s(:send, s(:self), var) } || e end end
def allowed_lhs?(elements)
def allowed_lhs?(elements) # Account for edge cases using one variable with a comma # E.g.: `foo, = *bar` elements.one? || elements.any?(&:splat_type?) end
def allowed_masign?(lhs_elements, rhs_elements)
def allowed_masign?(lhs_elements, rhs_elements) lhs_elements.size != rhs_elements.size || !find_valid_order(lhs_elements, add_self_to_getters(rhs_elements)) end
def allowed_rhs?(node)
def allowed_rhs?(node) # Edge case for one constant elements = Array(node).compact # Account for edge case of `Constant::CONSTANT` !node.array_type? || elements.any?(&:splat_type?) end
def assignment_corrector(node, rhs, order)
def assignment_corrector(node, rhs, order) if node.parent&.rescue_type? _assignment, modifier = *node.parent else _assignment, modifier = *rhs.parent end if modifier_statement?(node.parent) ModifierCorrector.new(node, rhs, modifier, config, order) elsif rescue_modifier?(modifier) RescueCorrector.new(node, rhs, modifier, config, order) else GenericCorrector.new(node, rhs, modifier, config, order) end end
def autocorrect(corrector, node, rhs)
def autocorrect(corrector, node, rhs) order = find_valid_order(node.assignments, Array(rhs).compact) correction = assignment_corrector(node, rhs, order) corrector.replace(correction.correction_range, correction.correction) end
def find_valid_order(left_elements, right_elements)
def find_valid_order(left_elements, right_elements) # arrange left_elements in an order such that no corresponding right # element refers to a left element earlier in the sequence # this can be done using an algorithm called a "topological sort" # fortunately for us, Ruby's stdlib contains an implementation assignments = left_elements.zip(right_elements) begin AssignmentSorter.new(assignments).tsort rescue TSort::Cyclic nil end end
def modifier_statement?(node)
def modifier_statement?(node) return false unless node node.basic_conditional? && node.modifier_form? end
def on_masgn(node) # rubocop:disable Metrics/AbcSize
def on_masgn(node) # rubocop:disable Metrics/AbcSize rhs = node.rhs rhs = rhs.body if rhs.rescue_type? rhs_elements = Array(rhs).compact # edge case for one constant return if allowed_lhs?(node.assignments) || allowed_rhs?(rhs) || allowed_masign?(node.assignments, rhs_elements) range = node.source_range.begin.join(rhs.source_range.end) add_offense(range) do |corrector| autocorrect(corrector, node, rhs) end end