# frozen_string_literal: true module RuboCop module Cop module Rails # Checks for redundant receiver in `with_options`. # Receiver is implicit from Rails 4.2 or higher. # # @example # # bad # class Account < ApplicationRecord # with_options dependent: :destroy do |assoc| # assoc.has_many :customers # assoc.has_many :products # assoc.has_many :invoices # assoc.has_many :expenses # end # end # # # good # class Account < ApplicationRecord # with_options dependent: :destroy do # has_many :customers # has_many :products # has_many :invoices # has_many :expenses # end # end # # @example # # bad # with_options options: false do |merger| # merger.invoke(merger.something) # end # # # good # with_options options: false do # invoke(something) # end # # # good # client = Client.new # with_options options: false do |merger| # client.invoke(merger.something, something) # end # # # ok # # When `with_options` includes a block, all scoping scenarios # # cannot be evaluated. Thus, it is ok to include the explicit # # receiver. # with_options options: false do |merger| # merger.invoke # with_another_method do |another_receiver| # merger.invoke(another_receiver) # end # end class RedundantReceiverInWithOptions < Base include RangeHelp extend AutoCorrector MSG = 'Redundant receiver in `with_options`.' def_node_search :all_block_nodes_in, <<~PATTERN (block ...) PATTERN def_node_search :all_send_nodes_in, <<~PATTERN (send ...) PATTERN def on_block(node) return unless node.method?(:with_options) return unless (body = node.body) return unless all_block_nodes_in(body).count.zero? send_nodes = all_send_nodes_in(body) return unless redundant_receiver?(send_nodes, node) send_nodes.each do |send_node| receiver = send_node.receiver add_offense(receiver.source_range) do |corrector| autocorrect(corrector, send_node, node) end end end alias on_numblock on_block private def autocorrect(corrector, send_node, node) corrector.remove(send_node.receiver.source_range) corrector.remove(send_node.loc.dot) corrector.remove(block_argument_range(send_node)) unless node.numblock_type? end def redundant_receiver?(send_nodes, node) proc = if node.numblock_type? ->(n) { n.receiver.lvar_type? && n.receiver.source == '_1' } else return false if node.arguments.empty? arg = node.arguments.first ->(n) { same_value?(arg, n.receiver) } end send_nodes.all?(&proc) end def block_argument_range(node) block_node = node.each_ancestor(:block).first block_argument = block_node.children[1].source_range range_between( search_begin_pos_of_space_before_block_argument( block_argument.begin_pos ), block_argument.end_pos ) end def search_begin_pos_of_space_before_block_argument(begin_pos) position = begin_pos - 1 if processed_source.raw_source[position] == ' ' search_begin_pos_of_space_before_block_argument(position) else begin_pos end end def same_value?(arg_node, recv_node) recv_node && recv_node.children[0] == arg_node.children[0] end end end end end