forked from rubocop/rubocop
Commit
This commit does not belong to any branch on this repository, and may belong to a fork outside of the repository.
[Fix rubocop#9061] Add new `Lint/IncompatibleIoSelectWithFiberSchedul…
…er` cop Fixes rubocop#9061. This PR adds new `Lint/IncompatibleIoSelectWithFiberScheduler` cop. It checks for `IO.select` that is incompatible with Fiber Scheduler since Ruby 3.0. ```ruby # bad IO.select([io], [], [], timeout) # good io.wait_readable(timeout) # bad IO.select([], [io], [], timeout) # good io.wait_writable(timeout) ``` Ref: `Fiber Scheduler` section of https://www.ruby-lang.org/en/news/2020/12/25/ruby-3-0-0-released/ This PR will make it possible to detect proven cases with redis/redis-rb#960.
- Loading branch information
Showing
5 changed files
with
206 additions
and
1 deletion.
There are no files selected for viewing
1 change: 1 addition & 0 deletions
1
changelog/new_add_new_incompatible_io_select_with_fiber_scheduler_cop.md
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1 @@ | ||
* [#9061](https://github.com/rubocop/rubocop/issues/9061): Add new `Lint/IncompatibleIoSelectWithFiberScheduler` cop. ([@koic][]) |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
67 changes: 67 additions & 0 deletions
67
lib/rubocop/cop/lint/incompatible_io_select_with_fiber_scheduler.rb
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,67 @@ | ||
# frozen_string_literal: true | ||
|
||
module RuboCop | ||
module Cop | ||
module Lint | ||
# | ||
# This cop checks for `IO.select` that is incompatible with Fiber Scheduler since Ruby 3.0. | ||
# | ||
# @example | ||
# | ||
# # bad | ||
# IO.select([io], [], [], timeout) | ||
# | ||
# # good | ||
# io.wait_readable(timeout) | ||
# | ||
# # bad | ||
# IO.select([], [io], [], timeout) | ||
# | ||
# # good | ||
# io.wait_writable(timeout) | ||
# | ||
class IncompatibleIoSelectWithFiberScheduler < Base | ||
extend AutoCorrector | ||
|
||
MSG = 'Use `%<preferred>s` instead of `%<current>s`.' | ||
RESTRICT_ON_SEND = %i[select].freeze | ||
|
||
# @!method io_select(node) | ||
def_node_matcher :io_select, <<~PATTERN | ||
(send | ||
(const {nil? cbase} :IO) :select $_ $_ {(array) nil} $...) | ||
PATTERN | ||
|
||
def on_send(node) | ||
return unless (read, write, timeout = io_select(node)) | ||
return unless scheduler_compatible?(read, write) || scheduler_compatible?(write, read) | ||
|
||
preferred = preferred_method(read, write, timeout) | ||
message = format(MSG, preferred: preferred, current: node.source) | ||
|
||
add_offense(node, message: message) do |corrector| | ||
corrector.replace(node, preferred) | ||
end | ||
end | ||
|
||
private | ||
|
||
def scheduler_compatible?(io1, io2) | ||
return false unless io1.array_type? && io1.values.size == 1 | ||
|
||
io2.array_type? ? io2.values.empty? : io2.nil_type? | ||
end | ||
|
||
def preferred_method(read, write, timeout) | ||
timeout_argument = timeout.empty? ? '' : "(#{timeout[0].source})" | ||
|
||
if read.array_type? && read.values[0] | ||
"#{read.values[0].source}.wait_readable#{timeout_argument}" | ||
else | ||
"#{write.values[0].source}.wait_writable#{timeout_argument}" | ||
end | ||
end | ||
end | ||
end | ||
end | ||
end |
131 changes: 131 additions & 0 deletions
131
spec/rubocop/cop/lint/incompatible_io_select_with_fiber_scheduler_spec.rb
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,131 @@ | ||
# frozen_string_literal: true | ||
|
||
RSpec.describe RuboCop::Cop::Lint::IncompatibleIoSelectWithFiberScheduler, :config do | ||
it 'registers and corrects an offense when using `IO.select` with single read argument' do | ||
expect_offense(<<~RUBY) | ||
IO.select([io], [], []) | ||
^^^^^^^^^^^^^^^^^^^^^^^ Use `io.wait_readable` instead of `IO.select([io], [], [])`. | ||
RUBY | ||
|
||
expect_correction(<<~RUBY) | ||
io.wait_readable | ||
RUBY | ||
end | ||
|
||
it 'registers and corrects an offense when using `IO.select` with single read and timeout arguments' do | ||
expect_offense(<<~RUBY) | ||
IO.select([io], [], [], timeout) | ||
^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ Use `io.wait_readable(timeout)` instead of `IO.select([io], [], [], timeout)`. | ||
RUBY | ||
|
||
expect_correction(<<~RUBY) | ||
io.wait_readable(timeout) | ||
RUBY | ||
end | ||
|
||
it 'registers and corrects an offense when using `::IO.select` with single read argument' do | ||
expect_offense(<<~RUBY) | ||
::IO.select([io], [], []) | ||
^^^^^^^^^^^^^^^^^^^^^^^^^ Use `io.wait_readable` instead of `::IO.select([io], [], [])`. | ||
RUBY | ||
|
||
expect_correction(<<~RUBY) | ||
io.wait_readable | ||
RUBY | ||
end | ||
|
||
it 'registers and corrects an offense when using `::IO.select` with single read and timeout arguments' do | ||
expect_offense(<<~RUBY) | ||
::IO.select([io], [], [], timeout) | ||
^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ Use `io.wait_readable(timeout)` instead of `::IO.select([io], [], [], timeout)`. | ||
RUBY | ||
|
||
expect_correction(<<~RUBY) | ||
io.wait_readable(timeout) | ||
RUBY | ||
end | ||
|
||
it 'registers and corrects an offense when using `IO.select` with single read, `nil`, and timeout arguments' do | ||
expect_offense(<<~RUBY) | ||
IO.select([io], nil, nil, timeout) | ||
^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ Use `io.wait_readable(timeout)` instead of `IO.select([io], nil, nil, timeout)`. | ||
RUBY | ||
|
||
expect_correction(<<~RUBY) | ||
io.wait_readable(timeout) | ||
RUBY | ||
end | ||
|
||
it 'registers and corrects an offense when using `IO.select` with single write, `nil`, and timeout arguments' do | ||
expect_offense(<<~RUBY) | ||
IO.select(nil, [io], nil, timeout) | ||
^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ Use `io.wait_writable(timeout)` instead of `IO.select(nil, [io], nil, timeout)`. | ||
RUBY | ||
|
||
expect_correction(<<~RUBY) | ||
io.wait_writable(timeout) | ||
RUBY | ||
end | ||
|
||
it 'registers and corrects an offense when using `IO.select` with single write argument' do | ||
expect_offense(<<~RUBY) | ||
IO.select([], [io], []) | ||
^^^^^^^^^^^^^^^^^^^^^^^ Use `io.wait_writable` instead of `IO.select([], [io], [])`. | ||
RUBY | ||
|
||
expect_correction(<<~RUBY) | ||
io.wait_writable | ||
RUBY | ||
end | ||
|
||
it 'registers and corrects an offense when using `IO.select` with single write and timeout arguments' do | ||
expect_offense(<<~RUBY) | ||
IO.select([], [io], [], timeout) | ||
^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ Use `io.wait_writable(timeout)` instead of `IO.select([], [io], [], timeout)`. | ||
RUBY | ||
|
||
expect_correction(<<~RUBY) | ||
io.wait_writable(timeout) | ||
RUBY | ||
end | ||
|
||
it 'registers and corrects an offense when using `IO.select` with single read as `self` and timeout arguments' do | ||
expect_offense(<<~RUBY) | ||
IO.select([self], nil, nil, timeout) | ||
^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ Use `self.wait_readable(timeout)` instead of `IO.select([self], nil, nil, timeout)`. | ||
RUBY | ||
|
||
expect_correction(<<~RUBY) | ||
self.wait_readable(timeout) | ||
RUBY | ||
end | ||
|
||
it 'registers and corrects an offense when using `IO.select` with single write as `self` and timeout arguments' do | ||
expect_offense(<<~RUBY) | ||
IO.select(nil, [self], nil, timeout) | ||
^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ Use `self.wait_writable(timeout)` instead of `IO.select(nil, [self], nil, timeout)`. | ||
RUBY | ||
|
||
expect_correction(<<~RUBY) | ||
self.wait_writable(timeout) | ||
RUBY | ||
end | ||
|
||
it 'does not register an offense when using `IO.select` with multiple read arguments' do | ||
expect_no_offenses(<<~RUBY) | ||
IO.select([foo, bar], [], []) | ||
RUBY | ||
end | ||
|
||
it 'does not register an offense when using `IO.select` with multiple write arguments' do | ||
expect_no_offenses(<<~RUBY) | ||
IO.select([], [foo, bar], []) | ||
RUBY | ||
end | ||
|
||
it 'does not register an offense when using `IO.select` with read and write arguments' do | ||
expect_no_offenses(<<~RUBY) | ||
IO.select([rp], [wp], []) | ||
RUBY | ||
end | ||
end |