rubocop-thread_safety 0.5.1 → 0.6.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- checksums.yaml +4 -4
- data/.github/workflows/ci.yml +43 -14
- data/.github/workflows/lint.yml +14 -10
- data/.rspec +1 -0
- data/.rubocop.yml +13 -11
- data/Appraisals +16 -4
- data/CHANGELOG.md +14 -0
- data/Gemfile +12 -1
- data/README.md +3 -1
- data/Rakefile +14 -0
- data/config/default.yml +15 -0
- data/config/obsoletion.yml +2 -0
- data/docs/modules/ROOT/pages/cops.adoc +8 -0
- data/docs/modules/ROOT/pages/cops_threadsafety.adoc +361 -0
- data/gemfiles/rubocop_1.48.gemfile +20 -0
- data/gemfiles/rubocop_1.66.gemfile +19 -0
- data/lib/rubocop/cop/mixin/operation_with_threadsafe_result.rb +44 -0
- data/lib/rubocop/cop/thread_safety/class_and_module_attributes.rb +5 -2
- data/lib/rubocop/cop/thread_safety/{instance_variable_in_class_method.rb → class_instance_variable.rb} +66 -12
- data/lib/rubocop/cop/thread_safety/dir_chdir.rb +37 -0
- data/lib/rubocop/cop/thread_safety/mutable_class_instance_variable.rb +8 -41
- data/lib/rubocop/cop/thread_safety/new_thread.rb +3 -5
- data/lib/rubocop/cop/thread_safety/rack_middleware_instance_variable.rb +120 -0
- data/lib/rubocop/thread_safety/inject.rb +2 -2
- data/lib/rubocop/thread_safety/version.rb +1 -1
- data/lib/rubocop/thread_safety.rb +2 -0
- data/lib/rubocop-thread_safety.rb +5 -1
- data/rubocop-thread_safety.gemspec +9 -8
- data/tasks/cops_documentation.rake +46 -0
- metadata +25 -90
- data/.rubocop_todo.yml +0 -12
- data/gemfiles/rubocop_0.90.gemfile +0 -7
- data/gemfiles/rubocop_1.20.gemfile +0 -7
@@ -0,0 +1,19 @@
|
|
1
|
+
# frozen_string_literal: true
|
2
|
+
|
3
|
+
# This file was generated by Appraisal
|
4
|
+
|
5
|
+
source 'https://rubygems.org'
|
6
|
+
|
7
|
+
gem 'appraisal'
|
8
|
+
gem 'bundler', '>= 1.10', '< 3'
|
9
|
+
gem 'prism', '~> 1.2.0'
|
10
|
+
gem 'pry'
|
11
|
+
gem 'rake', '>= 10.0'
|
12
|
+
gem 'rspec', '~> 3.0'
|
13
|
+
gem 'rubocop', '~> 1.66.0'
|
14
|
+
gem 'rubocop-rake', '~> 0.6.0'
|
15
|
+
gem 'rubocop-rspec'
|
16
|
+
gem 'simplecov'
|
17
|
+
gem 'yard'
|
18
|
+
|
19
|
+
gemspec path: '../'
|
@@ -0,0 +1,44 @@
|
|
1
|
+
# frozen_string_literal: true
|
2
|
+
|
3
|
+
module RuboCop
|
4
|
+
module Cop
|
5
|
+
# Common functionality for checking if a well-known operation
|
6
|
+
# produces an object with thread-safe semantics.
|
7
|
+
module OperationWithThreadsafeResult
|
8
|
+
extend NodePattern::Macros
|
9
|
+
|
10
|
+
# @!method operation_produces_threadsafe_object?(node)
|
11
|
+
def_node_matcher :operation_produces_threadsafe_object?, <<~PATTERN
|
12
|
+
{
|
13
|
+
(send (const {nil? cbase} :Queue) :new ...)
|
14
|
+
(send
|
15
|
+
(const (const {nil? cbase} :ThreadSafe) {:Hash :Array})
|
16
|
+
:new ...)
|
17
|
+
(block
|
18
|
+
(send
|
19
|
+
(const (const {nil? cbase} :ThreadSafe) {:Hash :Array})
|
20
|
+
:new ...)
|
21
|
+
...)
|
22
|
+
(send (const (const {nil? cbase} :Concurrent) _) :new ...)
|
23
|
+
(block
|
24
|
+
(send (const (const {nil? cbase} :Concurrent) _) :new ...)
|
25
|
+
...)
|
26
|
+
(send (const (const (const {nil? cbase} :Concurrent) _) _) :new ...)
|
27
|
+
(block
|
28
|
+
(send
|
29
|
+
(const (const (const {nil? cbase} :Concurrent) _) _)
|
30
|
+
:new ...)
|
31
|
+
...)
|
32
|
+
(send
|
33
|
+
(const (const (const (const {nil? cbase} :Concurrent) _) _) _)
|
34
|
+
:new ...)
|
35
|
+
(block
|
36
|
+
(send
|
37
|
+
(const (const (const (const {nil? cbase} :Concurrent) _) _) _)
|
38
|
+
:new ...)
|
39
|
+
...)
|
40
|
+
}
|
41
|
+
PATTERN
|
42
|
+
end
|
43
|
+
end
|
44
|
+
end
|
@@ -50,8 +50,7 @@ module RuboCop
|
|
50
50
|
MATCHER
|
51
51
|
|
52
52
|
def on_send(node)
|
53
|
-
return unless mattr?(node) || class_attr?(node) ||
|
54
|
-
singleton_attr?(node)
|
53
|
+
return unless mattr?(node) || (!class_attribute_allowed? && class_attr?(node)) || singleton_attr?(node)
|
55
54
|
|
56
55
|
add_offense(node)
|
57
56
|
end
|
@@ -74,6 +73,10 @@ module RuboCop
|
|
74
73
|
|
75
74
|
false
|
76
75
|
end
|
76
|
+
|
77
|
+
def class_attribute_allowed?
|
78
|
+
cop_config['ActiveSupportClassAttributeAllowed']
|
79
|
+
end
|
77
80
|
end
|
78
81
|
end
|
79
82
|
end
|
@@ -3,7 +3,7 @@
|
|
3
3
|
module RuboCop
|
4
4
|
module Cop
|
5
5
|
module ThreadSafety
|
6
|
-
# Avoid instance variables
|
6
|
+
# Avoid class instance variables.
|
7
7
|
#
|
8
8
|
# @example
|
9
9
|
# # bad
|
@@ -61,8 +61,8 @@ module RuboCop
|
|
61
61
|
#
|
62
62
|
# module_function :test
|
63
63
|
# end
|
64
|
-
class
|
65
|
-
MSG = 'Avoid instance variables
|
64
|
+
class ClassInstanceVariable < Base
|
65
|
+
MSG = 'Avoid class instance variables.'
|
66
66
|
RESTRICT_ON_SEND = %i[
|
67
67
|
instance_variable_set
|
68
68
|
instance_variable_get
|
@@ -99,21 +99,28 @@ module RuboCop
|
|
99
99
|
private
|
100
100
|
|
101
101
|
def class_method_definition?(node)
|
102
|
-
return false if method_definition?(node)
|
103
|
-
|
104
102
|
in_defs?(node) ||
|
105
103
|
in_def_sclass?(node) ||
|
106
104
|
in_def_class_methods?(node) ||
|
107
105
|
in_def_module_function?(node) ||
|
106
|
+
in_class_eval?(node) ||
|
108
107
|
singleton_method_definition?(node)
|
109
108
|
end
|
110
109
|
|
111
110
|
def in_defs?(node)
|
112
|
-
node.ancestors.any?
|
111
|
+
node.ancestors.any? do |ancestor|
|
112
|
+
break false if new_lexical_scope?(ancestor)
|
113
|
+
|
114
|
+
ancestor.defs_type?
|
115
|
+
end
|
113
116
|
end
|
114
117
|
|
115
118
|
def in_def_sclass?(node)
|
116
|
-
defn = node.ancestors.find
|
119
|
+
defn = node.ancestors.find do |ancestor|
|
120
|
+
break if new_lexical_scope?(ancestor)
|
121
|
+
|
122
|
+
ancestor.def_type?
|
123
|
+
end
|
117
124
|
|
118
125
|
defn&.ancestors&.any?(&:sclass_type?)
|
119
126
|
end
|
@@ -124,35 +131,51 @@ module RuboCop
|
|
124
131
|
|
125
132
|
def in_def_class_methods_dsl?(node)
|
126
133
|
node.ancestors.any? do |ancestor|
|
134
|
+
break if new_lexical_scope?(ancestor)
|
127
135
|
next unless ancestor.block_type?
|
128
|
-
next unless ancestor.children.first.is_a? AST::SendNode
|
129
136
|
|
130
137
|
ancestor.children.first.command? :class_methods
|
131
138
|
end
|
132
139
|
end
|
133
140
|
|
134
141
|
def in_def_class_methods_module?(node)
|
135
|
-
defn = node.ancestors.find
|
136
|
-
|
142
|
+
defn = node.ancestors.find do |ancestor|
|
143
|
+
break if new_lexical_scope?(ancestor)
|
144
|
+
|
145
|
+
ancestor.def_type?
|
146
|
+
end
|
147
|
+
return false unless defn
|
137
148
|
|
138
149
|
mod = defn.ancestors.find do |ancestor|
|
139
150
|
%i[class module].include?(ancestor.type)
|
140
151
|
end
|
141
|
-
return unless mod
|
152
|
+
return false unless mod
|
142
153
|
|
143
154
|
class_methods_module?(mod)
|
144
155
|
end
|
145
156
|
|
146
157
|
def in_def_module_function?(node)
|
147
158
|
defn = node.ancestors.find(&:def_type?)
|
148
|
-
return unless defn
|
159
|
+
return false unless defn
|
149
160
|
|
150
161
|
defn.left_siblings.any? { |sibling| module_function_bare_access_modifier?(sibling) } ||
|
151
162
|
defn.right_siblings.any? { |sibling| module_function_for?(sibling, defn.method_name) }
|
152
163
|
end
|
153
164
|
|
165
|
+
def in_class_eval?(node)
|
166
|
+
defn = node.ancestors.find do |ancestor|
|
167
|
+
break if ancestor.def_type? || new_lexical_scope?(ancestor)
|
168
|
+
|
169
|
+
ancestor.block_type?
|
170
|
+
end
|
171
|
+
return false unless defn
|
172
|
+
|
173
|
+
class_eval_scope?(defn)
|
174
|
+
end
|
175
|
+
|
154
176
|
def singleton_method_definition?(node)
|
155
177
|
node.ancestors.any? do |ancestor|
|
178
|
+
break if new_lexical_scope?(ancestor)
|
156
179
|
next unless ancestor.children.first.is_a? AST::SendNode
|
157
180
|
|
158
181
|
ancestor.children.first.command? :define_singleton_method
|
@@ -161,6 +184,7 @@ module RuboCop
|
|
161
184
|
|
162
185
|
def method_definition?(node)
|
163
186
|
node.ancestors.any? do |ancestor|
|
187
|
+
break if new_lexical_scope?(ancestor)
|
164
188
|
next unless ancestor.children.first.is_a? AST::SendNode
|
165
189
|
|
166
190
|
ancestor.children.first.command? :define_method
|
@@ -199,6 +223,36 @@ module RuboCop
|
|
199
223
|
def_node_matcher :module_function_for?, <<~PATTERN
|
200
224
|
(send nil? {:module_function} ({sym str} #match_name?(%1)))
|
201
225
|
PATTERN
|
226
|
+
|
227
|
+
# @!method new_lexical_scope?(node)
|
228
|
+
def_node_matcher :new_lexical_scope?, <<~PATTERN
|
229
|
+
{
|
230
|
+
(block (send (const nil? :Struct) :new ...) _ ({def defs} ...))
|
231
|
+
(block (send (const nil? :Class) :new ...) _ ({def defs} ...))
|
232
|
+
(block (send (const nil? :Data) :define ...) _ ({def defs} ...))
|
233
|
+
(block
|
234
|
+
(send nil?
|
235
|
+
{
|
236
|
+
:prepend_around_action
|
237
|
+
:prepend_before_action
|
238
|
+
:before_action
|
239
|
+
:append_before_action
|
240
|
+
:around_action
|
241
|
+
:append_around_action
|
242
|
+
:append_after_action
|
243
|
+
:after_action
|
244
|
+
:prepend_after_action
|
245
|
+
}
|
246
|
+
)
|
247
|
+
...
|
248
|
+
)
|
249
|
+
}
|
250
|
+
PATTERN
|
251
|
+
|
252
|
+
# @!method class_eval_scope?(node)
|
253
|
+
def_node_matcher :class_eval_scope?, <<~PATTERN
|
254
|
+
(block (send (const {nil? cbase} _) {:class_eval :class_exec}) ...)
|
255
|
+
PATTERN
|
202
256
|
end
|
203
257
|
end
|
204
258
|
end
|
@@ -0,0 +1,37 @@
|
|
1
|
+
# frozen_string_literal: true
|
2
|
+
|
3
|
+
module RuboCop
|
4
|
+
module Cop
|
5
|
+
module ThreadSafety
|
6
|
+
# Avoid using `Dir.chdir` due to its process-wide effect.
|
7
|
+
#
|
8
|
+
# @example
|
9
|
+
# # bad
|
10
|
+
# Dir.chdir("/var/run")
|
11
|
+
#
|
12
|
+
# # bad
|
13
|
+
# FileUtils.chdir("/var/run")
|
14
|
+
class DirChdir < Base
|
15
|
+
MESSAGE = 'Avoid using `%<module>s.%<method>s` due to its process-wide effect.'
|
16
|
+
RESTRICT_ON_SEND = %i[chdir cd].freeze
|
17
|
+
|
18
|
+
# @!method chdir?(node)
|
19
|
+
def_node_matcher :chdir?, <<~MATCHER
|
20
|
+
{
|
21
|
+
(send (const {nil? cbase} {:Dir :FileUtils}) :chdir ...)
|
22
|
+
(send (const {nil? cbase} :FileUtils) :cd ...)
|
23
|
+
}
|
24
|
+
MATCHER
|
25
|
+
|
26
|
+
def on_send(node)
|
27
|
+
chdir?(node) do
|
28
|
+
add_offense(
|
29
|
+
node,
|
30
|
+
message: format(MESSAGE, module: node.receiver.short_name, method: node.method_name)
|
31
|
+
)
|
32
|
+
end
|
33
|
+
end
|
34
|
+
end
|
35
|
+
end
|
36
|
+
end
|
37
|
+
end
|
@@ -7,7 +7,7 @@ module RuboCop
|
|
7
7
|
# mutable literal (e.g. array or hash).
|
8
8
|
#
|
9
9
|
# It is based on Style/MutableConstant from RuboCop.
|
10
|
-
# See https://github.com/rubocop
|
10
|
+
# See https://github.com/rubocop/rubocop/blob/master/lib/rubocop/cop/style/mutable_constant.rb
|
11
11
|
#
|
12
12
|
# Class instance variables are a risk to threaded code as they are shared
|
13
13
|
# between threads. A mutable object such as an array or hash may be
|
@@ -74,8 +74,10 @@ module RuboCop
|
|
74
74
|
# end
|
75
75
|
class MutableClassInstanceVariable < Base
|
76
76
|
extend AutoCorrector
|
77
|
+
|
77
78
|
include FrozenStringLiteral
|
78
79
|
include ConfigurableEnforcedStyle
|
80
|
+
include OperationWithThreadsafeResult
|
79
81
|
|
80
82
|
MSG = 'Freeze mutable objects assigned to class instance variables.'
|
81
83
|
FROZEN_STRING_LITERAL_TYPES_RUBY27 = %i[str dstr].freeze
|
@@ -84,22 +86,20 @@ module RuboCop
|
|
84
86
|
def on_ivasgn(node)
|
85
87
|
return unless in_class?(node)
|
86
88
|
|
87
|
-
|
88
|
-
on_assignment(value)
|
89
|
+
on_assignment(node.expression)
|
89
90
|
end
|
90
91
|
|
91
92
|
def on_or_asgn(node)
|
92
|
-
|
93
|
-
return unless lhs&.ivasgn_type?
|
93
|
+
return unless node.assignment_node.ivasgn_type?
|
94
94
|
return unless in_class?(node)
|
95
95
|
|
96
|
-
on_assignment(
|
96
|
+
on_assignment(node.expression)
|
97
97
|
end
|
98
98
|
|
99
99
|
def on_masgn(node)
|
100
100
|
return unless in_class?(node)
|
101
101
|
|
102
|
-
mlhs, values = *node
|
102
|
+
mlhs, values = *node # rubocop:disable InternalAffairs/NodeDestructuring
|
103
103
|
return unless values.array_type?
|
104
104
|
|
105
105
|
mlhs.to_a.zip(values.to_a).each do |lhs, value|
|
@@ -183,7 +183,7 @@ module RuboCop
|
|
183
183
|
end
|
184
184
|
|
185
185
|
def mutable_literal?(node)
|
186
|
-
return if node.nil?
|
186
|
+
return false if node.nil?
|
187
187
|
|
188
188
|
node.mutable_literal? || range_type?(node)
|
189
189
|
end
|
@@ -243,39 +243,6 @@ module RuboCop
|
|
243
243
|
}
|
244
244
|
PATTERN
|
245
245
|
|
246
|
-
# @!method operation_produces_threadsafe_object?(node)
|
247
|
-
def_node_matcher :operation_produces_threadsafe_object?, <<~PATTERN
|
248
|
-
{
|
249
|
-
(send (const {nil? cbase} :Queue) :new ...)
|
250
|
-
(send
|
251
|
-
(const (const {nil? cbase} :ThreadSafe) {:Hash :Array})
|
252
|
-
:new ...)
|
253
|
-
(block
|
254
|
-
(send
|
255
|
-
(const (const {nil? cbase} :ThreadSafe) {:Hash :Array})
|
256
|
-
:new ...)
|
257
|
-
...)
|
258
|
-
(send (const (const {nil? cbase} :Concurrent) _) :new ...)
|
259
|
-
(block
|
260
|
-
(send (const (const {nil? cbase} :Concurrent) _) :new ...)
|
261
|
-
...)
|
262
|
-
(send (const (const (const {nil? cbase} :Concurrent) _) _) :new ...)
|
263
|
-
(block
|
264
|
-
(send
|
265
|
-
(const (const (const {nil? cbase} :Concurrent) _) _)
|
266
|
-
:new ...)
|
267
|
-
...)
|
268
|
-
(send
|
269
|
-
(const (const (const (const {nil? cbase} :Concurrent) _) _) _)
|
270
|
-
:new ...)
|
271
|
-
(block
|
272
|
-
(send
|
273
|
-
(const (const (const (const {nil? cbase} :Concurrent) _) _) _)
|
274
|
-
:new ...)
|
275
|
-
...)
|
276
|
-
}
|
277
|
-
PATTERN
|
278
|
-
|
279
246
|
# @!method range_enclosed_in_parentheses?(node)
|
280
247
|
def_node_matcher :range_enclosed_in_parentheses?, <<~PATTERN
|
281
248
|
(begin ({irange erange} _ _))
|
@@ -12,17 +12,15 @@ module RuboCop
|
|
12
12
|
# Thread.new { do_work }
|
13
13
|
class NewThread < Base
|
14
14
|
MSG = 'Avoid starting new threads.'
|
15
|
-
RESTRICT_ON_SEND = %i[new].freeze
|
15
|
+
RESTRICT_ON_SEND = %i[new fork start].freeze
|
16
16
|
|
17
17
|
# @!method new_thread?(node)
|
18
18
|
def_node_matcher :new_thread?, <<~MATCHER
|
19
|
-
(send (const {nil? cbase} :Thread) :new)
|
19
|
+
(send (const {nil? cbase} :Thread) {:new :fork :start} ...)
|
20
20
|
MATCHER
|
21
21
|
|
22
22
|
def on_send(node)
|
23
|
-
|
24
|
-
|
25
|
-
add_offense(node)
|
23
|
+
new_thread?(node) { add_offense(node) }
|
26
24
|
end
|
27
25
|
end
|
28
26
|
end
|
@@ -0,0 +1,120 @@
|
|
1
|
+
# frozen_string_literal: true
|
2
|
+
|
3
|
+
module RuboCop
|
4
|
+
module Cop
|
5
|
+
module ThreadSafety
|
6
|
+
# Avoid instance variables in rack middleware.
|
7
|
+
#
|
8
|
+
# Middlewares are initialized once, meaning any instance variables are shared between executor threads.
|
9
|
+
# To avoid potential race conditions, it's recommended to design middlewares to be stateless
|
10
|
+
# or to implement proper synchronization mechanisms.
|
11
|
+
#
|
12
|
+
# @example
|
13
|
+
# # bad
|
14
|
+
# class CounterMiddleware
|
15
|
+
# def initialize(app)
|
16
|
+
# @app = app
|
17
|
+
# @counter = 0
|
18
|
+
# end
|
19
|
+
#
|
20
|
+
# def call(env)
|
21
|
+
# app.call(env)
|
22
|
+
# ensure
|
23
|
+
# @counter += 1
|
24
|
+
# end
|
25
|
+
# end
|
26
|
+
#
|
27
|
+
# # good
|
28
|
+
# class CounterMiddleware
|
29
|
+
# def initialize(app)
|
30
|
+
# @app = app
|
31
|
+
# @counter = Concurrent::AtomicReference.new(0)
|
32
|
+
# end
|
33
|
+
#
|
34
|
+
# def call(env)
|
35
|
+
# app.call(env)
|
36
|
+
# ensure
|
37
|
+
# @counter.update { |ref| ref + 1 }
|
38
|
+
# end
|
39
|
+
# end
|
40
|
+
#
|
41
|
+
# class IdentityMiddleware
|
42
|
+
# def initialize(app)
|
43
|
+
# @app = app
|
44
|
+
# end
|
45
|
+
#
|
46
|
+
# def call(env)
|
47
|
+
# app.call(env)
|
48
|
+
# end
|
49
|
+
# end
|
50
|
+
class RackMiddlewareInstanceVariable < Base
|
51
|
+
include AllowedIdentifiers
|
52
|
+
include OperationWithThreadsafeResult
|
53
|
+
|
54
|
+
MSG = 'Avoid instance variables in Rack middleware.'
|
55
|
+
|
56
|
+
RESTRICT_ON_SEND = %i[instance_variable_get instance_variable_set].freeze
|
57
|
+
|
58
|
+
# @!method rack_middleware_like_class?(node)
|
59
|
+
def_node_matcher :rack_middleware_like_class?, <<~MATCHER
|
60
|
+
(class (const nil? _) nil? (begin <(def :initialize (args (arg _)+) ...) (def :call (args (arg _)) ...) ...>))
|
61
|
+
MATCHER
|
62
|
+
|
63
|
+
# @!method app_variable(node)
|
64
|
+
def_node_search :app_variable, <<~MATCHER
|
65
|
+
(def :initialize (args (arg $_) ...) `(ivasgn $_ (lvar $_)))
|
66
|
+
MATCHER
|
67
|
+
|
68
|
+
def on_class(node)
|
69
|
+
return unless rack_middleware_like_class?(node)
|
70
|
+
|
71
|
+
constructor_method = find_constructor_method(node)
|
72
|
+
return unless (application_variable = extract_application_variable_from_contructor_method(constructor_method))
|
73
|
+
|
74
|
+
safe_variables = extract_safe_variables_from_constructor_method(constructor_method)
|
75
|
+
|
76
|
+
node.each_node(:def) do |def_node|
|
77
|
+
def_node.each_node(:ivasgn, :ivar) do |ivar_node|
|
78
|
+
variable, = ivar_node.to_a
|
79
|
+
if variable == application_variable || safe_variables.include?(variable) || allowed_identifier?(variable)
|
80
|
+
next
|
81
|
+
end
|
82
|
+
|
83
|
+
add_offense ivar_node
|
84
|
+
end
|
85
|
+
end
|
86
|
+
end
|
87
|
+
|
88
|
+
def on_send(node)
|
89
|
+
argument = node.first_argument
|
90
|
+
|
91
|
+
return unless argument&.sym_type? || argument&.str_type?
|
92
|
+
return if allowed_identifier?(argument.value)
|
93
|
+
|
94
|
+
add_offense node
|
95
|
+
end
|
96
|
+
|
97
|
+
private
|
98
|
+
|
99
|
+
def find_constructor_method(class_node)
|
100
|
+
class_node
|
101
|
+
.each_node(:def)
|
102
|
+
.find { |node| node.method?(:initialize) && node.arguments.size >= 1 }
|
103
|
+
end
|
104
|
+
|
105
|
+
def extract_application_variable_from_contructor_method(constructor_method)
|
106
|
+
constructor_method
|
107
|
+
.then { |node| app_variable(node) }
|
108
|
+
.then { |variables| variables.first[1] if variables.first }
|
109
|
+
end
|
110
|
+
|
111
|
+
def extract_safe_variables_from_constructor_method(constructor_method)
|
112
|
+
constructor_method
|
113
|
+
.each_node(:ivasgn)
|
114
|
+
.select { |ivasgn_node| operation_produces_threadsafe_object?(ivasgn_node.to_a[1]) }
|
115
|
+
.map { _1.to_a[0] }
|
116
|
+
end
|
117
|
+
end
|
118
|
+
end
|
119
|
+
end
|
120
|
+
end
|
@@ -1,7 +1,7 @@
|
|
1
1
|
# frozen_string_literal: true
|
2
2
|
|
3
|
-
# The original code is from https://github.com/rubocop
|
4
|
-
# See https://github.com/rubocop
|
3
|
+
# The original code is from https://github.com/rubocop/rubocop-rspec/blob/master/lib/rubocop/rspec/inject.rb
|
4
|
+
# See https://github.com/rubocop/rubocop-rspec/blob/master/MIT-LICENSE.md
|
5
5
|
module RuboCop
|
6
6
|
module ThreadSafety
|
7
7
|
# Because RuboCop doesn't yet support plugins, we have to monkey patch in a
|
@@ -8,7 +8,11 @@ require 'rubocop/thread_safety/inject'
|
|
8
8
|
|
9
9
|
RuboCop::ThreadSafety::Inject.defaults!
|
10
10
|
|
11
|
-
require 'rubocop/cop/
|
11
|
+
require 'rubocop/cop/mixin/operation_with_threadsafe_result'
|
12
|
+
|
13
|
+
require 'rubocop/cop/thread_safety/class_instance_variable'
|
12
14
|
require 'rubocop/cop/thread_safety/class_and_module_attributes'
|
13
15
|
require 'rubocop/cop/thread_safety/mutable_class_instance_variable'
|
14
16
|
require 'rubocop/cop/thread_safety/new_thread'
|
17
|
+
require 'rubocop/cop/thread_safety/dir_chdir'
|
18
|
+
require 'rubocop/cop/thread_safety/rack_middleware_instance_variable'
|
@@ -22,17 +22,18 @@ Gem::Specification.new do |spec|
|
|
22
22
|
f.match(%r{^(test|spec|features)/})
|
23
23
|
end
|
24
24
|
|
25
|
+
spec.metadata = {
|
26
|
+
'changelog_uri' => 'https://github.com/rubocop/rubocop-thread_safety/blob/master/CHANGELOG.md',
|
27
|
+
'source_code_uri' => 'https://github.com/rubocop/rubocop-thread_safety',
|
28
|
+
'bug_tracker_uri' => 'https://github.com/rubocop/rubocop-thread_safety/issues',
|
29
|
+
'rubygems_mfa_required' => 'true'
|
30
|
+
}
|
31
|
+
|
25
32
|
spec.bindir = 'exe'
|
26
33
|
spec.executables = spec.files.grep(%r{^exe/}) { |f| File.basename(f) }
|
27
34
|
spec.require_paths = ['lib']
|
28
35
|
|
29
|
-
spec.required_ruby_version = '>= 2.
|
30
|
-
|
31
|
-
spec.add_runtime_dependency 'rubocop', '>= 0.90.0'
|
36
|
+
spec.required_ruby_version = '>= 2.7.0'
|
32
37
|
|
33
|
-
spec.
|
34
|
-
spec.add_development_dependency 'bundler', '>= 1.10', '< 3'
|
35
|
-
spec.add_development_dependency 'pry' unless ENV['CI']
|
36
|
-
spec.add_development_dependency 'rake', '>= 10.0'
|
37
|
-
spec.add_development_dependency 'rspec', '~> 3.0'
|
38
|
+
spec.add_dependency 'rubocop', '>= 1.48.1'
|
38
39
|
end
|
@@ -0,0 +1,46 @@
|
|
1
|
+
# frozen_string_literal: true
|
2
|
+
|
3
|
+
require 'rubocop'
|
4
|
+
require 'rubocop-thread_safety'
|
5
|
+
require 'rubocop/cops_documentation_generator'
|
6
|
+
require 'yard'
|
7
|
+
|
8
|
+
YARD::Rake::YardocTask.new(:yard_for_generate_documentation) do |task|
|
9
|
+
task.files = ['lib/rubocop/cop/**/*.rb']
|
10
|
+
task.options = ['--no-output']
|
11
|
+
end
|
12
|
+
|
13
|
+
desc 'Generate docs of all cops departments'
|
14
|
+
task generate_cops_documentation: :yard_for_generate_documentation do
|
15
|
+
deps = ['ThreadSafety']
|
16
|
+
CopsDocumentationGenerator.new(departments: deps).call
|
17
|
+
end
|
18
|
+
|
19
|
+
desc 'Syntax check for the documentation comments'
|
20
|
+
task documentation_syntax_check: :yard_for_generate_documentation do
|
21
|
+
require 'parser/ruby31'
|
22
|
+
|
23
|
+
ok = true
|
24
|
+
YARD::Registry.load!
|
25
|
+
cops = RuboCop::Cop::Registry.global
|
26
|
+
cops.each do |cop|
|
27
|
+
examples = YARD::Registry.all(:class).find do |code_object|
|
28
|
+
next unless RuboCop::Cop::Badge.for(code_object.to_s) == cop.badge
|
29
|
+
|
30
|
+
break code_object.tags('example')
|
31
|
+
end
|
32
|
+
|
33
|
+
examples.to_a.each do |example|
|
34
|
+
buffer = Parser::Source::Buffer.new('<code>', 1)
|
35
|
+
buffer.source = example.text
|
36
|
+
parser = Parser::Ruby31.new(RuboCop::AST::Builder.new)
|
37
|
+
parser.diagnostics.all_errors_are_fatal = true
|
38
|
+
parser.parse(buffer)
|
39
|
+
rescue Parser::SyntaxError => e
|
40
|
+
path = example.object.file
|
41
|
+
puts "#{path}: Syntax Error in an example. #{e}"
|
42
|
+
ok = false
|
43
|
+
end
|
44
|
+
end
|
45
|
+
abort unless ok
|
46
|
+
end
|