Skip to content

Commit

Permalink
Add new Lint/ItWithoutArgumentsInBlock cop
Browse files Browse the repository at this point in the history
Follow up https://bugs.ruby-lang.org/issues/18980 and ruby/ruby#9152.

Emulates the following Ruby warnings in Ruby 3.3.

```ruby
$ ruby -e '0.times { it }'
-e:1: warning: `it` calls without arguments will refer to the first block param in Ruby 3.4;
use it() or self.it
```

`it` calls without arguments will refer to the first block param in Ruby 3.4.
So use `it()` or `self.it` to ensure compatibility.

```ruby
# bad
do_something { it }

# good
do_something { it() }
do_something { self.it }
```
  • Loading branch information
koic authored and bbatsov committed Dec 9, 2023
1 parent 4e9cfcc commit 3b0360b
Show file tree
Hide file tree
Showing 5 changed files with 203 additions and 0 deletions.
Original file line number Diff line number Diff line change
@@ -0,0 +1 @@
* [#12516](https://github.com/rubocop/rubocop/pull/12516): Add new `Lint/ItWithoutArgumentsInBlock` cop. ([@koic][])
6 changes: 6 additions & 0 deletions config/default.yml
Original file line number Diff line number Diff line change
Expand Up @@ -1963,6 +1963,12 @@ Lint/InterpolationCheck:
VersionAdded: '0.50'
VersionChanged: '1.40'

Lint/ItWithoutArgumentsInBlock:
Description: 'Checks uses of `it` calls without arguments in block.'
Reference: 'https://bugs.ruby-lang.org/issues/18980'
Enabled: pending
VersionAdded: '<<next>>'

Lint/LambdaWithoutLiteralBlock:
Description: 'Checks uses of lambda without a literal block.'
Enabled: pending
Expand Down
1 change: 1 addition & 0 deletions lib/rubocop.rb
Original file line number Diff line number Diff line change
Expand Up @@ -332,6 +332,7 @@
require_relative 'rubocop/cop/lint/ineffective_access_modifier'
require_relative 'rubocop/cop/lint/inherit_exception'
require_relative 'rubocop/cop/lint/interpolation_check'
require_relative 'rubocop/cop/lint/it_without_arguments_in_block'
require_relative 'rubocop/cop/lint/lambda_without_literal_block'
require_relative 'rubocop/cop/lint/literal_as_condition'
require_relative 'rubocop/cop/lint/literal_assignment_in_condition'
Expand Down
56 changes: 56 additions & 0 deletions lib/rubocop/cop/lint/it_without_arguments_in_block.rb
Original file line number Diff line number Diff line change
@@ -0,0 +1,56 @@
# frozen_string_literal: true

module RuboCop
module Cop
module Lint
# Emulates the following Ruby warning in Ruby 3.3.
#
# [source,ruby]
# ----
# $ ruby -e '0.times { it }'
# -e:1: warning: `it` calls without arguments will refer to the first block param in Ruby 3.4;
# use it() or self.it
# ----
#
# `it` calls without arguments will refer to the first block param in Ruby 3.4.
# So use `it()` or `self.it` to ensure compatibility.
#
# @example
#
# # bad
# do_something { it }
#
# # good
# do_something { it() }
# do_something { self.it }
#
class ItWithoutArgumentsInBlock < Base
include NodePattern::Macros

MSG = '`it` calls without arguments will refer to the first block param in Ruby 3.4; ' \
'use `it()` or `self.it`.'

def on_block(node) # rubocop:disable InternalAffairs/NumblockHandler
return unless (body = node.body)
return unless node.arguments.empty_and_without_delimiters?

if body.send_type? && deprecated_it_method?(body)
add_offense(body)
else
body.each_descendant(:send).each do |send_node|
next unless deprecated_it_method?(send_node)

add_offense(send_node)
end
end
end

def deprecated_it_method?(node)
return false unless node.method?(:it)

!node.receiver && node.arguments.empty? && !node.parenthesized? && !node.block_literal?
end
end
end
end
end
139 changes: 139 additions & 0 deletions spec/rubocop/cop/lint/it_without_arguments_in_block_spec.rb
Original file line number Diff line number Diff line change
@@ -0,0 +1,139 @@
# frozen_string_literal: true

RSpec.describe RuboCop::Cop::Lint::ItWithoutArgumentsInBlock, :config do
it 'registers an offense when using `it` without arguments in the single line block' do
expect_offense(<<~RUBY)
0.times { it }
^^ `it` calls without arguments will refer to the first block param in Ruby 3.4; use `it()` or `self.it`.
RUBY
end

it 'registers an offense when using `it` without arguments in the multiline block' do
expect_offense(<<~RUBY)
0.times do
it
^^ `it` calls without arguments will refer to the first block param in Ruby 3.4; use `it()` or `self.it`.
it = 1
it
end
RUBY
end

it 'does not register an offense when using `it` with arguments in the single line block' do
expect_no_offenses(<<~RUBY)
0.times { it(42) }
RUBY
end

it 'does not register an offense when using `it` with block argument in the single line block' do
expect_no_offenses(<<~RUBY)
0.times { it { do_something } }
RUBY
end

it 'does not register an offense when using `it()` in the single line block' do
expect_no_offenses(<<~RUBY)
0.times { it() }
RUBY
end

it 'does not register an offense when using `self.it` in the single line block' do
expect_no_offenses(<<~RUBY)
0.times { self.it }
RUBY
end

it 'does not register an offense when using `it` with arguments in the multiline block' do
expect_no_offenses(<<~RUBY)
0.times do
it(42)
it = 1
it
end
RUBY
end

it 'does not register an offense when using `it` with block argument in the multiline block' do
expect_no_offenses(<<~RUBY)
0.times do
it { do_something }
it = 1
it
end
RUBY
end

it 'does not register an offense when using `it()` in the multiline block' do
expect_no_offenses(<<~RUBY)
0.times do
it()
it = 1
it
end
RUBY
end

it 'does not register an offense when using `self.it` without arguments in the multiline block' do
expect_no_offenses(<<~RUBY)
0.times do
self.it
it = 1
it
end
RUBY
end

it 'does not register an offense when using `it` without arguments in `if` body' do
expect_no_offenses(<<~RUBY)
if false
it
end
RUBY
end

it 'does not register an offense when using `it` without arguments in `def` body' do
expect_no_offenses(<<~RUBY)
def foo
it
end
RUBY
end

it 'does not register an offense when using `it` without arguments in the block with empty block parameter' do
expect_no_offenses(<<~RUBY)
0.times { ||
it
}
RUBY
end

it 'does not register an offense when using `it` without arguments in the block with useless block parameter' do
expect_no_offenses(<<~RUBY)
0.times { |_n|
it
}
RUBY
end

it 'does not register an offense when using `it` inner local variable in block' do
expect_no_offenses(<<~RUBY)
0.times do
it = 1
it
end
RUBY
end

it 'does not register an offense when using `it` outer local variable in block' do
expect_no_offenses(<<~RUBY)
it = 1
0.times { it }
RUBY
end

it 'does not register an offense when using empty block' do
expect_no_offenses(<<~RUBY)
0.times {}
RUBY
end
end

0 comments on commit 3b0360b

Please sign in to comment.