From c6b95030fef81aeaab02e44d283197b4d50204a8 Mon Sep 17 00:00:00 2001 From: ytjmt <46666464+ytjmt@users.noreply.github.com> Date: Sat, 6 Jul 2024 16:18:49 +0900 Subject: [PATCH] Support Rails 7 syntax for Rails/EnumHash cop --- ..._rails_7_syntax_for_rails_enum_hash_cop.md | 1 + lib/rubocop/cop/rails/enum_hash.rb | 39 +- spec/rubocop/cop/rails/enum_hash_spec.rb | 370 ++++++++++++------ 3 files changed, 293 insertions(+), 117 deletions(-) create mode 100644 changelog/new_support_rails_7_syntax_for_rails_enum_hash_cop.md diff --git a/changelog/new_support_rails_7_syntax_for_rails_enum_hash_cop.md b/changelog/new_support_rails_7_syntax_for_rails_enum_hash_cop.md new file mode 100644 index 0000000000..355a76dd25 --- /dev/null +++ b/changelog/new_support_rails_7_syntax_for_rails_enum_hash_cop.md @@ -0,0 +1 @@ +* [#1309](https://github.com/rubocop/rubocop-rails/pull/1309): Support Rails 7 syntax for `Rails/EnumHash` cop. ([@ytjmt][]) diff --git a/lib/rubocop/cop/rails/enum_hash.rb b/lib/rubocop/cop/rails/enum_hash.rb index 2b69e2e2ee..992a4567c5 100644 --- a/lib/rubocop/cop/rails/enum_hash.rb +++ b/lib/rubocop/cop/rails/enum_hash.rb @@ -12,6 +12,12 @@ module Rails # # @example # # bad + # enum :status, [:active, :archived] + # + # # good + # enum :status, { active: 0, archived: 1 } + # + # # bad # enum status: [:active, :archived] # # # good @@ -23,7 +29,11 @@ class EnumHash < Base MSG = 'Enum defined as an array found in `%s` enum declaration. Use hash syntax instead.' RESTRICT_ON_SEND = %i[enum].freeze - def_node_matcher :enum?, <<~PATTERN + def_node_matcher :enum_with_array?, <<~PATTERN + (send nil? :enum $_ ${array} ...) + PATTERN + + def_node_matcher :enum_with_old_syntax?, <<~PATTERN (send nil? :enum (hash $...)) PATTERN @@ -32,17 +42,19 @@ class EnumHash < Base PATTERN def on_send(node) - enum?(node) do |pairs| + enum_with_array?(node) do |key, array| + add_offense(array, message: message(key)) do |corrector| + corrector.replace(array, build_hash(array)) + end + end + + enum_with_old_syntax?(node) do |pairs| pairs.each do |pair| key, array = array_pair?(pair) next unless key - add_offense(array, message: format(MSG, enum: enum_name(key))) do |corrector| - hash = array.children.each_with_index.map do |elem, index| - "#{source(elem)} => #{index}" - end.join(', ') - - corrector.replace(array, "{#{hash}}") + add_offense(array, message: message(key)) do |corrector| + corrector.replace(array, build_hash(array)) end end end @@ -50,6 +62,10 @@ def on_send(node) private + def message(key) + format(MSG, enum: enum_name(key)) + end + def enum_name(key) case key.type when :sym, :str @@ -69,6 +85,13 @@ def source(elem) elem.source end end + + def build_hash(array) + hash = array.children.each_with_index.map do |elem, index| + "#{source(elem)} => #{index}" + end.join(', ') + "{#{hash}}" + end end end end diff --git a/spec/rubocop/cop/rails/enum_hash_spec.rb b/spec/rubocop/cop/rails/enum_hash_spec.rb index d3aff4388c..104b4ff3a4 100644 --- a/spec/rubocop/cop/rails/enum_hash_spec.rb +++ b/spec/rubocop/cop/rails/enum_hash_spec.rb @@ -1,162 +1,314 @@ # frozen_string_literal: true RSpec.describe RuboCop::Cop::Rails::EnumHash, :config do - context 'when array syntax is used' do - context 'with %i[] syntax' do - it 'registers an offense' do + context 'when Rails 7 syntax is used' do + context 'when array syntax is used' do + context 'with %i[] syntax' do + it 'registers an offense' do + expect_offense(<<~RUBY) + enum :status, %i[active archived] + ^^^^^^^^^^^^^^^^^^^ Enum defined as an array found in `status` enum declaration. Use hash syntax instead. + RUBY + end + end + + context 'with %w[] syntax' do + it 'registers an offense' do + expect_offense(<<~RUBY) + enum :status, %w[active archived] + ^^^^^^^^^^^^^^^^^^^ Enum defined as an array found in `status` enum declaration. Use hash syntax instead. + RUBY + end + end + + context 'with %i() syntax' do + it 'registers an offense' do + expect_offense(<<~RUBY) + enum :status, %i(active archived) + ^^^^^^^^^^^^^^^^^^^ Enum defined as an array found in `status` enum declaration. Use hash syntax instead. + RUBY + end + end + + context 'with %w() syntax' do + it 'registers an offense' do + expect_offense(<<~RUBY) + enum :status, %w(active archived) + ^^^^^^^^^^^^^^^^^^^ Enum defined as an array found in `status` enum declaration. Use hash syntax instead. + RUBY + end + end + + context 'with [] syntax' do + it 'registers an offense' do + expect_offense(<<~RUBY) + enum :status, [:active, :archived] + ^^^^^^^^^^^^^^^^^^^^ Enum defined as an array found in `status` enum declaration. Use hash syntax instead. + RUBY + end + end + + context 'when the enum name is a string' do + it 'registers an offense' do + expect_offense(<<~RUBY) + enum "status", %i[active archived] + ^^^^^^^^^^^^^^^^^^^ Enum defined as an array found in `status` enum declaration. Use hash syntax instead. + RUBY + end + end + + context 'when the enum name is not a literal' do + it 'registers an offense' do + expect_offense(<<~RUBY) + enum KEY, %i[active archived] + ^^^^^^^^^^^^^^^^^^^ Enum defined as an array found in `KEY` enum declaration. Use hash syntax instead. + RUBY + end + end + + it 'autocorrects' do expect_offense(<<~RUBY) - enum status: %i[active archived] - ^^^^^^^^^^^^^^^^^^^ Enum defined as an array found in `status` enum declaration. Use hash syntax instead. + enum :status, [:old, :"very active", "is archived", 42] + ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ Enum defined as an array found in `status` enum declaration. Use hash syntax instead. + RUBY + + expect_correction(<<~RUBY) + enum :status, {:old => 0, :"very active" => 1, "is archived" => 2, 42 => 3} RUBY end - end - context 'with %w[] syntax' do - it 'registers an offense' do + it 'autocorrects constants' do expect_offense(<<~RUBY) - enum status: %w[active archived] - ^^^^^^^^^^^^^^^^^^^ Enum defined as an array found in `status` enum declaration. Use hash syntax instead. + enum :status, [OLD, ACTIVE] + ^^^^^^^^^^^^^ Enum defined as an array found in `status` enum declaration. Use hash syntax instead. + RUBY + + expect_correction(<<~RUBY) + enum :status, {OLD => 0, ACTIVE => 1} RUBY end - end - context 'with %i() syntax' do - it 'registers an offense' do + it 'autocorrects nested constants' do expect_offense(<<~RUBY) - enum status: %i(active archived) - ^^^^^^^^^^^^^^^^^^^ Enum defined as an array found in `status` enum declaration. Use hash syntax instead. + enum :status, [STATUS::OLD, STATUS::ACTIVE] + ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ Enum defined as an array found in `status` enum declaration. Use hash syntax instead. + RUBY + + expect_correction(<<~RUBY) + enum :status, {STATUS::OLD => 0, STATUS::ACTIVE => 1} RUBY end - end - context 'with %w() syntax' do - it 'registers an offense' do + it 'autocorrects %w[] syntax' do expect_offense(<<~RUBY) - enum status: %w(active archived) - ^^^^^^^^^^^^^^^^^^^ Enum defined as an array found in `status` enum declaration. Use hash syntax instead. + enum :status, %w[active archived] + ^^^^^^^^^^^^^^^^^^^ Enum defined as an array found in `status` enum declaration. Use hash syntax instead. + RUBY + + expect_correction(<<~RUBY) + enum :status, {"active" => 0, "archived" => 1} RUBY end - end - context 'with [] syntax' do - it 'registers an offense' do + it 'autocorrect %w() syntax' do expect_offense(<<~RUBY) - enum status: [:active, :archived] - ^^^^^^^^^^^^^^^^^^^^ Enum defined as an array found in `status` enum declaration. Use hash syntax instead. + enum :status, %w(active archived) + ^^^^^^^^^^^^^^^^^^^ Enum defined as an array found in `status` enum declaration. Use hash syntax instead. + RUBY + + expect_correction(<<~RUBY) + enum :status, {"active" => 0, "archived" => 1} RUBY end - end - context 'when the enum name is a string' do - it 'registers an offense' do + it 'autocorrect %i[] syntax' do expect_offense(<<~RUBY) - enum "status" => %i[active archived] - ^^^^^^^^^^^^^^^^^^^ Enum defined as an array found in `status` enum declaration. Use hash syntax instead. + enum :status, %i[active archived] + ^^^^^^^^^^^^^^^^^^^ Enum defined as an array found in `status` enum declaration. Use hash syntax instead. + RUBY + + expect_correction(<<~RUBY) + enum :status, {:active => 0, :archived => 1} RUBY end - end - context 'when the enum name is not a literal' do - it 'registers an offense' do + it 'autocorrect %i() syntax' do expect_offense(<<~RUBY) - enum KEY => %i[active archived] - ^^^^^^^^^^^^^^^^^^^ Enum defined as an array found in `KEY` enum declaration. Use hash syntax instead. + enum :status, %i(active archived) + ^^^^^^^^^^^^^^^^^^^ Enum defined as an array found in `status` enum declaration. Use hash syntax instead. + RUBY + + expect_correction(<<~RUBY) + enum :status, {:active => 0, :archived => 1} RUBY end end - context 'with multiple enum definition for a `enum` method call' do - it 'registers an offense' do - expect_offense(<<~RUBY) - enum status: %i[active archived], - ^^^^^^^^^^^^^^^^^^^ Enum defined as an array found in `status` enum declaration. Use hash syntax instead. - role: %i[owner member guest] - ^^^^^^^^^^^^^^^^^^^^^^ Enum defined as an array found in `role` enum declaration. Use hash syntax instead. - RUBY + context 'when hash syntax is used' do + it 'does not register an offense' do + expect_no_offenses('enum :status, { active: 0, archived: 1 }') end end + end - it 'autocorrects' do - expect_offense(<<~RUBY) - enum status: [:old, :"very active", "is archived", 42] - ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ Enum defined as an array found in `status` enum declaration. Use hash syntax instead. - RUBY + context 'when old syntax is used' do + context 'when array syntax is used' do + context 'with %i[] syntax' do + it 'registers an offense' do + expect_offense(<<~RUBY) + enum status: %i[active archived] + ^^^^^^^^^^^^^^^^^^^ Enum defined as an array found in `status` enum declaration. Use hash syntax instead. + RUBY + end + end - expect_correction(<<~RUBY) - enum status: {:old => 0, :"very active" => 1, "is archived" => 2, 42 => 3} - RUBY - end + context 'with %w[] syntax' do + it 'registers an offense' do + expect_offense(<<~RUBY) + enum status: %w[active archived] + ^^^^^^^^^^^^^^^^^^^ Enum defined as an array found in `status` enum declaration. Use hash syntax instead. + RUBY + end + end - it 'autocorrects constants' do - expect_offense(<<~RUBY) - enum status: [OLD, ACTIVE] - ^^^^^^^^^^^^^ Enum defined as an array found in `status` enum declaration. Use hash syntax instead. - RUBY + context 'with %i() syntax' do + it 'registers an offense' do + expect_offense(<<~RUBY) + enum status: %i(active archived) + ^^^^^^^^^^^^^^^^^^^ Enum defined as an array found in `status` enum declaration. Use hash syntax instead. + RUBY + end + end - expect_correction(<<~RUBY) - enum status: {OLD => 0, ACTIVE => 1} - RUBY - end + context 'with %w() syntax' do + it 'registers an offense' do + expect_offense(<<~RUBY) + enum status: %w(active archived) + ^^^^^^^^^^^^^^^^^^^ Enum defined as an array found in `status` enum declaration. Use hash syntax instead. + RUBY + end + end - it 'autocorrects nested constants' do - expect_offense(<<~RUBY) - enum status: [STATUS::OLD, STATUS::ACTIVE] - ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ Enum defined as an array found in `status` enum declaration. Use hash syntax instead. - RUBY + context 'with [] syntax' do + it 'registers an offense' do + expect_offense(<<~RUBY) + enum status: [:active, :archived] + ^^^^^^^^^^^^^^^^^^^^ Enum defined as an array found in `status` enum declaration. Use hash syntax instead. + RUBY + end + end - expect_correction(<<~RUBY) - enum status: {STATUS::OLD => 0, STATUS::ACTIVE => 1} - RUBY - end + context 'when the enum name is a string' do + it 'registers an offense' do + expect_offense(<<~RUBY) + enum "status" => %i[active archived] + ^^^^^^^^^^^^^^^^^^^ Enum defined as an array found in `status` enum declaration. Use hash syntax instead. + RUBY + end + end - it 'autocorrects %w[] syntax' do - expect_offense(<<~RUBY) - enum status: %w[active archived] - ^^^^^^^^^^^^^^^^^^^ Enum defined as an array found in `status` enum declaration. Use hash syntax instead. - RUBY + context 'when the enum name is not a literal' do + it 'registers an offense' do + expect_offense(<<~RUBY) + enum KEY => %i[active archived] + ^^^^^^^^^^^^^^^^^^^ Enum defined as an array found in `KEY` enum declaration. Use hash syntax instead. + RUBY + end + end - expect_correction(<<~RUBY) - enum status: {"active" => 0, "archived" => 1} - RUBY - end + context 'with multiple enum definition for a `enum` method call' do + it 'registers an offense' do + expect_offense(<<~RUBY) + enum status: %i[active archived], + ^^^^^^^^^^^^^^^^^^^ Enum defined as an array found in `status` enum declaration. Use hash syntax instead. + role: %i[owner member guest] + ^^^^^^^^^^^^^^^^^^^^^^ Enum defined as an array found in `role` enum declaration. Use hash syntax instead. + RUBY + end + end - it 'autocorrect %w() syntax' do - expect_offense(<<~RUBY) - enum status: %w(active archived) - ^^^^^^^^^^^^^^^^^^^ Enum defined as an array found in `status` enum declaration. Use hash syntax instead. - RUBY + it 'autocorrects' do + expect_offense(<<~RUBY) + enum status: [:old, :"very active", "is archived", 42] + ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ Enum defined as an array found in `status` enum declaration. Use hash syntax instead. + RUBY - expect_correction(<<~RUBY) - enum status: {"active" => 0, "archived" => 1} - RUBY - end + expect_correction(<<~RUBY) + enum status: {:old => 0, :"very active" => 1, "is archived" => 2, 42 => 3} + RUBY + end + + it 'autocorrects constants' do + expect_offense(<<~RUBY) + enum status: [OLD, ACTIVE] + ^^^^^^^^^^^^^ Enum defined as an array found in `status` enum declaration. Use hash syntax instead. + RUBY - it 'autocorrect %i[] syntax' do - expect_offense(<<~RUBY) - enum status: %i[active archived] - ^^^^^^^^^^^^^^^^^^^ Enum defined as an array found in `status` enum declaration. Use hash syntax instead. - RUBY + expect_correction(<<~RUBY) + enum status: {OLD => 0, ACTIVE => 1} + RUBY + end - expect_correction(<<~RUBY) - enum status: {:active => 0, :archived => 1} - RUBY - end + it 'autocorrects nested constants' do + expect_offense(<<~RUBY) + enum status: [STATUS::OLD, STATUS::ACTIVE] + ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ Enum defined as an array found in `status` enum declaration. Use hash syntax instead. + RUBY + + expect_correction(<<~RUBY) + enum status: {STATUS::OLD => 0, STATUS::ACTIVE => 1} + RUBY + end - it 'autocorrect %i() syntax' do - expect_offense(<<~RUBY) - enum status: %i(active archived) - ^^^^^^^^^^^^^^^^^^^ Enum defined as an array found in `status` enum declaration. Use hash syntax instead. - RUBY + it 'autocorrects %w[] syntax' do + expect_offense(<<~RUBY) + enum status: %w[active archived] + ^^^^^^^^^^^^^^^^^^^ Enum defined as an array found in `status` enum declaration. Use hash syntax instead. + RUBY - expect_correction(<<~RUBY) - enum status: {:active => 0, :archived => 1} - RUBY + expect_correction(<<~RUBY) + enum status: {"active" => 0, "archived" => 1} + RUBY + end + + it 'autocorrect %w() syntax' do + expect_offense(<<~RUBY) + enum status: %w(active archived) + ^^^^^^^^^^^^^^^^^^^ Enum defined as an array found in `status` enum declaration. Use hash syntax instead. + RUBY + + expect_correction(<<~RUBY) + enum status: {"active" => 0, "archived" => 1} + RUBY + end + + it 'autocorrect %i[] syntax' do + expect_offense(<<~RUBY) + enum status: %i[active archived] + ^^^^^^^^^^^^^^^^^^^ Enum defined as an array found in `status` enum declaration. Use hash syntax instead. + RUBY + + expect_correction(<<~RUBY) + enum status: {:active => 0, :archived => 1} + RUBY + end + + it 'autocorrect %i() syntax' do + expect_offense(<<~RUBY) + enum status: %i(active archived) + ^^^^^^^^^^^^^^^^^^^ Enum defined as an array found in `status` enum declaration. Use hash syntax instead. + RUBY + + expect_correction(<<~RUBY) + enum status: {:active => 0, :archived => 1} + RUBY + end end - end - context 'when hash syntax is used' do - it 'does not register an offense' do - expect_no_offenses('enum status: { active: 0, archived: 1 }') + context 'when hash syntax is used' do + it 'does not register an offense' do + expect_no_offenses('enum status: { active: 0, archived: 1 }') + end end end end