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