lib/rubocop/cop/rspec/factory_bot/dynamic_attribute_defined_statically.rb



# frozen_string_literal: true

module RuboCop
  module Cop
    module RSpec
      module FactoryBot
        # Prefer declaring dynamic attribute values in a block.
        #
        # @example
        #   # bad
        #   kind [:active, :rejected].sample
        #
        #   # good
        #   kind { [:active, :rejected].sample }
        #
        #   # bad
        #   closed_at 1.day.from_now
        #
        #   # good
        #   closed_at { 1.day.from_now }
        #
        #   # good
        #   kind :static
        #
        #   # good
        #   comments_count 0
        #
        #   # good
        #   type User::MAGIC
        class DynamicAttributeDefinedStatically < Cop
          MSG = 'Use a block to set a dynamic value to an attribute.'.freeze

          def_node_matcher :dynamic_defined_statically?, <<-PATTERN
          (send nil? _ (send ... ))
          PATTERN

          def_node_search :factory_attributes, <<-PATTERN
          (block (send nil? {:factory :trait} ...) _ { (begin $...) $(send ...) } )
          PATTERN

          def on_block(node)
            return if node.method_name == :trait
            factory_attributes(node).to_a.flatten.each do |attribute|
              if dynamic_defined_statically?(attribute)
                add_offense(attribute, location: :expression)
              end
            end
          end

          def autocorrect(node)
            if method_uses_parens?(node.location)
              autocorrect_replacing_parens(node)
            else
              autocorrect_without_parens(node)
            end
          end

          private

          def method_uses_parens?(location)
            return false unless location.begin && location.end
            location.begin.source == '(' && location.end.source == ')'
          end

          def autocorrect_replacing_parens(node)
            lambda do |corrector|
              corrector.replace(node.location.begin, ' { ')
              corrector.replace(node.location.end, ' }')
            end
          end

          def autocorrect_without_parens(node)
            lambda do |corrector|
              arguments = node.descendants.first
              expression = arguments.location.expression
              corrector.insert_before(expression, '{ ')
              corrector.insert_after(expression, ' }')
            end
          end
        end
      end
    end
  end
end