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



# frozen_string_literal: true

module RuboCop
  module Cop
    module RSpec
      module FactoryBot
        # Use a consistent style for parentheses in factory bot calls.
        #
        # @example
        #
        #   # bad
        #   create :user
        #   build(:user)
        #   create(:login)
        #   create :login
        #
        # @example `EnforcedStyle: require_parentheses` (default)
        #
        #   # good
        #   create(:user)
        #   create(:user)
        #   create(:login)
        #   build(:login)
        #
        # @example `EnforcedStyle: omit_parentheses`
        #
        #   # good
        #   create :user
        #   build :user
        #   create :login
        #   create :login
        #
        #   # also good
        #   # when method name and first argument are not on same line
        #   create(
        #     :user
        #   )
        #   build(
        #     :user,
        #     name: 'foo'
        #   )
        #
        class ConsistentParenthesesStyle < ::RuboCop::Cop::Base
          extend AutoCorrector
          include ConfigurableEnforcedStyle
          include RuboCop::RSpec::FactoryBot::Language
          include RuboCop::Cop::Util

          def self.autocorrect_incompatible_with
            [Style::MethodCallWithArgsParentheses]
          end

          MSG_REQUIRE_PARENS = 'Prefer method call with parentheses'
          MSG_OMIT_PARENS = 'Prefer method call without parentheses'

          FACTORY_CALLS = RuboCop::RSpec::FactoryBot::Language::METHODS

          RESTRICT_ON_SEND = FACTORY_CALLS

          # @!method factory_call(node)
          def_node_matcher :factory_call, <<-PATTERN
            (send
              {#factory_bot? nil?} %FACTORY_CALLS
              {sym str send lvar} _*
            )
          PATTERN

          def on_send(node)
            return if ambiguous_without_parentheses?(node)

            factory_call(node) do
              return if node.method?(:generate) && node.arguments.count > 1

              if node.parenthesized?
                process_with_parentheses(node)
              else
                process_without_parentheses(node)
              end
            end
          end

          private

          def process_with_parentheses(node)
            return unless style == :omit_parentheses
            return unless same_line?(node, node.first_argument)

            add_offense(node.loc.selector,
                        message: MSG_OMIT_PARENS) do |corrector|
              remove_parentheses(corrector, node)
            end
          end

          def process_without_parentheses(node)
            return unless style == :require_parentheses

            add_offense(node.loc.selector,
                        message: MSG_REQUIRE_PARENS) do |corrector|
              add_parentheses(node, corrector)
            end
          end

          def ambiguous_without_parentheses?(node)
            node.parent&.send_type? ||
              node.parent&.pair_type? ||
              node.parent&.array_type?
          end

          def remove_parentheses(corrector, node)
            corrector.replace(node.location.begin, ' ')
            corrector.remove(node.location.end)
          end
        end
      end
    end
  end
end