Welcome to mirror list, hosted at ThFree Co, Russian Federation.

gitlab.com/gitlab-org/gitlab-foss.git - Unnamed repository; edit this file 'description' to name the repository.
summaryrefslogtreecommitdiff
diff options
context:
space:
mode:
Diffstat (limited to 'rubocop/cop/rspec/factory_bot/inline_association.rb')
-rw-r--r--rubocop/cop/rspec/factory_bot/inline_association.rb109
1 files changed, 109 insertions, 0 deletions
diff --git a/rubocop/cop/rspec/factory_bot/inline_association.rb b/rubocop/cop/rspec/factory_bot/inline_association.rb
new file mode 100644
index 00000000000..1c2b8b55b46
--- /dev/null
+++ b/rubocop/cop/rspec/factory_bot/inline_association.rb
@@ -0,0 +1,109 @@
+# frozen_string_literal: true
+
+module RuboCop
+ module Cop
+ module RSpec
+ module FactoryBot
+ # This cop encourages the use of inline associations in FactoryBot.
+ # The explicit use of `create` and `build` is discouraged.
+ #
+ # See https://github.com/thoughtbot/factory_bot/blob/master/GETTING_STARTED.md#inline-definition
+ #
+ # @example
+ #
+ # Context:
+ #
+ # Factory.define do
+ # factory :project, class: 'Project'
+ # # EXAMPLE below
+ # end
+ # end
+ #
+ # # bad
+ # creator { create(:user) }
+ # creator { create(:user, :admin) }
+ # creator { build(:user) }
+ # creator { FactoryBot.build(:user) }
+ # creator { ::FactoryBot.build(:user) }
+ # add_attribute(:creator) { build(:user) }
+ #
+ # # good
+ # creator { association(:user) }
+ # creator { association(:user, :admin) }
+ # add_attribute(:creator) { association(:user) }
+ #
+ # # Accepted
+ # after(:build) do |instance|
+ # instance.creator = create(:user)
+ # end
+ #
+ # initialize_with do
+ # create(:project)
+ # end
+ #
+ # creator_id { create(:user).id }
+ #
+ class InlineAssociation < RuboCop::Cop::Cop
+ MSG = 'Prefer inline `association` over `%{type}`. ' \
+ 'See https://docs.gitlab.com/ee/development/testing_guide/best_practices.html#factories'
+
+ REPLACEMENT = 'association'
+
+ def_node_matcher :create_or_build, <<~PATTERN
+ (
+ send
+ ${ nil? (const { nil? (cbase) } :FactoryBot) }
+ ${ :create :build }
+ (sym _)
+ ...
+ )
+ PATTERN
+
+ def_node_matcher :association_definition, <<~PATTERN
+ (block
+ {
+ (send nil? $_)
+ (send nil? :add_attribute (sym $_))
+ }
+ ...
+ )
+ PATTERN
+
+ def_node_matcher :chained_call?, <<~PATTERN
+ (send _ _)
+ PATTERN
+
+ SKIP_NAMES = %i[initialize_with].to_set.freeze
+
+ def on_send(node)
+ _receiver, type = create_or_build(node)
+ return unless type
+ return if chained_call?(node.parent)
+ return unless inside_assocation_definition?(node)
+
+ add_offense(node, message: format(MSG, type: type))
+ end
+
+ def autocorrect(node)
+ lambda do |corrector|
+ receiver, type = create_or_build(node)
+ receiver = "#{receiver.source}." if receiver
+ expression = "#{receiver}#{type}"
+ replacement = node.source.sub(expression, REPLACEMENT)
+ corrector.replace(node.source_range, replacement)
+ end
+ end
+
+ private
+
+ def inside_assocation_definition?(node)
+ node.each_ancestor(:block).any? do |parent|
+ name = association_definition(parent)
+ name && !SKIP_NAMES.include?(name)
+ end
+ end
+ end
+ end
+ end
+ end
+end