forked from rubocop/rubocop-rails
-
Notifications
You must be signed in to change notification settings - Fork 0
/
Copy pathredundant_receiver_in_with_options.rb
134 lines (119 loc) · 3.71 KB
/
redundant_receiver_in_with_options.rb
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
# frozen_string_literal: true
module RuboCop
module Cop
module Rails
# This cop 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_matcher :with_options?, <<~PATTERN
(block
(send nil? :with_options
(...))
(args
$_arg)
$_body)
PATTERN
def_node_search :all_block_nodes_in, <<~PATTERN
(block ...)
PATTERN
def_node_search :all_send_nodes_in, <<~PATTERN
(send ...)
PATTERN
def on_block(node)
with_options?(node) do |arg, body|
return if body.nil?
return unless all_block_nodes_in(body).count.zero?
send_nodes = all_send_nodes_in(body)
if send_nodes.all? { |n| same_value?(arg, n.receiver) }
send_nodes.each do |send_node|
receiver = send_node.receiver
add_offense(receiver.source_range) do |corrector|
autocorrect(corrector, send_node)
end
end
end
end
end
private
def autocorrect(corrector, node)
corrector.remove(node.receiver.source_range)
corrector.remove(node.loc.dot)
corrector.remove(block_argument_range(node))
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