From b2b372408be88c6e2e2b23f16804057f65394074 Mon Sep 17 00:00:00 2001 From: ytjmt <46666464+ytjmt@users.noreply.github.com> Date: Sun, 16 Jun 2024 17:52:49 +0900 Subject: [PATCH] Support Rails 7 syntax for Rails/EnumUniqueness cop --- ..._7_syntax_for_rails_enum_uniqueness_cop.md | 1 + lib/rubocop/cop/rails/enum_uniqueness.rb | 36 ++- .../rubocop/cop/rails/enum_uniqueness_spec.rb | 296 +++++++++++++----- 3 files changed, 255 insertions(+), 78 deletions(-) create mode 100644 changelog/new_support_rails_7_syntax_for_rails_enum_uniqueness_cop.md diff --git a/changelog/new_support_rails_7_syntax_for_rails_enum_uniqueness_cop.md b/changelog/new_support_rails_7_syntax_for_rails_enum_uniqueness_cop.md new file mode 100644 index 0000000000..a1707d6c81 --- /dev/null +++ b/changelog/new_support_rails_7_syntax_for_rails_enum_uniqueness_cop.md @@ -0,0 +1 @@ +* [#1298](https://github.com/rubocop/rubocop-rails/pull/1298): Support Rails 7 syntax for `Rails/EnumUniqueness` cop. ([@ytjmt][]) diff --git a/lib/rubocop/cop/rails/enum_uniqueness.rb b/lib/rubocop/cop/rails/enum_uniqueness.rb index dfb49502d1..2885315afe 100644 --- a/lib/rubocop/cop/rails/enum_uniqueness.rb +++ b/lib/rubocop/cop/rails/enum_uniqueness.rb @@ -7,6 +7,18 @@ module Rails # # @example # # bad + # enum :status, { active: 0, archived: 0 } + # + # # good + # enum :status, { active: 0, archived: 1 } + # + # # bad + # enum :status, [:active, :archived, :active] + # + # # good + # enum :status, [:active, :archived] + # + # # bad # enum status: { active: 0, archived: 0 } # # # good @@ -24,6 +36,10 @@ class EnumUniqueness < Base RESTRICT_ON_SEND = %i[enum].freeze def_node_matcher :enum?, <<~PATTERN + (send nil? :enum $_ ${array hash} ...) + PATTERN + + def_node_matcher :enum_with_old_syntax?, <<~PATTERN (send nil? :enum (hash $...)) PATTERN @@ -32,15 +48,17 @@ class EnumUniqueness < Base PATTERN def on_send(node) - enum?(node) do |pairs| + enum?(node) do |key, args| + consecutive_duplicates(args.values).each do |item| + add_offense(item, message: message(key, item)) + end + end + + enum_with_old_syntax?(node) do |pairs| pairs.each do |pair| enum_values(pair) do |key, args| - items = args.values - - next unless duplicates?(items) - - consecutive_duplicates(items).each do |item| - add_offense(item, message: format(MSG, value: item.source, enum: enum_name(key))) + consecutive_duplicates(args.values).each do |item| + add_offense(item, message: message(key, item)) end end end @@ -57,6 +75,10 @@ def enum_name(key) key.source end end + + def message(key, item) + format(MSG, value: item.source, enum: enum_name(key)) + end end end end diff --git a/spec/rubocop/cop/rails/enum_uniqueness_spec.rb b/spec/rubocop/cop/rails/enum_uniqueness_spec.rb index 821504016e..5e2258af45 100644 --- a/spec/rubocop/cop/rails/enum_uniqueness_spec.rb +++ b/spec/rubocop/cop/rails/enum_uniqueness_spec.rb @@ -1,115 +1,269 @@ # frozen_string_literal: true RSpec.describe RuboCop::Cop::Rails::EnumUniqueness, :config do - context 'when array syntax is used' do - context 'with a single duplicated enum value' do - it 'registers an offense' do - expect_offense(<<~RUBY) - enum status: [:active, :archived, :active] - ^^^^^^^ Duplicate value `:active` found in `status` enum declaration. - RUBY + context 'when Rails 7 syntax is used' do + context 'when array syntax is used' do + context 'without options' do + context 'with a single duplicated enum value' do + it 'registers an offense' do + expect_offense(<<~RUBY) + enum :status, [:active, :archived, :active] + ^^^^^^^ Duplicate value `:active` found in `status` enum declaration. + RUBY + end + end + + context 'with several duplicated enum values' do + it 'registers two offenses' do + expect_offense(<<~RUBY) + enum :status, [:active, :archived, :active, :active] + ^^^^^^^ Duplicate value `:active` found in `status` enum declaration. + ^^^^^^^ Duplicate value `:active` found in `status` enum declaration. + RUBY + end + end + + context 'with no duplicated enum values' do + it 'does not register an offense for unique enum values' do + expect_no_offenses('enum :status, [:active, :archived]') + end + end + end + + context 'with options' do + context 'with a single duplicated enum value' do + it 'registers an offense' do + expect_offense(<<~RUBY) + enum :status, [:active, :archived, :active], default: :active, prefix: true + ^^^^^^^ Duplicate value `:active` found in `status` enum declaration. + RUBY + end + end + + context 'with several duplicated enum values' do + it 'registers two offenses' do + expect_offense(<<~RUBY) + enum :status, [:active, :archived, :active, :active], default: :active, prefix: true + ^^^^^^^ Duplicate value `:active` found in `status` enum declaration. + ^^^^^^^ Duplicate value `:active` found in `status` enum declaration. + RUBY + end + end + + context 'with no duplicated enum values' do + it 'does not register an offense for unique enum values' do + expect_no_offenses('enum :status, [:active, :archived], default: :active, prefix: true') + end + end end end - context 'with several duplicated enum values' do - it 'registers two offenses' do - expect_offense(<<~RUBY) - enum status: [:active, :archived, :active, :active] - ^^^^^^^ Duplicate value `:active` found in `status` enum declaration. - ^^^^^^^ Duplicate value `:active` found in `status` enum declaration. - RUBY + context 'when hash syntax is used' do + context 'without options' do + context 'with a single duplicated enum value' do + it 'registers an offense' do + expect_offense(<<~RUBY) + enum :status, { active: 0, archived: 0 } + ^ Duplicate value `0` found in `status` enum declaration. + RUBY + end + end + + context 'with several duplicated enum values' do + it 'registers two offenses' do + expect_offense(<<~RUBY) + enum :status, { active: 0, pending: 0, archived: 0 } + ^ Duplicate value `0` found in `status` enum declaration. + ^ Duplicate value `0` found in `status` enum declaration. + RUBY + end + end + + context 'with no duplicated enum values' do + it 'does not register an offense' do + expect_no_offenses('enum status: { active: 0, pending: 1 }') + end + end + end + + context 'with options' do + context 'with a single duplicated enum value' do + it 'registers an offense' do + expect_offense(<<~RUBY) + enum :status, { active: 0, archived: 0 }, default: :active, prefix: true + ^ Duplicate value `0` found in `status` enum declaration. + RUBY + end + end + + context 'with several duplicated enum values' do + it 'registers two offenses' do + expect_offense(<<~RUBY) + enum :status, { active: 0, pending: 0, archived: 0 }, default: :active, prefix: true + ^ Duplicate value `0` found in `status` enum declaration. + ^ Duplicate value `0` found in `status` enum declaration. + RUBY + end + end + + context 'with no duplicated enum values' do + it 'does not register an offense' do + expect_no_offenses('enum status: { active: 0, pending: 1 }, default: :active, prefix: true') + end + end end end - context 'with no duplicated enum values' do - it 'does not register an offense for unique enum values' do - expect_no_offenses('enum status: [:active, :archived]') + context 'when receiving a hash without literal values' do + context 'when value is a variable' do + it 'does not register an offense' do + expect_no_offenses('enum :status, statuses') + end + end + + context 'when value is a method chain' do + it 'does not register an offense' do + expect_no_offenses('enum :status, User.statuses.keys') + end + end + + context 'when value is a constant' do + it 'does not register an offense' do + expect_no_offenses('enum :status, STATUSES') + end end end - end - context 'when hash syntax is used' do - context 'with a single duplicated enum value' do + context 'when the enum name is a string' do it 'registers an offense' do expect_offense(<<~RUBY) - enum status: { active: 0, archived: 0 } - ^ Duplicate value `0` found in `status` enum declaration. + enum "status", [:active, :archived, :active] + ^^^^^^^ Duplicate value `:active` found in `status` enum declaration. RUBY end end - context 'with several duplicated enum values' do - it 'registers two offenses' do + context 'when the enum name is not a literal' do + it 'registers an offense' do expect_offense(<<~RUBY) - enum status: { active: 0, pending: 0, archived: 0 } - ^ Duplicate value `0` found in `status` enum declaration. - ^ Duplicate value `0` found in `status` enum declaration. + enum KEY, [:active, :archived, :active] + ^^^^^^^ Duplicate value `:active` found in `KEY` enum declaration. RUBY end end + end - context 'with no duplicated enum values' do - it 'does not register an offense' do - expect_no_offenses('enum status: { active: 0, pending: 1 }') + context 'when old syntax is used' do + context 'when array syntax is used' do + context 'with a single duplicated enum value' do + it 'registers an offense' do + expect_offense(<<~RUBY) + enum status: [:active, :archived, :active] + ^^^^^^^ Duplicate value `:active` found in `status` enum declaration. + RUBY + end + end + + context 'with several duplicated enum values' do + it 'registers two offenses' do + expect_offense(<<~RUBY) + enum status: [:active, :archived, :active, :active] + ^^^^^^^ Duplicate value `:active` found in `status` enum declaration. + ^^^^^^^ Duplicate value `:active` found in `status` enum declaration. + RUBY + end end - end - end - context 'when receiving a variable' do - it 'does not register an offense' do - expect_no_offenses(<<~RUBY) - var = { status: { active: 0, archived: 1 } } - enum var - RUBY + context 'with no duplicated enum values' do + it 'does not register an offense for unique enum values' do + expect_no_offenses('enum status: [:active, :archived]') + end + end end - end - context 'when receiving a hash without literal values' do - context 'when value is a variable' do - it 'does not register an offense' do - expect_no_offenses('enum status: statuses') + context 'when hash syntax is used' do + context 'with a single duplicated enum value' do + it 'registers an offense' do + expect_offense(<<~RUBY) + enum status: { active: 0, archived: 0 } + ^ Duplicate value `0` found in `status` enum declaration. + RUBY + end + end + + context 'with several duplicated enum values' do + it 'registers two offenses' do + expect_offense(<<~RUBY) + enum status: { active: 0, pending: 0, archived: 0 } + ^ Duplicate value `0` found in `status` enum declaration. + ^ Duplicate value `0` found in `status` enum declaration. + RUBY + end + end + + context 'with no duplicated enum values' do + it 'does not register an offense' do + expect_no_offenses('enum status: { active: 0, pending: 1 }') + end end end - context 'when value is a method chain' do + context 'when receiving a variable' do it 'does not register an offense' do - expect_no_offenses('enum status: User.statuses.keys') + expect_no_offenses(<<~RUBY) + var = { status: { active: 0, archived: 1 } } + enum var + RUBY end end - context 'when value is a constant' do - it 'does not register an offense' do - expect_no_offenses('enum status: STATUSES') + context 'when receiving a hash without literal values' do + context 'when value is a variable' do + it 'does not register an offense' do + expect_no_offenses('enum status: statuses') + end + end + + context 'when value is a method chain' do + it 'does not register an offense' do + expect_no_offenses('enum status: User.statuses.keys') + end + end + + context 'when value is a constant' do + it 'does not register an offense' do + expect_no_offenses('enum status: STATUSES') + end end end - end - context 'when the enum name is a string' do - it 'registers an offense' do - expect_offense(<<~RUBY) - enum "status" => [:active, :archived, :active] - ^^^^^^^ Duplicate value `:active` found in `status` enum declaration. - RUBY + context 'when the enum name is a string' do + it 'registers an offense' do + expect_offense(<<~RUBY) + enum "status" => [:active, :archived, :active] + ^^^^^^^ Duplicate value `:active` found in `status` enum declaration. + RUBY + end end - end - context 'when the enum name is not a literal' do - it 'registers an offense' do - expect_offense(<<~RUBY) - enum KEY => [:active, :archived, :active] - ^^^^^^^ Duplicate value `:active` found in `KEY` enum declaration. - RUBY + context 'when the enum name is not a literal' do + it 'registers an offense' do + expect_offense(<<~RUBY) + enum KEY => [:active, :archived, :active] + ^^^^^^^ Duplicate value `:active` found in `KEY` enum declaration. + RUBY + end end - end - context 'with multiple enum definition for a `enum` method call' do - it 'registers an offense' do - expect_offense(<<~RUBY) - enum status: [:active, :archived, :active], - ^^^^^^^ Duplicate value `:active` found in `status` enum declaration. - role: [:owner, :member, :guest, :member] - ^^^^^^^ Duplicate value `:member` found in `role` enum declaration. - RUBY + context 'with multiple enum definition for a `enum` method call' do + it 'registers an offense' do + expect_offense(<<~RUBY) + enum status: [:active, :archived, :active], + ^^^^^^^ Duplicate value `:active` found in `status` enum declaration. + role: [:owner, :member, :guest, :member] + ^^^^^^^ Duplicate value `:member` found in `role` enum declaration. + RUBY + end end end end