Skip to content

Commit

Permalink
Merge pull request #73 from viralpraxis/add-allow-call-with-block-opt…
Browse files Browse the repository at this point in the history
…ion-to-dir-chdir-cop

Add `AllowCallWithBlock` option to `ThreadSafety/DirChdir` cop
  • Loading branch information
viralpraxis authored Jan 12, 2025
2 parents 9f86764 + a2184ae commit 4176ff7
Show file tree
Hide file tree
Showing 5 changed files with 133 additions and 7 deletions.
4 changes: 4 additions & 0 deletions CHANGELOG.md
Original file line number Diff line number Diff line change
@@ -1,5 +1,9 @@
# Change log

## master

- [#73](https://github.com/rubocop/rubocop-thread_safety/pull/73): Add `AllowCallWithBlock` option to `ThreadSafety/DirChdir` cop. ([@viralpraxis](https://github.com/viralpraxis))

## 0.6.0

* [#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))
Expand Down
1 change: 1 addition & 0 deletions config/default.yml
Original file line number Diff line number Diff line change
Expand Up @@ -35,6 +35,7 @@ ThreadSafety/NewThread:
ThreadSafety/DirChdir:
Description: Avoid using `Dir.chdir` due to its process-wide effect.
Enabled: true
AllowCallWithBlock: false

ThreadSafety/RackMiddlewareInstanceVariable:
Description: Avoid instance variables in Rack middleware.
Expand Down
35 changes: 35 additions & 0 deletions docs/modules/ROOT/pages/cops_threadsafety.adoc
Original file line number Diff line number Diff line change
Expand Up @@ -136,6 +136,8 @@ end
|===
Avoid using `Dir.chdir` due to its process-wide effect.
If `AllowCallWithBlock` (disabled by default) option is enabled,
calling `Dir.chdir` with block will be allowed.
[#examples-threadsafetydirchdir]
=== Examples
Expand All @@ -149,6 +151,39 @@ Dir.chdir("/var/run")
FileUtils.chdir("/var/run")
----
[#allowcallwithblock_-false-_default_-threadsafetydirchdir]
==== AllowCallWithBlock: false (default)
[source,ruby]
----
# good
Dir.chdir("/var/run") do
puts Dir.pwd
end
----
[#allowcallwithblock_-true-threadsafetydirchdir]
==== AllowCallWithBlock: true
[source,ruby]
----
# bad
Dir.chdir("/var/run") do
puts Dir.pwd
end
----
[#configurable-attributes-threadsafetydirchdir]
=== Configurable attributes
|===
| Name | Default value | Configurable values
| AllowCallWithBlock
| `false`
| Boolean
|===
[#threadsafetymutableclassinstancevariable]
== ThreadSafety/MutableClassInstanceVariable
Expand Down
34 changes: 28 additions & 6 deletions lib/rubocop/cop/thread_safety/dir_chdir.rb
Original file line number Diff line number Diff line change
Expand Up @@ -4,13 +4,28 @@ module RuboCop
module Cop
module ThreadSafety
# Avoid using `Dir.chdir` due to its process-wide effect.
# If `AllowCallWithBlock` (disabled by default) option is enabled,
# calling `Dir.chdir` with block will be allowed.
#
# @example
# # bad
# Dir.chdir("/var/run")
#
# # bad
# FileUtils.chdir("/var/run")
#
# @example AllowCallWithBlock: false (default)
# # good
# Dir.chdir("/var/run") do
# puts Dir.pwd
# end
#
# @example AllowCallWithBlock: true
# # bad
# Dir.chdir("/var/run") do
# puts Dir.pwd
# end
#
class DirChdir < Base
MESSAGE = 'Avoid using `%<module>s.%<method>s` due to its process-wide effect.'
RESTRICT_ON_SEND = %i[chdir cd].freeze
Expand All @@ -24,12 +39,19 @@ class DirChdir < Base
MATCHER

def on_send(node)
chdir?(node) do
add_offense(
node,
message: format(MESSAGE, module: node.receiver.short_name, method: node.method_name)
)
end
return unless chdir?(node)
return if allow_call_with_block? && (node.block_argument? || node.parent&.block_type?)

add_offense(
node,
message: format(MESSAGE, module: node.receiver.short_name, method: node.method_name)
)
end

private

def allow_call_with_block?
!!cop_config['AllowCallWithBlock']
end
end
end
Expand Down
66 changes: 65 additions & 1 deletion spec/rubocop/cop/thread_safety/dir_chdir_spec.rb
Original file line number Diff line number Diff line change
Expand Up @@ -24,6 +24,33 @@
^^^^^^^^^^^^^^^^^^^^^^^ #{msg}
RUBY
end

it 'registers an offense with provided block' do
expect_offense(<<~RUBY)
Dir.chdir("/var/run") do
^^^^^^^^^^^^^^^^^^^^^ #{msg}
puts Dir.pwd
end
RUBY
end

it 'registers an offense with provided block with argument' do
expect_offense(<<~RUBY)
Dir.chdir("/var/run") do |dir|
^^^^^^^^^^^^^^^^^^^^^ #{msg}
puts dir
end
RUBY
end

it 'registers an offense with provided block argument' do
expect_offense(<<~RUBY)
def change_dir(&block)
Dir.chdir("/var/run", &block)
^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ #{msg}
end
RUBY
end
end

context 'with `FileUtils.chdir` method' do
Expand Down Expand Up @@ -54,9 +81,46 @@
end
end

context 'when received is not `Dir`' do
context 'when receiver is not `Dir`' do
it 'does not register an offense' do
expect_no_offenses 'chdir("/tmp")'
end
end

context 'with `AllowCallWithBlock` config set to `true`' do
let(:cop_config) do
{ 'Enabled' => true, 'AllowCallWithBlock' => true }
end

it 'registers an offense' do
expect_offense(<<~RUBY)
Dir.chdir("/var/run")
^^^^^^^^^^^^^^^^^^^^^ #{msg}
RUBY
end

it 'does not register an offense with provided block' do
expect_no_offenses(<<~RUBY)
Dir.chdir("/var/run") do
puts Dir.pwd
end
RUBY
end

it 'does not register an offense with provided block with argument' do
expect_no_offenses(<<~RUBY)
Dir.chdir("/var/run") do |dir|
puts dir
end
RUBY
end

it 'does not register an offense with provided block argument' do
expect_no_offenses(<<~RUBY)
def change_dir(&block)
Dir.chdir("/var/run", &block)
end
RUBY
end
end
end

0 comments on commit 4176ff7

Please sign in to comment.