diff --git a/CHANGELOG.md b/CHANGELOG.md index 810a2b9a..b18102f5 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -5,6 +5,7 @@ - Fix `FactoryBot/AssociationStyle` cop to ignore explicit associations with `strategy: :build`. ([@pirj]) - Change `FactoryBot/CreateList` so that it is not an offense if not repeated multiple times. ([@ydah]) - Fix a false positive for `FactoryBot/AssociationStyle` when `association` is called in trait block and column name is keyword. ([@ydah]) +- Fix a false positive for `FactoryBot/AssociationStyle` when `EnforcedStyle: Explicit` and using trait within trait. ([@ydah]) ## 2.23.1 (2023-05-15) diff --git a/lib/rubocop/cop/factory_bot/association_style.rb b/lib/rubocop/cop/factory_bot/association_style.rb index bf2f116e..5ccd2441 100644 --- a/lib/rubocop/cop/factory_bot/association_style.rb +++ b/lib/rubocop/cop/factory_bot/association_style.rb @@ -140,6 +140,11 @@ def on_send(node) (send nil? :association $...) PATTERN + # @!method trait_name(node) + def_node_search :trait_name, <<~PATTERN + (send nil? :trait (sym $_) ) + PATTERN + def autocorrect(corrector, node) if style == :explicit autocorrect_to_explicit_style(corrector, node) @@ -168,7 +173,8 @@ def autocorrect_to_implicit_style(corrector, node) def bad?(node) if style == :explicit - implicit_association?(node) + implicit_association?(node) && + !trait_within_trait?(node) else explicit_association?(node) && !with_strategy_build_option?(node) && @@ -239,6 +245,14 @@ def options_for_autocorrect_to_implicit_style(node) end options end + + def trait_within_trait?(node) + factory_node = node.ancestors.reverse.find do |ancestor| + ancestor.send_node.method?(:factory) if ancestor.block_type? + end + + trait_name(factory_node).include?(node.method_name) + end end end end diff --git a/spec/rubocop/cop/factory_bot/association_style_spec.rb b/spec/rubocop/cop/factory_bot/association_style_spec.rb index d4c23105..01afc883 100644 --- a/spec/rubocop/cop/factory_bot/association_style_spec.rb +++ b/spec/rubocop/cop/factory_bot/association_style_spec.rb @@ -334,5 +334,76 @@ def inspected_source_filename RUBY end end + + context 'when using trait within trait' do + it 'does not register an offense' do + expect_no_offenses(<<~RUBY) + factory :order do + trait :completed do + completed_at { 3.days.ago } + end + + trait :refunded do + completed + refunded_at { 1.day.ago } + end + end + RUBY + end + end + + context 'when factory inside a factory' do + it 'does not register an offense' do + expect_no_offenses(<<~RUBY) + factory :order do + trait :completed do + completed_at { 3.days.ago } + end + + factory :order_with_refund do + trait :refunded do + completed + refunded_at { 1.day.ago } + end + end + end + RUBY + end + end + + context 'when use an association with the same name as trait' do + it 'registers and corrects an offense' do + expect_offense(<<~RUBY) + factory :order do + trait :completed do + completed_at { 3.days.ago } + end + end + + factory :order_with_refund do + trait :refunded do + completed + ^^^^^^^^^ Use explicit style to define associations. + refunded_at { 1.day.ago } + end + end + RUBY + + expect_correction(<<~RUBY) + factory :order do + trait :completed do + completed_at { 3.days.ago } + end + end + + factory :order_with_refund do + trait :refunded do + association :completed + refunded_at { 1.day.ago } + end + end + RUBY + end + end end end