rubocop-thread_safety 0.4.0 → 0.4.4

Sign up to get free protection for your applications and to get access to all the features.
checksums.yaml CHANGED
@@ -1,7 +1,7 @@
1
1
  ---
2
2
  SHA256:
3
- metadata.gz: 33c8f2c01aa5f51c5ae09093a3ebb895a764330600ef32c05995836891cc1bd2
4
- data.tar.gz: 5d4bc3124aff9203407cb61b000564084962e0ad18d72fff954d0994eb944276
3
+ metadata.gz: 9f779f952ad93ee231e590b53de736e1aa6f8bfc421032e1093e495a6a1b58d5
4
+ data.tar.gz: 31afd6b2f36af51336751d2de2b31b794c61e64bd8363c71bde8750df1e0b728
5
5
  SHA512:
6
- metadata.gz: 130074abe1ba664fad05877bf2f215ed6965a8da36a92ba8865d05305c1f3661d9f2acfe67836620446c43bee81b3819111c764f37cbe2506474de3b0597de87
7
- data.tar.gz: ed769cdad040ba59dba88a68af2cadbf04e3eaedd1c389261050cd0917ec4a629b775798c20d3e893650796deea250dc01c80c05a32d5c2c19151fd9d88667d0
6
+ metadata.gz: 71ac5c818dce793fdf24c6cb7e3f486bf8e897e9a9f8066bdf8b2cd25ac6637060cf65314b25fa2d7f4450dccf55e0eb4310679d32a2488aeef545effa6b0c06
7
+ data.tar.gz: 3c90b593c9f948b1c75eec96edafeb14cb83934eae67e49fc032098cba86855fe5b39178c1f44376a6da05db994b8b8560bc0d4b48bf76239f3b6411474d5a24
data/.gitignore CHANGED
@@ -1,6 +1,7 @@
1
1
  /.bundle/
2
2
  /.yardoc
3
3
  /Gemfile.lock
4
+ /gemfiles/*.lock
4
5
  /_yardoc/
5
6
  /coverage/
6
7
  /doc/
data/.rubocop.yml CHANGED
@@ -1,10 +1,23 @@
1
1
  AllCops:
2
2
  DisplayCopNames: true
3
+ TargetRubyVersion: 2.3
4
+
5
+ Lint/RaiseException:
6
+ Enabled: true
7
+
8
+ Lint/StructNewOverride:
9
+ Enabled: true
3
10
 
4
11
  Metrics/BlockLength:
5
12
  Exclude:
6
13
  - "spec/**/*"
7
14
 
15
+ Metrics/ClassLength:
16
+ Enabled: false
17
+
18
+ Metrics/MethodLength:
19
+ Max: 14
20
+
8
21
  Naming/FileName:
9
22
  Exclude:
10
23
  - lib/rubocop-thread_safety.rb
@@ -18,6 +31,19 @@ Style/AutoResourceCleanup:
18
31
  Style/CollectionMethods:
19
32
  Enabled: true
20
33
 
34
+ Style/FrozenStringLiteralComment:
35
+ Exclude:
36
+ - "gemfiles/*.gemfile"
37
+
38
+ Style/HashEachMethods:
39
+ Enabled: true
40
+
41
+ Style/HashTransformKeys:
42
+ Enabled: false
43
+
44
+ Style/HashTransformValues:
45
+ Enabled: false
46
+
21
47
  Style/MethodCalledOnDoEndBlock:
22
48
  Enabled: true
23
49
  Exclude:
@@ -33,6 +59,10 @@ Style/OptionHash:
33
59
  Style/Send:
34
60
  Enabled: true
35
61
 
62
+ Style/StringLiterals:
63
+ Exclude:
64
+ - "gemfiles/*.gemfile"
65
+
36
66
  Style/StringMethods:
37
67
  Enabled: true
38
68
 
data/.travis.yml CHANGED
@@ -1,20 +1,64 @@
1
- sudo: false
2
1
  cache: bundler
3
2
  language: ruby
4
3
  rvm:
5
- - jruby-9.1.14.0
4
+ - jruby-9.2.9.0
6
5
  - 2.3.0
7
6
  - 2.4
8
7
  - 2.5
9
8
  - 2.6
10
9
  - 2.7
10
+ - 3.0
11
11
 
12
- matrix:
12
+ gemfile:
13
+ - gemfiles/rubocop_0.53.gemfile
14
+ - gemfiles/rubocop_0.81.gemfile
15
+ - gemfiles/rubocop_0.86.gemfile
16
+ - gemfiles/rubocop_1.20.gemfile
17
+
18
+ script_rubocop: &script_rubocop
19
+ - bundle exec rspec
20
+ - bundle exec rubocop
21
+
22
+ jobs:
13
23
  fast_finish: true
