rubocop-thread_safety 0.6.0 → 0.7.1

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 CHANGED
@@ -1,7 +1,7 @@
1
1
  ---
2
2
  SHA256:
3
- metadata.gz: fa732d8bbefbcfc25dfd2b8adbfcc84788d77d8a21992b152975d9ee92e2f105
4
- data.tar.gz: f2d29b69ad8e94c9c4b5de205b7301d45ffb3fb3a1c69429af27a5000e7350c6
3
+ metadata.gz: 7841ecd07e1f036568eea48b431d4743df6bc7254f83d91218c9f3bb67fd50bb
4
+ data.tar.gz: 98c2011b6ba532d5140cd693166a9db75c8074c240f4df1ef45c978b601b655c
5
5
  SHA512:
6
- metadata.gz: ec8b96d08f872297a64bbf558f93a27e09bbe61d2a06b7248fdb2a3896a7367c520eceb0ca7f1e88fdb26fc8a03e2eab1772794353b5e12a3724a3371360d689
7
- data.tar.gz: 7479c57f327e71e7fbc7b263ea13af5e4e908d7a29849946329bae072ba454636dca72465b1c7dc64d9150064d5e61ca68a81eb45a4ea247a5af69027afd6fb7
6
+ metadata.gz: a4a6d03e7a2b51443095239016626b89a739017c2ed5cbf90b44269b1e90bc4bcac9db8342aecca88c32436daa332b8833e0b0d51b768e991407691e3518f196
7
+ data.tar.gz: 4c2192cff08c1537730848b2f6318293e29a1baef1becfb4a41ac434469caec036466510cf3301e707f4e988b200faf2ef5985cf53122c9d637fa062f5ebd09b
data/CHANGELOG.md CHANGED
@@ -1,5 +1,17 @@
1
1
  # Change log
2
2
 
