forked from covermymeds/rubocop-thread_safety
-
-
Notifications
You must be signed in to change notification settings - Fork 7
/
instance_variable_in_class_method.rb
186 lines (156 loc) · 4.99 KB
/
instance_variable_in_class_method.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
# frozen_string_literal: true
module RuboCop
module Cop
module ThreadSafety
# Avoid instance variables in class methods.
#
# @example
# # bad
# class User
# def self.notify(info)
# @info = validate(info)
# Notifier.new(@info).deliver
# end
# end
#
# class Model
# class << self
# def table_name(name)
# @table_name = name
# end
# end
# end
#
# class Host
# %i[uri port].each do |key|
# define_singleton_method("#{key}=") do |value|
# instance_variable_set("@#{key}", value)
# end
# end
# end
#
# module Example
# module ClassMethods
# def test(params)
# @params = params
# end
# end
# end
#
# module Example
# module_function
#
# def test(params)
# @params = params
# end
# end
#
# module Example
# def test(params)
# @params = params
# end
#
# module_function :test
# end
class InstanceVariableInClassMethod < Base
MSG = 'Avoid instance variables in class methods.'
RESTRICT_ON_SEND = %i[
instance_variable_set
instance_variable_get
].freeze
def_node_matcher :instance_variable_set_call?, <<~MATCHER
(send nil? :instance_variable_set (...) (...))
MATCHER
def_node_matcher :instance_variable_get_call?, <<~MATCHER
(send nil? :instance_variable_get (...))
MATCHER
def on_ivar(node)
return unless class_method_definition?(node)
return if method_definition?(node)
return if synchronized?(node)
add_offense(node.loc.name, message: MSG)
end
alias on_ivasgn on_ivar
def on_send(node)
return unless instance_variable_call?(node)
return unless class_method_definition?(node)
return if method_definition?(node)
return if synchronized?(node)
add_offense(node, message: MSG)
end
private
def class_method_definition?(node)
return false if method_definition?(node)
in_defs?(node) ||
in_def_sclass?(node) ||
in_def_class_methods?(node) ||
in_def_module_function?(node) ||
singleton_method_definition?(node)
end
def in_defs?(node)
node.ancestors.any? do |ancestor|
ancestor.type == :defs
end
end
def in_def_sclass?(node)
defn = node.ancestors.find do |ancestor|
ancestor.type == :def
end
defn&.ancestors&.any? do |ancestor|
ancestor.type == :sclass
end
end
def in_def_class_methods?(node)
defn = node.ancestors.find(&:def_type?)
return unless defn
mod = defn.ancestors.find do |ancestor|
%i[class module].include?(ancestor.type)
end
return unless mod
class_methods_module?(mod)
end
def in_def_module_function?(node)
defn = node.ancestors.find(&:def_type?)
return unless defn
defn.left_siblings.any? { |sibling| module_function_bare_access_modifier?(sibling) } ||
defn.right_siblings.any? { |sibling| module_function_for?(sibling, defn.method_name) }
end
def singleton_method_definition?(node)
node.ancestors.any? do |ancestor|
next unless ancestor.children.first.is_a? AST::SendNode
ancestor.children.first.command? :define_singleton_method
end
end
def method_definition?(node)
node.ancestors.any? do |ancestor|
next unless ancestor.children.first.is_a? AST::SendNode
ancestor.children.first.command? :define_method
end
end
def synchronized?(node)
node.ancestors.find do |ancestor|
next unless ancestor.block_type?
s = ancestor.children.first
s.send_type? && s.children.last == :synchronize
end
end
def instance_variable_call?(node)
instance_variable_set_call?(node) || instance_variable_get_call?(node)
end
def module_function_bare_access_modifier?(node)
return false unless node
node.send_type? && node.bare_access_modifier? && node.method?(:module_function)
end
def match_name?(arg_name, method_name)
arg_name.to_sym == method_name.to_sym
end
def_node_matcher :class_methods_module?, <<~PATTERN
(module (const _ :ClassMethods) ...)
PATTERN
def_node_matcher :module_function_for?, <<~PATTERN
(send nil? {:module_function} ({sym str} #match_name?(%1)))
PATTERN
end
end
end
end