24
+ exclude:
25
+ - rvm: 2.3.0
26
+ gemfile: gemfiles/rubocop_0.86.gemfile
27
+ - rvm: 2.3.0
28
+ gemfile: gemfiles/rubocop_1.20.gemfile
29
+ - rvm: 2.5
30
+ gemfile: gemfiles/rubocop_0.53.gemfile
31
+ - rvm: 2.6
32
+ gemfile: gemfiles/rubocop_0.53.gemfile
33
+ - rvm: 2.7
34
+ gemfile: gemfiles/rubocop_0.53.gemfile
35
+ - rvm: 3.0
36
+ gemfile: gemfiles/rubocop_0.53.gemfile
37
+ include:
38
+ - rvm: jruby-9.2.9.0
39
+ gemfile: gemfiles/rubocop_0.81.gemfile
40
+ script: *script_rubocop
41
+ - rvm: 2.3.0
42
+ gemfile: gemfiles/rubocop_0.81.gemfile
43
+ script: *script_rubocop
44
+ - rvm: 2.4
45
+ gemfile: gemfiles/rubocop_0.81.gemfile
46
+ script: *script_rubocop
47
+ - rvm: 2.5
48
+ gemfile: gemfiles/rubocop_0.81.gemfile
49
+ script: *script_rubocop
50
+ - rvm: 2.6
51
+ gemfile: gemfiles/rubocop_0.81.gemfile
52
+ script: *script_rubocop
53
+ - rvm: 2.7
54
+ gemfile: gemfiles/rubocop_0.81.gemfile
55
+ script: *script_rubocop
56
+ - rvm: 3.0
57
+ gemfile: gemfiles/rubocop_0.81.gemfile
58
+ script: *script_rubocop
14
59
 
15
60
  before_install: gem install --remote bundler
16
61
  install:
17
62
  - bundle install --retry=3
18
63
  script:
19
64
  - bundle exec rspec
20
- - bundle exec rubocop
data/Appraisals ADDED
@@ -0,0 +1,23 @@
1
+ # frozen_string_literal: true
2
+
3
+ appraise 'rubocop-0.53' do
4
+ gem 'rubocop', '~> 0.53.0'
5
+ end
6
+
7
+ appraise 'rubocop-0.81' do
8
+ gem 'rubocop', '~> 0.81.0'
9
+ end
10
+
11
+ if Gem::Requirement.new('>= 2.4.0')
12
+ .satisfied_by?(Gem::Version.new(RUBY_VERSION))
13
+ appraise 'rubocop-0.86' do
14
+ gem 'rubocop', '~> 0.86.0'
15
+ end
16
+ end
17
+
18
+ if Gem::Requirement.new('>= 2.5.0')
19
+ .satisfied_by?(Gem::Version.new(RUBY_VERSION))
20
+ appraise 'rubocop-1.20' do
21
+ gem 'rubocop', '~> 1.20.0'
22
+ end
23
+ end
data/LICENSE.txt CHANGED
@@ -1,4 +1,4 @@
1
- Copyright 2016-2020 CoverMyMeds
1
+ Copyright 2016-2021 CoverMyMeds
2
2
 
3
3
  Permission is hereby granted, free of charge, to any person obtaining a copy of this software and associated documentation files (the "Software"), to deal in the Software without restriction, including without limitation the rights to use, copy, modify, merge, publish, distribute, sublicense, and/or sell copies of the Software, and to permit persons to whom the Software is furnished to do so, subject to the following conditions:
4
4
 
data/README.md CHANGED
@@ -34,6 +34,32 @@ Scan the application for just thread-safety issues:
34
34
 
35
35
  $ rubocop -r rubocop-thread_safety --only ThreadSafety,Style/GlobalVars,Style/ClassVars,Style/MutableConstant
36
36
 