3
+ ## master
4
+
5
+ ## 0.7.1
6
+
7
+ - [#84](https://github.com/rubocop/rubocop-thread_safety/pull/84): Rename `InstanceVariableInClassMethod` in default config ([@sambostock](https://github.com/sambostock))
8
+
9
+ ## 0.7.0
10
+
11
+ - [#80](https://github.com/rubocop/rubocop-thread_safety/pull/80) Make RuboCop ThreadSafety work as a RuboCop plugin. ([@bquorning](https://github.com/bquorning))
12
+ - [#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))
13
+ - [#73](https://github.com/rubocop/rubocop-thread_safety/pull/73): Add `AllowCallWithBlock` option to `ThreadSafety/DirChdir` cop. ([@viralpraxis](https://github.com/viralpraxis))
14
+
3
15
  ## 0.6.0
4
16
 
5
17
  * [#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))
data/README.md CHANGED
@@ -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
- require: rubocop-thread_safety
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,7 +35,7 @@ Install the gem:
32
35
 
33
36
  Scan the application for just thread-safety issues:
34
37
 
35
- $ rubocop -r rubocop-thread_safety --only ThreadSafety,Style/GlobalVars,Style/ClassVars,Style/MutableConstant
38
+ $ rubocop --plugin rubocop-thread_safety --only ThreadSafety,Style/GlobalVars,Style/ClassVars,Style/MutableConstant
36
39
 
37
40
  ### Configuration
38
41
 
data/config/default.yml CHANGED
@@ -7,8 +7,8 @@ ThreadSafety/ClassAndModuleAttributes:
7
7
  Enabled: true
8
8
  ActiveSupportClassAttributeAllowed: false
9
9
 
10
- ThreadSafety/InstanceVariableInClassMethod:
11
- Description: 'Avoid using instance variables in class methods.'
10
+ ThreadSafety/ClassInstanceVariable:
11
+ Description: 'Avoid class instance variables.'
12
12
  Enabled: true
13
13
 
14
14
  ThreadSafety/MutableClassInstanceVariable:
@@ -35,6 +35,7 @@ ThreadSafety/NewThread:
35
35
  ThreadSafety/DirChdir:
36
36
  Description: Avoid using `Dir.chdir` due to its process-wide effect.
37
37
  Enabled: true
38
+ AllowCallWithBlock: false
38
39
 
39
40
  ThreadSafety/RackMiddlewareInstanceVariable:
40
41
  Description: Avoid instance variables in Rack middleware.
@@ -49,7 +49,7 @@ module RuboCop
49
49
  ...)
50
50
  MATCHER
51
51
 
52
- def on_send(node)
52
+ def on_send(node) # rubocop:disable InternalAffairs/OnSendWithoutOnCSend
53
53
  return unless mattr?(node) || (!class_attribute_allowed? && class_attr?(node)) || singleton_attr?(node)
54
54
 
55
55
  add_offense(node)
@@ -87,7 +87,7 @@ module RuboCop
87
87
  end
88
88
  alias on_ivasgn on_ivar
89
89
 
90
- def on_send(node)
90
+ def on_send(node) # rubocop:disable InternalAffairs/OnSendWithoutOnCSend
91
91
  return unless instance_variable_call?(node)
92
92
  return unless class_method_definition?(node)
93
93
  return if method_definition?(node)
@@ -4,6 +4,8 @@ module RuboCop
4
4
  module Cop
5
5
  module ThreadSafety
6
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.
7
9
  #
8
10
  # @example
9
11
  # # bad
@@ -11,25 +13,51 @@ module RuboCop
11
13
  #
12
14
  # # bad
13
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
+ #
14
29
  class DirChdir < Base
15
- MESSAGE = 'Avoid using `%<module>s.%<method>s` due to its process-wide effect.'
30
+ MESSAGE = 'Avoid using `%<module>s%<dot>s%<method>s` due to its process-wide effect.'
16
31
  RESTRICT_ON_SEND = %i[chdir cd].freeze
17
32
 
18
33
  # @!method chdir?(node)
19
34
  def_node_matcher :chdir?, <<~MATCHER
20
35
  {
21
- (send (const {nil? cbase} {:Dir :FileUtils}) :chdir ...)
22
- (send (const {nil? cbase} :FileUtils) :cd ...)
36
+ (call (const {nil? cbase} {:Dir :FileUtils}) :chdir ...)
37
+ (call (const {nil? cbase} :FileUtils) :cd ...)
23
38
  }
24
39
  MATCHER
25
40
 
26
41
  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)
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
31
52
  )
32
- end
53
+ )
54
+ end
55
+ alias on_csend on_send
56
+
57
+ private
58
+
59
+ def allow_call_with_block?
60
+ !!cop_config['AllowCallWithBlock']
33
61
  end
34
62
  end
35
63
  end
@@ -198,7 +198,7 @@ module RuboCop
198
198
  end
199
199
 
200
200
  def range_type?(node)
201
- node.erange_type? || node.irange_type?
201
+ node.type?(:range)
202
202
  end
203
203
 
204
204
  def correct_splat_expansion(corrector, expr, splat_value)
@@ -245,7 +245,7 @@ module RuboCop
245
245
 
246
246
  # @!method range_enclosed_in_parentheses?(node)
247
247
  def_node_matcher :range_enclosed_in_parentheses?, <<~PATTERN
248
- (begin ({irange erange} _ _))
248
+ (begin (range _ _))
249
249
  PATTERN
250
250
  end
251
251
  end
@@ -16,12 +16,13 @@ module RuboCop
16
16
 
17
17
  # @!method new_thread?(node)
18
18
  def_node_matcher :new_thread?, <<~MATCHER
19
- (send (const {nil? cbase} :Thread) {:new :fork :start} ...)
19
+ (call (const {nil? cbase} :Thread) {:new :fork :start} ...)
20
20
  MATCHER
21
21
 
22
22
  def on_send(node)
23
23
  new_thread?(node) { add_offense(node) }
24
24
  end
25
+ alias on_csend on_send
25
26
  end
26
27
  end
27
28
  end
@@ -88,11 +88,12 @@ module RuboCop
88
88
  def on_send(node)
89
89
  argument = node.first_argument
90
90
 
91
- return unless argument&.sym_type? || argument&.str_type?
91
+ return unless argument&.type?(:sym, :str)
92
92
  return if allowed_identifier?(argument.value)
93
93
 
94
94
  add_offense node
95
95
  end
96
+ alias on_csend on_send
96
97
 
97
98
  private
98
99
 
@@ -0,0 +1,38 @@
1
+ # frozen_string_literal: true
2
+
3
+ require 'lint_roller'
4
+
5
+ module RuboCop
6
+ module ThreadSafety
7
+ # A plugin that integrates RuboCop ThreadSafety with RuboCop's plugin system.
8
+ class Plugin < LintRoller::Plugin
9
+ # :nocov:
10
+ def about
11
+ LintRoller::About.new(
12
+ name: 'rubocop-thread_safety',
13
+ version: Version::STRING,
14
+ homepage: 'https://github.com/rubocop/rubocop-thread_safety',
15
+ description: 'Thread-safety checks via static analysis.'
16
+ )
17
+ end
18
+ # :nocov:
19
+
20
+ def supported?(context)
21
+ context.engine == :rubocop
22
+ end
23
+
24
+ def rules(_context)
25
+ project_root = Pathname.new(__dir__).join('../../..')
26
+
27
+ obsoletion = project_root.join('config', 'obsoletion.yml')
28
+ ConfigObsoletion.files << obsoletion
29
+
30
+ LintRoller::Rules.new(
31
+ type: :path,
32
+ config_format: :rubocop,
33
+ value: project_root.join('config/default.yml')
34
+ )
35
+ end
36
+ end
37
+ end
38
+ end
@@ -2,6 +2,6 @@
2
2
 
3
3
  module RuboCop
4
4
  module ThreadSafety
5
- VERSION = '0.6.0'
5
+ VERSION = '0.7.1'
6
6
  end
7
7
  end
@@ -3,12 +3,5 @@
3
3
  module RuboCop
4
4
  # RuboCop::ThreadSafety detects some potential thread safety issues.
5
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
-
12
- ::RuboCop::ConfigObsoletion.files << PROJECT_ROOT.join('config', 'obsoletion.yml')
13
6
  end
14
7
  end
@@ -4,9 +4,7 @@ require 'rubocop'
4
4
 
5
5
  require 'rubocop/thread_safety'
6
6
  require 'rubocop/thread_safety/version'
7
- require 'rubocop/thread_safety/inject'
8
-
9
- RuboCop::ThreadSafety::Inject.defaults!
7
+ require 'rubocop/thread_safety/plugin'
10
8
 
11
9
  require 'rubocop/cop/mixin/operation_with_threadsafe_result'
12
10
 
metadata CHANGED
@@ -1,29 +1,48 @@
1
1
  --- !ruby/object:Gem::Specification
2
2
  name: rubocop-thread_safety
3
3
  version: !ruby/object:Gem::Version
4
- version: 0.6.0
4
+ version: 0.7.1
5
5
  platform: ruby
6
6
  authors:
7
7
  - Michael Gee
8
- autorequire:
9
8
  bindir: exe
10
9
  cert_chain: []
11
- date: 2024-11-11 00:00:00.000000000 Z
10
+ date: 2025-03-12 00:00:00.000000000 Z
12
11
  dependencies:
12
+ - !ruby/object:Gem::Dependency
13
+ name: lint_roller
14
+ requirement: !ruby/object:Gem::Requirement
15
+ requirements:
16
+ - - "~>"
17
+ - !ruby/object:Gem::Version
18
+ version: '1.1'
19
+ type: :runtime
20
+ prerelease: false
21
+ version_requirements: !ruby/object:Gem::Requirement
22
+ requirements:
23
+ - - "~>"
24
+ - !ruby/object:Gem::Version
25
+ version: '1.1'
13
26
  - !ruby/object:Gem::Dependency
14
27
  name: rubocop
15
28
  requirement: !ruby/object:Gem::Requirement
16
29
  requirements:
30
+ - - "~>"
31
+ - !ruby/object:Gem::Version
32
+ version: '1.72'
17
33
  - - ">="
18
34
  - !ruby/object:Gem::Version
19
- version: 1.48.1
35
+ version: 1.72.1
20
36
  type: :runtime
21
37
  prerelease: false
22
38
  version_requirements: !ruby/object:Gem::Requirement
23
39
  requirements:
40
+ - - "~>"
41
+ - !ruby/object:Gem::Version
42
+ version: '1.72'
24
43
  - - ">="
25
44
  - !ruby/object:Gem::Version
26
- version: 1.48.1
45
+ version: 1.72.1
27
46
  description: |2
28
47
  Thread-safety checks via static analysis.
29
48
  A plugin for the RuboCop code style enforcing & linting tool.
@@ -33,26 +52,11 @@ executables: []
33
52
  extensions: []
34
53
  extra_rdoc_files: []
35
54
  files:
36
- - ".github/dependabot.yml"
37
- - ".github/workflows/ci.yml"
38
- - ".github/workflows/lint.yml"
39
- - ".gitignore"
40
- - ".rspec"
41
- - ".rubocop.yml"
42
- - Appraisals
43
55
  - CHANGELOG.md
44
- - Gemfile
45
56
  - LICENSE.txt
46
57
  - README.md
47
- - Rakefile
48
- - bin/console
49
- - bin/setup
50
58
  - config/default.yml
51
59
  - config/obsoletion.yml
52
- - docs/modules/ROOT/pages/cops.adoc
53
- - docs/modules/ROOT/pages/cops_threadsafety.adoc
54
- - gemfiles/rubocop_1.48.gemfile
55
- - gemfiles/rubocop_1.66.gemfile
56
60
  - lib/rubocop-thread_safety.rb
57
61
  - lib/rubocop/cop/mixin/operation_with_threadsafe_result.rb
58
62
  - lib/rubocop/cop/thread_safety/class_and_module_attributes.rb
@@ -62,10 +66,8 @@ files:
62
66
  - lib/rubocop/cop/thread_safety/new_thread.rb
63
67
  - lib/rubocop/cop/thread_safety/rack_middleware_instance_variable.rb
64
68
  - lib/rubocop/thread_safety.rb
65
- - lib/rubocop/thread_safety/inject.rb
69
+ - lib/rubocop/thread_safety/plugin.rb
66
70
  - lib/rubocop/thread_safety/version.rb
67
- - rubocop-thread_safety.gemspec
68
- - tasks/cops_documentation.rake
69
71
  homepage: https://github.com/rubocop/rubocop-thread_safety
70
72
  licenses:
71
73
  - MIT
@@ -74,7 +76,7 @@ metadata:
74
76
  source_code_uri: https://github.com/rubocop/rubocop-thread_safety
75
77
  bug_tracker_uri: https://github.com/rubocop/rubocop-thread_safety/issues
76
78
  rubygems_mfa_required: 'true'
77
- post_install_message:
79
+ default_lint_roller_plugin: RuboCop::ThreadSafety::Plugin
78
80
  rdoc_options: []
79
81
  require_paths:
80
82
  - lib
@@ -89,8 +91,7 @@ required_rubygems_version: !ruby/object:Gem::Requirement
89
91
  - !ruby/object:Gem::Version
90
92
  version: '0'
91
93
  requirements: []
92
- rubygems_version: 3.5.11
93
- signing_key:
94
+ rubygems_version: 3.6.3
94
95
  specification_version: 4
95
96
  summary: Thread-safety checks via static analysis
96
97
  test_files: []
@@ -1,6 +0,0 @@
1
- version: 2
2
- updates:
3
- - package-ecosystem: 'github-actions'
4
- directory: '/'
5
- schedule:
6
- interval: 'weekly'
@@ -1,55 +0,0 @@
1
- name: CI
2
-
3
- on:
4
- push:
5
- branches:
6
- - master
7
- pull_request:
8
-
9
- jobs:
10
- confirm_documentation:
11
- runs-on: ubuntu-latest
12
- name: Confirm documentation
13
- steps:
14
- - uses: actions/checkout@v4
15
- - uses: ruby/setup-ruby@v1
16
- with:
17
- ruby-version: "3.2"
18
- bundler-cache: true
19
- - run: bundle exec rake documentation_syntax_check confirm_documentation
20
-
21
- test:
22
- runs-on: ubuntu-latest
23
- strategy:
24
- fail-fast: false
25
- matrix:
26
- ruby: ["2.7", "3.0", "3.1", "3.2", "3.3", ruby-head, jruby-9.4]
27
- rubocop_version: ["1.48", "1.66"]
28
- env:
29
- BUNDLE_GEMFILE: "gemfiles/rubocop_${{ matrix.rubocop_version }}.gemfile"
30
- steps:
31
- - uses: actions/checkout@v4
32
- - name: Set up Ruby
33
- uses: ruby/setup-ruby@v1
34
- with:
35
- bundler-cache: true # 'bundle install' and cache gems
36
- ruby-version: ${{ matrix.ruby }}
37
- bundler: 2.3.26
38
- - name: Run tests
39
- run: bundle exec rspec
40
-
41
- test-prism:
42
- runs-on: ubuntu-latest
43
- env:
44
- BUNDLE_GEMFILE: "gemfiles/rubocop_1.66.gemfile"
45
- PARSER_ENGINE: parser_prism
46
- steps:
47
- - uses: actions/checkout@v4
48
- - name: Set up Ruby
49
- uses: ruby/setup-ruby@v1
50
- with:
51
- bundler-cache: true # 'bundle install' and cache gems
52
- ruby-version: 3.3
53
- bundler: 2.3.26
54
- - name: Run tests
55
- run: bundle exec rspec
@@ -1,23 +0,0 @@
1
- name: Lint
2
-
3
- on:
4
- push:
5
- branches:
6
- - master
7
- pull_request:
8
-
9
- jobs:
10
- lint:
11
-
12
- runs-on: ubuntu-latest
13
- name: Rubocop
14
-
15
- steps:
16
- - uses: actions/checkout@v4
17
- - name: Set up Ruby
18
- uses: ruby/setup-ruby@v1
19
- with:
20
- bundler-cache: true # 'bundle install' and cache gems
21
- ruby-version: "2.7"
22
- - name: Run Rubocop
23
- run: bundle exec rubocop
data/.gitignore DELETED
@@ -1,10 +0,0 @@
1
- /.bundle/
2
- /.yardoc
3
- /Gemfile.lock
4
- /gemfiles/*.lock
5
- /_yardoc/
6
- /coverage/
7
- /doc/
8
- /pkg/
9
- /spec/reports/
10
- /tmp/
data/.rspec DELETED
@@ -1,3 +0,0 @@
1
- --color
2
- --require spec_helper
3
- --warnings
data/.rubocop.yml DELETED
@@ -1,81 +0,0 @@
1
- require:
2
- - rubocop/cop/internal_affairs
3
- - rubocop-rake
4
- - rubocop-rspec
5
-
6
- AllCops:
7
- DisplayCopNames: true
8
- TargetRubyVersion: 2.7
9
- NewCops: enable
10
-
11
- Lint/RaiseException:
12
- Enabled: true
13
-
14
- Lint/StructNewOverride:
15
- Enabled: true
16
-
17
- Metrics/BlockLength:
18
- Exclude:
19
- - "spec/**/*"
20
-
21
- Metrics/ClassLength:
22
- Enabled: false
23
-
24
- Metrics/MethodLength:
25
- Max: 14
26
-
27
- Naming/FileName:
28
- Exclude:
29
- - lib/rubocop-thread_safety.rb
30
- - rubocop-thread_safety.gemspec
31
-
32
- # Enable more cops that are disabled by default:
33
-
34
- Style/AutoResourceCleanup:
35
- Enabled: true
36
-
37
- Style/CollectionMethods:
38
- Enabled: true
39
-
40
- Style/FormatStringToken:
41
- Exclude:
42
- - spec/**/*
43
-
44
- Style/HashEachMethods:
45
- Enabled: true
46
-
47
- Style/HashTransformKeys:
48
- Enabled: false
49
-
50
- Style/HashTransformValues:
51
- Enabled: false
52
-
53
- Style/MethodCalledOnDoEndBlock:
54
- Enabled: true
55
- Exclude:
56
- - spec/**/*
57
-
58
- Style/MissingElse:
59
- Enabled: true
60
- EnforcedStyle: case
61
-
62
- Style/OptionHash:
63
- Enabled: true
64
-
65
- Style/Send:
66
- Enabled: true
67
-
68
- Style/StringMethods:
69
- Enabled: true
70
-
71
- Style/SymbolArray:
72
- Enabled: true
73
-
74
- RSpec/ExampleLength:
75
- Max: 11
76
- Exclude:
77
- - spec/rubocop/cop/thread_safety/rack_middleware_instance_variable_spec.rb
78
-
79
- RSpec/ContextWording:
80
- Exclude:
81
- - spec/shared_contexts.rb
data/Appraisals DELETED
@@ -1,21 +0,0 @@
1
- # frozen_string_literal: true
2
-
3
- customize_gemfiles do
4
- {
5
- single_quotes: true,
6
- heading: <<~HEADING.strip
7
- frozen_string_literal: true
8
-
9
- This file was generated by Appraisal
10
- HEADING
11
- }
12
- end
13
-
14
- appraise 'rubocop-1.48' do
15
- gem 'base64', '~> 0.1.1'
16
- gem 'rubocop', '~> 1.48.0'
17
- end
18
-
19
- appraise 'rubocop-1.66' do
20
- gem 'rubocop', '~> 1.66.0'
21
- end
data/Gemfile DELETED
@@ -1,17 +0,0 @@
1
- # frozen_string_literal: true
2
-
3
- source 'https://rubygems.org'
4
-
5
- gemspec
6
-
7
- gem 'appraisal'
8
- gem 'bundler', '>= 1.10', '< 3'
9
- gem 'prism', '~> 1.2.0'
10
- gem 'pry' unless ENV['CI']
11
- gem 'rake', '>= 10.0'
12
- gem 'rspec', '~> 3.0'
13
- gem 'rubocop', github: 'rubocop/rubocop'
14
- gem 'rubocop-rake', '~> 0.6.0'
15
- gem 'rubocop-rspec'
16
- gem 'simplecov'
17
- gem 'yard'
data/Rakefile DELETED
@@ -1,22 +0,0 @@
1
- # frozen_string_literal: true
2
-
3
- require 'bundler/gem_tasks'
4
- require 'open3'
5
- require 'rspec/core/rake_task'
6
-
7
- Dir['tasks/**/*.rake'].each { |t| load t }
8
-
9
- RSpec::Core::RakeTask.new(:spec)
10
-
11
- desc 'Confirm documentation is up to date'
12
- task confirm_documentation: :generate_cops_documentation do
13
- _, _, _, process =
14
- Open3.popen3('git diff --exit-code docs/')
15
-
16
- unless process.value.success?
17
- abort 'Please run `rake generate_cops_documentation` ' \
18
- 'and add docs/ to the commit.'
19
- end
20
- end
21
-
22
- task default: :spec
data/bin/console DELETED
@@ -1,15 +0,0 @@
1
- #!/usr/bin/env ruby
2
-
3
- # frozen_string_literal: true
4
-
5
- require 'bundler/setup'
6
- require 'rubocop-thread_safety'
7
-
8
- # You can add fixtures and/or initialization code here to make experimenting
9
- # with your gem easier. You can also use a different console, if you like.
10
-
11
- require 'pry'
12
- Pry.start
13
-
14
- # require "irb"
15
- # IRB.start
data/bin/setup DELETED
@@ -1,6 +0,0 @@
1
- #!/usr/bin/env bash
2
- set -euo pipefail
3
- IFS=$'\n\t'
4
- set -vx
5
-
6
- bundle install
@@ -1,8 +0,0 @@
1
- === Department xref:cops_threadsafety.adoc[ThreadSafety]
2
-
3
- * xref:cops_threadsafety.adoc#threadsafetyclassandmoduleattributes[ThreadSafety/ClassAndModuleAttributes]
4
- * xref:cops_threadsafety.adoc#threadsafetyclassinstancevariable[ThreadSafety/ClassInstanceVariable]
5
- * xref:cops_threadsafety.adoc#threadsafetymutableclassinstancevariable[ThreadSafety/MutableClassInstanceVariable]
6
- * xref:cops_threadsafety.adoc#threadsafetynewthread[ThreadSafety/NewThread]
7
- * xref:cops_threadsafety.adoc#threadsafetydirchdir[ThreadSafety/DirChdir]
8
- * xref:cops_threadsafety.adoc#threadsafetyrackmiddlewareinstancevariable[ThreadSafety/RackMiddlewareInstanceVariable]
@@ -1,361 +0,0 @@
1
- ////
2
- Do NOT edit this file by hand directly, as it is automatically generated.
3
-
4
- Please make any necessary changes to the cop documentation within the source files themselves.
5
- ////
6
-
7
- = ThreadSafety
8
-
9
- [#threadsafetyclassandmoduleattributes]
10
- == ThreadSafety/ClassAndModuleAttributes
11
-
12
- |===
13
- | Enabled by default | Safe | Supports autocorrection | Version Added | Version Changed
14
-
15
- | Enabled
16
- | Yes
17
- | No
18
- | -
19
- | -
20
- |===
21
-
22
- Avoid mutating class and module attributes.
23
-
24
- They are implemented by class variables, which are not thread-safe.
25
-
26
- [#examples-threadsafetyclassandmoduleattributes]
27
- === Examples
28
-
29
- [source,ruby]
30
- ----
31
- # bad
32
- class User
33
- cattr_accessor :current_user
34
- end
35
- ----
36
-
37
- [#configurable-attributes-threadsafetyclassandmoduleattributes]
38
- === Configurable attributes
39
-
40
- |===
41
- | Name | Default value | Configurable values
42
-
43
- | ActiveSupportClassAttributeAllowed
44
- | `false`
45
- | Boolean
46
- |===
47
-
48
- [#threadsafetyclassinstancevariable]
49
- == ThreadSafety/ClassInstanceVariable
50
-
51
- |===
52
- | Enabled by default | Safe | Supports autocorrection | Version Added | Version Changed
53
-
54
- | Enabled
55
- | Yes
56
- | No
57
- | -
58
- | -
59
- |===
60
-
61
- Avoid class instance variables.
62
-
63
- [#examples-threadsafetyclassinstancevariable]
64
- === Examples
65
-
66
- [source,ruby]
67
- ----
68
- # bad
69
- class User
70
- def self.notify(info)
71
- @info = validate(info)
72
- Notifier.new(@info).deliver
73
- end
74
- end
75
-
76
- class Model
77
- class << self
78
- def table_name(name)
79
- @table_name = name
80
- end
81
- end
82
- end
83
-
84
- class Host
85
- %i[uri port].each do |key|
86
- define_singleton_method("#{key}=") do |value|
87
- instance_variable_set("@#{key}", value)
88
- end
89
- end
90
- end
91
-
92
- module Example
93
- module ClassMethods
94
- def test(params)
95
- @params = params
96
- end
97
- end
98
- end
99
-
100
- module Example
101
- class_methods do
102
- def test(params)
103
- @params = params
104
- end
105
- end
106
- end
107
-
108
- module Example
109
- module_function
110
-
111
- def test(params)
112
- @params = params
113
- end
114
- end
115
-
116
- module Example
117
- def test(params)
118
- @params = params
119
- end
120
-
121
- module_function :test
122
- end
123
- ----
124
-
125
- [#threadsafetydirchdir]
126
- == ThreadSafety/DirChdir
127
-
128
- |===
129
- | Enabled by default | Safe | Supports autocorrection | Version Added | Version Changed
130
-
131
- | Enabled
132
- | Yes
133
- | No
134
- | -
135
- | -
136
- |===
137
-
138
- Avoid using `Dir.chdir` due to its process-wide effect.
139
-
140
- [#examples-threadsafetydirchdir]
141
- === Examples
142
-
143
- [source,ruby]
144
- ----
145
- # bad
146
- Dir.chdir("/var/run")
147
-
148
- # bad
149
- FileUtils.chdir("/var/run")
150
- ----
151
-
152
- [#threadsafetymutableclassinstancevariable]
153
- == ThreadSafety/MutableClassInstanceVariable
154
-
155
- |===
156
- | Enabled by default | Safe | Supports autocorrection | Version Added | Version Changed
157
-
158
- | Enabled
159
- | Yes
160
- | Always (Unsafe)
161
- | -
162
- | -
163
- |===
164
-
165
- Checks whether some class instance variable isn't a
166
- mutable literal (e.g. array or hash).
167
-
168
- It is based on Style/MutableConstant from RuboCop.
169
- See https://github.com/rubocop/rubocop/blob/master/lib/rubocop/cop/style/mutable_constant.rb
170
-
171
- Class instance variables are a risk to threaded code as they are shared
172
- between threads. A mutable object such as an array or hash may be
173
- updated via an attr_reader so would not be detected by the
174
- ThreadSafety/ClassAndModuleAttributes cop.
175
-
176
- Strict mode can be used to freeze all class instance variables, rather
177
- than just literals.
178
- Strict mode is considered an experimental feature. It has not been
179
- updated with an exhaustive list of all methods that will produce frozen
180
- objects so there is a decent chance of getting some false positives.
181
- Luckily, there is no harm in freezing an already frozen object.
182
-
183
- [#examples-threadsafetymutableclassinstancevariable]
184
- === Examples
185
-
186
- [#enforcedstyle_-literals-_default_-threadsafetymutableclassinstancevariable]
187
- ==== EnforcedStyle: literals (default)
188
-
189
- [source,ruby]
190
- ----
191
- # bad
192
- class Model
193
- @list = [1, 2, 3]
194
- end
195
-
196
- # good
197
- class Model
198
- @list = [1, 2, 3].freeze
199
- end
200
-
201
- # good
202
- class Model
203
- @var = <<~TESTING.freeze
204
- This is a heredoc
205
- TESTING
206
- end
207
-
208
- # good
209
- class Model
210
- @var = Something.new
211
- end
212
- ----
213
-
214
- [#enforcedstyle_-strict-threadsafetymutableclassinstancevariable]
215
- ==== EnforcedStyle: strict
216
-
217
- [source,ruby]
218
- ----
219
- # bad
220
- class Model
221
- @var = Something.new
222
- end
223
-
224
- # bad
225
- class Model
226
- @var = Struct.new do
227
- def foo
228
- puts 1
229
- end
230
- end
231
- end
232
-
233
- # good
234
- class Model
235
- @var = Something.new.freeze
236
- end
237
-
238
- # good
239
- class Model
240
- @var = Struct.new do
241
- def foo
242
- puts 1
243
- end
244
- end.freeze
245
- end
246
- ----
247
-
248
- [#configurable-attributes-threadsafetymutableclassinstancevariable]
249
- === Configurable attributes
250
-
251
- |===
252
- | Name | Default value | Configurable values
253
-
254
- | EnforcedStyle
255
- | `literals`
256
- | `literals`, `strict`
257
- |===
258
-
259
- [#threadsafetynewthread]
260
- == ThreadSafety/NewThread
261
-
262
- |===
263
- | Enabled by default | Safe | Supports autocorrection | Version Added | Version Changed
264
-
265
- | Enabled
266
- | Yes
267
- | No
268
- | -
269
- | -
270
- |===
271
-
272
- Avoid starting new threads.
273
-
274
- Let a framework like Sidekiq handle the threads.
275
-
276
- [#examples-threadsafetynewthread]
277
- === Examples
278
-
279
- [source,ruby]
280
- ----
281
- # bad
282
- Thread.new { do_work }
283
- ----
284
-
285
- [#threadsafetyrackmiddlewareinstancevariable]
286
- == ThreadSafety/RackMiddlewareInstanceVariable
287
-
288
- |===
289
- | Enabled by default | Safe | Supports autocorrection | Version Added | Version Changed
290
-
291
- | Enabled
292
- | Yes
293
- | No
294
- | -
295
- | -
296
- |===
297
-
298
- Avoid instance variables in rack middleware.
299
-
300
- Middlewares are initialized once, meaning any instance variables are shared between executor threads.
301
- To avoid potential race conditions, it's recommended to design middlewares to be stateless
302
- or to implement proper synchronization mechanisms.
303
-
304
- [#examples-threadsafetyrackmiddlewareinstancevariable]
305
- === Examples
306
-
307
- [source,ruby]
308
- ----
309
- # bad
310
- class CounterMiddleware
311
- def initialize(app)
312
- @app = app
313
- @counter = 0
314
- end
315
-
316
- def call(env)
317
- app.call(env)
318
- ensure
319
- @counter += 1
320
- end
321
- end
322
-
323
- # good
324
- class CounterMiddleware
325
- def initialize(app)
326
- @app = app
327
- @counter = Concurrent::AtomicReference.new(0)
328
- end
329
-
330
- def call(env)
331
- app.call(env)
332
- ensure
333
- @counter.update { |ref| ref + 1 }
334
- end
335
- end
336
-
337
- class IdentityMiddleware
338
- def initialize(app)
339
- @app = app
340
- end
341
-
342
- def call(env)
343
- app.call(env)
344
- end
345
- end
346
- ----
347
-
348
- [#configurable-attributes-threadsafetyrackmiddlewareinstancevariable]
349
- === Configurable attributes
350
-
351
- |===
352
- | Name | Default value | Configurable values
353
-
354
- | Include
355
- | `+app/middleware/**/*.rb+`, `+lib/middleware/**/*.rb+`, `+app/middlewares/**/*.rb+`, `+lib/middlewares/**/*.rb+`
356
- | Array
357
-
358
- | AllowedIdentifiers
359
- | `[]`
360
- | Array
361
- |===
@@ -1,20 +0,0 @@
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 'base64', '~> 0.1.1'
9
- gem 'bundler', '>= 1.10', '< 3'
10
- gem 'prism', '~> 1.2.0'
11
- gem 'pry'
12
- gem 'rake', '>= 10.0'
13
- gem 'rspec', '~> 3.0'
14
- gem 'rubocop', '~> 1.48.0'
15
- gem 'rubocop-rake', '~> 0.6.0'
16
- gem 'rubocop-rspec'
17
- gem 'simplecov'
18
- gem 'yard'
19
-
20
- gemspec path: '../'
@@ -1,19 +0,0 @@
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: '../'
@@ -1,20 +0,0 @@
1
- # frozen_string_literal: true
2
-
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
- 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
@@ -1,39 +0,0 @@
1
- # frozen_string_literal: true
2
-
3
- lib = File.expand_path('lib', __dir__)
4
- $LOAD_PATH.unshift(lib) unless $LOAD_PATH.include?(lib)
5
- require 'rubocop/thread_safety/version'
6
-
7
- Gem::Specification.new do |spec|
8
- spec.name = 'rubocop-thread_safety'
9
- spec.version = RuboCop::ThreadSafety::VERSION
10
- spec.authors = ['Michael Gee']
11
- spec.email = ['michaelpgee@gmail.com']
12
-
13
- spec.summary = 'Thread-safety checks via static analysis'
14
- spec.description = <<-DESCRIPTION
15
- Thread-safety checks via static analysis.
16
- A plugin for the RuboCop code style enforcing & linting tool.
17
- DESCRIPTION
18
- spec.homepage = 'https://github.com/rubocop/rubocop-thread_safety'
19
- spec.licenses = ['MIT']
20
-
21
- spec.files = `git ls-files -z`.split("\x0").reject do |f|
22
- f.match(%r{^(test|spec|features)/})
23
- end
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
-
32
- spec.bindir = 'exe'
33
- spec.executables = spec.files.grep(%r{^exe/}) { |f| File.basename(f) }
34
- spec.require_paths = ['lib']
35
-
36
- spec.required_ruby_version = '>= 2.7.0'
37
-
38
- spec.add_dependency 'rubocop', '>= 1.48.1'
39
- end
@@ -1,46 +0,0 @@
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