diff --git a/.github/workflows/test.yml b/.github/workflows/test.yml
index ba6afcd779..8b5c6b8291 100644
--- a/.github/workflows/test.yml
+++ b/.github/workflows/test.yml
@@ -83,7 +83,7 @@ jobs:
-e "/gem 'rubocop-performance',/d" \
-e "/gem 'rubocop-rspec',/d" -i Gemfile
cat << EOF > Gemfile.local
- gem 'rubocop', '1.72.1' # Specify the oldest supported RuboCop version
+ gem 'rubocop', '1.75.0' # Specify the oldest supported RuboCop version
EOF
- uses: ruby/setup-ruby@v1
with:
diff --git a/CHANGELOG.md b/CHANGELOG.md
index 287c94fa33..4f61463f4d 100644
--- a/CHANGELOG.md
+++ b/CHANGELOG.md
@@ -9,6 +9,16 @@
## master (unreleased)
+## 2.31.0 (2025-04-01)
+
+### New features
+
+* [#1471](https://github.com/rubocop/rubocop-rails/pull/1471): Support `it` block parameter in `Rails` cops. ([@koic][])
+
+### Bug fixes
+
+* [#1465](https://github.com/rubocop/rubocop-rails/issues/1465): Avoid warnings about methods of `RuboCop::Cop::EnforceSuperclass` being redefined. ([@davidrunger][])
+
## 2.30.3 (2025-03-03)
### Bug fixes
@@ -1263,3 +1273,4 @@
[@franzliedke]: https://github.com/franzliedke
[@ydakuka]: https://github.com/ydakuka
[@exterm]: https://github.com/exterm
+[@davidrunger]: https://github.com/davidrunger
diff --git a/docs/antora.yml b/docs/antora.yml
index 96dbb1a649..cefad11ed1 100644
--- a/docs/antora.yml
+++ b/docs/antora.yml
@@ -2,6 +2,6 @@ name: rubocop-rails
title: RuboCop Rails
# We always provide version without patch here (e.g. 1.1),
# as patch versions should not appear in the docs.
-version: '2.30'
+version: '2.31'
nav:
- modules/ROOT/nav.adoc
diff --git a/docs/modules/ROOT/pages/cops_rails.adoc b/docs/modules/ROOT/pages/cops_rails.adoc
index ac9ecd8c06..ded3d1a047 100644
--- a/docs/modules/ROOT/pages/cops_rails.adoc
+++ b/docs/modules/ROOT/pages/cops_rails.adoc
@@ -725,16 +725,16 @@ end
Prevents usage of `"*"` on an Arel::Table column reference.
-Using `arel_table["*"]` causes the outputted string to be a literal
-quoted asterisk (e.g. `my_model`.`*`). This causes the
-database to look for a column named `*` (or `"*"`) as opposed
+Using `arel_table["\*"]` causes the outputted string to be a literal
+quoted asterisk (e.g. `my_model`.`*`). This causes the
+database to look for a column named `\*` (or `"*"`) as opposed
to expanding the column list as one would likely expect.
[#safety-railsarelstar]
=== Safety
-This cop's autocorrection is unsafe because it turns a quoted `*` into
-an SQL `*`, unquoted. `*` is a valid column name in certain databases
+This cop's autocorrection is unsafe because it turns a quoted `\*` into
+an SQL `*`, unquoted. `\*` is a valid column name in certain databases
supported by Rails, and even though it is usually a mistake,
it might denote legitimate access to a column named `*`.
diff --git a/lib/rubocop-rails.rb b/lib/rubocop-rails.rb
index 9338cd8c81..f5c2901003 100644
--- a/lib/rubocop-rails.rb
+++ b/lib/rubocop-rails.rb
@@ -3,7 +3,6 @@
require 'rubocop'
require 'rack/utils'
require 'active_support/inflector'
-require 'active_support/core_ext/object/blank'
require_relative 'rubocop/rails'
require_relative 'rubocop/rails/version'
diff --git a/lib/rubocop/cop/mixin/database_type_resolvable.rb b/lib/rubocop/cop/mixin/database_type_resolvable.rb
index 3743514492..734451b04c 100644
--- a/lib/rubocop/cop/mixin/database_type_resolvable.rb
+++ b/lib/rubocop/cop/mixin/database_type_resolvable.rb
@@ -29,8 +29,8 @@ def database_from_yaml
end
def database_from_env
- url = ENV['DATABASE_URL'].presence
- return unless url
+ url = ENV.fetch('DATABASE_URL', '')
+ return if url.blank?
case url
when %r{\A(mysql2|trilogy)://}
diff --git a/lib/rubocop/cop/mixin/enforce_superclass.rb b/lib/rubocop/cop/mixin/enforce_superclass.rb
index 59f9763a60..12817949b8 100644
--- a/lib/rubocop/cop/mixin/enforce_superclass.rb
+++ b/lib/rubocop/cop/mixin/enforce_superclass.rb
@@ -1,7 +1,12 @@
# frozen_string_literal: true
module RuboCop
- module Cop
+ module Cop # rubocop:disable Style/Documentation
+ # The EnforceSuperclass module is also defined in `rubocop` (for backwards
+ # compatibility), so here we remove it before (re)defining it, to avoid
+ # warnings about methods in the module being redefined.
+ remove_const(:EnforceSuperclass) if defined?(EnforceSuperclass)
+
# Common functionality for enforcing a specific superclass.
module EnforceSuperclass
def self.included(base)
diff --git a/lib/rubocop/cop/mixin/index_method.rb b/lib/rubocop/cop/mixin/index_method.rb
index 2b3b2387bd..fa2ba5bc13 100644
--- a/lib/rubocop/cop/mixin/index_method.rb
+++ b/lib/rubocop/cop/mixin/index_method.rb
@@ -58,7 +58,7 @@ def set_new_method_name(new_method_name, corrector)
end
def set_new_arg_name(transformed_argname, corrector)
- return if block_node.numblock_type?
+ return unless block_node.block_type?
corrector.replace(block_node.arguments, "|#{transformed_argname}|")
end
@@ -84,6 +84,7 @@ def on_block(node)
end
alias on_numblock on_block
+ alias on_itblock on_block
def on_send(node)
on_bad_map_to_h(node) do |*match|
diff --git a/lib/rubocop/cop/rails/arel_star.rb b/lib/rubocop/cop/rails/arel_star.rb
index 74f43f9da3..60a6d775d9 100644
--- a/lib/rubocop/cop/rails/arel_star.rb
+++ b/lib/rubocop/cop/rails/arel_star.rb
@@ -5,14 +5,14 @@ module Cop
module Rails
# Prevents usage of `"*"` on an Arel::Table column reference.
#
- # Using `arel_table["*"]` causes the outputted string to be a literal
- # quoted asterisk (e.g. `my_model`.`*`). This causes the
- # database to look for a column named `*` (or `"*"`) as opposed
+ # Using `arel_table["\*"]` causes the outputted string to be a literal
+ # quoted asterisk (e.g. `my_model`.`*`). This causes the
+ # database to look for a column named `\*` (or `"*"`) as opposed
# to expanding the column list as one would likely expect.
#
# @safety
- # This cop's autocorrection is unsafe because it turns a quoted `*` into
- # an SQL `*`, unquoted. `*` is a valid column name in certain databases
+ # This cop's autocorrection is unsafe because it turns a quoted `\*` into
+ # an SQL `*`, unquoted. `\*` is a valid column name in certain databases
# supported by Rails, and even though it is usually a mistake,
# it might denote legitimate access to a column named `*`.
#
diff --git a/lib/rubocop/cop/rails/eager_evaluation_log_message.rb b/lib/rubocop/cop/rails/eager_evaluation_log_message.rb
index 45b3513c93..63629a10f6 100644
--- a/lib/rubocop/cop/rails/eager_evaluation_log_message.rb
+++ b/lib/rubocop/cop/rails/eager_evaluation_log_message.rb
@@ -45,12 +45,10 @@ def on_send(node)
return if node.parent&.block_type?
interpolated_string_passed_to_debug(node) do |arguments|
- message = format(MSG)
-
range = replacement_range(node)
replacement = replacement_source(node, arguments)
- add_offense(range, message: message) do |corrector|
+ add_offense(range) do |corrector|
corrector.replace(range, replacement)
end
end
diff --git a/lib/rubocop/cop/rails/index_by.rb b/lib/rubocop/cop/rails/index_by.rb
index e89b24bb4a..d5e67d7f06 100644
--- a/lib/rubocop/cop/rails/index_by.rb
+++ b/lib/rubocop/cop/rails/index_by.rb
@@ -37,6 +37,9 @@ class IndexBy < Base
(numblock
(call _ :to_h) $1
(array $_ (lvar :_1)))
+ (itblock
+ (call _ :to_h) $:it
+ (array $_ (lvar :it)))
}
PATTERN
@@ -50,6 +53,9 @@ class IndexBy < Base
(numblock
(call _ {:map :collect}) $1
(array $_ (lvar :_1)))
+ (itblock
+ (call _ {:map :collect}) $:it
+ (array $_ (lvar :it)))
}
:to_h)
PATTERN
@@ -66,6 +72,9 @@ class IndexBy < Base
(numblock
(call _ {:map :collect}) $1
(array $_ (lvar :_1)))
+ (itblock
+ (call _ {:map :collect}) $:it
+ (array $_ (lvar :it)))
}
)
PATTERN
diff --git a/lib/rubocop/cop/rails/index_with.rb b/lib/rubocop/cop/rails/index_with.rb
index fc72c51646..3f56b89675 100644
--- a/lib/rubocop/cop/rails/index_with.rb
+++ b/lib/rubocop/cop/rails/index_with.rb
@@ -40,6 +40,9 @@ class IndexWith < Base
(numblock
(call _ :to_h) $1
(array (lvar :_1) $_))
+ (itblock
+ (call _ :to_h) $:it
+ (array (lvar :it) $_))
}
PATTERN
@@ -53,6 +56,9 @@ class IndexWith < Base
(numblock
(call _ {:map :collect}) $1
(array (lvar :_1) $_))
+ (itblock
+ (call _ {:map :collect}) $:it
+ (array (lvar :it) $_))
}
:to_h)
PATTERN
@@ -69,6 +75,9 @@ class IndexWith < Base
(numblock
(call _ {:map :collect}) $1
(array (lvar :_1) $_))
+ (itblock
+ (call _ {:map :collect}) $:it
+ (array (lvar :it) $_))
}
)
PATTERN
diff --git a/lib/rubocop/cop/rails/output.rb b/lib/rubocop/cop/rails/output.rb
index 067fdb4be8..e3fdd161b5 100644
--- a/lib/rubocop/cop/rails/output.rb
+++ b/lib/rubocop/cop/rails/output.rb
@@ -23,7 +23,6 @@ class Output < Base
MSG = "Do not write to stdout. Use Rails's logger if you want to log."
RESTRICT_ON_SEND = %i[ap p pp pretty_print print puts binwrite syswrite write write_nonblock].freeze
- ALLOWED_TYPES = %i[send csend block numblock].freeze
def_node_matcher :output?, <<~PATTERN
(send nil? {:ap :p :pp :pretty_print :print :puts} ...)
@@ -40,7 +39,7 @@ class Output < Base
PATTERN
def on_send(node)
- return if ALLOWED_TYPES.include?(node.parent&.type)
+ return if node.parent&.call_type? || node.block_node
return if !output?(node) && !io_output?(node)
range = offense_range(node)
diff --git a/lib/rubocop/cop/rails/pluck.rb b/lib/rubocop/cop/rails/pluck.rb
index e6fcfb6060..25d8f243b6 100644
--- a/lib/rubocop/cop/rails/pluck.rb
+++ b/lib/rubocop/cop/rails/pluck.rb
@@ -59,6 +59,7 @@ class Pluck < Base
(any_block (call _ {:map :collect}) $_argument (send lvar :[] $_key))
PATTERN
+ # rubocop:disable Metrics/AbcSize
def on_block(node)
return if node.each_ancestor(:any_block).any?
@@ -68,20 +69,25 @@ def on_block(node)
match = if node.block_type?
block_argument = argument.children.first.source
use_block_argument_in_key?(block_argument, key)
- else # numblock
- argument == 1 && use_block_argument_in_key?('_1', key)
+ elsif node.numblock_type?
+ use_block_argument_in_key?('_1', key)
+ else # itblock
+ use_block_argument_in_key?('it', key)
end
next unless match
register_offense(node, key)
end
end
+ # rubocop:enable Metrics/AbcSize
alias on_numblock on_block
+ alias on_itblock on_block
private
def use_one_block_argument?(argument)
- return true if argument == 1 # Checks for numbered argument `_1`.
+ # Checks for numbered argument `_1` or `it block parameter.
+ return true if [1, :it].include?(argument)
argument.respond_to?(:one?) && argument.one?
end
diff --git a/lib/rubocop/cop/rails/redundant_receiver_in_with_options.rb b/lib/rubocop/cop/rails/redundant_receiver_in_with_options.rb
index 05df3c8675..ba71eb090a 100644
--- a/lib/rubocop/cop/rails/redundant_receiver_in_with_options.rb
+++ b/lib/rubocop/cop/rails/redundant_receiver_in_with_options.rb
@@ -85,18 +85,22 @@ def on_block(node)
end
alias on_numblock on_block
+ alias on_itblock on_block
private
def autocorrect(corrector, send_node, node)
corrector.remove(send_node.receiver)
corrector.remove(send_node.loc.dot)
- corrector.remove(block_argument_range(send_node)) unless node.numblock_type?
+ corrector.remove(block_argument_range(send_node)) if node.block_type?
end
+ # rubocop:disable Metrics/AbcSize
def redundant_receiver?(send_nodes, node)
proc = if node.numblock_type?
->(n) { n.receiver.lvar_type? && n.receiver.source == '_1' }
+ elsif node.itblock_type?
+ ->(n) { n.receiver.lvar_type? && n.receiver.source == 'it' }
else
return false if node.arguments.empty?
@@ -106,6 +110,7 @@ def redundant_receiver?(send_nodes, node)
send_nodes.all?(&proc)
end
+ # rubocop:enable Metrics/AbcSize
def block_argument_range(node)
block_node = node.each_ancestor(:block).first
diff --git a/lib/rubocop/cop/rails/reflection_class_name.rb b/lib/rubocop/cop/rails/reflection_class_name.rb
index 500e100348..16eeb05448 100644
--- a/lib/rubocop/cop/rails/reflection_class_name.rb
+++ b/lib/rubocop/cop/rails/reflection_class_name.rb
@@ -76,7 +76,7 @@ def reflection_class_value?(class_value)
def autocorrect(corrector, class_config)
class_value = class_config.value
replacement = const_or_string(class_value)
- return unless replacement.present?
+ return unless replacement
corrector.replace(class_value, replacement.source.inspect)
end
diff --git a/lib/rubocop/cop/rails/reversible_migration.rb b/lib/rubocop/cop/rails/reversible_migration.rb
index 7b65e00e23..b836a13805 100644
--- a/lib/rubocop/cop/rails/reversible_migration.rb
+++ b/lib/rubocop/cop/rails/reversible_migration.rb
@@ -205,6 +205,7 @@ def on_block(node)
end
alias on_numblock on_block
+ alias on_itblock on_block
private
diff --git a/lib/rubocop/cop/rails/schema_comment.rb b/lib/rubocop/cop/rails/schema_comment.rb
index bd707d68af..c9cd898a10 100644
--- a/lib/rubocop/cop/rails/schema_comment.rb
+++ b/lib/rubocop/cop/rails/schema_comment.rb
@@ -39,7 +39,7 @@ class SchemaComment < Base
# @!method comment_present?(node)
def_node_matcher :comment_present?, <<~PATTERN
- (hash <(pair {(sym :comment) (str "comment")} (_ [present?])) ...>)
+ (hash <(pair {(sym :comment) (str "comment")} (_ !blank?)) ...>)
PATTERN
# @!method add_column?(node)
diff --git a/lib/rubocop/rails/version.rb b/lib/rubocop/rails/version.rb
index ab245704e4..a26810ba44 100644
--- a/lib/rubocop/rails/version.rb
+++ b/lib/rubocop/rails/version.rb
@@ -4,7 +4,7 @@ module RuboCop
module Rails
# This module holds the RuboCop Rails version information.
module Version
- STRING = '2.30.3'
+ STRING = '2.31.0'
def self.document_version
STRING.match('\d+\.\d+').to_s
diff --git a/relnotes/v2.31.0.md b/relnotes/v2.31.0.md
new file mode 100644
index 0000000000..6f179fabb2
--- /dev/null
+++ b/relnotes/v2.31.0.md
@@ -0,0 +1,10 @@
+### New features
+
+* [#1471](https://github.com/rubocop/rubocop-rails/pull/1471): Support `it` block parameter in `Rails` cops. ([@koic][])
+
+### Bug fixes
+
+* [#1465](https://github.com/rubocop/rubocop-rails/issues/1465): Avoid warnings about methods of `RuboCop::Cop::EnforceSuperclass` being redefined. ([@davidrunger][])
+
+[@koic]: https://github.com/koic
+[@davidrunger]: https://github.com/davidrunger
diff --git a/rubocop-rails.gemspec b/rubocop-rails.gemspec
index 1da79ec206..93bf92ec62 100644
--- a/rubocop-rails.gemspec
+++ b/rubocop-rails.gemspec
@@ -37,6 +37,6 @@ Gem::Specification.new do |s|
# Rack::Utils::SYMBOL_TO_STATUS_CODE, which is used by HttpStatus cop, was
# introduced in rack 1.1
s.add_dependency 'rack', '>= 1.1'
- s.add_dependency 'rubocop', '>= 1.72.1', '< 2.0'
+ s.add_dependency 'rubocop', '>= 1.75.0', '< 2.0'
s.add_dependency 'rubocop-ast', '>= 1.38.0', '< 2.0'
end
diff --git a/spec/rubocop/cop/rails/create_table_with_timestamps_spec.rb b/spec/rubocop/cop/rails/create_table_with_timestamps_spec.rb
index 981046396a..d4729465b7 100644
--- a/spec/rubocop/cop/rails/create_table_with_timestamps_spec.rb
+++ b/spec/rubocop/cop/rails/create_table_with_timestamps_spec.rb
@@ -64,6 +64,17 @@
RUBY
end
+ it 'does not register an offense when including timestamps in itblock', :ruby34, unsupported_on: :parser do
+ expect_no_offenses <<~RUBY
+ create_table :users do
+ it.string :name
+ it.string :email
+
+ it.timestamps
+ end
+ RUBY
+ end
+
it 'does not register an offense when including timestamps with `to_proc` syntax' do
expect_no_offenses <<~RUBY
create_table :users, &:timestamps
diff --git a/spec/rubocop/cop/rails/index_by_spec.rb b/spec/rubocop/cop/rails/index_by_spec.rb
index ab3cfded01..49adfcb81a 100644
--- a/spec/rubocop/cop/rails/index_by_spec.rb
+++ b/spec/rubocop/cop/rails/index_by_spec.rb
@@ -262,4 +262,68 @@
end
end
end
+
+ context '`it` parameter', :ruby34, unsupported_on: :parser do
+ it 'registers an offense for `map { ... }.to_h`' do
+ expect_offense(<<~RUBY)
+ x.map { [it.to_sym, it] }.to_h
+ ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ Prefer `index_by` over `map { ... }.to_h`.
+ RUBY
+
+ expect_correction(<<~RUBY)
+ x.index_by { it.to_sym }
+ RUBY
+ end
+
+ context 'when values are transformed' do
+ it 'does not register an offense for `map { ... }.to_h`' do
+ expect_no_offenses(<<~RUBY)
+ x.map { [it.to_sym, foo(it)] }.to_h
+ RUBY
+ end
+ end
+
+ it 'registers an offense for Hash[map { ... }]' do
+ expect_offense(<<~RUBY)
+ Hash[x.map { [it.to_sym, it] }]
+ ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ Prefer `index_by` over `Hash[map { ... }]`.
+ RUBY
+
+ expect_correction(<<~RUBY)
+ x.index_by { it.to_sym }
+ RUBY
+ end
+
+ context 'when the referenced `it` parameter is not it' do
+ it 'does not register an offense for Hash[map { ... }]' do
+ expect_no_offenses(<<~RUBY)
+ Hash[x.map { [it.to_sym, y] }]
+ RUBY
+ end
+ end
+
+ it 'registers an offense for `to_h { ... }`' do
+ expect_offense(<<~RUBY)
+ x.to_h { [it.to_sym, it] }
+ ^^^^^^^^^^^^^^^^^^^^^^^^^^ Prefer `index_by` over `to_h { ... }`.
+ RUBY
+
+ expect_correction(<<~RUBY)
+ x.index_by { it.to_sym }
+ RUBY
+ end
+
+ context 'when `it` parameter other than `it` is referenced in the key' do
+ it 'registers an offense for `to_h { ... }`' do
+ expect_offense(<<~RUBY)
+ x.to_h { [y.to_sym, it] }
+ ^^^^^^^^^^^^^^^^^^^^^^^^^ Prefer `index_by` over `to_h { ... }`.
+ RUBY
+
+ expect_correction(<<~RUBY)
+ x.index_by { y.to_sym }
+ RUBY
+ end
+ end
+ end
end
diff --git a/spec/rubocop/cop/rails/index_with_spec.rb b/spec/rubocop/cop/rails/index_with_spec.rb
index 2cc3337758..eb900914af 100644
--- a/spec/rubocop/cop/rails/index_with_spec.rb
+++ b/spec/rubocop/cop/rails/index_with_spec.rb
@@ -261,6 +261,70 @@
end
end
end
+
+ context '`it` block parameter', :ruby34, unsupported_on: :parser do
+ it 'registers an offense for `map { ... }.to_h`' do
+ expect_offense(<<~RUBY)
+ x.map { [it, it.to_sym] }.to_h
+ ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ Prefer `index_with` over `map { ... }.to_h`.
+ RUBY
+
+ expect_correction(<<~RUBY)
+ x.index_with { it.to_sym }
+ RUBY
+ end
+
+ context 'when keys are transformed' do
+ it 'does not register an offense for `map { ... }.to_h`' do
+ expect_no_offenses(<<~RUBY)
+ x.map { [foo(it), it.to_sym] }.to_h
+ RUBY
+ end
+ end
+
+ it 'registers an offense for Hash[map { ... }]' do
+ expect_offense(<<~RUBY)
+ Hash[x.map { [it, it.to_sym] }]
+ ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ Prefer `index_with` over `Hash[map { ... }]`.
+ RUBY
+
+ expect_correction(<<~RUBY)
+ x.index_with { it.to_sym }
+ RUBY
+ end
+
+ context 'when the referenced `it` parameter is not `it`' do
+ it 'does not register an offense for Hash[map { ... }]' do
+ expect_no_offenses(<<~RUBY)
+ Hash[x.map { [y, it.to_sym] }]
+ RUBY
+ end
+ end
+
+ it 'registers an offense for `to_h { ... }`' do
+ expect_offense(<<~RUBY)
+ x.to_h { [it, it.to_sym] }
+ ^^^^^^^^^^^^^^^^^^^^^^^^^^ Prefer `index_with` over `to_h { ... }`.
+ RUBY
+
+ expect_correction(<<~RUBY)
+ x.index_with { it.to_sym }
+ RUBY
+ end
+
+ context 'when an `it` parameter other than `it` is referenced in the value' do
+ it 'registers an offense for `to_h { ... }`' do
+ expect_offense(<<~RUBY)
+ x.to_h { [it, y.to_sym] }
+ ^^^^^^^^^^^^^^^^^^^^^^^^^ Prefer `index_with` over `to_h { ... }`.
+ RUBY
+
+ expect_correction(<<~RUBY)
+ x.index_with { y.to_sym }
+ RUBY
+ end
+ end
+ end
end
context 'when using Rails 5.2 or older', :rails52 do
diff --git a/spec/rubocop/cop/rails/output_spec.rb b/spec/rubocop/cop/rails/output_spec.rb
index 9dac2f84e0..18cd8c6d3e 100644
--- a/spec/rubocop/cop/rails/output_spec.rb
+++ b/spec/rubocop/cop/rails/output_spec.rb
@@ -161,6 +161,12 @@
RUBY
end
+ it 'does not register an offense when io method is called with `it` parameter', :ruby34, unsupported_on: :parser do
+ expect_no_offenses(<<~RUBY)
+ obj.write { do_something(it) }
+ RUBY
+ end
+
it 'does not register an offense when a method is ' \
'safe navigation called to a local variable with the same name as a print method' do
expect_no_offenses(<<~RUBY)
diff --git a/spec/rubocop/cop/rails/pluck_spec.rb b/spec/rubocop/cop/rails/pluck_spec.rb
index 4f115e5183..84e0e9055a 100644
--- a/spec/rubocop/cop/rails/pluck_spec.rb
+++ b/spec/rubocop/cop/rails/pluck_spec.rb
@@ -129,6 +129,31 @@
end
end
+ context 'when using Ruby 3.4 or newer', :ruby34, unsupported_on: :parser do
+ context 'when using `it` block parameter' do
+ context "when `#{method}` can be replaced with `pluck`" do
+ it 'registers an offense' do
+ expect_offense(<<~RUBY, method: method)
+ x.%{method} { it[:foo] }
+ ^{method}^^^^^^^^^^^^^ Prefer `pluck(:foo)` over `%{method} { it[:foo] }`.
+ RUBY
+
+ expect_correction(<<~RUBY)
+ x.pluck(:foo)
+ RUBY
+ end
+ end
+
+ context 'when the `it` argument is used in `[]`' do
+ it 'does not register an offense' do
+ expect_no_offenses(<<~RUBY)
+ x.#{method} { it[foo...it.to_something] }
+ RUBY
+ end
+ end
+ end
+ end
+
context "when `#{method}` is used in block" do
it 'does not register an offense' do
expect_no_offenses(<<~RUBY)
diff --git a/spec/rubocop/cop/rails/redundant_active_record_all_method_spec.rb b/spec/rubocop/cop/rails/redundant_active_record_all_method_spec.rb
index 5b7e35bd48..bfdd945a80 100644
--- a/spec/rubocop/cop/rails/redundant_active_record_all_method_spec.rb
+++ b/spec/rubocop/cop/rails/redundant_active_record_all_method_spec.rb
@@ -329,6 +329,12 @@
RUBY
end
+ it "does not register an offense when using `#{method}` with `it` block", :ruby34, unsupported_on: :parser do
+ expect_no_offenses(<<~RUBY)
+ User.all.#{method} { it.do_something }
+ RUBY
+ end
+
it "does not register an offense when using `#{method}` with symbol block" do
expect_no_offenses(<<~RUBY)
User.all.#{method}(&:do_something)
diff --git a/spec/rubocop/cop/rails/redundant_receiver_in_with_options_spec.rb b/spec/rubocop/cop/rails/redundant_receiver_in_with_options_spec.rb
index 745bd62b7c..9d5693c628 100644
--- a/spec/rubocop/cop/rails/redundant_receiver_in_with_options_spec.rb
+++ b/spec/rubocop/cop/rails/redundant_receiver_in_with_options_spec.rb
@@ -59,6 +59,36 @@ class Account < ApplicationRecord
end
end
+ context 'Ruby >= 3.4', :ruby34, unsupported_on: :parser do
+ it 'registers an offense and corrects using explicit receiver in `with_options`' do
+ expect_offense(<<~RUBY)
+ class Account < ApplicationRecord
+ with_options dependent: :destroy do
+ it.has_many :customers
+ ^^ Redundant receiver in `with_options`.
+ it.has_many :products
+ ^^ Redundant receiver in `with_options`.
+ it.has_many :invoices
+ ^^ Redundant receiver in `with_options`.
+ it.has_many :expenses
+ ^^ Redundant receiver in `with_options`.
+ end
+ end
+ RUBY
+
+ expect_correction(<<~RUBY)
+ class Account < ApplicationRecord
+ with_options dependent: :destroy do
+ has_many :customers
+ has_many :products
+ has_many :invoices
+ has_many :expenses
+ end
+ end
+ RUBY
+ end
+ end
+
it 'does not register an offense when using implicit receiver in `with_options`' do
expect_no_offenses(<<~RUBY)
class Account < ApplicationRecord
diff --git a/spec/rubocop/cop/rails/relative_date_constant_spec.rb b/spec/rubocop/cop/rails/relative_date_constant_spec.rb
index e2a6113f6e..c199406fd5 100644
--- a/spec/rubocop/cop/rails/relative_date_constant_spec.rb
+++ b/spec/rubocop/cop/rails/relative_date_constant_spec.rb
@@ -26,6 +26,14 @@ class SomeClass
RUBY
end
+ it 'accepts a lambda with itblock', :ruby34, unsupported_on: :parser do
+ expect_no_offenses(<<~RUBY)
+ class SomeClass
+ EXPIRED_AT = -> { it.year.ago }
+ end
+ RUBY
+ end
+
it 'accepts a proc' do
expect_no_offenses(<<~RUBY)
class SomeClass
diff --git a/spec/rubocop/cop/rails/reversible_migration_spec.rb b/spec/rubocop/cop/rails/reversible_migration_spec.rb
index 74b5c11f1c..e2ea5d9483 100644
--- a/spec/rubocop/cop/rails/reversible_migration_spec.rb
+++ b/spec/rubocop/cop/rails/reversible_migration_spec.rb
@@ -48,6 +48,14 @@ def change
RUBY
end
+ context 'Ruby >= 3.4', :ruby34, unsupported_on: :parser do
+ it_behaves_like 'accepts', 'create_table using `it` parameter', <<~RUBY
+ create_table :users do
+ it.string :name
+ end
+ RUBY
+ end
+
it_behaves_like 'offense', 'execute', <<~RUBY
execute "ALTER TABLE `pages_linked_pages` ADD UNIQUE `page_id_linked_page_id` (`page_id`,`linked_page_id`)"
RUBY
@@ -120,6 +128,12 @@ def change
end
RUBY
+ it_behaves_like 'accepts', 'drop_table(with itblock)', <<~RUBY, :ruby34, unsupported_on: :parser
+ drop_table :users do
+ it.string :name
+ end
+ RUBY
+
it_behaves_like 'accepts', 'drop_table(with symbol proc)', <<~RUBY
drop_table :users, &:timestamps
RUBY
@@ -258,6 +272,14 @@ def change
RUBY
end
+ context 'Ruby >= 3.4', :ruby34, unsupported_on: :parser do
+ it_behaves_like 'offense', 'change_table(with change_default)', <<~RUBY
+ change_table :users do
+ it.change_default :authorized, 1
+ end
+ RUBY
+ end
+
context 'remove' do
context 'Rails >= 6.1', :rails61 do
it_behaves_like 'accepts', 't.remove (with type)', <<~RUBY
diff --git a/spec/rubocop/cop/rails/save_bang_spec.rb b/spec/rubocop/cop/rails/save_bang_spec.rb
index 5c6dc8997a..70d9173f2e 100644
--- a/spec/rubocop/cop/rails/save_bang_spec.rb
+++ b/spec/rubocop/cop/rails/save_bang_spec.rb
@@ -157,6 +157,23 @@
end
end
+ it "when assigning the return value of #{method} with `it` block", :ruby34, unsupported_on: :parser do
+ if update
+ expect_no_offenses(<<~RUBY)
+ x = object.#{method} do
+ it.name = 'Tom'
+ end
+ RUBY
+ else
+ expect_offense(<<~RUBY, method: method)
+ x = object.#{method} do
+ ^{method} Use `#{method}!` instead of `#{method}` if the return value is not checked. Or check `persisted?` on model returned from `#{method}`.
+ it.name = 'Tom'
+ end
+ RUBY
+ end
+ end
+
it "when using #{method} with if" do
if update
expect_no_offenses(<<~RUBY)
@@ -605,6 +622,23 @@ def foo
end
end
+ it "when using #{method} as last method call of an itblock", :ruby34, unsupported_on: :parser do
+ if allow_implicit_return
+ expect_no_offenses(<<~RUBY)
+ objects.each do
+ it.#{method}
+ end
+ RUBY
+ else
+ expect_offense(<<~RUBY, method: method)
+ objects.each do
+ it.#{method}
+ ^{method} Use `#{method}!` instead of `#{method}` if the return value is not checked.
+ end
+ RUBY
+ end
+ end
+
it "when using #{method} as part of the last line" do
if allow_implicit_return
expect_no_offenses(<<~RUBY)
diff --git a/spec/rubocop/cop/rails/transaction_exit_statement_spec.rb b/spec/rubocop/cop/rails/transaction_exit_statement_spec.rb
index 613642e3ad..d94f67ba1a 100644
--- a/spec/rubocop/cop/rails/transaction_exit_statement_spec.rb
+++ b/spec/rubocop/cop/rails/transaction_exit_statement_spec.rb
@@ -21,6 +21,16 @@
RUBY
end
+ it 'registers an offense when `return` is used in transaction with itblock', :ruby34, unsupported_on: :parser do
+ expect_offense(<<~RUBY, method: method)
+ ApplicationRecord.%{method} do
+ it.after_commit { }
+ return if user.active?
+ ^^^^^^ Exit statement `return` is not allowed. Use `raise` (rollback) or `next` (commit).
+ end
+ RUBY
+ end
+
it 'registers an offense when `break` is used in transactions' do
expect_offense(<<~RUBY, method: method)
ApplicationRecord.%{method} do
@@ -77,6 +87,18 @@
RUBY
end
+ it 'registers an offense when `return` is used in `each` with itblock in transactions', :ruby34,
+ unsupported_on: :parser do
+ expect_offense(<<~RUBY, method: method)
+ ApplicationRecord.%{method} do
+ foo.each do
+ return if it
+ ^^^^^^ Exit statement `return` is not allowed. Use `raise` (rollback) or `next` (commit).
+ end
+ end
+ RUBY
+ end
+
it 'registers an offense when `throw` is used in `loop` in transactions' do
expect_offense(<<~RUBY, method: method)
ApplicationRecord.%{method} do
@@ -108,6 +130,18 @@
RUBY
end
+ context 'when using Ruby >= 3.4', :ruby34, unsupported_on: :parser do
+ it 'does not register an offense when `break` is used in `each` with itblock in transactions' do
+ expect_no_offenses(<<~RUBY)
+ ApplicationRecord.#{method} do
+ foo.each do
+ break if it
+ end
+ end
+ RUBY
+ end
+ end
+
it 'registers an offense when `return` is used in `rescue`' do
expect_offense(<<~RUBY, method: method)
ApplicationRecord.%{method} do
diff --git a/spec/rubocop/cop/rails/uniq_before_pluck_spec.rb b/spec/rubocop/cop/rails/uniq_before_pluck_spec.rb
index 4704bec946..4315e5237f 100644
--- a/spec/rubocop/cop/rails/uniq_before_pluck_spec.rb
+++ b/spec/rubocop/cop/rails/uniq_before_pluck_spec.rb
@@ -80,6 +80,12 @@
Model.where(foo: 1).pluck(:name).uniq { _1[0] }
RUBY
end
+
+ it 'ignores uniq with an `it` block', :ruby34, unsupported_on: :parser do
+ expect_no_offenses(<<~RUBY)
+ Model.where(foo: 1).pluck(:name).uniq { it[0] }
+ RUBY
+ end
end
it 'registers an offense' do
diff --git a/spec/spec_helper.rb b/spec/spec_helper.rb
index dadf1a9597..378fe7262b 100644
--- a/spec/spec_helper.rb
+++ b/spec/spec_helper.rb
@@ -22,6 +22,9 @@
# Prism supports Ruby 3.3+ parsing.
config.filter_run_excluding unsupported_on: :prism if ENV['PARSER_ENGINE'] == 'parser_prism'
+ # With whitequark/parser, RuboCop supports Ruby syntax compatible with 2.0 to 3.3.
+ config.filter_run_excluding unsupported_on: :parser if ENV['PARSER_ENGINE'] != 'parser_prism'
+
config.example_status_persistence_file_path = 'spec/examples.txt'
config.disable_monkey_patching!
config.warnings = true