37
+ ### Configuration
38
+
39
+ There are some added [configuration options](https://github.com/covermymeds/rubocop-thread_safety/blob/master/config/default.yml) that can be tweaked to modify the behaviour of these thread-safety cops.
40
+
41
+ ### Correcting code for thread-safety
42
+
43
+ There are a few ways to improve thread-safety that stem around avoiding
44
+ unsynchronized mutation of state that is shared between multiple threads.
45
+
46
+ State shared between threads may take various forms, including:
47
+
48
+ * Class variables (`@@name`). Note: these affect child classes too.
49
+ * Class instance variables (`@name` in class context or class methods)
50
+ * Constants (`NAME`). Ruby will warn if a constant is re-assigned to a new value but will allow it. Mutable objects can still be mutated (e.g. push to an array) even if they are assigned to a constant.
51
+ * Globals (`$name`), with the possible exception of some special globals provided by ruby that are documented as thread-local like regular expression results.
52
+ * Variables in the scope of created threads (where `Thread.new` is called).
53
+
54
+ Improvements that would make shared state thread-safe include:
55
+
56
+ * `freeze` objects to protect against mutation. Note: `freeze` is shallow, i.e. freezing an array will not also freeze its elements.
57
+ * Use data structures or concurrency abstractions from [concurrent-ruby](https://github.com/ruby-concurrency/concurrent-ruby), e.g. `Concurrent::Map`
58
+ * Use a `Mutex` or similar to `synchronize` access.
59
+ * Use [`ActiveSupport::CurrentAttributes`](https://api.rubyonrails.org/classes/ActiveSupport/CurrentAttributes.html)
60
+ * Use [`RequestStore`](https://github.com/steveklabnik/request_store)
61
+ * Use `Thread.current[:name]`
62
+
37
63
  ## Development
38
64
 
39
65
  After checking out the repo, run `bin/setup` to install dependencies. Then, run `rake spec` to run the tests. You can also run `bin/console` for an interactive prompt that will allow you to experiment.
@@ -46,5 +72,5 @@ Bug reports and pull requests are welcome on GitHub at https://github.com/coverm
46
72
 
47
73
  ## Copyright
48
74
 
49
- Copyright (c) 2016-2020 CoverMyMeds.
75
+ Copyright (c) 2016-2021 CoverMyMeds.
50
76
  See [LICENSE.txt](LICENSE.txt) for further details.
@@ -0,0 +1,31 @@
1
+ # Additional configuration for thread_safety cops
2
+ #
3
+ # Without adding these to your rubocop config, these values will be the default.
4
+
5
+ ThreadSafety/ClassAndModuleAttributes:
6
+ Description: 'Avoid mutating class and module attributes.'
7
+ Enabled: true
8
+
9
+ ThreadSafety/InstanceVariableInClassMethod:
10
+ Description: 'Avoid using instance variables in class methods.'
11
+ Enabled: true
12
+
13
+ ThreadSafety/MutableClassInstanceVariable:
14
+ Description: 'Do not assign mutable objects to class instance variables.'
15
+ Enabled: true
16
+ EnforcedStyle: literals
17
+ SupportedStyles:
18
+ # literals: freeze literals assigned to constants
19
+ # strict: freeze all constants
20
+ # Strict mode is considered an experimental feature. It has not been updated
21
+ # with an exhaustive list of all methods that will produce frozen objects so
22
+ # there is a decent chance of getting some false positives. Luckily, there is
23
+ # no harm in freezing an already frozen object.
24
+ - literals
25
+ - strict
26
+
27
+ ThreadSafety/NewThread:
28
+ Description: >-
29
+ Avoid starting new threads.
30
+ Let a framework like Sidekiq handle the threads.
31
+ Enabled: true
@@ -0,0 +1,7 @@
1
+ # This file was generated by Appraisal
2
+
3
+ source "https://rubygems.org"
4
+
5
+ gem "rubocop", "~> 0.53.0"
6
+
7
+ gemspec path: "../"
@@ -0,0 +1,7 @@
1
+ # This file was generated by Appraisal
2
+
3
+ source "https://rubygems.org"
4
+
5
+ gem "rubocop", "~> 0.81.0"
6
+
7
+ gemspec path: "../"
@@ -0,0 +1,7 @@
1
+ # This file was generated by Appraisal
2
+
3
+ source "https://rubygems.org"
4
+
5
+ gem "rubocop", "~> 0.86.0"
6
+
7
+ gemspec path: "../"
@@ -0,0 +1,7 @@
1
+ # This file was generated by Appraisal
2
+
3
+ source "https://rubygems.org"
4
+
5
+ gem "rubocop", "~> 1.20.0"
6
+
7
+ gemspec path: "../"
@@ -15,20 +15,33 @@ module RuboCop
15
15
  class ClassAndModuleAttributes < Cop
16
16
  MSG = 'Avoid mutating class and module attributes.'
17
17
 
18
- def_node_matcher :mattr?, <<-MATCHER
18
+ def_node_matcher :mattr?, <<~MATCHER
19
19
  (send nil?
20
20
  {:mattr_writer :mattr_accessor :cattr_writer :cattr_accessor}
21
21
  ...)
22
22
  MATCHER
23
23
 
24
- def_node_matcher :attr?, <<-MATCHER
24
+ def_node_matcher :attr?, <<~MATCHER
25
25
  (send nil?
26
26
  {:attr :attr_accessor :attr_writer}
27
27
  ...)
28
28
  MATCHER
29
29
 
30
+ def_node_matcher :attr_internal?, <<~MATCHER
31
+ (send nil?
32
+ {:attr_internal :attr_internal_accessor :attr_internal_writer}
33
+ ...)
34
+ MATCHER
35
+
36
+ def_node_matcher :class_attr?, <<~MATCHER
37
+ (send nil?
38
+ :class_attribute
39
+ ...)
40
+ MATCHER
41
+
30
42
  def on_send(node)
31
- return unless mattr?(node) || singleton_attr?(node)
43
+ return unless mattr?(node) || class_attr?(node) ||
44
+ singleton_attr?(node)
32
45
 
33
46
  add_offense(node, message: MSG)
34
47
  end
@@ -36,7 +49,20 @@ module RuboCop
36
49
  private
37
50
 
38
51
  def singleton_attr?(node)
39
- attr?(node) && node.ancestors.map(&:type).include?(:sclass)
52
+ (attr?(node) || attr_internal?(node)) &&
53
+ defined_in_singleton_class?(node)
54
+ end
55
+
56
+ def defined_in_singleton_class?(node)
57
+ node.ancestors.each do |ancestor|
58
+ case ancestor.type
59
+ when :def then return false
60
+ when :sclass then return true
61
+ else next
62
+ end
63
+ end
64
+
65
+ false
40
66
  end
41
67
  end
42
68
  end
@@ -13,9 +13,41 @@ module RuboCop
13
13
  # Notifier.new(@info).deliver
14
14
  # end
15
15
  # end
16
+ #
17
+ # class Model
18
+ # class << self
19
+ # def table_name(name)
20
+ # @table_name = name
21
+ # end
22
+ # end
23
+ # end
24
+ #
25
+ # class Host
26
+ # %i[uri port].each do |key|
27
+ # define_singleton_method("#{key}=") do |value|
28
+ # instance_variable_set("@#{key}", value)
29
+ # end
30
+ # end
31
+ # end
32
+ #
33
+ # module Example
34
+ # module ClassMethods
35
+ # def test(params)
36
+ # @params = params
37
+ # end
38
+ # end
39
+ # end
16
40
  class InstanceVariableInClassMethod < Cop
17
41
  MSG = 'Avoid instance variables in class methods.'
18
42
 
43
+ def_node_matcher :instance_variable_set_call?, <<~MATCHER
44
+ (send nil? :instance_variable_set (...) (...))
45
+ MATCHER
46
+
47
+ def_node_matcher :instance_variable_get_call?, <<~MATCHER
48
+ (send nil? :instance_variable_get (...))
49
+ MATCHER
50
+
19
51
  def on_ivar(node)
20
52
  return unless class_method_definition?(node)
21
53
  return if synchronized?(node)
@@ -24,11 +56,20 @@ module RuboCop
24
56
  end
25
57
  alias on_ivasgn on_ivar
26
58
 
59
+ def on_send(node)
60
+ return unless instance_variable_call?(node)
61
+ return unless class_method_definition?(node)
62
+ return if synchronized?(node)
63
+
64
+ add_offense(node, message: MSG)
65
+ end
66
+
27
67
  private
28
68
 
29
69
  def class_method_definition?(node)
30
70
  in_defs?(node) ||
31
71
  in_def_sclass?(node) ||
72
+ in_def_class_methods?(node) ||
32
73
  singleton_method_definition?(node)
33
74
  end
34
75
 
@@ -48,6 +89,18 @@ module RuboCop
48
89
  end
49
90
  end
50
91
 
92
+ def in_def_class_methods?(node)
93
+ defn = node.ancestors.find(&:def_type?)
94
+ return unless defn
95
+
96
+ mod = defn.ancestors.find do |ancestor|
97
+ %i[class module].include?(ancestor.type)
98
+ end
99
+ return unless mod
100
+
101
+ class_methods_module?(mod)
102
+ end
103
+
51
104
  def singleton_method_definition?(node)
52
105
  node.ancestors.any? do |ancestor|
53
106
  next unless ancestor.children.first.is_a? AST::SendNode
@@ -64,6 +117,14 @@ module RuboCop
64
117
  s.send_type? && s.children.last == :synchronize
65
118
  end
66
119
  end
120
+
121
+ def instance_variable_call?(node)
122
+ instance_variable_set_call?(node) || instance_variable_get_call?(node)
123
+ end
124
+
125
+ def_node_matcher :class_methods_module?, <<~PATTERN
126
+ (module (const _ :ClassMethods) ...)
127
+ PATTERN
67
128
  end
68
129
  end
69
130
  end
@@ -0,0 +1,272 @@
1
+ # frozen_string_literal: true
2
+
3
+ module RuboCop
4
+ module Cop
5
+ module ThreadSafety
6
+ # This cop checks whether some class instance variable isn't a
7
+ # mutable literal (e.g. array or hash).
8
+ #
9
+ # It is based on Style/MutableConstant from RuboCop.
10
+ # See https://github.com/rubocop-hq/rubocop/blob/master/lib/rubocop/cop/style/mutable_constant.rb
11
+ #
12
+ # Class instance variables are a risk to threaded code as they are shared
13
+ # between threads. A mutable object such as an array or hash may be
14
+ # updated via an attr_reader so would not be detected by the
15
+ # ThreadSafety/ClassAndModuleAttributes cop.
16
+ #
17
+ # Strict mode can be used to freeze all class instance variables, rather
18
+ # than just literals.
19
+ # Strict mode is considered an experimental feature. It has not been
20
+ # updated with an exhaustive list of all methods that will produce frozen
21
+ # objects so there is a decent chance of getting some false positives.
22
+ # Luckily, there is no harm in freezing an already frozen object.
23
+ #
24
+ # @example EnforcedStyle: literals (default)
25
+ # # bad
26
+ # class Model
27
+ # @list = [1, 2, 3]
28
+ # end
29
+ #
30
+ # # good
31
+ # class Model
32
+ # @list = [1, 2, 3].freeze
33
+ # end
34
+ #
35
+ # # good
36
+ # class Model
37
+ # @var = <<~TESTING.freeze
38
+ # This is a heredoc
39
+ # TESTING
40
+ # end
41
+ #
42
+ # # good
43
+ # class Model
44
+ # @var = Something.new
45
+ # end
46
+ #
47
+ # @example EnforcedStyle: strict
48
+ # # bad
49
+ # class Model
50
+ # @var = Something.new
51
+ # end
52
+ #
53
+ # # bad
54
+ # class Model
55
+ # @var = Struct.new do
56
+ # def foo
57
+ # puts 1
58
+ # end
59
+ # end
60
+ # end
61
+ #
62
+ # # good
63
+ # class Model
64
+ # @var = Something.new.freeze
65
+ # end
66
+ #
67
+ # # good
68
+ # class Model
69
+ # @var = Struct.new do
70
+ # def foo
71
+ # puts 1
72
+ # end
73
+ # end.freeze
74
+ # end
75
+ class MutableClassInstanceVariable < Cop
76
+ include FrozenStringLiteral
77
+ include ConfigurableEnforcedStyle
78
+
79
+ MSG = 'Freeze mutable objects assigned to class instance variables.'
80
+ FROZEN_STRING_LITERAL_TYPES_RUBY27 = %i[str dstr].freeze
81
+ FROZEN_STRING_LITERAL_TYPES_RUBY30 = %i[str].freeze
82
+
83
+ def on_ivasgn(node)
84
+ return unless in_class?(node)
85
+
86
+ _, value = *node
87
+ on_assignment(value)
88
+ end
89
+
90
+ def on_or_asgn(node)
91
+ lhs, value = *node
92
+ return unless lhs&.ivasgn_type?
93
+ return unless in_class?(node)
94
+
95
+ on_assignment(value)
96
+ end
97
+
98
+ def on_masgn(node)
99
+ return unless in_class?(node)
100
+
101
+ mlhs, values = *node
102
+ return unless values.array_type?
103
+
104
+ mlhs.to_a.zip(values.to_a).each do |lhs, value|
105
+ next unless lhs.ivasgn_type?
106
+
107
+ on_assignment(value)
108
+ end
109
+ end
110
+
111
+ def autocorrect(node)
112
+ expr = node.source_range
113
+
114
+ lambda do |corrector|
115
+ splat_value = splat_value(node)
116
+ if splat_value
117
+ correct_splat_expansion(corrector, expr, splat_value)
118
+ elsif node.array_type? && !node.bracketed?
119
+ corrector.insert_before(expr, '[')
120
+ corrector.insert_after(expr, ']')
121
+ elsif requires_parentheses?(node)
122
+ corrector.insert_before(expr, '(')
123
+ corrector.insert_after(expr, ')')
124
+ end
125
+
126
+ corrector.insert_after(expr, '.freeze')
127
+ end
128
+ end
129
+
130
+ private
131
+
132
+ def frozen_string_literal?(node)
133
+ literal_types = if target_ruby_version >= 3.0
134
+ FROZEN_STRING_LITERAL_TYPES_RUBY30
135
+ else
136
+ FROZEN_STRING_LITERAL_TYPES_RUBY27
137
+ end
138
+ literal_types.include?(node.type) && frozen_string_literals_enabled?
139
+ end
140
+
141
+ def on_assignment(value)
142
+ if style == :strict
143
+ strict_check(value)
144
+ else
145
+ check(value)
146
+ end
147
+ end
148
+
149
+ def strict_check(value)
150
+ return if immutable_literal?(value)
151
+ return if operation_produces_immutable_object?(value)
152
+ return if operation_produces_threadsafe_object?(value)
153
+ return if frozen_string_literal?(value)
154
+
155
+ add_offense(value)
156
+ end
157
+
158
+ def check(value)
159
+ return unless mutable_literal?(value) ||
160
+ range_enclosed_in_parentheses?(value)
161
+ return if frozen_string_literal?(value)
162
+
163
+ add_offense(value)
164
+ end
165
+
166
+ def in_class?(node)
167
+ container = node.ancestors.find do |ancestor|
168
+ container?(ancestor)
169
+ end
170
+ return false if container.nil?
171
+
172
+ %i[class module].include?(container.type)
173
+ end
174
+
175
+ def container?(node)
176
+ return true if define_singleton_method?(node)
177
+
178
+ %i[def defs class module].include?(node.type)
179
+ end
180
+
181
+ def mutable_literal?(node)
182
+ return if node.nil?
183
+
184
+ node.mutable_literal? || range_type?(node)
185
+ end
186
+
187
+ def immutable_literal?(node)
188
+ node.nil? || node.immutable_literal?
189
+ end
190
+
191
+ def requires_parentheses?(node)
192
+ range_type?(node) ||
193
+ (node.send_type? && node.loc.dot.nil?)
194
+ end
195
+
196
+ def range_type?(node)
197
+ node.erange_type? || node.irange_type?
198
+ end
199
+
200
+ def correct_splat_expansion(corrector, expr, splat_value)
201
+ if range_enclosed_in_parentheses?(splat_value)
202
+ corrector.replace(expr, "#{splat_value.source}.to_a")
203
+ else
204
+ corrector.replace(expr, "(#{splat_value.source}).to_a")
205
+ end
206
+ end
207
+
208
+ def_node_matcher :define_singleton_method?, <<~PATTERN
209
+ (block (send nil? :define_singleton_method ...) ...)
210
+ PATTERN
211
+
212
+ def_node_matcher :splat_value, <<~PATTERN
213
+ (array (splat $_))
214
+ PATTERN
215
+
216
+ # NOTE: Some of these patterns may not actually return an immutable
217
+ # object but we will consider them immutable for this cop.
218
+ def_node_matcher :operation_produces_immutable_object?, <<~PATTERN
219
+ {
220
+ (const _ _)
221
+ (send (const {nil? cbase} :Struct) :new ...)
222
+ (block (send (const {nil? cbase} :Struct) :new ...) ...)
223
+ (send _ :freeze)
224
+ (send {float int} {:+ :- :* :** :/ :% :<<} _)
225
+ (send _ {:+ :- :* :** :/ :%} {float int})
226
+ (send _ {:== :=== :!= :<= :>= :< :>} _)
227
+ (send (const {nil? cbase} :ENV) :[] _)
228
+ (or (send (const {nil? cbase} :ENV) :[] _) _)
229
+ (send _ {:count :length :size} ...)
230
+ (block (send _ {:count :length :size} ...) ...)
231
+ }
232
+ PATTERN
233
+
234
+ def_node_matcher :operation_produces_threadsafe_object?, <<~PATTERN
235
+ {
236
+ (send (const {nil? cbase} :Queue) :new ...)
237
+ (send
238
+ (const (const {nil? cbase} :ThreadSafe) {:Hash :Array})
239
+ :new ...)
240
+ (block
241
+ (send
242
+ (const (const {nil? cbase} :ThreadSafe) {:Hash :Array})
243
+ :new ...)
244
+ ...)
245
+ (send (const (const {nil? cbase} :Concurrent) _) :new ...)
246
+ (block
247
+ (send (const (const {nil? cbase} :Concurrent) _) :new ...)
248
+ ...)
249
+ (send (const (const (const {nil? cbase} :Concurrent) _) _) :new ...)
250
+ (block
251
+ (send
252
+ (const (const (const {nil? cbase} :Concurrent) _) _)
253
+ :new ...)
254
+ ...)
255
+ (send
256
+ (const (const (const (const {nil? cbase} :Concurrent) _) _) _)
257
+ :new ...)
258
+ (block
259
+ (send
260
+ (const (const (const (const {nil? cbase} :Concurrent) _) _) _)
261
+ :new ...)
262
+ ...)
263
+ }
264
+ PATTERN
265
+
266
+ def_node_matcher :range_enclosed_in_parentheses?, <<~PATTERN
267
+ (begin ({irange erange} _ _))
268
+ PATTERN
269
+ end
270
+ end
271
+ end
272
+ end
@@ -13,8 +13,8 @@ module RuboCop
13
13
  class NewThread < Cop
14
14
  MSG = 'Avoid starting new threads.'
15
15
 
16
- def_node_matcher :new_thread?, <<-MATCHER
17
- (send (const nil? :Thread) :new)
16
+ def_node_matcher :new_thread?, <<~MATCHER
17
+ (send (const {nil? cbase} :Thread) :new)
18
18
  MATCHER
19
19
 
20
20
  def on_send(node)
@@ -0,0 +1,20 @@
1
+ # frozen_string_literal: true
2
+
3
+ # The original code is from https://github.com/rubocop-hq/rubocop-rspec/blob/master/lib/rubocop/rspec/inject.rb
4
+ # See https://github.com/rubocop-hq/rubocop-rspec/blob/master/MIT_LICENSE.md
5
+ module RuboCop
6
+ module ThreadSafety
7
+ # Because RuboCop doesn't yet support plugins, we have to monkey patch in a
8
+ # bit of our configuration.
9
+ module Inject
10
+ def self.defaults!
11
+ path = CONFIG_DEFAULT.to_s
12
+ hash = ConfigLoader.__send__(:load_yaml_configuration, path)
13
+ config = Config.new(hash, path).tap(&:make_excludes_absolute)
14
+ puts "configuration from \#{path}" if ConfigLoader.debug?
15
+ config = ConfigLoader.merge_with_default(config, path)
16
+ ConfigLoader.instance_variable_set(:@default_configuration, config)
17
+ end
18
+ end
19
+ end
20
+ end
@@ -2,6 +2,6 @@
2
2
 
3
3
  module RuboCop
4
4
  module ThreadSafety
5
- VERSION = '0.4.0'
5
+ VERSION = '0.4.4'
6
6
  end
7
7
  end
@@ -0,0 +1,12 @@
1
+ # frozen_string_literal: true
2
+
3
+ module RuboCop
4
+ # RuboCop::ThreadSafety detects some potential thread safety issues.
5
+ module ThreadSafety
6
+ PROJECT_ROOT = Pathname.new(File.expand_path('../../', __dir__))
7
+ CONFIG_DEFAULT = PROJECT_ROOT.join('config', 'default.yml').freeze
8
+ CONFIG = YAML.safe_load(CONFIG_DEFAULT.read).freeze
9
+
10
+ private_constant(:CONFIG_DEFAULT, :PROJECT_ROOT)
11
+ end
12
+ end
@@ -2,8 +2,13 @@
2
2
 
3
3
  require 'rubocop'
4
4
 
5
+ require 'rubocop/thread_safety'
5
6
  require 'rubocop/thread_safety/version'
7
+ require 'rubocop/thread_safety/inject'
8
+
9
+ RuboCop::ThreadSafety::Inject.defaults!
6
10
 
7
11
  require 'rubocop/cop/thread_safety/instance_variable_in_class_method'
8
12
  require 'rubocop/cop/thread_safety/class_and_module_attributes'
13
+ require 'rubocop/cop/thread_safety/mutable_class_instance_variable'
9
14
  require 'rubocop/cop/thread_safety/new_thread'
@@ -26,10 +26,12 @@ Gem::Specification.new do |spec|
26
26
  spec.executables = spec.files.grep(%r{^exe/}) { |f| File.basename(f) }
27
27
  spec.require_paths = ['lib']
28
28
 
29
- spec.add_runtime_dependency 'rubocop', '>= 0.51.0'
29
+ spec.required_ruby_version = '>= 2.3.0'
30
30
 
31
+ spec.add_runtime_dependency 'rubocop', '>= 0.53.0'
32
+
33
+ spec.add_development_dependency 'appraisal'
31
34
  spec.add_development_dependency 'bundler', '>= 1.10', '< 3'
32
- spec.add_development_dependency 'powerpack', '~> 0.1'
33
35
  spec.add_development_dependency 'pry' unless ENV['CI']
34
36
  spec.add_development_dependency 'rake', '>= 10.0'
35
37
  spec.add_development_dependency 'rspec', '~> 3.0'
metadata CHANGED
@@ -1,14 +1,14 @@
1
1
  --- !ruby/object:Gem::Specification
2
2
  name: rubocop-thread_safety
3
3
  version: !ruby/object:Gem::Version
4
- version: 0.4.0
4
+ version: 0.4.4
5
5
  platform: ruby
6
6
  authors:
7
7
  - Michael Gee
8
- autorequire:
8
+ autorequire:
9
9
  bindir: exe
10
10
  cert_chain: []
11
- date: 2020-06-22 00:00:00.000000000 Z
11
+ date: 2021-09-10 00:00:00.000000000 Z
12
12
  dependencies:
13
13
  - !ruby/object:Gem::Dependency
14
14
  name: rubocop
@@ -16,48 +16,48 @@ dependencies:
16
16
  requirements:
17
17
  - - ">="
18
18
  - !ruby/object:Gem::Version
19
- version: 0.51.0
19
+ version: 0.53.0
20
20
  type: :runtime
21
21
  prerelease: false
22
22
  version_requirements: !ruby/object:Gem::Requirement
23
23
  requirements:
24
24
  - - ">="
25
25
  - !ruby/object:Gem::Version
26
- version: 0.51.0
26
+ version: 0.53.0
27
27
  - !ruby/object:Gem::Dependency
28
- name: bundler
28
+ name: appraisal
29
29
  requirement: !ruby/object:Gem::Requirement
30
30
  requirements:
31
31
  - - ">="
32
32
  - !ruby/object:Gem::Version
33
- version: '1.10'
34
- - - "<"
35
- - !ruby/object:Gem::Version
36
- version: '3'
33
+ version: '0'
37
34
  type: :development
38
35
  prerelease: false
39
36
  version_requirements: !ruby/object:Gem::Requirement
40
37
  requirements:
41
38
  - - ">="
42
39
  - !ruby/object:Gem::Version
43
- version: '1.10'
44
- - - "<"
45
- - !ruby/object:Gem::Version
46
- version: '3'
40
+ version: '0'
47
41
  - !ruby/object:Gem::Dependency
48
- name: powerpack
42
+ name: bundler
49
43
  requirement: !ruby/object:Gem::Requirement
50
44
  requirements:
51
- - - "~>"
45
+ - - ">="
46
+ - !ruby/object:Gem::Version
47
+ version: '1.10'
48
+ - - "<"
52
49
  - !ruby/object:Gem::Version
53
- version: '0.1'
50
+ version: '3'
54
51
  type: :development
55
52
  prerelease: false
56
53
  version_requirements: !ruby/object:Gem::Requirement
57
54
  requirements:
58
- - - "~>"
55
+ - - ">="
56
+ - !ruby/object:Gem::Version
57
+ version: '1.10'
58
+ - - "<"
59
59
  - !ruby/object:Gem::Version
60
- version: '0.1'
60
+ version: '3'
61
61
  - !ruby/object:Gem::Dependency
62
62
  name: pry
63
63
  requirement: !ruby/object:Gem::Requirement
@@ -113,23 +113,32 @@ files:
113
113
  - ".rspec"
114
114
  - ".rubocop.yml"
115
115
  - ".travis.yml"
116
+ - Appraisals
116
117
  - Gemfile
117
118
  - LICENSE.txt
118
119
  - README.md
119
120
  - Rakefile
120
121
  - bin/console
121
122
  - bin/setup
123
+ - config/default.yml
124
+ - gemfiles/rubocop_0.53.gemfile
125
+ - gemfiles/rubocop_0.81.gemfile
126
+ - gemfiles/rubocop_0.86.gemfile
127
+ - gemfiles/rubocop_1.20.gemfile
122
128
  - lib/rubocop-thread_safety.rb
123
129
  - lib/rubocop/cop/thread_safety/class_and_module_attributes.rb
124
130
  - lib/rubocop/cop/thread_safety/instance_variable_in_class_method.rb
131
+ - lib/rubocop/cop/thread_safety/mutable_class_instance_variable.rb
125
132
  - lib/rubocop/cop/thread_safety/new_thread.rb
133
+ - lib/rubocop/thread_safety.rb
134
+ - lib/rubocop/thread_safety/inject.rb
126
135
  - lib/rubocop/thread_safety/version.rb
127
136
  - rubocop-thread_safety.gemspec
128
137
  homepage: https://github.com/covermymeds/rubocop-thread_safety
129
138
  licenses:
130
139
  - MIT
131
140
  metadata: {}
132
- post_install_message:
141
+ post_install_message:
133
142
  rdoc_options: []
134
143
  require_paths:
135
144
  - lib
@@ -137,15 +146,15 @@ required_ruby_version: !ruby/object:Gem::Requirement
137
146
  requirements:
138
147
  - - ">="
139
148
  - !ruby/object:Gem::Version
140
- version: '0'
149
+ version: 2.3.0
141
150
  required_rubygems_version: !ruby/object:Gem::Requirement
142
151
  requirements:
143
152
  - - ">="
144
153
  - !ruby/object:Gem::Version
145
154
  version: '0'
146
155
  requirements: []
147
- rubygems_version: 3.0.3
148
- signing_key:
156
+ rubygems_version: 3.0.3.1
157
+ signing_key:
149
158
  specification_version: 4
150
159
  summary: Thread-safety checks via static analysis
151
160
  test_files: []