rubocop-thread_safety 0.4.4 → 0.7.2
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/CHANGELOG.md +30 -0
- data/LICENSE.txt +2 -1
- data/README.md +15 -8
- data/config/default.yml +19 -2
- data/config/obsoletion.yml +2 -0
- data/lib/rubocop/cop/mixin/operation_with_threadsafe_result.rb +44 -0
- data/lib/rubocop/cop/thread_safety/class_and_module_attributes.rb +18 -5
- data/lib/rubocop/cop/thread_safety/class_instance_variable.rb +259 -0
- data/lib/rubocop/cop/thread_safety/dir_chdir.rb +65 -0
- data/lib/rubocop/cop/thread_safety/mutable_class_instance_variable.rb +41 -60
- data/lib/rubocop/cop/thread_safety/new_thread.rb +6 -5
- data/lib/rubocop/cop/thread_safety/rack_middleware_instance_variable.rb +121 -0
- data/lib/rubocop/thread_safety/plugin.rb +38 -0
- data/lib/rubocop/thread_safety/version.rb +3 -1
- data/lib/rubocop/thread_safety.rb +0 -5
- data/lib/rubocop-thread_safety.rb +5 -3
- metadata +31 -94
- data/.gitignore +0 -10
- data/.rspec +0 -2
- data/.rubocop.yml +0 -70
- data/.travis.yml +0 -64
- data/Appraisals +0 -23
- data/Gemfile +0 -6
- data/Rakefile +0 -8
- data/bin/console +0 -15
- data/bin/setup +0 -6
- data/gemfiles/rubocop_0.53.gemfile +0 -7
- data/gemfiles/rubocop_0.81.gemfile +0 -7
- data/gemfiles/rubocop_0.86.gemfile +0 -7
- data/gemfiles/rubocop_1.20.gemfile +0 -7
- data/lib/rubocop/cop/thread_safety/instance_variable_in_class_method.rb +0 -131
- data/lib/rubocop/thread_safety/inject.rb +0 -20
- data/rubocop-thread_safety.gemspec +0 -38
checksums.yaml
CHANGED
@@ -1,7 +1,7 @@
|
|
1
1
|
---
|
2
2
|
SHA256:
|
3
|
-
metadata.gz:
|
4
|
-
data.tar.gz:
|
3
|
+
metadata.gz: cdf9af170c6dcfe7ac085b6e68467551d4c14d1eac843fefb97f39645cf556a6
|
4
|
+
data.tar.gz: e6efe781ead225cbdcf14185a67851a57fa60fe0e31627342a7204eb5f656c3f
|
5
5
|
SHA512:
|
6
|
-
metadata.gz:
|
7
|
-
data.tar.gz:
|
6
|
+
metadata.gz: 18e2788dfc505506dd73e7231008200edca8c5055612442b14e6639ff362af00e4de983fc4be0980e6f3820a365175138cd7d04708ced0bf401db3c00eb429df
|
7
|
+
data.tar.gz: 94da6513c5ed7f1561f7e88385b3eddb6da8984127ac1da7e16f0a4c6c4c871390af0599b7b8c43c53bfc792af82363fdbe16b848fc7a93c6a10ea6d630e60d4
|
data/CHANGELOG.md
ADDED
@@ -0,0 +1,30 @@
|
|
1
|
+
# Change log
|
2
|
+
|
3
|
+
## master
|
4
|
+
|
5
|
+
## 0.7.2
|
6
|
+
|
7
|
+
- [#88](https://github.com/rubocop/rubocop-thread_safety/pull/88): Fix incorrect plugin metadata version. ([@viralpraxis](https://github.com/viralpraxis))
|
8
|
+
|
9
|
+
## 0.7.1
|
10
|
+
|
11
|
+
- [#84](https://github.com/rubocop/rubocop-thread_safety/pull/84): Rename `InstanceVariableInClassMethod` in default config ([@sambostock](https://github.com/sambostock))
|
12
|
+
|
13
|
+
## 0.7.0
|
14
|
+
|
15
|
+
- [#80](https://github.com/rubocop/rubocop-thread_safety/pull/80) Make RuboCop ThreadSafety work as a RuboCop plugin. ([@bquorning](https://github.com/bquorning))
|
16
|
+
- [#76](https://github.com/rubocop/rubocop-thread_safety/pull/76): Detect offenses when using safe navigation for `ThreadSafety/DirChdir`, `ThreadSafety/NewThread` and `ThreadSafety/RackMiddlewareInstanceVariable` cops. ([@viralpraxis](https://github.com/viralpraxis))
|
17
|
+
- [#73](https://github.com/rubocop/rubocop-thread_safety/pull/73): Add `AllowCallWithBlock` option to `ThreadSafety/DirChdir` cop. ([@viralpraxis](https://github.com/viralpraxis))
|
18
|
+
|
19
|
+
## 0.6.0
|
20
|
+
|
21
|
+
* [#59](https://github.com/rubocop/rubocop-thread_safety/pull/59): Rename `ThreadSafety::InstanceVariableInClassMethod` cop to `ThreadSafety::ClassInstanceVariable` to better reflect its purpose. ([@viralpraxis](https://github.com/viralpraxis))
|
22
|
+
* [#55](https://github.com/rubocop/rubocop-thread_safety/pull/55): Enhance `ThreadSafety::InstanceVariableInClassMethod` cop to detect offenses within `class_eval/exec` blocks. ([@viralpraxis](https://github.com/viralpraxis))
|
23
|
+
* [#54](https://github.com/rubocop/rubocop-thread_safety/pull/54): Drop support for RuboCop older than 1.48. ([@viralpraxis](https://github.com/viralpraxis))
|
24
|
+
* [#52](https://github.com/rubocop/rubocop-thread_safety/pull/52): Add new `RackMiddlewareInstanceVariable` cop to detect instance variables in Rack middleware. ([@viralpraxis](https://github.com/viralpraxis))
|
25
|
+
* [#48](https://github.com/rubocop/rubocop-thread_safety/pull/48): Do not report instance variables in `ActionDispatch` callbacks in singleton methods. ([@viralpraxis](https://github.com/viralpraxis))
|
26
|
+
* [#43](https://github.com/rubocop/rubocop-thread_safety/pull/43): Make detection of ActiveSupport's `class_attribute` configurable. ([@viralpraxis](https://github.com/viralpraxis))
|
27
|
+
* [#42](https://github.com/rubocop/rubocop-thread_safety/pull/42): Fix some `InstanceVariableInClassMethod` cop false positive offenses. ([@viralpraxis](https://github.com/viralpraxis))
|
28
|
+
* [#41](https://github.com/rubocop/rubocop-thread_safety/pull/41): Drop support for MRI older than 2.7. ([@viralpraxis](https://github.com/viralpraxis))
|
29
|
+
* [#38](https://github.com/rubocop/rubocop-thread_safety/pull/38): Fix `NewThread` cop detection is case of `Thread.start`, `Thread.fork`, or `Thread.new` with arguments. ([@viralpraxis](https://github.com/viralpraxis))
|
30
|
+
* [#36](https://github.com/rubocop/rubocop-thread_safety/pull/36): Add new `DirChdir` cop to detect `Dir.chdir` calls. ([@viralpraxis](https://github.com/viralpraxis))
|
data/LICENSE.txt
CHANGED
@@ -1,4 +1,5 @@
|
|
1
|
-
Copyright 2016-
|
1
|
+
Portions Copyright 2016-2023 Michael Gee and contributors
|
2
|
+
Portions Copyright 2016-2022 CoverMyMeds
|
2
3
|
|
3
4
|
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
5
|
|
data/README.md
CHANGED
@@ -1,16 +1,16 @@
|
|
1
1
|
# RuboCop::ThreadSafety
|
2
2
|
|
3
3
|
Thread-safety analysis for your projects, as an extension to
|
4
|
-
[RuboCop](https://github.com/
|
4
|
+
[RuboCop](https://github.com/rubocop/rubocop).
|
5
5
|
|
6
6
|
## Installation and Usage
|
7
7
|
|
8
8
|
### Installation into an application
|
9
9
|
|
10
|
-
Add this line to your application's Gemfile:
|
10
|
+
Add this line to your application's Gemfile (using `require: false` as it's a standalone tool):
|
11
11
|
|
12
12
|
```ruby
|
13
|
-
gem 'rubocop-thread_safety'
|
13
|
+
gem 'rubocop-thread_safety', require: false
|
14
14
|
```
|
15
15
|
|
16
16
|
Install it with Bundler by invoking:
|
@@ -19,11 +19,14 @@ Install it with Bundler by invoking:
|
|
19
19
|
|
20
20
|
Add this line to your application's `.rubocop.yml`:
|
21
21
|
|
22
|
-
|
22
|
+
plugins: rubocop-thread_safety
|
23
23
|
|
24
24
|
Now you can run `rubocop` and it will automatically load the RuboCop
|
25
25
|
Thread-Safety cops together with the standard cops.
|
26
26
|
|
27
|
+
> [!NOTE]
|
28
|
+
> The plugin system is supported in RuboCop 1.72+. In earlier versions, use `require` instead of `plugins`.
|
29
|
+
|
27
30
|
### Scanning an application without adding it to the Gemfile
|
28
31
|
|
29
32
|
Install the gem:
|
@@ -32,11 +35,11 @@ Install the gem:
|
|
32
35
|
|
33
36
|
Scan the application for just thread-safety issues:
|
34
37
|
|
35
|
-
$ rubocop
|
38
|
+
$ rubocop --plugin rubocop-thread_safety --only ThreadSafety,Style/GlobalVars,Style/ClassVars,Style/MutableConstant
|
36
39
|
|
37
40
|
### Configuration
|
38
41
|
|
39
|
-
There are some added [configuration options](https://github.com/
|
42
|
+
There are some added [configuration options](https://github.com/rubocop/rubocop-thread_safety/blob/master/config/default.yml) that can be tweaked to modify the behaviour of these thread-safety cops.
|
40
43
|
|
41
44
|
### Correcting code for thread-safety
|
42
45
|
|
@@ -60,6 +63,8 @@ Improvements that would make shared state thread-safe include:
|
|
60
63
|
* Use [`RequestStore`](https://github.com/steveklabnik/request_store)
|
61
64
|
* Use `Thread.current[:name]`
|
62
65
|
|
66
|
+
Certain system calls, such as `chdir`, affect the entire process. To avoid potential thread-safety issues, it's preferable to use (if possible) the `chdir` option in methods like `Kernel.system` and `IO.popen` rather than relying on `Dir.chdir`.
|
67
|
+
|
63
68
|
## Development
|
64
69
|
|
65
70
|
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.
|
@@ -68,9 +73,11 @@ To install this gem onto your local machine, run `bundle exec rake install`. To
|
|
68
73
|
|
69
74
|
## Contributing
|
70
75
|
|
71
|
-
Bug reports and pull requests are welcome on GitHub at https://github.com/
|
76
|
+
Bug reports and pull requests are welcome on GitHub at https://github.com/rubocop/rubocop-thread_safety.
|
72
77
|
|
73
78
|
## Copyright
|
74
79
|
|
75
|
-
Copyright (c) 2016-
|
80
|
+
Portions Copyright (c) 2016-2023 Michael Gee and [contributors](https://github.com/rubocop/rubocop-thread_safety/graphs/contributors).
|
81
|
+
Portions Copyright (c) 2016-2023 CoverMyMeds.
|
82
|
+
|
76
83
|
See [LICENSE.txt](LICENSE.txt) for further details.
|
data/config/default.yml
CHANGED
@@ -5,15 +5,17 @@
|
|
5
5
|
ThreadSafety/ClassAndModuleAttributes:
|
6
6
|
Description: 'Avoid mutating class and module attributes.'
|
7
7
|
Enabled: true
|
8
|
+
ActiveSupportClassAttributeAllowed: false
|
8
9
|
|
9
|
-
ThreadSafety/
|
10
|
-
Description: 'Avoid
|
10
|
+
ThreadSafety/ClassInstanceVariable:
|
11
|
+
Description: 'Avoid class instance variables.'
|
11
12
|
Enabled: true
|
12
13
|
|
13
14
|
ThreadSafety/MutableClassInstanceVariable:
|
14
15
|
Description: 'Do not assign mutable objects to class instance variables.'
|
15
16
|
Enabled: true
|
16
17
|
EnforcedStyle: literals
|
18
|
+
SafeAutoCorrect: false
|
17
19
|
SupportedStyles:
|
18
20
|
# literals: freeze literals assigned to constants
|
19
21
|
# strict: freeze all constants
|
@@ -29,3 +31,18 @@ ThreadSafety/NewThread:
|
|
29
31
|
Avoid starting new threads.
|
30
32
|
Let a framework like Sidekiq handle the threads.
|
31
33
|
Enabled: true
|
34
|
+
|
35
|
+
ThreadSafety/DirChdir:
|
36
|
+
Description: Avoid using `Dir.chdir` due to its process-wide effect.
|
37
|
+
Enabled: true
|
38
|
+
AllowCallWithBlock: false
|
39
|
+
|
40
|
+
ThreadSafety/RackMiddlewareInstanceVariable:
|
41
|
+
Description: Avoid instance variables in Rack middleware.
|
42
|
+
Enabled: true
|
43
|
+
Include:
|
44
|
+
- 'app/middleware/**/*.rb'
|
45
|
+
- 'lib/middleware/**/*.rb'
|
46
|
+
- 'app/middlewares/**/*.rb'
|
47
|
+
- 'lib/middlewares/**/*.rb'
|
48
|
+
AllowedIdentifiers: []
|
@@ -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
|
@@ -12,38 +12,47 @@ module RuboCop
|
|
12
12
|
# class User
|
13
13
|
# cattr_accessor :current_user
|
14
14
|
# end
|
15
|
-
class ClassAndModuleAttributes <
|
15
|
+
class ClassAndModuleAttributes < Base
|
16
16
|
MSG = 'Avoid mutating class and module attributes.'
|
17
|
+
RESTRICT_ON_SEND = %i[
|
18
|
+
mattr_writer mattr_accessor cattr_writer cattr_accessor
|
19
|
+
class_attribute
|
20
|
+
attr attr_accessor attr_writer
|
21
|
+
attr_internal attr_internal_accessor attr_internal_writer
|
22
|
+
].freeze
|
17
23
|
|
24
|
+
# @!method mattr?(node)
|
18
25
|
def_node_matcher :mattr?, <<~MATCHER
|
19
26
|
(send nil?
|
20
27
|
{:mattr_writer :mattr_accessor :cattr_writer :cattr_accessor}
|
21
28
|
...)
|
22
29
|
MATCHER
|
23
30
|
|
31
|
+
# @!method attr?(node)
|
24
32
|
def_node_matcher :attr?, <<~MATCHER
|
25
33
|
(send nil?
|
26
34
|
{:attr :attr_accessor :attr_writer}
|
27
35
|
...)
|
28
36
|
MATCHER
|
29
37
|
|
38
|
+
# @!method attr_internal?(node)
|
30
39
|
def_node_matcher :attr_internal?, <<~MATCHER
|
31
40
|
(send nil?
|
32
41
|
{:attr_internal :attr_internal_accessor :attr_internal_writer}
|
33
42
|
...)
|
34
43
|
MATCHER
|
35
44
|
|
45
|
+
# @!method class_attr?(node)
|
36
46
|
def_node_matcher :class_attr?, <<~MATCHER
|
37
47
|
(send nil?
|
38
48
|
:class_attribute
|
39
49
|
...)
|
40
50
|
MATCHER
|
41
51
|
|
42
|
-
def on_send(node)
|
43
|
-
return unless mattr?(node) || class_attr?(node) ||
|
44
|
-
singleton_attr?(node)
|
52
|
+
def on_send(node) # rubocop:disable InternalAffairs/OnSendWithoutOnCSend
|
53
|
+
return unless mattr?(node) || (!class_attribute_allowed? && class_attr?(node)) || singleton_attr?(node)
|
45
54
|
|
46
|
-
add_offense(node
|
55
|
+
add_offense(node)
|
47
56
|
end
|
48
57
|
|
49
58
|
private
|
@@ -64,6 +73,10 @@ module RuboCop
|
|
64
73
|
|
65
74
|
false
|
66
75
|
end
|
76
|
+
|
77
|
+
def class_attribute_allowed?
|
78
|
+
cop_config['ActiveSupportClassAttributeAllowed']
|
79
|
+
end
|
67
80
|
end
|
68
81
|
end
|
69
82
|
end
|
@@ -0,0 +1,259 @@
|
|
1
|
+
# frozen_string_literal: true
|
2
|
+
|
3
|
+
module RuboCop
|
4
|
+
module Cop
|
5
|
+
module ThreadSafety
|
6
|
+
# Avoid class instance variables.
|
7
|
+
#
|
8
|
+
# @example
|
9
|
+
# # bad
|
10
|
+
# class User
|
11
|
+
# def self.notify(info)
|
12
|
+
# @info = validate(info)
|
13
|
+
# Notifier.new(@info).deliver
|
14
|
+
# end
|
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
|
40
|
+
#
|
41
|
+
# module Example
|
42
|
+
# class_methods do
|
43
|
+
# def test(params)
|
44
|
+
# @params = params
|
45
|
+
# end
|
46
|
+
# end
|
47
|
+
# end
|
48
|
+
#
|
49
|
+
# module Example
|
50
|
+
# module_function
|
51
|
+
#
|
52
|
+
# def test(params)
|
53
|
+
# @params = params
|
54
|
+
# end
|
55
|
+
# end
|
56
|
+
#
|
57
|
+
# module Example
|
58
|
+
# def test(params)
|
59
|
+
# @params = params
|
60
|
+
# end
|
61
|
+
#
|
62
|
+
# module_function :test
|
63
|
+
# end
|
64
|
+
class ClassInstanceVariable < Base
|
65
|
+
MSG = 'Avoid class instance variables.'
|
66
|
+
RESTRICT_ON_SEND = %i[
|
67
|
+
instance_variable_set
|
68
|
+
instance_variable_get
|
69
|
+
].freeze
|
70
|
+
|
71
|
+
# @!method instance_variable_set_call?(node)
|
72
|
+
def_node_matcher :instance_variable_set_call?, <<~MATCHER
|
73
|
+
(send nil? :instance_variable_set (...) (...))
|
74
|
+
MATCHER
|
75
|
+
|
76
|
+
# @!method instance_variable_get_call?(node)
|
77
|
+
def_node_matcher :instance_variable_get_call?, <<~MATCHER
|
78
|
+
(send nil? :instance_variable_get (...))
|
79
|
+
MATCHER
|
80
|
+
|
81
|
+
def on_ivar(node)
|
82
|
+
return unless class_method_definition?(node)
|
83
|
+
return if method_definition?(node)
|
84
|
+
return if synchronized?(node)
|
85
|
+
|
86
|
+
add_offense(node.loc.name)
|
87
|
+
end
|
88
|
+
alias on_ivasgn on_ivar
|
89
|
+
|
90
|
+
def on_send(node) # rubocop:disable InternalAffairs/OnSendWithoutOnCSend
|
91
|
+
return unless instance_variable_call?(node)
|
92
|
+
return unless class_method_definition?(node)
|
93
|
+
return if method_definition?(node)
|
94
|
+
return if synchronized?(node)
|
95
|
+
|
96
|
+
add_offense(node)
|
97
|
+
end
|
98
|
+
|
99
|
+
private
|
100
|
+
|
101
|
+
def class_method_definition?(node)
|
102
|
+
in_defs?(node) ||
|
103
|
+
in_def_sclass?(node) ||
|
104
|
+
in_def_class_methods?(node) ||
|
105
|
+
in_def_module_function?(node) ||
|
106
|
+
in_class_eval?(node) ||
|
107
|
+
singleton_method_definition?(node)
|
108
|
+
end
|
109
|
+
|
110
|
+
def in_defs?(node)
|
111
|
+
node.ancestors.any? do |ancestor|
|
112
|
+
break false if new_lexical_scope?(ancestor)
|
113
|
+
|
114
|
+
ancestor.defs_type?
|
115
|
+
end
|
116
|
+
end
|
117
|
+
|
118
|
+
def in_def_sclass?(node)
|
119
|
+
defn = node.ancestors.find do |ancestor|
|
120
|
+
break if new_lexical_scope?(ancestor)
|
121
|
+
|
122
|
+
ancestor.def_type?
|
123
|
+
end
|
124
|
+
|
125
|
+
defn&.ancestors&.any?(&:sclass_type?)
|
126
|
+
end
|
127
|
+
|
128
|
+
def in_def_class_methods?(node)
|
129
|
+
in_def_class_methods_dsl?(node) || in_def_class_methods_module?(node)
|
130
|
+
end
|
131
|
+
|
132
|
+
def in_def_class_methods_dsl?(node)
|
133
|
+
node.ancestors.any? do |ancestor|
|
134
|
+
break if new_lexical_scope?(ancestor)
|
135
|
+
next unless ancestor.block_type?
|
136
|
+
|
137
|
+
ancestor.children.first.command? :class_methods
|
138
|
+
end
|
139
|
+
end
|
140
|
+
|
141
|
+
def in_def_class_methods_module?(node)
|
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
|
148
|
+
|
149
|
+
mod = defn.ancestors.find do |ancestor|
|
150
|
+
%i[class module].include?(ancestor.type)
|
151
|
+
end
|
152
|
+
return false unless mod
|
153
|
+
|
154
|
+
class_methods_module?(mod)
|
155
|
+
end
|
156
|
+
|
157
|
+
def in_def_module_function?(node)
|
158
|
+
defn = node.ancestors.find(&:def_type?)
|
159
|
+
return false unless defn
|
160
|
+
|
161
|
+
defn.left_siblings.any? { |sibling| module_function_bare_access_modifier?(sibling) } ||
|
162
|
+
defn.right_siblings.any? { |sibling| module_function_for?(sibling, defn.method_name) }
|
163
|
+
end
|
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
|
+
|
176
|
+
def singleton_method_definition?(node)
|
177
|
+
node.ancestors.any? do |ancestor|
|
178
|
+
break if new_lexical_scope?(ancestor)
|
179
|
+
next unless ancestor.children.first.is_a? AST::SendNode
|
180
|
+
|
181
|
+
ancestor.children.first.command? :define_singleton_method
|
182
|
+
end
|
183
|
+
end
|
184
|
+
|
185
|
+
def method_definition?(node)
|
186
|
+
node.ancestors.any? do |ancestor|
|
187
|
+
break if new_lexical_scope?(ancestor)
|
188
|
+
next unless ancestor.children.first.is_a? AST::SendNode
|
189
|
+
|
190
|
+
ancestor.children.first.command? :define_method
|
191
|
+
end
|
192
|
+
end
|
193
|
+
|
194
|
+
def synchronized?(node)
|
195
|
+
node.ancestors.find do |ancestor|
|
196
|
+
next unless ancestor.block_type?
|
197
|
+
|
198
|
+
s = ancestor.children.first
|
199
|
+
s.send_type? && s.children.last == :synchronize
|
200
|
+
end
|
201
|
+
end
|
202
|
+
|
203
|
+
def instance_variable_call?(node)
|
204
|
+
instance_variable_set_call?(node) || instance_variable_get_call?(node)
|
205
|
+
end
|
206
|
+
|
207
|
+
def module_function_bare_access_modifier?(node)
|
208
|
+
return false unless node.respond_to?(:send_type?)
|
209
|
+
|
210
|
+
node.send_type? && node.bare_access_modifier? && node.method?(:module_function)
|
211
|
+
end
|
212
|
+
|
213
|
+
def match_name?(arg_name, method_name)
|
214
|
+
arg_name.to_sym == method_name.to_sym
|
215
|
+
end
|
216
|
+
|
217
|
+
# @!method class_methods_module?(node)
|
218
|
+
def_node_matcher :class_methods_module?, <<~PATTERN
|
219
|
+
(module (const _ :ClassMethods) ...)
|
220
|
+
PATTERN
|
221
|
+
|
222
|
+
# @!method module_function_for?(node)
|
223
|
+
def_node_matcher :module_function_for?, <<~PATTERN
|
224
|
+
(send nil? {:module_function} ({sym str} #match_name?(%1)))
|
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
|
256
|
+
end
|
257
|
+
end
|
258
|
+
end
|
259
|
+
end
|
@@ -0,0 +1,65 @@
|
|
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
|
+
# If `AllowCallWithBlock` (disabled by default) option is enabled,
|
8
|
+
# calling `Dir.chdir` with block will be allowed.
|
9
|
+
#
|
10
|
+
# @example
|
11
|
+
# # bad
|
12
|
+
# Dir.chdir("/var/run")
|
13
|
+
#
|
14
|
+
# # bad
|
15
|
+
# FileUtils.chdir("/var/run")
|
16
|
+
#
|
17
|
+
# @example AllowCallWithBlock: false (default)
|
18
|
+
# # good
|
19
|
+
# Dir.chdir("/var/run") do
|
20
|
+
# puts Dir.pwd
|
21
|
+
# end
|
22
|
+
#
|
23
|
+
# @example AllowCallWithBlock: true
|
24
|
+
# # bad
|
25
|
+
# Dir.chdir("/var/run") do
|
26
|
+
# puts Dir.pwd
|
27
|
+
# end
|
28
|
+
#
|
29
|
+
class DirChdir < Base
|
30
|
+
MESSAGE = 'Avoid using `%<module>s%<dot>s%<method>s` due to its process-wide effect.'
|
31
|
+
RESTRICT_ON_SEND = %i[chdir cd].freeze
|
32
|
+
|
33
|
+
# @!method chdir?(node)
|
34
|
+
def_node_matcher :chdir?, <<~MATCHER
|
35
|
+
{
|
36
|
+
(call (const {nil? cbase} {:Dir :FileUtils}) :chdir ...)
|
37
|
+
(call (const {nil? cbase} :FileUtils) :cd ...)
|
38
|
+
}
|
39
|
+
MATCHER
|
40
|
+
|
41
|
+
def on_send(node)
|
42
|
+
return unless chdir?(node)
|
43
|
+
return if allow_call_with_block? && (node.block_argument? || node.parent&.block_type?)
|
44
|
+
|
45
|
+
add_offense(
|
46
|
+
node,
|
47
|
+
message: format(
|
48
|
+
MESSAGE,
|
49
|
+
module: node.receiver.short_name,
|
50
|
+
method: node.method_name,
|
51
|
+
dot: node.loc.dot.source
|
52
|
+
)
|
53
|
+
)
|
54
|
+
end
|
55
|
+
alias on_csend on_send
|
56
|
+
|
57
|
+
private
|
58
|
+
|
59
|
+
def allow_call_with_block?
|
60
|
+
!!cop_config['AllowCallWithBlock']
|
61
|
+
end
|
62
|
+
end
|
63
|
+
end
|
64
|
+
end
|
65
|
+
end
|