forked from rubocop/rubocop
-
Notifications
You must be signed in to change notification settings - Fork 0
/
redundant_self.rb
201 lines (171 loc) · 5.72 KB
/
redundant_self.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
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
# frozen_string_literal: true
module RuboCop
module Cop
module Style
# This cop checks for redundant uses of `self`.
#
# The usage of `self` is only needed when:
#
# * Sending a message to same object with zero arguments in
# presence of a method name clash with an argument or a local
# variable.
#
# * Calling an attribute writer to prevent a local variable assignment.
#
# Note, with using explicit self you can only send messages with public or
# protected scope, you cannot send private messages this way.
#
# Note we allow uses of `self` with operators because it would be awkward
# otherwise.
#
# @example
#
# # bad
# def foo(bar)
# self.baz
# end
#
# # good
# def foo(bar)
# self.bar # Resolves name clash with the argument.
# end
#
# def foo
# bar = 1
# self.bar # Resolves name clash with the local variable.
# end
#
# def foo
# %w[x y z].select do |bar|
# self.bar == bar # Resolves name clash with argument of the block.
# end
# end
class RedundantSelf < Base
extend AutoCorrector
MSG = 'Redundant `self` detected.'
KERNEL_METHODS = Kernel.methods(false)
KEYWORDS = %i[alias and begin break case class def defined? do
else elsif end ensure false for if in module
next nil not or redo rescue retry return self
super then true undef unless until when while
yield __FILE__ __LINE__ __ENCODING__].freeze
def self.autocorrect_incompatible_with
[ColonMethodCall]
end
def initialize(config = nil, options = nil)
super
@allowed_send_nodes = []
@local_variables_scopes = Hash.new { |hash, key| hash[key] = [] }.compare_by_identity
end
# Assignment of self.x
def on_or_asgn(node)
lhs, _rhs = *node
allow_self(lhs)
end
alias on_and_asgn on_or_asgn
def on_op_asgn(node)
lhs, _op, _rhs = *node
allow_self(lhs)
end
# Using self.x to distinguish from local variable x
def on_def(node)
add_scope(node)
end
alias on_defs on_def
def on_args(node)
node.children.each { |arg| on_argument(arg) }
end
def on_blockarg(node)
on_argument(node)
end
def on_masgn(node)
lhs, rhs = *node
add_masgn_lhs_variables(rhs, lhs)
end
def on_lvasgn(node)
lhs, rhs = *node
add_lhs_to_local_variables_scopes(rhs, lhs)
end
def on_in_pattern(node)
add_match_var_scopes(node)
end
def on_send(node)
return unless node.self_receiver? && regular_method_call?(node)
return if node.parent&.mlhs_type?
return if allowed_send_node?(node)
add_offense(node.receiver) do |corrector|
corrector.remove(node.receiver)
corrector.remove(node.loc.dot)
end
end
def on_block(node)
add_scope(node, @local_variables_scopes[node])
end
def on_if(node)
# Allow conditional nodes to use `self` in the condition if that variable
# name is used in an `lvasgn` or `masgn` within the `if`.
node.child_nodes.each do |child_node|
lhs, _rhs = *child_node
if child_node.lvasgn_type?
add_lhs_to_local_variables_scopes(node.condition, lhs)
elsif child_node.masgn_type?
add_masgn_lhs_variables(node.condition, lhs)
end
end
end
alias on_while on_if
alias on_until on_if
private
def add_scope(node, local_variables = [])
node.each_descendant do |child_node|
@local_variables_scopes[child_node] = local_variables
end
end
def allowed_send_node?(node)
@allowed_send_nodes.include?(node) ||
@local_variables_scopes[node].include?(node.method_name) ||
node.each_ancestor.any? do |ancestor|
@local_variables_scopes[ancestor].include?(node.method_name)
end ||
KERNEL_METHODS.include?(node.method_name)
end
def regular_method_call?(node)
!(node.operator_method? ||
KEYWORDS.include?(node.method_name) ||
node.camel_case_method? ||
node.setter_method? ||
node.implicit_call?)
end
def on_argument(node)
if node.mlhs_type?
on_args(node)
else
name, = *node
@local_variables_scopes[node] << name
end
end
def allow_self(node)
return unless node.send_type? && node.self_receiver?
@allowed_send_nodes << node
end
def add_lhs_to_local_variables_scopes(rhs, lhs)
if rhs&.send_type? && !rhs.arguments.empty?
rhs.arguments.each { |argument| @local_variables_scopes[argument] << lhs }
else
@local_variables_scopes[rhs] << lhs
end
end
def add_masgn_lhs_variables(rhs, lhs)
lhs.children.each do |child|
add_lhs_to_local_variables_scopes(rhs, child.to_a.first)
end
end
def add_match_var_scopes(in_pattern_node)
in_pattern_node.each_descendant(:match_var) do |match_var_node|
@local_variables_scopes[in_pattern_node] << match_var_node.children.first
end
end
end
end
end
end