Skip to content

Commit

Permalink
Support autocorrection even if reject is used on Performance/Count
Browse files Browse the repository at this point in the history
  • Loading branch information
r7kamura committed Oct 5, 2022
1 parent d5bb1fe commit 3554550
Show file tree
Hide file tree
Showing 3 changed files with 184 additions and 82 deletions.
Original file line number Diff line number Diff line change
@@ -0,0 +1 @@
* [#307](https://github.com/rubocop/rubocop-performance/pull/307): Support autocorrection even if `reject` is used on `Performance/Count`. ([@r7kamura][])
40 changes: 38 additions & 2 deletions lib/rubocop/cop/performance/count.rb
Original file line number Diff line number Diff line change
Expand Up @@ -79,12 +79,11 @@ def on_send(node)
def autocorrect(corrector, node, selector_node, selector)
selector_loc = selector_node.loc.selector

return if selector == :reject

range = source_starting_at(node) { |n| n.loc.dot.begin_pos }

corrector.remove(range)
corrector.replace(selector_loc, 'count')
negate_reject(corrector, node) if selector == :reject
end

def eligible_node?(node)
Expand All @@ -100,6 +99,43 @@ def source_starting_at(node)

range_between(begin_pos, node.source_range.end_pos)
end

def negate_reject(corrector, node)
if node.receiver.send_type?
negate_block_pass_reject(corrector, node)
else
negate_block_reject(corrector, node)
end
end

def negate_block_pass_reject(corrector, node)
corrector.replace(
node.receiver.loc.expression.with(begin_pos: node.receiver.loc.begin.begin_pos),
negate_block_pass_as_inline_block(node.receiver)
)
end

def negate_block_reject(corrector, node)
target =
if node.receiver.body.begin_type?
node.receiver.body.children.last
else
node.receiver.body
end
corrector.replace(target, negate_expression(target))
end

def negate_expression(node)
"!(#{node.source})"
end

def negate_block_pass_as_inline_block(node)
if node.last_argument.children.first.sym_type?
" { |element| !element.#{node.last_argument.children.first.value} }"
else
" { !#{node.last_argument.children.first.source}.call }"
end
end
end
end
end
Expand Down
225 changes: 145 additions & 80 deletions spec/rubocop/cop/performance/count_spec.rb
Original file line number Diff line number Diff line change
Expand Up @@ -177,101 +177,166 @@ def count(&block)
end
end

context 'autocorrect' do
context 'will correct' do
it 'select..size to count' do
expect_offense(<<~RUBY)
[1, 2].select { |e| e > 2 }.size
^^^^^^^^^^^^^^^^^^^^^^^^^ Use `count` instead of `select...size`.
RUBY

expect_correction(<<~RUBY)
[1, 2].count { |e| e > 2 }
RUBY
end
context 'with `select` and `size`' do
it 'registers an offense' do
expect_offense(<<~RUBY)
array.select { |e| e > 2 }.size
^^^^^^^^^^^^^^^^^^^^^^^^^ Use `count` instead of `select...size`.
RUBY

it 'select..count without a block to count' do
expect_offense(<<~RUBY)
[1, 2].select { |e| e > 2 }.count
^^^^^^^^^^^^^^^^^^^^^^^^^^ Use `count` instead of `select...count`.
RUBY
expect_correction(<<~RUBY)
array.count { |e| e > 2 }
RUBY
end
end

expect_correction(<<~RUBY)
[1, 2].count { |e| e > 2 }
RUBY
end
context 'with `select` and `count`' do
it 'registers an offense' do
expect_offense(<<~RUBY)
array.select { |e| e > 2 }.count
^^^^^^^^^^^^^^^^^^^^^^^^^^ Use `count` instead of `select...count`.
RUBY

it 'select..length to count' do
expect_offense(<<~RUBY)
[1, 2].select { |e| e > 2 }.length
^^^^^^^^^^^^^^^^^^^^^^^^^^^ Use `count` instead of `select...length`.
RUBY
expect_correction(<<~RUBY)
array.count { |e| e > 2 }
RUBY
end
end

expect_correction(<<~RUBY)
[1, 2].count { |e| e > 2 }
RUBY
end
context 'with `select` and `length`' do
it 'registers an offense' do
expect_offense(<<~RUBY)
array.select { |e| e > 2 }.length
^^^^^^^^^^^^^^^^^^^^^^^^^^^ Use `count` instead of `select...length`.
RUBY

it 'select...size when select has parameters' do
expect_offense(<<~RUBY)
Data = Struct.new(:value)
array = [Data.new(2), Data.new(3), Data.new(2)]
puts array.select(&:value).size
^^^^^^^^^^^^^^^^^^^^ Use `count` instead of `select...size`.
RUBY

expect_correction(<<~RUBY)
Data = Struct.new(:value)
array = [Data.new(2), Data.new(3), Data.new(2)]
puts array.count(&:value)
RUBY
end
expect_correction(<<~RUBY)
array.count { |e| e > 2 }
RUBY
end
end

describe 'will not correct' do
it 'reject...size' do
expect_offense(<<~RUBY)
[1, 2].reject { |e| e > 2 }.size
^^^^^^^^^^^^^^^^^^^^^^^^^ Use `count` instead of `reject...size`.
RUBY
context 'with `select` with symbol block argument and `size`' do
it 'registers an offense' do
expect_offense(<<~RUBY)
array.select(&:value).size
^^^^^^^^^^^^^^^^^^^^ Use `count` instead of `select...size`.
RUBY

expect_no_corrections
end
expect_correction(<<~RUBY)
array.count(&:value)
RUBY
end
end

it 'reject...count' do
expect_offense(<<~RUBY)
[1, 2].reject { |e| e > 2 }.count
^^^^^^^^^^^^^^^^^^^^^^^^^^ Use `count` instead of `reject...count`.
RUBY
context 'with `reject` and `size`' do
it 'registers an offense' do
expect_offense(<<~RUBY)
array.reject { |e| e > 2 }.size
^^^^^^^^^^^^^^^^^^^^^^^^^ Use `count` instead of `reject...size`.
RUBY

expect_no_corrections
end
expect_correction(<<~RUBY)
array.count { |e| !(e > 2) }
RUBY
end
end

it 'reject...length' do
expect_offense(<<~RUBY)
[1, 2].reject { |e| e > 2 }.length
^^^^^^^^^^^^^^^^^^^^^^^^^^^ Use `count` instead of `reject...length`.
RUBY
context 'with `reject` and `count`' do
it 'registers an offense' do
expect_offense(<<~RUBY)
array.reject { |e| e > 2 }.count
^^^^^^^^^^^^^^^^^^^^^^^^^^ Use `count` instead of `reject...count`.
RUBY

expect_no_corrections
end
expect_correction(<<~RUBY)
array.count { |e| !(e > 2) }
RUBY
end
end

it 'select...count when count has a block' do
expect_no_offenses(<<~RUBY)
[1, 2].select { |e| e > 2 }.count { |e| e.even? }
RUBY
end
context 'with `reject` and `length`' do
it 'registers an offense' do
expect_offense(<<~RUBY)
array.reject { |e| e > 2 }.length
^^^^^^^^^^^^^^^^^^^^^^^^^^^ Use `count` instead of `reject...length`.
RUBY

it 'reject...size when select has parameters' do
expect_offense(<<~RUBY)
Data = Struct.new(:value)
array = [Data.new(2), Data.new(3), Data.new(2)]
puts array.reject(&:value).size
^^^^^^^^^^^^^^^^^^^^ Use `count` instead of `reject...size`.
RUBY
expect_correction(<<~RUBY)
array.count { |e| !(e > 2) }
RUBY
end
end

expect_no_corrections
end
context 'with `reject` with symbol block argument and `size`' do
it 'registers an offense' do
expect_offense(<<~RUBY)
array.reject(&:value).size
^^^^^^^^^^^^^^^^^^^^ Use `count` instead of `reject...size`.
RUBY

expect_correction(<<~RUBY)
array.count { |element| !element.value }
RUBY
end
end

context 'with `reject` with variable block argument and `size`' do
it 'registers an offense' do
expect_offense(<<~RUBY)
array.reject(&block).size
^^^^^^^^^^^^^^^^^^^ Use `count` instead of `reject...size`.
RUBY

expect_correction(<<~RUBY)
array.count { !block.call }
RUBY
end
end

context 'with `reject` with some statements and `length`' do
it 'registers an offense' do
expect_offense(<<~RUBY)
array.reject {
^^^^^^^^ Use `count` instead of `reject...length`.
foo
bar
}.length
RUBY

expect_correction(<<~RUBY)
array.count {
foo
!(bar)
}
RUBY
end
end

context 'with `reject` with some conditional statement and `length`' do
it 'registers an offense' do
expect_offense(<<~RUBY)
array.reject {
^^^^^^^^ Use `count` instead of `reject...length`.
foo
if bar
baz
else
qux
end
}.length
RUBY

expect_correction(<<~RUBY)
array.count {
foo
!(if bar
baz
else
qux
end)
}
RUBY
end
end
end

0 comments on commit 3554550

Please sign in to comment.