troessner/reek

View on GitHub
lib/reek/smell_detectors/control_parameter_helpers/call_in_condition_finder.rb

Summary

Maintainability
A
0 mins
Test Coverage
# frozen_string_literal: true

module Reek
  module SmellDetectors
    module ControlParameterHelpers
      #
      # CallInConditionFinder finds usages of the given parameter
      # in the context of a method call in a condition, e.g.:
      #
      # def alfa(bravo)
      #   if charlie(bravo)
      #     delta
      #   end
      # end
      #
      # or
      #
      # def alfa(bravo)
      #   if bravo.charlie?
      #     delta
      #   end
      # end
      #
      # Those usages are legit and should not trigger the ControlParameter smell warning.
      #
      class CallInConditionFinder
        COMPARISON_METHOD_NAMES = [:==, :!=, :=~].freeze

        #
        # @param node [Reek::AST::Node] the node in our current scope,
        #   e.g. s(:def, :alfa,
        #          s(:args,
        #            s(:arg, :bravo),
        # @param parameter [Symbol] the parameter name in question
        #   e.g. in the example above this would be :bravo
        #
        def initialize(node, parameter)
          @node = node
          @parameter = parameter
        end

        #
        # @return [Boolean] if the parameter in question has been used in the context of a
        #   method call in a condition
        #
        def uses_param_in_call_in_condition?
          condition = node.condition
          return false unless condition

          condition.each_node(:send) do |inner|
            return true if regular_call_involving_param?(inner)
          end
          false
        end

        private

        attr_reader :node, :parameter

        #
        # @return [Boolean] if the parameter is used in a method call that is not a comparison call
        #   e.g. this would return true given that "bravo" is the parameter in question:
        #
        #     if charlie(bravo) then delta end
        #
        #   while this would return false (since its a comparison):
        #
        #     if bravo == charlie then charlie end
        #
        def regular_call_involving_param?(call_node)
          call_involving_param?(call_node) && !comparison_call?(call_node)
        end

        #
        # @return [Boolean] if the parameter is used in the given method call
        #
        def call_involving_param?(call_node)
          call_node.each_node(:lvar).any? { |it| it.var_name == parameter }
        end

        #
        # @return [Boolean] if the given method call is a comparison call
        #
        # :reek:UtilityFunction
        def comparison_call?(call_node)
          COMPARISON_METHOD_NAMES.include? call_node.name
        end
      end
    end
  end
end