-
-
Notifications
You must be signed in to change notification settings - Fork 248
/
redundant_receiver_in_with_options.rb
136 lines (119 loc) · 3.9 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
135
136
# 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
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