rubocop-hq/rubocop

View on GitHub
lib/rubocop/cop/style/redundant_self_assignment_branch.rb

Summary

Maintainability
A
25 mins
Test Coverage
A
100%
# frozen_string_literal: true

module RuboCop
  module Cop
    module Style
      # Checks for places where conditional branch makes redundant self-assignment.
      #
      # It only detects local variable because it may replace state of instance variable,
      # class variable, and global variable that have state across methods with `nil`.
      #
      # @example
      #
      #   # bad
      #   foo = condition ? bar : foo
      #
      #   # good
      #   foo = bar if condition
      #
      #   # bad
      #   foo = condition ? foo : bar
      #
      #   # good
      #   foo = bar unless condition
      #
      class RedundantSelfAssignmentBranch < Base
        include RangeHelp
        extend AutoCorrector

        MSG = 'Remove the self-assignment branch.'

        # @!method bad_method?(node)
        def_node_matcher :bad_method?, <<~PATTERN
          (send nil? :bad_method ...)
        PATTERN

        def on_lvasgn(node)
          variable, expression = *node
          return unless use_if_and_else_branch?(expression)

          if_branch = expression.if_branch
          else_branch = expression.else_branch
          return if inconvertible_to_modifier?(if_branch, else_branch)

          if self_assign?(variable, if_branch)
            register_offense(expression, if_branch, else_branch, 'unless')
          elsif self_assign?(variable, else_branch)
            register_offense(expression, else_branch, if_branch, 'if')
          end
        end

        private

        def use_if_and_else_branch?(expression)
          return false unless expression&.if_type?

          !expression.ternary? || !expression.else?
        end

        def inconvertible_to_modifier?(if_branch, else_branch)
          multiple_statements?(if_branch) || multiple_statements?(else_branch) ||
            (else_branch.respond_to?(:elsif?) && else_branch.elsif?)
        end

        def multiple_statements?(branch)
          return false unless branch&.begin_type?

          !branch.children.empty?
        end

        def self_assign?(variable, branch)
          variable.to_s == branch&.source
        end

        def register_offense(if_node, offense_branch, opposite_branch, keyword)
          add_offense(offense_branch) do |corrector|
            assignment_value = opposite_branch ? opposite_branch.source : 'nil'
            replacement = "#{assignment_value} #{keyword} #{if_node.condition.source}"
            if opposite_branch.respond_to?(:heredoc?) && opposite_branch.heredoc?
              replacement += opposite_branch.loc.heredoc_end.with(
                begin_pos: opposite_branch.source_range.end_pos
              ).source
            end

            corrector.replace(if_node, replacement)
          end
        end
      end
    end
  end
end